一个连通无向图G=(V, E),每条边(u, v) ∈ E,都有权重w(u, v),希望找到一个无环子集T E,既能够将所有节点都连接起来,又具有最小的权重。由于T是无环的,并且能连接所有的节点,所以T必然是一棵树,成T为最小生成树。最小生成树不唯一。
一:最小生成树的形成
本章所讨论的两种算法都采用贪心策略,这个贪心策略由下面的通用算法描述:
GENERIC-MST(G, w)
A =∅
while A does not form a spanningtree
find an edge (u, v) that issafe for A
A = A {(u, v)}
return A
该通用算法遵循循环不变式:在每次循环之前,A是某颗最小生成树的一个子集。我们要做的主要事情就是选择一条边(u, v),将其加入到集合A中,使得A不违反循环不变式,也就是A∪ {(u,v)}也是某颗最小生成树的子集。称这样的边(u, v)为集合A的安全边。
算法的关键就在于如何找到安全边,下面介绍找安全边的规则:
无向图G=(V, E)的一个切割(S, V-S)是集合V的一个划分,如下图所示,如果一条边(u, v)∈ E的一个端点位于集合S,另一个端点位于集合V-S,则称该条边横跨切割(S, V-S)。如果集合A中不存在横跨该切割的边,称该切割尊重集合A。在横跨一个切割的所有边中,权重最小的边称为轻量级边。
定理:设G=(V, E)是一个在边E上定义了权重函数w的连通无向图,设集合A为E的一个子集,且A包括在G的某颗最小生成树中,设(S, V-S)是图G中尊重集合A的任意一个切割,又设(u, v)是横跨切割(S, V-S)的一条轻量级边。那么边(u, v)对于集合A是安全的。
在算法执行过程中的任何一个时刻。图GA=(V,A)是一个森林,GA中的每一个连通分量都是一棵树(某些树可能只包含一个顶点,例如在算法开始时,A为空集,森林中包含|V|棵树,每个顶点对应于一棵树)。
推论:设G=(V, E)是一个在边E上定义了权重函数w的连通无向图,设集合A为E的一个子集,且A包括在G的某颗最小生成树中,设C=(Vc, Ec)为森林GA = (V, A)中的一个连通分量。如果边(u, v)是连接C和GA中某个其他分量的一条轻量级边,那么边(u, v)对于集合A是安全的。
二:Kruskal算法和Prim算法
1:Kruskal算法
Kruskal算法找到安全边的办法是:在所有连接森林中两颗不同树的边中,找到权重最小的的边(u ,v)。设C1和C2为边(u, v)所连接的两棵树。由于边(u, v)一定是连接C1和其他某棵树的一条轻量级边,根据推论,边(u, v)是C1的一条安全边。
Kruskal算法使用一个不相交集合数据结构来维护几个互不相交的元素集合,每个集合代表森林中的一棵树:
MST-KRUSKAL(G, w)
A = ∅
for each vertex v
MAKE-SET(v)
sort the edges of G.E into nondecreasing order by weight w
for each edge (u, v) 属于 G.E, taken in nondecreasing order by weight
if FIND-SET(u) != FIND-SET(v)
A = A {(u, v)}
UNION(u, v)
return A
循环中,需要检查端点u和端点v是否属于同一棵树。如果是,该条边不能加入到森林里,否则将会形成回路,如果不是,则两个端点分别属于不同的树。
对于图G=(V, E),Kruskal算法的运行时间依赖于不相交集合数据结构的实现方式,时间复杂度为O(ElgV)。下图为算法运行方式:
2:Prim算法
Prim算法中,集合A中的边始终构成一棵树,这棵树从任意一个根节点开始,一直长大到覆盖V中所有节点为止。算法每一步在连接集合A和A之外的节点的所有边中,选择一条轻量级边加入到A中:
MST-PRIM(G, w, r)
for each u ∈ G.V
u.key = ∞
u.π = NIL
r.key = 0
Q = G.V
while Q != ∅
u = EXTRACT-MIN(Q)
for each v ∈ G.Adj[u]
if v ∈Q and w(u, v) < v.key
v.π = u
v.key = w(u, v)
算法第7行将找出节点u ,该节点是某条横跨切割(V-Q, Q)的轻量级边的一个端点,然后将u从队列Q中删除,并将其加入到集合V-Q中,也就是将边(u, u.π)加入到集合A中。在for循环中,将每个与u邻接但却不在树中的节点v的key和π 属性进行更新。
Prim算法的运行时间取决于最小优先队列Q的实现方式,如果Q采用普通的最小堆实现,则时间为O(E lg V),如果采用斐波那契堆,来实现最小队列Q,则时间将改进到O(E + V lg V),下面为算法运行过程: