3.5.12 · D2Graphs

Visual walkthrough — Floyd-Warshall — all-pairs shortest paths, O(V³)

2,075 words9 min readBack to topic

Before line one, three plain words you must own:

Our whole goal: for every ordered pair of cities , find the cheapest total to get from to . That cheapest number we will call .


Step 1 — Draw the map and read off the "no-stopping" costs

WHAT. We start with a tiny map: four cities and some one-way arrows with weights. We build a table whose cell in row , column holds the best cost we currently know from to .

WHY. Before we let ourselves get clever with detours, we can only trust what we can see directly: a single arrow, or staying put (cost ). Everything else we don't yet know — we mark it ("unreachable so far"). This honest starting table is the seed the whole algorithm grows from.

PICTURE. In the figure, the map is on the left, the table on the right. Green cells are the diagonal (staying home is free). Blue cells are direct arrows. Gray cells are pairs with no direct arrow yet.


Step 2 — Invent the subscript: "which cities am I allowed to stop at?"

WHAT. We give the subscript on a precise meaning. Define = the cheapest cost from to if the only cities you may stop at along the way are . (Endpoints and don't count as "stops" — you always may start and end where you must.)

WHY. This is the trick that turns a huge problem into a ladder of tiny ones. Instead of solving "best path using anything," we solve a sequence: best path using no stops, then allowing stop , then also , then also … Each rung reuses the rung below. This is the Dynamic Programming idea — see Dynamic Programming — solve a small version, then let the allowed set grow by exactly one.

PICTURE. The figure shows the SAME pair three times. On the left only direct hops are legal (a padlock on every middle city). In the middle, city is unlocked — a new detour becomes possible. On the right, city is unlocked too. The set of "keys you hold" grows left to right.

So (Step 1) allows no stops, and once every city is unlocked we will have the true answer. The whole algorithm is just: turn one padlock at a time.


Step 3 — Unlock ONE new city: the only two things that can happen

WHAT. Suppose we already have (cities unlocked) and now we unlock city . We want . Look at the truly-best route that is now legal. City is either used by that route, or it is not.

WHY. These two cases are exhaustive — there is no third option. So the new best must be the cheaper of the two. That "cheaper of two" is exactly what the symbol means: = whichever of is smaller.

PICTURE. Top path: it ignores — so it was already legal before, cost (orange). Bottom path: it goes through — pictured as (blue) then (green), cost . We keep whichever is shorter.


WHAT. In the detour case we claimed each half ( and ) may itself use only as stops — i.e. each half is a problem we already solved. Why can we say that?

WHY. Because the best detour visits exactly once. If it visited twice, the chunk between the two visits is a loop starting and ending at ; as long as there are no Negative Cycles a loop only adds cost, so deleting it makes the path cheaper — contradicting "best." With visited once, cut the path at that single : the first half never touches again, so its only stops are older cities. Same for the second half. Each half is a value — the recurrence closes on itself perfectly.

PICTURE. Left: a "bad" imagined route that loops back through twice — the red loop is wasted cost. Right: snip the loop out; now is touched once and the two clean halves each live entirely in the older allowed set.


Step 5 — Watch the table fill: a full numeric run

WHAT. Take four cities with arrows , , , . We turn the padlocks and watch cells drop.

WHY. Seeing real numbers relax convinces you the ladder actually works — abstract becomes "5 became 4."

PICTURE. Four side-by-side tables, one per unlock. A cell turns orange the moment it improves. Trace : starts at (direct), and after enough padlocks the route wins.

Term-by-term for the winning update at (unlocking city ):

  • the is the direct arrow ;
  • the is , already found when city was unlocked;
  • the is the arrow ;
  • swaps for . The cell flips orange.

Step 6 — WHY the -loop must be OUTERMOST (the ordering trap)

WHAT. The code is three nested loops. It is tempting to think order doesn't matter. It does: (the padlock) must be the outer loop; and inner.

WHY. The ladder is only valid because, when we unlock , every pair already reflects the full set . If were an inner loop, you'd unlock city for pair but not yet for pair — reading a half-finished table. The subscript ladder would collapse.

PICTURE. Two runs of the same graph. Left: k outermost → the correct final table. Right: k innermost → a red WRONG cell where a value was read before it was ready.

for k in range(n):        # PADLOCK first — unlock one city fully
    for i in range(n):    # every origin...
        for j in range(n):#   every destination...
            if dist[i][k] + dist[k][j] < dist[i][j]:
                dist[i][j] = dist[i][k] + dist[k][j]

Step 7 — The degenerate cases: negatives, the diagonal, and

WHAT. Three edge cases the pictures must cover, or you'll trip on them.

  1. A negative edge (allowed). never inspects the sign of an edge — it only compares totals. So a cheap-but-negative arrow is used automatically. This is why Floyd-Warshall beats running Dijkstra's Algorithm per source (Dijkstra breaks on negatives) and matches Bellman-Ford without its per-source cost.
  2. A negative cycle (forbidden — "shortest" stops meaning anything). Detect it: after the run, if any diagonal cell , then leaving home and returning cost less than zero — a loop that pays you. That can only be a negative cycle.
  3. Unreachable pairs stay . In real code use a large sentinel (not literal infinity) and skip relaxation when either half is the sentinel, so doesn't overflow into a fake shortcut.

WHY. Case 1 is the whole reason to prefer this algorithm; case 2 is its built-in safety alarm; case 3 is the trap that silently corrupts answers in real code.

PICTURE. Left panel: a negative edge making the detour beat the direct . Right panel: a triangle ; the diagonal cell slides to — the red alarm.


The one-picture summary

Everything above is one loop: turn a padlock, then for every pair ask "does the newly-open city shorten this trip?" — repeated until all padlocks are open.

Recall Feynman retelling — the whole walkthrough in plain words

You have a map of one-way roads with times. You want the fastest time between every pair of towns. Start pessimistic: the only trips you trust are single roads, everything else is "no idea" (infinity). Now play a game with a ring of padlocked towns. Open padlock on town : walk through every pair of towns and ask, "would stopping at be faster than what I had?" If yes, write the smaller number. Open padlock on town and ask the same for every pair — and crucially, town 's check may reuse the improvement town already gave you, because you finished all of town first (that's why the padlock loop is outermost). Keep opening padlocks one town at a time. A detour through a newly-opened town splits neatly into "best way in" plus "best way out," both already computed — so you never solve anything twice. Negative roads are fine — you only ever compare totals. But if any town can leave home and come back for less than zero, its diagonal goes negative and that's your alarm: a negative cycle, no answer exists. When the last padlock opens, every cell already holds the true fastest time.

Why is the detour split into two previously-solved subproblems?
The best path visits once (revisiting is a non-negative loop), so cutting at leaves two halves that use only older cities — each is a value.
What flips a cell orange in Step 5?
A relaxation succeeds and overwrites the cell with the smaller total.
Why does handle negative edges automatically?
compares path totals, never edge signs, so a cheaper negative-weight route is chosen with no special case.