最小生成樹-kruskal算法
Kruskal算法
最小生成樹是典型的貪心法求解的問題,假如有N個節點,則最小生成樹會有N-1條邊,要每條邊權值之和最小,只需要每次選出當前權值最小的邊,如果當前邊會形成環路,則這條邊是無效的也就是加入進去會是冗余的,因為這條邊新連通的是兩個已經被別的邊連通了的節點,如果不會形成環路,就加入這條邊,這樣就能保證最終的權值之和最小。
具體寫法:
判環路一般借助並查集來實現。
而每次提取出權值最小的邊,我們可以用結構體來存放邊,然後事先通過排序將所有邊按照權值由小到大進行排序,然後我們就可以在O(1)的時間內找到當前權值最小的邊,當然也可以借助優先隊列。
先定義存放邊的結構體,同時重載了<運算符。
[cpp] view plain copy
- struct edge{
- int a,b,value;
- edge(int x=0,int y=0,int z=0):a(x),b(y),value(z){}
- bool operator <(edge x){
- return value<x.value;
- }
- };
進行排序,直接調用庫函數。
[cpp] view plain copy
- 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
- int par[maxN];
- void init(){
- for(int i=0;i<maxN;i++)par[i]=i;
- }
- int find(int x){
- if(par[x]!=x)par[x]=find(par[x]);
- return par[x];
- }
- bool join(int a,int b){
- int x=find(a),y=find(b);
- if(x!=y){
- par[x]=y;
- return true;
- }
- return false;
- }
挑選最小生成樹的N-1條邊,如果找不出足夠的N-1條邊,說明不存在生成樹,圖不連通,這裏返回 -1代表無解。
[cpp] view plain copy
- long long kruskal(){
- init();
- int MSTnum=0;long long ans=0;
- sort(all.begin(),all.end());
- for(int i=0;i<all.size();i++)if(join(all[i].a,all[i].b)){ //即不會構成環的情況
- ans+=all[i].value;
- MSTnum++;
- if(MSTnum==N-1)return ans; //找到符合情況的N-1條邊
- }
- return -1; //如果能找到N-1條邊這條語句並不會被執行
- }
如果存在有不存在生成樹的情況只需要檢查一下選出的邊的數量,如果最終這個變量小於N-1,則圖不連通,無解,並查集中也可以判斷一下所有節點在最終是否都並入了一個集合,但這顯然不如前一種方法。
完整代碼示例:(題目:hdu-1863)
[cpp] view plain copy
- #include<bits/stdc++.h>
- using namespace std;
- const int maxN=10050;
- int N;
- struct edge{
- int a,b,value;
- edge(int x=0,int y=0,int z=0):a(x),b(y),value(z){}
- bool operator <(edge x){
- return value<x.value;
- }
- };
- vector<edge> all;
- int par[maxN];
- void init(){
- for(int i=0;i<maxN;i++)par[i]=i;
- }
- int find(int x){
- if(par[x]!=x)par[x]=find(par[x]);
- return par[x];
- }
- bool join(int a,int b){
- int x=find(a),y=find(b);
- if(x!=y){
- par[x]=y;
- return true;
- }
- return false;
- }
- long long kruskal(){
- init();
- int MSTnum=0;long long ans=0;
- sort(all.begin(),all.end());
- for(int i=0;i<all.size();i++)if(join(all[i].a,all[i].b)){
- ans+=all[i].value;
- MSTnum++;
- if(MSTnum==N-1)return ans;
- }
- return -1;
- }
- int main(){
- int m,a,b,c;
- while(cin>>m>>N&&m){
- all.clear();
- for(int i=0;i<m;i++){
- cin>>a>>b>>c;
- all.push_back(edge(a,b,c));
- }
- int ans=kruskal();
- if(ans==-1)cout<<‘?‘<<endl;
- else cout<<ans<<endl;
- }
- }
最小生成樹-kruskal算法