Bellman-Ford and Floyd-Warshall

Find cheapest routes when some costs are negative, catch negative cycles, and build a table of costs between every pair of places, in JS and Python.

18 minLesson 3 of 4
On this page
  1. Bellman-Ford: relax every edge, every round
  2. Negative cycles
  3. Floyd-Warshall: every pair at once
  4. Which one to use
  5. Common mistakes
  6. Check yourself
  7. What to practise next

A courier drives one-way legs between four depots: S, A, B and C. Each leg costs fuel. One leg is different: whoever drives from B to A carries a paid parcel, and the pay is 4 more than the fuel. So that leg's cost is −4. The courier starts at S and wants the cheapest way to reach each depot.

Try Dijkstra's rule, "settle the closest place first". S→A costs 1 and S→B costs 2, so A is settled first, at 1, and C gets 1 + 3 = 4. Next B reveals S→B→A, which costs 2 − 4 = −2. But A was already "final", so C ends at 4 when it should be 1. With a negative cost, a place that looks far away can lead somewhere cheaper, so settling the closest first is no longer safe.

The heap code in the Dijkstra lesson never marks a place as settled, so here it would push A again and patch C. On other graphs that patching can snowball into an exponential amount of work, and a negative loop makes it run forever.

Bellman-Ford: relax every edge, every round

Bellman-Ford gives up on choosing a clever order. Each round, it relaxes every edge, in any fixed order: if dist[u] + cost beats dist[v], lower dist[v]. Then it does that again.

Here is why that is enough. After round 1, every cheapest route of one edge is counted. A cheapest route of 2 edges is a 1-edge route plus one more edge, and round 2 relaxes that edge. So after round k, every cheapest route with up to k edges is counted. Without a loop of negative total cost, a cheapest route never visits a place twice, so it has at most V − 1 edges. V − 1 rounds always finish the job.

Bellman-Ford from SUse the arrows or ← → keys
dist[start] = 0, all others ∞repeat V − 1 times:  for each edge (u, v, cost):    if dist[u] + cost < dist[v]:      dist[v] = dist[u] + costfor each edge: if it still improves → negative cycle
  1. edges, in this order
    0A→C 3
    1C→S 1
    2B→A −4
    3S→A 1
    4S→B 2
    31-412S0B∞A∞C∞
    round
    0

    1Start: S costs 0, every other depot is ∞. The edges are checked in the fixed order shown below, every round.

  2. edges, in this order
    0A→C 3
    1C→S 1
    2B→A −4
    3S→A 1
    4S→B 2
    31-412S0B∞A∞C∞
    round
    1

    2Round 1. A→C, C→S and B→A all start at a depot that is still ∞. There is no route to extend, so nothing changes.

  3. edges, in this order
    0A→C 3
    1C→S 1
    2B→A −4
    3S→A 1
    4S→B 2
    31-412S0B∞A1C∞
    round
    1

    3S→A: 0 + 1 = 1, better than ∞. A = 1.

  4. edges, in this order
    0A→C 3
    1C→S 1
    2B→A −4
    3S→A 1
    4S→B 2
    31-412S0B2A1C∞
    round
    1

    4S→B: 0 + 2 = 2. B = 2. Round 1 is over: every route of one edge is now counted.

  5. edges, in this order
    0A→C 3
    1C→S 1
    2B→A −4
    3S→A 1
    4S→B 2
    31-412S0B2A1C4
    round
    2

    5Round 2. A→C: 1 + 3 = 4. C = 4. Then C→S gives 4 + 1 = 5, no better than 0.

  6. edges, in this order
    0A→C 3
    1C→S 1
    2B→A −4
    3S→A 1
    4S→B 2
    31-412S0B2A−2C4
    round
    2

    6B→A: 2 + (−4) = −2, better than 1. A drops to −2: the paid leg makes the longer route cheaper.

  7. edges, in this order
    0A→C 3
    1C→S 1
    2B→A −4
    3S→A 1
    4S→B 2
    31-412S0B2A−2C4
    round
    2

    7S→A gives 1 and S→B gives 2. Neither beats what we have. Round 2 is over, but C still says 4: it was computed from A's old value.

  8. edges, in this order
    0A→C 3
    1C→S 1
    2B→A −4
    3S→A 1
    4S→B 2
    31-412S0B2A−2C1
    round
    3

    8Round 3. A→C: −2 + 3 = 1, better than 4. C = 1. The improvement to A has now flowed one edge further.

  9. edges, in this order
    0A→C 3
    1C→S 1
    2B→A −4
    3S→A 1
    4S→B 2
    31-412S0B2A−2C1
    round
    3

    9The other four edges change nothing. That was round 3 = V − 1, the last one. Every cheapest route uses at most 3 edges, so every distance is now final.

  10. edges, in this order
    0A→C 3
    1C→S 1
    2B→A −4
    3S→A 1
    4S→B 2
    31-412S0B2A−2C1
    edge checks
    20

    10Check pass: one more look at every edge. None improves anything, so there is no negative cycle. Final costs from S: A = −2, B = 2, C = 1. That took 3 rounds × 5 edges = 15 edge checks, plus 5 for the check pass.

