最小生成树——Boruvka算法

最小生成树

前言:在最小生成树当中有三种算法,分别是kruskal、prime、boruvka,这三种算法的话前两种都是比较固定的,极难变式,但是第三种就不同了,三种的核心算法就是贪心,但是前两种都是扫描然后拓展,极难变式,但是第三种用的是启发式合并拓展,并且拓展思想结合了前两种算法。冲着这个启发式合并就很好变式了,所以有些题目就会Boruvka的变式,所以本博客也就主要讲这一种算法,其它两种就只介绍一下思路。

kruskal算法

将所有边从小到大排序,一开始没有一条边,所以每个点都可以看作一棵树,然后我们从小到大拓展每一条边,如果这条边连接了两棵树,那么我们把它选入最小生成树的集合,并且将这两棵树连接起来,这一步可以用并查集完成。然后我们来证明一下这个贪心的正确性,我们假设我们当前枚举的这条边,有一条边边权比它更小,而且同样连接了这两棵树,那么我们根据算法也就是说我们会先枚举到这个边权小的边,然后将这个边权小的选入最小生成树集合,所以说这种情况是不可能存在的,所以说我们的贪心是正确的。

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
struct node
{
	int x,y,z;
}edge[200100];
int f[5100];
int get(int x)
{
	return x==f[x]?x:f[x]=get(f[x]);
}
bool cmp(const node x,const node y)
{
	return x.z<y.z;
}
int main()
{
	long long ans=0,num=0;
	int n,m;
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++)
		f[i]=i;
	for(int i=1;i<=m;i++)
		scanf("%d %d %d",&edge[i].x,&edge[i].y,&edge[i].z);
	sort(edge+1,edge+1+m,cmp);
	for(int i=1;i<=m;i++)
	{
		int f1=get(edge[i].x),f2=get(edge[i].y);
		if(f1!=f2)
		{
			f[f1]=f2;
			ans+=edge[i].z;
			num++;
		}
	}
	if(num!=n-1)
		printf("orz");
	else printf("%lld",ans);
	return 0;
}

prime算法

对于这个也是贪心,贪心思路也和kruskal很像,只不过kruskal是根据边拓展,但是prime是根据点拓展,我们一开始选一个点作为最小生成树的点集,然后我们要通过一些边来连接最小生成树的点集和其他点,那么这个边怎么选呢,就是选从最小生成树的点集能到达的点中边权最小的来拓展,所以我们用d数组来记录,di表示i节点到最小生成树点集中的点并且只经过一条边,所有合法边的最小边权,那么我们直接每次都选di最小的且i不在最小生成树的点集中,这个可以用优先队列,然后拓展后的点要像最短路一样松弛一下,也就是更新一下i节点可到的点的d

#include<iostream>
#include<cstring>
#include<queue>
#include<cstdio>
using namespace std;
priority_queue<pair<int,int> > q;
int v[5100],d[5100],num,n,head[5100],Next[400100],ver[400100],edge[400100],size=0;
long long ans;
void add(int x,int y,int u)
{
	size++;
	Next[size]=head[x];
	head[x]=size;
	ver[size]=y;
	edge[size]=u;
}
void prime()
{
	memset(v,0,sizeof(v));
	memset(d,0x3f3f,sizeof(d));
	d[1]=0;
	q.push(make_pair(0,1));
	while(q.size() && num<n)
	{
		int x=q.top().second;
		q.pop();
		if(v[x])
			continue;
		v[x]=1;
		num++;
		ans+=d[x];
		for(int i=head[x];i;i=Next[i])
		{
			int y=ver[i],u=edge[i];
			if(d[y]>u)
				d[y]=u,q.push(make_pair(-d[y],y));
		}
	}
}
int main()
{
	int m,x,y,u;
	scanf("%d %d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		scanf("%d %d %d",&x,&y,&u);
		add(x,y,u);
		add(y,x,u);
	}
	prime();
	if(num==n)
		printf("%lld",ans);
	else printf("orz");
	return 0;
}

boruvka

我们先将每个点看作一棵树,就像kruskal一样,但是拓展就不一样了,而是和prime一样,我们用di表示以i为根的树通过一条边到达其他树的边,满足条件的边的边权的最小值,那么我们就拓展这个,所以每次都会由两个合并成一个,这就是启发式合并,时间复杂度也都是O(mlogn)

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
struct node
{
	int x,y,u;
}edge[200100];
int n,m,f[5100],d[5100],v[200100],e[5100],num,ans;
int get(int x)
{
	return x==f[x]?x:f[x]=get(f[x]);
}
void bor()
{
	for(int i=1;i<=n;i++)
		f[i]=i;
	while(1)
	{
		int res=0;
		memset(d,0x3f3f,sizeof(d));//清空d数组 
		for(int i=1;i<=m;i++)//每个边都计算一下d 
		{
			int f1=get(edge[i].x),f2=get(edge[i].y);//用并查集找到根 
			if(f1==f2 || v[i])//如果一棵树或者拓展过这个边 
				continue;
			res++;//记录一下看下有没有边可以拓展 
			if(edge[i].u<d[f1])
				d[f1]=edge[i].u,e[f1]=i;//更新,e数组表示d数组对应的边 
			if(edge[i].u<d[f2])
				d[f2]=edge[i].u,e[f2]=i;
		}
		if(res==0 || num==n-1)
			break;
		for(int i=1;i<=n;i++)//扫描每棵树 
		{
			int f1=get(edge[e[i]].x),f2=get(edge[e[i]].y);
			if(d[i]==0x3f3f3f3f || f1==f2 || v[e[i]])
				continue;
			v[e[i]]=1;//加入最小生成树集合 
			f[f1]=f2;
			ans+=edge[e[i]].u;
			num++;
		}
	}
}
int main()
{
	scanf("%d %d",&n,&m);
	for(int i=1;i<=m;i++)
		scanf("%d %d %d",&edge[i].x,&edge[i].y,&edge[i].u);
	bor();
	if(num==n-1)
		printf("%d",ans);
	else printf("orz");
	return 0;
}

Boruvka变式题

先咕咕咕,下次一定补

posted @ 2022-03-11 15:30  予柒  阅读(2224)  评论(3)    收藏  举报
返回顶端
Live2D /*修改地一:waifu.css*/
/*修改地二:waifu.css*/
/*修改地三:live2d.js*/ /*修改地四:waifu-tips.js*/