【算法】圖的最小生成樹(Kruskal算法)

這篇文章是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

運行結果:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章