Kruskal算法求最小生成树 附hdoj1162

171 篇文章 0 订阅
63 篇文章 0 订阅

转自:http://blog.csdn.net/architect19/article/details/8924538

自己实现的kruskal算法。

其实懂了并查集,实现kurskal算法便很简单了。

按照克鲁斯卡尔算法,从最小的边开始选(所以把所有边按权值非降序排序),然后选的过程一定是从小到大地选,如果发现不适合的就抛弃,而且绝对不会回溯再次检查是否适合(不吃回头艹)。因此如何判断边适合就是算法的关键了。

这里的关键其实就是并查集,并查集是用集合的观点来看结点与结点的关系。结点作为元素,只有属于和不属于某个集合。当日,集合要求元素不能重复。

集合内点与点的关系只有:同属于一个集合。图或树中的点,它们之间的关系很多,什么左孩子右孩子,爷爷孙子等等。但在集合中,这些都不需要考虑。

并查集其实就是带有快速合并查询功能的动态集合。

建立并查集,想象为图或树的每个元素画一个圈,即每个元素分别看成一个集合,同时也看成一棵树,而树根就是自己,树根的标志的结点的父母是自己,因此初始化为par[i] = i

然后图或树中两个元素合并,合并前会查询是否同属于一个集合(查询就是看树根是否一样,刚开始肯定都不一样),是则不理睬,否则合并,合并的时候把其中一棵树作为另一棵树的孩子。

假如查询前,树的深度很大,形状几乎是线性的,那么查询的时候,递归压栈完毕后就找到了树根,那么回溯(出栈)时,每一层的子树的树根都直接修改指向树根。这样下次查询时就是O(1)的效率了(这里被称作路径压缩)。查询的效率实际测试是非常高的,但理论分析需要作平摊分析,这个我还没搞懂。

搞懂并查集后就好办了。

选择适合的边,即这条边加入后树不能出现环路(否则就不是树了),其实就是说这条边的两个端点不能属于同一个非空集合,而一个并查集是非空的,而且是一棵树(现在的形状是连通的,原来的形状也一定是连通的,只是集合中不再关系如何连接,只关心 是否 连通)。若边x的两个端点a, b属于同一个非空集合c(代表元素是c),x加入后,c既连通了a,又连通了b,而ab本身是连通的,那么就形成了环。

建立并查集后,没有空集。因此,选择的条件就是:两个端点是否属于同一个集合,是则不需要合并,也不能选择,否则选择这条边,并且合并为一个集合。可见,选择是伴随集合的动态变化而变化的。

[cpp]  view plain copy
  1. #include <iostream>  
  2. #include <algorithm>  
  3. using namespace std;  
  4. const int MAXN = 10000;  
  5.   
  6. struct edge {  
  7.     int a, b;  
  8.     int weight;  
  9.     bool operator < (const edge& cur) const  
  10.     {  
  11.         return weight < cur.weight;  
  12.     }  
  13. };  
  14.   
  15. int par[MAXN];  
  16. edge e[MAXN];  
  17. edge MST[MAXN];  
  18.   
  19. void makeSet(int n)  
  20. {  
  21.     for (int i = 0; i != n; i++) {  
  22.         par[i] = i;  
  23.     }  
  24. }  
  25.   
  26. int getPar(int i)  
  27. {  
  28.     if (par[i] != i) {  
  29.         par[i] = getPar(par[i]);  
  30.     }  
  31.     return par[i];  
  32. }  
  33.   
  34. //假定可以合并  
  35. //可以合并则合并并且返回真,不能合并则返回假,  
  36. bool unionSet(int farther, int son)  
  37. {  
  38.     int f = getPar(farther);  
  39.     int s = getPar(son);  
  40.     if (f == s)  
  41.         return false;  
  42.     par[s] = f;  
  43.     return true;  
  44. }  
  45.   
  46. //结点0打印为结点A  
  47. //仅提供主函数在结点数目很小的情况下使用,方便检查边  
  48. char trans(int i)  
  49. {  
  50.     return i+'A';  
  51. }  
  52.   
  53. int main()  
  54. {  
  55.     int n, m; //点数 边数  
  56.     int k = 0;  
  57.     int weight_sum = 0;  
  58.   
  59.     cin >> n >> m;  
  60.       
  61.     //假定输出无向图的边,其边数为2*m  
  62.     for (int i = 0; i != 2*m; i++) {  
  63.         cin >> e[i].a >> e[i].b >> e[i].weight;  
  64.     }  
  65.   
  66.     makeSet(n);  
  67.     sort(e, e+2*m);  
  68.       
  69.     for (int i = 0; i != 2*m; i++) {  
  70.         if (unionSet(e[i].a, e[i].b)) {  
  71.             MST[k++] = e[i];  
  72.             weight_sum += e[i].weight;  
  73.         }  
  74.     }  
  75.   
  76.     //输出MST  
  77.     for (int i = 0; i != k; i++) {  
  78.         cout << trans(MST[i].a) << "->" << trans(MST[i].b) << " "  
  79.              << "weight = " << MST[i].weight << endl;  
  80.     }  
  81.     cout << "weight_sum = " << weight_sum << endl;  
  82.     return 0;  
  83. }  
  84.   
  85. /* 
  86.   测试数据参考 
  87.    
  88. 点数+边数 
  89. 两端点+边权值 
  90. //图片请参考 百度百科kruskal的图片 
  91.  
  92. 7 11 
  93. 0 1 7 
  94. 0 3 5 
  95. 1 0 7 
  96. 1 2 8 
  97. 1 3 9 
  98. 1 4 7 
  99. 2 1 8 
  100. 2 4 5 
  101. 3 0 5 
  102. 3 1 9 
  103. 3 4 15 
  104. 3 5 6 
  105. 4 1 7 
  106. 4 2 5 
  107. 4 3 15 
  108. 4 5 8 
  109. 4 6 9 
  110. 5 3 6 
  111. 5 4 8 
  112. 5 6 11 
  113. 6 4 9 
  114. 6 5 11 
  115.  
  116. 对应输出: 
  117. A->D weight = 5 
  118. E->C weight = 5 
  119. F->D weight = 6 
  120. E->B weight = 7 
  121. B->A weight = 7 
  122. E->G weight = 9 
  123. weight_sum = 39 
  124.  
  125.  */  

