北航离散数学期末总结

用python语言描述图论中的概念及算法

判断子图算法

def  issubgraph(V,E,Vs,Es):
    tv=(Vs <= V) and (Es <= E) # 点集是子集,边集是子集
    return tv

判断真子图算法

def  ispropersubgraph(V,E,Vs,Es):
    # 首先要是一个子图,然后点集或者边集是真子集
    tv=((Vs <= V) and (Es <= E)) and ((Vs < V) or (Es < E))
    return tv

判断生成子图算法

def  isspanningsubgraph(V,E,Vs,Es):
    # 边集是子集,点集相同
    tv=((Vs == V) and (Es <= E))
    return tv

判断导出子图算法

def  isinducedsubgraph(V,E,Vs,Es):
    tv=((Vs <= V) and (Es <= E)) # 首先判断是否是一个子图
   	# 对于E中的边所关联的两个点,要么不是都在Vs中,如果都在Vs里,
    # 这条边就要在Es中出现
    for (u,v) in E:
        tv=tv and ((not((u in Vs) and (v in Vs))) or ((u,v) in Es))
    return tv

完全图构建算法

def completeset(n):
    V=set({})
    E=set({})
    # 两两顶点之间创建边
    for i in range(n):
        V=V | {i}
        for j in range(n):
            if(i < j):
                E=E | {(i,j)}
    return (V,E)

圈图构建算法

def cyclicset(n):
    V={0} # 取出一个顶点作为起始顶点
    E=set({})
    for k in range(1,n):
        V=V | {k}
        E=E | {(k-1,k)} # 每个顶点和下一个顶点连边
    E=E | {(n-1,0)} # 补上圈的最后一条边
    return (V,E)

轮图构建算法

def wheelgset(n):
    V={0,1} # 0作为内部的轮心
    E=set({})
    # 整体思路和圈图的是一样的
    # 只不过对于外面的圈上的每个顶点都增加一个和内部轮心
    # 相连的边
    for k in range(1,n):
        V=V | {k}
        E=E | {(0,k)}
        E=E | {(k-1,k)}
    E=E | {(n-1,1)}
    return (V,E)

二部图构建算法

def completebipartitegraph(n,m):
    V0=set({})
    V1=set({})
    # 添加V0中的n个节点
    for k in range(n):
        V0=V0 | {k}
    E=set({})
    # 添加V1中的m个节点
    for k in range(n,n+m):
        V1=V1 | {k}
    # 对于V0和V1中的每两个节点之间创建边
    for u in V0:
        for v in V1:
            E=E | {(u,v)}           
    return (V0,V1,E)

n立方体图构建算法

def ncubeset(m): # 在这里m是二进制数的位数,如m = 3
    V=set({})
    E=set({})
    n=2**m # 总共的顶点数,n = 8
    for i in range(n):
        V=V | {i}
        u=1 # u作为一个指标,n = 001, 010, 100
        k=0 # k作为一个遍历,用来使n里面的1二进制位遍历m个位置
        while(k < m):
            # 如果i里面u中1二进制位所对应的位是0,则将该位变成1
            # 如果i里面u中1二进制位所对应的位是1,则将该位变成0
            # 通过遍历,就可以使得每个顶点相邻的m个顶点与其都
            # 只有一个二进制位不同
            if(i & u == 0): 
                j=i | u
            else:         
                j=i - u
            E=E | {(i,j)}
            u=u*2
            k=k+1
    return (V,E)

0-1序列图构建算法

def sequence01set(m):
    V=set({})
    E=set({})
    n=2**m
    # 思路就是对于每个顶点,将其左移一位,然后在最低位填0或1
    # 这样就构造了两条边,而且其所关联的两个顶点满足条件
    for i in range(n):
        V=V | {i}
        j=(2*i)%n
        E=E | {(i,j)}
        j=(2*i + 1)%n
        E=E | {(i,j)}
    return (V,E)

通路算法

import datetime   

def pathset0(V,E,u0):
    # path1最后保存的是所有以u0为起始节点的边
    path1=set({})
    for (u,v) in E:
        if(u == u0):
            path1=path1 |{(u,v)}
    return path1

def  pathset(path0,E):
    path=set({})
    for p0 in path0:
        y=p0[-1] # 取通路的最后一个顶点
        for (u,v) in E:
            if u==v: # 如果是自环,忽略
                continue
            p=p0
            if (u==y): # 如果发现能将该通路扩充
                p=p+tuple([v]) # 将新的节点添加到当前路径中
                path=path |{p}
    return path

def  main(V,E,k):
    global path
    t0=datetime.datetime.now( )
    path=pathset0(V,E,1)
    for k in range(k): # k这个参数我认为是寻找长度为k的通路
        path=pathset(path,E)
    t1=datetime.datetime.now( )
    t=t1-t0
    return [t,len(path)]
	

