图的生成树算法

代码来自《数据结构与算法Python语言实现》裘宗燕著

# 图的生成树

#----------图的邻接矩阵实现--------------------
class Graph:

    def __init__(self,mat,unconn = 0):
        # 顶点个数
        vnum = len(mat)
        # 检查是否为方阵
        for x in mat:
            if len(x) != vnum:
                raise ValueError("Argument for 'Graph'.")
        
        # 拷贝
        self._mat = [mat[i][:] for i in range(vnum)]
        self._unconn = unconn
        self._vnum = vnum

    # 返回顶点个数
    def vertex_num(self):
        return self._vnum

    # 验证序号v是否有效
    def _invalid(self,v):
        return 0>v or v>=self._vnum    
                
    # 添加顶点
    def add_vertex(self):
        raise ValueError("Adj-Matrix does not support 'add_vertex'.")

    # 添加边,即添加两个点之间的关联线段
    def add_edge(self,vi,vj,val=1):
        # 先检查vi和vj是否有效
        if self._invalid(vi) or self._invalid(vj):
            raise ValueError(str(vi) + 'or' + str(vj) + 'is not a valid vertex.')
        # 为线段附加权重
        self._mat[vi][vj] = val

    # 获取边的权重
    def get_edge(self,vi,vj):
        if self._invalid(vi) or self._invalid(vj):
            raise ValueError(str(vi) + 'or' + str(vj) + 'is not valid vertex.')
        return self._mat[vi][vj]

    # 获取指定结点的所有相连边
    def out_edges(self,vi):
        if self._invalid(vi):
            raise ValueError(str(vi) + 'is not a valid vertex.')
        return self._out_edges(self._mat[vi],self._unconn)
    
    @staticmethod # 静态方法,无需实例化即可调用
    def _out_edges(row,unconn):
        edges = []
        for i in range(len(row)):
            if row[i] != unconn:
                edges.append((i,row[i]))
        return edges
    
    # 将矩阵转化为字符串模式
    def __str__(self):
        return "[\n" + ".\n".join(map(str,self._mat)) + "\n]" + "\nUnconnected:" + str(self._unconn)
#----------图的邻接矩阵实现--------------------



#----------图的压缩邻接矩阵实现-----------------
class GraphAL(Graph):
    # GraphAL继承自Graph    
    
    def __init__(self,mat = [],unconn = 0):
        vnum = len(mat)
        # 检查是否为方阵
        for x in mat:
            if len(x) != vnum:
                raise ValueError("Argument for 'Graph'.")

        # 只复制带有权重的边
        self._mat = [Graph._out_edges(mat[i],unconn) for i in range(vnum)]
        self._vnum = vnum
        self._unconn = unconn

    # 添加一个顶点
    def add_vertex(self):
        self._mat.append([])
        self._vnum += 1
        return self._vnum - 1

    # 添加一个边
    def add_edge(self,vi,vj,val=1):
        if self._vnum == 0:
            raise ValueError("Cannot add edge to an empty Graph!")
        if self._invalid(vi) or self._invalid(vj):
            raise ValueError(str(vi) + 'or' + str(vj) + 'is not valid vertex!')

        row = self._mat[vi]
        i = 0
        while i < len(row):
            # 寻找vj对应的权重
            if row[i][0] == vj:
                self._mat[vi][i] = [vj,val]
                return None 
            # 如果没有找到vj对应的值,说明vi和vj不相连
            # 则退出循环,添加vi与vj的关联边
            if row[i][0] > vj:
                break
            i += 1
        # 添加vi与vj的关联边
        self._mat[vi].insert(i,(vj,val))

    def get_edge(self,vi,vj):
        if self._invalid(vi) or self._invalid(vj):
            raise ValueError(str(vi) + 'or' + str(vj) + 'is not valid vertex!')
        
        # 寻找对应vi和vj的边的权重
        if i,val in self._mat[vi]:
            if i == vj:
                return val
        
        # 如果没有匹配到,则返回0
        return self._unconn
#----------图的压缩邻接矩阵实现-----------------



#----------栈的实现---------------------------
class Node:
    def __init__(self):
        self.val = val
        self.next = None

