最小生成樹之kruskal算法
1.kruskal算法
假設連通網N=(V,{E})。則令最小生成樹的初始狀態爲只有n個頂點而無邊的非連通圖T=(V,{}),圖中每個頂點自成一個連通分量。在E中選擇最小代價的邊,若該邊依附的頂點落在T中不同的連通分量中,則將該邊加入到T中,否則捨去此邊而選擇下一條代價最小的邊,依次類推,直到T中所有頂點都在同一連通分量上爲止。
示例如下:
圖中先將每個頂點看作獨立的子圖,然後查找最小權值邊,這條邊是有限制條件的,邊得兩個頂點必須不在同一個圖中,如上圖,第一個圖中找到最小權值邊爲(v1,v3),且滿足限制條件,繼續查找到邊(v4,v6),(v2,v5),(v3,v6),當查找到最後一條邊時,僅僅只有(v2,v3)滿足限制條件,其他的如(v3,v4),(v1,v4)都在一個子圖裏面,不滿足條件,至此已經找到最小生成樹的所有邊。
2.kruskal算法程序設計
由於我們要查找邊權值最小的邊,那麼我們的第一步應該把邊權值排序,這樣就可以很快查找到權值最小的邊,爲了簡化程序設計,我們不使用其他的數據結構,僅僅設立一個結構體數組來存儲邊,用一個標記數組來標記哪些邊已經被選擇(實際程序設計的時候沒有用到標記數組);
解決了邊得存儲和排序問題,現在是算法最關鍵的就是怎麼判斷邊的兩個頂點不在一個子圖裏面,一個簡單的辦法是設立一個輔助數組f[n],初始化如下:
void Initialize()
{
int i;
for(i=0; i<n;i++)
f[i] = i;
}
如此初始化是爲了讓每個頂點各自爲一個圖,如果找到一條邊(i,j)那麼做如下標記:(i<j)
void Mark_same(int i, int j)
{
//找到i的父節點
while(f[i] != i)
{
i= f[i];
}
f[j] = i;//將j指向其父節點
}
上面的標記過程也給了我們怎麼判斷兩個頂點是否在一個圖中找到了方法,即判斷其父節點是否相同,相同則是在一個圖裏;
int Is_same(int i, int j)
{
//找到i的父節點
while(f[i] != i)
{
i= f[i];
}
//找到i的父節點
while(f[j] != j)
{
j= f[j];
}
return i == j ? 1 : 0;
}
注意:實際設計程序的時候不用標記已選邊,因爲已選擇的邊會講端點集合合併爲一個集合,從而在判斷是否爲同一集合的時候就可以排除了。
測試用例:
Kruskal.txt
0 1 6
0 2 1
0 3 5
1 2 5
1 4 3
2 3 5
2 4 6
2 5 4
4 5 6
3 5 2
測試程序:
#include <stdio.h>
#include <stdlib.h>
#define MAX 100
#define N 6//頂點數目
/* 定義邊(x,y),權爲w */
typedef struct
{
int x,y;
int w;
}edge;
edge e[MAX];
/* father[x]表示x的父節點 */
int father[N];
/* 比較函數,按權值(相同則按x座標)非降序排序 */
int cmp(const void *a, const void *b)
{
if ((*(edge *)a).w == (*(edge *)b).w)
{
return (*(edge *)a).x - (*(edge *)b).x;
}
return (*(edge *)a).w - (*(edge *)b).w;
}
/* 判斷集合是否相同 */
int Is_same(int i, int j)
{
//找到i的父節點
while(father[i] != i)
{
i = father[i];
}
//找到i的父節點
while(father[j] != j)
{
j = father[j];
}
return i == j ? 1 : 0;
}
/* 合併x,y所在的集合 */
void Mark_same(int i, int j)
{
int temp;
if(i > j)
{
temp = i;
i = j;
j = temp;
}
//找到i的父節點
while(father[i] != i)
{
i= father[i];
}
father[j] = i;//將j指向其父節點
}
//初始化 father數組
void Initialize()
{
int i;
for(i=0; i<N;i++)
father[i] = i;
}
/* 主函數 */
int main()
{
int i = 0,j, n;
int x, y;
FILE *fr;
fr = fopen("kruskal.txt","r");
if(!fr)
{
printf("fopen failed\n");
exit(1);
}
/* 讀取邊信息並初始化集合 */
while(fscanf(fr,"%d %d %d", &e[i].x, &e[i].y, &e[i].w) != EOF)
i++;
/* 將邊排序 */
qsort(e, i, sizeof(edge), cmp);
Initialize();
for (i = 0; i < N; i++)
{
if(!Is_same(e[i].x, e[i].y))
{
printf("%d %d\n",e[i].x+1, e[i].y+1);
Mark_same(e[i].x, e[i].y);
}
}
system("pause");
return 0;
}
程序結果:
1 3
4 6
2 5
3 6
1 4
2 3
請按任意鍵繼續. . .