最小生成树 (Minimum Spanning Tree)
最小生成树是图论领域的一个基本概念,适用于加权连通图,其中包括若干顶点(节点)以及连接这些顶点的边(边可以有权重)。在一个加权连通图中,生成树(Spanning Tree)是一个无环子图,它包含图中的所有顶点,并且用最少数量的边将它们连接起来。注意,无环是指子图中不存在任何边的闭环,最少数量的边意味着任意两个顶点之间有且仅有一条路径相互到达。
“最小生成树”这一术语的“最小”指的是在所有可能的生成树中,边的权重之和最小的那一个。在实际应用中,最小生成树可以帮助找到在网络设计、电路设计等方面成本最低的方案。
我们来举一个简单的例子。假设有四个城市,每两个城市之间可以修建道路相连,不同的道路成本不同,现在的目标是花最少的成本将这四个城市全部连通。最小生成树算法即是解决此类问题的有效工具。
生成最小生成树的算法
接下来,我们将介绍四个生成最小生成树的经典算法,它们分别是克鲁斯卡尔(Kruskal)算法和普里姆(Prim)算法,以及相对少见的Borůvka算法和Sollin算法。
1. 克鲁斯卡尔(Kruskal)算法
克鲁斯卡尔算法是基于边的贪心策略。它的基本思想是按照边权重从小到大的顺序选择边,从而构造最小生成树。选取的边必须满足添加后不形成环路。
伪代码:
KRUSKAL(G):
A = ∅ // A将存储最小生成树的边
对于G中的每个顶点v:
MAKE-SET(v)
将G中的所有边按权重由低到高排序
对于每条边(u, v)按序做如下操作:
if FIND-SET(u) ≠ FIND-SET(v): // 检查u和v是否在树的不同分量中
A = A ∪ {(u, v)} // 将边(u, v)加入到A中
UNION(u, v) // 将u和v的分量合并
返回A
其中 MAKE-SET、FIND-SET 和 UNION 是不相交集合数据结构的操作,用于维护和查询顶点间是否存在环路。
2. 普里姆(Prim)算法
普里姆算法是基于点的贪心策略。在这个算法中,我们从任选的一个顶点开始构建最小生成树,逐步扩大树的范围,每一步都添加一条连接树与非树顶点且权重最小的边。
伪代码:
PRIM(G, w, r): // G是图,w是权重函数,r是开始顶点
for each u ∈ G.V:
u.key = ∞ // 初始化所有顶点的键值为无穷大
u.π = NIL // π属性用来记录最小生成树的父节点
r.key = 0
Q = G.V
while Q ≠ ∅:
u = EXTRACT-MIN(Q) // 从Q中取出键值最小的顶点u
for each v ∈ G.Adj[u]: // 遍历u的所有邻居v
if v ∈ Q and w(u, v) < v.key:
v.π = u // 更新v的父节点为u
v.key = w(u, v) // 更新v的键值为u与v之间边的权重
在Prim算法中,EXTRACT-MIN(Q)
是优先队列的操作,它用于选择权重最小的边,而G.Adj[u]
表示图中与顶点u相邻的所有顶点集合。
3. Borůvka算法
Borůvka算法是最早的最小生成树算法之一,适用于稀疏图。算法的每个阶段为图中的每个连通分量选择一条权重最小的边,并将这些边添加到生成树中,直到图变为连通的。
伪代码:
BORUVKA(G):
forest = each vertex in G is a separate tree
while there is more than one tree in the forest:
for each tree T in the forest:
find the smallest edge connecting T to another tree
add this edge to the forest
return the edges added to the forest
4. Sollin算法
Sollin算法(也被称为Borůvka的改进版本)同样适用于稀疏图,其基本想法是在每个阶段找到每个连通分量键值最小的边,并将它们加入生成树,像Borůvka算法一样重复这个过程,直到所有分量合并到一起。
伪代码:
SOLLIN(G):
Initialize a forest F with each vertex in G as a separate tree
while F has more than one tree do
for each tree T in the forest F do
let e = the lightest edge with one end in T
if there is no edge chosen for T or e is lighter than the already chosen edge
choose e for T
for each edge e chosen in this round do
if e connects two different trees then
add e to the forest F
return the forest F as the minimum spanning tree
在Sollin算法中,检查加入边e后是否会造成环路的操作通过查询树的根节点来实现,保证了每次迭代加入的边一定属于不同的连通分量。