基本通路算法

def  basicpathset(path0,E):
    path=set({})
    # 总体的思路是和pathset()一样的
    # 但是由于是基本通路,要保证每个顶点出现不超过1次
    for pk in path0:
        x=pk[-1]
        for (u,v) in E:
            p=pk
            if (u==x and v not in pk):
                p=p+tuple([v])
                path=path |{p}
            if(v==x and u not in pk): # 这里好像又考虑无向图了...
                # 知道思路就好,如果自己写也不会写成这样
                p=p+tuple([u])
                path=path |{p}
    return path

def basicpathnset(V,E,v0):
    pathk=pathv0(V,E,v0) # 这里的pathv0,我真不知道是什么....
    # 我在这里当作pathset0这个函数
    m=len(V)-2
    for k in range(m):
        n=len(pathk) # 我不知道这个n他要干啥
        pathk=basicpathset(pathk,E)
    return pathk

连通图的构建

def connectedgraph(V,E):
    for (u,v) in E: # 神奇操作
        break
    Vc=set({u,v})
    Ec=set({(u,v)})
    while E !=set({}):
        n=len(Ec) # 和后面的len(Ec) == n相对应,用来判断是都本次循环
        # 对Ec进行了修改,如果没有就说明已经构造完了,就break
        for (u,v) in E:
            # 这里就是不断地加点
            if (u,v) not in Ec and (u in Vc or v in Vc):
                Vc=Vc | {u,v}
                Ec=Ec | {(u,v)}
        if len(Ec) == n: 
            break
    return [Vc,Ec]

连通图判断

def  isconnectedgraph(V,E):
    # 就是判断这个图是不是只有一个连通分支
    # 所以先构造一个联通子图,然后看边集和E是不是相等
    [Vc,Ec]=connectedgraph(V,E)
    if set(Ec)==set(E):
        tv=True
    else:
        tv=False
    return tv

树的构建

def  graph2tree(V,E):
    for (u,v) in E: # 神奇操作
        break
    Vt={u,v}
    Et={(u,v)}
    n=len(V)
    while(len(Et) < (n-1)): # m = n - 1
        m=len(Vt) # 用来判断本次循环是否对Vt更改,如果没有,就直接返回
        # 避圈加边
        for (u,v) in E:
            if((u in Vt and v not in Vt)
               or (u not in Vt and v in Vt)):
                Vt=Vt | {u} | {v}
                Et=Et | {(u,v)}
        E=E-Et # 将已经加过的边从原来E中去掉
        if(m == len(Vt)):
            break
    return [Vt,Et]

树的判断

def  istree(V,E):
    for (u,v) in E:
        Vt={u,v}
    E=E-{(u,v)}
    tv=True
    while(E != set({})):
        for (u,v) in E:
            # 如果E中的边所关联的两个点在Vt中已经出现
        	# 则说明出现了圈,不为树
            if u in Vt and v in Vt:
                E=E-{(u,v)}
                tv=False
                break
            # 从E中去掉边,将点添加到Vt中
            if u in Vt and v not in Vt:
                Vt=Vt | {u} | {v}
                E=E-{(u,v)}
            if u not in Vt and v in Vt:
                Vt=Vt | {u} | {v}
                E=E-{(u,v)}
        if(tv == False):
            break
    # 判断是否连通
    if(len(Vt) != len(V)):
        tv=False
    return tv

二叉树构建算法

def createbitree(nodes):
    # []是树
    if(len(nodes) == 0):
        return [ ]
    # [a]是树
    if(len(nodes) == 1):
        return [nodes[0]]
    # 从当前结点集中任意挑出一个点作为父结点
    k=random.randint(0,len(nodes)-1)
    # 左儿子
    Lnodes=nodes[0:k]
    # 右儿子
    Rnodes=nodes[k+1:]
    a=nodes.pop(k)
    # 递归构造
    L=createbitree(Lnodes)
    R=createbitree(Rnodes)
    return [a,L,R]

前序遍历

# 树的形式[root, left, right]
# 和数据结构书上的差不多
# 中序和后序同理,在这里就不写了
def preordertraversal(tree):
    if(len(tree)==0):
        return []
    if(len(tree)==1):
        return [tree[0]]
    else:
        return [tree[0]]+preordertraversal(tree[1])+preordertraversal(tree[2])

霍夫曼编码

