1. 程式人生 > 其它 >最小生成樹——Kruskal演算法

最小生成樹——Kruskal演算法

技術標籤:演算法演算法貪心演算法

最小生成樹——Kruskal演算法

Kruskal演算法描述

Kruskal演算法是基於貪心的思想得到的。首先我們把所有的邊按照權值先從小到大排列,接著按照順序選取每條邊,如果這條邊的兩個端點不屬於同一集合,那麼就將它們合併,直到所有的點都屬於同一個集合為止。至於怎麼合併到一個集合,那麼這裡我們就可以用到一個工具——-並查集。換而言之,Kruskal演算法就是基於並查集的貪心演算法

Kruskal演算法流程

對於圖G(V,E),以下是演算法描述:
輸入: 圖G
輸出: 圖G的最小生成樹
具體流程:
(1)將圖G看做一個森林,每個頂點為一棵獨立的樹
(2)將所有的邊加入集合S,即一開始S = E

(3)從S中拿出一條最短的邊(u,v),如果(u,v)不在同一棵樹內,則連線u,v合併這兩棵樹,同時將(u,v)加入生成樹的邊集E’
(4)重複(3)直到所有點屬於同一棵樹,邊集E’就是一棵最小生成樹

Kruskal演算法的時間複雜度

Kruskal演算法每次要從都要從剩餘的邊中選取一個最小的邊。通常我們要先對邊按權值從小到大排序,這一步的時間複雜度為為O(|Elog|E|)。Kruskal演算法的實現通常使用並查集,來快速判斷兩個頂點是否屬於同一個集合。最壞的情況可能要列舉完所有的邊,此時要迴圈|E|次,所以這一步的時間複雜度為O(|E|α(V)),其中α為Ackermann函式,其增長非常慢,我們可以視為常數。所以Kruskal演算法的時間複雜度為O(|Elog|E|)。

問題

省政府“暢通工程”的目標是使全省任何兩個村莊間都可以實現公路交通(但不一定有直接的公路相連,只要能間接通過公路可達即可)。經過調查評估,得到的統計表中列出了有可能建設公路的若干條道路的成本。現請你編寫程式,計算出全省暢通需要的最低成本。

輸入

測試輸入包含若干測試用例。
每個測試用例的第1行給出評估的道路條數 N、村莊數目M ( < 100 );
隨後的 N 行對應村莊間道路的成本,每行給出一對正整數,分別是兩個村莊的編號,以及此兩村莊間道路的成本(也是正整數)。
為簡單起見,村莊從1到M編號。當N為0時,全部輸入結束,相應的結果不要輸出。

輸出

對每個測試用例,在1行裡輸出全省暢通需要的最低成本。若統計資料不足以保證暢通,則輸出“?”。

程式碼

#include <cstdio>
#include <cstdlib>
#define MAXN 10000 + 10
using namespace std;
 
int par[MAXN], Rank[MAXN];
typedef struct{
    int a, b, price;
}Node;
Node a[MAXN];
 
int cmp(const void*a, const void *b){
    return ((Node*)a)->price - ((Node*)b)->price;
}
void Init(int n){
    for(int i = 0; i < n; i++){
        Rank[i] = 0;
        par[i] = i;
    }
}
 
int find(int x){
    int root = x;
    while(root != par[root]) root = par[root];
    while(x != root){
        int t = par[x];
        par[x] = root;
        x = t;
    }
    return root;
}
 
void unite(int x, int y){
    x = find(x);
    y = find(y);
    if(Rank[x] < Rank[y]){
        par[x] = y;
    }
    else{
        par[y] = x;
        if(Rank[x] == Rank[y]) Rank[x]++;
    }
}
//n為邊的數量,m為村莊的數量
int Kruskal(int n, int m){
    int nEdge = 0, res = 0;
    //將邊按照權值從小到大排序
    qsort(a, n, sizeof(a[0]), cmp);
    for(int i = 0; i < n && nEdge != m - 1; i++){
        //判斷當前這條邊的兩個端點是否屬於同一棵樹
        if(find(a[i].a) != find(a[i].b)){
            unite(a[i].a, a[i].b);
            res += a[i].price;
            nEdge++;
        }
    }
    //如果加入邊的數量小於m - 1,則表明該無向圖不連通,等價於不存在最小生成樹
    if(nEdge < m-1) res = -1;
    return res;
}
int main(){
    int n, m, ans;
    while(scanf("%d%d", &n, &m), n){
        Init(m);
        for(int i = 0; i < n; i++){
            scanf("%d%d%d", &a[i].a, &a[i].b, &a[i].price);
            //將村莊編號變為0~m-1(這個僅僅只是個人習慣,並非必要的)
            a[i].a--;
            a[i].b--;
        }
        ans = Kruskal(n, m);
        if(ans == -1) printf("?\n");
        else printf("%d\n", ans);
    }
    return 0;
}

執行結果

在這裡插入圖片描述