3.5.12Graphs

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

1,958 words9 min readdifficulty · medium2 backlinks

WHAT is the problem?

The output is a full V×VV \times V matrix of distances (and optionally a matrix to reconstruct paths).


WHY a new algorithm (and not just run Dijkstra V times)?

  • Running Dijkstra from every source: O(V(E+VlogV))O(V \cdot (E + V\log V)) — but breaks with negative edges.
  • Running Bellman-Ford from every source: O(VVE)=O(V2E)O(V \cdot VE) = O(V^2 E), which for dense graphs (EV2E \approx V^2) is O(V4)O(V^4).
  • Floyd-Warshall: a clean O(V3)O(V^3), handles negative edges, and is 5 lines of code. For dense graphs and small VV (say V400V \le 400) it's the king.

HOW: derive it from scratch (dynamic programming)

Let's define a subproblem carefully — this is the heart of it.

Base case (k=0k=0): no intermediate vertices allowed, so the only legal paths are single edges. d0(i,j)={0i=jw(i,j)(i,j)E+otherwised_0(i,j) = \begin{cases} 0 & i = j \\ w(i,j) & (i,j)\in E \\ +\infty & \text{otherwise}\end{cases}

Transition — the key insight. Consider the shortest path from ii to jj using intermediates {1,,k}\{1,\dots,k\}. Vertex kk is either used by this path or not:

  • kk is NOT used → the path only uses {1,,k1}\{1,\dots,k-1\}, so its length is dk1(i,j)d_{k-1}(i,j).
  • kk IS used → because there are no negative cycles, the optimal path visits kk exactly once. Split it at kk: a piece iki \to k and a piece kjk \to j, each using only {1,,k1}\{1,\dots,k-1\} as intermediates. So its length is dk1(i,k)+dk1(k,j)d_{k-1}(i,k) + d_{k-1}(k,j).

We take the better of the two:

Collapsing to a 2D array (the in-place trick)

The recurrence updates layer kk from layer k1k-1. We can overwrite the same matrix dd. Is that safe? When computing d[i][j]d[i][j] using d[i][k]+d[k][j]d[i][k]+d[k][j]:

  • d[i][k]d[i][k] in the new layer = min(d[i][k],d[i][k]+d[k][k])\min(d[i][k], d[i][k]+d[k][k]). Since d[k][k]=0d[k][k]=0 (no negative cycle), it's unchanged.
  • Same for d[k][j]d[k][j].

So overwriting does not corrupt the values we read. Hence one V×VV\times V matrix suffices.

def floyd_warshall(n, dist):       # dist[i][j] = weight or INF, dist[i][i]=0
    for k in range(n):             # k = intermediate vertex being "unlocked"
        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

Detecting negative cycles

After running, check the diagonal. If any d(i,i)<0d(i,i) < 0, then ii lies on a negative cycle (you found a path from ii back to ii cheaper than staying put).


Path reconstruction

Keep a next[i][j] (or parent[i][j]) matrix. Initialize next[i][j] = j for each edge. Whenever you relax through kk, set next[i][j] = next[i][k]. To rebuild iji\to j: follow next until you reach jj.


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

Worked Example 1 — a tiny 3-node graph

Vertices 0,1,20,1,2. Edges: 01=40\to1 = 4, 02=110\to2 = 11, 12=21\to2 = 2. No others.

Initial matrix (\infty shown as \cdot):

from\to 0 1 2
0 0 4 11
1 · 0 2
2 · · 0

Unlock k=0k=0: any path improved by going through 0? Nothing points into 0, so no change. Why this step? d[i][0]=d[i][0]=\infty for i0i\ne 0, so d[i][0]+d[0][j]d[i][0]+d[0][j] is huge.

Unlock k=1k=1: check d[0][2]d[0][2] vs d[0][1]+d[1][2]=4+2=6<11d[0][1]+d[1][2] = 4+2 = 6 < 11. Update d[0][2]=6d[0][2]=6. Why this step? Now route 0120\to1\to2 is legal because 1 is unlocked.

Unlock k=2k=2: 2 has no outgoing edges, no improvements.

Final d[0][2]=6d[0][2]=6. ✔


Worked Example 2 — negative edge (where Dijkstra fails)

Vertices 0,1,20,1,2. Edges: 01=40\to1=4, 02=50\to2=5, 12=31\to2=-3.

Initial: d[0][1]=4, d[0][2]=5, d[1][2]=3d[0][1]=4,\ d[0][2]=5,\ d[1][2]=-3.

k=1k=1: d[0][2]d[0][2] vs d[0][1]+d[1][2]=4+(3)=1<5d[0][1]+d[1][2]=4+(-3)=1 < 5 → update d[0][2]=1d[0][2]=1. Why this step? The negative edge 121\to2 makes the detour through 1 cheaper — Floyd-Warshall handles it naturally since min doesn't care about edge sign (as long as no negative cycle).

Final d[0][2]=1d[0][2]=1. Dijkstra would have wrongly committed to 55.


Worked Example 3 — negative cycle detection

Edges: 01=1, 12=1, 20=10\to1=1,\ 1\to2=-1,\ 2\to0=-1. Cycle total =111=1<0=1-1-1=-1<0.

After running, d[0][0]d[0][0] relaxes to 1+(1)+(1)=1<01+(-1)+(-1) = -1 < 0. The diagonal goes negative → negative cycle detected. Why this step? The diagonal d[i][i]d[i][i] starts at 0; only a negative loop back to ii can push it below 0.


Recall Feynman: explain to a 12-year-old

Imagine cities connected by roads with travel times. You want the fastest time between every pair of cities. You play a game: "What if I'm now allowed to stop at City A on the way?" You recheck every pair: maybe going through A is faster. Then you unlock City B too, recheck everyone again. Then City C... After you've unlocked all cities, every pair already has its fastest time. That's it — you just kept asking "would stopping at this new city help?" for one city at a time.


Active Recall

What does the DP state dk(i,j)d_k(i,j) represent in Floyd-Warshall?
Shortest path from ii to jj using only vertices {1,,k}\{1,\dots,k\} as intermediates (endpoints always allowed).
What is the Floyd-Warshall recurrence?
dk(i,j)=min(dk1(i,j), dk1(i,k)+dk1(k,j))d_k(i,j)=\min(d_{k-1}(i,j),\ d_{k-1}(i,k)+d_{k-1}(k,j)).
What is the time and space complexity of Floyd-Warshall?
O(V3)O(V^3) time, O(V2)O(V^2) space.
Why must the kk loop be outermost?
The DP recurses on kk; all pairs must finish accounting for {1,,k}\{1,\dots,k\} before unlocking k+1k{+}1, else you read half-updated values.
How do you detect a negative cycle with Floyd-Warshall?
After running, if any d(i,i)<0d(i,i)<0 then ii lies on a negative cycle.
Can Floyd-Warshall handle negative edge weights?
Yes, as long as there are no negative cycles. (Dijkstra cannot handle negative edges.)
Why is the in-place single-matrix update correct?
Reading d[i][k]d[i][k] and d[k][j]d[k][j] is safe because d[k][k]=0d[k][k]=0 means those entries don't change in iteration kk.
What is the base case (k=0k=0) matrix?
00 on diagonal, edge weight w(i,j)w(i,j) if edge exists, else ++\infty.
How do you reconstruct paths in Floyd-Warshall?
Keep next[i][j]; init to jj for edges; on relaxation through kk set next[i][j]=next[i][k]; follow next to rebuild.
When is Floyd-Warshall better than running Dijkstra V times?
Dense graphs, small VV, or graphs with negative edges; cleaner O(V3)O(V^3) vs O(V2E)O(V^2 E) for dense.

Connections

  • Dijkstra's Algorithm — single-source, non-negative weights only
  • Bellman-Ford — single-source, handles negatives, detects negative cycles
  • Dynamic Programming — Floyd-Warshall is layered DP over intermediate sets
  • Johnson's Algorithm — APSP for sparse graphs, reweights then runs Dijkstra
  • Transitive Closure — same loop structure with OR instead of min (Warshall's algorithm)
  • Negative Cycles — detection via the diagonal

Concept Map

solved by

breaks on

slow O V^4 dense

handled by

based on

uses intermediates 1..k

starts from

recurrence

k not used

k used

valid because

answer at k=V

space saved by

APSP problem

Floyd-Warshall

Dijkstra x V

negative edges

Bellman-Ford x V

DP state d_k i,j

allow vertex set

base case k=0 edges only

min of skip k or split at k

d_k-1 i,j

d_k-1 i,k + d_k-1 k,j

no negative cycle

V x V distance matrix

in-place 2D overwrite

Hinglish (regional understanding)

Intuition Hinglish mein samjho

Floyd-Warshall ka core idea bahut simple hai: tum har pair of cities ke beech ka shortest distance ek saath nikalna chahte ho. Tum ek game khelte ho — "Agar main beech mein City kk pe ruk sakta hoon, toh kya koi route sasta ho jata hai?" Tum ek-ek karke har city ko "unlock" karte ho, aur har baar saare pairs ko recheck karte ho: d[i][j] = min(d[i][j], d[i][k] + d[k][j]). Jab saari cities unlock ho jaati hain, har pair ke paas already best answer hota hai.

Sabse important baat: k ka loop hamesha sabse bahar (outermost) hona chahiye. Yeh isliye kyunki yeh ek dynamic programming hai jo kk pe recurse karti hai — pehle ek intermediate city ke liye saare pairs settle ho, tab agli city unlock karo. Agar tum k ko andar daal d-oge toh aadhe-adhure values padhoge aur galat answer aayega. Yaad rakhne ka trick: K-I-J, "Kings Inspect Journeys".

Floyd-Warshall ki khaasiyat yeh hai ki yeh negative edges ko handle kar leta hai (Dijkstra nahi kar sakta), bas negative cycle nahi hona chahiye. Aur negative cycle pakadna bhi easy hai — algorithm chalne ke baad agar koi d[i][i]<0d[i][i] < 0 hai, matlab us vertex se ghoom ke wapas aane ka cost negative hai, yani negative cycle hai. Complexity O(V3)O(V^3) hai, code sirf 4-5 lines, isliye dense graphs aur chhote VV (jaise V400V \le 400) ke liye yeh perfect hai — competitive programming mein bahut kaam aata hai.

Go deeper — visual, from zero

Test yourself — Graphs

Connections