1 / 10

Edge order affects speed, not correctness. This order was unlucky: the news about A took a full round to reach C. The code stops early when a round changes nothing.

// edges: list of [from, to, cost], one-way. nodes: list of names.
function bellmanFord(nodes, edges, start) {
  const dist = {};
  for (const node of nodes) dist[node] = Infinity;
  dist[start] = 0;
  for (let round = 1; round < nodes.length; round++) { // V - 1 rounds
    let changed = false;
    for (const [u, v, cost] of edges) {
      if (dist[u] + cost < dist[v]) {
        dist[v] = dist[u] + cost;
        changed = true;
      }
    }
    if (!changed) break; // nothing moved: every distance is final
  }
  for (const [u, v, cost] of edges) {
    if (dist[u] + cost < dist[v]) return null; // still improving: negative cycle
  }
  return dist;
}
// bellmanFord(["S", "A", "B", "C"], edges, "S") → { S: 0, A: -2, B: 2, C: 1 }
import math
 
# edges: list of (from, to, cost), one-way. nodes: list of names.
def bellman_ford(nodes, edges, start):
    dist = {node: math.inf for node in nodes}
    dist[start] = 0
    for _ in range(len(nodes) - 1):  # V - 1 rounds
        changed = False
        for u, v, cost in edges:
            if dist[u] + cost < dist[v]:
                dist[v] = dist[u] + cost
                changed = True
        if not changed:
            break  # nothing moved: every distance is final
    for u, v, cost in edges:
        if dist[u] + cost < dist[v]:
            return None  # still improving: negative cycle
    return dist
 
# bellman_ford(["S", "A", "B", "C"], edges, "S") → {'S': 0, 'A': -2, 'B': 2, 'C': 1}

Negative cycles

Suppose a new leg C→B appears with cost −2. Now B→A→C→B costs −4 + 3 − 2 = −3. Every lap of that loop makes the courier 3 richer, so there is no cheapest route any more. One more lap is always cheaper.

B A C −4 3 −2 one lap = −3
A negative cycle. Each lap around B → A → C → B adds −3, so the cost of reaching any of them has no floor.

Bellman-Ford catches this. Without a negative cycle, everything is final after V − 1 rounds. So the code makes one extra pass: if any edge can still lower a distance, a negative loop is to blame, and the function returns null instead of wrong numbers.

Floyd-Warshall: every pair at once

Now the dispatcher wants the cheapest cost from every depot to every other. You could run Bellman-Ford from each one. Floyd-Warshall does it with three short loops and one table.

The idea is to allow stopovers one depot at a time. Start with a table of direct legs. Then allow S as a stopover: for every pair i, j, check whether i → S → j is cheaper. Then add A, then B, then C. When you allow depot k, the best route from i to j either avoids k, so the table already has it, or goes through k once: the best i → k plus the best k → j, both already in the table.

