最小生成樹是圖這一數據結構里最常討論的方面之一。
先用一下幾個概念回憶一下什么是最小生成樹:
連通圖:任意兩個結點之間都有一個路徑相連
生成樹(Spannirng Tree):連通圖的一個極小的連通子圖,它含有圖中全部n個頂點,但只有足以構成一棵樹的n-1條邊
最小生成樹(Minimum Spannirng Tree):連通圖的最小代價的生成樹(各邊的權值之和最小)
最小生成樹性質(MST性質):
設G=(V,E)是一個連通網絡,U是頂點集V的一個真子集。若(u,v)是G中一條“一個端點在U中(例如:u∈U),另一個端點不在U中的邊(例如:v∈V-U),且(u,v)具有最小權值,則一定存在G的一棵最小生成樹包括此邊(u,v)。
證明:http://fdcwqmst.blog.163.com/blog/static/164061455201010392833100/
構造最小生成樹的兩種方法:Prim算法和Kruskal算法。它們都利用了MST性質;都使用貪心策略,一次生成一條權值最小的“安全邊”。
下面看一下Prim算法的具體內容。
算法思想:
1. 從圖中選取一個節點作為起始節點(也是樹的根節點),標記為已達;初始化所有未達節點到樹的距離為到根節點的距離;
2. 從剩余未達節點中選取到樹距離最短的節點i,標記為已達;更新未達節點到樹的距離(如果節點到節點i的距離小於現距離,則更新);
3. 重復步驟2直到所有n個節點均為已達。
上述過程應該是很清晰的,下面看一下Prim算法的C語言實現:

#define N 10 // 定義最大節點數,實際有幾個是幾個 #define MAXDIST 100 // 最大距離,表示兩個節點間不可達, 為了輸入方便設置成100,實際可用INT_MAX // 為了計算方便傳入的距離矩陣用指針數組的格式,n是節點數 int Prim(int (*map)[N], int n) { int i, j; int minDist, minIndex, totalWeight; int *visited, *parent, *dist; // 分別保存節點的已達標志,父節點,到樹的距離 // 申請空間並清零 visited = (int *)malloc(n * sizeof(int)); parent = (int *)malloc(n * sizeof(int)); dist = (int *)malloc(n * sizeof(int)); memset(visited, 0, n * sizeof(int)); memset(parent, 0, n * sizeof(int)); memset(dist, 0, n * sizeof(int)); // 初始化,設置節點0為根節點 visited[0] = 1; totalWeight = 0; // 初始化未達節點到樹的距離 for (i = 1; i < n; ++i) { parent[i] = 0; dist[i] = map[0][i]; } printf("\nEdge\tWeight\n"); // n - 1次循環找出n - 1條邊 for (i = 0; i < n - 1; ++i) { minDist = MAXDIST; minIndex = i; // 找出到樹距離最小的節點 for (j = 1; j < n; ++j) { if (visited[j] == 0 && dist[j] < minDist) { minDist = dist[j]; minIndex = j; } } if (minIndex == i) // 所有節點到樹的距離都為MAXDIST,說明不是連通圖,返回 { printf("This is not a connected graph!\n"); return MAXDIST; } // 標記並輸出找到的節點和邊 visited[minIndex] = 1; totalWeight += minDist; printf("%d-->%d\t%3d\n", parent[minIndex], minIndex, map[parent[minIndex]][minIndex]); // 更新剩余節點到樹的距離 for (j = 1; j < n; ++j) { if (visited[j] == 0 && map[j][minIndex] < dist[j]) { parent[j] = minIndex; dist[j] = map[j][minIndex]; } } } printf("\nTotal Weight: %d\n\n", totalWeight); return totalWeight; }
再寫一個測試函數驗證一下功能:

1 int main() 2 { 3 int map[N][N]; 4 int i, j; 5 int n, tmp; 6
7 printf("Num of nodes: "); 8 scanf("%d", &n); 9
10 printf("Distance matrix (lower triangular) : "); 11 for (i = 1; i < n; ++i) 12 { 13 map[i][i] = 0; 14
15 for (j = 0; j < i; ++j) 16 { 17 scanf("%d", &tmp); 18 map[i][j] = tmp; 19 map[j][i] = tmp; 20 } 21 } 22
23 Prim(map, n); 24
25 return 0; 26 }
測試圖的距離矩陣如下:
因為無向圖矩陣是對稱的,所以程序中設置只要輸入下三角數據即可,也就是下面這些:
也就是輸入的數據是這些: 4 2 5 3 4 1 100 3 100 6 100 100 2 2 4
下面是程序運行截圖:
算法效率 O(n^2):
很明顯對於每個節點(初始節點除外),都要進行此次遍歷查找距離樹最近的節點 O(n),和一次更新操作O(n),所以總的時間復雜度為O(n^2)。
優化:
上述過程中,冗余操作在於對已經標記為可達的節點,在每次遍歷查找和更新時,都還要訪問一次。優化的關鍵就在於如何避免這種操作。
使用優先隊列(Priority Queue),或叫堆(Heap),將每次更新的節點加入到隊列中,而且保證隊列有一定的大小順序(每次調整的時間復雜度O(logn))。而查找具有最小距離的元素,只需要取隊列首個元素再調整隊列(O(logn))。所以取出n個節點,總的時間復雜度為O(nlogn)。
很明顯這是空間換時間的策略,效果相當可觀。但是在數據量大的情況下,可能會因為內存不足而無法工作。