Bellman-Ford Algorithm
Key Points
- ā¢BellmanāFord finds single-source shortest paths even when some edge weights are negative.
- ā¢It relaxes every edge repeatedly for Vā1 rounds, where V is the number of vertices, to allow paths with up to Vā1 edges to improve.
- ā¢If a further relaxation is still possible on round V, a negative-weight cycle is reachable from the source.
- ā¢All vertices reachable from any such cycle effectively have distance āā because you can loop to reduce cost without bound.
- ā¢The algorithm runs in O(VE) time and O(V) space for distances and parents, plus O(E) to store edges.
- ā¢You must guard against overflow by not relaxing from nodes at +INF and by using 64-bit integers when weights can be large.
- ā¢Early stopping is safe: if an entire pass makes no changes, the current distances are already optimal.
- ā¢BellmanāFord is the engine behind feasibility checks for difference constraints and as a subroutine in Johnsonās algorithm.
Prerequisites
- āGraphs and directed edges ā Understanding vertices, edges, and how to model problems as directed weighted graphs is essential.
- āBig-O notation ā To analyze the O(VE) time and O(V+E) space complexity.
- āShortest path concept ā Knowing what a path is and how path costs accumulate clarifies relaxation and correctness.
- āBreadth-first search (BFS) ā Used to propagate negative-cycle reachability after detection.
- āPriority vs. queue mechanics ā Helps contrast BellmanāFord with Dijkstraās and understand SPFA heuristics.
- āInteger overflow and numeric limits ā Essential to safely implement sentinel INF and 64-bit arithmetic.
Detailed Explanation
Tap terms for definitions01Overview
The BellmanāFord algorithm is a classic method for computing the shortest path distances from a single source to all vertices in a weighted directed graph, even when some edges have negative weights. Unlike Dijkstraās algorithm, which assumes nonnegative weights, BellmanāFord systematically relaxes all edges Vā1 times (where V is the number of vertices) so that paths with up to Vā1 edges are considered. The key insight is that any simple shortest path can have at most Vā1 edges, since repeating a vertex would introduce a cycle. After these Vā1 passes, a final pass is used to detect the presence of any negative-weight cycle reachable from the source: if you can still improve a distance, then an endlessly improving (more negative) cycle exists along some reachable path. In such cases, all vertices reachable from that cycle are conceptually at distance āā because you can keep looping to reduce the path cost without bound. BellmanāFordās time complexity is O(VE), which is slower than Dijkstraās on sparse graphs, but its ability to handle negative edges and detect negative cycles makes it uniquely powerful. It serves as a building block in more advanced techniques, such as Johnsonās algorithm for all-pairs shortest paths and feasibility checks for systems of difference constraints.
02Intuition & Analogies
Imagine planning a road trip where some roads have tolls (positive cost) and a few offer rebates or cashback (negative cost). If you plan naively with only the best current deals, you might miss a great route that becomes attractive only after chaining several smaller deals together. BellmanāFord acts like a careful planner who repeatedly rechecks every road deal, allowing multi-step chains of rebates to reveal better overall savings. Each full recheck corresponds to one additional allowed hop in your trip plan. After Vā1 rechecks, any simple route involving at most Vā1 roads has been considered, so you should have the best prices possible. Now, suppose there is a bizarre promotion loop where you can drive around a small set of roads and end up with more cashback than you started with. Then you could loop forever to make your total cost as negative as you want. BellmanāFordās final recheck is specifically designed to spot such a loop: if any route still gets cheaper, you must have encountered a negative cycle. Furthermore, any destination that you can drive to after visiting that loop will have a conceptually unbounded discount (āā). In practice, the algorithm marks all those destinations as affected by a negative cycle. This ārecheck all deals repeatedly, then verify no infinite-discount loops existā intuition is the heart of BellmanāFord.
03Formal Definition
04When to Use
Use BellmanāFord when your graph may contain negative edge weights or when you must detect negative-weight cycles. It is a reliable choice for graphs with up to roughly 2Ć10^5 edges in competitive programming if optimized and used judiciously, though input sizes vary by problem. Typical scenarios include currency arbitrage detection (negative log exchange rates), scheduling with gains/penalties, and checking feasibility of difference constraints of the form x_v ā x_u ⤠w(u, v). BellmanāFord is also a necessary component in Johnsonās algorithm to reweight edges before running Dijkstraās for all-pairs shortest paths. Prefer Dijkstraās algorithm on nonnegative-weight graphs as it is faster (e.g., O((V + E) log V) with a heap). Prefer BellmanāFord when correctness with negative edges matters more than speed. If you only need to detect the existence of a negative cycle reachable from a super-source, BellmanāFord can be slightly simplified since exact distances are not required beyond feasibility.
ā ļøCommon Mistakes
- Forgetting to skip relaxation from vertices with distance +INF. Adding a weight to +INF can overflow or produce meaningless values. Always check if d[u] is finite before d[u] + w(u, v).
- Using 32-bit ints for distances when edge weights or path sums can exceed that range. Use 64-bit types (long long) and choose an INF value safely below LLONG_MAX to avoid overflow on additions.
- Running fewer than Vā1 relaxation passes. That can leave some shortest paths undiscovered, especially those requiring many edges.
- Not performing the final ācycle detectionā pass. Without it, you might incorrectly report finite distances in the presence of reachable negative cycles.
- Failing to propagate the negative-cycle effect. After detecting an edge that can still relax on pass V, you must mark every vertex reachable from any such edge as having distance āā.
- Confusing undirected negative edges with negative cycles. In undirected graphs, a single negative edge instantly creates a negative cycle of length 2; treat undirected inputs as two directed edges.
- Over-optimizing with SPFA without safeguards. SPFA can be faster in practice on many inputs but can degrade to O(VE) or worse; know worst-case behavior and add iteration caps if needed.
Key Formulas
k-edge relaxation DP
Explanation: After k full passes, the best known distance to v equals the minimum over all paths from the source to v that use at most k edges. This recurrence explains why Vā1 passes suffice when no negative cycle is reachable.
Negative cycle detection condition
Explanation: A further improvement beyond the (Vā1)-edge limit implies a beneficial cycle is being exploited. That cycle must have negative total weight and be reachable from the source.
Unbounded improvement on negative cycles
Explanation: If a directed cycle C has negative total weight, you can loop around C arbitrarily many times to reduce total path cost without bound. Therefore, any vertex reachable after such a cycle has distance āā.
Time complexity
Explanation: The algorithm performs |V|ā1 full passes over all |E| edges, and a final pass to check for cycles, leading to O(VE) time. This is generally slower than Dijkstraās on nonnegative graphs.
Space complexity
Explanation: We store distances and predecessors for all vertices (O(V)) and the edge list (O(E)). Auxiliary arrays for cycle propagation add linear overhead.
Simple path edge bound
Explanation: Any path with more than |V|ā1 edges must repeat a vertex and contain a cycle. Thus shortest simple paths have at most |V|ā1 edges.
Triangle inequality on shortest paths
Explanation: When BellmanāFord finishes without detecting negative cycles, the distances satisfy all edge constraints. This is the feasibility condition that certifies optimality.
Johnson reweighting via potentials
Explanation: Using BellmanāFord distances as vertex potentials reweights all edges to be nonnegative, enabling Dijkstraās. This works only if no negative cycle exists.
Complexity Analysis
Code Examples
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 struct Edge { 5 int u, v; // from u to v 6 long long w; // weight 7 }; 8 9 int main() { 10 ios::sync_with_stdio(false); 11 cin.tie(nullptr); 12 13 int n, m, s; // n vertices (0..n-1), m edges, source s 14 if (!(cin >> n >> m >> s)) { 15 cerr << "Usage: provide n m s followed by m edges (u v w)\n"; 16 return 0; 17 } 18 19 vector<Edge> edges(m); 20 for (int i = 0; i < m; ++i) { 21 int u, v; long long w; 22 cin >> u >> v >> w; 23 edges[i] = {u, v, w}; 24 } 25 26 const long long INF = (long long)4e18; // safe INF to avoid overflow on add 27 vector<long long> d(n, INF); 28 d[s] = 0; 29 30 // V-1 relaxation rounds 31 for (int i = 0; i < n - 1; ++i) { 32 bool any = false; 33 for (const auto &e : edges) { 34 if (d[e.u] == INF) continue; // unreachable source endpoint 35 if (d[e.v] > d[e.u] + e.w) { 36 d[e.v] = d[e.u] + e.w; 37 any = true; 38 } 39 } 40 if (!any) break; // early stopping optimization 41 } 42 43 // Build adjacency to propagate negative cycle reachability 44 vector<vector<int>> adj(n); 45 for (const auto &e : edges) adj[e.u].push_back(e.v); 46 47 // Find vertices whose distance can still be improved ā on or after a reachable negative cycle 48 vector<int> bad(n, 0); 49 for (const auto &e : edges) { 50 if (d[e.u] == INF) continue; 51 if (d[e.v] > d[e.u] + e.w) { 52 bad[e.v] = 1; 53 } 54 } 55 56 // Propagate bad marks to everything reachable (they are effectively -INF) 57 queue<int> q; 58 vector<int> vis(n, 0); 59 for (int v = 0; v < n; ++v) if (bad[v]) { q.push(v); vis[v] = 1; } 60 while (!q.empty()) { 61 int u = q.front(); q.pop(); 62 for (int v : adj[u]) if (!vis[v]) { vis[v] = 1; q.push(v); } 63 } 64 65 // Output: if unreachable ā INF; if vis[v] ā -INF; else finite distance 66 for (int v = 0; v < n; ++v) { 67 if (d[v] == INF) { 68 cout << "INF\n"; 69 } else if (vis[v]) { 70 cout << "-INF\n"; 71 } else { 72 cout << d[v] << "\n"; 73 } 74 } 75 return 0; 76 } 77
Reads a directed weighted graph and a source. Performs Vā1 rounds of relaxation over an edge list, with early stopping. Then does one more logical check for improvable edges; any vertex that can still be improved is either on or reachable from a negative-weight cycle. A BFS over the directed adjacency marks all vertices reachable from such flagged nodes. Finally, it prints per-vertex results: INF for unreachable, āINF for affected by negative cycles, and the finite shortest distance otherwise.
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 struct Edge { int u, v; long long w; }; 5 6 int main() { 7 ios::sync_with_stdio(false); 8 cin.tie(nullptr); 9 10 int n, m, s, t; // source s, target t 11 if (!(cin >> n >> m >> s >> t)) { 12 cerr << "Usage: n m s t then m lines: u v w\n"; 13 return 0; 14 } 15 vector<Edge> edges(m); 16 for (int i = 0; i < m; ++i) { 17 int u, v; long long w; cin >> u >> v >> w; 18 edges[i] = {u, v, w}; 19 } 20 21 const long long INF = (long long)4e18; 22 vector<long long> d(n, INF); 23 vector<int> parent(n, -1); 24 d[s] = 0; 25 26 // V-1 relaxations with parent tracking 27 for (int i = 0; i < n - 1; ++i) { 28 bool any = false; 29 for (const auto &e : edges) { 30 if (d[e.u] == INF) continue; 31 long long cand = d[e.u] + e.w; 32 if (cand < d[e.v]) { 33 d[e.v] = cand; 34 parent[e.v] = e.u; 35 any = true; 36 } 37 } 38 if (!any) break; 39 } 40 41 // Check if target is affected by a negative cycle 42 vector<int> affected(n, 0); 43 for (const auto &e : edges) { 44 if (d[e.u] == INF) continue; 45 if (d[e.u] + e.w < d[e.v]) affected[e.v] = 1; 46 } 47 // If t is affected or can reach from affected, mark; build adjacency to propagate 48 vector<vector<int>> adj(n); 49 for (auto &e : edges) adj[e.u].push_back(e.v); 50 queue<int> q; vector<int> vis(n, 0); 51 for (int v = 0; v < n; ++v) if (affected[v]) { q.push(v); vis[v] = 1; } 52 while (!q.empty()) { 53 int u = q.front(); q.pop(); 54 for (int v : adj[u]) if (!vis[v]) { vis[v] = 1; q.push(v); } 55 } 56 57 if (d[t] == INF) { 58 cout << "No path from s to t.\n"; 59 return 0; 60 } 61 if (vis[t]) { 62 cout << "Shortest path to t is -INF due to a reachable negative cycle.\n"; 63 return 0; 64 } 65 66 // Reconstruct path s -> ... -> t using parent[] 67 vector<int> path; 68 for (int cur = t; cur != -1; cur = parent[cur]) path.push_back(cur); 69 reverse(path.begin(), path.end()); 70 71 // Sanity: ensure path starts with s 72 if (path.empty() || path[0] != s) { 73 cout << "No path from s to t.\n"; 74 return 0; 75 } 76 77 cout << "Distance: " << d[t] << "\nPath (" << path.size() << " vertices): "; 78 for (size_t i = 0; i < path.size(); ++i) cout << path[i] << (i+1==path.size()? '\n' : ' '); 79 return 0; 80 } 81
Tracks a predecessor for each vertex when a relaxation improves its distance. After standard BellmanāFord, it checks and propagates negative-cycle effects. If the target is unaffected and reachable, it reconstructs the path by walking parent pointers backward from t to s and then reversing the sequence.
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 struct Edge { int u, v; long long w; }; 5 6 int main() { 7 ios::sync_with_stdio(false); 8 cin.tie(nullptr); 9 10 int n, m, s; cin >> n >> m >> s; 11 vector<Edge> edges(m); 12 for (int i = 0; i < m; ++i) { 13 int u, v; long long w; cin >> u >> v >> w; 14 edges[i] = {u, v, w}; 15 } 16 17 const long long INF = (long long)4e18; 18 vector<long long> d(n, INF); 19 d[s] = 0; 20 21 int rounds = 0; 22 for (int i = 0; i < n - 1; ++i) { 23 bool any = false; 24 for (const auto &e : edges) { 25 if (d[e.u] == INF) continue; 26 long long cand = d[e.u] + e.w; 27 if (cand < d[e.v]) { d[e.v] = cand; any = true; } 28 } 29 rounds++; 30 if (!any) break; // stop if nothing changed 31 } 32 33 // Quick negative cycle check 34 bool neg = false; 35 for (const auto &e : edges) { 36 if (d[e.u] == INF) continue; 37 if (d[e.u] + e.w < d[e.v]) { neg = true; break; } 38 } 39 40 cout << "Rounds used: " << rounds << "\n"; 41 if (neg) cout << "Negative cycle reachable from source.\n"; 42 else { 43 for (int v = 0; v < n; ++v) { 44 if (d[v] == INF) cout << "INF\n"; 45 else cout << d[v] << "\n"; 46 } 47 } 48 return 0; 49 } 50
Implements BellmanāFord with an early exit when a full pass performs no updates. It also reports how many rounds were actually needed and checks for a reachable negative cycle. On graphs where shortest paths use few edges, this optimization can significantly reduce runtime in practice while preserving correctness.
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 // Solve constraints of the form x[v] - x[u] <= w(u,v) 5 // Build a graph with edge u -> v of weight w(u,v) and add a super-source 0 with 0-weight edges to all vertices. 6 // If a negative cycle is reachable from 0, the system is infeasible. 7 8 struct Edge { int u, v; long long w; }; 9 10 int main() { 11 ios::sync_with_stdio(false); 12 cin.tie(nullptr); 13 14 int n, m; // n variables (1..n), m constraints 15 cin >> n >> m; 16 vector<Edge> edges; 17 edges.reserve(m + n); 18 19 for (int i = 0; i < m; ++i) { 20 int u, v; long long w; // x[v] - x[u] <= w 21 cin >> u >> v >> w; 22 edges.push_back({u, v, w}); 23 } 24 25 // Add super-source 0 -> i with 0 weight 26 for (int i = 1; i <= n; ++i) edges.push_back({0, i, 0}); 27 28 const long long INF = (long long)4e18; 29 vector<long long> d(n + 1, INF); 30 d[0] = 0; 31 32 // Bellman-Ford on vertices {0..n} 33 for (int i = 0; i < n; ++i) { // (n) rounds suffice on n+1 vertices for feasibility 34 bool any = false; 35 for (auto &e : edges) { 36 if (d[e.u] == INF) continue; 37 long long cand = d[e.u] + e.w; 38 if (cand < d[e.v]) { d[e.v] = cand; any = true; } 39 } 40 if (!any) break; 41 } 42 43 bool infeasible = false; 44 for (auto &e : edges) { 45 if (d[e.u] == INF) continue; 46 if (d[e.u] + e.w < d[e.v]) { infeasible = true; break; } 47 } 48 49 if (infeasible) cout << "Infeasible (negative cycle detected).\n"; 50 else { 51 cout << "Feasible. One solution (potentials):\n"; 52 for (int i = 1; i <= n; ++i) cout << d[i] << (i==n?'\n':' '); 53 } 54 return 0; 55 } 56
Transforms each inequality x[v] ā x[u] ⤠w into a directed edge uāv with weight w and adds a super-source 0 with 0-weight edges to all variables. Running BellmanāFord from 0 checks feasibility: any reachable negative cycle violates the constraints. If feasible, the distances provide one valid assignment of variables (potentials) that satisfies all inequalities.