1. 程式人生 > >圖->連通性->最小生成樹(普里姆演算法)

圖->連通性->最小生成樹(普里姆演算法)

文字描述

  用連通網來表示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