Bellman-Ford
Single-source shortest paths that tolerate negative edge weights: relax every edge V - 1 times, then one more pass to detect negative cycles.
Overview
Bellman-Ford solves single-source shortest paths on graphs where edges may be negative. It has no clever ordering: it simply relaxes every edge, repeats that V - 1 times, and is guaranteed correct because after i rounds every shortest path that uses at most i edges has been found. A V-th round that still improves something proves a negative cycle reachable from the source.
Preconditions and complexity: directed graph (an undirected negative edge is itself a negative cycle), any weights, single source. Time O(V · E) — on a dense graph that is O(V³), and even on sparse graphs it is far slower than Dijkstra's Algorithm's O((V + E) log V). Use it only when negative edges exist, when you must detect negative cycles, or when a bound on path length (at most k edges) is part of the problem.
Intuition
A mental model before the formal terms.
Think of dist as a rumour spreading one hop per round. In round 1 everyone adjacent to the source learns the best one-edge price. In round 2 everyone learns the best price using two edges, because their neighbours now hold correct one-edge prices. A shortest path never revisits a node (if there is no negative cycle), so it has at most V - 1 edges, and after V - 1 rounds the rumour has stabilised. If prices still drop in round V, someone is looping around a cycle that pays you to travel it — a negative cycle.
How it works
- Set
dist[s] = 0, all others∞. Represent the graph as an Edge List(u, v, w). - Repeat
V - 1times: for every edge(u, v, w), ifdist[u] + w < dist[v]setdist[v] = dist[u] + w,parent[v] = u. Skip edges whosedist[u]is still∞. - Early exit: if a full round changes nothing, distances are final — stop.
- Negative-cycle check: run one more round. Any edge that still relaxes lies on, or is reachable from, a negative cycle. To extract the cycle, follow
parentfrom thatvforVsteps to land inside the cycle, then walk around it. - Variant: to find shortest paths with at most k edges, run exactly
krounds but relax from a copy of the previous round's distances so that one round cannot chain several edges.
Why it works
Induction on rounds: after round i, dist[v] ≤ the weight of the best path to v with at most i edges. Base: round 0, only s at 0. Step: the best (i+1)-edge path ends with some edge (u, v); its prefix is an i-edge path to u, already reflected in dist[u], so relaxing (u, v) in round i+1 sets dist[v] at least as low.
Without a negative cycle, some shortest path is simple and has ≤ V - 1 edges, so V - 1 rounds suffice. dist never goes below the true shortest distance because every update corresponds to an actual walk.
If a negative cycle is reachable, no finite shortest distance exists for its nodes, so relaxation continues forever; in particular round V still improves something. Conversely if round V improves nothing, the values are a fixed point satisfying the triangle inequality, hence optimal.
Recognition
How to tell a problem wants this.
- Edge weights can be negative (costs with refunds, currency exchange as
-log rate, "profit" edges). - The problem asks to detect a negative cycle or an arbitrage opportunity.
- "At most k stops / edges" constraints — the round-limited variant is the natural fit.
- Small graphs (
V·E ≤ ~10^7) where simplicity matters more than speed.
Interactive visualization
Play, step, change the input. ← → and space work too.
| round | S | A | B | C | D | E |
|---|---|---|---|---|---|---|
| 0 | 0 | ∞ | ∞ | ∞ | ∞ | ∞ |
| now | 0 | ∞ | ∞ | ∞ | ∞ | ∞ |
1dist = {v: ∞}; dist[source] = 02for round in 1 .. n-1:3 for (u, v, w) in edges:4 if dist[u] + w < dist[v]:5 dist[v] = dist[u] + w; parent[v] = u6 if nothing changed: break7for (u, v, w) in edges:8 if dist[u] + w < dist[v]: report negative cyclePseudocode
1dist = [INF] * n; dist[s] = 02for round in 1..n-1:3 changed = false4 for (u, v, w) in edges:5 if dist[u] != INF and dist[u] + w < dist[v]:6 dist[v] = dist[u] + w; parent[v] = u; changed = true7 if not changed: break8for (u, v, w) in edges: if dist[u] + w < dist[v]: report negative cycleImplementations
1from math import inf2 3 4def bellman_ford(5 n: int, edges: list[tuple[int, int, int]], s: int6) -> tuple[list[float], list[int], bool]:7 """edges = directed (u, v, w) triples; nodes 0..n-1.8 Returns (dist, parent, has_negative_cycle); dist = inf for unreachable nodes."""91 · Initialize distances10 dist: list[float] = [inf] * n11 parent = [-1] * n12 dist[s] = 0132 · Relax every edge V - 1 times14 for _ in range(n - 1):15 changed = False16 for u, v, w in edges:17 if dist[u] != inf and dist[u] + w < dist[v]:18 dist[v] = dist[u] + w19 parent[v] = u20 changed = True213 · Early exit when a round changes nothing22 if not changed:23 break244 · Negative-cycle detection round25 has_negative_cycle = any(26 dist[u] != inf and dist[u] + w < dist[v] for u, v, w in edges27 )285 · Result29 return dist, parent, has_negative_cycle- Edges are
(u, v, w)tuples unpacked directly in theforheader. math.infis the idiomatic sentinel;inf + wstaysinf, so the guard mainly saves work and keeps parity with C++.- The early exit uses a
changedflag;for ... elsecould detect it too but the flag is clearer. - The detection round is a generator expression inside
any(...)— it short-circuits on the first still-relaxing edge.
CPython interprets ~10^7 relaxations per second; V·E above ~10^7 needs PyPy or a rethink.
distislist[float]becauseinfis a float; mixing ints and floats compares correctly in Python.- The
any(...)generator allocates no intermediate list — preferred over a list comprehension here. - For the "at most k edges" variant, relax from a copy:
nxt = dist[:]each round.
- Using
sys.maxsizeas infinity and then treatingsys.maxsize + was unreachable — it is a perfectly finite int; usemath.inf. - Relaxing in place for the k-edges variant, letting one round chain multiple edges.
- Writing
for u, v, w in edges:when edges are lists of lists of varying length — unpacking raisesValueErrormid-run.
- Infinity sentinel: JS/TS
Infinityand Pythonmath.infabsorb additions safely (inf + w == inf), so thedist[u] != INFguard is an optimisation there; in C++INF + wonlong longis undefined behaviour, so the guard is mandatory (or shrink INF to MAX/4). - Edge shape: C++ uses a small
struct Edge; JS/TS destructure[u, v, w]tuples (TS checks the arity); Python unpacks(u, v, w)tuples in the loop header. - Detection round: JS/TS
edges.some(...)and Pythonany(generator)both short-circuit; C++ uses a plain loop with earlyreturn false. - Number range: Python ints are unbounded; C++ needs
long long; JS/TS integer sums are exact only below 2^53.
Complexity
Best case: distances converge after one round and the early exit fires. SPFA (queue-based Bellman-Ford) is often fast in practice but still O(VE) worst case.
Compare growth rates in the Complexity Explorer →When to use — and when not to
- Negative edge weights without negative cycles (Dijkstra is wrong here).
- Detecting negative cycles: arbitrage, "can the cost be decreased forever?".
- Shortest path with a bound on the number of edges (k rounds with a copied array).
- As the reweighting step of Johnson's all-pairs algorithm on sparse graphs with negative edges.
- Distributed settings (distance-vector routing) where each node only talks to neighbours.
- All weights non-negative — Dijkstra's Algorithm is asymptotically and practically much faster.
- Unweighted — BFS Shortest Path (Unweighted).
- All-pairs on a small dense graph — Floyd-Warshall is
O(V³)versusO(V² · E) ≈ O(V⁴)for running Bellman-Ford from every node. - Large sparse graphs (
V, E ≈ 10^5) without negative edges —O(VE) = 10^10is infeasible.
Alternatives
Common mistakes
- Relaxing edges out of nodes with
dist = ∞— with fixed-width integers∞ + woverflows (or, for negativew, becomes a bogus finite value). - For the "at most k edges" variant, relaxing in place: one round can then chain many edges and exceeds the limit. Relax from a copy.
- Concluding "negative cycle" from the check without restricting to nodes reachable from the source — unreachable cycles are irrelevant if only
s-distances matter (the check above already skips∞tails). - Running exactly
Vrounds and treating the last as normal — theV-th round is the detection round. - Applying it to an undirected graph with a negative edge: that edge alone is a negative cycle.
Interview patterns
- Cheapest flights within k stops:
k + 1rounds with a copied distance array. - Currency arbitrage: edge weight
-log(rate), negative cycle ⇔ arbitrage. - Detect and print a negative cycle by following parents
Vtimes from a still-relaxing node. - Johnson's algorithm: Bellman-Ford from a virtual source to compute potentials, then Dijkstra from every node.
- Network Delay TimeAdvanced
- Coin ChangeIntermediate