1. 程式人生 > >Prim演算法生成最小生成樹詳解

Prim演算法生成最小生成樹詳解

建立一個low陣列代表相應每個節點連線所需最小費用,一個vis陣列標記相應節點是否連線過。

low陣列的初始值用節點1到其餘各點的費用來填充


如該圖所示low陣列的初始值應該為:

06345

以上便是1號節點對於其他各點的費用

接下來在low陣列中尋找最小值  為3號節點費用為3最小。

接著以3號節點為起始點更新

06345

接下來在low陣列中尋找最小值  為4號節點費用為4最小。

同理以4號節點為起始點更新

06342

這裡可以看到第三次更新時便改變了連線5號節點所需的最小費用,值得注意的是low陣列的值每次都由最小費用的節點優化而來,因此重新選中一個節點時,在它進行優化之前,low陣列的值必定是最小值(因為之前每一個節點前輩都已經優化過了^_^)

總結一下便是:

第一步:將所有一號節點可以達到的點所需費用加入low陣列作為相應每一個點連線所需的假想最小費用

第二步:不斷搜尋low陣列中沒被確定(vis陣列標記)的最小值,然後通過這個節點去更新(優化)low陣列

Prim演算法在選取起始點使用貪心,而對low陣列優化的全過程則有DP的影子。

我們可以看出Prim演算法需要列舉節點,所以相對於Kruskal演算法來說稠密圖更適合Prim演算法,而Kruskal演算法解決稀疏圖比Prim演算法更有效率。

克魯斯卡爾演算法->http://blog.csdn.net/x__1998/article/details/79503583

#include <stdio.h>
#include <string.h>
#define inf 0x3f3f3f3f
int map[1001][1001],low[1001],vis[1001];
int Prime(int cost[][1001],int n)
{
	int i,j,ans=0;
	printf ("low陣列的變化過程如下:\n");
	printf ("0 ");
	for (i = 2;i <= n; i++){//先將節點1作為起始點將所有的費用暫時放入low陣列 
		low[i] = cost[1][i];
		printf ("%d ",low[i]);
	}
	printf ("\n");
	vis[1]=1;//標記1節點表示1節點所需的費用固定
	for (i = 2;i <= n; i++){//還有n-1個點沒有確定最小連線費用
		int min = inf,p=-1;
		for (j = 1;j <= n; j++){//找出費用最小的點的話,那該節點連線所需費用一定最小 
			if (!vis[j]&&min > low[j]){
				min = low[j];
				p = j;
			}
			printf ("%d ",low[j]);
		}
		printf ("\n");
		if(p == -1)
			return NULL; 
		ans += low[p];
		vis[p] = 1;//確定這個點所需的費用已經最小將其加入佇列 
		for (j = 1;j <= n; j++){//維護low陣列,以當前找到的新節點作為起點,更新各節點的最小費用
		//否則上一行找到的最小值節點無法確定就是最小的,其包含了動態規劃的思想
			if (!vis[j] && cost[p][j] < low[j]){//這就是優化的核心語句了 
				low[j] = cost[p][j];
			}
		}
	}
	printf ("最小權值:%d\n",ans);
}
int main ()
{
	int m,n;
	printf ("請輸入節點數以及邊數:\n");
	while (~scanf ("%d%d",&m,&n)){
		memset(map,inf,sizeof map);
		memset(vis,0,sizeof vis);
		memset(low,0,sizeof low);
		int i,j,a,b,w;
		printf ("請輸入邊的權值:\n"); 
		for (i = 1;i <= n; i++){
			scanf ("%d%d%d",&a,&b,&w);
			if (w < map[a][b]){//這裡為了防止輸入的資料重複輸入同兩個點之間的權值
				map[a][b] = w;
				map[b][a] = w;
			}
				
		}
		Prime(map,m);
		printf ("----------------------\n請輸入節點數以及邊數:\n");
	}
	return 0;
}