转自: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本身是连通的,那么就形成了环。
建立并查集后,没有空集。因此,选择的条件就是:两个端点是否属于同一个集合,是则不需要合并,也不能选择,否则选择这条边,并且合并为一个集合。可见,选择是伴随集合的动态变化而变化的。
- #include <iostream>
- #include <algorithm>
- using namespace std;
- const int MAXN = 10000;
- struct edge {
- int a, b;
- int weight;
- bool operator < (const edge& cur) const
- {
- return weight < cur.weight;
- }
- };
- int par[MAXN];
- edge e[MAXN];
- edge MST[MAXN];
- void makeSet(int n)
- {
- for (int i = 0; i != n; i++) {
- par[i] = i;
- }
- }
- int getPar(int i)
- {
- if (par[i] != i) {
- par[i] = getPar(par[i]);
- }
- return par[i];
- }
- //假定可以合并
- //可以合并则合并并且返回真,不能合并则返回假,
- bool unionSet(int farther, int son)
- {
- int f = getPar(farther);
- int s = getPar(son);
- if (f == s)
- return false;
- par[s] = f;
- return true;
- }
- //结点0打印为结点A
- //仅提供主函数在结点数目很小的情况下使用,方便检查边
- char trans(int i)
- {
- return i+'A';
- }
- int main()
- {
- int n, m; //点数 边数
- int k = 0;
- int weight_sum = 0;
- cin >> n >> m;
- //假定输出无向图的边,其边数为2*m
- for (int i = 0; i != 2*m; i++) {
- cin >> e[i].a >> e[i].b >> e[i].weight;
- }
- makeSet(n);
- sort(e, e+2*m);
- for (int i = 0; i != 2*m; i++) {
- if (unionSet(e[i].a, e[i].b)) {
- MST[k++] = e[i];
- weight_sum += e[i].weight;
- }
- }
- //输出MST
- for (int i = 0; i != k; i++) {
- cout << trans(MST[i].a) << "->" << trans(MST[i].b) << " "
- << "weight = " << MST[i].weight << endl;
- }
- cout << "weight_sum = " << weight_sum << endl;
- return 0;
- }
- /*
- 测试数据参考
- 点数+边数
- 两端点+边权值
- //图片请参考 百度百科kruskal的图片
- 7 11
- 0 1 7
- 0 3 5
- 1 0 7
- 1 2 8
- 1 3 9
- 1 4 7
- 2 1 8
- 2 4 5
- 3 0 5
- 3 1 9
- 3 4 15
- 3 5 6
- 4 1 7
- 4 2 5
- 4 3 15
- 4 5 8
- 4 6 9
- 5 3 6
- 5 4 8
- 5 6 11
- 6 4 9
- 6 5 11
- 对应输出:
- A->D weight = 5
- E->C weight = 5
- F->D weight = 6
- E->B weight = 7
- B->A weight = 7
- E->G weight = 9
- weight_sum = 39
- */
上面测试数据对应的图为:
为了验证代码的正确性,找个简单的题目测试一下 hdoj1162
这个题目用Prim算法可能更加适合,但数据量很小,根据n个点建立(n(n-1))/2条边也不超过5000条,虽然是稠密图,但用卡鲁斯卡尔算法也没问题
AC代码如下:
- #include <iostream>
- #include <algorithm>
- #include <cmath>
- #include <cstdio>
- using namespace std;
- const int MAXN = 110;
- //点结构
- struct point {
- double x, y;
- };
- //边结构 并重载操作符<
- struct edge {
- double a, b;
- double weight;
- bool operator < (const edge& cur) const
- {
- return weight < cur.weight;
- }
- };
- point p[MAXN];
- edge e[MAXN*MAXN/2];
- int par[MAXN];
- void makeSet(int n)
- {
- for (int i = 0; i != n; i++) {
- par[i] = i;
- }
- }
- int getPar(int i)
- {
- if (par[i] != i) {
- par[i] = getPar(par[i]);
- }
- return par[i];
- }
- //假定可以合并
- //可以合并则合并并且返回真,不能合并则返回假,
- bool unionSet(int farther, int son)
- {
- int f = getPar(farther);
- int s = getPar(son);
- if (f == s)
- return false;
- par[s] = f;
- return true;
- }
- inline double dist(double x1, double x2, double y1, double y2)
- {
- return sqrt( (x1-x2)*(x1-x2)+(y1-y2)*(y1-y2) );
- }
- int main()
- {
- int n;
- while (cin >> n) {
- int cnt = 0;
- double weight_sum = 0.0;
- //input point
- for (int i = 0; i != n; i++) {
- cin >> p[i].x >> p[i].y;
- }
- //make edge by points, cnt will equal to (n*(n-1))/2
- for (int i = 0; i != n; i++) {
- for (int j = i+1; j != n; j++) {
- e[cnt].a = i;
- e[cnt].b = j;
- e[cnt].weight = dist(p[i].x, p[j].x, p[i].y, p[j].y);
- ++cnt;
- }
- }
- makeSet(n);
- sort(e, e+cnt);
- //kruskal algorithm
- for (int i = 0; i != cnt; i++) {
- if (unionSet(e[i].a, e[i].b)) {
- weight_sum += e[i].weight;
- }
- }
- printf("%.2lf\n", weight_sum);
- }
- return 0;
- }
- /*
- 参考测试数据
- 3
- 1.0 1.0
- 2.0 2.0
- 2.0 4.0
- 3
- 1.0 1.0
- 2.0 2.0
- 3.0 3.0
- 输出为
- 3.41
- 2.83
- */