5 图算法深入之图的最小生成树

在前一篇中,我们探讨了图算法中的Dijkstra算法与A*算法,这是两种广泛使用的路径搜索算法。它们主要解决的是寻找最短路径的问题。而在图论中,除了路径问题外,最小生成树(Minimum Spanning Tree,MST)也是一个非常重要的主题。本篇将深入讲解最小生成树的概念、算法以及实践中的应用。

最小生成树的定义

在一个无向图中,最小生成树 是一个包含所有顶点的子图,且构成一棵树(即无环图),并且该树的边的总权值最小。

特点:

  1. 包含所有顶点:最小生成树必须包含图中所有的顶点。
  2. 边的最小权值:所选的边的权值总和是所有可能生成树中的最小值。
  3. 唯一性:当图的边权不同的时候,最小生成树唯一;当存在相同边权时,可能有多棵最小生成树。

常用算法

Kruskal算法

Kruskal算法是一种基于边的贪心算法。它的基本思想是:按照边的权重从小到大排序,然后不断选择最小的边,只有在选择后不会形成环时才加入到生成树中。

算法步骤:

  1. 将图的所有边按权重从小到大排序。
  2. 初始化一个空的生成树。
  3. 遍历排序后的边,选择当前权重最小的边,检查该边是否形成环:
    • 如果没有形成环,则将该边加入生成树。
    • 如果形成了环,则丢弃该边。
  4. 重复步骤3,直到生成树包含$V-1$条边($V$为顶点的数量)。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class UnionFind:
def __init__(self, n):
self.parent = list(range(n))

def find(self, u):
if self.parent[u] != u:
self.parent[u] = self.find(self.parent[u]) # 路径压缩
return self.parent[u]

def union(self, u, v):
pu = self.find(u)
pv = self.find(v)
if pu != pv:
self.parent[pu] = pv

def kruskal(vertices, edges):
uf = UnionFind(len(vertices))
mst = []
edges.sort(key=lambda x: x[2]) # 按权重排序

for u, v, weight in edges:
if uf.find(u) != uf.find(v):
uf.union(u, v)
mst.append((u, v, weight))

return mst

# 示例数据
vertices = [0, 1, 2, 3]
edges = [(0, 1, 1), (1, 2, 2), (2, 3, 1), (0, 3, 3), (1, 3, 2)]
mst = kruskal(vertices, edges)
print("最小生成树的边:", mst)

复杂度

Kruskal算法的时间复杂度为 $O(E \log E + E \log V)$,其中$E$是边的数量,$V$是顶点的数量。

Prim算法

Prim算法也是一种贪心算法,但它是基于顶点的。它的基本思想是:从一个起始点开始,不断选择与已选边相连的、权重最小的边来扩展生成树。

算法步骤:

  1. 从任意顶点开始,标记为已选。
  2. 选择与已选顶点相连的、权重最小的边,并将其连接的顶点加入已选。
  3. 重复步骤2,直到选择的边数为 $V - 1$。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import heapq

def prim(vertices, edges):
graph = {v: [] for v in vertices}
for u, v, weight in edges:
graph[u].append((weight, v))
graph[v].append((weight, u))

mst = []
visited = {vertices[0]} # 从第一个顶点开始
edges_heap = graph[vertices[0]]
heapq.heapify(edges_heap)

while edges_heap:
weight, u = heapq.heappop(edges_heap)
if u not in visited:
visited.add(u)
mst.append((vertices[0], u, weight)) # 记录边
for weight, v in graph[u]:
if v not in visited:
heapq.heappush(edges_heap, (weight, v))

return mst

# 示例数据
vertices = [0, 1, 2, 3]
edges = [(0, 1, 1), (1, 2, 2), (2, 3, 1), (0, 3, 3), (1, 3, 2)]
mst = prim(vertices, edges)
print("最小生成树的边:", mst)

复杂度

Prim算法的时间复杂度为 $O(E \log V)$,适合密集图。

应用场景

最小生成树在许多实际应用中都非常重要,比如:

  1. 网络设计:构建最小成本的网络连接,如计算机网络、水管网络等。
  2. 聚类分析:在数据科学中,最小生成树可以用于分群,连接相似的数据点。
  3. 图像处理:一些图像分割技术使用最小生成树来处理像素之间的关系。

总结

在这一篇中,我们深入探讨了“最小生成树”的概念以及两种主要的求解算法:Kruskal和Prim。在下一篇中,我们将转向网络流算法的基础,讨论如何在网络中进行流量优化和资源分配。最小生成树的学习为我们后续的网络流算法的理解打下了良好的基础。

5 图算法深入之图的最小生成树

https://zglg.work/algorithm-one/5/

作者

IT教程网(郭震)

发布于

2024-08-11

更新于

2024-08-12

许可协议

分享转发

交流

更多教程加公众号

更多教程加公众号

加入星球获取PDF

加入星球获取PDF

打卡评论