一、最小生成树是什么?
想象你要给一个小区铺设水管,要求所有住户都能通水,但总管道长度最短。这就是最小生成树(Minimum Spanning Tree, MST)的典型场景——在连通图中选出总权重最小的边,连接所有节点且不形成环路。
二、Prim算法:从点出发的“生长式”策略
Prim算法像一棵慢慢长大的树。它从一个起点开始,每次选择当前已连接部分到未连接部分的最短边,逐步扩展。
技术栈:Python
def prim(graph):
nodes = list(graph.keys())
visited = {nodes[0]} # 从第一个节点开始
edges = []
while len(visited) < len(nodes):
min_edge = None
# 遍历已访问节点的所有邻边
for node in visited:
for neighbor, weight in graph[node]:
if neighbor not in visited:
if not min_edge or weight < min_edge[2]:
min_edge = (node, neighbor, weight)
if min_edge:
edges.append(min_edge)
visited.add(min_edge[1]) # 标记新节点为已访问
return edges
# 示例图(邻接表形式)
graph = {
'A': [('B', 2), ('C', 3)],
'B': [('A', 2), ('C', 1), ('D', 1)],
'C': [('A', 3), ('B', 1), ('D', 4)],
'D': [('B', 1), ('C', 4)]
}
print(prim(graph)) # 输出:[('A', 'B', 2), ('B', 'D', 1), ('B', 'C', 1)]
特点:
- 适合稠密图(边多),因为每次只处理当前节点的邻边。
- 时间复杂度为O(V²),可用优先队列优化到O(E log V)。
三、Kruskal算法:按边排序的“拼图式”策略
Kruskal算法像玩拼图:将所有边按权重从小到大排序,逐个尝试加入,不形成环就保留。
技术栈:Python
def kruskal(graph):
parent = {}
def find(node):
while parent[node] != node:
node = parent[node]
return node
edges = []
# 初始化父节点(用于检测环路)
for node in graph:
parent[node] = node
# 提取所有边并排序
all_edges = []
for node in graph:
for neighbor, weight in graph[node]:
all_edges.append((weight, node, neighbor))
all_edges.sort() # 按权重升序
# 遍历边
for weight, u, v in all_edges:
root_u, root_v = find(u), find(v)
if root_u != root_v: # 不形成环
edges.append((u, v, weight))
parent[root_v] = root_u # 合并集合
return edges
print(kruskal(graph)) # 输出:[('B', 'D', 1), ('B', 'C', 1), ('A', 'B', 2)]
特点:
- 适合稀疏图(边少),排序后只需遍历一次。
- 时间复杂度为O(E log E),主要来自排序。
四、如何根据图的稀疏度选型?
稠密图(边数≈节点数²):
- 选Prim(尤其是邻接矩阵存储时)。
- 例如:城市之间的交通网,几乎每两个城市都有直达路线。
稀疏图(边数≈节点数):
- 选Kruskal,排序后处理更高效。
- 例如:社交网络中,每个人只连接少量好友。
注意事项:
- Prim需要指定起点,但结果不受起点影响。
- Kruskal需检测环路,通常用并查集(Union-Find)优化。
五、总结
- Prim像“滚雪球”,适合边多的场景;Kruskal像“挑积木”,适合边少的场景。
- 实际应用中,图的存储方式(邻接表或矩阵)也会影响选择。
- 两种算法结果可能相同(总权重一致),但边顺序不同。
评论