1,運營商的挑戰:ios
1,在下圖標出的城市間架設一條通訊線路;算法
2,要求:數組
1,任意兩個城市間都可以通訊;函數
2,將架設成本降至最低;測試
2,問題抽象:spa
1,如何在圖中選擇 n - 1 條邊使得 n 個頂點間兩兩可達,而且這 n - 1 條邊的權值之和最小?指針
3,最小(大)生成樹:code
1,僅使用圖中的 n - 1 條邊鏈接圖中的 n 個頂點;對象
2,不能使用產生迴路的邊;blog
3,各邊上的權值總和達到最小(大);
4,尋找最小生成樹:
5,使用 prim 方法手工尋找最小生成樹:
6,最小生成樹算法步驟(prim):
1,選擇某一頂點 v0 做爲起始頂點,使得 T = {v0},F = {v1, v2, ..., vn},E = {};
2,每次選擇一條邊,這條邊是全部(u, v)中權值最小的邊,且 u 屬於 T,v 屬於 F;
3,修改 T,F,E:
T = T + {v}, F = F - {v}, E = E + {(u, v)}
4,當 F != NULL 時,且(u, v)存在,轉 2;不然,結束;
7,最小生成樹算法的原材料:
1,若是 T 集合到 F 集合中同一個頂點的鏈接有多條,那麼選取權值最小的鏈接;
8,最小生成樹算法流程圖:
9,注意事項:
1,最小生成樹僅針對無向圖有意義;
2,必須判斷圖對象是否可以看作無向圖;
1,若是能夠纔可以用 prim 算法;
10,有向圖可看作無向圖的充分條件:
1,有向的任意兩頂點之間若存在鏈接,則兩頂點相互可達、權值相等;
11,圖類型(Graph)中的新增成員函數:
1,virtual bool isAdjacent(int i, int j) = 0;
1,判斷在當前圖中頂點 i 到頂點 j 是否鄰接;
2,bool asUndirected();
1,判斷當前的有向圖是否可以看作無向圖;
12,最小生成樹 prim 算法實現:
1,判斷鄰接的實現:
1,鄰接矩陣的實現:
1 /* 判斷 i 到 j 頂點邊是否鏈接,值不爲空就鏈接 */ 2 bool isAdjacent(int i, int j) 3 { 4 return (0 <= i) && (i < vCount()) && (0 <= j) && (j < vCount()) && (m_edges[i][j] != NULL); 5 }
2,鄰接鏈表的實現:
1 bool isAdjacent(int i, int j) 2 { 3 return (0 <= i) && (i < vCount()) && (0 <= j) && (j < vCount()) && (m_list.get(i)->edge.find(Edge<E>(i, j)) >= 0); 4 }
2,判斷是否爲無向圖的實現:
1 /* 最小生成樹只能用於無向圖,而咱們針對的是有向圖,因此要判斷有向圖何時可以被當作無向圖 */ 2 bool asUndirected() 3 { 4 bool ret = true; 5 6 for(int i=0; i<vCount(); i++) 7 { 8 for(int j=0; j<vCount(); j++) 9 { 10 /* i 到 j 是鏈接的,而且 j 到 i 也是鏈接的,而後權值也要相等 */ 11 if( isAdjacent(i, j) ) 12 { 13 ret = ret && isAdjacent(j, i) && (getEdge(i, j) == getEdge(j, i)); 14 } 15 } 16 } 17 18 return ret; 19 }
3,Prim算法實現:
1 /* Prim 法實現最小、大生成樹;返回值是數組,由於最小、大生成樹的結果就是一系列的邊,因此返回邊的數組;參數表示理論上的最大權值; */ 2 SharedPointer< Array< Edge<E> > > prim(const E& LIMIT, const bool MINIMUM = true) // 返回一個指向存儲邊的數組指針 3 { 4 LinkQueue< Edge<E> > ret; // 返回邊隊列,本質是 E 集合 5 6 /* 執行 prim 算法 */ 7 if( asUndirected() ) // 無向圖 8 { 9 DynamicArray<int> adjVex(vCount());//保存最小權值的邊的 F集合中頂點 10 DynamicArray<bool> mark(vCount()); // 保存 T 集合或者 F 集合的標記 11 DynamicArray<E> cost(vCount()); // 保存最小權值的頂點中 E 集合中頂點,尋找最小值要配合 mark 使用 12 SharedPointer< Array<int> > aj = NULL; // 保存某個頂點鄰接數組 13 bool end = false; // 用於標記判斷 prim 是否要中斷執行 14 int v = 0; // 表明習慣性的從 0 頂點生成最小生成樹 15 16 /* 執行初始化 */ 17 for(int i=0; i<vCount(); i++) 18 { 19 adjVex[i] = -1; // 沒有邊被訪問 20 mark[i] = false; // 頂點都沒有被訪問 21 cost[i] = LIMIT; // 參數傳遞理論上的最大權值 22 } 23 24 mark[v] = true; // 初始頂點作標記 25 26 aj = getAdgacent(v); // 獲取初始頂點的鄰接頂點 27 28 /* 設置初始頂點對應的位置 */ 29 for(int j=0; j<aj->length(); j++) 30 { 31 cost[(*aj)[j]] = getEdge(v, (*aj)[j]); // 保存/到對應頂點的響應權值 32 adjVex[(*aj)[j]] = v; // 記錄權值所對應的頂點,即可以獲得邊 33 } 34 35 /* 真正循環找邊 */ 36 for(int i=0; (i<vCount()) && !end; i++) // 最多循環頂點次;也可能條件不知足,提早結束,因此有 !end 37 { 38 E m = LIMIT; 39 int k = -1; // 記錄最小值的頂點 40 41 /* 經過 cost 數組找最小值 */ 42 for(int j=0; j<vCount(); j++) 43 { 44 if( !mark[j] && (MINIMUM ? (cost[j] < m) : (cost[j] > m)) ) // !makr[j] 條件是由於選取最小權值時本質上是選取鏈接的最小邊,對應的頂點是在 F集合,此時 mark 中對應的值爲假當中的,則在 mark 數組中對應的就爲假,因此要這個條件,這裏有最小值最大值的設置 45 { 46 m = cost[j]; 47 k = j; // 獲得記錄的最小值的頂點號 48 } 49 } 50 end = (k == -1); // 是否找到合法最小權值,由於有可能在上面 if 條件中沒有找到合法的最小權值 51 if( !end ) 52 { 53 ret.add(Edge<E>(adjVex[k], k, getEdge(adjVex[k], k))); // 在 adjVex 中找到這條邊 54 55 mark[k] = true; // 標記頂點進入了 T 集合 56 57 aj = getAdgacent(k); // 找新的集合鏈接 58 59 /* 找到以後更新 cost 數組和 adgVex 數組 */ 60 for(int j=0; j<aj->length(); j++) 61 { 62 if( !mark[(*aj)[j]] && (MINIMUM ? (getEdge(k, (*aj)[j]) < cost[(*aj)[j]]) : (getEdge(k, (*aj)[j]) > cost[(*aj)[j]])) ); //只對 F 集合操做 63 { 64 cost[(*aj)[j]] = getEdge(k ,(*aj)[j]); //若是 T 到 F 集合新鏈接權值較小,則記錄到 cost 數組中,新加入的點 k 和以前 T 集合裏的點到 F 集合裏的點的權值要比較呢;若是在 k 到 F 集合中找不到合適的點,則用T中的點代替 65 adjVex[(*aj)[j]] = k; // 將最小權值的起始點設入到鄰接邊中 66 } 67 } 68 } 69 } 70 } 71 else 72 { 73 THROW_EXCEPTION(InvalidOperationException, "Prim operation is for undirected graph only ..."); 74 } 75 76 /* 判斷邊的數目是否夠,即 n-1 條邊 */ 77 if( ret.length() != (vCount() - 1) ) 78 { 79 THROW_EXCEPTION(InvalidOperationException, "No enough edge for prim operation ..."); 80 } 81 return toArray(ret); // 返回值是邊的數組 82 }
14,prim 算法測試代碼:
1 #include <iostream> 2 #include "MatrixGraph.h" 3 #include "ListGraph.h" 4 5 using namespace std; 6 using namespace DTLib; 7 8 template< typename V, typename E > 9 Graph<V, E>& GraphEasy() 10 { 11 static MatrixGraph<4, V, E> g; 12 13 g.setEdge(0, 1, 1); 14 g.setEdge(1, 0, 1); 15 g.setEdge(0, 2, 3); 16 g.setEdge(2, 0, 3); 17 g.setEdge(1, 2, 1); 18 g.setEdge(2, 1, 1); 19 g.setEdge(1, 3, 4); 20 g.setEdge(3, 1, 4); 21 g.setEdge(2, 3, 1); 22 g.setEdge(3, 2, 1); 23 24 return g; 25 } 26 27 template< typename V, typename E > 28 Graph<V, E>& GraphComplex() 29 { 30 static ListGraph<V, E> g(9); 31 32 g.setEdge(0, 1, 10); 33 g.setEdge(1, 0, 10); 34 g.setEdge(0, 5, 11); 35 g.setEdge(5, 0, 11); 36 g.setEdge(1, 2, 18); 37 g.setEdge(2, 1, 18); 38 g.setEdge(1, 8, 12); 39 g.setEdge(8, 1, 12); 40 g.setEdge(1, 6, 16); 41 g.setEdge(6, 1, 16); 42 g.setEdge(2, 3, 22); 43 g.setEdge(3, 2, 22); 44 g.setEdge(2, 8, 8); 45 g.setEdge(8, 2, 8); 46 g.setEdge(3, 8, 21); 47 g.setEdge(8, 3, 21); 48 g.setEdge(3, 6, 24); 49 g.setEdge(6, 3, 24); 50 g.setEdge(3, 7, 16); 51 g.setEdge(7, 3, 16); 52 g.setEdge(3, 4, 20); 53 g.setEdge(4, 3, 20); 54 g.setEdge(4, 5, 26); 55 g.setEdge(5, 4, 26); 56 g.setEdge(4, 7, 7); 57 g.setEdge(7, 4, 7); 58 g.setEdge(5, 6, 17); 59 g.setEdge(6, 5, 17); 60 g.setEdge(6, 7, 19); 61 g.setEdge(7, 6, 19); 62 63 return g; 64 } 65 66 int main() 67 { 68 Graph<int, int>& g = GraphEasy<int, int>(); 69 SharedPointer< Array< Edge<int> > > sa = g.prim(65535); 70 71 int w = 0; 72 73 for(int i=0; i<sa->length(); i++) 74 { 75 w += (*sa)[i].data; 76 77 cout << (*sa)[i].b << " " << (*sa)[i].e << " " << (*sa)[i].data << endl; 78 } 79 80 cout << "Weight: " << w << endl; 81 82 return 0; 83 }
15,小結:
1,最小生成樹使得頂點間的連通代價最小;
2,Prim 算法經過頂點的動態標記尋找最小生成樹;
3,Prim 算法的關鍵是集合概念的運用(T 集合,F 集合);
4,利用 Prim 算法的思想也能尋找圖的「最大生成樹」;