最小生成树:Kruskal算法
来源:互联网 发布:许晴 任盈盈 知乎 编辑:程序博客网 时间:2024/06/08 04:58
最小生成树:Kruskal算法
概览
Kruskal算法(克鲁斯卡尔算法)是一种用来寻找最小生成树的算法,由Joseph Kruskal在1956年发表。Kruskal算法是基于贪心的思想得到的。Kruskal算法在图中存在相同权值的边时有效。
问题
给定一个无向图,如果它任意两个顶点都联通并且是一棵树,那么我们就称之为生成树(Spanning Tree)。如果是带权值的无向图,那么权值之和最小的生成树,我们就称之为最小生成树(MST, Minimum Spanning Tree)。
由Kruskal算法,我们可以求得带权无向图的最小生成树的权值之和。
算法描述
算法思想
Kruskal算法是基于贪心的思想得到的。首先把所有的边按照权值先从小到大排列,接着按照顺序选取每条边,如果这条边的两个端点不属于同一集合,那么就将两个集合合并,直到所有的点都属于同一个集合为止。合并到一个集合时可以使用并查集优化。换言之,Kruskal算法就是基于并查集的贪心算法。
算法过程
- 记图中有V个顶点,E条边。
- 将所有边按权值从小到大排序。排序完成后,我们选择边AD,这样我们的图就变成了下图。
- 在剩下的边里继续寻找权值最小的,这里找到了CE,它的权值也是5。
- 依此类推找到DF,AB,BE。
- 下面继续选择到BC或EF。尽管现在长度为8的边是最小的未选择的边,但现在它们已经连通了(对于BC可以通过CE,EB连接,对于EF可以通过EB,BA,AD,DF连接),所以不需要选择BC或EF。类似地,BD也不需要选择。最后剩下EG和FG,选择权值更小的EG。
算法的简单证明
对图的顶点数n做归纳,证明Kruskal算法对任意n阶图适用:
当n=1时,显然能够找到最小生成树。
假设Kruskal算法对n=k阶图适用,那么,在k+1阶图G中,把权值最小的边的两个端点u和v做合并操作,即把u与v合为一个点v'
,把原来接在u和v的边都接到v'
上去,这样就能够得到一个k阶图G'
,G'
的最小生成树T'
可以用Kruskal算法得到。
下证T'+{<u,v>}
是G的最小生成树。
用反证法,如果T'+{<u,v>}
不是最小生成树,设最小生成树是T,即W(T)<W(T'+{<u,v>})
。显然T应该包含<u,v>
,否则,可以用<u,v>
加入到T中,删除环上原有的任意一条边,形成一棵更小权值的生成树,而T-{<u,v>}
是G'
的生成树(因为G'
就是由G中的u,v两点合并产生的),所以W(T')≤W(T-{<u,v>})
,即W(T'+{<u,v>})≤W(T)
。与W(T)<W(T'+{<u,v>})
矛盾。假设不成立。所以T'+{<u,v>}
是G的最小生成树,Kruskal算法对k+1阶图也适用。
由数学归纳法,Kruskal算法得证。
程序代码
#include <iostream>#include <algorithm>using namespace std;const int node_num = 100; //最大顶点数struct Edge{ int u, v, w;} e[node_num * node_num]; //边的两端点和权值bool cmp(const Edge &a, const Edge &b){ return a.w < b.w;}int main(){ int v_num; //顶点数 int a_num; //边数 int sum = 0; //生成树权值之和 int vset[node_num]; //记录各顶点所在的树 int k = 1; //记录生成树中顶点数量 cout << "v_num:"; cin >> v_num; cout << "a_num:"; cin >> a_num; for (int i = 0; i < v_num; ++i) { vset[i] = i; //初始化顶点所在的树 } for (int i = 0; i < a_num; ++i) { cin >> e[i].u >> e[i].v >> e[i].w; } sort(e, e + a_num, cmp); //按边权值从小到大排列 for (int index = 0; index < a_num; ++index) { int sn1 = vset[e[index].u], sn2 = vset[e[index].v]; //读取顶点所在的树 if (sn1 != sn2) //如果不在同一个树内 { sum += e[index].w; //加上这条边的权值 ++k; for (int i = 0; i < v_num; ++i) //遍历每个顶点,把属于sn2这棵树的顶点全部加入sn1这棵树 { if (vset[i] == sn2) { vset[i] = sn1; } } } } if (k == v_num) { cout << sum << endl; } else { cout << "E" << endl; //如果k不等于顶点数,说明不存在最小生成树 } return 0;}
运行结果:
算法优化
并查集与路径压缩概述
并查集(Union Find Sets)是一种用于管理分组的数据结构。它具备两个操作:查询元素a和元素b是否为同一组、将元素a和b合并为同一组。
有一组指针指向每个节点的父节点,根节点的指针指向自己,通过循环就可以找到根节点。
路径压缩,就是在每次查找时,找到根节点,然后令查找路径上的每个节点都直接指向根节点。
将a,b节点合并到同一棵树,就是将a节点的根节点的父节点指针指向b节点的根节点。
实现
int find(int x) {//查找根节点 int p = x, t; while (uset[p] != p){ p = uset[p]; }//返回根节点 while (x != p) { t = uset[x]; uset[x] = p; x = t; }//路径压缩 return x;}
例题
USACO agrinet / POJ 1258
描述
农民约翰被选为他们镇的镇长!他其中一个竞选承诺就是在镇上建立起互联网,并连接到所有的农场。当然,他需要你的帮助。
约翰已经给他的农场安排了一条高速的网络线路,他想把这条线路共享给其他农场。为了使花费最少,他想铺设最短的光纤去连接所有的农场。你将得到一份各农场之间连接费用的列表,你必须找出能连接所有农场并所用光纤最短的方案。每两个农场间的距离不会超过100000。
输入输出格式
第一行: 农场的个数,N(3<=N<=100)。
第二行..结尾:接下来的行包含了一个N*N的矩阵,表示每个农场之间的距离。当然,对角线将会是0,因为线路从第i个农场到它本身的距离在本题中没有意义。
只有一个输出,是连接到每个农场的光纤的最小长度和。
样例
SAMPLE INPUT:
4
0 4 9 21
4 0 8 17
9 8 0 16
21 17 16 0
SAMPLE OUTPUT:
28
程序代码
#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int node_num = 100;struct Edge{ int u, v, w;} e[node_num * node_num];bool cmp(const Edge &a, const Edge &b){ return a.w < b.w;}int main(){ int v_num; while (cin >> v_num) { int vi = 0, sum = 0, vset[node_num], k = 1; for (int i = 0; i < v_num; ++i) { vset[i] = i; for (int j = 0; j < v_num; ++j) { e[vi].u = i; e[vi].v = j; cin >> e[vi].w; ++vi; } } sort(e, e + vi, cmp); for (int index = 0; index < vi; ++index) { int sn1 = vset[e[index].u], sn2 = vset[e[index].v]; if (sn1 != sn2) { sum += e[index].w; ++k; for (int i = 0; i < v_num; ++i) { if (vset[i] == sn2) { vset[i] = sn1; } } } } cout << sum << endl; } return 0;}
POJ 3723
描述
Windy有一个村庄,他想建立一支军队来保护他的村庄。他选了N个女孩和M个男孩,想让他们做自己的士兵。在没有任何关系的情况下,他雇佣一个士兵需要10000元。有一些女孩和男孩之间存在关系,Windy可以利用这些关系省一些花费。如果女孩x和男孩y间存在关系d且他们中的一个人已经被雇佣了,那么Windy可以用10000-d元雇佣他们中的另一个。现在提供女孩和男孩之间的所有关系,你的任务是算出Windy最少要花多少钱。注意,雇佣一个士兵的时候最多只能用一个关系。
输入输出格式
第1行:测试数据组数。
每组测试数据第1行:3个整数N,M,R。
下面R行,每行包含3个整数xi,yi,di。
在每组测试数据后有一个空行。
1≤N,M≤10000
0≤R≤50000
每组输入数据对应一行输出数据。
样例
SAMPLE INPUT:
2
5 5 8
4 3 6831
1 3 4583
0 0 6592
0 1 3063
3 3 4975
1 3 2049
4 2 2104
2 2 7815 5 10
2 4 9820
3 2 6236
3 1 8864
2 4 8326
2 0 5156
2 0 1463
4 1 2439
0 4 4373
3 4 8889
2 4 3133
SAMPLE OUTPUT:
71071
54223
程序代码
#include <iostream>#include <cstring>#include <algorithm>#include <cstdio>using namespace std;const int node_num = 20000;int u[50000], v[50000], w[50000], pre[node_num], p[50000];bool cmp(const int &a, const int &b){ return w[a] > w[b];}int findroot(const int);int main(){ int n; scanf("%d", &n); for (int N = 0; N < n; ++N) { int ngirl, nboy, vi, sum = 0; scanf("%d%d%d", &ngirl, &nboy, &vi); for (int i = 0; i < ngirl + nboy; ++i) { pre[i] = i; } for (int i = 0; i < vi; ++i) { p[i] = i; } for (int i = 0; i < vi; ++i) { int t; scanf("%d%d%d", &u[i], &t, &w[i]); v[i] = t + ngirl; } sort(p, p + vi, cmp); for (int i = 0; i < vi; ++i) { int index = p[i]; int rootx = findroot(u[index]), rooty = findroot(v[index]); if (rootx != rooty) { sum += w[index]; pre[rooty] = rootx; } } sum = (ngirl + nboy) * 10000 - sum; printf("%d\n", sum); } return 0;}int findroot(const int x){ if (pre[x] == x) { return x; } return pre[x] = findroot(pre[x]);}
参考
- 最小生成树之Kruskal算法
- 最小生成树-Prim算法和Kruskal算法
- 【模版】并查集 及路径压缩
- Kruskal 最小生成树算法
- 最小生成树Kruskal算法
- 最小生成树----Kruskal算法
- 最小生成树kruskal算法
- 最小生成树 - Kruskal算法
- 最小生成树---Kruskal算法
- 最小生成树(kruskal算法)
- 最小生成树,kruskal算法
- 最小生成树--kruskal算法
- 最小生成树---kruskal算法
- 最小生成树--Kruskal算法
- 最小生成树 : Kruskal 算法
- 最小生成树-Kruskal算法
- 最小生成树 Kruskal算法
- 最小生成树kruskal算法
- 最小生成树kruskal算法
- 【Kruskal】算法 最小生成树
- 最小生成树kruskal算法
- 博弈论(阶梯博弈)POJ 1704
- Chrome浏览器中使用 iframe 嵌入网页导致视频不能全屏的问题解决方法
- JQuery 中阻止事件冒泡小案例
- L1-015. 跟奥巴马一起画方块
- ckeditor4.7集成
- 最小生成树:Kruskal算法
- 进程间通信——管道
- Python中的Number(数字)
- Android安装系统App(adb push实现)
- MySQL的MMM主主互备一从+KEEPALIVED实现高可用
- bootstrap学习使用——快速入门
- C语言开发面试题
- 集合类
- L1-016. 查验身份证