圖論(2)--論最小生成樹
阿新 • • 發佈:2020-07-31
圖論(2)--論最小生成樹
樹狀陣列??樹...樹.樹呢?? --\(zyx\)
上回書說到,簡單的最小路問題,那麼這次我們來談談最小生成樹問題.
最小生成樹是指在固定的\(n\)個點,\(m\)條邊的圖中,找出\(n-1\)條邊,使組成的圖的邊權最小且每個點之間可以相互到達(不一定是直接到達可以通過點的轉移).
我們知道由\(n\)個點,\(n-1\)條邊組成的圖不是一顆樹嗎??所以這就是我們所要探討的話題--最小生成樹的名字的來歷.
其實問題並不難,簡單的來說,就是找\(n-1\)條邊,使構成的圖裡沒有環,且邊權和最小.對於這個問題 我們提出兩種方法也是最為普遍的兩種方法
Part 1 Prim演算法
利用一種很NB的思想--紅黑樹,
思路
設圖的頂點集合為U,樹的頂點集合為V
從圖中任意一點出發,找到N-1條邊(x,y),x∈U,y∈V,且權值最小。
通俗的講,就是不斷找權值最小且不產生閉環的N-1條邊
舉例子
話不多說,先上圖
(1)從\(V3\)出發
(2)找到邊(\(V3,V1\)),符合條件且最小,將\(V1\)加入\(V\)
以此類推……
(N)找到邊\((V2,V5)\),符合條件且最小,將\(V5\)加入\(V\),最小生成樹構造完成
/*Prim核心程式碼*/ for (int i=2;i<=n;i++) { lowcost[i]=a[i][1];//將與V1(或任意一點)有關的邊存入lowcost(與各點最小權值) } for (int i=1;i<n;i++) { minval=1000000;//初始化最小值為正無窮 for (int j=1;j<=n;j++) { if (lowcost[j]>0&&lowcost[j]<minval)//如果當前權值不為0(即未連線過)且更小 { k=j;//記錄當前點 minval=lowcost[j];//將最小值存入 } } ans+=minval;//統計最小生成樹最小權值和 lowcost[k]=0;//標記該點 for (int j=1;j<=n;j++) { if (lowcost[j]>0&&lowcost[j]>a[k][j])//由於U集合點增加,需更新與各點最小權值邊 { lowcost[j]=a[k][j]; } } }
Part 2 \(Kruskal\)演算法
這個演算法就充分的利用了並查集.
思路
利用並查集的思想,在最初把所有的點的祖先定為自己,並將邊按全職大小從小到大排序,遍歷所有邊,如果兩端點的祖先不相同,則合併集合,\(ans\)加上邊權的大小,同時計數器\(++\) 等到計數器\(=n-1\)的時候結束迴圈,則很容易得到答案;
感性/理性證明
不確定是哪一種的證明方式
最小表示邊權最小,所以我們只要排序後找前\(n-1\)條邊,若有重邊或存在環等不滿足樹的行為,則去掉這條邊,加上第\(n\)條邊即可,最終的答案一點是最小的.
程式碼
#include<iostream> #include<cstdio> #include<algorithm> #define maxn 500005 using namespace std; struct node{ int x,y,z; }edge[maxn];//從x->y 的邊權為z的邊 int fa[maxn],n,m,ans; bool operator <(node x,node y) { return x.z<y.z; } int get(int x) { if(x==fa[x]) return x; return fa[x]=get(fa[x]); } 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].z); } int cnt=1; sort(edge+1,edge+m+1); for(int i=1;i<=n;i++) fa[i]=i; for(int i=1;i<=m;i++) { if(cnt == n) { printf("%d\n",ans); return 0; } int x=get(edge[i].x),y=get(edge[i].y); if(x==y) continue; fa[x]=y; cnt+=1; ans+=edge[i].z; } if(cnt!=n) printf("orz"); return 0; }