這篇文章是2.0版本,修正了前一版中的錯誤,感謝廣大網友指正!
前面介紹了圖的最小生成樹的Prim算法,這個算法是從頂點的角度來刻畫生成樹的。今天要說的Kruskal(克魯斯卡爾)算法,是從邊的角度來進行刻畫的。
Kruskal算法用到的數據結構有:
1、邊頂點與權值存儲結構(即圖是由連接某一條邊的兩個頂點,以及這條邊的權值來進行存儲,具體看後面的例子)
2、並查集(具體是什麼以及作用在後面的例子中解釋)
Kruskal算法步驟 -- 我們今天要實現的目標依然與前面Prim算法的相同,計算最小生成樹的權值之和
1、前期準備(數據結構)
在無向圖右邊的便是圖的存儲結構,可以看出這個存儲結構不同於我們所熟知的鄰接矩陣和鄰接表,這個存儲結構的每一項,以邊爲單位,存儲着連接這條邊的兩個頂點,以及這條邊的權值。比如第一項,頂點0與頂點1鄰接,這條邊的權值爲1,所以左邊填入(0,1),右邊爲1。這個頂點誰先誰後都可以。存儲結構以邊爲基準,有幾條邊就有幾項。
在存儲結構的右邊就是上面提到的並查集了,並查集就是一個用雙親表示法所表示的森林,我們可以利用這個結構來查找某一個頂點的雙親,進而找到根結點。這樣,我們就能判斷某兩個頂點是否同源,在圖中的表現就是加上這條邊後會不會形成環。如果形成環,就不是簡單圖,就不屬於考研(好吧,LL在準備考研,怕忘了,就記錄下來)數據結構的研究範圍了。並查集以頂點爲基準,有幾個頂點,就有幾項。
PS.這裏適用與頂點編號連續的情況,這樣在並查集中,數組的下標就對應頂點的編號,數組的值就是這個頂點所在的雙親。這就是樹的雙親表示法。高效率地利用數組下標。
2、算法步驟
a、對圖的存儲結構,按照權值,從小到大排序。(上圖是已經排序好的)
b、對並查集進行初始化,即把每一個位置中的值初始化爲其對應下標。(上圖是已經初始化好的)
c、選取存儲結構的第一項(最小項),查詢該邊所對應的頂點在並查集中是否同源,同源則進行e,不同源則進行d
d、若不同源,則把該邊加入生成樹,並計算和;修改前者的根在並查集中位置的值爲後者的根。如下圖:第一項(0,1)不同源,頂點0的根爲0,頂點1的根爲1,設a爲並查集數組,把a[0] = 1,即把並查集中下標爲0的位置中的值修改爲1。這樣,(0,1)這條路徑就加入了最小生成樹。
e、若同源,則跳過,繼續遍歷存儲結構,如下圖
Now指針指的是現在所處理的項,頂點0的根爲4(因爲第0個結點的雙親是結點1,結點1的雙親是結點4,結點4的雙親是它自己,說明結點4就是結點0的根結點),頂點2的根也爲4,則跳過該項,繼續遍歷。
f、重複d~e,直到存儲結構中所有的項被遍歷。
現在就到代碼階段了。
我們要準備以下函數:
1、排序函數sort,任何一種排序算法都行,下面的示例代碼中,我採用的是冒泡排序算法
2、尋源函數getRoot,尋找某一個點在並查集中的根,注意,是根,不是雙親!,所以,判斷的條件爲如果某一個下標的值就是其本身,設a爲並查集數組,v爲數組值,如果a[v] = v,它就是根,否則就讓v = a[v],向上尋找,直到其相等。
下面上代碼
1、圖的存儲結構(a,b爲邊的兩個頂點,w爲邊的權值)
#define Max 50
typedef struct road *Road;
typedef struct road
{
int a , b;
int w;
}road;
typedef struct graph *Graph;
typedef struct graph
{
int e , n;
Road data;
}graph;
2、排序sort函數(按照權值從小到大)
void sort(Road data, int n)
{
int i , j;
for(i = 1 ; i <= n-1 ; i++)
{
for(j = 1 ; j <= n-i ; j++)
{
if(data[j].w > data[j+1].w)
{
road t = data[j];
data[j] = data[j+1];
data[j+1] = t;
}
}
}
}
3、getRoot尋源函數(v爲並查集,x爲待查頂點)
int getRoot(int v[], int x)
{
while(v[x] != x)
{
x = v[x];
}
return x;
}
4、完整代碼(我這裏頂點採用了先小後大的排序)
#include <stdio.h>
#include <stdlib.h>
#define Max 50
typedef struct road *Road;
typedef struct road
{
int a , b;
int w;
}road;
typedef struct graph *Graph;
typedef struct graph
{
int e , n;
Road data;
}graph;
Graph initGraph(int m , int n)
{
Graph g = (Graph)malloc(sizeof(graph));
g->n = m;
g->e = n;
g->data = (Road)malloc(sizeof(road) * (g->e));
return g;
}
void create(Graph g)
{
int i;
for(i = 1 ; i <= g->e ; i++)
{
int x , y, w;
scanf("%d %d %d",&x,&y,&w);
if(x < y)
{
g->data[i].a = x;
g->data[i].b = y;
}
else
{
g->data[i].a = y;
g->data[i].b = x;
}
g->data[i].w = w;
}
}
int getRoot(int v[], int x)
{
while(v[x] != x)
{
x = v[x];
}
return x;
}
void sort(Road data, int n)
{
int i , j;
for(i = 1 ; i <= n-1 ; i++)
{
for(j = 1 ; j <= n-i ; j++)
{
if(data[j].w > data[j+1].w)
{
road t = data[j];
data[j] = data[j+1];
data[j+1] = t;
}
}
}
}
int Kruskal(Graph g)
{
int sum = 0;
//並查集
int v[Max];
int i;
//init
for(i = 1 ; i <= g->n ; i++)
{
v[i] = i;
}
sort(g->data , g->e);
//main
for(i = 1 ; i <= g->e ; i++)
{
int a , b;
a = getRoot(v,g->data[i].a);
b = getRoot(v,g->data[i].b);
if(a != b)
{
v[a] = b;
sum += g->data[i].w;
}
}
return sum;
}
int main()
{
int m , n , id = 1;
while(scanf("%d %d",&m,&n) != EOF)
{
int r , i;
Graph g = initGraph(m,n);
create(g);
r = Kruskal(g);
printf("Case %d:%d\n",id++,r);
free(g);
}
return 0;
}
輸入數據:
第一行兩個整數表示圖的頂點和邊的個數
然後接下來的若干行,第一個數表示起點,第二個數表示終點,第三個數表示權值
6 10
1 2 16
1 6 21
1 5 19
2 3 5
2 4 6
2 6 11
3 4 6
6 4 14
5 4 18
5 6 33
2 1
1 2 9
運行結果: