最小生成樹——Kruskal演算法
最小生成樹——Kruskal演算法
Kruskal演算法描述
Kruskal演算法是基於貪心的思想得到的。首先我們把所有的邊按照權值先從小到大排列,接著按照順序選取每條邊,如果這條邊的兩個端點不屬於同一集合,那麼就將它們合併,直到所有的點都屬於同一個集合為止。至於怎麼合併到一個集合,那麼這裡我們就可以用到一個工具——-並查集。換而言之,Kruskal演算法就是基於並查集的貪心演算法。
Kruskal演算法流程
對於圖G(V,E),以下是演算法描述:
輸入: 圖G
輸出: 圖G的最小生成樹
具體流程:
(1)將圖G看做一個森林,每個頂點為一棵獨立的樹
(2)將所有的邊加入集合S,即一開始S = 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;
}