Data Structures & Algorithms (DSA) Module 12 – Shortest paths, spanning trees and flows
All-pairs shortest paths: Floyd-Warshall
How Floyd-Warshall fills the distance table of every pair in O(V³) by allowing one more intermediate vertex per round, and how it flags negative cycles.
What you will learn
- Implement Floyd-Warshall in O(V³) time and O(V²) memory
- Detect negative cycles from the diagonal of the table
- Compute reachability with Warshall's transitive closure
- Choose between Floyd-Warshall, Dijkstra from every vertex and Johnson's algorithm
Before you start
What you’ll learn · Before you start
What you will learn
- Implement Floyd-Warshall in O(V³) time and O(V²) memory
- Detect negative cycles from the diagonal of the table
- Compute reachability with Warshall's transitive closure
- Choose between Floyd-Warshall, Dijkstra from every vertex and Johnson's algorithm
Before you start
On this page
The all-pairs shortest-path problem asks for the shortest distance between every ordered pair of vertices, a whole
table instead of one row. Floyd-Warshall fills that table with one short triple loop. It starts from the direct edges
and then, for k = 0, 1, …, V − 1, allows vertex k as an extra stop on every path: dist[i][j] becomes
dist[i][k] + dist[k][j] whenever that is shorter. After the last round every path may stop anywhere, so every entry
is a shortest distance. It takes O(V³) time and O(V²) memory, works with negative edges, and reports a negative cycle
as a negative entry on the diagonal. Robert Floyd published the shortest-path version in 1962, and Stephen Warshall
published the same loop over true and false that year, for reachability. On sparse graphs, Dijkstra’s algorithm from
every vertex, or Johnson’s algorithm when some weights are negative, does the same job faster.
One more allowed stop per round
Number the vertices 0 to V − 1 and keep a V × V table:
- Start with 0 on the diagonal, the weight of the cheapest direct edge from i to j where there is one, and infinity everywhere else.
- For each k in turn, the outer loop: for every pair (i, j), if
dist[i][k] + dist[k][j] < dist[i][j], store the smaller value.
After round k, dist[i][j] is the shortest distance among the paths whose intermediate stops all lie among vertices 0
to k. Round k only asks whether going through k helps, given the best paths that avoid every vertex after k. The loop
order is the whole algorithm: k must be the outer loop.
A latency table for six data centres
Six data centres and their one-way links
Text description of the diagram
Each arrow is a one-way link, labelled with its latency in milliseconds.
- A ring of links: A to B 4, B to C 3, C to D 2, D to E 5, E to F 1 and F to A 7.
- Six more links: A to D 12, B to E 9, C to F 8, E to B 2, D to A 6 and F to C 3.
- The thick links are the fastest route from A to F: A to B, B to E and E to F, 4 + 9 + 1 = 14 milliseconds. No pair of centres is further apart, so A to F is the slowest pair in the finished table.
# Floyd-Warshall on six made-up data centres, A to F, with one-way network latencies in milliseconds. The table is
# printed after each k: round k lets paths pass through centre k, and a * marks every entry that round improved.
import math
names = "ABCDEF"
links = [("A", "B", 4), ("B", "C", 3), ("C", "D", 2), ("D", "E", 5), ("E", "F", 1), ("F", "A", 7),
("A", "D", 12), ("B", "E", 9), ("C", "F", 8), ("E", "B", 2), ("D", "A", 6), ("F", "C", 3)]
n = len(names)
dist = [[0 if i == j else math.inf for j in range(n)] for i in range(n)]
for a, b, ms in links:
dist[names.index(a)][names.index(b)] = ms
def show(title, changed=frozenset()):
print(title)
print(" " + "".join(f"{c:>5}" for c in names))
for i in range(n):
cells = []
for j in range(n):
text = "-" if dist[i][j] == math.inf else str(dist[i][j])
cells.append(f"{text + ('*' if (i, j) in changed else ''):>5}")
print(f" {names[i]} " + "".join(cells))
show("direct links only (- means no link):")
for k in range(n):
changed = set()
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]
changed.add((i, j))
show(f"after k = {names[k]}: paths may also pass through {names[: k + 1]} ({len(changed)} improved)", changed) Output
direct links only (- means no link):
A B C D E F
A 0 4 - 12 - -
B - 0 3 - 9 -
C - - 0 2 - 8
D 6 - - 0 5 -
E - 2 - - 0 1
F 7 - 3 - - 0
after k = A: paths may also pass through A (3 improved)
A B C D E F
A 0 4 - 12 - -
B - 0 3 - 9 -
C - - 0 2 - 8
D 6 10* - 0 5 -
E - 2 - - 0 1
F 7 11* 3 19* - 0
after k = B: paths may also pass through AB (5 improved)
A B C D E F
A 0 4 7* 12 13* -
B - 0 3 - 9 -
C - - 0 2 - 8
D 6 10 13* 0 5 -
E - 2 5* - 0 1
F 7 11 3 19 20* 0
after k = C: paths may also pass through ABC (7 improved)
A B C D E F
A 0 4 7 9* 13 15*
B - 0 3 5* 9 11*
C - - 0 2 - 8
D 6 10 13 0 5 21*
E - 2 5 7* 0 1
F 7 11 3 5* 20 0
after k = D: paths may also pass through ABCD (6 improved)
A B C D E F
A 0 4 7 9 13 15
B 11* 0 3 5 9 11
C 8* 12* 0 2 7* 8
D 6 10 13 0 5 21
E 13* 2 5 7 0 1
F 7 11 3 5 10* 0
after k = E: paths may also pass through ABCDE (6 improved)
A B C D E F
A 0 4 7 9 13 14*
B 11 0 3 5 9 10*
C 8 9* 0 2 7 8
D 6 7* 10* 0 5 6*
E 13 2 5 7 0 1
F 7 11 3 5 10 0
after k = F: paths may also pass through ABCDEF (4 improved)
A B C D E F
A 0 4 7 9 13 14
B 11 0 3 5 9 10
C 8 9 0 2 7 8
D 6 7 9* 0 5 6
E 8* 2 4* 6* 0 1
F 7 11 3 5 10 0
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 table_after_k.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
The table started with the 12 direct links and 18 gaps. Allowing A as a stop filled 3 entries, such as D to B through A in 10 ms. Allowing B gave A to C in 7, and allowing C shortened A to D from the direct 12 to 9, through B and C. Some entries improved twice: E to A was 13 after round D and 8 after round F, through F. After round F every gap was filled, and the slowest pair, A to F, takes 14 ms.
The code and its cost
# Floyd-Warshall: k is the outer loop, so round k lets every path also pass through vertex k. O(V^3) time and
# O(V^2) memory; negative edges are fine, and a negative cycle shows up as a negative entry on the diagonal.
import math
def floyd_warshall(n, edges):
"""Edges (u, v, w) on vertices 0..n-1. Returns the distance table, or None if the graph has a negative cycle."""
dist = [[0 if i == j else math.inf for j in range(n)] for i in range(n)]
for u, v, w in edges:
dist[u][v] = min(dist[u][v], w) # keep the cheapest of parallel edges
for k in range(n):
row_k = dist[k]
for i in range(n):
d_ik = dist[i][k]
if d_ik == math.inf:
continue # i cannot reach k: nothing to gain through k
row_i = dist[i]
for j in range(n):
if d_ik + row_k[j] < row_i[j]:
row_i[j] = d_ik + row_k[j]
if any(dist[i][i] < 0 for i in range(n)):
return None
return dist
names = "ABCDEF"
links = [("A", "B", 4), ("B", "C", 3), ("C", "D", 2), ("D", "E", 5), ("E", "F", 1), ("F", "A", 7),
("A", "D", 12), ("B", "E", 9), ("C", "F", 8), ("E", "B", 2), ("D", "A", 6), ("F", "C", 3)]
table = floyd_warshall(6, [(names.index(a), names.index(b), ms) for a, b, ms in links])
print("from " + " ".join(f"{c:>3}" for c in names))
for i, row in enumerate(table):
print(f" {names[i]} " + " ".join(f"{d:>3}" for d in row))
worst = max((table[i][j], names[i], names[j]) for i in range(6) for j in range(6))
print(f"the slowest pair: {worst[1]} to {worst[2]}, {worst[0]} ms") Output
from A B C D E F A 0 4 7 9 13 14 B 11 0 3 5 9 10 C 8 9 0 2 7 8 D 6 7 9 0 5 6 E 8 2 4 6 0 1 F 7 11 3 5 10 0 the slowest pair: A to F, 14 ms
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 floyd_warshall.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
The three loops run V³ times: 6.4 × 10⁷ steps for V = 400 and 10⁹ for V = 1,000. The table holds V² numbers, which is
800 MB of 8-byte integers for V = 10⁴, before any per-entry overhead of the language. The if d_ik == inf test skips
rows that cannot reach k, which saves work on sparse graphs without changing the answer. A second table of next hops,
filled in the same loop, rebuilds the route of every pair.
Negative edges and the diagonal
# Negative edges in Floyd-Warshall: fine on their own, but a negative cycle makes the diagonal negative, because a
# vertex on it can reach itself for less than nothing.
import math
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, w in edges:
dist[u][v] = min(dist[u][v], w)
for k in range(n):
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
names = "PQRS"
for label, s_to_q in (("S -> Q costs 1, so the cycle Q, R, S weighs +1", 1), ("S -> Q costs -1, so the cycle Q, R, S weighs -1", -1)):
edges = [(0, 1, 3), (1, 2, -2), (2, 3, 2), (3, 1, s_to_q)]
dist = floyd_warshall(4, edges)
print(f"{label}:")
print(" P to S:", dist[0][3], " diagonal:", [dist[i][i] for i in range(4)]) Output
S -> Q costs 1, so the cycle Q, R, S weighs +1: P to S: 3 diagonal: [0, 0, 0, 0] S -> Q costs -1, so the cycle Q, R, S weighs -1: P to S: 2 diagonal: [0, -1, -1, -2]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 diagonal.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
With the cycle Q, R, S weighing +1, the negative edge Q → R simply made P to S cost 3 and every diagonal entry stayed 0.
Once the cycle weighed −1, each of its vertices could reach itself for less than nothing, so its diagonal entries
turned negative. That is the test: after the last round, the graph has a negative cycle exactly when some
dist[i][i] is below 0, and then the other entries cannot be trusted as distances.
Choosing an all-pairs method
| Method | Time | Use it for |
|---|---|---|
| Floyd-Warshall | O(V³) | Dense graphs, or V up to a few hundred; negative edges allowed, negative cycles found |
| Dijkstra from every vertex | O(V (V + E) log V) | Sparse graphs with no negative weight |
| Johnson’s algorithm | O(VE log V) | Sparse graphs with negative edges; negative cycles found |
| BFS from every vertex | O(V (V + E)) | Graphs without weights |
Johnson’s algorithm reweights the edges with potentials from one Bellman-Ford run, so that Dijkstra’s algorithm can then run from every vertex. Warshall’s transitive closure is the same triple loop over true and false, and it answers which vertex can reach which.
Exercise and self-check
Return the table of all shortest distances, or a cycle alarm. The largest sample test has 120 vertices.
Exercise · Medium · Python, JavaScript
The table of all shortest distances, or a cycle alarm
A directed graph has vertices 0 to n - 1 and edges (u, v, w) (JavaScript: three-element arrays) whose weights may be negative; there may be several edges between the same two vertices, and loops from a vertex to itself. Write all_pairs(n, edges) (JavaScript: allPairs(n, edges)), which returns the n × n table of shortest distances as a list of rows, where table[i][j] is the shortest distance from i to j, 0 on the diagonal, and None (JavaScript: null) when j cannot be reached from i.
If the graph contains a cycle of negative total weight anywhere, some pairs have no shortest distance: return None (JavaScript: null) instead of the table. The tests include the lesson's six data centres, negative edges without a negative cycle, a negative loop on one vertex, 300 small random graphs checked against every path and every cycle, and graphs of 120 vertices whose distances are known in advance, with and without one negative cycle.
Python · Starter code · all_pairs.py
def all_pairs(n, edges):
"""Edges (u, v, w), any sign. The table of shortest distances (None if unreachable), or None if a negative cycle exists."""
raise NotImplementedError("write all_pairs") The sample tests · test_all_pairs.py
import random
from all_pairs import all_pairs
DATA_CENTRES = [(0, 1, 4), (1, 2, 3), (2, 3, 2), (3, 4, 5), (4, 5, 1), (5, 0, 7), (0, 3, 12), (1, 4, 9), (2, 5, 8),
(4, 1, 2), (3, 0, 6), (5, 2, 3)]
def by_every_path(n, edges):
"""The slow answer: None if any cycle is negative, else the cheapest simple path for every pair."""
out = [[] for _ in range(n)]
for u, v, w in edges:
out[u].append((v, w))
table = [[0 if i == j else None for j in range(n)] for i in range(n)]
for s in range(n):
stack = [(s, 0, {s})]
while stack:
u, total, seen = stack.pop()
for v, w in out[u]:
if v == s and total + w < 0:
return None # a negative cycle through s
if v not in seen:
if table[s][v] is None or total + w < table[s][v]:
table[s][v] = total + w
stack.append((v, total + w, seen | {v}))
return table
def test_data_centres():
"""the lesson's six data centres"""
assert all_pairs(6, DATA_CENTRES) == [
[0, 4, 7, 9, 13, 14], [11, 0, 3, 5, 9, 10], [8, 9, 0, 2, 7, 8],
[6, 7, 9, 0, 5, 6], [8, 2, 4, 6, 0, 1], [7, 11, 3, 5, 10, 0],
]
def test_negative_edges_without_a_negative_cycle():
"""Q, R, S form a cycle of +1 with a negative edge in it; nothing leads back to P"""
edges = [(0, 1, 3), (1, 2, -2), (2, 3, 2), (3, 1, 1)]
assert all_pairs(4, edges) == [[0, 3, 1, 3], [None, 0, -2, 0], [None, 3, 0, 2], [None, 1, -1, 0]]
def test_negative_cycle():
"""S -> Q of -1 makes the cycle Q, R, S weigh -1"""
assert all_pairs(4, [(0, 1, 3), (1, 2, -2), (2, 3, 2), (3, 1, -1)]) is None
def test_negative_loop():
"""a loop of -1 on vertex 1 is a negative cycle, even though nothing else is wrong"""
assert all_pairs(3, [(0, 1, 2), (1, 2, 2), (1, 1, -1)]) is None
def test_parallel_edges_and_a_positive_loop():
"""two edges from 0 to 1 (5 and 2) and a loop of +4 on vertex 1"""
assert all_pairs(2, [(0, 1, 5), (0, 1, 2), (1, 1, 4), (1, 0, 1)]) == [[0, 2], [1, 0]]
def test_random_graphs():
"""300 small random graphs with weights from -3 to 9, against every path and every cycle"""
rng = random.Random(23)
for _ in range(300):
n = rng.randint(1, 5)
edges = [(rng.randrange(n), rng.randrange(n), rng.randint(-3, 9)) for _ in range(rng.randint(0, 9))]
assert all_pairs(n, edges) == by_every_path(n, edges), (n, edges)
def line_graph(rng, connected, isolated):
"""Vertices on a line, each edge as long as the gap it spans, then potentials: every distance is known."""
n = connected + isolated
labels = list(range(n))
rng.shuffle(labels)
on_line, alone = labels[:connected], labels[connected:]
x = dict(zip(on_line, sorted(rng.sample(range(100_000), connected))))
edges = []
for a, b in zip(on_line, on_line[1:]):
edges += [(a, b, x[b] - x[a]), (b, a, x[b] - x[a])]
for _ in range(1_000):
a, b = rng.choice(on_line), rng.choice(on_line)
edges.append((a, b, abs(x[a] - x[b]) + rng.randint(0, 50)))
p = {v: rng.randint(-5_000, 5_000) for v in range(n)}
edges = [(u, v, w + p[u] - p[v]) for u, v, w in edges]
table = [[0 if i == j else None for j in range(n)] for i in range(n)]
for a in on_line:
for b in on_line:
if a != b:
table[a][b] = abs(x[a] - x[b]) + p[a] - p[b]
return edges, table, on_line
def test_large_graph():
"""120 vertices, about 1,200 edges, many of them negative, and 5 vertices with no edges at all"""
rng = random.Random(29)
edges, table, _ = line_graph(rng, 115, 5)
rng.shuffle(edges)
assert all_pairs(120, edges) == table
def test_large_graph_with_one_negative_cycle():
"""the same kind of graph, plus one edge that closes a cycle of weight -1"""
rng = random.Random(31)
edges, table, on_line = line_graph(rng, 115, 5)
a, b = on_line[3], on_line[90]
edges.append((b, a, -table[a][b] - 1)) # a reaches b at table[a][b], so a -> ... -> b -> a weighs -1
rng.shuffle(edges)
assert all_pairs(120, edges) is None JavaScript · Starter code · all_pairs.mjs
/** Edges [u, v, w], any sign. The table of shortest distances (null if unreachable), or null if a negative cycle exists. */
export function allPairs(n, edges) {
throw new Error('write allPairs');
} The sample tests · all_pairs.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { allPairs } from './all_pairs.mjs';
const DATA_CENTRES = [[0, 1, 4], [1, 2, 3], [2, 3, 2], [3, 4, 5], [4, 5, 1], [5, 0, 7], [0, 3, 12], [1, 4, 9], [2, 5, 8],
[4, 1, 2], [3, 0, 6], [5, 2, 3]];
function byEveryPath(n, edges) {
const out = Array.from({ length: n }, () => []);
for (const [u, v, w] of edges) out[u].push([v, w]);
const table = Array.from({ length: n }, (_, i) => Array.from({ length: n }, (_, j) => (i === j ? 0 : null)));
for (let s = 0; s < n; s++) {
const stack = [[s, 0, new Set([s])]];
while (stack.length) {
const [u, total, seen] = stack.pop();
for (const [v, w] of out[u]) {
if (v === s && total + w < 0) return null;
if (seen.has(v)) continue;
if (table[s][v] === null || total + w < table[s][v]) table[s][v] = total + w;
stack.push([v, total + w, new Set([...seen, v])]);
}
}
}
return table;
}
function seeded(seed) {
return () => (seed = (seed * 48271) % 2147483647) / 2147483647;
}
const below = (random, k) => Math.floor(random() * k);
const between = (random, lo, hi) => lo + below(random, hi - lo + 1);
function shuffle(random, a) {
for (let i = a.length - 1; i > 0; i--) {
const j = below(random, i + 1);
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}
function lineGraph(random, connected, isolated) {
const n = connected + isolated;
const labels = shuffle(random, Array.from({ length: n }, (_, i) => i));
const onLine = labels.slice(0, connected);
const spots = new Set();
while (spots.size < connected) spots.add(below(random, 100000));
const sorted = [...spots].sort((a, b) => a - b);
const x = new Map(onLine.map((v, i) => [v, sorted[i]]));
const raw = [];
for (let i = 0; i + 1 < onLine.length; i++) {
const [a, b] = [onLine[i], onLine[i + 1]];
raw.push([a, b, x.get(b) - x.get(a)], [b, a, x.get(b) - x.get(a)]);
}
for (let k = 0; k < 1000; k++) {
const a = onLine[below(random, onLine.length)];
const b = onLine[below(random, onLine.length)];
raw.push([a, b, Math.abs(x.get(a) - x.get(b)) + between(random, 0, 50)]);
}
const p = Array.from({ length: n }, () => between(random, -5000, 5000));
const edges = raw.map(([u, v, w]) => [u, v, w + p[u] - p[v]]);
const table = Array.from({ length: n }, (_, i) => Array.from({ length: n }, (_, j) => (i === j ? 0 : null)));
for (const a of onLine) for (const b of onLine) if (a !== b) table[a][b] = Math.abs(x.get(a) - x.get(b)) + p[a] - p[b];
return { edges, table, onLine };
}
test("the lesson's six data centres", () => {
assert.deepEqual(allPairs(6, DATA_CENTRES), [
[0, 4, 7, 9, 13, 14], [11, 0, 3, 5, 9, 10], [8, 9, 0, 2, 7, 8],
[6, 7, 9, 0, 5, 6], [8, 2, 4, 6, 0, 1], [7, 11, 3, 5, 10, 0],
]);
});
test('Q, R, S form a cycle of +1 with a negative edge in it; nothing leads back to P', () => {
const edges = [[0, 1, 3], [1, 2, -2], [2, 3, 2], [3, 1, 1]];
assert.deepEqual(allPairs(4, edges), [[0, 3, 1, 3], [null, 0, -2, 0], [null, 3, 0, 2], [null, 1, -1, 0]]);
});
test('S -> Q of -1 makes the cycle Q, R, S weigh -1', () => {
assert.equal(allPairs(4, [[0, 1, 3], [1, 2, -2], [2, 3, 2], [3, 1, -1]]), null);
});
test('a loop of -1 on vertex 1 is a negative cycle, even though nothing else is wrong', () => {
assert.equal(allPairs(3, [[0, 1, 2], [1, 2, 2], [1, 1, -1]]), null);
});
test('two edges from 0 to 1 (5 and 2) and a loop of +4 on vertex 1', () => {
assert.deepEqual(allPairs(2, [[0, 1, 5], [0, 1, 2], [1, 1, 4], [1, 0, 1]]), [[0, 2], [1, 0]]);
});
test('300 small random graphs with weights from -3 to 9, against every path and every cycle', () => {
const random = seeded(23);
for (let k = 0; k < 300; k++) {
const n = between(random, 1, 5);
const m = below(random, 10);
const edges = Array.from({ length: m }, () => [below(random, n), below(random, n), between(random, -3, 9)]);
assert.deepEqual(allPairs(n, edges), byEveryPath(n, edges), JSON.stringify([n, edges]));
}
});
test('120 vertices, about 1,200 edges, many of them negative, and 5 vertices with no edges at all', () => {
const random = seeded(29);
const { edges, table } = lineGraph(random, 115, 5);
shuffle(random, edges);
assert.deepEqual(allPairs(120, edges), table);
});
test('the same kind of graph, plus one edge that closes a cycle of weight -1', () => {
const random = seeded(31);
const { edges, table, onLine } = lineGraph(random, 115, 5);
const [a, b] = [onLine[3], onLine[90]];
edges.push([b, a, -table[a][b] - 1]);
shuffle(random, edges);
assert.equal(allPairs(120, edges), null);
}); A hint
Start the table with 0 on the diagonal, the cheapest direct edge where there is one and infinity elsewhere. Then let k run over all vertices in the outer loop and improve table[i][j] through k for every pair. At the end, a negative entry on the diagonal means a negative cycle; otherwise turn infinity into None.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide, QuickJS): nothing is sent to mysmartcopilot.com. The first run of each language downloads it: Python (about 13.5 MB) or JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Check yourself
4 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
Why k must be the outer loop, and what negative cycles do to the numbers Pro: opens with a Pro, Premium or Ultimate pass
The loop order of Floyd-Warshall is not a detail: an invariant over k proves the algorithm, and the order that looks equivalent breaks it. This part proves the invariant, rebuilds routes from a next-hop table, measures the wrong order on random graphs, and reproduces the exponentially large numbers that a negative cycle creates.
In this part:
- The invariant over k, proved
- Routes from a next-hop table, and the wrong loop order, measured
- Exponentially large entries on a negative cycle, reproduced
2 runnable examples (Python) · about 2 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Reachability, Johnson's reweighting and the size limit, measured Pro: opens with a Pro, Premium or Ultimate pass
The same triple loop answers reachability, Johnson’s reweighting beats it on sparse graphs, and Python itself sets a practical limit on V. This part computes a dependency closure with bitsets, works Johnson’s algorithm through on five vertices, times Floyd-Warshall up to V = 500, and compares the three methods with numbers.
In this part:
- Warshall’s transitive closure on a package graph, with bitsets
- Johnson’s reweighting, worked on five vertices
- How large V can be in Python, timed
3 runnable examples (Python) · about 3 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Floyd-Warshall in five languages, and a practice set Pro: opens with a Pro, Premium or Ultimate pass
The triple loop is the same in every language, but infinity is not, and that is where implementations go wrong. This part runs the data-centre table in Python, JavaScript, Java, C++ and Go with recorded output, explains the guard that keeps infinity from overflowing, and ends with a practice set with answers.
In this part:
- Floyd-Warshall in Python, JavaScript, Java, C++ and Go
- The sentinel and the 32-bit trap, with numbers
- Practice set
2 runnable examples (Build and run all three, C++, Go, Java, JavaScript, Python) · about 3 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Interview questions
-
Fresher to mid · Warm-up
When is Floyd-Warshall a better choice than running Dijkstra's algorithm from every vertex?
Floyd-Warshall costs V³ steps whatever the number of edges, while Dijkstra's algorithm from every vertex costs about V (V + E) log V. On a dense graph, where E is close to V², the second is about V³ log V, so Floyd-Warshall wins, and its three short loops have small constant factors. It also accepts negative edge weights and shows a negative cycle on its diagonal, which repeated Dijkstra cannot do. Dijkstra from every vertex wins on sparse graphs: with V = 10⁴ and E = 5 × 10⁴ it needs about 7 × 10⁹ steps against 10¹² for Floyd-Warshall, and it never has to hold the whole V × V table at once. On a sparse graph with negative weights, Johnson's algorithm reweights the edges so that the Dijkstra runs become possible.
-
Mid to senior
A logistics company has 300 hubs and 6,000 one-way lanes with costs; some lanes carry a credit for return loads, so their cost is negative. Every night it needs the cheapest cost between every pair of hubs. Which method do you use, how long will it take, and how do you catch bad data?
-
Senior to staff
You keep an all-pairs table of travel times for 2,000 junctions. A new one-way road opens from u to v with travel time w. How do you update the table without running Floyd-Warshall again, what does it cost, and what changes if a road closes instead?
-
Senior to staff
A catalogue has 20,000 courses and 60,000 prerequisite links, and the registrar wants to answer "does course A depend on course B, directly or not?" instantly for any pair. Compare Warshall's closure with booleans, with bitsets, and a search from every course, in memory and time.
Key takeaways
- Floyd-Warshall fills the table of all distances with a triple loop in O(V³) time and O(V²) memory.
- The vertex k that may be used as a stop is the outer loop; after round k, paths may stop at vertices 0 to k.
- Negative edges are fine, and a negative entry on the diagonal means a negative cycle.
- On sparse graphs, Dijkstra’s algorithm from every vertex, or Johnson’s algorithm with negative edges, is faster.
References
- Algorithm 97: Shortest path (Robert W. Floyd, Communications of the ACM 5(6), 1962) (Association for Computing Machinery (ACM))
- A theorem on Boolean matrices (Stephen Warshall, Journal of the ACM 9(1), 1962) (Association for Computing Machinery (ACM))
- Efficient algorithms for shortest paths in sparse networks (Donald B. Johnson, Journal of the ACM 24(1), 1977) (Association for Computing Machinery (ACM))
- The Floyd-Warshall algorithm on graphs with negative cycles (Stefan Hougardy, Information Processing Letters 110(8-9), 2010) (Elsevier)
- Dictionary of Algorithms and Data Structures: all pairs shortest path (National Institute of Standards and Technology (NIST))
- Dictionary of Algorithms and Data Structures: Floyd-Warshall algorithm (National Institute of Standards and Technology (NIST))
- Dictionary of Algorithms and Data Structures: Johnson's algorithm (National Institute of Standards and Technology (NIST))
- Dictionary of Algorithms and Data Structures: transitive closure (National Institute of Standards and Technology (NIST))
- heapq: heap queue algorithm (Python Software Foundation)
- The Java Language Specification, Java SE 25: 4.2.2 Integer Operations (Oracle)
- Arithmetic operators: overflows (cppreference.com)
- GATE 2027 syllabus for Computer Science and Information Technology (CS) (IIT Madras (GATE 2027 organising institute))
- Computer Science Curricula 2023: Algorithmic Foundations (AL) (ACM, IEEE Computer Society and AAAI)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress