http://hi.baidu.com/%CA%FD%BE%DD%BD%E1%B9%B9%BF%CE%B3%CC/blog/item/fd0dd984f3b99126c75cc39c
求无向连通图的最小生成树算法——Prim与Kruskal及相关优化
最小生成树是图论里很重要的部分。但是由于它属于图论所以NOIP基本不考,对于NOI又太基础,所以竞赛中出现的几率比较小,即使要考也不可能考裸的生成树算法= =
最小生成树就Prim和Kruskal两个算法,又没有多大的优化余地,所以学习起来还是很简单的。
一.Prim算法
1.算法思想
2.实现
以上操作重复|V|-1次结束。由于每次加入S的点i都在当时取到了符合流程②的边min{lowcost},而lowcost[i]=w(i,closest[i]),所以此时的最小生成树的各边就是(i,closest[i]),i∈V且not (i=x)【需要注意的是出发点x的closest[x]还是x,所以应忽略,实际取到x-1条边】。把i从1取到|V|,便得到最小生成树T的每条边。
Pascal程序:(摘自NOCOW,略有改动)
(设边权存于数组cost,图的顶点个数|V|为n)
不难看出,以上算法包含一个二重循环,算法复杂度为O(V^2),与图的稠密度无关。
3.使用堆优化
对于上面代码绿色部分,转换成堆后就是一个建立一个有V个节点的小根堆的过程,复杂度为O(V);对于红色部分,就是取堆顶元素【O(1)】并删除、维护【O(logV)】;对于橙色部分,对于for循环我们可以用一个数组edge[i]记录与i节点相连的所有边,这样我们就把for循环的复杂度变成了与E相关。显然,图中的每条边仅在其两顶点进入最小生成树时被访问一次,也就是每条边被访问2次,因此橙色部分的执行次数总共是2E次而与外面的while无关。里面的lowcost修改时需要维护堆,复杂度为O(logV)。因此,算法的总复杂度为O(VlogV+ElogV)=O(ElogV)。具体程序见本文最下方。
对于|E|接近|V|的稀疏图,算法复杂度接近O(VlogV)显然比O(V^2)的算法优秀很多。然而对于E接近N^2的稠密图,算法复杂度接近O(V^2logV)反而不如平方算法。因此,使用堆优化的Prim算法适用于稀疏图,而不优化的Prim算法适用于稠密图。不过O(V^2logV)实际上不比O(V^2)慢多少,而现在的题目大多是稀疏图,所以这个算法在总体上是优于朴素Prim算法的。
顺便一提,如果用斐波那契堆来做的话,时间复杂度可以进一步优化为O(E+VlogV)。但事实上这样做的代码长度会到令人发指的地步,所以是一种只存在于论文的算法。应付一般的竞赛用堆优化的Prim就可以应付所有数据了。
二.Kruskal算法
1.算法思想
关于判断是否会产生环,有一个简单的方法:若i与j中已有一条路径i-k1-k2-…-kn-j,若加入边(i,j)就会形成环【i-k1-k2-…-kn-j-i】。因此,若一条边连接两个连通分量就不会形成环,反之则会形成环。初始时每一个点就是一个连通分量,当加入(i,j)时,判断i与j是否属于不同连通分量。如果是的话,在加入边后把i与j所在的连通分量合并。重复以上操作,就可以保证不会产生环。
2.实现
显然,Kruskal的难点在于判断、合并连通分量。Pascal语言中已有集合类型可以实现这一操作,不过竞赛中set是一个极其危险的类型【基本常识】。此处只用到查询、合并两个操作,是一个很明显的并查集模型,每一个连通分量就是并查集里的一个集合,通过并查集的算法即可实现算法。
3.时间复杂度分析
对E条边排序的复杂度为O(ElogE);E次并查集的查找、合并的复杂度也约为O(ElogE),因此算法的复杂度为O(ElogE)。又由于|E|的取值范围为【|V|-1】~【|V|(|V|-1)/2】=【|V|】~【|V|^2】,所以log|E|的范围是【log|V|】~【2log|V|】。因此O(logE)可以写作O(logV)。为了方便与Prim算法复杂度的比较,一般来说把Kruskal的复杂度表述为O(ElogV)
三、Prim、Kruskal、Prim+Heap算法效率实测
评测环境:WindowsXP,FreePascal2.40,Pentium(R) Dual-Core CPU T4300@2.10GHz,2G内存
通过上图可以看出:
1.Prim在稠密图中比Kruskal优,在稀疏图中比Kruskal劣。
2.Prim+Heap在任何时候都有令人满意的的时间复杂度,但是代价是空间消耗极大。【以及代码很复杂>_<】
3.时间复杂度并不能反映出一个算法的实际优劣。
竞赛所给的题大多数是稀疏图,所以尽可能地使用Prim+Heap吧,在稀疏图中这是无敌的。如果一定要在朴素Prim和Kruskal里选一个的话那就用Kruskal吧。当然Prim的代码比较简单,对付水题用Prim也无所谓,只要不是极稀疏图两者相差不大。