【0】README
0.1) 本文總結於 數據結構與算法分析, 源代碼均為原創, 旨在 理解 Kruskal(克魯斯卡爾)算法 的idea 並用 源代碼加以實現;
0.2)最小生成樹的基礎知識,參見 http://blog.csdn.net/pacosonswjtu/article/details/49947085
【1】 Kruskal 算法(使用到了不相交集ADT的union/find 操作)
1.1)第二種貪婪策略是: 連續地按照最小的權選擇邊, 並且當所選的邊不產生圈時就可以吧它作為取定的邊;
1.2)形式上, Kruskal算法是在處理一個森林——樹的集合。 開始的時候, 存在 |V|顆單節點樹, 而添加一邊則將兩棵樹合並成一顆樹, 當算法終止的時候就只有一棵樹了, 這顆樹就是最小生成樹;
1.3)Kruskal算法的執行步驟, 如下圖所示,看個荔枝:
對上圖的分析(Analysis):
- A1)當添加到森林中的邊足夠多時算法終止, 實際上, 算法就是要決定邊(u, v)應該添加還是放棄。(前一章節中的 Union/Find 算法在這里適用)
1.4)我們用到了一個恆定的事實:在算法實施的任意時刻,兩個頂點屬於同一個集合當且僅當它們在當前的森林中連通;因此, 每個頂點最初是在它自己的集合中;
- 1.4.1) 如果u 和 v 在同一個集合中, 那么連接它們的邊就要放棄, 因為由於它們已經連通 了,如果再添加一條邊(u, v)就會形成一個圈了。
- 1.4.2)如果這兩個頂點不在同一個集合中, 則將該邊加入, 並對包含頂點u 和 v 的這兩個集合實施一次合並。
- 1.4.3)容易看到,這樣將保持集合不變性, 因為一旦邊(u, v)添加到生成森林中, 若w連通到 u 而 x連通到v, 則x 和 w 必然是連通的, 因此屬於相同集合 ;
1.5)固然,將邊排序可便於選取,不過,用線性時間建立一個堆則是更好的想法;
- 1.5.1)此時, deleteMin 將使得邊依序得到測試。 典型情況下, 在算法終止前只有一小部分邊需要測試, 盡管測試所有的邊的情況是有可能的;例如,還有一個頂點 v8以及值為100的邊(v5, v8),那么所有的邊都會要考察到;
1.6)因為一條邊由3個部分的數據組成, 所以在某些機器上吧優先隊列實現成指向邊的指針數組比實現成邊的數組更為有效。
- 1.6.1)這種實現的 效果在於, 為重新排列堆, 需要移動的只有那些指針, 而大量的記錄則不必移動;
1.7)時間復雜度:該算法的最壞情形運行時間為 O(|E|log|E|), 它受堆操作控制。 注意, 由於 |E|=O(|V|^2), 因此這個運行時間實際上是 O(|E|log|V|)。在實踐中, 該算法要比這個時間界指示的時間快得多;
【2】source code + printing results(將我的代碼打印結果 同 "1.3" 上圖中的手動模擬的 Kruskal 算法的結果進行比較,你會發現, 它們的結果完全相同,這也證實了我的代碼的可行性)
2.0)code specification:
- s1)本代碼采用了優先隊列(二叉小根堆)來升序選取邊;
- s2)本代碼采用了用到了不相交集ADT的 find和setUion 操作來對邊的兩個vertexes 進行union 操作以及更新它們的根;
- s3)對於根的初始化,我是這樣初始化的—— parent[0]=-1,parent[1]=-2, parent[2]=-3, parent 說白了,就是 set的 一個 index, 所以開始肯定是不一樣的; 然后,在union的時候,我只要檢查是否 i == -parent[i]-1 就可以知道 它是否是樹的根;
- s4) 在合並的時候,要對邊的兩個頂點 start 和 end 的 parent做update, 這里涉及到4種情況—— start為根且end不為根;start為根且end為根;start為不為根且end為根;start不為根且end不為根; (干貨,本代碼的重中之重以及新穎之處)
2.1)download source code: https://github.com/pacosonTang/dataStructure-algorithmAnalysis/tree/master/chapter9/p240_kruskal
2.2)source code at a glance(for complete code , please click the given link above):
#include <stdio.h>
#include "binaryheap.h"
// allocate memory for the vertexes with size
Vertex* makeEmptyVertexes(int size)
{
Vertex *array;
int i;
array = (Vertex*)malloc(size * sizeof(Vertex));
if(!array)
{
Error("out of space, from func makeEmptyVertexes");
return NULL;
}
// initializing the set index towards every vertex with its array index
for(i = 1; i <= size; i++)
array[i-1] = -i;
return array;
}
void printParent(Vertex* vertexes, int size)
{
int i;
printf("\n\n\t the parent of every vertex at a glance");
for(i=0; i<size; i++)
printf("\n\t parent[%d] = %d", i, vertexes[i]);
}
int find(Vertex *parent, Vertex single)
{
while (single >= 0)
single = parent[single];
return single;
}
//judge whether the vertex index is the parent or not, also 1 or 0
//if the vertex under index is not the parent ,that's to say its parent is one of other vertexes
int isParent(Vertex *parent, Vertex index)
{
return parent[index] == -index-1;
}
void setUnion(Vertex *parent, Vertex start, Vertex end)
{
if(isParent(parent, start) ) // start is the parent
{
if(!isParent(parent, end)) // but end is not the parent
end = find(parent, end) + 1; // find the parent of end
parent[start] = end;
}
else // start is not the parent
{
start = -find(parent, start) - 1; // find the parent of start
if(!isParent(parent, end)) // but end is not the parent
end = find(parent, end) + 1; // find the parent of end
parent[end] = start;
}
}
void kruskal(BinaryHeap bh, int vertexNum)
{
int counter;
int set1;
int set2;
Vertex start;
Vertex end;
Vertex* parent;
ElementType singleEdge;
counter = 0;
parent = makeEmptyVertexes(vertexNum);
while(counter < vertexNum - 1)
{
singleEdge = deleteMin(bh);
start = singleEdge.start;
end = singleEdge.end;
set1 = find(parent, start);
set2 = find(parent, end);// find the set of vertex start and end
if(set1 != set2)
{
setUnion(parent, start, end);
counter++;
printf("\n\t weight(v%d,v%d) = %d", singleEdge.start+1, singleEdge.end+1, singleEdge.weight);
}
}
printParent(parent, vertexNum);
printf("\n\n\t");
}
int main()
{
BinaryHeap bh;
ElementTypePtr temp;
int vertexNum;
int size = 7;
int capacity;
int i;
int j;
int adjTable[7][7] =
{
{0, 2, 4, 1, 0, 0, 0},
{2, 0, 0, 3, 10, 0, 0},
{4, 0, 0, 2, 0, 5, 0},
{1, 3, 2, 0, 7, 8, 4},
{0, 10, 0, 7, 0, 0, 6},
{0, 0, 5, 8, 0, 0, 1},
{0, 0, 0, 4, 6, 1, 0},
};
vertexNum = 7;
capacity = vertexNum * vertexNum;
bh = initBinaryHeap(capacity);
temp = makeEmptyElement();
printf("\n\n\t ====== test for kruskal alg building minimum spanning tree ======\n");
//building binary heap with edge including 2 vertexs and its weight
for(i = 0; i < size; i++)
{
for(j = i+1; j < size; j++)
if(adjTable[i][j])
{
temp->start = i;
temp->end = j;
temp->weight = adjTable[i][j];
insertHeap(temp, bh); // insertAdj the adjoining table over
}
}
kruskal(bh, vertexNum);
return 0;
}
// allocate memory for the array with size
ElementTypePtr *makeEmptyArray(int size)
{
ElementTypePtr *array;
int i;
array = (ElementTypePtr*)malloc(size * sizeof(ElementTypePtr));
if(!array)
{
Error("out of space, from func makeEmptyArray");
return NULL;
}
for(i=0; i<size; i++)
array[i] = makeEmptyElement();
return array;
}
// allocate memory for the single element
ElementTypePtr makeEmptyElement()
{
ElementTypePtr temp;
temp = (ElementTypePtr)malloc(sizeof(ElementType));
if(!temp)
{
Error("out of space, from func makeEmptyElement!");
return NULL;
}
return temp;
}
2.3)printing results: