環境: Codeblocks 13.12 + GCC 4.7.1
基本思想:(1)構造一個只含n個頂點,邊集為空的子圖。若將圖中各個頂點看成一棵樹的根節點,則它是一個含有n棵樹的森林。(2)從網的邊集 E 中選取一條權值最小的邊,若該條邊的兩個頂點分屬不同的樹,則將其加入子圖。也就是說,將這兩個頂點分別所在的兩棵樹合成一棵樹;反之,若該條邊的兩個頂點已落在同一棵樹上,則不可取,而應該取下一條權值最小的邊再試之(3)依次類推,直至森林中只有一棵樹,也即子圖中含有 n-1條邊為止。
大白話:(1)將圖中的所有邊都去掉。(2)將邊按權值從小到大的順序添加到圖中,保證添加的過程中不會形成環(3)重復上一步直到連接所有頂點,此時就生成了最小生成樹。這是一種貪心策略。
難點:判斷某條邊<u, v>的加入是否會在已經選定的邊集集合中形成環。
解決辦法:使用並查集,分別找出兩個頂點u, v所在樹的根節點。若根節點相同,說明u, v在同一棵樹中,則u, v連接起來會形成環;若根節點不同,則u, v不在一棵樹中,連接起來不會形成環,而是將兩棵樹合並。
圖解過程:原圖如下 邊集數組按權值順序排列


邊<1, 2>和<4, 5>在添加到圖中的時候形成了環,所以不能將v1和v2,v4和v5連起來。
判斷是否成環
int Find(int *parent, int f) { while ( parent[f] > 0) { f = parent[f]; } return f; }
for (i = 0; i < G.numEdges; i++) { n = Find(parent, edges[i].begin);//尋找邊edge[i]的“首節點”所在樹的樹根 m = Find(parent, edges[i].end);//尋找邊edge[i]的“尾節點”所在樹的樹根 //假如n與m不等,說明兩個頂點不在一棵樹內,因此這條邊的加入不會使已經選擇的邊集產生回路 if (n != m) { parent[n] = m; printf("(%d, %d) %d\n", edges[i].begin, edges[i].end, edges[i].weight); } }
代碼:
//克魯斯卡爾算法 //在連通網中求出最小生成樹 #include <stdio.h> #include <stdlib.h> #define MAXEDGE 20 #define MAXVEX 20 #define INFINITY 65535 typedef struct { int arc[MAXVEX][MAXVEX]; int numVertexes, numEdges;//頂點數,邊數 }MGraph; typedef struct { int begin; int end; int weight; }Edge; //對邊集數組Edge結構的定義 //創建圖的鄰接矩陣 void CreateMGraph(MGraph *G) { int i, j; G->numEdges=11; G->numVertexes=7; for (i = 0; i < G->numVertexes; i++) { for ( j = 0; j < G->numVertexes; j++) { if (i==j) G->arc[i][j]=0; else G->arc[i][j] = G->arc[j][i] = INFINITY; } } G->arc[0][1]=7; G->arc[0][3]=5; G->arc[1][2]=8; G->arc[1][3]=9; G->arc[1][4]=7; G->arc[2][4]=5; G->arc[3][4]=15; G->arc[3][5]=6; G->arc[4][5]=8; G->arc[4][6]=9; G->arc[5][6]=11; for(i = 0; i < G->numVertexes; i++) { for(j = i; j < G->numVertexes; j++) { G->arc[j][i] =G->arc[i][j]; } } } //快速排序的條件 int cmp(const void* a, const void* b) { return (*(Edge*)a).weight - (*(Edge*)b).weight; } //找到根節點 int Find(int *parent, int f) { while ( parent[f] > 0) { f = parent[f]; } return f; } // 生成最小生成樹 void MiniSpanTree_Kruskal(MGraph G) { int i, j, n, m; int k = 0; int parent[MAXVEX]; //用於尋找根節點的數組 Edge edges[MAXEDGE]; //定義邊集數組,edge的結構為begin,end,weight,均為整型 // 用來構建邊集數組並排序(將鄰接矩陣的對角線右邊的部分存入邊集數組中) for ( i = 0; i < G.numVertexes-1; i++) { for (j = i + 1; j < G.numVertexes; j++) { if (G.arc[i][j] < INFINITY) { edges[k].begin = i; //編號較小的結點為首 edges[k].end = j; //編號較大的結點為尾 edges[k].weight = G.arc[i][j]; k++; } } } //為邊集數組Edge排序 qsort(edges, G.numEdges, sizeof(Edge), cmp); for (i = 0; i < G.numVertexes; i++) parent[i] = 0; printf("打印最小生成樹:\n"); for (i = 0; i < G.numEdges; i++) { n = Find(parent, edges[i].begin);//尋找邊edge[i]的“首節點”所在樹的樹根 m = Find(parent, edges[i].end);//尋找邊edge[i]的“尾節點”所在樹的樹根 //假如n與m不等,說明兩個頂點不在一棵樹內,因此這條邊的加入不會使已經選擇的邊集產生回路 if (n != m) { parent[n] = m; printf("(%d, %d) %d\n", edges[i].begin, edges[i].end, edges[i].weight); } } } int main(void) { MGraph G; CreateMGraph(&G); MiniSpanTree_Kruskal(G); return 0; }

