概覽
相比於普里姆算法(Prim算法),克魯斯卡爾算法直接以邊為目標去構建最小生成樹。從按權值由小到大排好序的邊集合{E}中逐個尋找權值最小的邊來構建最小生成樹,只要構建時,不會形成環路即可保證當邊集合{E}中的邊都被嘗試了過后所形成的樹為最小生成樹。
定義
假設G=(V, {E})是連通網(即帶權連通圖),則令最小生成樹的初始狀態為只有N個頂點而無邊的非連通圖T=(V, {}),圖T中每個頂點自成一個連通分量。在圖G的邊集{E}中選擇權值最小的邊e,若e依附的頂點落在T中不同的連通分量上,則將e加入到T中,否則舍去e而選擇下一條權值最小的邊。以此類推,直至T中所有頂點都在同一連通分量上為止。
相關概念
連通:在無向圖G中,如果從頂點v到v’有路徑,則稱v和v’是連通的。
連通圖:如果對於圖G中任意兩個頂點vi、vj∈E,vi和vj都是連通的,則稱G是連通圖。
過程簡述
輸入:帶權連通圖(網)G的邊集E及頂點個數。(E已按權值的升序排序。)
初始:T=(V, {}), V是圖G的頂點集合且各頂點自成一個連通分量;表示邊的集合為空{}。
操作:重復以下操作,直到T中所有頂點都在同一個連通分量上。
- 依次取E中一條邊e(邊e必為未嘗試過的邊中權值最小的邊。因為{E}已按權值升序排序)。
- 將e的兩個頂點分別放入T的各連通分量集合V中以測試該頂點是否分別在不同連通分量中。
- 設存在一個方法/函數Find(V, vertex),從連通分量集合V的vertex頂點開始沿該連通分量查找,返回以vertex開始的連通分量的最后一個頂點(的下標)。令n=Find(V, e.begin)和m=Find(V, e.end),若n≠m則e存在於T的不同連通分量中,故將e.end加入到以e.begin開始的連通分量中去。(注:e.begin表示e的開始頂點;e.end表示e的結束頂點;雖然無向圖的邊不存在開始頂點或結束頂點,但是作為程序表示,也得有兩個值來表示邊的兩個頂點。)(為什么n≠m則兩個頂點分別位於不同的連通分量中?若v1、v4、v6位於同一個連通分量,v3、v7位於另一個連通分量。那么怎么表示這兩個連通分量呢?可以用一個數組來表示!數組的索引本身即是頂點v的下標,而v在數組中對應的存儲單元存有構成該連通分量的下一個頂點v’。用一個數組parent表示圖T的V。那么parent[1]=4,parent[4]=6,parent[6]=0,0表示該連通圖中已無別的頂點;parent[3]=7,parent[7]=0。對於邊(1, 6)(或(6, 1)),將1帶入parent數組,最終會沿着連通圖找到n=6;將6帶入parent數組,最終會沿着連通圖找到m=6。n=m所以這兩個頂點位於相同連通圖中。而對於邊(4, 7)(或(7, 4)),將4帶入parent數組,得到n=6;將7帶入parent數組,得到m=7。n≠m所以兩個頂點位於不同連通圖中。把點v7所處的連通圖放入v1、v4、v6構成的連通圖中:parent[6]=7(parent[n]=m)反過來點v4所處的連通圖放入v3、v7構成的連通圖中,即parent[7]=6也行,只采用兩者之一即可。)
- 直到T中所有頂點都在同一連通分量上為止。(E中的每一條邊都嘗試一遍即可。)
輸出:最小生成樹。
如何實現
輸入:用Edge類表示邊,其中Begin/End/Weight域分別表示邊的兩個頂點的下標及權重。Edge數組E表示邊集,N表示頂點個數。(E已按權值的升序排序。)
初始:用包含N個存儲單元的數組parent表示T=(V, {})的 V,即各頂點自成的連通分量。parent數組的下標i即為頂點的下標,i處存放的值parent[i]即為連通分量中下一個頂點的下標,parent[i]=0表示該連通分量已結束。將parent的各存儲單元初始化為0。
操作:重復以下操作,直到T中所有頂點都在同一個連通分量上。
- 依次取E中一條邊e。
- 將e.Begin和e.End帶入parent數組,找到連通分量中的最后一個頂點。n=Find(parent, e.Begin)和m=Find(parent, e.end),若n≠m則e存在於T的不同連通分量中,故將點e.End所處的連通圖加入到點e.Begin所處的連通圖中去,即parent[n]=m。(反過來parent[m]=n也行。)
- 直到E中的每一條邊都嘗試一遍即可。
輸出:最小生成樹。
如上面的這個圖G=(V, {E}),其中V={v0, v1, v2, v3, v4, v5, v6, v7, v8},E= {(v4, v7, 7), (v2, v8, 8), (v0, v1, 10), (v0, v5, 11), (v1, v8, 12), (v3, v7, 16), (v1, v6, 16), (v5, v6, 17), (v1, v2, 18), (v6, v7, 19), (v3, v4, 20), (v3, v8, 21), (v2, v3, 22), (v3,v6, 24), (v4, v5, 26)}
用一個邊集來表示該圖G,得上圖右邊的數組。
① 輸入:帶權連通圖G=(V, {E})的邊集合及頂點數目,求圖G的最小生成樹。
② 初始:T={V, {}},用數組parent=int[9]來表示V,parent數組記錄的是以索引i表示的頂點開始到parent[i]表示的頂點構成的連通圖。例如:(parent數組本身就含有兩個信息:索引和索引處的值,vertex數組是不存在的,只是為了輔助理解。)
非連通圖頭頂點下標vertex:[ 0, 1, 2, 3, 4, 5, 6, 7, 8 ]
非連通圖尾頂點下標parent:[ 0, 0, 8, 0, 7, 0, 0, 0, 0 ]
parent[2]=8,parent[8]=0即頂點v2、v8構成一個連通分量。
parent[4]=7,parent[7]=0即頂點v4、v7構成一個連通分量。
③ 操作:
1.上圖中,邊(4, 7, 7)權值最小,取該邊為e。
2. 此時parent[4]=0,故n=4;parent[7]=0,故m=7;n≠m故parent[4]=7,將{v4}和{v7}這兩個連通圖合並為一個連通圖。執行后parent數組如下:
非連通圖頭頂點下標vertex:[ 0, 1, 2, 3, 4, 5, 6, 7, 8 ]
非連通圖尾頂點下標parent:[ 0, 0, 0, 0, 7, 0, 0, 0, 0 ]
解釋:parent[2]=0,即v2自成一個連通圖。parent[4]=7,parent[7]=0即v4所在連通圖中還有v7,v7接下來沒有別的頂點了,即v4、v7在同一個連通圖中。
3.從E中取下一條邊繼續上面1、2步驟的操作。
④輸出:
演示過程
(4,7) = 7
非連通圖頭頂點下標:[ 0, 1, 2, 3, 4, 5, 6, 7, 8 ]
非連通圖尾頂點下標:[ 0, 0, 0, 0, 7, 0, 0, 0, 0 ]
(2,8) = 8
非連通圖頭頂點下標:[ 0, 1, 2, 3, 4, 5, 6, 7, 8 ]
非連通圖尾頂點下標:[ 0, 0, 8, 0, 7, 0, 0, 0, 0 ]
(0,1) = 10
非連通圖頭頂點下標:[ 0, 1, 2, 3, 4, 5, 6, 7, 8 ]
非連通圖尾頂點下標:[ 1, 0, 8, 0, 7, 0, 0, 0, 0 ]
(0,5) = 11
非連通圖頭頂點下標:[ 0, 1, 2, 3, 4, 5, 6, 7, 8 ]
非連通圖尾頂點下標:[ 1, 5, 8, 0, 7, 0, 0, 0, 0 ]
(1,8) = 12
非連通圖頭頂點下標:[ 0, 1, 2, 3, 4, 5, 6, 7, 8 ]
非連通圖尾頂點下標:[ 1, 5, 8, 0, 7, 8, 0, 0, 0 ]
(3,7) = 16
非連通圖頭頂點下標:[ 0, 1, 2, 3, 4, 5, 6, 7, 8 ]
非連通圖尾頂點下標:[ 1, 5, 8, 7, 7, 8, 0, 0, 0 ]
(1,6) = 16
非連通圖頭頂點下標:[ 0, 1, 2, 3, 4, 5, 6, 7, 8 ]
非連通圖尾頂點下標:[ 1, 5, 8, 7, 7, 8, 0, 0, 6 ]
(6,7) = 19
非連通圖頭頂點下標:[ 0, 1, 2, 3, 4, 5, 6, 7, 8 ]
非連通圖尾頂點下標:[ 1, 5, 8, 7, 7, 8, 7, 0, 6 ]
運行結果
(4, 7) = 7
(2, 8) = 8
(0, 1) = 10
(0, 5) = 11
(1, 8) = 12
(3, 7) = 16
(1, 6) = 16
(6, 7) = 19
算法代碼
見鏈接:克魯斯卡爾(Kruskal)算法(代碼) - kokiafan - 博客園 (cnblogs.com)
復雜度
它的時間復雜度為O(eloge)(e為網中的邊數),所以,適合於求邊稀疏的網的最小生成樹。
參考資料:
《大話數據結構》 - 程傑 著 - 清華大學出版社 第252頁