基于Python的数据结构实验——图的最小生成树(图的数组表示,Kruskal与Prim算法)(附详细代码和注释)

  • Post author:
  • Post category:python


创建名为 prac08.py 的文件,在文件中定义两个类,一个是顶点的结点类,另一个 是图类,该类包含存储连通网的邻接矩阵、广度优先遍历图的方法和求连通网的最小生成树 的方法。请按以下步骤实现连通网的最小生成树算法。

  1. 创建一个下图所示的无向网,并使用数组表示法存储它。

  1. 广度优先遍历该网,判断该网是否连通。

  1. 使用 Prim 算法构造该网的最小生成树,并将构造过程输出。

  1. 使用 Kruskal 算法构造该网的最小生成树,并将构造过程输出。

  1. 比较3和4中构造最小生成树的过程,深入理解 Prim 算法和 Kruskal 算法构造连 通网的最小生成树的差异。


(注:本文所使用的图的写入方法是根据连接作图,也就是无需提前构建节点,直接写连接,涉及到的节点将会自动导入,如上图可表示为:A1B,A3E,A1D等边,用边描述图)

class Vertex(object):  # 节点类
    def __init__(self, data):
        self.data = data  # 节点的data属性
        
class Graph(object):  # 新建图
    def __init__(self):
        self.adjacency_matrix = []  # 邻接矩阵
        self.vertex_set = []  # 节点集
        self.vertex_counter = 0  # 看看有几个节点
    
    def CreateGraph(self):
        list_relation = []  # 临时存储边用的列表
        list_vertex = []  # 临时存储节点用的列表
        while True:
            info = input("请输入一条连接,格式为A,n,B,A、B分别为相连的两个顶点,n为边的权值(或输入“终止”以结束):")
            if info != "终止":
                try:
                    relation = info.split(",")  # 逗号分隔出两个节点和一个边
                    relation[1] = int(relation[1])  # 将中间的边的权值转换为数字
                    relation_reversed = list(reversed(relation))  # 将列表倒序
                    list_relation.append(relation_reversed)  # 通过删除这行、上一行和下一行这三行可以实现转无向网到有向网,这算是编程的时候的意外收获
                    list_relation.append(relation)  # 先后添加入正序和倒序的关系,每两个构成一个无向边(双向边)
                except Exception as e:  # 捕获异常(主要是防止输入错误之类的)
                    print(e)
            else:
                break  # 终止
        for i in range(len(list_relation)):  # 将临时存储边的列表中的节点提取出来存入节点列表
            list_vertex.append(list_relation[i][0])
            list_vertex.append(list_relation[i][2])
        list_vertex = list(set(list_vertex))  # 列表去重(不是最好的,但是代码量最小的)
        list_vertex.sort()  # 排序(便于下一步即使不用字典也可以轻松地实现两个列表之间的index对照)
        for i in range(len(list_vertex)):
            self.vertex_counter += 1  # 算节点数的,也可以直接用len
            vertex = Vertex(list_vertex[i])  # 新建图节点
            self.vertex_set.append(vertex)  # 添加入节点集
        self.adjacency_matrix = [[0 for j in range(self.vertex_counter)]for i in range(self.vertex_counter)]  # 根据之前获得的节点数初始化邻接矩阵
        for i in range(len(list_relation)):  # 对每一个边写入邻接矩阵
            index_j = 0
            index_k = 0  # 两个用于把for循环中确定的节点位置取出来的参数
            for j in range(len(list_vertex)):  # 循环用于确定该边的起始节点是第几号节点,从而确定其位于邻接矩阵的第几行
                if list_vertex[j] == list_relation[i][0]:
                    index_j = j
            for k in range(len(list_vertex)):  # 循环用于确定该边的终止节点是第几号节点,从而确定其位于邻接矩阵的第几列
                if list_vertex[k] == list_relation[i][2]:
                    index_k = k
            self.adjacency_matrix[index_j][index_k] = list_relation[i][1]  # 按照获得的坐标写入权值
        print("创建完成")
        
    def BFS(self):  # BFS算法锦星便利,并获取遍历结果
        visited = ["0" for i in range(self.vertex_counter)]  # 是否以访问的标签列表(这里就体现出之前排序的作用了)
        queue = [[self.adjacency_matrix[0], 0]]  # 队列,有点像二叉树的层次遍历,这里存有某个节点的全部连接信息以及这是第几个节点,初始化
        while queue:  # 是否为空(遍历是否完成)
            cursor = queue.pop(0)  # 出队
            if visited[cursor[1]] == "0":  # 如果之前没访问到就访问一下
                visited[cursor[1]] = "1"
            for i in range(len(self.adjacency_matrix)):  # 遍历该节点的全部连接属性
                if self.adjacency_matrix[cursor[1]][i] != 0 and visited[i] == "0":  # 这个连接是否存在以及对应的另一端的节点是否已访问
                    queue.append([self.adjacency_matrix[i], i])  # 没有的话就写入准备访问
                else:
                    continue
        return visited  # 返回访问结果,正常应该全为“1”,不可达的就会出现“0”
    
    def Prim(self, vertex):  # Prim算法
        arc = []  # 用于存储生成树信息
        close_edge = [[] for i in range(self.vertex_counter)]  # 临边(最多也不过吧所有的连接一遍,也就是节点数-1)
        min_edge = 0  # 最小边
        index = 0  # 用于计数的索引
        while index < self.vertex_counter:  # 初始化close_edge
            close_edge[index] = [vertex, self.adjacency_matrix[vertex][index]]
            index = index + 1
        index = 1  # 不能再赋0了,毕竟自己没法连接自己(在本情境下)
        while index < self.vertex_counter:
            min_edge = self.GetMin(close_edge)  # 获取该条件下权值最小的边,将其处世界店,终止节点和权值存入arc
            arc.append([self.vertex_set[close_edge[min_edge][0]].data, self.vertex_set[min_edge].data, self.adjacency_matrix[min_edge][close_edge[min_edge][0]]])
            close_edge[min_edge][1] = 0
            i = 0
            while i < self.vertex_counter:
                if self.adjacency_matrix[min_edge][i] < close_edge[i][1]:  # close_edge需要更新,不然的话下一次还会访问到这个边
                    close_edge[i] = [min_edge, self.adjacency_matrix[min_edge][i]]
                i += 1
            index += 1
        
        print("Prim算法构造的最小生成树的边如下:")
        for i in arc:
            print(i)
    
    def GetMin(self, close_edge):  # 获取最小权值的边
        index = 0
        min_weight = float("inf")
        vertex = 0
        while index < self.vertex_counter:  # 不断循环根据close_edge中已有的边找到其中最小的一个,原理简单,实现简单
            if close_edge[index][1] != 0 and close_edge[index][1] < min_weight:
                min_weight = close_edge[index][1]
                vertex = index
            index = index + 1
        return vertex
    
    def LocateVertex(self, vertex):  #定位定点在邻接表中位置的方法
        index = 0
        while self.vertex_set[index].data != vertex and index < len(self.adjacency_matrix):
            index += 1  # 就是一个循环查找,遇到一样的事后就终止自加
        return index
    
    def GetEdges(self, edges):  # 对图中的边进行升序排列和存储
        horizontal = 0
        while horizontal < self.vertex_counter:  # 一层循环(行循环)
            vertical = horizontal  # 这一步主要是为了考虑到,对于邻接矩阵,其上三角部分和下三角部分完全对称,没有全部遍历的必要(当然,遍历了也不是不行)
            while vertical < self.vertex_counter:  # 二层循环(列循环)
                if self.adjacency_matrix[horizontal][vertical] > 0 and self.adjacency_matrix[horizontal][vertical] < float("inf"):
                    index = 0
                    flag = True
                    while index < len(edges) and flag:
                        if edges[index][2] > self.adjacency_matrix[horizontal][vertical]:
                            edges.insert(index, [self.vertex_set[horizontal].data, self.vertex_set[vertical].data, self.adjacency_matrix[horizontal][vertical]])  # 根据行列标以及节点位置写入一个边
                            flag = False
                        index += 1
                    if flag:
                        edges.append([self.vertex_set[horizontal].data, self.vertex_set[vertical].data, self.adjacency_matrix[horizontal][vertical]])  # 同上
                vertical += 1
            horizontal += 1
            
    def Kruskal(self, edges):
        flag = [[] for i in range(self.vertex_counter)]
        index = 0
        while index < self.vertex_counter:  # 初始化节点标记,用于判断节点是否属于同一连通分量
            flag[index] = index
            index += 1
        index = 0
        while index < len(edges):  # 遍历每一条边直至所有边均属于同一连通分量
            vertex_1 = self.LocateVertex(edges[index][0])  # 初始节点
            vertex_2 = self.LocateVertex(edges[index][1])  # 终止节点
            if flag[vertex_1] != flag[vertex_2]:  # 能够成连接的节点
                flag_1 = flag[vertex_1]
                flag_2 = flag[vertex_2]  # 提出节点
                limit = 0
                while limit < self.vertex_counter:
                    if flag[limit] == flag_2:
                        flag[limit] = flag_1  # 将符合条件的边并入同一个连通分量
                    limit += 1
                index += 1
            else:
                edges.pop(index)  # 将该边删除
        
        print("Kruskal算法构造的最小生成树的边如下:")
        for i in edges:
            print(i)
        
if __name__ == "__main__":
    graph = Graph()
    graph.CreateGraph()
    visited = graph.BFS()
    flag = True
    for i in range(len(visited)):
        if visited[i] == "0":
            flag = False
            print("第%d个节点不可达" % i)
        else:
            continue
    if flag:
        print("所有节点可达")
    graph.Prim(0)
    edges = []
    graph.GetEdges(edges)
    graph.Kruskal(edges)



版权声明:本文为weixin_61864411原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。