Bellman–Ford Algorithm
Find the shortest distance from one source vertex to every other vertex in a weighted directed graph that may contain negative edge weights, and report whether a negative cycle exists.
Note. Filed under Medium in the course's source list. It is a graph relaxation algorithm rather than textbook tabulation, though its layer-by-layer relaxation is genuinely a DP over 'shortest path using at most k edges'.
Do this lesson first: coin changeExample input
5 vertices, source 0, edges 0→1 (6), 0→3 (7), 1→2 (5), 1→3 (8), 1→4 (-4), 2→1 (-2), 3→2 (-3), 3→4 (9), 4→0 (2), 4→2 (7)
Expected output
distances 0, 2, 4, 7, -2; no negative cycle
Break it down
Answer each question out loud before you open it. Getting it wrong here is the useful part. A revealed answer you never guessed at teaches you nothing.
Fill the table
The table pauses before each cell you have to supply. Type the value the recurrence gives, and the write animation confirms it.
Press start. The animation stops at every cell YOUR recurrence must fill.
Row 0 allows no edges, so only the source is reachable: 0 there and ∞ everywhere else. Each row reads only the row above, so the table fills one whole row at a time, which is exactly one round of relaxation. On 5 vertices, row 4 is final when there is no negative cycle: a cheapest walk then never needs to repeat a vertex, so it uses at most 4 edges. Row 5 is the check: if any cell there is lower than the one above it, a walk with 5 edges beat every walk with at most 4, which means it repeated a vertex and gained by going round a cycle, a negative one.
Cols →
Rows ↓edges allowed (k)
Cols →vertex: cheapest walk from vertex 0
- 1
function bellmanFord(n, edges, src) { - 2
const dp = [new Array(n).fill(Infinity)]; - 3
dp[0][src] = 0; - 4
for (let k = 1; k <= n; k++) { // round n is the check - 5
dp[k] = [...dp[k - 1]]; - 6
for (const [u, v, w] of edges) { - 7
dp[k][v] = Math.min(dp[k][v], dp[k - 1][u] + w); - 8
} - 9
} - 10
const negativeCycle = dp[n].some((d, v) => d < dp[n - 1][v]); - 11
return { dist: dp[n - 1], negativeCycle }; - 12
}
The code, the trap, the variations
- 1
function bellmanFord(n, edges, src) { - 2
const dp = [new Array(n).fill(Infinity)]; - 3
dp[0][src] = 0; - 4
for (let k = 1; k <= n; k++) { // round n is the check - 5
dp[k] = [...dp[k - 1]]; - 6
for (const [u, v, w] of edges) { - 7
dp[k][v] = Math.min(dp[k][v], dp[k - 1][u] + w); - 8
} - 9
} - 10
const negativeCycle = dp[n].some((d, v) => d < dp[n - 1][v]); - 11
return { dist: dp[n - 1], negativeCycle }; - 12
}
Where people go wrong
Stopping after V - 1 rounds and reporting row V - 1 without the check. On graph C that reports -6 as the distance to vertex 4, but row 5 is lower again in three of the five columns. The graph has negative cycles, the cheapest being 0 → 3 → 2 → 1 → 4 → 0 at -4, and that one passes through every vertex, so a walk can go round it as often as it likes: no vertex has a shortest distance, not even vertex 4, whose column happens to hold still for one round. The second trap shows up when ∞ is stored as a large number such as 1e9: 1e9 + (-4) is smaller than 1e9, so an unreachable vertex looks reachable after one round. Skip any edge whose start is still ∞, which is what JavaScript's Infinity does for free.
Cheapest route with at most K stops, so at most K + 1 edges.
Nothing in the recurrence changes. Fill rows up to K + 1 and read that row; the limit is the layer index this table already has. Relaxing in place breaks this version, because in place a round can use more than one new edge.
Cheapest walk with EXACTLY k edges.
Drop the dp[k - 1][v] candidate, so a cell must arrive over an edge in this round; row 0 is unchanged.
All weights are non-negative.
The table is still correct, and the check row can never move. Dijkstra's algorithm then answers the same question in O(E log V), because a finished vertex can never be improved.