淺談三種求最小生成樹的方法

liuchanglc發表於2020-12-28

本篇文章的定義均來自與oi-wiki

定義

我們定義無向連通圖的 最小生成樹 \((Minimum\ Spanning\ Tree,MST)\)為邊權和最小的生成樹。

注意:只有連通圖才有生成樹,而對於非連通圖,只存在生成森林。

\(Prim\)演算法

\(Prim\) 演算法是一種常見並且好寫的最小生成樹演算法。該演算法的基本思想是從一個結點開始,不斷加點

具體來說,每次要選擇距離最小的一個結點,以及用新的邊更新其他結點的距離

時間複雜度 \(O(n^2)\)

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define rg register
inline int read(){
	rg int x=0,fh=1;
	rg char ch=getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') fh=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*fh;
}
const int maxn=5e3+5;
int mp[maxn][maxn],dis[maxn],n,m;
bool vis[maxn];
void prim(){
	dis[1]=0;
	for(rg int i=1;i<n;i++){
		rg int x=0;
		for(rg int j=1;j<=n;j++){
			if(!vis[j] && (x==0 || dis[j]<dis[x])) x=j;
		}
		vis[x]=1;
		for(rg int j=1;j<=n;j++){
			if(!vis[j]) dis[j]=std::min(dis[j],mp[j][x]);
		}
	}
}
int main(){
	memset(mp,0x3f,sizeof(mp));
	memset(dis,0x3f,sizeof(dis));
	n=read(),m=read();
	rg int aa,bb,cc;
	for(rg int i=1;i<=m;i++){
		aa=read(),bb=read(),cc=read();
		mp[aa][bb]=mp[bb][aa]=std::min(mp[aa][bb],cc);
	}
	prim();
	rg int ans=0;
	for(rg int i=1;i<=n;i++){
		ans+=dis[i];
	}
	printf("%d\n",ans);
	return 0;
}

Kruskal 演算法

\(Kruskal\) 演算法是另一種常見並且好寫的最小生成樹演算法,由 \(Kruskal\) 發明

該演算法的基本思想是從小到大加入邊,是個貪心演算法

時間複雜度 \(O(mlogn)\)

#include<bits/stdc++.h>
using namespace std;
const int maxn=600000;
struct asd{
    int from,to,val;
}b[maxn];
int tot=1,head[maxn];
void ad(int aa,int bb,int cc){
    b[tot].from=aa;
    b[tot].to=bb;
    b[tot].val=cc;
    tot++;
}
bool cmp(asd aa,asd bb){
    return aa.val<bb.val;
}
int fa[maxn];
int zhao(int xx){
    if(xx==fa[xx]) return xx;
    return fa[xx]=zhao(fa[xx]);
}
void bing(int xx,int yy){
    fa[zhao(xx)]=zhao(yy);
}
int shu(int xxx){
    sort(b+1,b+tot,cmp);
    int ans=0,cnt=0;
    for(int i=1;i<tot;i++){
        int xx=b[i].from,yy=b[i].to;
        if(zhao(xx)!=zhao(yy)){
            bing(xx,yy);
            ans+=b[i].val;
            if(++cnt==xxx) return ans;
        }
    }
    return -1;
}
int main(){
    for(int i=0;i<maxn;i++) fa[i]=i;
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        int aa,bb,cc;
        scanf("%d%d%d",&aa,&bb,&cc);
        ad(aa,bb,cc);
    }
    int ans=shu(n-1);
    if(ans==-1) printf("orz\n");
    else printf("%d\n",ans);
    return 0;
}

\(Boruvka\) 演算法

\(Boruvka\) 其實是一種多路增廣的 \(Prim\)

\(Boruvka\) 演算法每一次的增廣,會對現在的每一個連通塊都找一遍的最短邊,最後每個連通塊擇優,將這些邊全部連上

時間複雜度 \(O(mlogn)\)

#include<cstdio>
#include<algorithm>
#include<cstring>
#define rg register
inline int read(){
	rg int x=0,fh=1;
	rg char ch=getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') fh=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*fh;
}
const int maxn=1e6+5;
struct asd{
	int zb,yb,val;
}b[maxn];
int n,m,fa[maxn],cnt,sum,bes[maxn];
int zhao(rg int xx){
	if(xx==fa[xx]) return xx;
	return fa[xx]=zhao(fa[xx]);
}
bool vis[maxn];
bool pd(rg int aa,rg int bb){
	if(bb==0) return 1;
	if(b[aa].val!=b[bb].val) return b[aa].val<b[bb].val;
	return aa<bb;
}
int main(){
	n=read(),m=read();
	for(rg int i=1;i<=m;i++){
		b[i].zb=read(),b[i].yb=read(),b[i].val=read();
	}
	for(rg int i=1;i<=n;i++) fa[i]=i;
	rg bool jud=1;
	rg int aa,bb;
	while(jud){
		jud=0;
		memset(bes,0,sizeof(bes));
		for(rg int i=1;i<=m;i++){
			if(vis[i]) continue;
			aa=zhao(b[i].zb),bb=zhao(b[i].yb);
			if(aa==bb) continue;
			if(pd(i,bes[aa])) bes[aa]=i;
			if(pd(i,bes[bb])) bes[bb]=i;
		}
		for(rg int i=1;i<=n;i++){
			if(bes[i]!=0 && vis[bes[i]]==0){
				jud=1;
				cnt++;
				sum+=b[bes[i]].val;
				vis[bes[i]]=1;
				fa[zhao(b[bes[i]].zb)]=zhao(b[bes[i]].yb);
			}
		}
	}
	if(cnt!=n-1) printf("orz\n");
	else printf("%d\n",sum);
	return 0;
}

相關文章