Purpose: Find the shortest path from a single source to all other vertices in a weighted graph, even with negative edge weights, and detect negative-weight cycles, in O(V·E) time.
Algorithm
- Initialize a
dist[]array with infinity for all vertices, except the source, which is 0. - Relax all edges repeatedly,
V - 1times (whereVis the number of vertices):- For each edge
(u, v, w), ifdist[u] + w < dist[v], updatedist[v] = dist[u] + w.
- For each edge
- After
V - 1passes, all shortest paths (if no negative cycle exists) are finalized. - Do one more pass over all edges: if any edge can still be relaxed (
dist[u] + w < dist[v]), a negative-weight cycle exists and shortest paths are not well-defined.
Code
bool bellmanFord(int n, vector<array<int,3>>& edges, int src, vector<int>& dist) {
dist.assign(n, INT_MAX);
dist[src] = 0;
for (int i = 0; i < n - 1; i++) {
for (auto& [u, v, w] : edges) {
if (dist[u] != INT_MAX && dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
}
}
}
// check for negative cycle
for (auto& [u, v, w] : edges) {
if (dist[u] != INT_MAX && dist[u] + w < dist[v]) {
return false; // negative cycle exists
}
}
return true;
}Paradigm
Dynamic Programming. dist[v] after k iterations is the optimal solution to the subproblem “shortest path to v using at most k edges,” built up from the k-1 solution by relaxing edges — a bottom-up DP over path length, unlike Dijkstra’s greedy commit-and-never-revisit approach.
Complexity
- Time: O(V·E)
- Space: O(V)
Proof of Correctness
Claim: After k iterations of relaxing all edges, dist[v] is correct for every vertex v whose shortest path from the source uses at most k edges.
Proof by induction on k:
Base case: After 0 iterations, dist[src] = 0 is correct for the shortest path using 0 edges (just the source itself); all other vertices are still infinity, correctly reflecting that no path using 0 edges reaches them.
Inductive step: Assume after k iterations, dist[v] is correct for all vertices whose shortest path uses at most k edges. Consider a vertex x whose shortest path uses exactly k+1 edges, with the last edge being (y, x, w). Since the path to y (the second-to-last vertex) uses at most k edges, dist[y] is already correct after k iterations by the inductive hypothesis. In the (k+1)-th iteration, since we relax every edge (including (y, x, w)), dist[x] gets updated to dist[y] + w, which is exactly the shortest path length. So dist[x] becomes correct after k+1 iterations.
Why V - 1 iterations suffice: A shortest path in a graph with V vertices (assuming no negative cycles) can have at most V - 1 edges, since a simple path visits each vertex at most once. So after V - 1 iterations, all shortest distances are finalized.
Negative cycle detection: If a negative-weight cycle is reachable from the source, distances along that cycle can be decreased indefinitely by going around it repeatedly. This means even after V - 1 iterations, at least one edge can still be relaxed further — which is exactly what the extra V-th check detects. ∎
Variants / Use Cases
- Negative cycle detection in currency arbitrage → model exchange rates as edge weights (log-transformed) to detect profitable arbitrage loops
- Bellman-Ford with path reconstruction → maintain a
parent[]array to backtrack the actual shortest path - SPFA (Shortest Path Faster Algorithm) → queue-based optimization of Bellman-Ford, faster in practice on sparse graphs
- Distance-vector routing protocols (RIP) → real-world application, each node runs a distributed version of Bellman-Ford
- Difference constraints systems → solving systems of inequalities
x_j - x_i ≤ wusing shortest-path formulation - Detecting unreachable negative cycles → only report cycles reachable from the source, not all cycles in the graph