最小生成树问题——连接n个针脚,可以使用n-1根连线,每个连线连接两个针脚,使得所使用的连线长度最短
抽象为图问题,一个连通无向图G = (V, E),V是针脚的集合,E是针脚之间的可能连接,且对于每条边都有权重w(u, v),希望找到一个无环子集,T属于E,权重之和最小
通用方法——在每个时刻生长最小生成树的一条边,并在整个策略的实施过程中,管理一个遵守下述循环不变式的边集合A:
每遍循环之前,A是某棵最小生成树的一个子集
即在每一步,选择一条边(u, v),将其加入到集合A中【贪心算法】,使得A不违反循环不变式;这样的边成为集合A的 安全边
GENERIC-MST(G, w)
A = 空集
while A does not form a spanning tree //A还不是生成树
find an edge(u, v) that is safe for A
A = A 并上 {(u, v)}
return A
Kruskal算法和Prim算法在确定安全边的细节有所不同
在Kruskal算法中,集合A是一个森林,其结点就是给定图中的结点,每次加入到集合A中的安全变永远是权重最小的链接两个不同分量的边; 在Prim算法里,集合A是一棵树,每次加入到A中的安全变永远是连接A和A之外某个结点的边中权重最小的边。
Kruskal算法——在所有连接森林中两棵不同树的边里面,找到权重最小的边(u, v)
使用不想交集合结构来维护几棵互不相交的树,即元素集合;FIND-SET(u)能够找到u所在的集合的代表,因此判断两个元素是否在同一个集合,只需要FIND-SET(u) == FIND-SET(v)
MST-KRUSKAL(G, w)
A = 空集
for each vertex v 属于 G.V
MAKE-SET(v)
sort the edegs 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
Prim算法——在连接集合A和A之外的结点的所有边中,选择一条轻量级边加入到A中
所有不在树A中的结点都存放在一个基于key属性的 最小优先队列Q中;对于每个结点v,属性v.key保存的是连接v和树中结点的所有边中最小边的权重(如果不存在则为INF);v.p给出结点v在树中的父结点
从结点r出发构造,集合A维持在A = {(v, v.p) : v 属于 V - {r} - Q};最后生成的最小生成树为A = {(v, v.p) : v 属于 V - {r}}
MST-PRIM(G, w, r)
for each u 属于 G.V
u.key = INF
u.p = 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 //还在Q中(即还没选择进来)的需要更新
v.p = u
v.key = w(u, v)
上面的过程少了构造最小优先队列一步,比如初始结点为r,则Q中存储的u.key不应该都为INF,初始化的时候就需要构造最小优先队列;而更新之后也得保持最小优先队列的性质