最小生成树算法总结

最小生成树算法框架

在这里插入图片描述
什么是最小生成树?
一个图有 n 个结点,很多条边。最小生成树需要包含原图中的 n 个结点,并且有保持图连通的最少的边,且使这个树的权值达到最小

遇到稠密图就用朴素版prim,稀疏图就用kruskal即可,堆优化版的prim没有必要掌握。


朴素版prim

朴素版 prim 和 dijkstra 算法很像。
dijkstra 中的 s集合是确定最短路的点
prim 中的 s集合是已经被加入到最小生成树的点
在这里插入图片描述
这里的集合s代表 在当前已经被加入到最小生成树的点。

每循环一次,都可以将一个点加入 s,循环 n 次后,所有点都可以加入最小生成树了。

举个例子: 求下图的最小生成树:
在这里插入图片描述
① 我们选 A 为起点,将 A 加入集合s
在这里插入图片描述
② 集合s为{A},与 s 最近的是 D,将 D 加入s
在这里插入图片描述
③ 集合s为{A、D},与 s 最近的是 B,将 B 加入s
在这里插入图片描述
④ 集合s为{A、B、D},与 s 最近的是 E,将 E 加入s
在这里插入图片描述
⑤ 集合s为{A、B、D、E},与 s 最近的是C,将 C 加入s
在这里插入图片描述
耶!所有点都进集合了!我们的最小生成树就得到了:)
在这里插入图片描述
例题:AcWing 858. Prim算法求最小生成树
在这里插入图片描述

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std; 
const int N = 510,INF = 0x3f3f3f3f;
int n,m;
int g[N][N]; // 邻接矩阵的存储
int dist[N]; // 存储点到 集合s 的最短距离 
bool st[N];  // true为在 集合s 内 

int prim()
{
	memset(dist,0x3f,sizeof(dist));
	
	int res = 0; //最小生成树的所有边的权重之和 
	for(int i=0; i<n; i++)
	{
		int t = -1;
		for(int j=1; j<=n; j++) //找到当前在 集合s外 且距离 集合s 最小的点t
		  if(!st[j] && (t == -1 || dist[t] > dist[j]))
		    t = j;	
		
		if(i && dist[t] == INF) return INF; //当前图不连通,不存在最小生成树
		if(i) res += dist[t];   //将这个点与集合s中的某相连的点的权值加入答案
	
		for(int j=1; j<=n; j++) dist[j] = min(dist[j],g[t][j]);	 //用 t 更新其他点到集合的距离  
		st[t] = true;  //将 t 加入 集合s 
	}
	return res;
}

int main()
{
	cin >> n >> m;
	memset(g,0x3f,sizeof(g));
	
	while(m--)
	{
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		g[a][b] = g[b][a] = min(g[a][b],c); //无向图相反要建两条变 ,重边的话只取权值最小的 
	}
	int  t = prim();
	
	if(t == INF) cout<<"impossible";
	else cout<<t;
}

注意: 下面这两部顺序不能颠倒的! 要先把那个边的权值加入答案中,再去更新其他点。因为可能出现自环。

if(i) res += dist[t];   //将这个点与集合s中的某相连的点的权值加入答案
	
for(int j=1; j<=n; j++) dist[j] = min(dist[j],g[t][j]);	 //用 t 更新其他点到集合的距离 

举个例子哈 :)
在这里插入图片描述
刚开始集合里是A,A将B的dist更新为3,当B进入集合,我们要用B去更新其他点的到集合的最短距离时,B会将自己更新为-2,更新后再将-2加入答案是不对的,因为我们要求的是最小生成树,树是不能有环的,所以要先将3加入答案,再用B更新!!!


kruskal

此算法需要用到并查集!排序用 sort 即可。sort的时间复杂度为O(mlogm)
在这里插入图片描述

举个例子: 求此图的最小生成树
在这里插入图片描述
对所有边排序后,刚开始所有点都是不连通的。
① 枚举最小边A——D,A与D不连通,合并A与D
在这里插入图片描述
② 枚举B——E,B与E不连通,合并B与E
在这里插入图片描述
③ 枚举B——C,B与C不连通,合并B与C
在这里插入图片描述
④ 枚举A——C,A与C不连通,合并A与C
在这里插入图片描述
⑤ 枚举D——E,D与E连通,不合并
⑥ 枚举A——B,A与B连通,不合并
⑦ 枚举C——E,C与E连通,不合并
⑧ 所有边都已经枚举过了,最小生成树就得到了:)
在这里插入图片描述
例题:AcWing 859. Kruskal算法求最小生成树
在这里插入图片描述

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std; 
const int N = 2e5+10;
int n,m;
int p[N]; // 并查集的p数组
 
struct Edge
{
	int a,b,w; // a—>b的边权值为w 
}edges[N]; 

bool cmp(Edge a,Edge b)
{
   return a.w<b.w;	
} 

int find(int x) //并查集 
{
	if(p[x] != x) p[x] = find(p[x]);
	return p[x];
}

void kruskal()
{
	sort(edges,edges+m,cmp); //排序
	
	for(int i=1; i<=n; i++) p[i] = i; //初始化并查集
	
	int res = 0, cnt = 0;
	for(int i=0; i<m; i++)  //从小到大枚举所有边 
	{
		int a = edges[i].a, b = edges[i].b, w = edges[i].w;
		if(find(a) != find(b)) //判断 a 和 b 是否连通
		{
		   p[find(a)] = b; //合并 
		   res += w;	//最小生成树的所有树边权值之和
		   cnt++;   //当前最小生成树的边数
		} 
	}
	if(cnt < n-1) cout<<"impossible"; //小于 n-1 说明不连通
	else cout<<res; 
}
int main()
{
	cin >> n >> m;
	for(int i=0; i<m; i++)
	{
		int a, b, w;
		scanf("%d%d%d",&a,&b,&w);
		edges[i] = {a,b,w};
	}
    kruskal();
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值