Master Floyd-Warshall algorithm completely — the all-pairs shortest path approach, the DP recurrence, step-by-step table filling, path reconstruction, negative cycle detection, transitive closure, and comparison with running Dijkstra V times.
What Is Floyd-Warshall?
Floyd-Warshall computes shortest paths between ALL pairs of vertices in a weighted graph. While Dijkstra finds shortest paths from ONE source, Floyd-Warshall gives you the complete distance matrix for every (source, destination) pair.
- Single-source shortest path: Dijkstra (non-negative), Bellman-Ford (any)
- All-pairs shortest path: Floyd-Warshall (any weights), or run Dijkstra V times
Complete Implementation
def floyd_warshall(n, edges, directed=False):
"""
Floyd-Warshall all-pairs shortest path.
Time: O(V³), Space: O(V²)
Works with negative weights (but detects negative cycles).
"""
INF = float('inf')
# Initialize distance matrix
dist = [[INF]*n for _ in range(n)]
for i in range(n):
dist[i][i] = 0 # Distance from i to itself = 0
# Fill in direct edges
for u, v, w in edges:
dist[u][v] = w
if not directed:
dist[v][u] = w
# Main algorithm: try each vertex k as intermediate
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]
# Check for negative cycles: dist[i][i] < 0 means negative cycle through i
for i in range(n):
if dist[i][i] < 0:
print(f"Negative cycle detected involving vertex {i}")
return None
return dist
edges = [(0,1,3),(0,3,7),(1,0,8),(1,2,2),(2,0,5),(2,3,1),(3,0,2)]
dist = floyd_warshall(4, edges, directed=True)
for row in dist:
print(row)
# [0, 3, 5, 6]
# [5, 0, 2, 3]
# [3, 6, 0, 1]
# [2, 5, 7, 0]
Step-by-Step Table Filling
| 0 | 1: 3, 0→3: 7, 1→2: 2, 2→0: 5, 2→3: 1, 3→0: 2 |
| 0 | 0 3 ∞ 7 |
| 1 | 8 0 2 ∞ |
| 2 | 5 ∞ 0 1 |
| 3 | 2 ∞ ∞ 0 |
Path Reconstruction
def floyd_warshall_with_path(n, edges, directed=False):
"""Floyd-Warshall with path reconstruction using 'next' matrix."""
INF = float('inf')
dist = [[INF]*n for _ in range(n)]
next_v = [[None]*n for _ in range(n)] # next_v[i][j] = first step from i to j
for i in range(n):
dist[i][i] = 0
for u, v, w in edges:
dist[u][v] = w
next_v[u][v] = v # To go from u to v, first step is v
if not directed:
dist[v][u] = w
next_v[v][u] = u
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]
next_v[i][j] = next_v[i][k] # Path goes through k
def reconstruct_path(u, v):
if dist[u][v] == INF:
return None # No path
path = [u]
while u != v:
u = next_v[u][v]
path.append(u)
return path
return dist, reconstruct_path
edges = [(0,1,3),(0,3,7),(1,2,2),(2,0,5),(2,3,1),(3,0,2)]
dist, get_path = floyd_warshall_with_path(4, edges, directed=True)
print(get_path(0, 2)) # [0, 1, 2]
print(dist[0][2]) # 5 (0→1→2 = 3+2)
Transitive Closure
Floyd-Warshall can also compute whether any path exists between each pair (reachability):
def transitive_closure(n, edges, directed=False):
"""
Compute reachability matrix.
reach[i][j] = True if there's a path from i to j.
"""
reach = [[False]*n for _ in range(n)]
for i in range(n):
reach[i][i] = True
for u, v, _ in edges:
reach[u][v] = True
if not directed:
reach[v][u] = True
for k in range(n):
for i in range(n):
for j in range(n):
reach[i][j] = reach[i][j] or (reach[i][k] and reach[k][j])
return reach
edges = [(0,1,1),(1,2,1),(3,4,1)]
reach = transitive_closure(5, edges, directed=True)
print(reach[0][2]) # True (0→1→2)
print(reach[0][3]) # False (no path from 0 to 3)
Comparison: Floyd-Warshall vs Running Dijkstra V Times
For all-pairs shortest paths, you have two options:
| Approach | Time | Space | Handles Negative Weights |
|---|
| Floyd-Warshall | O(V³) | O(V²) | ✅ Yes |
| Dijkstra × V | O(V(V+E) log V) | O(V+E) | ❌ No |
| Bellman-Ford × V | O(V²E) | O(V) | ✅ Yes |
When Floyd-Warshall wins:
- Dense graphs (E ≈ V²): Dijkstra × V = O(V × V² × log V) = O(V³ log V) vs Floyd-Warshall O(V³)
- Negative weights present
- Simple implementation needed
When Dijkstra × V wins:
- Sparse graphs (E ≈ V): Dijkstra × V = O(V × (V+V) log V) = O(V² log V) < O(V³)
- No negative weights
Floyd-Warshall in Practice
# Network latency between all pairs of data centers
# V = 100 data centers, E = 500 connections
# Floyd-Warshall: 100³ = 10^6 operations — fast!
# Social network "degrees of separation" (unweighted)
# V = 10^6 users — Floyd-Warshall: 10^18 operations — impossible!
# Use BFS instead for large sparse graphs
# Practical limit: Floyd-Warshall viable for V ≤ 500-1000
Summary
Floyd-Warshall: three nested loops over all vertex triples.
for k in range(n): # Intermediate vertex
for i in range(n): # Source
for j in range(n): # Destination
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])
- Time: O(V³) — suitable for V ≤ 1000
- Space: O(V²) — always this, regardless of edges
- Handles negative weights: Yes (detects negative cycles via
dist[i][i] < 0) - Use for: All-pairs shortest paths, transitive closure, small dense graphs
*Next Lesson: Minimum Spanning Tree*