最小生成樹——Prim、Kruskal、Sollin(Boruvka)

文章出自:http://dsqiu.iteye.com/blog/1689178

最小生成樹——Prim、Kruskal、Sollin(Boruvka)

 

本文內容框架:

1.Prim算法及其基於優先隊列實現

      2.Kruskal算法

      3.Sollin算法

對於最小生成樹,有兩種算法可以解決。一種是Prim算法,該算法的時間複雜度爲O(n²),與圖中邊數無關,該算法適合於稠密圖,而另外一種是Kruskal,該算法的時間主要取決於邊數,它較適合於稀疏圖。

 

Prim算法

 

Prim算法描述

 

設圖G =(V,E),其生成樹的頂點集合爲U。

①、把v0放入U。

②、在所有u∈U,v∈V-U的邊(u,v)∈E中找一條最小權值的邊,加入生成樹。

③、把②找到的邊的v加入U集合。如果U集合已有n個元素,則結束,否則繼續執行②。

時間複雜度

 

最小邊、權的數據結構 時間複雜度(總計)
鄰接矩陣、搜索 O(V2)
二叉堆(後文僞代碼中使用的數據結構)、鄰接表 O((V + E) log(V)) = O(E log(V))
斐波那契堆鄰接表 O(E + V log(V))

(該圖轉自wikipedia)

 

通過鄰接矩陣圖表示的簡易實現中,找到所有最小權邊共需O(V²)的運行時間。使用簡單的二叉堆與鄰接表來表示的話,普里姆算法的運行時間則可縮減爲O(E *log V),其中E爲連通圖的邊數,V爲頂點數。如果使用較爲複雜的斐波那契堆,則可將運行時間進一步縮短爲O(E + V* log V),這在連通圖足夠密集時(當E滿足Ω(V* log V)條件時),可較顯著地提高運行速度。

 

Prim算法實現

用優先隊列實現

 

Cpp代碼  收藏代碼
  1. #include <iostream>    
  2. #include <cstdio>    
  3. #include <cstdlib>    
  4. #include <cstring>    
  5. #include <cmath>    
  6. #include <algorithm>    
  7. #include <set>    
  8. #include <map>    
  9. #include <vector>    
  10. #include <queue>    
  11. #include <ctime>    
  12. using namespace std;    
  13. #define LL long long    
  14. const int N = 2000;    
  15. const int INF = 1 << 30;    
  16.     
  17. struct Node    
  18. {    
  19.     int v,next,w;    
  20.     bool operator < (const Node &a) const    
  21.     {    
  22.         return w > a.w;    
  23.     }    
  24. } p[N],t1,t2;    
  25.     
  26. int dis[N],vis[N],head[N],cnt;    
  27. int res;    
  28.     
  29. void addedge(int u,int v,int w)    
  30. {    
  31.     p[cnt].v = v;    
  32.     p[cnt].next = head[u];    
  33.     p[cnt].w = w;    
  34.     head[u] = cnt++;    
  35. }    
  36.     
  37. void prim()    
  38. {    
  39.     priority_queue<Node> q;    
  40.     for(int i = head[0] ; i != -1 ; i = p[i].next)    
  41.     {    
  42.         int v = p[i].v;    
  43.         if(p[i].w < dis[v])    
  44.         {    
  45.             dis[v] = p[i].w;    
  46.             t1.w = dis[v];    
  47.             t1.v =  v;    
  48.             q.push(t1);    
  49.         }    
  50.     }    
  51.     vis[0] = 1;    
  52.     while(!q.empty())    
  53.     {    
  54.         t1 = q.top();    
  55.         q.pop();    
  56.         int u = t1.v;    
  57.         if(vis[u]) continue;    
  58.         vis[u] = 1;    
  59.         res += dis[u];    
  60.         for(int i = head[u]; i != -1; i = p[i].next)    
  61.         {    
  62.             int v = p[i].v;    
  63.             if(!vis[v] && dis[v] > p[i].w)    
  64.             {    
  65.                 dis[v] = p[i].w;    
  66.                 t2.v = v;    
  67.                 t2.w = dis[v];    
  68.                 q.push(t2);    
  69.             }    
  70.         }    
  71.     }    
  72. }    
  73.     
  74. int main()    
  75. {    
  76.     int n,m,w;    
  77.     while(scanf("%d",&n),n)    
  78.     {    
  79.         memset(p,0,sizeof(p));    
  80.         memset(head,-1,sizeof(head));    
  81.         memset(vis,0,sizeof(vis));    
  82.         char u,v;    
  83.         for(int i=0; i<n-1; i++)    
  84.         {    
  85.             cin>>u>>m;    
  86.             for(int j=0; j<m; j++)    
  87.             {    
  88.                 cin>>v>>w;    
  89.                 addedge(u-'A',v-'A',w);    
  90.                 addedge(v-'A',u-'A',w);    
  91.             }    
  92.         }    
  93.         for(int i = 0 ; i < n ;  i ++) dis[i] = INF;    
  94.         res = 0;    
  95.         prim();    
  96.         printf("%d\n",res);    
  97.     }    
  98.     return 0;    
  99. }    

 

