最小生成樹(Prim算法和Kruskal算法)
1)最小生成樹
給定一個無向圖,如果它的某個子圖中任意兩個頂點都互相連通並且是一棵樹,那麽這棵樹就叫生成樹。如果邊上有權值,那麽使得邊權和最小的生成樹叫做最小生成樹(MST,Minimum Spanning Tree)
2)應用
比如讓你為一個鎮的九個村莊架設通信網絡,每個村莊相當於一個頂點,權值是村與村之間可通達的直線距離,要求你必須用最小的成本完成這次任務;或者村莊之間建公路,連通N個村莊要N-1條路,如何讓建路成本最低之類的問題。
1、Prim算法
①該算法是構建最小生成樹的算法之一。它是以某頂點為起點,不斷添加各頂點上最小權值的邊,來構建最小生成樹。
②算法流程:
第一步:設圖G頂點集合為U,首先任意選擇圖G中的一點作為起始點a,將該點加入集合V;
第二步:從集合U中找到另一點b使得點b到V中任意一點的權值最小,此時將b點也加入集合V;
以此類推,現在的集合V={a,b},再從集合U中找到另一點c使得點c到V中任意一點的權值最小,此時將c點加入集合V;直至所有頂點全部被加入V,此時就構建出了一棵MST。
③參考代碼:
#include <iostream> #include <string.h> #define INT_MAX 1000000000 using namespace std; int matrix[100][100];//鄰接矩陣 bool visited[100];//標記數組 int path[100];//記錄生成樹路徑 int lowcost[100];//邊的權值 int vertex_num,arc_num;//頂點數,弧數 int sum;//權值總和 int source;//源點 void prim(int source); int main() { cout << "請輸入圖的頂點數(<=100):"; cin >> vertex_num; cout << "請輸入圖的弧數:"; cin >> arc_num; for (int i = 0; i < vertex_num; i++) for (int j = 0; j < vertex_num; j++) matrix[i][j]= INT_MAX; //初始化matrix數組 cout << "請輸入弧的信息:\n"; int u, v, w; for (int i = 0; i < arc_num; i++) { cin >> u >> v >> w; matrix[u][v] = matrix[v][u] = w; } cout << "請輸入起點(<" << vertex_num << "):"; cin >> source; prim(source); cout << "最小生成樹權和為:" << sum << endl; cout << "最小生成樹路徑為:\n"; for (int i = 0; i < vertex_num; i++) if (i != source) cout << i << "----" << path[i] << endl; return 0; } void prim(int source){ memset(visited,0,sizeof(visited)); visited[source]=true; for(int i=0;i<vertex_num;i++){ lowcost[i] = matrix[source][i]; path[i]=source; } int min_cost,min_cost_index;//最小的權值,和其下標 sum=0; for(int i=1;i<vertex_num;i++){//找除源點外的n-1個點,如果這裏寫多了, //那麽下邊的for裏if進不去,那麽sum的值也會錯誤 min_cost=INT_MAX; for(int j=0;j<vertex_num;j++){//遍歷所有頂點 if(visited[j]==false&&lowcost[j]<min_cost){//找到與源點的權值最小的點 min_cost=lowcost[j]; min_cost_index=j; } } visited[min_cost_index]=true;//講找到的頂點進行標記 sum+=min_cost;//權值總和 for(int j=0;j<vertex_num;j++){ if(visited[j]==false&&matrix[min_cost_index][j]<lowcost[j]){//更新lowcost,以便找下個頂點 lowcost[j]=matrix[min_cost_index][j]; path[j] = min_cost_index; } } } }
摘自http://www.61mon.com/index.php/archives/199/comment-page-1#comments
2、Kruskal算法
①該算法是構建最小生成樹的另一個算法,其是對邊進行操作,來構建最小生成樹的。
②算法流程:
第一步:把所有的邊按權值從小到大排列。
第二步:按照順序選取每條邊,如果這條邊的兩個端點不屬於同一集合,那麽就將它們合並。
重復第二步,直到 所有的點都屬於同一個集合。
第二步用並查集來實現,又因為每次都選的權值最小的,所以這其實就是運用並查集的貪心算法。
③為什麽這樣做就可以構建最小生成樹呢?因為N個頂點只需要N-1條邊就夠了,那麽選哪N-1條呢,為了讓權值和最小,當然選從小到大排序的前N-1條邊嘍。
④參考代碼:
#include <iostream> #include <algorithm> #define MAX_INT 100 using namespace std; struct Edge{//邊 int a;//邊上的兩個點 int b; int weight;//權值 }edge[MAX_INT]; int par[MAX_INT];//i的父親的編號 int rank[MAX_INT];//i的高度 void init(int n);//初始化 int find(int x);//查找根節點並且壓縮路徑 bool unite(int x,int y);//合並 bool compare(const Edge&a,const Edge&b); int vertex_num,arc_num;//頂點數,弧數 int case_num;//測試組數 int sum;//權值總和 int main() { cout<<"請輸入測試組數:"<<endl; cin>>case_num; while(case_num){ sum=0; cout<<"請輸入頂點數和弧數:"<<endl; cin>>vertex_num>>arc_num; init(vertex_num); cout<<"請輸入"<<arc_num<<"條弧的信息:"<<endl; for(int i=0;i<arc_num;i++){ cin>>edge[i].a>>edge[i].b>>edge[i].weight; } sort(edge,edge+arc_num,compare); cout<<"最小生成樹的路徑為:"<<endl; int j; for(j=0;j<arc_num;j++){ if(unite(edge[j].a,edge[j].b)){ sum+=edge[j].weight; cout<<edge[j].a<<"----"<<edge[j].b<<" "<<edge[j].weight<<endl; } } if(j==arc_num){ cout<<"最小生成樹的權值和為:"<<sum<<endl; }else if(j<arc_num){ cout<<"data error!"<<endl; } case_num--; } return 0; } void init(int n){ for(int i=0;i<n;i++){ par[i]=i;//父節點都先初始化為自己 rank[i]=0;//高度初始化為0 } } int find(int x){ if(x==par[x]){ return x; }else{ return par[x]=find(par[x]); } } bool unite(int x,int y){ x=find(x); y=find(y); if(x==y){ return false; }else{ if(rank[x]<rank[y]){ par[x]=y; }else{ par[y]=x; if(rank[x]==rank[y]) rank[x]++; } return true; } } bool compare(const Edge&a,const Edge&b){//按從小到大的順序排序 return a.weight<b.weight; }
代碼參考自:http://blog.csdn.net/niushuai666/article/details/6689285
3、Kruskal算法和Prim算法的區別(或者說什麽時候用Prim什麽時候用Kruskal)?
關於這個問題,我也不是很明白,後續會補充,歡迎在評論區寫下自己對此的理解,一起來討論下吧??
。
最小生成樹(Prim算法和Kruskal算法)