定義:在無向連通圖中邊權和最小的生成樹為最小生成樹。生成樹的定義是在一個無向圖中包含所有節點且邊數最少的連通子圖。
Kruskal演算法
考慮一種貪心,我們將邊權從小到大排列,依次將邊加入生成樹中,如果此次加邊形成了環,則扔掉這條邊。
我們可以輕易證明此貪心的正確性,若在某次加入中此條邊不是最小生成樹的一條邊,那後面這兩個點最終也要連起來,這兩個點連起來的路徑和這條邊會組成一個環,這個環中一定有邊權大於這條邊的邊權(因為一定有比這條邊後加入的邊,不然這條邊加入就會組成環被扔掉),用這條邊替換,圖依舊聯通,但是總權值減少了,所以要是不選取這條邊,接下來構成的生成樹一定不是最少的。
程式碼實現我們需要用並查集查詢兩點是否聯通和將兩點連線。
點選檢視程式碼
struct Node{
int u,v,w;
}g[N];
bool cmp(Node x,Node y){
return x.w<y.w;
}
int fa[N],n,m,ans;//m表示邊數,n表示點數,ans表示總權值
//標準並查集
int find(int x){
if(fa[x]==x) return fa[x];
return fa[x]=find(fa[x]);
}
void merge(int x,int y){
if(find(x)!=find(y)) fa[find(x)]=find(y);
}
void kruskal(){
int tot=0;
sort(g+1,g+1+m,cmp);
for(int i=1;i<=m;i++){
if(find(g[i].u)!=find(g[i].v)){//如果沒有形成環就加入
tot++;
ans+=g[i].w;
if(tot>=n-1){
cout<<ans;
return;//已建成最小生成樹,返回
}
merge(g[i].u,g[i].v);
}
}
cout<<"no answer";//若未返回就是找不到
}
Prim演算法
從一個節點開始,選擇距離最小的節點,類似Dijkstra演算法的操作,理論上來說在稠密圖時間複雜度優於Kruskal演算法,但是Prim演算法的常數十分大,實際不一定跑下來更快。
點選檢視程式碼
struct Node{
int u,v,w;
};
vector<Node>g[N];
struct T{
int d,u;
friend bool operator<(T a,T b){
return a.d>b.d;
}
};
priority_queue< T > q;
int n,m,dis[N],vis[N];
int prim(){
int ans=0;
memset(vis,0,sizeof(vis));
memset(dis,0x3f,sizeof(dis));
dis[1]=0;
q.push(T{0,1});
while(!q.empty()){
T t=q.top();
q.pop();
int d=t.d,u=t.u;
if(vis[u]) continue;
vis[u]=1;
ans+=d;
for(Node e:g[u]){
int v=e.v;
if(!vis[v]) q.push(T{e.w,v});
}
}
return ans;
}