最小產生樹
在含有n個頂點的連通圖中選擇n-1條邊,構成一棵極小連通子圖,並使該連通子圖中n-1條邊上權值之和達到最小,則稱其為連通網的最小產生樹。
例如,對於如上圖G4所示的連通網可以有多棵權值總和不相同的產生樹。
克魯斯卡爾演算法介紹
克魯斯卡爾(Kruskal)演算法,是用來求加權連通圖的最小產生樹的演算法。
基本思想:按照權值從小到大的順序選擇n-1條邊,並保證這n-1條邊不構成迴路。
具體做法:首先構造一個只含n個頂點的森林,然後依權值從小到大從連通網中選擇邊加入到森林中,並使森林中不產生迴路,直至森林變成一棵樹為止。
克魯斯卡爾演算法圖解
以上圖G4為例,來對克魯斯卡爾進行示範(假設,用數組R儲存最小產生樹結果)。
第1步:將邊(E,F)加入R中。
邊(E,F)的權值最小,因此將它加入到最小產生樹結果R中。
第2步:將邊(C,D)加入R中。
上一步操作之後,邊(C,D)的權值最小,因此將它加入到最小產生樹結果R中。
第3步:將邊(D,E)加入R中。
上一步操作之後,邊(D,E)的權值最小,因此將它加入到最小產生樹結果R中。
第四步:將邊(B,F)加入R中。
上一步操作之後,邊(C,E)的權值最小,但(C,E)會和已有的邊構成迴路;因此,跳過邊(C,E)。同理,跳過邊(C,F)。將邊(B,F)加入到最小產生樹結果R中。
第5步:將邊(E,G)加入R中。
上一步操作之後,邊(E,G)的權值最小,因此將它加入到最小產生樹結果R中。
第6步:將邊(A,B)加入R中。
上一步操作之後,邊(F,G)的權值最小,但(F,G)會和已有的邊構成迴路;因此,跳過邊(F,G)。同理,跳過邊(B,C)。將邊(A,B)加入到最小產生樹結果R中。
此時,最小產生樹構造完成。它包括的邊依次是:(E,F) (C,D) (D,E) (B,F) (E,G) (A,B)。
克魯斯卡爾演算法分析
根據前面介紹的克魯斯卡爾演算法的基本思想和做法,我們能夠瞭解到,克魯斯卡爾演算法重點需要解決的以下兩個問題:
問題一 對圖的所有邊按照權值大小進行排序。
問題二 將邊添加到最小產生樹中時,怎麼樣判斷是否形成了迴路。
問題一很好解決,採用排序演算法進行排序即可。
問題二,處理方式是:記錄頂點在”最小產生樹”中的終點,頂點的終點是”在最小產生樹中與它連通的最大頂點”(關於這一點,後面會通過圖片給出說明)。然後每次需要將一條邊添加到最小生存樹時,判斷該邊的兩個頂點的終點是否重合,重合的話則會構成迴路。 以下圖來進行說明:
在將(E,F) (C,D) (D,E)加入到最小產生樹R中之後,這幾條邊的頂點就都有了終點:
**(01) C的終點是F。
(02) D的終點是F。
(03) E的終點是F。
(04) F的終點是F。**
關於終點,就是將所有頂點按照從小到大的順序排列好之後;某個頂點的終點就是”與它連通的最大頂點”。 因此,接下來,雖然(C,E)是權值最小的邊。但是C和E的重點都是F,即它們的終點相同,因此,將(C,E)加入最小產生樹的話,會形成迴路。這就是判斷迴路的方式。
克魯斯卡爾演算法的代碼說明
有了前面的演算法分析之後,下面我們來查看具體代碼。這裡選取”鄰接矩陣”進行說明,對於”鄰接表”實現的圖在後面的源碼中會給出相應的源碼。
1. 基本定義
// 鄰接矩陣typedef struct _graph{ char vexs[MAX]; // 頂點集合 int vexnum; // 頂點數 int edgnum; // 邊數 int matrix[MAX][MAX]; // 鄰接矩陣}Graph, *PGraph;// 邊的結構體typedef struct _EdgeData{ char start; // 邊的起點 char end; // 邊的終點 int weight; // 邊的權重}EData;
Graph是鄰接矩陣對應的結構體。
vexs用於儲存頂點,vexnum是頂點數,edgnum是邊數;matrix則是用於儲存矩陣資訊的二維數組。例如,matrix[i][j]=1,則表示”頂點i(即vexs[i])”和”頂點j(即vexs[j])”是鄰接點;matrix[i][j]=0,則表示它們不是鄰接點。
EData是鄰接矩陣邊對應的結構體。
轉自 http://www.cnblogs.com/skywang12345/
2. 克魯斯卡爾演算法
模板1
/* * 克魯斯卡爾(Kruskal)最小產生樹 */void kruskal(Graph G){ int i,m,n,p1,p2; int length; int index = 0; // rets數組的索引 int vends[MAX]={0}; // 用於儲存"已有最小產生樹"中每個頂點在該最小樹中的終點。 EData rets[MAX]; // 結果數組,儲存kruskal最小產生樹的邊 EData *edges; // 圖對應的所有邊 // 擷取"圖中所有的邊" edges = get_edges(G); // 將邊按照"權"的大小進行排序(從小到大) sorted_edges(edges, G.edgnum); for (i=0; i<G.edgnum; i++) { p1 = get_position(G, edges[i].start); // 擷取第i條邊的"起點"的序號 p2 = get_position(G, edges[i].end); // 擷取第i條邊的"終點"的序號 m = get_end(vends, p1); // 擷取p1在"已有的最小產生樹"中的終點 n = get_end(vends, p2); // 擷取p2在"已有的最小產生樹"中的終點 // 如果m!=n,意味著"邊i"與"已經添加到最小產生樹中的頂點"沒有形成環路 if (m != n) { vends[m] = n; // 設定m在"已有的最小產生樹"中的終點為n rets[index++] = edges[i]; // 儲存結果 } } free(edges); // 統計並列印"kruskal最小產生樹"的資訊 length = 0; for (i = 0; i < index; i++) length += rets[i].weight; printf("Kruskal=%d: ", length); for (i = 0; i < index; i++) printf("(%c,%c) ", rets[i].start, rets[i].end); printf("\n");}
模板2
#include<stdio.h>#include<string.h>#define inf 0x3f3f3f3fint map[110][110],dis[110],visit[110];/*關於三個數組:map數組存的為點邊的資訊,比如map[1][2]=3,表示1號點和2號點的距離為3dis數組存的為起始點與每個點的最短距離,比如dis[3]=5,表示起始點與3號點最短距離為5visit數組存的為0或者1,1表示已經走過這個點。*/int n,m;int dijstra(){ int i,j,pos=1,min,sum=0; memset(visit,0,sizeof(visit));//初始化為.,表示開始都沒走過 for(i=1; i<=n; ++i) { dis[i]=map[1][i]; } visit[1]=1; dis[1]=0; for(i=1; i<n; i++) { min=inf; for(j=1; j<=n; ++j) { if(visit[j]==0&&min>dis[j]) { min=dis[j]; pos=j; } } visit[pos]=1;//表示這個點已經走過 for(j=1; j<=n; ++j) { if(visit[j]==0&&dis[j]>dis[pos]+map[pos][j])//更新dis的值 dis[j]=dis[pos]+map[pos][j]; } } return dis[n];}int main(){ int i,j; while(~scanf("%d%d",&n,&m),n||m)//n表示n個點,m表示m條邊 { for(i=1; i<=n; ++i) { for(j=1; j<=n; ++j) { map[i][j]=inf;//開始時將每條邊賦為最大值 } } int a,b,c; for(i=1; i<=m; ++i) { scanf("%d%d%d",&a,&b,&c); if(c<map[a][b])//防止有重邊 map[a][b]=map[b][a]=c; } int count=dijstra(); printf("%d\n",count); } return 0;}