1. 程式人生 > >圖解最小生成樹 - 克魯斯卡爾(Kruskal)算法

圖解最小生成樹 - 克魯斯卡爾(Kruskal)算法

lin p s nbsp 時間 top table idt max 回路

我們在前面講過的《克裏姆算法》是以某個頂點為起點,逐步找各頂點上最小權值的邊來構建最小生成樹的。同樣的思路,我們也可以直接就以邊為目標去構建,因為權值為邊上,直接找最小權值的邊來構建生成樹也是很自然的想法,只不過構建時要考慮是否會形成環而已,此時我們就用到了圖的存儲結構中的邊集數組結構,如圖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)算法