【刷题】图论——最小生成树:Prim【模板】


假设图有n个点m条边

1、朴素版Prim O(n^2)

通常用于稠密图。

1.1、算法步骤

  • 初始化所有点的 d i s [ i ] = ∞ dis[i]=\infty dis[i]=,S为空集
  • f o r ( i = 0 ; i < n ; i + + ) for (i=0; i < n; i ++ ) for(i=0;i<n;i++)
    • 找到不在集合S中,dis最小的点x。(S为空集则随便找一个点)
    • x加入S中
    • 用x更新S外的点到集合S的距离dis

其他点到集合S的距离:假设某个点有多条边连向集合内的点,其中最小的边就是这个点到集合的距离;如果没有边连向集合内的点,则距离为 ∞ \infty
因为这边新加进集合的点只有x,因此只要看与x连接的最小的边即可。

1.3、算法实现

在这里插入图片描述

题目链接

稠密图采用邻接矩阵存储。
res更新要写在dis更新前面,防止出现负的自环使dis变小,或者已经进入S的点的dis再被更新,计入res中。
最好去掉自环。

#include <iostream>
#include <cstring> 
using namespace std;

const int N = 505, INF = 0x3f3f3f3f;
int n, m;
int g[N][N], dis[N];
bool flag[N];

int prim() {
	memset(dis, 0x3f, sizeof dis);
	memset(flag, 0, sizeof flag);
	int res = 0;
	for (int i = 0; i < n; i ++ ) {
		int x = -1;
		for (int j = 1; j <= n; j ++ ) {
			if (!flag[j] && (x == -1 || dis[x] > dis[j])) x = j;
		}
		
		flag[x] = true;
		
		// 不是0号点且最近的点也是无穷,代表图不连通,无最小生成树 
		if (i && dis[x] == INF) return INF;
		// res更新要写在dis更新前面,防止出现负的自环使dis更新,或者已经进入S的点的dis再被更新,计入res中。 
		if (i) res += dis[x];
		
		for (int j = 1; j <= n; j ++ ) {
			dis[j] = min(dis[j], g[x][j]);
		}
	}
	return res; 
}

int main() {
	scanf("%d%d", &n, &m);
	memset(g, 0x3f, sizeof g);
	
	while (m -- ) {
		int u, v, w;
		scanf("%d%d%d", &u, &v, &w);
		if (u != v) // 最好去掉自环 
			g[u][v] = g[v][u] = min(g[u][v], w); // 重边保留最小
	}
	
	int ans = prim();
	if (ans == INF) printf("orz\n");
	else printf("%d\n", ans);
	return 0;
} 

堆优化版Prim O(mlogn)

由于时间复杂度和kruskal差不多,且写起来较复杂,故很少用。

思路和迪杰斯特拉堆优化相同,在 “找到不在集合S中dis最小的点x” 这步采用堆优化。

注意几点细节:

  1. 去掉自环,防止负自环影响答案
  2. 在 “用x更新S外的点到集合S的距离dis” 这步需要加:
    if (flag[y] == true) continue; // 防止已经进入S的点的dis再被更新
    此处和迪杰斯特拉不同,因为最小生成树边不要求正,所以可能重复更新。
  3. 计算完所有dis,如果存在dis[i]=INF则代表这个点永远走不到。
#include <iostream>
#include <cstring> 
#include <queue> 
using namespace std;

const int N = 10005, M = 500005, INF = 0x3f3f3f3f;
typedef pair<int, int> PII;

int n, m;
int dis[N];
int head[N], lnk[M], val[M], nxt[M], idx;
bool flag[N];

void add (int u, int v, int w) {
	lnk[idx] = v;
	val[idx] = w;
	nxt[idx] = head[u];
	head[u] = idx ++ ;
}

int prim() {
	memset(dis, 0x3f, sizeof dis);
	memset(flag, 0, sizeof flag);
	int res = 0;
	priority_queue<PII, vector<PII>, greater<PII> > heap;
	heap.push({0, 1}); // {dis[1], 1号点}
	dis[1] = 0;
	
	while (!heap.empty()) {
		int x = heap.top().second;
		heap.pop();
		if (flag[x]) continue;
		flag[x] = true;
		
		for (int i = head[x]; i != -1; i = nxt[i]) {
			int y = lnk[i], w = val[i];
			if (flag[y] == true) continue; // 防止已经进入S的点的dis再被更新
			dis[y] = min(dis[y], w);
			heap.push({dis[y], y});
		}
	}
	
	for (int i = 1; i <= n; i ++) {
		// 从1号点出发永远无法走到i,代表图不连通,无最小生成树 
		if (dis[i] == INF) return INF;
		res += dis[i];
	}
	
	return res; 
}

int main() {
	scanf("%d%d", &n, &m);
	memset(head, -1, sizeof head);
	while (m -- ) {
		int u, v, w;
		scanf("%d%d%d", &u, &v, &w);
		if (u != v) {// 最好去掉自环 
			add(u, v, w);
			add(v, u, w);
		}
	}
	int ans = prim();
	if (ans == INF) printf("orz\n");
	else printf("%d\n", ans);
	return 0;
} 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值