当前位置:   article > 正文

最小生成树—Prim算法_最小生成树prim算法

最小生成树prim算法

我们要讨论的问题是如何在一个无向图中找到它的最小生成树,虽然这个问题对有向图也有意义,但是处理起来更麻烦。

一个无向图 G 的最小生成树就是连接 G 上所有顶点的边构成的树,且这些边的总权值最低。当且仅当图是连通的才有最小生成树。

在无向图的最小生成树中,边的条数为|V|-1。最小生成树是一棵树,因为它无圈;边的总权值最低,所以它最小;包含所有顶点,所以是生成树。显然,最小生成树是包含所有顶点的最小的树。如果我们需要给一所房子里安装电路,那么这就是一个最小生成树问题。

对于任意生成树,如果向树中增加一条不属于树的边e,就会形成一个圈,除去圈中任意一条边,又会变回生成树。如果边e的值比除去的边值低,那么新的生成树的值就比原树小。如果在建立生成树时所添加的边在所有不成圈的边值中最小,那么最后得到的生成树就不能被改进,否则更改后的树要么成圈,要么权值更高。

最小生成树的建立方法有两种:Prim算法和Kruskal算法。下面以下图为例,从v_{1}出发,建立该图的最小生成树。

Prim算法:

计算最小生成树的方法是使其连续的一步步的成长。在每一步,都要把当前节点当作根节点并往上加边。 

Prim算法与Djkstra算法非常相似(Djkstra算法在前一篇文章),在处理当前顶点v时,先将其标记为已知,如果v的邻接顶点w未知,且对应的dvw要小于w当前的d,那么就需要更新w顶点的d以及path,与Djkstra算法不同的是,d表示的不再是从w到输入顶点s的距离,而是w与邻接顶点v的距离,所以其是否更新的判断法则也需要改变。这个算法也是一种贪婪算法,因为在每一步,我们所加入的都是目前权值最小的边,这个算法不会形成圈,因为在算法中不会再改变已知的顶点,并且每一步都将处理顶点视为根节点,所以任何顶点都只能与最后使其d改变的顶点直接相连。

下面说明Prim算法的具体过程,从v_{1}开始建立最小生成树:

vKnowndpath
v_{1}00-1
v_{2}0inf-1
v_30inf-1
v_40inf-1
v_50inf-1
v_60inf-1
v_70inf-1

 与Djkstra算法一样,首先找到d最小的未知顶点,即v_1,将其标记为已知,并更新与它邻接的未知顶点的信息,并且当前更新边中的最小边添加进树(这个操作实际上就是标记该节点为已知,则该节点和其path节点之间的边就被添加进树中,加上边(v_1,v_1)(假设有),总共七条边添加在树中,也就是所有的顶点都被标记为已知,算法就结束):

vKnowndpath
v_{1}10-1
v_{2}02v_{1}
v_304v_{1}
v_401v_{1}
v_50inf-1
v_60inf-1
v_70inf-1

接下来,d最小的未知顶点是v_4,将其标记为已知,并更新其邻接顶点信息,由于v_1已知,所以不考察v_1,而边(v_4,v_2)的值要大于目前v_2的d,所以v_2的信息不变,但边(v_4,v_3)的值小于v_3目前的d,所以v_3的信息要进行更新,而目前最小的边为(v_1,v_2),所以将它添加进树:

vKnowndpath
v_{1}10-1
v_{2}02v_{1}
v_302v_4
v_411v_{1}
v_507v_4
v_608v_4
v_704v_4

 下一个要选择的顶点是v_{2},并加入目前的最小边,显然它不会使任何信息改变(除过其自身的Known变为1)。

vKnowndpath
v_{1}10-1
v_{2}12v_{1}
v_302v_4
v_411v_{1}
v_507v_4
v_608v_4
v_704v_4

下面要选择的顶点是v_3,对其进行与之前顶点同样的处理:

vKnowndpath
v_{1}10-1
v_{2}12v_{1}
v_312v_4
v_411v_{1}
v_507v_4
v_605v_3
v_704v_4

下一步选择的顶点是v_7

vKnowndpath
v_{1}10-1
v_{2}12v_{1}
v_312v_4
v_411v_{1}
v_506v_7
v_601v_7
v_714v_4

接下来选择v_{6},并将最小的边加入树中,它不会对任何信息产生影响 :

vKnowndpath
v_{1}10-1
v_{2}12v_{1}
v_312v_4
v_411v_{1}
v_506v_7
v_611v_7
v_714v_4

 最后考察v_{5},它也不会对任何信息产生影响:

vKnowndpath
v_{1}10-1
v_{2}12v_{1}
v_312v_4
v_411v_{1}
v_516v_7
v_611v_7
v_714v_4

到此,就成功建立了该图的最小生成树,代码如下:

  1. void Prim(g* p) {
  2. int min, i, Dmin;
  3. for (;;) {
  4. Dmin = inf;//找到d最小的未知顶点
  5. for (i = 0; i < 7; i++) {
  6. if (p->v[i]->known == 0 && p->v[i]->d < Dmin) {
  7. min = i;
  8. Dmin = p->v[i]->d;
  9. }
  10. }
  11. if (Dmin == inf) {//满足这个条件时,要么顶点全部已知,要么图是不连通的
  12. break;
  13. }
  14. p->v[min]->known = 1;
  15. l* tmp = p->v[min]->next;
  16. while (tmp != NULL) {
  17. if (p->v[tmp->val]->known == 0) {
  18. if (p->v[tmp->val]->d > tmp->dvw) {//判断条件相比Djkstra算法改变
  19. p->v[tmp->val]->d = tmp->dvw;
  20. p->v[tmp->val]->path = min;
  21. }
  22. }
  23. tmp = tmp->next;
  24. }
  25. }
  26. }

使用两层循环的Prim算法时间复杂度为O(|V|^2),当图稠密时,使用这样的方法是好的,如果图是稀疏的,使用二叉堆的时间复杂度为O(|E|log|V|),对于稀疏图来说,这是一个好的界(当图是稠密的,则有|E|=O(|V|^2),所以使用两层循环是合适的,当图稀疏时,不再满足这个条件,那么 O(|E|log|V|)的界就比O(|V|^2)好很多了)。前面提到当且仅当图是连通的才具有最小生成树,判断图是否连通很简单,当程序中最外层的for循环终止后,如果图中仍存在顶点的d=inf,那么就说明图是不连通的。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/爱喝兽奶帝天荒/article/detail/820698
推荐阅读
相关标签
  

闽ICP备14008679号