最小生成树问题

在这里插入图片描述
最小生成树问题一般对应无向图,一般都有m≤n*n,所以mlogm和mlogn差不多,所以我们一般用kruskal而不常用堆优化的prim

1.朴素版Prim o(n2)
基本思路
初始化 dis[i] <- 正无穷大
for (i=0;i<n;i++)  n次迭代,因为要加入n个点到集合中
	t <- 找到集合外距离最近的点
	用 t 更新 其他点 -> “集合” 的距离 //Dij是来更新到起点的距离
	st[t]=true;//把t加到集合里

优化方式和dij差不多

例题

在这里插入图片描述
稠密图

输入
4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4
输出
6

在这里插入图片描述

代码
#include <iostream>
#include <queue>
#include <cstring>
#include <algorithm>
const int maxn=510;
const int INF=0x3f3f3f3f;
using namespace std;
int n,m;
int mp[maxn][maxn],dis[maxn];//dis表示点到集合的距离
bool st[maxn];
int prim()
{
	memset(dis,0x3f,sizeof(dis));
	int ans=0;
	for(int i=0;i<n;i++)//进行n次迭代
	{
		int t=-1;
		for(int j=1;j<=n;j++)·//开始遍历点,寻找距离集合最短的点
			if(!st[j]&&(t==-1||dis[t]>dis[j]))
				t=j;
		
		if(i&&dis[t]==INF)//不是第一个点且当前距离集合最近的点都是正无穷
			return INF;//说明最短路不存在
		if(i)
			ans+=dis[t];//不能把初始化时的INF加进去
		
		st[t]=true;
		for(int j=1;j<=n;j++)
			dis[j]=min(dis[j],mp[t][j]);//用当前加入的这个点t去更新其他点到集合的距离
	}
	return ans;
}
int main()
{
	cin>>n>>m;
	memset(mp,0x3f,sizeof(mp));
	for(int i=0;i<m;i++)
	{
		int a,b,c;
		cin>>a>>b>>c;
		mp[a][b]=mp[b][a]=min(mp[a][b],c);//无向图,有重边的话保存最小的那条边
	}

	int ans=prim();

	if(ans==INF)
	cout<<"impossible"<<endl;
	else
	cout<<ans<<endl;
	return 0;
}

和dijstra算法极为相似,只是更新那里有一点不同: dis[j]=min(dis[j],mp[t][j]);

注意:最小生成树时不能有自环的

 所以如果存在一个点它有负权自环,是不应该加进去的。如果for更新放在求权值和之后,可能会导致把负权自环更新过的dis加进去,这里的顺序要注意先进行累加。
在这里插入图片描述

以下写法会导致两次调用prim(),初始化会有问题,st会没有初始化到

if(prim()==INF) puts(“impossible”);
else cout<<prim()<<endl;

堆优化就是用 堆来维护dis[],把时间复杂度降到O(1)
在这里插入图片描述
思路和代码都比kruskal麻烦,所以不常用。

2.Kruskal O(mlogm)

时间主要花在排序上,但排序的常数很小,就是一个logn进行运算的次数很少,所以通常时间上会表现得比较理论上好。

基本思路
1.将所有边按权重从小到大排序//O(mlogm) 算法的瓶颈
2.枚举每条边 a,b,权重c  //O(m)
	if a,b不连通 //并查集判环
		将这条边加到集合中
例题

在这里插入图片描述

输入
4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4
输出
6
代码
#include <iostream>
#include <queue>
#include <cstring>
#include <algorithm>
const int maxn=1e5+5;
const int INF=0x3f3f3f3f;
using namespace std;
int n,m;
struct node{
	int a,b,w;
	bool operator < (const node &t)const
	{
		return w<t.w;
	}
}edges[2*maxn];
int p[maxn];//用来存储并查集的父亲节点
int find(int x)//找到它们的祖宗结点
{
	if(p[x]!=x)
	p[x]=find(p[x]);

	return p[x];
}
int kruskal()
{
	sort(edges,edges+m);//这里采用了重载运算符方式,也可以单独去写一个cmp函数作为第三个参数
	for(int i=1;i<=n;i++)//初始化并查集,刚开始时,每个人的父节点都是它自己
	p[i]=i;

	int ans=0,cnt=0;
	for(int i=0;i<m;i++)
	{
		int a,b,w;
		a=edges[i].a,b=edges[i].b,w=edges[i].w;
		a=find(a),b=find(b);
		if(a!=b)//说明不连通
		{
			p[a]=b;//合并起来,让a的父节点变成b
			ans+=w;
			cnt++;//当前集合中边数+1
		}
	}
	if(cnt<n-1)//一共n个点,如果没有n-1条边,说明不能全部连通
	return INF;
	return ans;
}
int main()
{
	cin>>n>>m;
	for(int i=0;i<m;i++)
	{
		int a,b,w;
		cin>>a>>b>>w;
		edges[i]={a,b,w};
	}
	int ans=kruskal();

	if(ans==INF)
	cout<<"impossible"<<endl;
	else
	cout<<ans<<endl;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值