class SStack:
    def __init__(self):
        self.top = None

    # 获取栈顶的元素
    def peek(self):
        if self.top != None:
            return self.top.val
        else:
            return None

    # 将新元素压入到栈中
    def push(self,n):
        n = Node(n)
        n.next = self.top
        self.top = n
        return n.val

    # 退出栈
    def pop(self):
        if self.top == None:
            return None
        else:
            tmp = self.top.val
            self.top = self.top.next
            return tmp
#----------栈的实现---------------------------



#----------1. 非递归DFS遍历--------------------
def DFS_graph(graph,v0):
    # 顶点个数
    vnum = graph.vertex_num()
    # 为每个顶点打上未访问的标记
    visited = [0] * vnum
    # 遍历序列
    DFS_seq = [v0]
    # 栈、用于存储每个顶点的边信息
    st = SStack()
    st.push((0,graph.out_edges(v0))) # 压入与起始结点相连的边中的第一条

    while not st.is_empty():
        # 取出顶点序号i与边列表
        i,edges = st.pop()
        if i < len(edges):
            # v是顶点,e是边
            v,e = edges[i]
            
            # 压入与该顶点相连的下一个边
            # 待前一个边下面的所有结点都访问完了之后再访问
            st.push((i+1,edges))
            
            # 如果没有访问过这个顶点
            if not visited[v]:
                # 将顶点加入列表中
                DFS_seq.append(v)
                # 访问过之后将标记置为1
                visited[v] = 1

                # 将与搜索到的顶点相连的顶点压入栈中,以备下次访问,这个比163行的更先访问到
                st.push((0,graph.out_edges(v)))

    return DFS_seq
#----------1. 非递归DFS遍历--------------------



#---------2. DFS生成树-------------------------
def DFS_span_forest(graph):
    # 顶点个数
    vnum = graph.vertex_num()
    # 记录路径信息
    span_forest = [None] * vnum

    def dfs(graph,v):
        # 需要修改非局部变量,所以将之声明为nonlocal
        nonlocal span_forest
        # 遍历顶点v连接的所有路径,u是顶点,w是权重
        for u,w in graph.out_edges(v):
            # 如果这个顶点尚未加入span_forest
            if span_forest[u] is None:
                # 将结点u的上一结点和连接权重记录下来
                span_forest[u] = (v,w)
                dfs(graph,u)
    
    # 避免还有不连通的点,所以要遍历到每个顶点
    for v in range(vnum):
        if span_forest[v] is None:
            span_forest[v] = [v,0]
            dfs(graph,v)
    return span_forest            
#---------2. DFS生成树-------------------------



#---------3. Kruskal最小生成树-----------------
def Kruskal(graph):
    # 基本思路:设G = (V,E) 是一个网络,其中|V|(顶点数量)为n,则Kruskal的基本思路为:
    # (1)取包含G的所有n个顶点但是没有任何边的孤立点子图T = (V,{})
    #     T中的每个顶点自称一个连通分量,下面将通过不断扩充T的方式构造G的最小生成树;
    # (2)将边集E按照权值递增的顺序排序,在构造中的每一步按顺序地检查这个边序列
    #     找到最短的且两端位于T的两个不同的连通分量的边e,将e加入T,这样两个连通分量
    #     就在e的作用下连接成了一个连通分量;
    # (3)每次操作使T减少一个连通分量。不断重复这个动作,往T中加入新边,直到T中所有顶点
    #     都包含在一个连通分量为止,这个连通分量就是G的一棵最小生成树。

    # 获取顶点个数
    vnum = graph.vertex_num()
    # 将各顶点的代表元初始化,代表元代表了顶点所属的连通分量
    reps = [i for i in range(vnum)]
    # mst记录最小生成树的边,edges记录graph所有的边
    mst,edges = [],[]
    # 将所有的边加入edges
    for vi in range(vnum):
        for v,w in graph.out_edges(vi):
            edges.append((w,vi,v))
    # 按权重排列
    edges.sort()
    for w,vi,vj in edges:
        # 如果vi和vj属于两个不同的连通分量
        if reps[vi] != reps[vj]:
            mst.append(((vi,vj),w))
            
            # 如果有n-1条边代表已经构造完成
            if len(mst) == vnum - 1:
                break

            # 修改代表元,将所有与vj在同一连通分量的顶点归入vi所在的连通分量中            
            rep,orep = reps[vi],reps[vj]
            for i in range(vnum):
                if reps[i] == orep:
                    reps[i] = rep
    return mst
#---------3. Kruskal最小生成树-----------------

你可能感兴趣的:(图的生成树算法)