1. 程式人生 > >最小生成樹的兩種演算法:Prim和Kruskal演算法

最小生成樹的兩種演算法:Prim和Kruskal演算法

越來越明白了一個道理:你寫不出程式碼的原因只有一個,那就是你沒有徹底理解這個演算法的思想!!

以前寫過最小生成樹,但是,水了幾道題後,過了一段時間,就會忘卻,一點也寫不出來了。也許原因只有一個,那就是我沒有徹底理解這兩種演算法。

主題:

其實,求最小生成樹有兩個要點,一個是權值最小,還有一個就是這個圖必須是樹。而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;
}