最小生成树

最小生成树,即求把一个图删除部分边以后,仍然能使图连通的树的边权和的最小值。
最小生成树有两种解法,一种是Prim算法,另一种是Kruskal算法

1.Prim算法

Prim算法的主要思想是从一个点开始,记录能连通到这个点的边权的最小值,最后相加求出答案。
假设用 \(dis\) 数组记录当前已经访问的点中能到达这个点的边权最小值,用 \(vis\) 数组记录这个点是否被访问过,如果被访问过,则选最小的这条边,对这条边所能到达的点的 \(dis\) 值取最小,最后如果有点没有被访问过,则图本身就不连通,输出无解;如果都访问过,则输出边权和。
主体部分的代码如下(用 vector 存边)

void Prim()
{
	int tot = 0, ans = 0;
	for (int i = 0; i <= n; ++i) dis[i] = INT_MAX;
	dis[1] = 0;
	while(1)
	{
		int u = 0;
		for (int i = 1; i <= n; ++i)
		{
			if (!vis[i] && dis[i] < dis[u])
			{
				u = i;
			}
		}
		if (u == 0)
		{
			break;
		}
		++ tot;
		vis[u] = 1;
		ans += dis[u];
		for (int i = 0; i < v[u].size(); ++ i)
		{
			dis[to] = min(dis[v[u][i].u], v[u][i].w);
		}
	}
	if (tot == n)
	{
		cout << ans;
	}
	else
	{
		cout << "-1";
	}
}

2.Kruskal算法

Kruskal算法本质就是先按照边权排序,再根据当前已选择的边来判断这条边是否要选,如果两个点已经连通,则不需要选;如果两个点未连通,则选这条边。
注意:Kruskal算法需要用到并查集,不会的自己学。并查集其实就是一棵树,同一个集团里的人都在同一棵树内,不同集团的人在不同的树内。因此,用 \(fa\) 数组记录祖先,就可以写出一下查找祖先的代码(含路径压缩,路径压缩就是把一条路径上的人的祖先全部指向一个人身上):


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

Kruskal算法整合起来,代码如下:

#include <bits/stdc++.h>
#define int long long
using namespace std;
int n, m;
struct edge
{
	int x, y, z;
}a[200020];
int fa[200020];
bool cmp(edge A, edge B)
{
	return A.z < B.z;
}
int find(int x)
{
	if (fa[x] != x) fa[x] = find(fa[x]);
	return fa[x];
}
signed main()
{
	cin >> n >> m;
	for (int i = 1; i <= m; ++ i)
	{
		cin >> a[i].x >> a[i].y >> a[i].z;
	}
	sort(a + 1, a + 1 + m, cmp);
	for (int i = 1; i <= n; ++ i) fa[i] = i;
	int cnt = n, ans = 0;
	for (int i = 1; i <= m; ++ i)
	{
		int fx = find(a[i].x), fy = find(a[i].y);
		if (fx != fy)
		{
			-- cnt;
			ans += a[i].z;
			fa[fx] = fy;
		}
		if (cnt == 1) break;
	}
	if (cnt <= 1) cout << ans;
	else cout << "-1";
}
posted @ 2025-04-28 20:55  langni2013  阅读(15)  评论(0)    收藏  举报