本章介紹克魯斯卡爾算法。和以往同樣,本文會先對克魯斯卡爾算法的理論論知識進行介紹,而後給出C語言的實現。後續再分別給出C++和Java版本的實現。html
目錄
1. 最小生成樹
2. 克魯斯卡爾算法介紹
3. 克魯斯卡爾算法圖解
4. 克魯斯卡爾算法分析
5. 克魯斯卡爾算法的代碼說明
6. 克魯斯卡爾算法的源碼 git轉載請註明出處:http://www.cnblogs.com/skywang12345/github
更多內容:數據結構與算法系列 目錄算法
在含有n個頂點的連通圖中選擇n-1條邊,構成一棵極小連通子圖,並使該連通子圖中n-1條邊上權值之和達到最小,則稱其爲連通網的最小生成樹。
數組
例如,對於如上圖G4所示的連通網能夠有多棵權值總和不相同的生成樹。數據結構
克魯斯卡爾(Kruskal)算法,是用來求加權連通圖的最小生成樹的算法。 ui
基本思想:按照權值從小到大的順序選擇n-1條邊,並保證這n-1條邊不構成迴路。
具體作法:首先構造一個只含n個頂點的森林,而後依權值從小到大從連通網中選擇邊加入到森林中,並使森林中不產生迴路,直至森林變成一棵樹爲止。spa
以上圖G4爲例,來對克魯斯卡爾進行演示(假設,用數組R保存最小生成樹結果)。code
第1步:將邊<E,F>加入R中。
邊<E,F>的權值最小,所以將它加入到最小生成樹結果R中。
第2步:將邊<C,D>加入R中。
上一步操做以後,邊<C,D>的權值最小,所以將它加入到最小生成樹結果R中。
第3步:將邊<D,E>加入R中。
上一步操做以後,邊<D,E>的權值最小,所以將它加入到最小生成樹結果R中。
第4步:將邊<B,F>加入R中。
上一步操做以後,邊<C,E>的權值最小,但<C,E>會和已有的邊構成迴路;所以,跳過邊<C,E>。同理,跳過邊<C,F>。將邊<B,F>加入到最小生成樹結果R中。
第5步:將邊<E,G>加入R中。
上一步操做以後,邊<E,G>的權值最小,所以將它加入到最小生成樹結果R中。
第6步:將邊<A,B>加入R中。
上一步操做以後,邊<F,G>的權值最小,但<F,G>會和已有的邊構成迴路;所以,跳過邊<F,G>。同理,跳過邊<B,C>。將邊<A,B>加入到最小生成樹結果R中。 htm
此時,最小生成樹構造完成!它包括的邊依次是:<E,F> <C,D> <D,E> <B,F> <E,G> <A,B>。
根據前面介紹的克魯斯卡爾算法的基本思想和作法,咱們可以瞭解到,克魯斯卡爾算法重點須要解決的如下兩個問題:
問題一 對圖的全部邊按照權值大小進行排序。
問題二 將邊添加到最小生成樹中時,怎麼樣判斷是否造成了迴路。
問題一很好解決,採用排序算法進行排序便可。
問題二,處理方式是:記錄頂點在"最小生成樹"中的終點,頂點的終點是"在最小生成樹中與它連通的最大頂點"(關於這一點,後面會經過圖片給出說明)。而後每次須要將一條邊添加到最小生存樹時,判斷該邊的兩個頂點的終點是否重合,重合的話則會構成迴路。 如下圖來進行說明:
在將<E,F> <C,D> <D,E>加入到最小生成樹R中以後,這幾條邊的頂點就都有了終點:
(01) C的終點是F。
(02) D的終點是F。
(03) E的終點是F。
(04) F的終點是F。
關於終點,就是將全部頂點按照從小到大的順序排列好以後;某個頂點的終點就是"與它連通的最大頂點"。 所以,接下來,雖然<C,E>是權值最小的邊。可是C和E的重點都是F,即它們的終點相同,所以,將<C,E>加入最小生成樹的話,會造成迴路。這就是判斷迴路的方式。
有了前面的算法分析以後,下面咱們來查看具體代碼。這裏選取"鄰接矩陣"進行說明,對於"鄰接表"實現的圖在後面的源碼中會給出相應的源碼。
1. 基本定義
// 鄰接矩陣 typedef struct _graph { char vexs[MAX]; // 頂點集合 int vexnum; // 頂點數 int edgnum; // 邊數 int matrix[MAX][MAX]; // 鄰接矩陣 }Graph, *PGraph; // 邊的結構體 typedef struct _EdgeData { char start; // 邊的起點 char end; // 邊的終點 int weight; // 邊的權重 }EData;
Graph是鄰接矩陣對應的結構體。
vexs用於保存頂點,vexnum是頂點數,edgnum是邊數;matrix則是用於保存矩陣信息的二維數組。例如,matrix[i][j]=1,則表示"頂點i(即vexs[i])"和"頂點j(即vexs[j])"是鄰接點;matrix[i][j]=0,則表示它們不是鄰接點。
EData是鄰接矩陣邊對應的結構體。
2. 克魯斯卡爾算法
/* * 克魯斯卡爾(Kruskal)最小生成樹 */ void kruskal(Graph G) { int i,m,n,p1,p2; int length; int index = 0; // rets數組的索引 int vends[MAX]={0}; // 用於保存"已有最小生成樹"中每一個頂點在該最小樹中的終點。 EData rets[MAX]; // 結果數組,保存kruskal最小生成樹的邊 EData *edges; // 圖對應的全部邊 // 獲取"圖中全部的邊" edges = get_edges(G); // 將邊按照"權"的大小進行排序(從小到大) sorted_edges(edges, G.edgnum); for (i=0; i<G.edgnum; i++) { p1 = get_position(G, edges[i].start); // 獲取第i條邊的"起點"的序號 p2 = get_position(G, edges[i].end); // 獲取第i條邊的"終點"的序號 m = get_end(vends, p1); // 獲取p1在"已有的最小生成樹"中的終點 n = get_end(vends, p2); // 獲取p2在"已有的最小生成樹"中的終點 // 若是m!=n,意味着"邊i"與"已經添加到最小生成樹中的頂點"沒有造成環路 if (m != n) { vends[m] = n; // 設置m在"已有的最小生成樹"中的終點爲n rets[index++] = edges[i]; // 保存結果 } } free(edges); // 統計並打印"kruskal最小生成樹"的信息 length = 0; for (i = 0; i < index; i++) length += rets[i].weight; printf("Kruskal=%d: ", length); for (i = 0; i < index; i++) printf("(%c,%c) ", rets[i].start, rets[i].end); printf("\n"); }
這裏分別給出"鄰接矩陣圖"和"鄰接表圖"的克魯斯卡爾算法源碼。