轉自http://blog.csdn.net/acceptedxukai/article/details/6978868

 

Kruskal算法

  Kruskal算法與Prim算法的不同之處在於,Kruskal在找最小生成樹結點之前,需要對所有權重邊做從小到大排序。將排序好的權重邊依次加入到最小生成樹中,如果加入時產生迴路就跳過這條邊,加入下一條邊。當所有結點都加入到最小生成樹中之後,就找出了最小生成樹。

Kruskal算法步驟

1.新建圖G,G中擁有原圖中相同的點,但沒有邊

2.將原圖中所有的邊按權值從小到大排序

3.從權值最小的邊開始,如果這條邊鏈接的兩個點於圖G中不在同一個連通分量中,則添加這條邊到圖G中

4.重複3,直至圖G中所有的點都在同一個連通分量中

Kruskal算法實現

利用最小堆來存儲邊集E,利用並-查集來判斷向T中添加邊是否構成環路。

Cpp代碼  收藏代碼
  1. #include <iostream>  
  2. #include <vector>  
  3. #include <algorithm>  
  4. using namespace std;  
  5.    
  6. struct Edge   
  7. {  
  8.     int from, to, w; //~ 不要被假象迷惑,這裏是無向圖  
  9.     Edge(int f, int t, int _w): from(f), to(t), w(_w){}  
  10.     /* 
  11.     //~ bool operator <(const Edge& e){ return w < e.w; } 
  12.     bool operator >(const Edge& e){ return w > e.w; } 
  13.     */  
  14. };  
  15. //~ 爲什麼我把operator<重載爲成員會出錯?  
  16. //~ bool operator <(const Edge& e1, const Edge& e2){ return e1.w < e2.w; }  
  17. bool operator >(const Edge& e1, const Edge& e2){ return e1.w > e2.w; }   
  18. bool AddEdge(vector<int> & V, const Edge& e);  
  19. int main(int argc, char* argv[])  
  20. {  
  21.     vector<Edge> E;  
  22.     int from, to, w;  
  23.     int n; //~ 頂點數  
  24.     cin>>n;  
  25.     vector<int> V(n+1, -1); //~ 頂點並查集  
  26.     while (cin>>from>>to>>w) E.push_back(Edge(from, to, w));  
  27.    
  28.     make_heap(E.begin(), E.end(), greater<Edge>());  
  29.     int count = 0; //~ 已添加邊數  
  30.     while (E.size())  
  31.     {  
  32.         Edge e = E[0];  
  33.         if(AddEdge(V, e)) //~ 將成功添加的邊輸出  
  34.         {  
  35.             count++;  
  36.             if(count == n - 1) break//~ 樹已生成完畢  
  37.             cout<<e.from<<"->"<<e.to<<": "<<e.w<<endl;  
  38.         }  
  39.         pop_heap(E.begin(), E.end(),greater<Edge>());  
  40.         E.pop_back();  
  41.     }  
  42.     if (count != n - 1) cout<<"I cannot do what you want."<<endl;  
  43.     return 0;  
  44. }  
  45.    
  46. bool AddEdge(vector<int> & V, const Edge& e)  
  47. {  
  48.     int i = e.from;  
  49.     for (; V[i] > 0;) i = V[i]; //~ 尋找根節點  
  50.     int j = e.to;  
  51.     for (; V[j] > 0;) j = V[j]; //~ 尋找根節點  
  52.     if (i == j)    return false//~ i,j兩節點已經聯通  
  53.     if (V[i] > V[j]) //~ 將小集合合併至大集合上  
  54.         V[i] = j;  
  55.     else  
  56.         V[j] = i;  
  57.     return true//~ ^_^  

 轉自http://www.cppblog.com/superKiki/archive/2010/05/02/114180.aspx

 

Sollin(Boruvka)算法

Sollin(Brouvka)算法雖然是最小生成樹最古老的一個算法之一,其實是前面介紹兩種算法的綜合,每次迭代同時擴展多課子樹,直到得到最小生成樹T。

Sollin(Boruvka)算法步驟

1.用定點數組記錄每個子樹(一開始是單個定點)的最近鄰居。(類似Prim算法)

2.對於每一條邊進行處理(類似Kruskal算法)

如果這條邊連成的兩個頂點同屬於一個集合,則不處理,否則檢測這條邊連接的兩個子樹,如果是連接這兩個子樹的最小邊,則更新(合併)

 

由於每次循環迭代時,每棵樹都會合併成一棵較大的子樹,因此每次循環迭代都會使子樹的數量至少減少一半,或者說第i次迭代每個分量大小至少爲。所以,循環迭代的總次數爲O(logn)。每次循環迭代所需要的計算時間:對於第2步,每次檢查所有邊O(m),去更新每個連通分量的最小弧;對於第3步,合併個子樹。所以總的複雜度爲O(E*logV)。

Sollin(Boruvka)算法實現

 

C代碼  收藏代碼
  1. typedef struct{int v;int w;double wt;}Edge;  
  2. typeder struct{int V;int E;double **adj}Graph;  
  3. /*nn存儲每個分量的最鄰近,a存儲尚未刪除且還沒在MST中的邊 
  4. *h用於訪問要檢查的下一條邊 
  5. *N用於存放下一步所保存的邊 
  6. *每一步都對應着檢查剩餘的邊,連接不同分量的頂點的邊被保留在下一步中 
  7. *最後每一步將每個分量與它最鄰近的分量合併,並將最近鄰邊添加到MST中 
  8. */  
  9. Edge nn[maxE],a[maxE];  
  10. void Boruvka(Graph g,Edge mst[])  
  11. {  
  12. int h,i,j,k,v,w,N;  
  13. Edge e;  
  14. int E=GRAPHedges(a,G);  
  15. for(UFinit(G->V);E!=0;E=N)  
  16. {  
  17.      for(k=0;k<G->V;k++)  
  18.            nn[k]=Edge(G->V,G->V,maxWT);  
  19.      for(h=0,N=0;h<E;h++)  
  20.      {  
  21.           i=find(a[h].v);j=find(a[h].w);  
  22.           if(i==h) continue;  
  23.           if(a[h].wt<nn[i].wt)nn[i]=a[h];  
  24.           if(a[h].wt<nn[j].wt)nn[j]=a[h];  
  25.           a[N++]=a[h];  
  26.       }  
  27.       for(k=0;k<G->V;k++)  
  28.       {  
  29.            e=nn[k];v=e.v;w=e.w;  
  30.            if(v!=G->V&&!UFfind(v,w))  
  31.            {  
  32.                 UFunion(v,w);mst[k]=e;  
  33.              }  
  34.         }  
  35.   
  36. }  

  小結

這篇文章詳細的講解了圖最小生成樹的三個算法的原理和實現,希望能派上用場。如果你有任何建議或者批評和補充,請留言指出,不勝感激,更多參考請移步互聯網。


發佈了26 篇原創文章 · 獲贊 79 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章