def main( ):
    global W,tree,gtree
    # W中的每个子列表第一项是权值,第二项是名字
    W=[[0.08,'a'],[0.10,'b'],[0.12,'c'],[0.15,'d'],[0.20,'e'],[0.35,'f']]    
    # tree的形式是[[1, [...], [...]]],省略号表示形式是递归的
    tree=Huffmantree(W)
    # 将tree的形式变成[1, [...], [...]]
    tree=tree[0]
    gtree=Huffmantree2graph(tree)
    drawgraph(gtree)
    return gtree

def Huffmantree(W):
    tree=sorted(W) # 首先对权值进行排序
    while len(tree) != 1:
        # 将权值最小的两个子树结合起来
        # 权值相加,权值小的作为左子树,权值大的作为右子树
        tree=[[tree[0][0]+tree[1][0],tree[0],tree[1]]]+tree[2:]
        # 重新进行排序
        tree=sorted(tree)
    return tree

def  Huffmantree2graph(tree):
    gtree=set({})
    if len(tree) == 2: # 如果是底层,例如(1.0, 0.38)这种形式,则直接返回
        return gtree
    L=Huffmantree2graph(tree[1]) # 递归左子树
    R=Huffmantree2graph(tree[2]) # 递归右子树
    a=math.floor(tree[0]*10000)/10000
    l=math.floor(tree[1][0]*10000)/10000
    r=math.floor(tree[2][0]*10000)/10000
    # 将当前结点的左右子树和左右子树的递归给添加到gtree
    gtree= {(a,l)} | {(a,r)}|L|R
    return gtree

def huffmancoding(subtree,code):
    # 和上一个函数思路是差不多的
    if len(subtree) == 2:
        return [[subtree[1],code]]
    else:
        node0=subtree[1]
        node1=subtree[2]
        # 如果是左子树,就加0,如果是右子树,就加1
        huffmancode=huffmancoding(node0,code+'0')+huffmancoding(node1,code+'1')
        return huffmancode

Prim和Kruskal以及Dijiskra和Floyd略

欧拉图构建方法

# 其实总体的思路就是树上欧拉图构建的证明
# 如果书上理解了,这里就会方便很多
def  subEulerCircuit(v0,E):
    # 这个函数起始就是从v0开始找圈
    circuit=tuple([v0])
    S=set({})
    while (circuit[0]!=circuit[-1] or len(circuit) ==1):
        y=circuit[-1]
        for (u,v) in E:
            if(u==y or v==y):
                if (u==y):
                    circuit=circuit+tuple([v])
                else:
                    circuit=circuit+tuple([u])
                E=E-{(u,v)}
                S=S|{(u,v)}
                break
    return [circuit,S]

def EulerCircuit(v0,E):
    # 首先找到一个圈
    [circuit,S]=subEulerCircuit(v0,E)
    # 将E中在这个圈里的边去掉
    E=E-S
    while (E != set({})):
        V1=set2V(E)
        V2=set2V(S)
        V1V2=V1&V2 # 找到E和S的公共点
        for v0 in V1V2:
            # 以公共点找圈
            [subcircuit,S]=subEulerCircuit(v0,E)
            k=circuit.index(v0)
            # 将两个圈合成一个
            circuit=circuit[0:k]+subcircuit+circuit[k+1:-1]+tuple([circuit[-1]])
            E=E-S
            break
    return circuit

def set2V(E): # 将边集转换成顶点集
    V=set({})
    for (u,v) in E:
        V=V|{u,v}
    return V

哈密尔顿周游算法

# 整体算法ppt上写的很详细
# 在这里只是将算法拆分到具体的代码上
def tourpath0(V,E,path,m):
    while(len(path) < m):
        w=path[-1] # 取出路径最后一个点
        i=V.index(w)
        E1=E[i]
        E2=E1[1] # 找到这个点的邻接点集
        for u in E2: # 遍历邻接点集
            if(u not in path): # 如果不在当前路径,就添加
                path.append(u)
                break
        if(path[-1] == w): # 如果本次循环没有添加任何结点,就退出
            break
    return path

def  tourpath1(V,E,path,m):
    v=path.pop( ) # 取路径path的末尾顶点v=path.pop( )为v
    while(len(path) != 0): # 若 path非空
        u=path[-1] # 取路径path的末尾顶点path[-1]为u
        i=V.index(u)
        E1=E[i]
        E2=E1[1]
        k=E2.index(v) # 从顶点u的邻接表从顶点v以后依次检查k=E2.index(v)
        while(k < (len(E2)-1)):
            k=k+1
            v=E2[k]
            if(v not in path): # 若v=E2[k]不在path,则v为path的新末尾顶点,直至邻接表结束
                path.append(v)
                break
        if(u != path[-1]): #若path有新顶点,则结束,否则,path弹出末尾顶点。
            break
        v=path.pop( )
    return path

