最小生成树

定义:
给定一张带边权的无向图G = (V, E), V中有n个顶点,由n个顶点和n-1条边构成的连通块就是G的一颗生成树,边权值最小的生成树就是最小生成树。
定理:任意一颗最小生成树一定含有无向图中权值最小的边e(x, y, z);

因为如果不含e,把e加进最小生成树中时会产生一个环,用e代替环中任意一条边,都能得到一条权值和更小的生成树。

图中权值为1的边一定在最小生成树中。
对于Kruskal和prim算法,我感觉都是要一条一条地找出必须加入最小生成树的边/点
Kruskal:
1.建立并查集表示连通块,初始状态每个点都是一个连通块,即fa[i] = i;
2.将所有边按权值由大到小排序,依次扫描每条边(x, y, z);
3.若x,y属于同一连通块,那么不连这条边,continue;
4.若x,y不属于一个连通块,合并两个连通块 fa[y] = x,把z累加进ans
5.扫描所有边
为什么4时一定要加入这条边呢?因为假设这条边是连接两个连通块1,2的边,最小生成树中一定要有
连通1,2的边,而这条边是其中权值最小的(因为我们经过排序了)
时间复杂度是o(mlogm)

#include <bits/stdc++.h>
using namespace std;
struct rec{
	int x, y, z;
}edge[500010];
int fa[100010], n, m, ans;
bool compare(rec a, rec b) {
	return a.z < b.z;
}
int find (int x) {
	if (fa[x] == x) return x;
	else return find (fa[x]);
}
int main () {
	cin >> n >> m;
	for (int i = 1; i <= m; i++) scanf("%d%d%d", &edge[i].x, &edge[i].y, &edge[i].z);
	sort(edge + 1, edge + m + 1, compare);
	for (int i = 1; i <= m; i++) fa[i] = i;
	for (int i = 1; i <= m; i++) {
		int x = find(edge[i].x), y = find(edge[i].y);
		if (x == y) continue;
		fa[x] = y;
		ans += edge[i]. z;
	}
	cout << ans << endl;
}

prim:
prim致力于维护最小生成树的一部分。维护一个最小生成树的节点集合T,剩余的节点集合为S,开始时T中只有一个点1,我们不断的找到一边(x, y, z),使得x,y分别处于S,T集合且是这一类边中权值最小的边,把x从S中删去,加入T集合,把z累加到ans中。
稠密图适用,时间复杂度o(n^2);
可以看出,prim算法很像朴素版dijkstra
和朴素版dijkstra的区别是:prim中 d[i] 是i到连通块的距离,不用加上之前走的路径,所以prim是=, dijkstra是 +=

#include <bits/stdc++.h>
using namespace std;
int n, m, ans = 0;
int a[3010][3010], d[3010];
bool v[3010];
void prim () {
	memset(d, 0x3f, sizeof d);
	memset(v, 0, sizeof v);
	d[1] = 0;
	for (int i = 1; i < n; i++) {
		int x = 0;
		for (int j = 1; j <= n; j++) {
			if (!v[j] && (x == 0 || d[j] < d[x])) x = j;
		}
		v[x] = 1;
		for (int y = 1; y <= n; y++)
			if (!v[y]) d[y] = min(d[y], a[x][y]);
	}
}
int main () {
	cin >> n >> m;
	memset(a, 0x3f, sizeof a);
	for (int i = 1; i <= n; i++) a[i][i] = 0;
	for (int i = 1; i <= m; i++) {
		int x, y, z;
		cin >> x >> y >> z;
		a[y][x] = a[x][y] = min(a[x][y], z);
	}
	prim();
	for (int i = 2; i <= n; i++) ans += d[i];
	cout << ans << endl;
	return 0;
}

一道prim的板子题

https://www.acwing.com/problem/content/1142/

农夫约翰被选为他们镇的镇长!

他其中一个竞选承诺就是在镇上建立起互联网,并连接到所有的农场。

约翰已经给他的农场安排了一条高速的网络线路,他想把这条线路共享给其他农场。

约翰的农场的编号是1,其他农场的编号是 2∼n。

为了使花费最少,他希望用于连接所有的农场的光纤总长度尽可能短。

你将得到一份各农场之间连接距离的列表,你必须找出能连接所有农场并使所用光纤最短的方案。

输入格式
第一行包含一个整数 n,表示农场个数。

接下来 n 行,每行包含 n 个整数,输入一个对角线上全是0的对称矩阵。
其中第 x+1 行 y 列的整数表示连接农场 x 和农场 y 所需要的光纤长度。

输出格式
输出一个整数,表示所需的最小光纤长度。

数据范围
3≤n≤100
每两个农场间的距离均是非负整数且不超过100000。

#include <bits/stdc++.h>
using namespace std;
int n, ans = 0;
int a[110][110], d[110];
bool v[110];
void prim () {
	memset(d, 0x3f, sizeof d);
	memset(v, 0, sizeof v);
	d[1] = 0;
	for (int i = 1; i < n; i++) {
		int x = 0;
		for (int j = 1; j <= n; j++) {
			if (!v[j] &&(x == 0 || d[j] < d[x])) x = j;
		}
		v[x] = 1;
		for (int y = 1; y <= n; y++) 
			if (!v[y]) d[y] = min(d[y], a[x][y]);
	}
}
int main () {
	cin >> n;
	memset(a, 0x3f, sizeof a);
	for (int i = 1; i <= n; i++) 
		for (int j = 1; j <= n; j++) cin >> a[i][j];
	prim();
	for (int i = 1; i <= n; i++) ans += d[i];
	cout << ans << endl;
		
	return 0;
}
posted @ 2022-04-21 15:40  misasteria  阅读(57)  评论(0编辑  收藏  举报