最小生成树——推荐使用Kruskal

142 篇文章 0 订阅
73 篇文章 0 订阅

题目https://www.luogu.org/problemnew/show/P3366
题解
Prim在稠密图中比Kruskal优,在稀疏图中比Kruskal劣。Prim是以更新过的节点的连边找最小值,Kruskal是直接将边排序。Prim以点找边,Kruskal找边避环。
模板参考算法笔记,尽量用邻接表来做,邻接矩阵可能会被卡。Kruskal和Prim(稠密图会MLE的话用这个),所以一般都用Kruskal

kruskal主要思路:
1.输入边,用结构体储存
2.用结构体快排以边比较从小到大快排(也可以用堆)
3.建一个并查集,并初始化并查集(并查集代表两个点有没有在同一个树里面)
Prim:
1.确定一个根节点
2.该节点所有的边依次进入优先队列
3.入队出队,知道队列为空
推荐方法一、方法二作为模板

方法一:Kruskal+堆优化(推荐模板)

#include<bits/stdc++.h>
using namespace std;
const int maxm=4000005;
const int maxn=5005;
struct Edge{
	int u,v,w;
	bool operator<(const Edge a) const{
		return w>a.w;//优先队列设置为小根堆,优先级高的数字小 
	}
}edge[maxm];
priority_queue<Edge>q;//优先队列 ,堆优化 
int n,m;
int fa[maxn];
inline int read()//读入优化 
{
	int x=0,w=0;char ch=0;
	while(!isdigit(ch)) {w|=ch=='-';ch=getchar();}
	while(isdigit(ch)){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
	return w?-x:x;
}
inline int gf(int x){return fa[x]==x?fa[x]:fa[x]=gf(fa[x]);}//并查集 
int main()
{
	n=read(),m=read();
	for(int i=1;i<=n;i++) fa[i]=i;//并查集初始化
	for(int i=1;i<=m;i++) q.push((Edge){read(),read(),read()});//真骚
	int ans=0,cnt=0;
	while(!q.empty()){//挨个边来 
		Edge kkk=q.top();q.pop();
		int ance1=gf(kkk.u),ance2=gf(kkk.v);//ancestor祖先
		if(ance1!=ance2) ans+=kkk.w,cnt++,fa[ance1]=ance2;
		if(cnt==n-1) break; //最小生成树最多有n-1条边 
	} 
	if(cnt<n-1) cout<<"orz"<<endl;
	cout<<ans<<endl;
	return 0;
}

在这里插入图片描述
方法二:堆优化+Prim+邻接表(前向星实现,推荐模板)
Prim要注意无向图的情况,要在前向星中实现两个方向的边的存储

#include<bits/stdc++.h>
#define R register int
using namespace std;
const int INF=2147483647;
int k,n,m,cnt,sum,ai,bi,ci,head[5005],vis[5005]={0};
int dis[5005];
 //cnt是最小生成树中已经包含的点的个数 
 //dis[]存的是到某点的一条边的已知最小权值 
 struct Edge{
 	int v,w,next;
 }e[400005];
 void add(int u,int v,int w)
 {//前向星 
 	e[++k].v=v;
 	e[k].w=w;
 	e[k].next=head[u];
 	head[u]=k;
 }//其他程序中,k通常写为cnt,是边的编号 
 typedef pair<int,int>pii;//定义别名,也可以用结构体代替pair 
 priority_queue<pii,vector<pii>,greater<pii> >q;//固定格式 
 //设置为小根堆 ,排序的关键字是pii.first 
 void prim()
 {
 	dis[1]=0;//设置根节点为1 
 	q.push(make_pair(0,1));//重要初始化 
 	while(!q.empty()&&cnt<n){//cnt代表最小生成树中点的个数 
 		int d=q.top().first,u=q.top().second;
 		//弹出权值最小的边 
 		//d是权值,u是边上另一个点 
		q.pop();
		if(vis[u]) continue;//u已经在最小生成树中 
		cnt++;sum+=d;vis[u]=1;
		for(R i=head[u];i!=-1;i=e[i].next) //以点找边的过程 
		{//i!=-1与主程序中对head[]数组的赋值有关
			if(e[i].w<dis[e[i].v])
			    dis[e[i].v]=e[i].w;
			    q.push(make_pair(dis[e[i].v],e[i].v));
		 } 
	 }
 }
 int main()
 {
 	//memset(dis,127,sizeof(dis));
	 //memset只能用来赋值为0,-1,其他数字用fill
	 fill(dis,dis+5005,INF); 
 	//for(int i=0;i<=5005;i++) cout<<i<<' '<<dis[i]<<endl;; 
 	memset(head,-1,sizeof(head));
 	scanf("%d%d",&n,&m);
 	for(R i=1;i<=m;i++){
 		scanf("%d%d%d",&ai,&bi,&ci);
 		add(ai,bi,ci);add(bi,ai,ci);
//无向图  Kruskal中无此步骤,因为是直接添边 ,Prim中以点找边就要这么处理 
	 }
	 prim();
	 if(cnt==n) printf("%d",sum);
	 else printf("orz");
	 return 0;
 }

在这里插入图片描述
在这里插入图片描述
方法三:Kruskal+快排

#include<bits/stdc++.h>
using namespace std;
int n,m,i,j,u,v,total;
struct edge{int start,to;long long val;}bian[2000005];
int f[100000];
long long ans;//找爹函数 
int find(int x){return (f[x]==x)?x:f[x]=find(f[x]);}
bool cmp(edge a,edge b){return a.val<b.val;}
inline void kruskal()
{
	for(int i=1;i<=m;i++)//遍历所有边 
	{
		u=find(bian[i].start);v=find(bian[i].to);
		if(u==v) continue;
		ans+=bian[i].val;
		f[u]=v;//并查集u、v的爹变成同一个爹操作 
		total++;
		if(total==n-1) break;
	} 
}
int main()
{
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++) f[i]=i;//并查集赋初值
	for(i=1;i<=m;i++)
	{
		scanf("%d%d%d",&bian[i].start,&bian[i].to,&bian[i].val);
	 } 
	 sort(bian+1,bian+m+1,cmp);
	 kruskal();
	 printf("%d",ans);
	 return 0;
}

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值