最小生成树——Kruskal算法

连通无回路的无向图称之为树,如果无向图G的生成子图T是树,则称T是G的生成树,生成树不止一种,其中各边权值和最小的树被称为最小生成树。

  • 边数等于点数减一
  • 没有环
  • 图连通并且边都为桥(就是说去掉任意一条边,图就不再连通)
  • 两点间路径唯一
  • 边权和最小

其中(1)为原无向图,(2)为其中一种生成树,(3)为其中一种最小生成树。


思维

kruskal算法也是一种贪心的算法,去除n阶无向图中所有的边,然后每次挑选所有边中权值最小的那一条边相连,如果连边会导致图形成环,就抛弃这条边,去寻找下一条边,直到图上存在n-1条边位置,即可得到最小生成树。
其选边过程为:

所以算法实现先对所有边按照权值进行排序,在以贪心思想接连构成边。采取避环的思想,直到得到一颗最小生成树。
其中实现的难点在于如何判断构成了环,首先利用搜素的思想遍历当然是可行的,但是时间复杂度较高。所以这时候就能用上之前提到过的并查集,当处于同一个集合的时候便可以判断二者之间有通路,若在链接便会构成环。
给出之前并查集的传送门:(https://www.cnblogs.com/pullself/p/10053829.html)


代码实现

时间复杂度为O(mlogm)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
const int MAX = 200005;

int pre[50050], ran[50050], ans = 0;

struct edge {
	int x, y;
	int w;
}e[MAX];

bool cmp(edge a, edge b) {
	if (a.w != b.w) return a.w < b.w;
	else return a.x < b.x;
}

void make_pre(int i)
{
	pre[i] = i;
	ran[i] = 0;
}

int find_pre(int i)
{
	if (pre[i] == i)
		return pre[i];
	return  pre[i] = find_pre(pre[i]);
}

int unite(int x, int y, int w) {
	int rootx, rooty;
	rootx = find_pre(x);
	rooty = find_pre(y);
	if (rootx == rooty) return 0;
	if (ran[rootx] > ran[rooty]) {
		pre[rooty] = rootx;
	}
	else {
		if (ran[rootx] == ran[rooty]) ran[rooty]++;
		pre[rootx] = rooty;
	}
	ans += w;
	return 1;
}

int main() {
	int n, m;
	cin >> n >> m;
	for (int i = 1; i <= n; i++) make_pre(i);
	for (int i = 1; i <= m; i++) {
		int x, y, w;
		cin >> x >> y >> w;
		e[i].x = x;
		e[i].y = y;
		e[i].w = w;
	}
	sort(e + 1, e + m + 1, cmp);
	ans = 0;
	int count = 0;
	for (int i = 1; i <= m; i++) {
		int flag = unite(e[i].x, e[i].y, e[i].w);
		if (flag) count++;
		if (count == n - 1) break;
	}
	cout << ans << endl;
	return 0;
}
posted @ 2018-12-16 14:20  水喵桑  阅读(421)  评论(0编辑  收藏  举报