圖解最小生成樹 - 克魯斯卡爾(Kruskal)算法
我們在前面講過的《克裏姆算法》是以某個頂點為起點,逐步找各頂點上最小權值的邊來構建最小生成樹的。同樣的思路,我們也可以直接就以邊為目標去構建,因為權值為邊上,直接找最小權值的邊來構建生成樹也是很自然的想法,只不過構建時要考慮是否會形成環而已,此時我們就用到了圖的存儲結構中的邊集數組結構,如圖7-6-7
假設現在我們已經通過鄰接矩陣得到了邊集數組edges並按權值從小到大排列如上圖。
下面我們對著程序和每一步循環的圖示來看:
算法代碼:(改編自《大話數據結構》)
C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
typedef struct { int begin; int end; int weight; } Edge; /* 查找連線頂點的尾部下標 */ int Find(int *parent, int f) { while (parent[f] > 0) f = parent[f]; return f; } /* 生成最小生成樹 */ void MiniSpanTree_Kruskal(MGraph G) { int i, j, n , m; int k = 0; int parent[MAXVEX];/* 定義一數組用來判斷邊與邊是否形成環路 */ Edge edges[MAXEDGE];/* 定義邊集數組,edge的結構為begin,end,weight,均為整型 */ /* 此處省略將鄰接矩陣G轉換為邊集數組edges並按權由小到大排列的代碼*/ for (i = 0; i < G.numVertexes; i++) parent[i] = 0; cout << "打印最小生成樹:" << endl; for (i = 0; i < G.numEdges; i++)/* 循環每一條邊 */ { n = Find(parent, edges[i].begin); m = Find(parent, edges[i].end); if (n != m)/* 假如n與m不等,說明此邊沒有與現有的生成樹形成環路 */ { parent[n] = m;/* 將此邊的結尾頂點放入下標為起點的parent中。 */ /* 表示此頂點已經在生成樹集合中 */ cout << "(" << edges[i].begin << ", " << edges[i].end << ") " << edges[i].weight << endl; } } } |
1、程序 第17~28行是初始化操作,中間省略了一些存儲結構轉換代碼。
2、第30~42行,i = 0 第一次循環,n = Find( parent, 4) = 4; 同理 m = 7; 因為 n != m 所以parent[4] = 7, 並且打印 “ (4, 7) 7 ” 。此時我們已經將邊(v4, v7)納入到最小生成樹中,如下圖的第一個小圖。
3、繼續循環,當i從1 至 6 時,分別把(v2, v8), (v0, v1), (v0, v5), (v1, v8), (v3, v7), (v1, v6)納入到最小生成樹中,如下圖所示,此時parent數組為
{ 1, 5, 8, 7, 7, 8, 0, 0, 6 },如何解讀現在這些數字的意義呢?從圖i = 6來看,其實是有兩個連通的邊集合A與B 納入到最小生成樹找中的,如圖7-6-12所示。parent[0] = 1表示v0 和v1 已經在生成樹的邊集合A中,將parent[0] = 1中的 1 改成下標,由parent[1] = 5 ,表示v1 和v5 已經在生成樹的邊集合A中,parent[5] = 8 ,表示v5 和v8 已經在生成樹的邊集合A中,parent[8] = 6 ,表示v8 和v6 已經在生成樹的邊集合A中,parent[6] = 0 表示集合A暫時到頭,此時邊集合A有 v0, v1, v5, v6, v8。查看parent中沒有查看的值,parent[2] = 8,表明v2 和 v8在一個集合中,即A中。再由parent[3] = 7, parent[4] = 7 和 parent[7] = 0 可知v3, v4, v7 在一個邊集合B中。
4、當i = 7時, 調用Find函數,n = m = 6,不再打印,繼續下一循環,即告訴我們,因為(v5, v6) 使得邊集合A形成了回路,因此不能將其納入生成樹中,如圖7-6-12所示。
5、當i = 8時與上面相同,由於邊(v1, v2) 使得邊集合A形成了回路,因此不能將其納入到生成樹中,如圖7-6-12所示。
6、當i = 9時,n = 6, m = 7, 即parent[6] = 7,打印“(6, 7)19” ,此時parent數組為{ 1, 5, 8, 7, 7, 8, 7, 0, 6 } ,如圖7-6-13所示。
最後,我們來總結一下克魯斯卡爾算法的定義:
假設 N = (V, {E} )是連通網,則令最小生成樹的初始狀態為只有n個頂點而無邊的非連通圖T { V, {} },圖中每個頂點自成一個連通分量。在E中選擇代價最小的邊,若該邊依附的頂點落在T中不同的連通分量上,則將其加入到 T 中,否則舍去此邊而選擇下一條代價最小的邊。依次類推,直至T中所有頂點都在同一連通分量上為止。
此算法的Find函數由邊數e決定,時間復雜度為O(loge),而外面有一個for循環e次,所以克魯斯卡爾算法的時間復雜度為O(eloge)。
對比普裏姆和克魯斯卡爾算法,克魯斯卡爾算法主要針對邊來展開,邊數少時效率比較高,所以對於稀疏圖有較大的優勢;而普裏姆算法對於稠密圖,即邊數非常多的情況下更好一些。
圖解最小生成樹 - 克魯斯卡爾(Kruskal)算法