האלגוריתם של Prim (פרים)
עודכן לאחרונה
האלגוריתם של Prim בונה עץ פורש מינימלי (MST), קבוצת הקשתות הזולה ביותר שמחברת את כל הצמתים בלי מעגלים, על ידי גידול של עץ יחיד החוצה מצומת התחלה. בכל צעד הוא בודק את כל הקשתות שחוצות מהעץ לצומת שמחוצה לו, ומוסיף את הזולה ביותר. לחצו על הפעלה למעלה כדי לראות את העץ מתרחב, ותמיד לוקח את הקשת עם המשקל הנמוך ביותר שמגיעה לצומת חדש.
מכיוון שהוא תמיד בוחר את הקשת החוצה המינימלית, כל הוספה בטוחה (מובטח שהיא חלק מעץ פורש מינימלי כלשהו). עם תור עדיפויות מבוסס ערימה בינארית, שבו המפתח של כל צומת חיצוני הוא הקשת הזולה ביותר אליו, Prim רץ ב-O(E log V). זה בניגוד ל-Kruskal, שממיין את כל הקשתות באופן גלובלי במקום לגדל עץ קשיר אחד.
סיבוכיות זמן וזיכרון
| מימוש | סיבוכיות | הערות |
|---|---|---|
| ערימה בינארית | O(E log V) | תור עדיפויות של קשתות חוצות |
| מטריצת סמיכות | O(V²) | פשוט יותר; טוב לגרפים צפופים |
| זיכרון | O(V + E) | שייכות לעץ ותור עדיפויות |
| הכי מתאים ל | גרפים צפופים | גדל מצומת התחלה יחיד |
צעד אחר צעד
| צעד | מה קורה |
|---|---|
| 1 | מתחילים את העץ עם צומת יחיד כלשהו. |
| 2 | בודקים את כל הקשתות שחוצות מהעץ לצומת שמחוצה לו. |
| 3 | בוחרים את הקשת החוצה עם המשקל הקטן ביותר. |
| 4 | מוסיפים לעץ את הקשת הזו ואת הצומת החדש שלה. |
| 5 | חוזרים על כך עד שכל הצמתים בעץ. |
דוגמה מפורטת
בניית ה-MST של גרף עם 4 צמתים והקשתות A-B=1, A-C=3, B-C=2, B-D=4, C-D=5, החל מ-A:
| צעד | עץ | קשתות חוצות | הקשת שנבחרה |
|---|---|---|---|
| 1 | {A} | A-B=1, A-C=3 | A-B (משקל 1) |
| 2 | {A, B} | A-C=3, B-C=2, B-D=4 | B-C (משקל 2) |
| 3 | {A, B, C} | B-D=4, C-D=5 | B-D (משקל 4) |
| 4 | {A, B, C, D} | אין: כל הצמתים בעץ | סיום: משקל ה-MST הוא 1+2+4 = 7 |
מתי להשתמש באלגוריתם של Prim
| השתמשו בו כאשר | הימנעו ממנו כאשר |
|---|---|
| אתם צריכים עץ פורש מינימלי של גרף ממושקל, קשיר ולא מכוון. | הגרף מכוון או שאתם צריכים מסלולים קצרים ביותר: השתמשו במקום זאת ב-Dijkstra או ב-Bellman-Ford. |
הגרף צפוף (E קרוב ל-V²); צורת המטריצה של O(V²) פשוטה ומהירה. | הגרף דליל והקשתות כבר ממוינות או קלות למיון: Kruskal לעתים קרובות פשוט יותר. |
| אתם רוצים שהעץ יגדל מאזור אחד החוצה (למשל פריסת רשת הדרגתית). | הגרף לא קשיר: Prim פורש רק רכיב אחד; אתם צריכים יער פורש מינימלי. |
| כבר יש לכם מבנה סמיכות ותור עדיפויות זמינים. | אתם צריכים לזהות מעגלים על פני קבוצת קשתות גלובלית: union-find (Kruskal) מתאים לזה יותר. |
קוד Prim's Algorithm
מימוש נקי של Prim's Algorithm שאפשר להריץ, ב-Python, JavaScript, Java, C++, C. בחרו שפה, העתיקו את הקוד, או פתחו אותו טעון מראש בעורך האונליין של Coddy.
קוד Prim's Algorithm ב-Python
1import heapq2
3
4def prim(graph, start):5 visited = {start}6 heap = [(w, start, v) for v, w in graph[start]]7 heapq.heapify(heap)8 mst, total = [], 09 while heap and len(visited) < len(graph):10 w, u, v = heapq.heappop(heap)11 if v in visited:12 continue13 visited.add(v)14 mst.append((u, v, w))15 total += w16 # Offer the new node's edges to the frontier17 for neighbor, weight in graph[v]:18 if neighbor not in visited:19 heapq.heappush(heap, (weight, v, neighbor))20 return mst, total21
22
23graph = {24 "A": [("B", 4), ("C", 1)],25 "B": [("A", 4), ("C", 3), ("D", 2)],26 "C": [("A", 1), ("B", 3), ("D", 5)],27 "D": [("B", 2), ("C", 5), ("E", 7)],28 "E": [("D", 7)],29}30
31mst, total = prim(graph, "A")32for u, v, w in mst:33 print(f"{u} - {v} (weight {w})")34print("Total MST weight:", total)קוד Prim's Algorithm ב-JavaScript
1const graph = {2 A: { B: 4, C: 2 },3 B: { A: 4, C: 1, D: 5 },4 C: { A: 2, B: 1, D: 8, E: 10 },5 D: { B: 5, C: 8, E: 2 },6 E: { C: 10, D: 2 },7};8
9function prim(start) {10 const inTree = new Set([start]);11 const mst = [];12 let total = 0;13 while (inTree.size < Object.keys(graph).length) {14 // JS has no built-in heap: scan all crossing edges for the cheapest15 let best = null;16 for (const u of inTree) {17 for (const [v, w] of Object.entries(graph[u])) {18 if (!inTree.has(v) && (best === null || w < best[2])) {19 best = [u, v, w];20 }21 }22 }23 const [u, v, w] = best;24 inTree.add(v);25 mst.push(`${u}-${v} (${w})`);26 total += w;27 }28 return { mst, total };29}30
31const { mst, total } = prim("A");32console.log("MST edges:", mst.join(", "));33console.log("Total weight:", total);קוד Prim's Algorithm ב-Java
1import java.util.ArrayList;2import java.util.List;3import java.util.PriorityQueue;4
5public class Main {6 public static void main(String[] args) {7 int n = 6;8 List<List<int[]>> adj = new ArrayList<>();9 for (int i = 0; i < n; i++) adj.add(new ArrayList<>());10 int[][] edges = {11 {0, 1, 4}, {0, 2, 3}, {1, 2, 1}, {1, 3, 2},12 {2, 3, 4}, {3, 4, 2}, {4, 5, 6}, {2, 5, 7}13 };14 for (int[] e : edges) {15 adj.get(e[0]).add(new int[]{e[1], e[2]});16 adj.get(e[1]).add(new int[]{e[0], e[2]});17 }18
19 boolean[] inMst = new boolean[n];20 // Always grow the tree along the cheapest crossing edge21 PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[1] - b[1]);22 pq.add(new int[]{0, 0}); // {node, edge weight}23 int total = 0, taken = 0;24 while (!pq.isEmpty() && taken < n) {25 int[] cur = pq.poll();26 if (inMst[cur[0]]) continue;27 inMst[cur[0]] = true;28 total += cur[1];29 taken++;30 for (int[] edge : adj.get(cur[0])) {31 if (!inMst[edge[0]]) pq.add(edge);32 }33 }34 System.out.println("MST nodes reached: " + taken);35 System.out.println("MST total weight: " + total);36 }37}קוד Prim's Algorithm ב-C++
1#include <iostream>2#include <queue>3#include <vector>4
5int main() {6 // Undirected weighted graph: adj[u] = {(neighbor, weight), ...}7 std::vector<std::vector<std::pair<int, int>>> adj = {8 {{1, 2}, {3, 6}}, // 09 {{0, 2}, {2, 3}, {3, 8}, {4, 5}}, // 110 {{1, 3}, {4, 7}}, // 211 {{0, 6}, {1, 8}}, // 312 {{1, 5}, {2, 7}}, // 413 };14 int n = static_cast<int>(adj.size());15 std::vector<bool> inMST(n, false);16 using State = std::pair<int, int>; // (edge weight, node)17 std::priority_queue<State, std::vector<State>, std::greater<State>> pq;18 pq.push({0, 0});19 int total = 0, picked = 0;20 // Always grow the tree along the cheapest crossing edge21 while (!pq.empty() && picked < n) {22 auto [w, u] = pq.top();23 pq.pop();24 if (inMST[u]) continue;25 inMST[u] = true;26 total += w;27 ++picked;28 std::cout << "Add node " << u << " (edge weight " << w << ")\n";29 for (auto [v, weight] : adj[u]) {30 if (!inMST[v]) pq.push({weight, v});31 }32 }33 std::cout << "MST total weight: " << total << "\n";34 return 0;35}קוד Prim's Algorithm ב-C
1#include <stdbool.h>2#include <stdio.h>3
4#define N 55#define INF 10000000006
7int main(void) {8 // Undirected weighted graph: w[u][v] = 0 means no edge9 int w[N][N] = {10 {0, 2, 0, 6, 0},11 {2, 0, 3, 8, 5},12 {0, 3, 0, 0, 7},13 {6, 8, 0, 0, 0},14 {0, 5, 7, 0, 0},15 };16 int key[N], parent[N];17 bool inMST[N] = {false};18 for (int v = 0; v < N; v++) {19 key[v] = INF;20 parent[v] = -1;21 }22 key[0] = 0;23 int total = 0;24 // O(V^2) scan for the cheapest crossing edge (no heap needed here)25 for (int iter = 0; iter < N; iter++) {26 int u = -1;27 for (int v = 0; v < N; v++) {28 if (!inMST[v] && (u == -1 || key[v] < key[u])) u = v;29 }30 inMST[u] = true;31 total += key[u];32 if (parent[u] != -1) {33 printf("Add edge %d - %d (weight %d)\n", parent[u], u, key[u]);34 }35 for (int v = 0; v < N; v++) {36 if (w[u][v] > 0 && !inMST[v] && w[u][v] < key[v]) {37 key[v] = w[u][v];38 parent[v] = u;39 }40 }41 }42 printf("MST total weight: %d\n", total);43 return 0;44}שאלות נפוצות על האלגוריתם של Prim
מהי סיבוכיות הזמן של האלגוריתם של Prim?
O(E log V). גרסה פשוטה יותר עם מטריצת סמיכות, שסורקת בכל צעד כדי למצוא את הקשת החוצה המינימלית, היא O(V²), והיא יכולה להיות מהירה יותר בגרפים צפופים. שתיהן משתמשות ב-O(V + E) זיכרון.מה ההבדל בין האלגוריתם של Prim לאלגוריתם של Kruskal?
האם האלגוריתם של Prim תמיד מוצא את ה-MST האופטימלי?
מתי כדאי להשתמש באלגוריתם של Prim במקום באלגוריתם של Kruskal?
O(V²) נמנעת ממיון כל E הקשתות ומגדלת עץ אחד מצומת התחלה. Kruskal מצטיין בגרפים דלילים, שבהם מיון רשימת הקשתות זול ו-union-find שומר על בדיקות מעגלים מהירות. שניהם מייצרים MST נכון, ולכן הבחירה תלויה בעיקר בצפיפות הקשתות ובמבני הנתונים שכבר יש לכם.