最小生成樹模板(prim+kruskal+prim的優化)
阿新 • • 發佈:2019-02-02
最小生成樹:解決極小連通子圖連線所有點使得花費最小問題
1.prim演算法
思想:隨機選擇一個點,作為初始集合,並儲存所有點到這個集合的最短距離,然後找與這個集合距離最短的點,也將其加入這個集合,由於新加入了一個點,所以要更新所有未加入集合點到這個集合的最短距離,不斷重複,直到連通整個圖。
1)鄰接矩陣建圖樸素法(無向圖)複雜度o(v^2)
#include<iostream> #include<cstdio> #include<cstring> #define maxn 111 #define inf 1<<31-1 using namespace std; int n,m,ans; int vis[maxn],mapp[maxn][maxn],dis[maxn];//dis記錄未被加入集合的點到集合的最短距離 void init()//初始將所有邊權值賦為正無窮大 { for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) mapp[i][j]=inf; } void prim() { fill(vis,vis+maxn,0); for(int i=1;i<=n;i++) dis[i]=mapp[1][i];//隨便選擇一個點,這裡選擇第一個點,dis記錄所有點到這個點的距離 dis[1]=0;//自己到自己距離為0 vis[1]=1;//標記為訪問過 for(int i=1;i<n;i++)//第一個點已經加入集合,還有m-1個點 { int Min=inf,k; for(int j=1;j<=n;j++) if(!vis[j]&&Min>dis[j]) Min=dis[j],k=j;//選擇到集合距離最短的點,更新最短距離並記錄該點 if(Min==inf) {ans=-1;return;}//Min為無窮大表明沒有與該圖連通的點,也就是說這個圖不是完全圖 ans+=Min; vis[k]=1;//將與該集合距離最短的點標記為訪問過 for(int j=1;j<=n;j++) if(!vis[j]&&dis[j]>mapp[k][j]) dis[j]=mapp[k][j];//更新所有與該集合的最近距離 } }
2)prim+鄰接矩陣建圖+堆優化
#include<iostream> #include<cstdio> #include<queue> #define maxn 111 #define inf 1<<29 using namespace std; int vis[maxn],mapp[maxn][maxn],n,m,dis[maxn]; struct node { int v,len;//v表示當前節點,len表示該點到已經連通集合的最短距離 friend bool operator <(node a,node b) { return a.len>b.len; } }; int prim() { node cur; int ans = 0; priority_queue<node>q; fill(dis,dis+maxn,inf); fill(vis,vis+maxn,0); cur.v=1; cur.len=0; q.push(cur); while(q.size()) { cur=q.top(); q.pop(); if(vis[cur.v]) continue; vis[cur.v]=1; ans+=cur.len; for(int i=1;i<=n;i++) { if(!vis[i]&&dis[i]>mapp[i][cur.v]) { node next; next.v=i; next.len=mapp[i][cur.v]; dis[i]=mapp[i][cur.v]; q.push(next); } } } return ans; }
2.kruskal 複雜度o(eloge)
kruskal的思想就是先記錄兩點和這兩個點的權值,然後對所有邊進行排序。這裡還用到了了並查集,若這兩點不屬於同一個集合,說明這兩個點之間沒有路,因為已經是從小到大排了序,所以直接把他們連通,讓他們屬於同一個集合,不斷判斷,直到所有邊都進行了判斷,然後再根據是否只有一個節點的父親是他本身,若是,則說明最小生成樹已經求出,否則不存在這樣的樹.
以hdu 1863為例
#include<iostream> #include<algorithm> #include<cstdio> #define maxn 111 using namespace std; int father[maxn]; struct node { int l,r; int value; }; int Find(int x)//並查集查詢是否屬於同一個集合 { if(father[x]!=x) father[x]=Find(father[x]); return father[x]; } int Merge(int x,int y)//若不是同一集合,則加入集合 { x=Find(x); y=Find(y); if(x!=y) father[x]=y; } bool same(int x,int y)//判斷是否是同一集合 { return Find(x)==Find(y) ? 1:0; } bool rule(node x,node y) { return x.value<y.value; } int main() { int n,m; node a[maxn]; while(cin>>m>>n&&m) { for(int i=1;i<=n;i++) father[i]=i;//並查集初始化 for(int i=1;i<=m;i++) cin>>a[i].l>>a[i].r>>a[i].value; sort(a+1,a+n+1,rule);//排序 int ans=0; for(int i=1;i<=m;i++) { if(!same(a[i].l,a[i].r))//若兩點之間沒有路徑,連線這條路,即加入同一個集合 { Merge(a[i].l,a[i].r); ans+=a[i].value; } } int sum = 0; for(int i=1;i<=n;i++) if(father[i]==i) sum++;//看是否所有點都連通,若只有一個點的父親是它本身,則表明所有點已經連通 if(sum==1) cout<<ans<<endl; else cout<<'?'<<endl; } return 0; }