אלגוריתם Bellman-Ford
עודכן לאחרונה
Bellman-Ford מוצא את המסלול הקצר ביותר מצומת מקור לכל צומת אחר, ובניגוד ל-Dijkstra הוא עובד גם כשחלק ממשקלי הקשתות שליליים. הוא פועל בריכוך בכוח גס: הוא סורק שוב ושוב כל קשת, ואם מעבר דרכה נותן מרחק קצר יותר, הוא מעדכן את המרחק של צומת היעד. לחצו על הפעלה למעלה כדי לראות את המרחקים הזמניים יורדים סבב אחר סבב.
אחרי V - 1 סבבים על כל הקשתות, כל מסלול קצר ביותר (שיש בו לכל היותר V - 1 קשתות) כבר נמצא. לכן הסיבוכיות היא O(V · E), איטי יותר מ-Dijkstra, אבל הוא מתמודד עם משקלים שליליים ויכול לזהות מעגל במשקל שלילי (אם סבב מספר V עדיין מרכך קשת, אין מסלול קצר ביותר).
סיבוכיות זמן וזיכרון
| מדד | סיבוכיות | הערות |
|---|---|---|
| זמן | O(V · E) | V − 1 סבבים על כל E הקשתות |
| זיכרון | O(V) | מרחק אחד לכל צומת |
| משקלים שליליים | נתמכים | היתרון המרכזי שלו על פני Dijkstra |
| מעגלים שליליים | ניתנים לזיהוי | סבב מספר V שעדיין מרכך מעיד על מעגל כזה |
צעד אחר צעד
| צעד | מה קורה |
|---|---|
| 1 | קובעים את מרחק המקור ל-0 ואת כל השאר לאינסוף. |
| 2 | חוזרים על הצעד הבא V − 1 פעמים. |
| 3 | לכל קשת (u → v, w), בודקים אם dist[u] + w < dist[v]. |
| 4 | אם כן, מרככים אותה: dist[v] = dist[u] + w. |
| 5 | עוצרים מוקדם אם סבב שלם לא שינה כלום. |
דוגמה מפורטת
מקור S בגרף עם 4 צמתים S, A, B, C וקשתות S→A (4), S→B (5), A→C (3), B→A (-3), שמרוככות בסדר הזה. המרחקים מתחילים ב-S=0 וכל השאר ∞:
| סבב | מרחקים [S, A, B, C] | פעולה |
|---|---|---|
| אתחול | [0, ∞, ∞, ∞] | מרחק המקור הוא 0, כל השאר אינסוף. |
| 1 | [0, 2, 5, 7] | מרככים את S→A ל-4, את S→B ל-5, את A→C ל-7, ואז B→A מוריד את A ל-5 + (-3) = 2. |
| 2 | [0, 2, 5, 5] | A→C משפר עכשיו את C ל-2 + 3 = 5; אף קשת אחרת לא מתרככת. |
| 3 | [0, 2, 5, 5] | אף קשת לא מתרככת, ולכן המרחקים סופיים: A עולה 2 דרך S→B→A. |
מתי להשתמש ב-Bellman-Ford
| השתמשו בו כאשר | הימנעו ממנו כאשר |
|---|---|
| משקלי הקשתות יכולים להיות שליליים. | כל המשקלים אי שליליים (Dijkstra מהיר יותר). |
| אתם חייבים לזהות מעגלים במשקל שלילי. | הגרף ענק וצפוף: O(V · E) איטי מדי. |
| הגרף קטן או דליל. | אתם צריכים רק שאילתה אחת ממקור ליעד בגרף גדול. |
| אתם צריכים פתרון בסיס פשוט וקל למימוש. | המשקלים אי שליליים וזמן התגובה חשוב. |
קוד Bellman-Ford Algorithm
מימוש נקי של Bellman-Ford Algorithm שאפשר להריץ, ב-Python, JavaScript, Java, C++, C. בחרו שפה, העתיקו את הקוד, או פתחו אותו טעון מראש בעורך האונליין של Coddy.
קוד Bellman-Ford Algorithm ב-Python
1def bellman_ford(vertices, edges, start):2 dist = {v: float("inf") for v in vertices}3 dist[start] = 04 # Relax every edge V-1 times5 for _ in range(len(vertices) - 1):6 for u, v, w in edges:7 if dist[u] + w < dist[v]:8 dist[v] = dist[u] + w9 # One more pass: any improvement means a negative cycle10 for u, v, w in edges:11 if dist[u] + w < dist[v]:12 raise ValueError("Graph contains a negative-weight cycle")13 return dist14
15
16vertices = ["A", "B", "C", "D", "E"]17edges = [18 ("A", "B", 6), ("A", "C", 7), ("B", "D", 5), ("B", "E", -4),19 ("C", "D", -3), ("D", "B", -2), ("E", "D", 7),20]21
22for node, d in bellman_ford(vertices, edges, "A").items():23 print(f"A -> {node}: {d}")קוד Bellman-Ford Algorithm ב-JavaScript
1const nodes = ["A", "B", "C", "D", "E"];2const edges = [3 ["A", "B", 6], ["A", "C", 7], ["B", "D", 5], ["B", "E", -4],4 ["C", "D", -3], ["D", "B", -2], ["E", "A", 2],5];6
7function bellmanFord(source) {8 const dist = Object.fromEntries(nodes.map((n) => [n, Infinity]));9 dist[source] = 0;10 // Relax every edge V-1 times11 for (let i = 0; i < nodes.length - 1; i++) {12 for (const [u, v, w] of edges) {13 if (dist[u] + w < dist[v]) dist[v] = dist[u] + w;14 }15 }16 // One more pass: any improvement means a negative cycle17 for (const [u, v, w] of edges) {18 if (dist[u] + w < dist[v]) throw new Error("Negative cycle detected");19 }20 return dist;21}22
23console.log("Shortest distances from A:", bellmanFord("A"));קוד Bellman-Ford Algorithm ב-Java
1import java.util.Arrays;2
3public class Main {4 public static void main(String[] args) {5 int n = 5;6 // Directed edges {from, to, weight}; negative weights allowed7 int[][] edges = {8 {0, 1, 6}, {0, 2, 7}, {1, 3, 5}, {1, 2, 8},9 {2, 4, 9}, {3, 1, -2}, {4, 3, 7}, {2, 3, -3}10 };11 int[] dist = new int[n];12 Arrays.fill(dist, Integer.MAX_VALUE);13 dist[0] = 0;14
15 // Relax every edge V-1 times16 for (int i = 0; i < n - 1; i++) {17 for (int[] e : edges) {18 if (dist[e[0]] != Integer.MAX_VALUE && dist[e[0]] + e[2] < dist[e[1]]) {19 dist[e[1]] = dist[e[0]] + e[2];20 }21 }22 }23
24 // One more pass: any improvement means a negative cycle25 boolean hasNegativeCycle = false;26 for (int[] e : edges) {27 if (dist[e[0]] != Integer.MAX_VALUE && dist[e[0]] + e[2] < dist[e[1]]) {28 hasNegativeCycle = true;29 }30 }31
32 System.out.println("Distances from 0: " + Arrays.toString(dist));33 System.out.println("Negative cycle: " + hasNegativeCycle);34 }35}קוד Bellman-Ford Algorithm ב-C++
1#include <iostream>2#include <vector>3
4struct Edge {5 int from, to, weight;6};7
8int main() {9 const int INF = 1000000000;10 const int n = 5;11 std::vector<Edge> edges = {12 {0, 1, 6}, {0, 2, 7}, {1, 3, 5}, {2, 3, -3},13 {1, 4, -4}, {3, 4, 9}, {2, 1, -2},14 };15 std::vector<int> dist(n, INF);16 dist[0] = 0;17 // Relax every edge V-1 times18 for (int pass = 0; pass < n - 1; ++pass) {19 for (const Edge& e : edges) {20 if (dist[e.from] != INF && dist[e.from] + e.weight < dist[e.to]) {21 dist[e.to] = dist[e.from] + e.weight;22 }23 }24 }25 // One extra pass: any improvement means a negative cycle26 bool negativeCycle = false;27 for (const Edge& e : edges) {28 if (dist[e.from] != INF && dist[e.from] + e.weight < dist[e.to]) {29 negativeCycle = true;30 }31 }32 if (negativeCycle) {33 std::cout << "Negative cycle detected\n";34 } else {35 for (int v = 0; v < n; ++v) {36 std::cout << "Distance 0 -> " << v << ": " << dist[v] << "\n";37 }38 }39 return 0;40}קוד Bellman-Ford Algorithm ב-C
1#include <stdbool.h>2#include <stdio.h>3
4#define N 55#define INF 10000000006
7typedef struct {8 int from, to, weight;9} Edge;10
11int main(void) {12 Edge edges[] = {13 {0, 1, 6}, {0, 2, 7}, {1, 3, 5}, {2, 3, -3},14 {1, 4, -4}, {3, 4, 9}, {2, 1, -2},15 };16 int m = sizeof(edges) / sizeof(edges[0]);17 int dist[N];18 for (int v = 0; v < N; v++) dist[v] = INF;19 dist[0] = 0;20 // Relax every edge V-1 times21 for (int pass = 0; pass < N - 1; pass++) {22 for (int i = 0; i < m; i++) {23 Edge e = edges[i];24 if (dist[e.from] != INF && dist[e.from] + e.weight < dist[e.to]) {25 dist[e.to] = dist[e.from] + e.weight;26 }27 }28 }29 // One extra pass: any improvement means a negative cycle30 bool negativeCycle = false;31 for (int i = 0; i < m; i++) {32 Edge e = edges[i];33 if (dist[e.from] != INF && dist[e.from] + e.weight < dist[e.to]) {34 negativeCycle = true;35 }36 }37 if (negativeCycle) {38 printf("Negative cycle detected\n");39 } else {40 for (int v = 0; v < N; v++) {41 printf("Distance 0 -> %d: %d\n", v, dist[v]);42 }43 }44 return 0;45}שאלות נפוצות על Bellman-Ford
מהי סיבוכיות הזמן של Bellman-Ford?
O(V · E): הוא מבצע V - 1 סבבים, ובכל סבב מרכך את כל E הקשתות. זה איטי יותר מ-O((V + E) log V) של Dijkstra, וזה המחיר של תמיכה במשקלי קשתות שליליים.מתי כדאי להשתמש ב-Bellman-Ford במקום ב-Dijkstra?
איך Bellman-Ford מזהה מעגל שלילי?
V - 1 סבבי הריכוך, כל המסלולים הקצרים ביותר סופיים אם אין מעגל שלילי. אם סבב נוסף עדיין יכול לרכך קשת, סימן שאפשר להגיע למעגל במשקל שלילי, והמסלולים הקצרים ביותר אינם מוגדרים (אפשר להסתובב בלולאה לנצח ולהוריד את העלות).מה ההבדל בין Bellman-Ford ל-Floyd-Warshall?
O(V · E), ואילו Floyd-Warshall מחשב מסלולים קצרים ביותר בין כל הזוגות ב-O(V³). בגרף צפוף, הרצת Bellman-Ford מכל מקור עולה O(V² · E), ולכן Floyd-Warshall הוא בדרך כלל הבחירה הטובה יותר כשצריך כל זוג. שניהם מתמודדים עם קשתות שליליות ויכולים לזהות מעגלים שליליים.למה Bellman-Ford צריך בדיוק V - 1 סבבים?
V - 1 קשתות, כי מסלול שעובר ביותר מ-V צמתים חייב לחזור על אחד מהם. כל סבב מבטיח שלפחות קשת אחת נוספת בכל מסלול קצר ביותר מרוככת נכון, ולכן V - 1 סבבים מספיקים כדי לקבע את כולם. טעות נפוצה היא לחשוב שעוד סבבים משפרים את התשובה: הם אף פעם לא משפרים, אלא אם קיים מעגל שלילי.האם Bellman-Ford יכול לעצור מוקדם?
V - 1 סבבים. אופטימיזציית היציאה המוקדמת הזו הופכת אותו לעתים קרובות למהיר הרבה יותר בגרפים שמתכנסים מהר, אם כי החסם במקרה הגרוע נשאר O(V · E).