一、算法介紹
普里姆算法(Prim's algorithm),圖論中的一種算法,可在加權連通圖里搜索最小生成樹。意即由此算法搜索到的邊子集所構成的樹中,不但包括了連通圖里的所有頂點,且其所有邊的權值之和亦為最小。像 Kruskal算法一樣,Prim算法也是貪婪算法。
二、Prim算法思想
Prim算法的思想很簡單,一棵生成樹意味着必須連接所有頂點。因此必須將兩個不相交的頂點子集連接起來才能生成生成樹 。並且它們必須以最小的權重邊連接,以使其成為最小的生成樹(MST)。它從一棵空的生成樹開始。這個想法是維持兩組頂點。第一組包含 MST 中已包含的頂點,另一組包含尚未包含的頂點。在每一步中,它都會考慮連接兩組的所有邊,並從這些邊中選取最小權重邊。選取完邊后,它將邊的另一個頂點點移動到包含 MST 的集合。
將圖中的兩組頂點連接起來的一組邊線在圖論中稱為割,割是表示圖的一組頂點中的兩個不相交的子集。因此,在Prim算法的每一步中,都會去找到一個割線(分為兩組,一組包含MST中已經包含的頂點,另一組包含其余的頂點),從割中選取最小權重邊,然后將此頂點包含到 MST 的集合中。
步驟:
1)創建一個集合 mstSet,該集合跟蹤已包含在 MST 中的頂點。
2)為輸入圖中的所有頂點分配一個鍵值。將所有鍵值初始化為 INFINITE。將第一個頂點的鍵值指定為 0,以便首先選擇它。
3)雖然當前的 mstSet 不包括所有頂點:
a)選擇一個在 mstSet 中不存在且具有最小鍵值的頂點 u。
b)將 u 包含到 mstSet 中。
c)更新 u 的所有相鄰頂點的鍵值。要更新鍵值,需遍歷所有相鄰的頂點。對於每個相鄰頂點 v,如果邊 u-v 的權重小於 v 的先前鍵值,則將鍵值更新為 u-v 的權重。
下面用一個例子來理解:
集合 mstSet 最初為空,並且分配給頂點的鍵為 {0,INF,INF,INF,INF,INF,INF,INF},其中 INF 表示無窮大。現在選擇具有最小鍵值的頂點。選擇頂點0,將其包含在 mstSet 中。因此,mstSet 變為 {0}。在包含到 mstSet 之后,更新相鄰頂點的鍵值。0的相鄰頂點是 1 和 7。1 和 7 的關鍵值更新為 4 和 8。下面的子圖顯示了頂點及其關鍵值,僅顯示了具有有限關鍵值的頂點。MST 中包含的頂點顯示為綠色。
選擇具有最小鍵值且尚未包含在 MST 中的頂點(不在 mstSet 中)。選擇頂點1並將其添加到 mstSet。因此,mstSet 現在變為 {0,1}。更新相鄰頂點1的鍵值,頂點2的鍵值變為 8。
選擇具有最小鍵值且尚未包含在 MST 中的頂點(不在 mstSet 中)。我們可以選擇頂點7 或 頂點2,讓頂點7被選擇。因此,mstSet 現在變為 {0,1,7}。更新相鄰頂點7的關鍵值。頂點 6 和 8 的關鍵值變得有限(分別為 1 和 7)。
選擇具有最小鍵值且尚未包含在 MST 中的頂點(不在 mstSet 中)。選擇了頂點6。這樣 mstSet 現在變成 {0,1,7,6}。更新相鄰頂點6的關鍵點值。更新頂點 5 和 8 的關鍵點值。
重復上述步驟,直到 mstSet 包含給定圖的所有頂點。最后,我們得到下圖。
三、算法代碼
Prim算法:
1 /** 2 * 為使用鄰接矩陣表示的圖構造和打印MST 3 * 4 * @param graph 圖的鄰接矩陣 5 */ 6 public void primMST(int[][] graph) { 7 /* 存儲構造的MST */ 8 int[] parent = new int[V]; 9 10 /* 用於選擇切割中最小權重的邊的關鍵值 */ 11 int[] key = new int[V]; 12 13 /* 表示尚未包含在MST中的一組頂點 */ 14 Boolean[] mstSet = new Boolean[V]; 15 16 /* 將所有鍵初始化為INFINITE */ 17 for (int i = 0; i < V; i++) { 18 key[i] = Integer.MAX_VALUE; 19 mstSet[i] = false; 20 } 21 22 /* 首先把一個頂點包含在MST中 */ 23 key[0] = 0; // 設置鍵0,以便此頂點被選為第一個頂點 24 parent[0] = -1; // 第一個節點始終是MST的根 25 26 for (int count = 0; count < V-1; count++) { 27 /* 從頂點集合中選擇尚未包含在MST中最小關鍵點頂點 */ 28 int u = minKey(key, mstSet); 29 30 /* 將選取的頂點添加到MST集 */ 31 mstSet[u] = true; 32 33 // graph[u][v]僅對於m的相鄰頂點為非零 34 // 對於MST中尚未包含的頂點,mstSet[v]為false 35 // 僅當graph[u][v]小於key[v]時更新密鑰 36 for (int v = 0; v < V; v++) { 37 if (graph[u][v] != 0 && !mstSet[v] && graph[u][v] < key[v]) { 38 parent[v] = u; 39 key[v] = graph[u][v]; 40 } 41 } 42 } 43 44 /* 打印構造的MST */ 45 printMST(parent, graph); 46 }
選取尚未包含在 MST 的最小關鍵值的頂點。
1 /** 2 * 用於從尚未包含在MST中的一組頂點中查找具有最小關鍵值的頂點 3 * 4 * @param key 5 * @param mstSet 6 * @return 7 */ 8 private int minKey(int[] key, Boolean[] mstSet) { 9 /* 初始化最小值 */ 10 int min = Integer.MAX_VALUE, min_index = -1; 11 12 for (int v = 0; v < V; v++) { 13 if (!mstSet[v] && key[v] < min) { 14 min = key[v]; 15 min_index = v; 16 } 17 } 18 19 return min_index; 20 }
使用鄰接矩陣的 Prim 算法中找到所有最小權邊共需時間復雜度為 O(V2)。使用簡單的二叉堆與鄰接表來表示的話,算法的時間復雜度可減少至 O(E·log2V),其中 E 為圖的邊集,V 為圖的點集。
本文源代碼:

1 package algorithm.mst; 2 3 public class PrimAlgorithm { 4 private static int V = 5; 5 6 /** 7 * 用於從尚未包含在MST中的一組頂點中查找具有最小關鍵值的頂點 8 * O(V) 9 * 10 * @param key 11 * @param mstSet 12 * @return 13 */ 14 private int minKey(int[] key, Boolean[] mstSet) { 15 /* 初始化最小值 */ 16 int min = Integer.MAX_VALUE, min_index = -1; 17 18 for (int v = 0; v < V; v++) { 19 if (!mstSet[v] && key[v] < min) { 20 min = key[v]; 21 min_index = v; 22 } 23 } 24 25 return min_index; 26 } 27 28 /** 29 * 打印構造的MST 30 * @param parent 31 * @param graph 32 */ 33 private void printMST(int[] parent, int[][] graph) { 34 System.out.println("Edge \t Weight"); 35 for (int i = 1; i < V; i++) { 36 System.out.println(parent[i] + " - " + i + "\t" + graph[i][parent[i]]); 37 } 38 } 39 40 /** 41 * 為使用鄰接矩陣表示的圖構造和打印MST 42 * 43 * @param graph 圖的鄰接矩陣 44 */ 45 public void primMST(int[][] graph) { 46 /* 存儲構造的MST */ 47 int[] parent = new int[V]; 48 49 /* 用於選擇切割中最小權重的邊的關鍵值 */ 50 int[] key = new int[V]; 51 52 /* 表示尚未包含在MST中的一組頂點 */ 53 Boolean[] mstSet = new Boolean[V]; 54 55 /* 將所有鍵初始化為INFINITE */ 56 for (int i = 0; i < V; i++) { 57 key[i] = Integer.MAX_VALUE; 58 mstSet[i] = false; 59 } 60 61 /* 首先把一個頂點包含在MST中 */ 62 key[0] = 0; // 設置鍵0,以便此頂點被選為第一個頂點 63 parent[0] = -1; // 第一個節點始終是MST的根 64 65 for (int count = 0; count < V-1; count++) { 66 /* 從頂點集合中選擇尚未包含在MST中最小關鍵點頂點 */ 67 int u = minKey(key, mstSet); 68 69 /* 將選取的頂點添加到MST集 */ 70 mstSet[u] = true; 71 72 // graph[u][v]僅對於m的相鄰頂點為非零 73 // 對於MST中尚未包含的頂點,mstSet[v]為false 74 // 僅當graph[u][v]小於key[v]時更新密鑰 75 for (int v = 0; v < V; v++) { 76 if (graph[u][v] != 0 && !mstSet[v] && graph[u][v] < key[v]) { 77 parent[v] = u; 78 key[v] = graph[u][v]; 79 } 80 } 81 } 82 83 /* 打印構造的MST */ 84 printMST(parent, graph); 85 } 86 87 public static void main(String[] args) { 88 /* 創建一個圖的鄰接矩陣 89 2 3 90 (0) -- (1) -- (2) 91 | / \ | 92 6| 8/ \5 |7 93 | / \ | 94 (3) --------- (4) 95 9 96 */ 97 PrimAlgorithm t = new PrimAlgorithm(); 98 int[][] graph = new int[][] { 99 { 0, 2, 0, 6, 0 }, 100 { 2, 0, 3, 8, 5 }, 101 { 0, 3, 0, 0, 7 }, 102 { 6, 8, 0, 0, 9 }, 103 { 0, 5, 7, 9, 0 } 104 }; 105 106 // 打印解決方案 107 t.primMST(graph); 108 } 109 }