1. 程式人生 > >最小生成樹-kruskal算法

最小生成樹-kruskal算法

sso 如果能 互連 集合 pre names 返回 release 會有

Kruskal算法

最小生成樹是典型的貪心法求解的問題,假如有N個節點,則最小生成樹會有N-1條邊,要每條邊權值之和最小,只需要每次選出當前權值最小的邊,如果當前邊會形成環路,則這條邊是無效的也就是加入進去會是冗余的,因為這條邊新連通的是兩個已經被別的邊連通了的節點,如果不會形成環路,就加入這條邊,這樣就能保證最終的權值之和最小。

具體寫法:

判環路一般借助並查集來實現。

而每次提取出權值最小的邊,我們可以用結構體來存放邊,然後事先通過排序將所有邊按照權值由小到大進行排序,然後我們就可以在O(1)的時間內找到當前權值最小的邊,當然也可以借助優先隊列。

先定義存放邊的結構體,同時重載了<運算符。

[cpp] view plain copy
  1. struct edge{
  2. int a,b,value;
  3. edge(int x=0,int y=0,int z=0):a(x),b(y),value(z){}
  4. bool operator <(edge x){
  5. return value<x.value;
  6. }
  7. };

進行排序,直接調用庫函數。

[cpp] view plain copy
  1. sort(all,all+N); //all存放全部邊

利用並查集來判斷是否會形成環路,這裏假設存放all數組N個元素的父親節點或者說標誌節點的數組為par[N],find函數用於尋找邊的兩個端節點所屬集合的根節點或者說標誌節點,join函數完成這兩個節點所屬的兩個集合的合並和判斷是否會形成環路,如果在合並之前兩個節點已經同屬於一個集合,這時如果再將連接這兩個節點的邊放入生成樹的集合,則會形成回路,舉個例子,某個圖只有a,b,c三個節點並且相互連通,我們先進行join(a,b),join(b,c)操作連通a,b和b,c,這時已經有一條路徑連通三個節點了,如果這時在進行join(a,c)操作連通a,c的話就會形成環路,這裏設定如果不會產生環路的話返回true。

[cpp] view plain copy
  1. int par[maxN];
  2. void init(){
  3. for(int i=0;i<maxN;i++)par[i]=i;
  4. }
  5. int find(int x){
  6. if(par[x]!=x)par[x]=find(par[x]);
  7. return par[x];
  8. }
  9. bool join(int a,int b){
  10. int x=find(a),y=find(b);
  11. if(x!=y){
  12. par[x]=y;
  13. return true;
  14. }
  15. return false;
  16. }

挑選最小生成樹的N-1條邊,如果找不出足夠的N-1條邊,說明不存在生成樹,圖不連通,這裏返回 -1代表無解。

[cpp] view plain copy
  1. long long kruskal(){
  2. init();
  3. int MSTnum=0;long long ans=0;
  4. sort(all.begin(),all.end());
  5. for(int i=0;i<all.size();i++)if(join(all[i].a,all[i].b)){ //即不會構成環的情況
  6. ans+=all[i].value;
  7. MSTnum++;
  8. if(MSTnum==N-1)return ans; //找到符合情況的N-1條邊
  9. }
  10. return -1; //如果能找到N-1條邊這條語句並不會被執行
  11. }

如果存在有不存在生成樹的情況只需要檢查一下選出的邊的數量,如果最終這個變量小於N-1,則圖不連通,無解,並查集中也可以判斷一下所有節點在最終是否都並入了一個集合,但這顯然不如前一種方法。
完整代碼示例:(題目:hdu-1863)

[cpp] view plain copy
    1. #include<bits/stdc++.h>
    2. using namespace std;
    3. const int maxN=10050;
    4. int N;
    5. struct edge{
    6. int a,b,value;
    7. edge(int x=0,int y=0,int z=0):a(x),b(y),value(z){}
    8. bool operator <(edge x){
    9. return value<x.value;
    10. }
    11. };
    12. vector<edge> all;
    13. int par[maxN];
    14. void init(){
    15. for(int i=0;i<maxN;i++)par[i]=i;
    16. }
    17. int find(int x){
    18. if(par[x]!=x)par[x]=find(par[x]);
    19. return par[x];
    20. }
    21. bool join(int a,int b){
    22. int x=find(a),y=find(b);
    23. if(x!=y){
    24. par[x]=y;
    25. return true;
    26. }
    27. return false;
    28. }
    29. long long kruskal(){
    30. init();
    31. int MSTnum=0;long long ans=0;
    32. sort(all.begin(),all.end());
    33. for(int i=0;i<all.size();i++)if(join(all[i].a,all[i].b)){
    34. ans+=all[i].value;
    35. MSTnum++;
    36. if(MSTnum==N-1)return ans;
    37. }
    38. return -1;
    39. }
    40. int main(){
    41. int m,a,b,c;
    42. while(cin>>m>>N&&m){
    43. all.clear();
    44. for(int i=0;i<m;i++){
    45. cin>>a>>b>>c;
    46. all.push_back(edge(a,b,c));
    47. }
    48. int ans=kruskal();
    49. if(ans==-1)cout<<‘?‘<<endl;
    50. else cout<<ans<<endl;
    51. }
    52. }

最小生成樹-kruskal算法