1,最小生成樹的特徵:ios
1,選取的邊是圖中權值較小的邊;算法
2,全部邊鏈接後不構成迴路;數組
2,prim 算法是以頂點爲核心的,最下生成樹最大的特徵是邊,但 prim 算法非要以頂點爲核心來進行,有些複雜和難以理解;函數
3,既然最小生成樹關心的是如何選擇 n - 1 條邊,那麼是否能夠直接以邊爲核心進行算法設計?測試
4,簡單嘗試:spa
1,由 4 個頂點構成的圖,選擇 3 條權值最小的邊;設計
2,還要設法避免迴路;3d
5,須要解決的問題:code
1,如何判斷新選擇的邊與已選擇的邊是否構成迴路?blog
6,技巧:前驅標記數組(避開新加入邊形成迴路問題)
1,定義數組:Array<int> p(vCount());
2,定義數組元素的意義:
1,p[n] 表示頂點 n 在邊的鏈接通路上的另外一端頂點;
3,前驅標記數組到底是怎麼來的?
7,最小生成樹算法的核心步驟(Kruskal):
1,定義前驅標記數組:Array<int> p(vCount());
2,獲取當前圖中的全部邊,並存儲於 edges 數組中;
3,對數組 deges 按照權值進行排序;
4,利用 p 數組在 edges 數組中選擇前 n - 1 不構成迴路的邊;
8,Kruskal 算法流程:
9,關鍵的 find 查找函數:
10,最小生成樹算法 Kruskal (克魯斯卡)實現:
1 /* 最小、大生成樹的 kruskal 算法 */ 2 SharedPointer< Array< Edge<E> > > kruskal(const bool MINMUM = true) 3 { 4 LinkQueue< Edge<E> > ret; // 返回的隊列 5 SharedPointer< Array< Edge<E> > > edges = getUndirectedEdges(); // 將無相圖中的全部邊都拿到 6 DynamicArray<int> p(vCount()); // 前驅標記數組 7 8 /* 設置前驅標記值 */ 9 for(int i=0; i<p.length(); i++) 10 { 11 p[i] = -1; 12 } 13 14 /* 對邊數組排序 */ 15 Sort::Shell(*edges, MINMUM); // 第二個參數對邊進行從大到小的次序排序,用來生成最大生成樹 16 17 /* 進入循環,挑選邊 */ 18 for(int i=0; (i<edges->length()) && (ret.length() < (vCount()-1)); i++) // 最多循環邊的個數次,且若是邊不少但已經有 N - 1 條邊被選擇了,那麼結束循環 19 { 20 int b = find(p, (*edges)[i].b); // 在前驅標記數組中查找挑選的邊的兩個頂點 21 int e = find(p, (*edges)[i].e); // 前驅標記數組用於判斷新選擇的邊是否會形成迴路 22 23 if( b != e) // 相等會構成迴路 24 { 25 p[e] = b; // 修改前驅標記數組 26 27 ret.add((*edges)[i]); // 將這條邊加入結果集合中去 28 } 29 } 30 31 if( ret.length() != (vCount()-1) ) // 判斷邊是否夠,不夠就不能構成最小生成樹 32 { 33 THROW_EXCEPTION(InvalidOperationException, "No enough edges for Kruskal operation ..."); 34 } 35 36 return toArray(ret); // 將結果轉換爲數組 37 }
11,Kruskal 算法測試代碼:
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.kruskal(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 }
13,小結:
1,Prim 算法以頂點爲核心尋找最小生成樹,不夠直接;
2,Kruskal 算法以邊爲核心尋找最小生成樹,直觀簡單;
3,Kruskal 算法中的關鍵是前驅標記數組的使用;
4,前驅標記數組用於判斷新選擇的邊是否會形成迴路;