האלגוריתם של Kruskal (קרוסקל)
עודכן לאחרונה
האלגוריתם של Kruskal בונה עץ פורש מינימלי (MST): קבוצת הקשתות הזולה ביותר שמחברת את כל הצמתים בלי מעגלים. הוא ממיין את כל הקשתות לפי משקל, ואז מוסיף בצורה חמדנית את הקשת הזולה הבאה, כל עוד היא מחברת שני צמתים שעוד לא מחוברים. לחצו על הפעלה למעלה כדי לראות את העץ גדל, קשת בטוחה וזולה אחת בכל פעם.
הבדיקה "האם כבר מחוברים?" מתבצעת בעזרת מבנה הנתונים union-find (קבוצות זרות): מדלגים על קשת אם שני הקצוות שלה כבר באותו רכיב, כי הוספה שלה הייתה יוצרת מעגל. המיון שולט בעלות, ונותן O(E log E) בסך הכל. Kruskal מצטיין בגרפים דלילים.
סיבוכיות זמן וזיכרון
| מדד | סיבוכיות | הערות |
|---|---|---|
| זמן | O(E log E) | נשלט על ידי מיון הקשתות |
| פעולות union-find | ≈ O(E α(V)) | כמעט קבוע לכל בדיקה עם כיווץ מסלולים |
| זיכרון | O(V + E) | רשימת קשתות ומבנה קבוצות זרות |
| הכי מתאים ל | גרפים דלילים | עובד מרשימת קשתות גלובלית |
צעד אחר צעד
| צעד | מה קורה |
|---|---|
| 1 | ממיינים את כל הקשתות לפי משקל, בסדר עולה. |
| 2 | מכניסים כל צומת לרכיב משלו (union-find). |
| 3 | לוקחים את הקשת הזולה הבאה. |
| 4 | אם הקצוות שלה ברכיבים שונים, מוסיפים אותה לעץ ומאחדים אותם. |
| 5 | אחרת מדלגים עליה (היא הייתה יוצרת מעגל). |
| 6 | עוצרים כשבעץ יש V − 1 קשתות. |
דוגמה מפורטת
גרף עם הצמתים A, B, C, D והקשתות A-B(1), B-C(2), A-C(3), C-D(4), B-D(5). הקשתות הממוינות: A-B(1), B-C(2), A-C(3), C-D(4), B-D(5):
| קשת (משקל) | רכיבים לפני | פעולה |
|---|---|---|
A-B(1) | {A} {B} {C} {D} | רכיבים שונים: מוסיפים ל-MST ומאחדים ל-{A,B}. |
B-C(2) | {A,B} {C} {D} | רכיבים שונים: מוסיפים ל-MST ומאחדים ל-{A,B,C}. |
A-C(3) | {A,B,C} {D} | A ו-C כבר יחד: מדלגים (היה נוצר מעגל). |
C-D(4) | {A,B,C} {D} | רכיבים שונים: מוסיפים ל-MST ומאחדים ל-{A,B,C,D}. |
| עצירה | {A,B,C,D} | בעץ יש V - 1 = 3 קשתות. MST = A-B, B-C, C-D, משקל כולל 7. |
מתי להשתמש באלגוריתם של Kruskal
| השתמשו בו כאשר | הימנעו ממנו כאשר |
|---|---|
| הגרף דליל (מעט קשתות ביחס לצמתים). | הגרף צפוף: Prim עם ערימה בדרך כלל מהיר יותר. |
| הקשתות כבר זמינות כרשימה גלובלית שאפשר למיין. | הקשתות מגיעות רק דרך רשימות סמיכות שצריך לסרוק לכל צומת. |
| אתם רוצים מימוש פשוט שנשען על union-find. | אתם צריכים שהעץ יגדל מצומת התחלה מסוים. |
| אתם רוצים לבנות יער פורש של גרף לא קשיר. | אתם חייבים לטפל בקשתות שמגיעות בזרם בלי מיון מלא. |
קוד Kruskal's Algorithm
מימוש נקי של Kruskal's Algorithm שאפשר להריץ, ב-Python, JavaScript, Java, C++, C. בחרו שפה, העתיקו את הקוד, או פתחו אותו טעון מראש בעורך האונליין של Coddy.
קוד Kruskal's Algorithm ב-Python
1def find(parent, x):2 while parent[x] != x:3 parent[x] = parent[parent[x]] # path compression4 x = parent[x]5 return x6
7
8def kruskal(vertices, edges):9 # Take the cheapest edge that does not close a cycle10 parent = {v: v for v in vertices}11 mst, total = [], 012 for w, u, v in sorted(edges):13 root_u, root_v = find(parent, u), find(parent, v)14 if root_u != root_v:15 parent[root_u] = root_v16 mst.append((u, v, w))17 total += w18 return mst, total19
20
21vertices = ["A", "B", "C", "D", "E"]22edges = [23 (4, "A", "B"), (1, "A", "C"), (3, "B", "C"), (2, "B", "D"),24 (5, "C", "D"), (6, "C", "E"), (7, "D", "E"),25]26
27mst, total = kruskal(vertices, edges)28for u, v, w in mst:29 print(f"{u} - {v} (weight {w})")30print("Total MST weight:", total)קוד Kruskal's Algorithm ב-JavaScript
1const nodes = ["A", "B", "C", "D", "E"];2const edges = [3 ["A", "B", 4], ["A", "C", 2], ["B", "C", 1], ["B", "D", 5],4 ["C", "D", 8], ["C", "E", 10], ["D", "E", 2],5];6
7function kruskal() {8 // Union-find with path compression detects cycles cheaply9 const parent = Object.fromEntries(nodes.map((n) => [n, n]));10 const find = (x) => (parent[x] === x ? x : (parent[x] = find(parent[x])));11 const mst = [];12 let total = 0;13 const sorted = [...edges].sort((a, b) => a[2] - b[2]);14 for (const [u, v, w] of sorted) {15 const rootU = find(u);16 const rootV = find(v);17 if (rootU !== rootV) {18 parent[rootU] = rootV;19 mst.push(`${u}-${v} (${w})`);20 total += w;21 }22 }23 return { mst, total };24}25
26const { mst, total } = kruskal();27console.log("MST edges:", mst.join(", "));28console.log("Total weight:", total);קוד Kruskal's Algorithm ב-Java
1import java.util.Arrays;2
3public class Main {4 static int[] parent;5
6 static int find(int x) {7 if (parent[x] != x) parent[x] = find(parent[x]); // path compression8 return parent[x];9 }10
11 static boolean union(int a, int b) {12 int rootA = find(a), rootB = find(b);13 if (rootA == rootB) return false; // would create a cycle14 parent[rootA] = rootB;15 return true;16 }17
18 public static void main(String[] args) {19 int n = 6;20 int[][] edges = {21 {0, 1, 4}, {0, 2, 3}, {1, 2, 1}, {1, 3, 2},22 {2, 3, 4}, {3, 4, 2}, {4, 5, 6}, {2, 5, 7}23 };24 Arrays.sort(edges, (a, b) -> a[2] - b[2]);25 parent = new int[n];26 for (int i = 0; i < n; i++) parent[i] = i;27
28 int total = 0, used = 0;29 for (int[] e : edges) {30 if (union(e[0], e[1])) {31 System.out.println("Take edge " + e[0] + "-" + e[1] + " (weight " + e[2] + ")");32 total += e[2];33 if (++used == n - 1) break;34 }35 }36 System.out.println("MST total weight: " + total);37 }38}קוד Kruskal's Algorithm ב-C++
1#include <algorithm>2#include <iostream>3#include <numeric>4#include <vector>5
6struct Edge {7 int u, v, weight;8};9
10struct DSU {11 std::vector<int> parent;12 explicit DSU(int n) : parent(n) {13 std::iota(parent.begin(), parent.end(), 0);14 }15 int find(int x) {16 if (parent[x] != x) parent[x] = find(parent[x]); // path compression17 return parent[x];18 }19 bool unite(int a, int b) {20 int ra = find(a), rb = find(b);21 if (ra == rb) return false; // would create a cycle22 parent[ra] = rb;23 return true;24 }25};26
27int main() {28 const int n = 5;29 std::vector<Edge> edges = {30 {0, 1, 4}, {0, 2, 2}, {1, 2, 1}, {1, 3, 5},31 {2, 3, 8}, {2, 4, 10}, {3, 4, 3},32 };33 // Greedily take the cheapest edge that connects two components34 std::sort(edges.begin(), edges.end(),35 [](const Edge& a, const Edge& b) { return a.weight < b.weight; });36 DSU dsu(n);37 int total = 0;38 std::cout << "MST edges:\n";39 for (const Edge& e : edges) {40 if (dsu.unite(e.u, e.v)) {41 std::cout << " " << e.u << " - " << e.v42 << " (weight " << e.weight << ")\n";43 total += e.weight;44 }45 }46 std::cout << "Total weight: " << total << "\n";47 return 0;48}קוד Kruskal's Algorithm ב-C
1#include <stdbool.h>2#include <stdio.h>3#include <stdlib.h>4
5#define N 56
7typedef struct {8 int u, v, weight;9} Edge;10
11int parent[N];12
13int find(int x) {14 if (parent[x] != x) parent[x] = find(parent[x]); // path compression15 return parent[x];16}17
18bool unite(int a, int b) {19 int ra = find(a), rb = find(b);20 if (ra == rb) return false; // would create a cycle21 parent[ra] = rb;22 return true;23}24
25int cmpEdge(const void* a, const void* b) {26 return ((const Edge*)a)->weight - ((const Edge*)b)->weight;27}28
29int main(void) {30 Edge edges[] = {31 {0, 1, 4}, {0, 2, 2}, {1, 2, 1}, {1, 3, 5},32 {2, 3, 8}, {2, 4, 10}, {3, 4, 3},33 };34 int m = sizeof(edges) / sizeof(edges[0]);35 for (int i = 0; i < N; i++) parent[i] = i;36 // Greedily take the cheapest edge that connects two components37 qsort(edges, m, sizeof(Edge), cmpEdge);38 int total = 0;39 printf("MST edges:\n");40 for (int i = 0; i < m; i++) {41 if (unite(edges[i].u, edges[i].v)) {42 printf(" %d - %d (weight %d)\n",43 edges[i].u, edges[i].v, edges[i].weight);44 total += edges[i].weight;45 }46 }47 printf("Total weight: %d\n", total);48 return 0;49}שאלות נפוצות על האלגוריתם של Kruskal
מהו עץ פורש מינימלי?
V - 1 קשתות עבור V צמתים.מה ההבדל בין האלגוריתם של Kruskal לאלגוריתם של Prim?
למה האלגוריתם של Kruskal משתמש ב-union-find?
מתי כדאי להשתמש באלגוריתם של Kruskal במקום באלגוריתם של Prim?
O(E log E) זול כש-E קטן. האלגוריתם של Prim עם ערימה בינארית או ערימת פיבונאצ'י נוטה לנצח בגרפים צפופים שבהם E מתקרב ל-V², כי הוא נמנע ממיון של כל הקשתות מראש.האם האלגוריתם של Kruskal עובד על גרפים לא קשירים?
V - 1 קשתות, ובמקום זאת מייצר יער פורש מינימלי: MST אחד לכל רכיב קשירות. זה קורה באופן טבעי, כי union-find לעולם לא מאחד צמתים שאין ביניהם מסלול.