The Bellman-Ford Algorithm: Shortest Paths with Negative Weights
An algorithm that finds shortest paths in graphs with negative edge weights and detects negative cycles
Contents
An algorithm that finds shortest paths in graphs with negative edge weights and detects negative cycles as well.
When computing shortest paths in a weighted graph, Dijkstra's algorithm no longer applies once edge weights can be negative. Bellman-Ford permits negative weights, still finds the shortest paths, and additionally detects whether a negative cycle exists.
What it solves
It finds the shortest path from a single source vertex to every other vertex. What separates it from Dijkstra is that it works even when some edge weights are negative.
Key properties
- Works on graphs containing negative weights
- Can detect the presence of a negative cycle
- Time complexity: (V is the vertex count, E the edge count)
Dijkstra vs Bellman-Ford
| Property | Dijkstra | Bellman-Ford |
|---|---|---|
| Time complexity | ||
| Negative weights | Not supported | Supported |
| Negative cycle detection | Not supported | Supported |
The algorithm
Step 1: relaxation
Iterate over every edge V-1 times, updating distances as you go.
Relaxation is the operation of lowering a recorded distance whenever a shorter path to that vertex is discovered.
for i in range(V-1):
for (u, v, w) in edges:
if dist[u] + w < dist[v]:
dist[v] = dist[u] + wWhy V-1 iterations: a shortest path contains at most V-1 edges.
Step 2: negative cycle detection
After the V-1 iterations, scan the edges one more time. If any distance still updates, a negative cycle exists.
for (u, v, w) in edges:
if dist[u] + w < dist[v]:
return "negative cycle detected"Example graph
| from | to | weight |
|---|---|---|
| 0 | 1 | 6 |
| 0 | 2 | 7 |
| 1 | 2 | 8 |
| 1 | 3 | 5 |
| 1 | 4 | -4 |
| 2 | 3 | -3 |
| 2 | 4 | 9 |
| 3 | 1 | -2 |
| 4 | 0 | 2 |
| 4 | 3 | 7 |
Trace (source: 0)
- Initial:
[0, ∞, ∞, ∞, ∞] - After pass 1:
[0, 2, 7, 4, 2] - After pass 2:
[0, 2, 7, 4, -2] - After pass 3:
[0, 2, 7, 4, -2]
Result: [0, 2, 7, 4, -2]
The intermediate values per pass come from relaxing in the edges order below; a different edge order produces different intermediate values. The final result is the same either way.
Python implementation
bellman_ford(start, n, edges) takes the source vertex start, the vertex count n, and the edge list edges. It initializes the distance array to INF, sets the source to 0, and returns a (dist, has_negative_cycle) tuple. When has_negative_cycle is True, the distance array cannot be trusted.
import sys
def bellman_ford(start, n, edges):
INF = sys.maxsize
dist = [INF] * n
dist[start] = 0
# V-1 iterations
for _ in range(n - 1):
for u, v, w in edges:
if dist[u] != INF and dist[u] + w < dist[v]:
dist[v] = dist[u] + w
# negative cycle detection
for u, v, w in edges:
if dist[u] != INF and dist[u] + w < dist[v]:
return dist, True # negative cycle exists
return dist, False
# Run
edges = [
(0, 1, 6), (0, 2, 7), (1, 2, 8), (1, 3, 5),
(1, 4, -4), (2, 3, -3), (2, 4, 9),
(3, 1, -2), (4, 0, 2), (4, 3, 7)
]
dist, has_negative_cycle = bellman_ford(0, 5, edges)
if has_negative_cycle:
print("Negative cycle detected")
else:
print(f"Shortest distances: {dist}")Java implementation
The logic matches the Python version, except that it throws an exception on a negative cycle instead of returning a boolean.
import java.util.*;
public class BellmanFord {
static class Edge {
int u, v, w;
Edge(int u, int v, int w) {
this.u = u; this.v = v; this.w = w;
}
}
public static int[] bellmanFord(int start, int n, List<Edge> edges) {
int[] dist = new int[n];
Arrays.fill(dist, Integer.MAX_VALUE);
dist[start] = 0;
// V-1 iterations
for (int i = 0; i < n - 1; i++) {
for (Edge e : edges) {
if (dist[e.u] != Integer.MAX_VALUE &&
dist[e.u] + e.w < dist[e.v]) {
dist[e.v] = dist[e.u] + e.w;
}
}
}
// negative cycle detection
for (Edge e : edges) {
if (dist[e.u] != Integer.MAX_VALUE &&
dist[e.u] + e.w < dist[e.v]) {
throw new RuntimeException("Negative cycle detected");
}
}
return dist;
}
}Go implementation
Like the Python version it returns (dist, bool), using math.MaxInt32 in place of infinity.
func BellmanFord(start, n int, edges []Edge) ([]int, bool) {
dist := make([]int, n)
for i := range dist {
dist[i] = math.MaxInt32
}
dist[start] = 0
// V-1 iterations
for i := 0; i < n-1; i++ {
for _, e := range edges {
if dist[e.u] != math.MaxInt32 &&
dist[e.u]+e.w < dist[e.v] {
dist[e.v] = dist[e.u] + e.w
}
}
}
// negative cycle detection
for _, e := range edges {
if dist[e.u] != math.MaxInt32 &&
dist[e.u]+e.w < dist[e.v] {
return dist, true
}
}
return dist, false
}When a negative cycle exists
The shortest distance to any vertex reachable from a negative cycle diverges to and is therefore undefined. (Every additional pass keeps lowering the value.)
Applications
| Field | Use |
|---|---|
| Finance and economics | Detecting currency arbitrage opportunities |
| Networking | Routing with negative costs |
| Games | Path computation with negative effects |
Summary
- V-1 passes to relax distances
- One extra pass to decide whether a negative cycle exists
- Time complexity , which is inefficient on large graphs
- The default choice whenever negative weights are involved
Bellman-Ford settles the shortest distances in V-1 relaxation passes and identifies negative cycles with one additional check. Vertices reachable from a negative cycle have diverging distances, so they must be flagged separately. Its complexity makes it slow on large graphs with many edges. When there are no negative weights Dijkstra is faster, so reach for Bellman-Ford when negative weights or negative cycle detection is actually required.