參考書籍:數據結構(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);
}