Алгоритм Беллмана-Форда
Последнее обновление
Беллман-Форд находит кратчайший путь от исходной вершины до всех остальных вершин и, в отличие от Дейкстры, работает даже когда некоторые веса рёбер отрицательны. Он работает методом релаксации перебором: он многократно проходит по каждому ребру и, если путь через это ребро даёт меньшее расстояние, обновляет расстояние целевой вершины. Нажмите воспроизведение выше, чтобы увидеть, как предварительные расстояния уменьшаются проход за проходом.
После V - 1 проходов по всем рёбрам найден каждый кратчайший путь (который может содержать не более V - 1 рёбер). Это делает его O(V · E) - медленнее Дейкстры, но он обрабатывает отрицательные веса и может обнаружить цикл отрицательного веса (если V-й проход всё ещё релаксирует ребро, кратчайшего пути не существует).
Временная и пространственная сложность
| Показатель | Сложность | Примечания |
|---|---|---|
| Время | O(V · E) | V − 1 проходов по всем E рёбрам |
| Память | O(V) | Одно расстояние на вершину |
| Отрицательные веса | Поддерживаются | Его ключевое преимущество перед Дейкстрой |
| Отрицательные циклы | Обнаруживаемы | Релаксирующий 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] | Действие |
|---|---|---|
| Init | [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. |
Когда использовать Беллмана-Форда
| Используйте, когда | Избегайте, когда |
|---|---|
| Веса рёбер могут быть отрицательными. | Все веса неотрицательны (Дейкстра быстрее). |
| Нужно обнаруживать циклы отрицательного веса. | Граф огромный и плотный - 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}Часто задаваемые вопросы о Беллмане-Форде
Какова временная сложность Беллмана-Форда?
O(V · E): он делает V - 1 проходов, и каждый проход релаксирует все E рёбер. Это медленнее, чем O((V + E) log V) у Дейкстры, что является платой за поддержку отрицательных весов рёбер.Когда использовать Беллмана-Форда вместо Дейкстры?
Как Беллман-Форд обнаруживает отрицательный цикл?
V - 1 проходов релаксации все кратчайшие пути завершены, если отрицательного цикла не существует. Если ещё один проход всё ещё может релаксировать ребро, значит достижим цикл отрицательного веса, и кратчайшие пути не определены (можно бесконечно ходить по циклу, снижая стоимость).В чём разница между Беллманом-Фордом и Флойдом-Уоршеллом?
O(V · E), тогда как Флойд-Уоршелл вычисляет кратчайшие пути между всеми парами за O(V³). На плотном графе запуск Беллмана-Форда из каждого источника стоит O(V² · E), поэтому Флойд-Уоршелл обычно лучший выбор, когда нужны все пары. Оба обрабатывают отрицательные рёбра и могут сигнализировать об отрицательных циклах.Почему Беллману-Форду нужно ровно V - 1 проходов?
V - 1 рёбер, потому что путь, проходящий более V вершин, должен повторить одну из них. Каждый проход гарантирует, что корректно релаксируется хотя бы ещё одно ребро каждого кратчайшего пути, поэтому V - 1 проходов достаточно, чтобы зафиксировать их все. Распространённое заблуждение — что дополнительные проходы улучшают результат; они никогда не улучшают, если только не существует отрицательного цикла.Может ли Беллман-Форд остановиться раньше?
V - 1 проходов. Эта оптимизация раннего выхода часто делает его гораздо быстрее на графах, которые быстро сходятся, хотя граница худшего случая остаётся O(V · E).