本文鏈接:http://www.cnblogs.com/Ash-ly/p/5494975.html
定義:
設G = (V, E)是連通的無向圖,T是圖G的一個最小生成樹.如果有另外一棵樹T1,T1 ≠ T,滿足不存在樹T',T' ≠ T,w(T') < w(T1),則稱T1是圖G的次小生成樹.
算法:
1:基本算法
最簡單也最容易想到的是,設T是G的最小生成樹,依次枚舉T的邊並去掉,再求最小生成樹,所得到的這些值的最小值就是次小生成樹,由於最小生成樹有N-1條邊,這種方法就相當於運行了N次最小生成樹的算法,算法的時間復雜度比較高.大約是N * M的數量級.
2:算法二
定義由最小生成樹T進行一次可行變化得到的新的生成樹所組成的集合,稱為樹T的鄰集,記為N(T).所謂可行變化即去掉T中的一條邊,再新加入G中的一條邊,使得新生成的圖仍為樹.設T是圖G的最小生成樹,如果T1滿足w(T1) = min{ w(T') | T' E N(T)},則T1是圖G的次小生成樹.
定理:如果圖G的邊的個數E和個點的個數N不滿足關系E + 1 = N,那么存在邊(u,v) 屬於 T 和(x, y)不屬於T滿足T \ (u, v) U (x, y)是圖的一顆次小生成樹.
既然存在邊(u, v) 屬於 T 和(x, y) 不屬於T滿足T \ (u, v) U (x, y)是圖的一顆次小生成樹,那么所有的T \ (u, v) U (x, y)剛好構成了T的鄰集,則T的鄰集中權值最小的就是次小生成樹了,但是如果每次枚舉(u, v)和(x, y),還要判斷能否構成一棵樹,復雜度太高.那么這里應該這么做,先加入(x,y),對於一棵樹加入(x, y)后一定會成環,如果刪去環上除(x ,y)以外權值最大的一條邊,會得到加入(x,y)時權值最小的邊.那么接下來的問題就是如何快速求得這個環上權值最大邊了.最小生成樹中x到y的最長邊可以使用樹形動態規划或者LCA等方法在O(N2)的時間復雜度內計算出來.但是如果使用的是Kruskal算法求最小生成樹,可以在算法的運行過程中求出x到y路徑上的最長邊,因為每次合並兩個等價類的時候,分別屬於兩個等價類的每兩個點之間的最長邊一定是當前加入的邊,按照這條性質進行記錄的話就可以求出來最小生成樹中在每兩個點的路徑上的最長邊.
算法實現上,首先用求最小生成樹的算法求出其權值之和為mst,然后枚舉不屬於最小生成樹的邊(x, y),並添加到最小生成樹中,那么樹必定形成環,然后刪掉這個環內(不包含(x,y))最長的邊.然后計算權值之和,枚舉所有不屬於最小生成樹的邊,取其權值的最小值,就是次小生成樹.
下面用圖來說明下計算(x,y)之間的最長邊:
比如利用 kruskal 走到了上述步驟,接下來要添加邊(V2, V5),已知權值為8,那么V2 和 V5 分別屬於兩個等價類<V2, V1, V4, V6> 和 <V5, V3> 的每兩個點之間的距離應該都更新為8,即length[3][2] = length[3][1] = length[3][4] = length[3][6] = 8, length[5][2] = length[5][1] = length[5][4] = length[5][6] = 8,每增加一條邊,就如此的更新下去,最后就可以得到圖中兩個節點之間的路徑上的最長距離.
下面再用圖簡單闡述下算法:
假設上圖是圖的最小生成樹,並存在邊(v5, v6),且其權值為8,那么當枚舉到此條邊時,在圖中添加邊(v5,v6),圖中便有了一個環,在此環中找到除新加邊以外最大的邊的權值為7,那么刪除此邊,計算權值,然后繼續枚舉其他不存在於最小生成樹的邊,從中取得最小值,就是要求的答案.
代碼:
1 #include <iostream> 2 #include <cstring> 3 #include <cstdlib> 4 #include <algorithm> 5 #include <cstdio> 6 #include <string> 7 8 using namespace std; 9 typedef long long LL; 10 11 const int MAXN = 500; 12 const int MAXE = 500 * 500; 13 const int INF = 0x3f3f3f3f; 14 int pre[MAXN + 7]; 15 16 void initPre(int n){ for(int i = 0; i <= n; i++) pre[i] = i; } 17 18 //並查集 19 int Find(int x){ return x == pre[x] ? x : pre[x] = Find(pre[x]); } 20 21 void merge(int x, int y){ int fx = Find(x), fy = Find(y); if(fx != fy) pre[fx] = fy; } 22 23 struct Edge{ //前向星存邊 24 int u, v; //起點 終點 25 int w; 26 bool select; 27 }edge[MAXE + 7]; 28 29 bool cmp(Edge a, Edge b){ 30 if(a.w != b.w) return a.w < b.w; 31 if(a.u != b.u) return a.u < b.u; 32 return a.v < b.v; 33 } 34 35 struct Node{//鏈式前向星 用於存儲每個集合里面的邊 36 int to; 37 int next; 38 }link[MAXN + 7]; 39 40 int head[MAXN + 7];//鄰接表的頭結點的位置 41 int End[MAXN + 7];//鄰接表的尾節點的位置 42 int length[MAXN + 7][MAXN + 7];//最小生成樹中任意兩點路徑上的最長邊 43 44 int kruskal(int n, int m){ 45 //初始化鄰接表,對於每一個頂點添加一個指向自身的邊,表示以i為代表元的集合中只有點i 46 for(int i = 1; i <= n; i++){ 47 link[i].to = i, link[i].next = head[i]; 48 End[i] = i, head[i] = i; 49 } 50 sort(edge + 1, edge + 1 + m, cmp); 51 int cnt = 0; 52 for(int i = 1; i <= m; i++){ 53 if(cnt == n - 1) break;//當找到的邊數等於節點數-1,說明mst已經找到 54 int fx = Find(edge[i].u); 55 int fy = Find(edge[i].v); 56 if(fx != fy){ 57 for(int j = head[fx]; j != -1; j = link[j].next)//修改length數組 58 for(int k = head[fy]; k != -1; k = link[k].next) 59 //每次合並兩個等價類的之后,分別屬於兩個等價類的兩個節點之間的最長邊一定是當前加入的邊 60 length[link[j].to][link[k].to] = length[link[k].to][link[j].to] = edge[i].w; 61 //合並鄰接表 62 link[End[fy]].next = head[fx]; 63 End[fy] = End[fx]; 64 merge(fx, fy); 65 cnt++; 66 edge[i].select = true; 67 } 68 } 69 if(cnt < n - 1) return -1; 70 return 1; 71 } 72 73 int main(){ 74 //初始化建圖后執行以下操作 75 int flag = kruskal(n, m); 76 int mst = 0; 77 for(int i = 1; i <= m; i++) if(edge[i].select) mst += edge[i].w;//計算出最小生成樹 78 int secmst = INF; 79 //在 T/(u,v) + (x, y)中尋得次小生成樹 80 for(int i = 1; i <= m; i++) if(!edge[i].select) secmst = min(secmst, mst + edge[i].w - length[edge[i].u][edge[i].v]); 81 return 0; 82 }