圖->連通性->最小生成樹(普裏姆算法)
文字描述
用連通網來表示n個城市及n個城市間可能設置的通信線路,其中網的頂點表示城市,邊表示兩城市之間的線路,賦於邊的權值表示相應的代價。對於n個定點的連通網可以建立許多不同的生成樹,每一棵生成樹都可以是一個通信網。現在,我們要選擇這樣一個生成樹,使總的耗費最少。這個問題就是構造連通網的最小代價生成樹(Minimum Cost Spanning Tree: 最小生成樹)的問題。一棵生成樹的代價就是樹上各邊的代價之和。
有多種算法可以構造最小生成樹,其他多數都利用的最小生成的MST(minimum spanning tree)性質: 假設N={V, {E}}是一個連通網,U是頂點集V的一個非空子集。若(u,v)是一條具有最小權值(代價)的邊,其中u屬於U, v屬於V-U,則必存在一棵包含邊(u,v)的最小生成樹。 該性質可以用反證法證明。
現介紹普裏姆(Prim)算法是如何利用MST性質求連通圖的最小生成樹的:
假設N={V,{E}}是連通網,TE是N上最小生成樹中邊的集合。算法從U={u0} (u0屬於V, TE={})開始,重復執行下述操作:在所有u屬於U, v屬於V-U 的邊(u,v)屬於E 中找一條代價最小的邊(u0,v0)並入集合TE,同時v0並入U,直至U=V為止。
為實現這個算法需附設一個輔助數組closeedge, 以記錄從U到V-U具有最小代價的邊。對每個屬於V-U的頂點vi ,在輔助數組中存在一個相應分量closedge[i-1],它包括兩個域:
1:lowcose:存儲該邊上的權; 顯然closedge[i-1].lowcost = Min{cost(u,vi) | u屬於U}
2:vex: 存儲該邊依附的U中的頂點。
示意圖
算法分析
在代碼實現中的MinSpanTree_PRIM函數中。若網中有n個頂點, 則第一個初始化的循環語句的頻度為n,第二個循環語句的頻度為n-1;其中第二個循環中有兩個內循環:其一是在 closedge[v].lowcost中求最小值,其頻度為n-1;其二是重新選擇具有最小代價的邊,其頻度為n。由此,普裏姆算法的時間復雜度為n^2, 與網中的邊數無關,因此使用適用於求邊稠密的網的最小生成樹。
代碼實現
1 #include <stdio.h> 2 #include <stdlib.h> 3最小生成樹(普裏姆算法)#include <string.h> 4 5 #define DEBUG 6 7 #ifdef DEBUG 8 #include <stdarg.h> 9 #define LOG(args...) _log_(__FILE__, __FUNCTION__, __LINE__, ##args); 10 void _log_(const char *file, const char *function, int line, const char * format, ...) 11 { 12 char buf[1024] = {0}; 13 va_list list; 14 va_start(list, format); 15 sprintf(buf, "[%s,%s,%d]", file, function, line); 16 vsprintf(buf+strlen(buf), format, list); 17 sprintf(buf+strlen(buf), "\n"); 18 va_end(list); 19 printf(buf); 20 } 21 #else 22 #define LOG 23 #endif // DEBUG 24 25 #define INFINITY 100000 //最大值 26 #define MAX_VERTEX_NUM 20 //最大頂點數 27 28 //---------鄰接矩陣的存儲結構----------------------------------------- 29 30 ////////////////////////////////////////////////////////////// 31 // 鄰接矩陣作為圖的存儲結構 32 ////////////////////////////////////////////////////////////// 33 typedef enum {DG, DN, UDG, UDN} GraphKind; //{有向圖,有向網,無向圖,無向網} 34 typedef int VRType; 35 typedef char VertexType; //頂點類型 36 typedef struct{ 37 char note[10]; 38 }InfoType; 39 typedef struct ArcCell{ 40 VRType adj; //頂點關系類型:1)對無權圖,用1或0表示相鄰否;2)對帶權圖,則為權值類型 41 InfoType *info; //該弧相關信息的指針 42 }ArcCell, AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; 43 typedef struct{ 44 VertexType vexs[MAX_VERTEX_NUM]; //頂點向量 45 AdjMatrix arcs; //鄰接矩陣 46 int vexnum, arcnum; //圖的當前頂點數和弧數 47 GraphKind kind; //圖的種類標誌 48 }MGraph; 49 50 51 //---------采用鄰接矩陣創建無向網----------------------------------------- 52 53 ////////////////////////////////////////////////////////////// 54 // 若G中存在頂點u,則返回該頂點在圖中位置;否則返回-1。 55 ////////////////////////////////////////////////////////////// 56 int LocateVex(MGraph G, VertexType v){ 57 int i = 0; 58 for(i=0; i<G.vexnum; i++){ 59 if(G.vexs[i] == v){ 60 return i; 61 } 62 } 63 return -1; 64 } 65 66 67 ////////////////////////////////////////////////////////////// 68 // 采用數組表示法(鄰接矩陣),構造無向網 69 ////////////////////////////////////////////////////////////// 70 int CreateUDN(MGraph *G) 71 { 72 printf("\n創建一個無向網(帶權):\n"); 73 int i = 0, j = 0, k = 0, IncInfo = 0; 74 int v1 = 0, v2 = 0, w = 0; 75 char tmp[10] = {0}; 76 printf("輸入頂點數,弧數,其他信息標誌位: "); 77 scanf("%d,%d,%d", &G->vexnum, &G->arcnum, &IncInfo); 78 for(i=0; i<G->vexnum; i++) 79 { 80 printf("輸入第%d個頂點: ", i+1); 81 memset(tmp, 0, sizeof(tmp)); 82 scanf("%s", tmp); 83 G->vexs[i] = tmp[0]; 84 } 85 for(i=0; i<G->vexnum; i++) 86 { 87 for(j=0; j<G->vexnum; j++) 88 { 89 G->arcs[i][j].adj = INFINITY; 90 G->arcs[i][j].info = NULL; 91 } 92 } 93 for(k=0; k<G->arcnum; k++) 94 { 95 printf("輸入第%d條弧: 弧尾, 弧頭,權值: ", k+1); 96 memset(tmp, 0, sizeof(tmp)); 97 scanf("%s", tmp); 98 sscanf(tmp, "%c,%c,%d", &v1, &v2, &w); 99 i = LocateVex(*G, v1); 100 j = LocateVex(*G, v2); 101 G->arcs[i][j].adj = w; 102 if(IncInfo){ 103 // 104 } 105 G->arcs[j][i] = G->arcs[i][j]; 106 } 107 return 0; 108 } 109 110 int CreateGraph(MGraph *G) 111 { 112 printf("輸入圖類型: -有向圖(0), -有向網(1), -無向圖(2), +無向網(3): "); 113 scanf("%d", &G->kind); 114 switch(G->kind) 115 { 116 case DG: 117 case DN: 118 case UDG: 119 printf("還不支持!\n"); 120 return -1; 121 case UDN: 122 return CreateUDN(G); 123 default: 124 return -1; 125 } 126 } 127 128 129 ////////////////////////////////////////////////////////////// 130 // 打印鄰接矩陣中的信息 131 ////////////////////////////////////////////////////////////// 132 void printG(MGraph G) 133 { 134 printf("\n打印鄰接矩陣:\n"); 135 if(G.kind == DG){ 136 printf("類型:有向圖;頂點數 %d, 弧數 %d\n", G.vexnum, G.arcnum); 137 }else if(G.kind == DN){ 138 printf("類型:有向網;頂點數 %d, 弧數 %d\n", G.vexnum, G.arcnum); 139 }else if(G.kind == UDG){ 140 printf("類型:無向圖;頂點數 %d, 弧數 %d\n", G.vexnum, G.arcnum); 141 }else if(G.kind == UDN){ 142 printf("類型:無向網;頂點數 %d, 弧數 %d\n", G.vexnum, G.arcnum); 143 } 144 int i = 0, j = 0; 145 printf("\t"); 146 for(i=0; i<G.vexnum; i++) 147 printf("%c\t", G.vexs[i]); 148 printf("\n"); 149 for(i=0; i<G.vexnum; i++){ 150 printf("%c\t", G.vexs[i]); 151 for(j=0; j<G.vexnum; j++){ 152 if(G.arcs[i][j].adj == INFINITY){ 153 printf("INF\t"); 154 }else{ 155 printf("%d\t", G.arcs[i][j].adj); 156 } 157 } 158 printf("\n"); 159 } 160 } 161 162 163 //---------求最小生成樹的算法。(普裏姆算法)----------------------------------------- 164 165 ////////////////////////////////////////////////////////////// 166 // 定義輔助數組CloseEdge: 記錄從頂點集U到V-U的代價最小的邊的輔助數組。 167 ////////////////////////////////////////////////////////////// 168 struct Edge{ 169 VertexType adjvex; 170 VRType lowcost; 171 }CloseEdge[MAX_VERTEX_NUM]; 172 173 ////////////////////////////////////////////////////////////// 174 // 返回輔助數組中, 權值最小的頂點的位置。 175 ////////////////////////////////////////////////////////////// 176 int minimum(struct Edge edgelist[], int count) 177 { 178 int ret = -1; 179 int min = INFINITY; 180 int i = 0; 181 for(i=0; i<count; i++) { 182 if ((edgelist[i].lowcost) && (edgelist[i].lowcost < min)) { 183 ret = i; 184 min = edgelist[i].lowcost; 185 } 186 } 187 return ret; 188 } 189 190 ////////////////////////////////////////////////////////////// 191 // 采用普裏姆算法求最小生成樹的算法。 192 ////////////////////////////////////////////////////////////// 193 void MinSpanTree_PRIM(MGraph G, VertexType v) 194 { 195 printf("\n采用普裏姆算法求鄰接矩陣存儲的帶權的無向網的最小生成樹,所求最小生成樹的邊依次為:\n"); 196 int k = -1; 197 int i = 0; 198 int j = 0; 199 k = LocateVex(G, v); 200 //輔助數組初始化 201 for(j=0; j<G.vexnum; j++){ 202 if(j!=k) 203 { 204 CloseEdge[j].adjvex = v; 205 CloseEdge[j].lowcost = G.arcs[k][j].adj; 206 } 207 } 208 //初始狀態下, U={v} 209 CloseEdge[k].lowcost = 0; 210 //選擇其余的G.vexnum-1個頂點 211 for(i=1; i<G.vexnum; i++) 212 { 213 //求出T的下一個結點,第k個頂點 214 k = minimum(CloseEdge, G.vexnum); 215 //輸出生成樹的邊 216 printf("%c, %c\n", CloseEdge[k].adjvex, G.vexs[k]); 217 //第k個頂點並入U集合 218 CloseEdge[k].lowcost = 0; 219 //新頂點並入U後重新選擇最小邊。 220 for(j=0; j<G.vexnum; j++){ 221 if(G.arcs[k][j].adj < CloseEdge[j].lowcost){ 222 CloseEdge[j].adjvex = G.vexs[k]; 223 CloseEdge[j].lowcost = G.arcs[k][j].adj; 224 } 225 } 226 } 227 } 228 229 int main(int argc, char *argv[]) 230 { 231 MGraph G; 232 if(CreateGraph(&G) > -1) 233 printG(G); 234 MinSpanTree_PRIM(G, G.vexs[0]); 235 return 0; 236 }
代碼運行
/home/lady/CLionProjects/untitled/cmake-build-debug/untitled 輸入圖類型: -有向圖(0), -有向網(1), -無向圖(2), +無向網(3): 3 創建一個無向網(帶權): 輸入頂點數,弧數,其他信息標誌位: 6,10,0 輸入第1個頂點: a 輸入第2個頂點: b 輸入第3個頂點: c 輸入第4個頂點: d 輸入第5個頂點: e 輸入第6個頂點: f 輸入第1條弧: 弧尾, 弧頭,權值: c,a,1 輸入第2條弧: 弧尾, 弧頭,權值: c,b,5 輸入第3條弧: 弧尾, 弧頭,權值: c,d,5 輸入第4條弧: 弧尾, 弧頭,權值: c,e,6 輸入第5條弧: 弧尾, 弧頭,權值: c,f,4 輸入第6條弧: 弧尾, 弧頭,權值: a,b,6 輸入第7條弧: 弧尾, 弧頭,權值: b,e,3 輸入第8條弧: 弧尾, 弧頭,權值: e,f,6 輸入第9條弧: 弧尾, 弧頭,權值: f,d,2 輸入第10條弧: 弧尾, 弧頭,權值: d,a,5 打印鄰接矩陣: 類型:無向網;頂點數 6, 弧數 10 a b c d e f a INF 6 1 5 INF INF b 6 INF 5 INF 3 INF c 1 5 INF 5 6 4 d 5 INF 5 INF INF 2 e INF 3 6 INF INF 6 f INF INF 4 2 6 INF 采用普裏姆算法求鄰接矩陣存儲的帶權的無向網的最小生成樹,所求最小生成樹的邊依次為: a, c c, f f, d c, b b, e Process finished with exit code 0
圖->連通性->最小生成樹(普裏姆算法)