def  tourpath(V,E,path,m):
    if(len(path) == m): # 若len(path) == m,则求新的周游通路,即tourpath1(V,E,path,m)
        path=tourpath1(V,E,path,m)
    while(len(path) != 0): # 若len(path) != 0,依次迭代执行tourpath0与tourpath1
        path=tourpath0(V,E,path,m)
        if(len(path) == m):
            break
        path=tourpath1(V,E,path,m)        
    return path

# 这个是构建邻接表
def adjacentlist(V,E):
    Ea=[]
    for w in V:
        e0=[w]
        e1=[]
        for (u,v) in E:
            if u == w and (v not in e1):
                e1=e1+[v]
            if v==w and (u not in e1):
                e1=e1+[u]
        e0=e0+[sorted(e1)]
        Ea=Ea+[e0]
    return sorted(Ea)

关键路径算法

# di0中存的是入度为0的结点集
def stepu0v(di0,E):
    S=set({})
    for u0 in di0: # 对于每个入度为0的结点
        # S添加E中所有以u0为起始结点的边集
        for (w,u,v) in E:
            if u == u0:
                S=S|{(w,u,v)}
    # 最后S中包含的是所有以di0中结点为起始结点的边集
    return S

# do0中存的是出度为0的结点集
def stepuv0(do0,E):
    S=set({})
    for v0 in do0:
        for (w,u,v) in E:
            if v == v0:
                S=S|{(w,u,v)}
    # 最后S中包含的是所有以di0中结点为终止结点的边集
    return S

def craticalTE(V,E,di,v0,vn):
    Hx= [0]*len(V) # 初始化所有的最早完成时间都是0
    
    di0={v0} # 以v0作为初始入度为0的结点
    while vn not in di0:
        S=stepu0v(di0,E) # 得到以di0中结点为起始结点的边集
        [Hx,di,di0]=TEpath(S,Hx,di)
        E=E-S
    return Hx

# 与TE同理
def craticalTL(V,E,do,Hn,v0,vn):
    Hy= [1000]*len(V)
    Hy[len(V)-1]=Hn   
    do0={vn}
    while v0 not in do0:
        S=stepuv0(do0,E)
        [Hy,do,do0]=TLpath(S,Hy,do)
        E=E-S
    return Hy

def TEpath(S,Hx,di):
    # 对S中的每一条边,更新最早完成时间的值
    di0=set({})
    for (w,u,v) in S:
        if Hx[v] < Hx[u]+w: # 更新
            Hx[v]=Hx[u]+w
        if di[v]>0: # 如果v的入度大于0,则减1
            di[v]=di[v]-1
        if di[v] == 0: # 将入度为0的点添加到di0中
            di0=di0 | {v}
    return [Hx,di,di0]

def TLpath(S,Hy,do):
    do0=set({})
    for (w,u,v) in S:
        if Hy[u] > Hy[v]-w:
            Hy[u]=Hy[v]-w
        if do[u]>0:
            do[u]=do[u]-1
        if do[u] == 0:
            do0=do0 | {u}
    return [Hy,do,do0]

def craticalpath(V,E,di,do,v0,vn):
    Hx=craticalTE(V,E,di,v0,vn)
    Hn=Hx[len(V)-1]
    Hy=craticalTL(V,E,do,Hn,v0,vn)
    V=list(V)
    N=len(V)
    C=set({})
    for k in range(N):
        if Hx[k] == Hy[k]:
            C=C|{V[k]}
    W=set({})
    for (w,u,v) in E:
        if u in C and v in C:
            W=W|{(u,v,w)}    
    return W

def main( ):
    global V0,E0,V,E,path
    V0={0,1,2,3,4,5} # 顶点集
    E0={(0,1),(0,2),(0,3),(1,2),(1,4),(2,3),(2,5),(3,4),(3,5),(4,5)} # 边集
    E={(1,0,1),(3,0,2),(5,0,3),(1,1,2),(2,1,4),(1,2,3),(2,2,5),(1,3,4),(3,3,5),(2,4,5)} # 带权的边集
    drawdigraph(E0)
    [d,di,do]=degreeset(V0,E) # 我理解这个函数的意思是,对每一个顶点算出度、入度、出度
    						  # 保存在三个容器中
    v0=0 # 起始结点
    vn=5 # 终止结点
    W=craticalpath(V0,E,di,do,v0,vn)
    S=graphw2graph(W)
    drawdigraph(S)
    return W

二分图判断

def  isbigraph(V,E,V1,V2):       
    tv=(V==V1|V2) # V是V1和V2的并
    tv=tv&(V1&V2==set({})) # V1交V2是空集
    for (u,v) in E: # E中的每条边都是一个点在V1一个点在V2
        tv=tv&(u in V1)&(v in V2)
        return tv

你可能感兴趣的:(北航课程期末总结)