相关文章:
数据结构–图的概念
图搜索算法 - 深度优先搜索法(DFS)
图搜索算法 - 广度优先搜索法(BFS)
图搜索算法 - 拓扑排序
图搜索算法-最短路径算法-戴克斯特拉算法
图搜索算法-最短路径算法-贝尔曼-福特算法
最小生成树
首先认识什么叫生成树(spanning tree),一个有N个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有N个结点,并且有保持图连通的最少的边,如图所示。
最小生成树(Minimum Spanning Tree)问题(下面简称MST)是指给定连接图G具有正的边权重,找到连接所有结点的边的最小权重集。MST应用场景多为网络设计,如光纤网络、燃气管道网络、电缆、道路交通等路线设计,其问题可以用克鲁斯卡尔(kruskal)算法或普里姆(prim)算法求解。
克鲁斯卡尔算法(kruskal)
这里有一个案例,一家公司在6个城市有分公司,他们之间需要用专用电话线路链接起来,电信公司给出了每个城市之间铺设线路的费用,如图所示。现在需要设计一个方案,用最少费用完成这个电话线路铺设。
面对这个问题,自然会想到尽量选择最小权重的边,结果必然是最小了。在算法里面,称为贪心算法。基本思想是通过获得局部的最优解,从而得到最终的最优解。现在要使用的克鲁斯卡尔算法核心思想是贪心算法,目标非常明确简单,步骤如下。
(1)初始化一个生成树集合(简称MST)为空。
(2)按边的权重由大到小进行排序。
(3)选择最小权重的边,检查它是否与MST形成一个循环图。如果没有形成循环图,则把该边放进MST,否则将其丢弃。
(4)重复步骤3,直到生成树中有(N-1)个边(N为结点数)。
分析步骤,克鲁斯卡尔算法最多遍历E条边,每次选择最小代价的边仅需要O(logE)的时间,因此,克鲁斯卡尔算法的时间复杂度为O(ElogE),空间上需要一个N-1空间记录最小生成树,所以空间复杂度为O(N)。现在尝试手动计算求解,首先是边权重的排序,结果如表所示。
然后选择权重最小的边【上海-武汉】,因此MST初始值为空,当然没有循环图,所以不存在循环图,当然把这条边放进MST中,接着挑选边【广州-成都】,同样也不存在循环图,如图所示。
用同样的方式,选择【广州-厦门】、【广州-武汉】都进入了MST,然后挑选【厦门-武汉】,这时候就形成了一个循环,因此丢弃此边,当前MST的状态如下图所示。
紧接着选择【厦门-上海】,同样也形成了循环,同样不能放到MST中。然后继续选择【北京-上海】,这条边则成功进入MST中,此时MST的边数量已经达到5个,满足结束条件,最终结果如下图6-8所示。此图也就是电话路线的铺设方案,总费用是MST上所有边的权重和为14。
用代码来表现此算法,首先知道这是一个有权无向图,则使用邻居列表并且带有权重,【Graph】类的输入不适用于这里,需要创建新的类叫【GraphPower】。
class GraphPower(): """有权图类"""def __init__(self, points):self.amount = len(points) # 记录结点的总数self.points = points # 记录结点位置和值的关系self.graph = [] # 初始化图的邻接列表def add_edge(self, u, v, w):if u in self.points and v in self.points:index_u = self.points.index(u)index_v = self.points.index(v)self.graph.append([index_u, index_v, w]) # 录入数据else:print("录入数据有误")
算法的过程在此,创建【GraphKruskal】类,kruskalMST()函数是克鲁斯卡尔算法主体,然后用不相交集来检验是否有循环出现。
class GraphKruskal(GraphPower): """克鲁斯卡尔算法,输入的是有权无向图,求解最小生成树"""def find(self, parent, i):# 寻找其父结点if parent[i] == i: return i return self.find(parent, parent[i])def kruskalMST(self):# 基于不相交集实现克鲁斯卡尔算法主程序# 第一步初始化MST =[] # 初始化MST,也是最终结果 parent = [] # 初始化列表记录结点的相交连接结点下标,用于检测是否有循环for node in range(self.amount): # 每一个结点创建一个子集合 parent.append(node) index_sorted_edge = 0 # 根据权重已排序的边的下标 index_reslut = 0 # MST列表中的下标 # 第二步,根据权重排序self.graph = sorted(self.graph,key=lambda item: item[2]) # 权重w在item中的三个位置while len(MST) < self.amount -1 : # 结束条件:直到生成树中有(N-1)个边 # 第三步,选择最小权重的边u,v,w = self.graph[index_sorted_edge] index_sorted_edge = index_sorted_edge + 1# 检查它是否与MST形成一个循环图。如果没有形成循环图,则把该边放进MST,否则将其丢弃parent1 = self.find(parent, u) parent2 = self.find(parent ,v)if parent1 != parent2:MST.append([u,v,w]) # 结果中添加新的边parent[parent2] = parent1 # 更新不相交集self.print_result(MST)def print_result(self, result):print("输出最小生成树结果")total = 0for u,v,weight in result:total += weightprint ("{} -- {} == {}".format(self.points[u], self.points[v],weight))else:print("权重总和为:%d" % total)
用例子作为输入来验证结果,运行情况如下。
g = GraphKruskal(["广州","厦门","成都","武汉","上海","北京"])
g.add_edge("广州","厦门", 2)
g.add_edge("广州","武汉", 3)
g.add_edge("广州","成都", 2)
g.add_edge("武汉","厦门", 4)
g.add_edge("武汉","成都", 8)
g.add_edge("武汉","上海", 1)
g.add_edge("武汉","北京", 9)
g.add_edge("厦门","上海", 5)
g.add_edge("成都","北京", 7)
g.add_edge("上海","北京", 6)
g.kruskalMST()
# ------------结果------------------
输出最小生成树结果
武汉 -- 上海 == 1
广州 -- 厦门 == 2
广州 -- 成都 == 2
广州 -- 武汉 == 3
上海 -- 北京 == 6
权重总和为:14
更多内容
想获取完整代码或更多相关图的算法内容,请查看我的书籍:《数据结构和算法基础Python语言实现》