本文鏈接:http://www.cnblogs.com/Ash-ly/p/5536796.html
定義:
設G = (V, E)是一個有向圖,如果具有下述性質
(1)G中不包含有向環;
(2)存在一個頂點vi,它不是任何弧的終點,而V的其它頂點都恰好是唯一的一條弧的終點.則稱 G是以vi為根的樹形圖.
最小樹形圖就是有向圖G = (V, E)中以vi為根的樹形圖中權值的和最小的那一個.
用朱劉算法求最小樹形圖:
最小樹形圖基於貪心和縮點的思想,所謂縮點,就是將幾個點看成一個點,所有連到這幾個點的邊都視為連到收縮點,所有從這幾個點連出的邊都視為從收縮點連出.這里假設根節點為V0.
(1)求最短弧集合E0.
從所有以Vi(i ≠ 0)為終點的弧中取一條最短的,若對於點i,沒有入邊,則不存在最小樹形圖,算法結束;如果能取,則得到由n個點和n-1條邊組成的圖G的一個子圖G',這個子圖的權值一定是最小的,但是不一定是一棵樹.
(2)檢查E0.
若E0沒有有向環且不包含收縮點,則計算結束,E0就是G以V0為根的最小樹形圖,若E0沒有有向環,但是存在收縮點,轉到步驟(4),若E0含有有向環,則轉入步驟(3).
(3)收縮G中的有向環.
把G中的環C收縮成點u,對於圖G中兩端都屬於C的邊就會被收縮掉,其他弧仍然保留,得到一個新的圖G1,G1中以收縮點為終點的弧的長度要變化,變化的規則是:設點v在環C中,且環中指向v的邊的權值為w,點v'不在環C中,則對於G中的每一條邊<v', v>,在G1中有邊<v', u>和其對應,且權值WG1(<v', u>) = WG(<v', v>) - w;對於圖G中以環C中的點為起點的邊<v', v>,在圖G1中有邊<u, v'>,則WG1(<u, v'>) = WG(<v', v>).有一點需要注意,在這里生成的圖
G1可能存在重邊.
對於圖G和G1:
<1>:如果圖G1中沒有以v0為根的最小樹形圖,則圖G也沒有.
<2>:如果G1中有一v0為根的最小樹形圖,則可按照步驟(4)的展開方法得到圖G的最小樹形圖.
所以,應該對於圖G1代到(1)中反復求其最小樹形圖,直到G1的最小樹形圖u求出.
(4)展開收縮點.
假設圖G1的最小樹形圖為T1,那么所有T1中的弧都屬於圖G的最小樹形圖T.將G1的一個收縮點u展開成環C,從C中去掉與T1具有相同終點的弧,其他弧都屬於T.
總結一下,為了求一個圖的最小樹形圖,先求出最短弧集合E0.如果E0不存在,則圖的最小樹形圖也不存在.如果E0存在且不具有環,則E0就是最小樹形圖.如果E0存在但是存在有向環,則把這個環收縮成一個點u,形成新的圖G1,然后對於G1繼續求其的最小樹形圖,直到求到圖Gi.如果Gi不具有最小樹形圖,那么此圖不存在最小樹形圖,如果Gi存在最小樹形圖,那么逐層展開,就得到了原圖的最小樹形圖.
下面有張圖:
第一幅圖為原始圖G,首先對於圖G求其最短弧集合E0,即第二幅圖G1.然后檢查E0是滿足條件,在這里,可以看到G1具有兩個環,那么把這兩個環收縮,如第三幅圖所示,U1,U2分別為收縮后的點,然后對應的權值進行更新,起點是環中的點,終點是環外的點,則權值不變,反之,起點是環外的點,終點是環內的點,則權值應該減去E0中指向環內點的權值,形成新的圖,如第三幅圖.對於其反復求最小樹形圖,直到不存在最小樹形圖,或者求得縮點后的圖的最小樹形圖,然后展開就好了,如第六幅圖.
如果只要求計算權值的話,則不需要展開,所有環中權值的和加上其他各個點與點之間,或者收縮點和點之間的權值就是總的權值.
代碼:
1 #include <iostream> 2 #include <cmath> 3 #include <cstdio> 4 #include <cstring> 5 #include <cstdlib> 6 #include <algorithm> 7 #include <string> 8 typedef long long LL; 9 using namespace std; 10 const int MAXV = 100; 11 const int MAXE = 10000; 12 const int INF = 0x3f3f3f3f; 13 14 //求具有V個點,以root為根節點的圖map的最小樹形圖 15 int zhuliu(int root, int V, int map[MAXV + 7][MAXV + 7]){ 16 bool visited[MAXV + 7]; 17 bool flag[MAXV + 7];//縮點標記為true,否則仍然存在 18 int pre[MAXV + 7];//點i的父節點為pre[i] 19 int sum = 0;//最小樹形圖的權值 20 int i, j, k; 21 for(i = 0; i <= V; i++) flag[i] = false, map[i][i] = INF; 22 pre[root] = root; 23 while(true){ 24 for(i = 1; i <= V; i++){//求最短弧集合E0 25 if(flag[i] || i == root) continue; 26 pre[i] = i; 27 for(j = 1; j <= V; j++) 28 if(!flag[j] && map[j][i] < map[pre[i]][i]) 29 pre[i] = j; 30 if(pre[i] == i) return -1; 31 } 32 for(i = 1; i <= V; i++){//檢查E0 33 if(flag[i] || i == root) continue; 34 for(j = 1; j <= V; j++) visited[j] = false; 35 visited[root] = true; 36 j = i;//從當前點開始找環 37 do{ 38 visited[j] = true; 39 j = pre[j]; 40 }while(!visited[j]); 41 if(j == root)continue;//沒找到環 42 i = j;//收縮G中的有向環 43 do{//將整個環的取值保存,累計計入原圖的最小樹形圖 44 sum += map[pre[j]][j]; 45 j = pre[j]; 46 }while(j != i); 47 j = i; 48 do{//對於環上的點有關的邊,修改其權值 49 for(k = 1; k <= V; k++) 50 if(!flag[k] && map[k][j] < INF && k != pre[j]) 51 map[k][j] -= map[pre[j]][j]; 52 j = pre[j]; 53 }while(j != i); 54 for(j = 1; j <= V; j++){//縮點,將整個環縮成i號點,所有與環上的點有關的邊轉移到點i 55 if(j == i) continue; 56 for(k = pre[i]; k != i; k = pre[k]){ 57 if(map[k][j] < map[i][j]) map[i][j] = map[k][j]; 58 if(map[j][k] < map[j][i]) map[j][i] = map[j][k]; 59 } 60 } 61 for(j = pre[i]; j != i; j = pre[j]) flag[j] = true;//標記環上其他點為被縮掉 62 break;//當前環縮點結束,形成新的圖G',跳出繼續求G'的最小樹形圖 63 } 64 if(i > V){//如果所有的點都被檢查且沒有環存在,現在的最短弧集合E0就是最小樹形圖.累計計入sum,算法結束 65 for(i = 1; i <= V; i++) 66 if(!flag[i] && i != root) sum += map[pre[i]][i]; 67 break; 68 } 69 } 70 return sum; 71 }