【贪心法求解最小生成树之Kruskal算法详细分析】---Greedy Algorithm for MST

初衷:

最近在看算法相关的东西,看到贪心法解决mst的问题,可惜树上讲解的不是很清新,到网上找了很多资料讲解的也不透彻

只是随便带过就草草了事、这几天抽空看了下,总算基本思路理清楚了

主要还是得感谢强大的google,帮我找到一个很好的英文资料。(下面有链接,有兴趣的同学可以看看)

理顺了思路,就和大家分享下~希望对学习贪心法的同学会有所帮助。

 

这篇博客的主要内容是贪心法求解Minimum Spanning Tree (MST)(最小生成树)的问题

贪心法求解最小生成树常用的有两种算法,分别是Prim’s MST algorithm和Kruskal's MST algorithm(prim算法和kruskal算法)

这里主要说的是kruskal算法

最小生成树的简单定义:

给定一股无向联通带权图G(V,E).E 中的每一条边(v,w)权值位C(v,w)。如果G的子图G'是一个包含G中所有定点的子图,那么G'称为G的生成树,如果G'的边的权值最小

那么G'称为G的最小生成树

kruskal算法的基本思想:

1.首先将G的n个顶点看成n个孤立的连通分支(n个孤立点)并将所有的边按权从小大排序。

2.按照边权值递增顺序,如果加入边后存在圈则这条边不加,直到形成连通图

对2的解释:如果加入边的两个端点位于不同的连通支,那么这条边可以顺利加入而不会形成圈

本例中用到的图:

权值递增排序:

kruskal加边后情况:

所以对于任意边(u,v)要判断这两个点是不是存在于同一个连通支里。

  如果是,则舍弃这条边,接着判断另一条边

  如果不是,则把这条边加入到图中,并且把u,v属于的连通支合并

然后操作下一条边

这个算法执行的过程就是按照规定一个个连通支合并的过程,使最后只剩一个连通支。

What kind of data structure supports such operations?

这是一个值得思考的问题、、、逛社区的时候,有用链表的、二维数组的、、这里不讨论这些存储结构的可行性

这里要讨论的是有向树的存储

some implementation details(基本操作)

makeset(x): create a singleton set containing just x            //初始化的时候把整个图分为n个独立连通块

find(x): to which set does x belong?                        //对于任意给定点x,判断x属于哪一个连通块

union(x, y): merge the sets containing x and y      //合并两个连通块其中,x,y为某边的两个端点,如果通过上面的find操作属于不同的连通块才把他们合并

 

Algorithm(算法实现) :                                   

Kruskal(G)

1.For all u∈V do

          makeset(u);            //初始化,让每个点成为独立的连通块

2. X={Æ};                      

3. Sort the edges E by weight;             //按边的权值大小排序

4. For all edges (u, v) ∈ E in increasing order of weight do           //对于条边e(u,v)(权值递增顺序)判断能否加入到图中

       if find(u) ≠find(v) then                         //如两个端点不在同一个连通块,则合并这两个连通块

           add edge (u, v) to X;

           union(u, v);

下面是算法中的实现细节

How to store a set? (如何存储连通块)

例子;

{B, E}

      

{A, C, D, F, G, H}

对于每一个联通块,还有两个需要保存的,也就是树的根节点rank和树高height

Root: its parent pointer points to itself.

Rank: the height of subtree hanging from that node.

还有一个会用到的关系,对于树中的点x,p(x)表示x的父节点

下面是函数实现

Makeset(x)

1.P(x)=x;                      //Constant time operation

2.Rank(x)=0;

Find(x)

1.While x ≠P(x)  do          //The time taken is proportional to the height of the tree.

    x=P(x);

 2. return(x);

 执行上述操作后的实例:

After makeset(A), makeset(B), …, makeset(G).(执行makeset后)

 每个点成为了孤立的连通支,右上角的数字代表树的rank

After union(A,D), union(B,E), union(C, F).(合并AD,BE,CF后)

