最小生成树——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变式题
先咕咕咕,下次一定补

浙公网安备 33010602011771号