Floyd-Warshall on the courier mapUse the arrows or ← → keys
dist = direct costs (0 on the diagonal, ∞ if no leg)for k in nodes:   # allow k as a stopover  for i in nodes:    for j in nodes:      dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])
  1. dist (row = from, column = to)
    SABC
    S012∞
    A∞0∞3
    B∞−40∞
    C1∞∞0
    stopovers allowed
    none

    1Start with direct legs only. 0 on the diagonal (staying put is free), ∞ where there is no direct leg.

  2. dist (row = from, column = to)
    SABC
    S012∞
    A∞0∞3
    B∞−40∞
    C1230
    k
    S
    stopovers allowed
    S
    cells improved
    2

    2Allow stopovers at S. Only C has a leg into S, so only row C can improve: C→S→A = 1 + 1 = 2 and C→S→B = 1 + 2 = 3.

  3. dist (row = from, column = to)
    SABC
    S0124
    A∞0∞3
    B∞−40−1
    C1230
    k
    A
    stopovers allowed
    S, A
    cells improved
    2

    3Now S or A may be stopovers. Through A: S→A→C = 1 + 3 = 4 and B→A→C = −4 + 3 = −1. C→A→C would be 2 + 3 = 5, worse than staying put at 0.

  4. dist (row = from, column = to)
    SABC
    S0−221
    A∞0∞3
    B∞−40−1
    C1−130
    k
    B
    stopovers allowed
    S, A, B
    cells improved
    3

    4Add B. S→B→A = 2 − 4 = −2 beats 1. S→B→C = 2 + (−1) = 1 beats 4: that reuses the B→A→C route found in the last step. C→B→A = 3 − 4 = −1 beats 2.

  5. dist (row = from, column = to)
    SABC
    S0−221
    A4063
    B0−40−1
    C1−130
    k
    C
    stopovers allowed
    S, A, B, C
    cells improved
    3

    5Add C, the last one. A→C→S = 3 + 1 = 4, A→C→B = 3 + 3 = 6, B→C→S = −1 + 1 = 0. Every pair has now been allowed every stopover.

  6. dist (row = from, column = to)
    SABC
    S0−221
    A4063
    B0−40−1
    C1−130
    checks
    64

    6Done. Row S matches Bellman-Ford: −2, 2, 1. Every diagonal cell is still 0, so there is no negative cycle. 4 × 4 × 4 = 64 checks gave all 16 answers.

1 / 6
// Places are numbered 0..n-1. edges: list of [from, to, cost], one-way.
function floydWarshall(n, edges) {
  const dist = Array.from({ length: n }, (_, i) =>
    Array.from({ length: n }, (_, j) => (i === j ? 0 : Infinity)));
  for (const [u, v, cost] of edges) dist[u][v] = Math.min(dist[u][v], cost);
  for (let k = 0; k < n; k++) {       // allow k as a stopover
    for (let i = 0; i < n; i++) {
      for (let j = 0; j < n; j++) {
        if (dist[i][k] + dist[k][j] < dist[i][j]) {
          dist[i][j] = dist[i][k] + dist[k][j];
        }
      }
    }
  }
  return dist; // a negative dist[i][i] means i is on a negative cycle
}
import math
 
# Places are numbered 0..n-1. edges: list of (from, to, cost), one-way.
def floyd_warshall(n, edges):
    dist = [[0 if i == j else math.inf for j in range(n)] for i in range(n)]
    for u, v, cost in edges:
        dist[u][v] = min(dist[u][v], cost)
    for k in range(n):          # allow k as a stopover
        for i in range(n):
            for j in range(n):
                if dist[i][k] + dist[k][j] < dist[i][j]:
                    dist[i][j] = dist[i][k] + dist[k][j]
    return dist  # a negative dist[i][i] means i is on a negative cycle

Negative cycles show up for free: a loop from i back to i with a negative total puts a negative number on the diagonal.

Which one to use

ApproachTimeSpaceWhy
BFSO(V + E)O(V)One start, every edge costs the same.
DijkstraO((V + E) log V)O(V + E)One start, no negative costs.
Bellman-FordO(V × E)O(V)One start, negative costs allowed. Up to V − 1 rounds, each touching every edge. Detects negative cycles.
Floyd-WarshallO(V³)O(V²)Every pair. Three nested loops over the places. Fine up to a few hundred places.

Common mistakes

Check yourself

Quick check

A graph has 6 places. What is the largest number of rounds Bellman-Ford needs before the distances are final, if there is no negative cycle?

After V − 1 rounds, Bellman-Ford's check pass finds that an edge can still lower a distance. What does that mean?

You need the cheapest cost between every pair among 200 warehouses, and all costs are positive. Which fits best?

What to practise next

Shortest paths answer "how do I get from here to there cheaply?". The last lesson in this module asks a different question: what is the cheapest set of links that connects every place at all? That is Minimum Spanning Trees, and it brings back Union-Find.