數據結構(18)--Prim算法求解無向網的最小生成樹

參考書籍:數據結構(C語言版)嚴蔚敏吳偉民編著清華大學出版社

本文中的代碼可從這裏下載:https://github.com/qingyujean/data-structure

1.最小生成樹

    對於帶權的連通圖(連通網)G,其生成樹也是帶權的,將權值之和最小的生成樹稱爲最小生成樹

    最小生成樹的MST性質
    假設 N =(V,{E})是一個連通網,U是頂點集 V 的一個非空子集。若(u,v)是一條具有最小權值(代價)的邊,其中 u ∈U,v∈V-U,則必存在一棵包含邊(u,v)的最小生成樹。

2.普里姆(Prim)算法

    基本思想:
    (1)假設 G=(V,{E}) 是一個具有 n 個頂點的連通網絡,T=(U,{TE})是 G 的最小生成樹,其中 U 是 T 的頂點集,TE 是 T 的邊集,U 和 TE 的初值均爲空;
    (2)從 V 中任取一個頂點(假定爲 V1),將此頂點併入 U中,此時最小生成樹頂點集 U={V1};
    (3)從那些其中一個端點已在 U 中,另一端點仍在 U 外的所有邊中,找一條最短(即權值最小)的邊,設該邊爲(Vi,Vj),其中 Vi∈U,Vj∈V-U,並把該邊和頂點 Vj分別併入 T 的邊集 TE 和頂點集 U;
    (4)如此進行下去,每次往生成樹裏併入一個頂點和一條邊,直到 n-1 次後,把所有 n 個頂點都併入生成樹 T 的頂點集 U 中,此時 U=V,TE中包含有(n-1)條邊;這樣,T 就是最後得到的最小生成樹。

    算法時間複雜度O(n^2),與邊無關,適合求解邊稠密的網的最小生成樹。

    實現該算法需附設一個輔助數組closedge,以記錄從 U 到 V-U 具有最小代價的邊。對每個頂點 vi∈V-U,在輔助數組中存在一個相應分量closedge[i-1](下標從0開始),它包括兩個域。其中:lowcost存儲該邊上的權。顯然,
   closedge[i-1].lowcost = Min{cost(u,vi)|u∈U}    即vi到已生成子樹的最短距離等於到U中所有頂點中的最小邊的權值。
   adjvex域存儲該邊依附的在U中的頂點。

    示例:求下圖最小生成樹。假設開始頂點就選爲頂點1,故有U={1},V-U={2,3,4,5,6}

    

3.代碼實現

3.1定義

 

#include<stdio.h>
//#include<stdlib.h>
/*
圖的表示方法
DG(有向圖)或者DN(有向網):鄰接矩陣、鄰接表(逆鄰接表--爲求入度)、十字鏈表
UDG(無向圖)或者UDN(無向網):鄰接矩陣、鄰接表、鄰接多重表
*/

//1.數組表示法(鄰接矩陣):將以有向網爲例
#define INFINITY 32767//最大值:假定爲無窮大
#define MAX_VERTEX_NUM 10//最大頂點數目
//typedef enum GraphKind {DG, DN, UDG, UDN};  //有向圖:0,有向網:1,無向圖:2,無向網:3

typedef int VRType;//頂點關係類型,對於無權圖或網,用0或1表示相鄰否;對於帶權圖或網,則爲相應權值
typedef int VertexType;//頂點類型
typedef VRType AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
typedef struct{
	VertexType vexs[MAX_VERTEX_NUM];//頂點向量
	AdjMatrix arcs;//鄰接矩陣
	int vexnum, arcnum;//圖的當前頂點數和弧數
	//GraphKind kind;//圖的種類標誌
}MGraph;//鄰接矩陣表示的圖


typedef struct{
	VertexType adjvex;
	VRType lowcost;
}closedge[MAX_VERTEX_NUM];//記錄從頂點集U到V-U的代價最小的邊的輔助數組
closedge close;

 

3.2鄰接矩陣構造無向網G

 

 

//若圖G中存在頂點v,則返回v在圖中的位置信息,否則返回其他信息
int locateVex(MGraph G, VertexType v){
	for(int i = 0; i < G.vexnum; i++){
		if(G.vexs[i] == v)
			return i;
	}
	return -1;//圖中沒有該頂點
}