上面测试数据对应的图为:


为了验证代码的正确性,找个简单的题目测试一下 hdoj1162

这个题目用Prim算法可能更加适合,但数据量很小,根据n个点建立(n(n-1))/2条边也不超过5000条,虽然是稠密图,但用卡鲁斯卡尔算法也没问题


AC代码如下:

[cpp]  view plain copy
  1. #include <iostream>  
  2. #include <algorithm>  
  3. #include <cmath>  
  4. #include <cstdio>  
  5. using namespace std;  
  6. const int MAXN = 110;  
  7.   
  8. //点结构  
  9. struct point {  
  10.     double x, y;  
  11. };  
  12.   
  13. //边结构 并重载操作符<  
  14. struct edge {  
  15.     double a, b;  
  16.     double weight;  
  17.     bool operator < (const edge& cur) const  
  18.     {  
  19.         return weight < cur.weight;  
  20.     }  
  21. };  
  22.   
  23. point p[MAXN];  
  24. edge e[MAXN*MAXN/2];  
  25. int par[MAXN];  
  26.   
  27. void makeSet(int n)  
  28. {  
  29.     for (int i = 0; i != n; i++) {  
  30.         par[i] = i;  
  31.     }  
  32. }  
  33.   
  34. int getPar(int i)  
  35. {  
  36.     if (par[i] != i) {  
  37.         par[i] = getPar(par[i]);  
  38.     }  
  39.     return par[i];  
  40. }  
  41.   
  42. //假定可以合并  
  43. //可以合并则合并并且返回真,不能合并则返回假,  
  44. bool unionSet(int farther, int son)  
  45. {  
  46.     int f = getPar(farther);  
  47.     int s = getPar(son);  
  48.     if (f == s)  
  49.         return false;  
  50.     par[s] = f;  
  51.     return true;  
  52. }  
  53.   
  54. inline double dist(double x1, double x2, double y1, double y2)  
  55. {  
  56.     return sqrt( (x1-x2)*(x1-x2)+(y1-y2)*(y1-y2) );  
  57. }  
  58.   
  59. int main()  
  60. {  
  61.     int n;  
  62.     while (cin >> n) {  
  63.         int cnt = 0;  
  64.         double weight_sum = 0.0;  
  65.   
  66.         //input point  
  67.         for (int i = 0; i != n; i++) {  
  68.             cin >> p[i].x >> p[i].y;  
  69.         }  
  70.         //make edge by points, cnt will equal to (n*(n-1))/2  
  71.         for (int i = 0; i != n; i++) {  
  72.             for (int j = i+1; j != n; j++) {  
  73.                 e[cnt].a = i;  
  74.                 e[cnt].b = j;  
  75.                 e[cnt].weight = dist(p[i].x, p[j].x, p[i].y, p[j].y);  
  76.                 ++cnt;  
  77.             }  
  78.         }  
  79.         makeSet(n);  
  80.         sort(e, e+cnt);  
  81.         //kruskal algorithm  
  82.         for (int i = 0; i != cnt; i++) {  
  83.             if (unionSet(e[i].a, e[i].b)) {  
  84.                 weight_sum += e[i].weight;  
  85.             }  
  86.         }  
  87.         printf("%.2lf\n", weight_sum);  
  88.     }  
  89.     return 0;  
  90. }  
  91.   
  92. /* 
  93. 参考测试数据 
  94. 3 
  95. 1.0 1.0 
  96. 2.0 2.0 
  97. 2.0 4.0 
  98.  
  99. 3 
  100. 1.0 1.0 
  101. 2.0 2.0 
  102. 3.0 3.0 
  103.  
  104. 输出为 
  105. 3.41 
  106. 2.83 
  107.  */  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值