文章目录
- 什么是最小生成树?
- Prim算法求最小生成树
- Python实现:
- Kruskal算法求最小生成树
- 并查集
- Python实现:
- Reference
什么是最小生成树?
在图论中,树是图的一种,无法构成闭合回路的节点-边连接组合称之为树;
加权无向图中,连通一棵总权值最小的树称为最小生成树。
对于最小生成树的经典解法主要是Prim和Kruskal算法:
Prim算法求最小生成树
- 从任意节点开始,初始化生成树为空;
- 每次选择一条从已加入生成树的节点到未加入节点之间的最小边;
- 将新的节点加入生成树,并更新所有未加入节点与生成树之间的最小边;
- 重复步骤2-3,直到所有节点都加入生成树。
Python实现:
class Node:def __init__(self,node,length):self.node = nodeself.length = lengthclass Edge:def __init__(self, x, y, length):self.x = xself.y = yself.length = lengthclass Prim:def __init__(self,n,m):self.n = nself.m = mself.v = [[] for i in range(n+1)] # 邻接表self.e = [] # 存放与当前节点相连的边self.s = [] # 存放最小生成树里的所有边self.vis = [False for i in range(n+1)] # 用于存放每个节点是否已加入生成树的标记def graphy(self):# 用邻接表构建图for i in range(self.m):x, y, length = list(map(int, input().split()))self.v[x].append(Node(y,length))self.v[y].append(Node(x,length))def insert(self, point):# 找到一个新节点for i in range(len(self.v[point])):# 将最小边的新节点加入生成树if not self.vis[self.v[point][i].node]:self.e.append(Edge(point,self.v[point][i].node,self.v[point][i].length))self.vis[point] = Trueself.e = sorted(self.e, key = lambda e:e.length)def run(self, start):# 求解录入的无向连通图的最小生成树self.insert(start)for _ in range(self.n-1):for i in range(len(self.e)):if not self.vis[self.e[i].y]:self.s.append(self.e[i])self.insert(self.e[i].y)break# 判断是否存在连通if len(self.s) != (self.n - 1):print("error!")else:count = 0for i in range(len(self.s)):count += self.s[i].lengthprint(count)def main():n, m = list(map(int, input().split()))prim = Prim(n, m)prim.graphy()prim.run(1)if __name__ == '__main__':main()
Kruskal算法求最小生成树
- 对所有边按权重进行排序;
- 初始化一个空的生成树,以及一个并查集(Union-Find)来跟踪节点的连接情况;
- 逐条检查排序后的边,尝试将边加入生成树:
a. 如果当前边连接的两个节点不在同一个集合,则加入该边,并合并这两个节点所在的集合;
b. 如果当前边连接的两个节点已经在同一个集合,则跳过此边,避免形成环;- 重复步骤3,直到生成树中有 (N-1,N是节点数) 条边。
并查集
并查集(Union-Find
Set)是一种高效的数据结构,主要用于解决一些动态连通性问题。它可以快速地判断某两个元素是否属于同一个集合,同时支持合并两个集合的操作。并查集最常见的应用场景是:判断加权无向图中的连通性问题,例如:
判断两个节点是否属于同一个连通分量。 动态合并两个连通分量。
Python实现:
class Edge:def __init__(self, x, y, length):self.x = xself.y = yself.length = lengthclass Kruskal:def __init__(self, n, m):self.n = nself.m = mself.e = [] # 保存所有边self.s = [] # 存放最小生成树里的所有边self.parent = list(range(n+1))for i in range(m):x,y,length = list(map(int,input().split()))self.e.append(Edge(x,y,length))self.e.sort(key = lambda e:e.length)def find(self,f):if f == self.parent[f]:return felse:self.parent[f] = self.find(self.parent[f])return self.parent[f]def merge(self,fa,fb):a = self.find(fa)b = self.find(fb)self.parent[a] = bdef run(self):for j in range(self.m):ed = self.e[j]if self.find(ed.x) != self.find(ed.y):self.s.append(ed)self.merge(ed.x, ed.y)if len(self.s) != self.n - 1:print("error!")else:count = 0for i in range(len(self.s)):count += self.s[i].lengthprint(count)def main():n,m = list(map(int,input().split()))kruskal = Kruskal(n,m)kruskal.run()if __name__ == "__main__":main()
Reference
最小生成树:Prim算法和Kruskal算法
蓝桥云课-最小生成树
并查集