最小生成樹的兩種演算法:Prim和Kruskal演算法
阿新 • • 發佈:2019-02-06
越來越明白了一個道理:你寫不出程式碼的原因只有一個,那就是你沒有徹底理解這個演算法的思想!!
以前寫過最小生成樹,但是,水了幾道題後,過了一段時間,就會忘卻,一點也寫不出來了。也許原因只有一個,那就是我沒有徹底理解這兩種演算法。
主題:
其實,求最小生成樹有兩個要點,一個是權值最小,還有一個就是這個圖必須是樹。而Prim和Kruskal的不同之處在於兩者選擇的變數不同,Prim選擇的是始終保持權值最小,然後逐個加點構建一棵樹。而Kruskal則是始終保證是一棵樹(雖然構建過程中不一定是真正的樹,但並查集判環可以這樣理解:是為了保證結果是一顆樹),然後逐條加邊,使權值最小。
知道上述兩種思想後,來談談程式碼的(都是基於貪心)實現:
Prim:(這裡把權值理解成距離)假設,已經確定的點的集合為S,那麼還未確定的點可以記為1-S,我們每次從還未確定的點集合1-S中,選擇一個裡離集合S中的點直接相連,且權值最小(貪心)的點加入S中,不妨被這個點為t,則S與1-S將發生變化:由於t變成了S中的點,那麼1-S中與t相連的點的距離實際上變成了該點與S的距離。由於初始化時,已經有一個點已經標誌,那麼只需要迴圈N-1次就夠了,而且只能是N-1次,否則可能會發生錯誤(視INF而定),這就是Prim演算法。
Kruskal:這個演算法相對於Prim來說就比較好理解多了,每次選擇權值最小的(貪心)的邊,然後看看該邊的兩個點是否與樹矛盾(用並查集判斷就行了),在加邊的過程中記錄有效點的數目,達到N個就結束,不用把每條邊都考慮進去。
附上 :HDU 1233 還是暢通工程兩種演算法的程式碼,幫助理解:
Prime:
#include<iostream> #include<cstdio> #include<cstring> #include<queue> #define INF 0x7fffffff using namespace std; int Map[110][110],dis[110],vis[110]; int N,M; void prime(){ memset(vis,0,sizeof(vis)); for(int i=1;i<=N;i++) dis[i]=Map[1][i];//權值可以理解成距離,似乎更好理解 int sum=0;vis[1]=1;//初始1為開始的點,可以任意點 for(int p=1;p<=N-1;p++){//進行N-1次迴圈,加入剩下的N-1個點 int t,Min=INF; for(int i=1;i<=N;i++) if(!vis[i] && dis[i]<Min)//尋早集合1-S中到集合S最近的點t Min=dis[i],t=i; sum+=Min;vis[t]=1; for(int i=1;i<=N;i++) if(!vis[i] && dis[i]>Map[t][i])//更新與t相連且在1-S中的點到集合S的距離 dis[i]=Map[t][i]; } cout<<sum<<endl; } int main(){ //freopen("D:\\in.txt","r",stdin); while(cin>>N && N){ for(int i=0;i<=N;i++) for(int j=0;j<=N;j++) Map[i][j]=INF; M=(N-1)*N/2; int a,b,c; for(int i=0;i<M;i++){ scanf("%d %d %d",&a,&b,&c); Map[a][b]=Map[b][a]=c; } prime(); } return 0; }
Kruskal:
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
struct edge{
int u,v,w;
bool operator<(const edge &a)const{
return w<a.w;
}
}E[10010];
int N,M;
int pre[110];
int find(int x){
int t=x;
while(pre[t]!=t) t=pre[t];
while(x!=t) {int px=pre[x]; pre[x]=t,x=px; }
return t;
}
void Kruskal(){
for(int i=0;i<=N;i++) pre[i]=i;//初始話並查集
int cnt=1,ans=0;
for(int i=0;i<M;i++){
int u=E[i].u,v=E[i].v,w=E[i].w;
int fu=find(u),fv=find(v);
if(fu==fv) continue;//並查集判斷是否滿足樹的性質
ans+=w;
pre[fv]=fu;cnt++;
if(cnt==N) break;//已經滿樹
}
cout<<ans<<endl;
}
int main(){
//freopen("D:\\in.txt","r",stdin);
while(cin>>N && N){
M=N*(N-1)/2;
for(int i=0;i<M;i++){
scanf("%d %d %d",&E[i].u,&E[i].v,&E[i].w);
}
sort(E,E+M);//把邊排序
Kruskal();
}
return 0;
}