• 【贪心法求解最小生成树之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!

  • 相关阅读:
    构建之法十五
    十二周学习总结笔记
    构建之法十四
    构建之法十一
    构建之法十三
    用户体验评价
    构建之法第十章
    十一周总结学习笔记
    找水王
    十周总结学习笔记
  • 原文地址:https://www.cnblogs.com/yanlingyin/p/greedy.html
Copyright © 2020-2023  润新知