最小生成树(Kruskal & Prim)

最小生成树

定义

对于一个带权连通无向图G=(V,E),生成树不同,每棵树的权(树中所有边上的权值和)也不同,设R为G的所有生成树的集合,若T为R中权值和最小的生成树,则T称为G的最小生成树(Minimum-Spanning-Tree,MST)

1、最小生成树可能有多个,但边的权值之和总是唯一且最小的
2、最小生成树的边数=定点数-1,砍掉一条则不连通,增加一条则会出现回路
3、若一个连通图本身就是一颗树,则其最小生成树就是它本身
4、只有连通图才有生成树,非连通图只有生成森林

如何求最小生成树?

两种算法:Kruskal算法和Prim算法。

Kruskal算法

在一个点集中,通过贪心的思想去找边权从小到大的边。这样,我们可以保证生成的树的边权之和最小。(证明请移步其他博客)

再通过并查集去查看两个点是否已经加入了生成树中(祖先是否相同)。如果已经在生成树中,则不加入这条边。这条边不仅仅是多余的,而且还不是最小值(前面加入最小生成树的边一定小)。

最后,判断是否能够生成一棵树。每一次合并两个点代表生成了一条边,故有n个点的点集应该有n-1条边。若没有,则说明这不是一棵有所有点的最小生成树。

由于Kruskal是使用的并查集,故对于稀疏图比较友好。

所以,它的时间复杂度为O(|n|log|n|)。

Prim算法

从一个点开始往外扩展,通过贪心的思想从小到大去扩展每一个点的连边。过程类似于BFS,每一次扩展一的点的所有边,找到权值最小的那一条。

写法也非常类似Dijkstra,甚至堆优化都和Dijkstra几乎一样。

由于时间复杂度较高:O(|n|^2),因此多用于稠密图。

Kruskal和Prim的区别

Kruskal算法适用于稀疏图(并查集有类似离散化的效果),而且时间复杂度相对较低,且是从点集中选取两点之间最短的边来建树。

Prim算法适用于稠密图,时间复杂度相对较高,且是从一个点开始用类似BFS的方式贪心扩展每一个点(边权值从小到大),直到扩展完所有的边。

代码

模板题:P3366 【模板】最小生成树

Prim算法:

因为是数据范围是5000,开个5000*5000的二维表问题不大。干就完事了。

朴素版Prim

#include <bits/stdc++.h>
#define inf 0x3f3f3f3f

using namespace std;

const int N = 5e3 + 10;

int g[N][N];
int dist[N];
bool st[N];
int n, m;

void prim()
{
	memset(dist, inf, sizeof dist);
	int res = 0;
	dist[1] = 0;
	
	for (int i = 1; i <= n; i++)
	{
		int t = -1;
		
		for (int j = 1; j <= n; j++)
			if (!st[j] && (t == -1 || dist[t] > dist[j]))
				t = j;
				
		if (dist[t] == inf)
		{
			puts("impossible");
			return;
		}
		
		res += dist[t];
		st[t] = true;
		
		for (int j = 1; j <= n; j++)
			if (!st[j] && dist[j] > g[t][j])
				dist[j] = g[t][j];
	}
	
	cout << res << endl;
}

int main()
{
	memset(g, inf, sizeof g);
	
	scanf("%d%d", &n, &m);
	
	for (int i = 1; i <= m; i++)
	{
		int a, b, w;
		scanf("%d%d%d", &a, &b, &w);
		g[a][b] = g[b][a] = min(g[a][b], w);
	}
	
	prim();
	
	return 0;
}

Kruskal算法

一定要记得初始化并查集fa[]数组!排序是按边权从小到大排序!

#include <bits/stdc++.h>
#define inf 0x3f3f3f3f

using namespace std;

const int N = 2e5 + 10;

struct edge
{
	int a, b, w;
	
	bool operator < (const edge &W) const
	{
		return w < W.w;
	}
};

edge e[N];
int fa[N];
int n, m, a, b, w;

int find(int x)
{
	if (fa[x] == x)
		return x;
	
	return fa[x] = find(fa[x]);
}

void merge(int x, int y)
{
	int fx = find(x);
	int fy = find(y);
	
	if (fx != fy)
		fa[fx] = fy;
}

int kruskal()
{
	for (int i = 1; i <= n; i++)
		fa[i] = i;
	
	sort(e + 1, e + 1 + m);
	
	int res = 0, cnt = 0;
	
	for (int i = 1; i <= m; i++)
	{
		int a = e[i].a, b = e[i].b, w = e[i].w;
		
		a = find(a), b = find(b);
		
		if (a != b)
		{
			fa[a] = b;
			cnt++;
			res += w;
		}
	}
	
	if (cnt != n - 1)
		return inf;
	else
		return res;
}

int main()
{
	scanf("%d%d", &n, &m);
	
	for (int i = 1; i <= m; i++)
	{
		scanf("%d%d%d", &a, &b, &w);
		e[i] = {a, b, w};
	}
	
	int t = kruskal();
	
	if (t == inf)
		puts("orz");
	else
		printf("%d\n", t);
	
	return 0;
}
posted @ 2023-03-04 11:39  煎饼Li  阅读(49)  评论(0编辑  收藏  举报