//採用鄰接矩陣表示法構造無向網G
void createUDN(MGraph &G){
	printf("輸入頂點數和弧數如:(5,3):");
	scanf("%d,%d", &G.vexnum, &G.arcnum);

	//構造頂點向量
	printf("輸入%d個頂點(以空格隔開如:v1 v2 v3):", G.vexnum);
	getchar();//吃掉換行符
	for(int m = 0; m < G.vexnum; m++){
		scanf("v%d", &G.vexs[m]);
		getchar();//吃掉空格符
	}

	
	//初始化鄰接矩陣
	int i=0, j=0;
	for(i = 0; i < G.vexnum; i++){
		for(j = 0; j < G.vexnum; j++)
			G.arcs[i][j] = INFINITY;
	}

	//構造鄰接矩陣
	VertexType v1, v2;//分別是一條弧的弧尾和弧頭(起點和終點)
	VRType w;//對於無權圖或網,用0或1表示相鄰否;對於帶權圖或網,則爲相應權值	
	printf("\n每行輸入一條弧依附的頂點(先弧尾後弧頭)和權值(如:v1 v2 3):\n");
	fflush(stdin);//清除殘餘後,後面再讀入時不會出錯
	for(int k = 0; k < G.arcnum; k++){
		scanf("v%d v%d %d",&v1, &v2, &w);
		fflush(stdin);//清除殘餘後,後面再讀入時不會出錯
		i = locateVex(G, v1);
		j = locateVex(G, v2);
		G.arcs[i][j] = w;

		//又因爲是無向網,所以鄰接矩陣相對於對角線對稱,所以還得對另外半邊三角陣賦值!!!!!!!!!!重要
		G.arcs[j][i] = w;//重要!!!
	}
}

 

3.3Prim算法實現

 

 

int minimun(MGraph G, closedge close){
	int min = INFINITY;
	int min_i = -1;
	for(int i = 0; i < G.vexnum; i++){
		if(close[i].lowcost>0 && close[i].lowcost < min){
			min = close[i].lowcost;
			min_i = i;
		}
	}
	return min_i;//返回具有最小代價的邊(u->vi)的vi的下標,即頂點vi的在圖中的位置
}


//用prim算法從第u個頂點出發構造網G的最小生成樹T,輸出T的各個邊,O(n^2)
void miniSpanTreePRIM(MGraph G, VertexType u){
	int k = locateVex(G, u);//找到頂點u在圖中的位置
	//初始化輔助數組
	for(int i = 0; i < G.vexnum; i++){
		if(i != k){
			close[i].adjvex = k;
			close[i].lowcost = G.arcs[k][i];
		}
	}
	close[k].lowcost = 0;//初始時,U={u}

	for(i = 1; i < G.vexnum; i++){//選擇其餘的G.vexnum-1個頂點,每次選出一個,共需要選G.vexnum-1次
		k = minimun(G, close);//求出T的下一個結點:第k頂點
		printf("v%dv%d\n",G.vexs[close[k].adjvex], G.vexs[k]);//輸出生成樹的邊(邊起始點,邊終點)
		close[k].lowcost = 0;//將第k頂點併入U集
		for(int j = 0; j < G.vexnum; j++){//由於U集有新頂點vk的併入,導致V-U裏的各個頂點的lowcost的變化需要更新
			if(G.arcs[k][j] < close[j].lowcost){
				close[j].adjvex = k;
				close[j].lowcost = G.arcs[k][j];//重新選擇最小代價邊
			}
		}
	}
	printf("\n");
}

 

3.4.演示

 

 

/*測試:
6,10
v1 v2 v3 v4 v5 v6

v1,v2,6
v1,v3,1
v1,v4,5

v2,v3,5
v2,v5,3

v3,v4,5
v3,v5,6
v3,v6,4

v4,v6,2

v5,v6,6
*/
void main(){
	MGraph G;
	createUDN(G);	
	//printUDN(G);


	VertexType u;
	printf("請輸入構造最小生成樹的出發點:");
	scanf("v%d", &u);
	fflush(stdin);//清除殘餘後,後面再讀入時不會出錯
	miniSpanTreePRIM(G, u);

}

 

 

 

 

 

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