After union(C,G), union(E,A).(合并CG,EA后)

注意看新的连通支右上角的rank有变化,合并的过程中尽量使得rank达到最小

After union(B,G).

关于Rank的几点说明:

Property 1: For any x, rank(x) < rank(P(x)).                         对于任意x,x的rank小于他的父节点的rank

Property 2: Any root node of rank k has at least 2k nodes in its tree.    任何rank 为k 的连通支至少有2k个节点

Property 3: If there are n elements overall, there can be at most n/2k nodes of rank k.         如果一共有n个节点,那么rank 为k的连通支一共有n/2k

对property2的解释:因为union的原则是让union后的树rank最小,所以union后的树至少是二叉树,也就是说除叶子节点外的节点至少有两个孩子。

对property3的解释:因为rank为k 的树至少有2k 个节点,所以最多有n/2k个


算法效率分析:

Kruskal(G)

1.For all u∈V do

          makeset(u);            //初始化,让每个点成为独立的连通块

2. X={Æ};                      

3. Sort the edges E by weight;             //按边的权值大小排序

4. For all edges (u, v) ∈ E in increasing order of weight do           //对于条边e(u,v)(权值递增顺序)判断能否加入到图中

       if find(u) ≠find(v) then                         //如两个端点不在同一个连通块,则合并这两个连通块

           add edge (u, v) to X;

           union(u, v);


上面的算法中

makeset():可以在常数时间内完成

sort edges :对边的权值进行排序的效率O(|E|log|V|)   (排序算法的时间效率、自己google不啰嗦)

find():由给定的点往上查找,直到树根为止所花时间为树的高度即log|V|。

注意:如何确定find()执行的次数是一个值得考虑的问题

如果从点的角度,是很难得到准确答案的,因为每个对于某一个点,和他相连的点是不确定的, 即不通过的点情况不同,要逐一考虑岂不很麻烦

其实find()执行的次数是和边数紧密相连的。请看算法中,循环的体是依据边的权值顺序展开的。而对于每一条我们考虑的边,都要考虑它连接的两个点

所以,find()的执行次数就是边数的两倍。执行一个finad()的效率是log|V|,而union基本可在常数时间内完成

所以

Union and Find operations: O(|E|log|V|)

 


 其实这个算法的思想很简单、每一次选最小的,如果符合条件就把它加入到我们的结果中,如果不满足条件,则选下一个最小的

只是实现起来考虑的需要多一些、比如用何种数据结构存储、判断两个点是不是属于同一个连通支等等

可见,贪心法只是提供一种解决的基本思路,要真正解决问题还要考虑如何实现、这也是很关键的一点。

 

如果你看到这里,可能会发现这个算法的效率不是很可观、在最后的union and find 中所用的时间和点的数量n有很大的关系。

如果n很大的话,就会花很多时间。

那么、这个算法的效率可以提高吗?

答案是肯定的。用到的技术为

Amortized Analysis   平摊分析(也叫摊销分析),这可以说是一种很神奇的思想,先透露一下吧

对于这个问题的union and find 操作,本文中的效率为O(|E|log|V|)。Amortized Analysis后,可以让复杂度为:O(|E|Log*n)

Log*n是个什么东西呢?当n为宇宙中所有物质的数量的时候,Log*n<=8

也就是让上文中的log(n)的最大值降到8.

如何?是不是很客观的效率提升。。。。

 有兴趣的同学可以关注我的下一篇博客、会针对本文的问题详细解释Amortized Analysis !!

参考资料:

http://en.wikipedia.org/wiki/Minimum_spanning_tree

http://www.cs.berkeley.edu/~vazirani/algorithms/chap5.pdf

 

如果有不对的地方、希望各位能指出。

 

本文博主原创如有转载请注明出处http://www.cnblogs.com/yanlingyin/

 

thanks!

 

posted @ 2011-11-16 20:19  Geek_Ling  阅读(18956)  评论(5编辑  收藏  举报