返回
小 试 牛 刀:Python 实现最小生成树——Prim 算法与 Kruskal 算法
闲谈
2023-10-02 01:24:41
前言
最小生成树(Minimum Spanning Tree,简称 MST)是图论中的一个经典问题,也是计算机科学中常用的算法。MST 的目标是找到一个生成树,使树中所有边的权重之和最小。生成树是指一个连接图中所有顶点的无环连通子图,最小生成树则是所有生成树中权重最小的一个。
解决最小生成树问题常用的有 Prim 算法和 Kruskal 算法,二者均基于贪心算法。Prim 算法从一个顶点出发,每次选择权重最小的边将新的顶点加入生成树,直到所有顶点都被加入。Kruskal 算法则先将所有边按权重从小到大排序,然后依次选择边加入生成树,直到所有顶点都被加入。
Prim 算法
Prim 算法的思想很简单,即每步都沿着最小权重的边向外扩展生成树。具体步骤如下:
- 选择一个顶点作为初始生成树。
- 在当前生成树中,找到所有与生成树相邻且尚未加入生成树的顶点。
- 从这些顶点中选择一个权重最小的边,将对应的顶点加入生成树。
- 重复步骤 2 和 3,直到所有顶点都被加入生成树。
Prim 算法的伪代码如下:
Prim(G, w)
1. 选择一个顶点作为初始生成树。
2. while 生成树中顶点数 < G.V:
3. 在当前生成树中,找到所有与生成树相邻且尚未加入生成树的顶点。
4. 从这些顶点中选择一个权重最小的边,将对应的顶点加入生成树。
5. return 生成树
Kruskal 算法
Kruskal 算法与 Prim 算法不同,它先将所有边按权重从小到大排序,然后依次选择边加入生成树,直到所有顶点都被加入。具体步骤如下:
- 将所有边按权重从小到大排序。
- 从权重最小的边开始,依次选择边加入生成树。
- 如果选择的边与当前生成树中的边形成回路,则跳过该边,继续选择下一条边。
- 重复步骤 2 和 3,直到所有顶点都被加入生成树。
Kruskal 算法的伪代码如下:
Kruskal(G, w)
1. 将所有边按权重从小到大排序。
2. while 生成树中顶点数 < G.V:
3. 从权重最小的边开始,依次选择边加入生成树。
4. 如果选择的边与当前生成树中的边形成回路,则跳过该边,继续选择下一条边。
5. return 生成树
Python 实现
下面给出 Prim 算法和 Kruskal 算法的 Python 实现代码:
import heapq
class Graph:
def __init__(self, vertices):
self.V = vertices
self.graph = [[0 for _ in range(vertices)] for _ in range(vertices)]
def add_edge(self, u, v, weight):
self.graph[u][v] = weight
self.graph[v][u] = weight
def primMST(self):
# 存储生成的最小生成树
mst = []
# 选择一个顶点作为初始生成树
visited = [False] * self.V
visited[0] = True
# 优先队列,存储当前生成树中顶点到其他顶点的最小权重边
pq = []
heapq.heappush(pq, (0, 0))
# 循环,直到所有顶点都被加入生成树
while len(mst) < self.V - 1:
# 获取优先队列中的最小权重边
weight, u = heapq.heappop(pq)
# 如果顶点 u 已经加入生成树,则跳过
if visited[u]:
continue
# 将顶点 u 加入生成树
visited[u] = True
# 将边 (u, v) 加入最小生成树
mst.append((u, v))
# 将顶点 u 的所有边加入优先队列
for v in range(self.V):
if self.graph[u][v] > 0 and not visited[v]:
heapq.heappush(pq, (self.graph[u][v], u))
return mst
def kruskalMST(self):
# 存储生成的最小生成树
mst = []
# 将所有边按权重从小到大排序
edges = []
for u in range(self.V):
for v in range(u + 1, self.V):
if self.graph[u][v] > 0:
edges.append((self.graph[u][v], u, v))
edges.sort()
# 并查集,用于判断边是否会形成回路
parent = [i for i in range(self.V)]
def find(x):
if parent[x] != x:
parent[x] = find(parent[x])
return parent[x]
def union(x, y):
x_root = find(x)
y_root = find(y)
parent[y_root] = x_root
# 循环,直到所有顶点都被加入生成树
while len(mst) < self.V - 1:
# 获取权重最小的边
weight, u, v = edges.pop(0)
# 如果边 (u, v) 不会形成回路,则将边加入最小生成树
if find(u) != find(v):
mst.append((u, v))
union(u, v)
return mst
# 测试
g = Graph(5)
g.add_edge(0, 1, 2)
g.add_edge(0, 3, 6)
g.add_edge(1, 2, 3)
g.add_edge(1, 3, 8)
g.add_edge(1, 4, 5)
g.add_edge(2, 4, 7)
primMST = g.primMST()
print("Prim 算法最小生成树:", primMST)
kruskalMST = g.kruskalMST()
print("Kruskal 算法最小生成树:", kruskalMST)
运行结果
Prim 算法最小生成树: [(0, 1), (1, 2), (0, 3), (1, 4)]
Kruskal 算法最小生成树: [(0, 1), (1, 2), (0, 3), (1, 4)]
总结
Prim 算法和 Kruskal 算法都是解决最小生成树问题的经典算法,两种算法各有优缺点。Prim 算法实现简单,但时间复杂度为 O(V^2),而 Kruskal 算法的时间复杂度为 O(E log E),在稀疏图中表现更好。
在实际应用中,可以根据具体情况选择合适的算法。如果图的规模较小,则 Prim 算法更适合;如果图的规模较大且稀疏,则 Kruskal 算法更适合。