Shortest Path Algorithm: Bellman-Ford Algorithm
Bellman-Ford Algorithm is an algorithm used to compute single-source shortest paths in a weighted directed graph. Unlike Dijkstra's Algorithm, Bellman-Ford can handle graphs containing edges with negative weights and can detect the existence of negative-weight cycles within the graph.
1. Problem Description and Core Idea
Suppose we have a weighted directed graph G(V, E), where V is the set of vertices and E is the set of edges. Given a source vertex s, our goal is to find the shortest path weights from s to all other vertices in the graph. If there exists a reachable negative-weight cycle from s, then some shortest paths may not exist (i.e., the path weight can be decreased indefinitely).
The core idea of the Bellman-Ford Algorithm is straightforward: through the Relaxation operation, it systematically considers all edges to gradually approximate the solution for the shortest paths. It guarantees finding the shortest paths (if they exist) by performing |V|-1 rounds of relaxation operations on all edges.
2. Key Concept: Relaxation Operation
The relaxation operation is the foundation of the Bellman-Ford (and Dijkstra's) algorithm. For an edge pointing from vertex u to vertex v with weight w, the relaxation process is as follows:
Assume we know a current estimated distance dist[u] from source s to vertex u, and a current estimated distance dist[v] from s to v. The relaxation operation checks whether a shorter path to v can be obtained by going through u.
Specific steps:
- Check condition:
dist[u] + w(u, v) < dist[v] - If the condition is true, it means we have found a shorter path to v (i.e., s -> ... -> u -> v).
- We then update the value of
dist[v]:dist[v] = dist[u] + w(u, v) - Typically, we also record the predecessor of
vasuto later reconstruct the shortest path.
3. Detailed Algorithm Steps
The execution of the Bellman-Ford Algorithm can be clearly divided into two main phases.
Phase One: Initialization and Relaxation
-
Initialization:
- Create an array
dist[]of size |V| to store the estimated shortest distance from source s to each vertex. - Set
dist[s]to 0, indicating the distance from s to itself is 0. - Set
dist[v]for all other vertices to a very large value (e.g.,Infinity), indicating initially we know of no path to these vertices.
- Create an array
-
Core Relaxation Loop:
- Perform |V| - 1 rounds of relaxation operations on all edges of the graph.
- In each round, we iterate through every edge (u, v) in the graph and attempt to relax it.
- Why |V| - 1 rounds? Because in a shortest path that does not contain a negative-weight cycle, there are at most |V| - 1 edges. After |V| - 1 rounds of relaxing all edges, it is sufficient to guarantee that the shortest path information "propagates" from source s to all reachable vertices.
Phase Two: Negative-weight Cycle Detection
After completing |V| - 1 rounds of relaxation, the shortest paths are theoretically obtained. However, to handle negative-weight cycles, an additional check is needed.
- Detect Negative-weight Cycles:
- Traverse every edge (u, v) in the graph once more.
- For each edge, check if a relaxation operation is still possible. That is, check if
dist[u] + w(u, v) < dist[v]still holds. - If the above condition holds for any edge, then it indicates the graph contains a reachable negative-weight cycle from s. Because only with a negative-weight cycle can the path weight be decreased indefinitely, preventing the relaxation process from ever stopping.
- If a negative-weight cycle is detected, the algorithm reports "Graph contains a negative-weight cycle," rendering shortest paths meaningless.
- If no negative-weight cycle is detected, then the
dist[]array stores the lengths of the shortest paths from source s to each vertex.
4. A Simple Example
Consider a simple directed graph with vertices A, B, C, and source A.
Edges: A->B (weight 4), A->C (weight 5), B->C (weight -2)
- Initialization:
dist[A]=0,dist[B]=Inf,dist[C]=Inf - First Round Relaxation (process all edges):
- Process A->B:
0+4 < Inf=> updatedist[B]=4 - Process A->C:
0+5 < Inf=> updatedist[C]=5 - Process B->C:
4+(-2)=2 < 5=> updatedist[C]=2
- Process A->B:
- (Since |V|=3, we only need 2 rounds of relaxation. But the result is already stable after the first round.)
- Second Round Relaxation: Process all edges again, finding no distance values can be updated further.
- Negative-weight Cycle Detection: Check all edges again; no case of
dist[u] + w < dist[v]is found. Therefore, no negative-weight cycle exists. - Final Result: The shortest distance from A to B is 4, and from A to C is 2.
5. Time and Space Complexity Analysis
- Time Complexity: O(|V| * |E|). The algorithm performs |V| - 1 loop rounds, each round iterating through all |E| edges. Negative-weight cycle detection also requires traversing all edges, with complexity O(|E|). Thus the total complexity is O(|V||E| + |E|) = O(|V||E|). This is acceptable for dense graphs (where edge count is close to |V|²), but for sparse graphs, it is less efficient than Dijkstra's Algorithm.
- Space Complexity: O(|V|). The main space is used to store the
dist[]array and the predecessor array.
6. Summary and Comparison
| Feature | Bellman-Ford Algorithm | Dijkstra's Algorithm |
|---|---|---|
| Graph Type | Weighted directed graph | Weighted directed graph |
| Weight Requirement | Can handle negative-weight edges | Requires non-negative edge weights |
| Functionality | Can detect negative-weight cycles | Cannot detect negative-weight cycles |
| Time Complexity | O( | V |
| Applicable Scenarios | Scenarios with negative-weight edges or requiring detection of negative-weight cycles | Common scenarios with non-negative edge weights, typically faster |
The Bellman-Ford Algorithm is irreplaceable in certain scenarios due to its robustness, such as when calculating paths in routing protocols where negative weights might be encountered (though uncommon), or when we need to ensure the network has no negative-weight cycles that could cause infinite loops.