算法基本思想:設無向連通網爲G=(V, E),令G的最小生成樹爲T=(U, TE),其初態爲U=V,TE={ },然後,按照邊的權值由小到大的順序,考察G的邊集E中的各條邊。若被考察的邊的兩個頂點屬於T的兩個不同的連通分量,則將此邊作爲最小生成樹的邊加入到T中,同時把兩個連通分量連接爲一個連通分量;若被考察邊的兩個頂點屬於同一個連通分量,則捨去此邊,以免造成迴路,如此下去,當T中的連通分量個數爲1時,此連通分量便爲G的一棵最小生成樹。
我們在前面講過的《克里姆算法》是以某個頂點爲起點,逐步找各頂點上最小權值的邊來構建最小生成樹的。同樣的思路,我們也可以直接就以邊爲目標去構建,因爲權值爲邊上,直接找最小權值的邊來構建生成樹也是很自然的想法,只不過構建時要考慮是否會形成環而已,此時我們就用到了圖的存儲結構中的邊集數組結構,如圖7-6-7
假設現在我們已經通過鄰接矩陣得到了邊集數組edges並按權值從小到大排列如上圖。
下面我們對着程序和每一步循環的圖示來看:
算法代碼:(改編自《大話數據結構》)
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)。
對比普里姆和克魯斯卡爾算法,克魯斯卡爾算法主要針對邊來展開,邊數少時效率比較高,所以對於稀疏圖有較大的優勢;而普里姆算法對於稠密圖,即邊數非常多的情況下更好一些。