首先谈下对最小生成树(Minimum Spanning Tree, MST)用处的理解,然后给出经典的Kruskal和Prim算法的伪码和理解。
首先显然它是解决最优化问题的,用的是贪心的思想(于是显然它是非常“优”的了,能用它解决就用它吧)。
最小生成树是从一个图简化而来,保留所有的(从树的角度来讲是连通图的所有)顶点以及连通性,砍掉尽可能多的边并使得剩下的边权值的和最小(用最短的边保持连通性)。
映射到现实问题,当我们面对一些有关系的实体(比如一个实体可以导出另一个实体),我们不愿丢掉连通性(砍掉两点之间的边一定是在有其它路径存在的前提下),希望获得最小代价的时候,可以考虑最小生成树。它与单源最短的区别就在于,第一它是全局的一种优化,没有指定的起点,不是针对某个特定的实体对其他的实体关系的问题;第二它多条边组成的路径对两端的点来说未必是最短的,但是它每条边对于两边的集合来说是最短的,单源最短路具有最优子结构,也就是说,单源最短路生成的图中,只要源点不作为中点,任意两点之间的路径一定是最短路。
最小生成树总的贪心思路是:每一步加入一条安全边。Kruskal和Prim以不同的方式寻找安全边。
说明一些术语:
安全边:不妨碍最小生成树边集的一条割的轻边。
割:把顶点分成两个集合,把相连的边剪掉。宛如一把剪刀。
不妨碍:A是一个边的集合,A中没有边通过割B,就说割B不妨碍边集A。
轻边:最小权值边。
Kruskal:
MST-KRUSKAL(G,w)
//initial
A <- empty
for each vertex v from V[G]
do MAKE-SET(v)
//add safe edge to form tree
sort the edges of E into nondecreasing order by weight w
for each edge(u,v) from E
do if FIND-SET(u) != FIND-SET(v)
then A <- A + {(u,v)}
UNION(u,v)
return A
算法思想是维护不想交的元素集合,连接这些集合的边是安全边。一个集合看做一个树,也就是一开始就是把图割成各个顶点独自组成各自树,集合中是当前森林一棵树中的顶点。这个森林没有丢点,但失去了连通性。FIND-SET(u)返回包含u的集合中的一个代表元素,以此对两个点使用可以测试是否在同一个集合,用UNION合并集合。我们不断用最小的边连通原本不连通的集合。
它的运行时间取决于集合的实现方式。
Prim;
MST-PRIM(G,w,r)
//initial
for each u from V[G]
do key[u] <- inf //infinity
pre[u] <- NULL
key[r] <- 0
Q <- V[G]
//add safe edge to form tree
while Q != empty
do u <- EXTRACT-MIN(Q)
for each v from Adj[u]
do if v from Q and w(w,v) < key[v]
then pre[v] <- u
key[v] <- w(u,v)
算法很像Dijkstra,它本身只找出树不找出整个森林(当然我们可以再外面套一个循环找出森林),不断向集合加入一个离集合最近的点与边,找出连通子集的所有点,在找的过程中就保证了连通性。key[v]代表v到集合轻边的值(没有边则为infinity)。
性能取决于优先队列Q的实现。
比较:
Kruskal适合稀疏图,Prim适合稠密图。但它们从渐进意义上好的实现的复杂度是相等的。
习题应用:
poj1789,poj2485,poj1258,poj3026