Floyd-Warshall Algorithm
The Floyd-Warshall algorithm computes the shortest paths between every pair of vertices in a weighted graph — a problem known as All-Pairs Shortest Paths (APSP). It is one of the most elegant applications of dynamic programming in graph theory: just three nested loops and a single relaxation step. In this post we dissect the DP recurrence, walk through a full 5-node example showing the distance matrix evolve at each iteration, build path reconstruction with a next[][] array, explore negative-cycle detection and transitive closure, and provide an interactive animation you can step through at your own pace.
Why Floyd-Warshall?
Single-source algorithms like Dijkstra and Bellman-Ford answer “what is the shortest path from one source to all other vertices?” But many problems require shortest paths between every pair:
- Network routing: Pre-compute all-pairs distances so any query “shortest path from A to B” is answered in O(1).
- Transitive closure: Determine which vertices can reach which — Floyd-Warshall generalises to boolean reachability.
- Detecting negative cycles: If any diagonal element
dist[i][i]becomes negative, there is a negative-weight cycle through vertex i. - Dense graphs: On dense graphs (E ≈ V²), running Dijkstra V times costs O(V³) anyway, and Floyd-Warshall’s constant factor is much smaller due to cache-friendly matrix access.
- Small vertex count: With V ≤ 400–500, Floyd-Warshall’s O(V³) is perfectly fast and trivially simple to implement.
The DP Recurrence
Let dist(k)[i][j] denote the shortest path from i to j using only vertices {0, 1, …, k} as intermediate nodes. The base case is direct edge weights:
dist(−1)[i][j] = weight(i,j) if edge exists, ∞ otherwise
dist(−1)[i][i] = 0
// Recurrence: can we improve i→j by routing through vertex k?
dist(k)[i][j] = min(
dist(k−1)[i][j], // best path NOT using k as intermediate
dist(k−1)[i][k] + dist(k−1)[k][j] // path through k
)
After processing all k from 0 to V−1, dist(V−1)[i][j] holds the true shortest path from i to j (considering all possible intermediates).
Why k Must Be the Outer Loop
The recurrence builds on dist(k−1) values. If k were an inner loop, we would attempt to use intermediate vertex k before computing all (k−1)-optimal paths. The outer k loop ensures that when we process intermediate vertex k, all paths using intermediates {0, …, k−1} are already finalised. This ordering is what makes the DP correct.
In practice, we update the matrix in-place because the values dist[i][k] and dist[k][j] are never worsened by the relaxation step for intermediate k (a shortest path from i to k doesn’t benefit from routing through k again).
for i = 0 to V-1:
for j = 0 to V-1:
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])
Step-by-Step Walkthrough
Let’s trace Floyd-Warshall on this 5-node directed, weighted graph. We will watch the distance matrix evolve as k goes from 0 to 4.
Edges: 0→1 (4), 0→4 (8), 1→2 (2), 1→3 (5), 2→3 (1), 3→4 (3), 4→2 (6).
Initial Distance Matrix (direct edges only)
Set dist[i][j] = weight(i,j) if edge exists, ∞ otherwise, and dist[i][i] = 0.
| 0 | 1 | 2 | 3 | 4 | |
|---|---|---|---|---|---|
| 0 | 0 | 4 | ∞ | ∞ | 8 |
| 1 | ∞ | 0 | 2 | 5 | ∞ |
| 2 | ∞ | ∞ | 0 | 1 | ∞ |
| 3 | ∞ | ∞ | ∞ | 0 | 3 |
| 4 | ∞ | ∞ | 6 | ∞ | 0 |
Intermediate vertex 0
For each pair (i,j), check if dist[i][0] + dist[0][j] < dist[i][j]. Since no vertex has a finite-weight edge to vertex 0, all dist[i][0] = ∞ for i ≠ 0. No path through vertex 0 can improve anything.
Intermediate vertex 1
Vertex 0 can reach vertex 1 (dist[0][1]=4), and vertex 1 can reach vertices 2 and 3. So we discover new paths:
dist[0][2] = min(∞, 4+2) = 6path 0→1→2dist[0][3] = min(∞, 4+5) = 9path 0→1→3
| 0 | 1 | 2 | 3 | 4 | |
|---|---|---|---|---|---|
| 0 | 0 | 4 | 6 | 9 | 8 |
| 1 | ∞ | 0 | 2 | 5 | ∞ |
| 2 | ∞ | ∞ | 0 | 1 | ∞ |
| 3 | ∞ | ∞ | ∞ | 0 | 3 |
| 4 | ∞ | ∞ | 6 | ∞ | 0 |
Intermediate vertex 2
Vertex 2 can reach vertex 3 (dist[2][3]=1). Any vertex that can reach 2 may now improve its distance to 3:
dist[0][3] = min(9, 6+1) = 7path 0→1→2→3dist[1][3] = min(5, 2+1) = 3path 1→2→3dist[4][3] = min(∞, 6+1) = 7path 4→2→3
| 0 | 1 | 2 | 3 | 4 | |
|---|---|---|---|---|---|
| 0 | 0 | 4 | 6 | 7 | 8 |
| 1 | ∞ | 0 | 2 | 3 | ∞ |
| 2 | ∞ | ∞ | 0 | 1 | ∞ |
| 3 | ∞ | ∞ | ∞ | 0 | 3 |
| 4 | ∞ | ∞ | 6 | 7 | 0 |
Intermediate vertex 3
Vertex 3 can reach vertex 4 (dist[3][4]=3). Vertices that can now reach 3 may improve their distance to 4:
dist[1][4] = min(∞, 3+3) = 6path 1→2→3→4dist[2][4] = min(∞, 1+3) = 4path 2→3→4
| 0 | 1 | 2 | 3 | 4 | |
|---|---|---|---|---|---|
| 0 | 0 | 4 | 6 | 7 | 8 |
| 1 | ∞ | 0 | 2 | 3 | 6 |
| 2 | ∞ | ∞ | 0 | 1 | 4 |
| 3 | ∞ | ∞ | ∞ | 0 | 3 |
| 4 | ∞ | ∞ | 6 | 7 | 0 |
Intermediate vertex 4
Vertex 4 can reach vertex 2 (dist[4][2]=6). Vertex 3 can reach 4 (dist[3][4]=3), so it discovers a path to 2:
dist[3][2] = min(∞, 3+6) = 9path 3→4→2
| 0 | 1 | 2 | 3 | 4 | |
|---|---|---|---|---|---|
| 0 | 0 | 4 | 6 | 7 | 8 |
| 1 | ∞ | 0 | 2 | 3 | 6 |
| 2 | ∞ | ∞ | 0 | 1 | 4 |
| 3 | ∞ | ∞ | 9 | 0 | 3 |
| 4 | ∞ | ∞ | 6 | 7 | 0 |
Notice how each k iteration can use improvements discovered in earlier k iterations. For example, at k=2 we used dist[0][2]=6, which was itself computed at k=1. This cascading improvement is the power of Floyd-Warshall’s DP.
Path Reconstruction
The distance matrix tells us the cost of shortest paths, but not the actual paths. To reconstruct paths, maintain a next[i][j] matrix: next[i][j] stores the first vertex after i on the shortest path from i to j.
if edge(i,j) exists: next[i][j] = j
else: next[i][j] = −1
// During relaxation: when a shorter path through k is found
if dist[i][k] + dist[k][j] < dist[i][j]:
dist[i][j] = dist[i][k] + dist[k][j]
next[i][j] = next[i][k] // route through k, so first hop is same as i→k
Reconstructing a Path
To print the shortest path from u to v, follow the next pointers:
if next[u][v] == −1: return “no path”
path = [u]
while u ≠ v:
u = next[u][v]
path.append(u)
return path
Example: Shortest path from 1 to 4 (cost 6). Starting at next[1][4] = 2, then next[2][4] = 3, then next[3][4] = 4. Path: 1 → 2 → 3 → 4 with total weight 2+1+3 = 6. ✔
Interactive Animation
Step through the Floyd-Warshall algorithm on our 5-node graph. Watch the distance matrix update as each intermediate vertex k is processed. Orange cells show values that improved in the current step.
Use Step for manual control, Play for auto-advance, or Reset to start over.
| 0 | 1 | 2 | 3 | 4 | |
|---|---|---|---|---|---|
| 0 | 0 | 4 | ∞ | ∞ | 8 |
| 1 | ∞ | 0 | 2 | 5 | ∞ |
| 2 | ∞ | ∞ | 0 | 1 | ∞ |
| 3 | ∞ | ∞ | ∞ | 0 | 3 |
| 4 | ∞ | ∞ | 6 | ∞ | 0 |
Detecting Negative Cycles
Floyd-Warshall handles negative edge weights correctly as long as no negative-weight cycle exists. But how do we detect one?
for i = 0 to V-1:
if dist[i][i] < 0:
// vertex i is on a negative cycle
If a negative cycle is detected, the distances in the matrix are unreliable for vertices that can reach or be reached by the cycle. In competitive programming, you may need a second pass to mark all pairs (i,j) where the path passes through a negative cycle as having distance −∞: if dist[i][k] + dist[k][j] < dist[i][j] and dist[k][k] < 0, set dist[i][j] = −∞.
Transitive Closure (Warshall’s Algorithm)
A beautiful special case: replace arithmetic with boolean logic. Instead of “shortest distance,” we ask “can vertex i reach vertex j?” This is the transitive closure of the graph, and the algorithm is called Warshall’s algorithm (1962, the predecessor of Floyd-Warshall).
reach[i][j] = true if edge(i,j) exists or i == j
for k = 0 to V-1:
for i = 0 to V-1:
for j = 0 to V-1:
reach[i][j] = reach[i][j] OR (reach[i][k] AND reach[k][j])
The logic is identical to Floyd-Warshall: “can i reach j either directly or by going through k?” This is O(V³) but can be optimised with bitset operations to O(V³/64).
C++ Implementation
1. Basic Floyd-Warshall
#include <bits/stdc++.h>
using namespace std;
const long long INF = 1e18;
void floydWarshall(vector<vector<long long>>& dist) {
int V = dist.size();
for (int k = 0; k < V; k++)
for (int i = 0; i < V; i++)
for (int j = 0; j < V; j++)
if (dist[i][k] < INF && dist[k][j] < INF)
dist[i][j] = min(dist[i][j],
dist[i][k] + dist[k][j]);
}
int main() {
int V = 5;
vector<vector<long long>> dist(V, vector<long long>(V, INF));
for (int i = 0; i < V; i++) dist[i][i] = 0;
// Edges: u, v, weight
dist[0][1] = 4; dist[0][4] = 8;
dist[1][2] = 2; dist[1][3] = 5;
dist[2][3] = 1; dist[3][4] = 3;
dist[4][2] = 6;
floydWarshall(dist);
for (int i = 0; i < V; i++) {
for (int j = 0; j < V; j++)
cout << (dist[i][j] >= INF ? -1 : dist[i][j]) << "\t";
cout << "\n";
}
return 0;
}
dist[i][k] < INF prevents adding two large values that could overflow. Always use long long with INF = 1e18 in competitive programming.
2. With Path Reconstruction
void floydWarshallPath(vector<vector<long long>>& dist,
vector<vector<int>>& nxt) {
int V = dist.size();
// Initialise next: nxt[i][j] = j if edge exists
nxt.assign(V, vector<int>(V, -1));
for (int i = 0; i < V; i++)
for (int j = 0; j < V; j++)
if (dist[i][j] < INF && i != j)
nxt[i][j] = j;
for (int k = 0; k < V; k++)
for (int i = 0; i < V; i++)
for (int j = 0; j < V; j++)
if (dist[i][k] < INF && dist[k][j] < INF
&& dist[i][k] + dist[k][j] < dist[i][j]) {
dist[i][j] = dist[i][k] + dist[k][j];
nxt[i][j] = nxt[i][k];
}
}
vector<int> getPath(int u, int v, vector<vector<int>>& nxt) {
if (nxt[u][v] == -1) return {}; // no path
vector<int> path = {u};
while (u != v) {
u = nxt[u][v];
path.push_back(u);
}
return path;
}
3. Transitive Closure (Bitset-Optimised)
// O(V^3 / 64) with bitset optimisation
const int MAXV = 500;
bitset<MAXV> reach[MAXV];
void transitiveClosure(int V, vector<pair<int,int>>& edges) {
for (int i = 0; i < V; i++) {
reach[i].reset();
reach[i].set(i); // i reaches itself
}
for (auto& [u, v] : edges)
reach[u].set(v);
for (int k = 0; k < V; k++)
for (int i = 0; i < V; i++)
if (reach[i].test(k))
reach[i] |= reach[k];
// reach[i].test(j) == true means i can reach j
}
The bitset trick replaces the inner j-loop with a single bitwise OR operation, giving a 64× speedup in practice.
Complexity Analysis
| Aspect | Complexity | Notes |
|---|---|---|
| Time | O(V³) | Three nested loops over V vertices |
| Space | O(V²) | Distance matrix (in-place update) |
| Path reconstruction | +O(V²) | Additional next[][] matrix |
| Path query | O(V) | Follow next pointers |
Comparison with repeated Dijkstra: Running Dijkstra from every vertex costs O(V · (V+E) log V) with a binary heap. For dense graphs (E ≈ V²), this is O(V³ log V), worse than Floyd-Warshall. For sparse graphs (E ≈ V), repeated Dijkstra is O(V² log V), much better. Floyd-Warshall wins on simplicity and cache performance for small V.
Competitive Programming Patterns
1. Shortest Paths in Small Graphs
The most common pattern: V ≤ 400, all-pairs shortest paths needed. Build the adjacency matrix, run Floyd-Warshall, answer queries in O(1). Often disguised as “find the city with fewest reachable neighbours within distance threshold” (LeetCode 1334).
2. Minimax Paths
Find the path from i to j that minimises the maximum edge weight along the path. Replace the relaxation with:
dist[i][j] = min(dist[i][j], max(dist[i][k], dist[k][j])).
3. Widest (Bottleneck) Paths
Find the path that maximises the minimum edge weight (widest bottleneck). Replace with:
dist[i][j] = max(dist[i][j], min(dist[i][k], dist[k][j])).
4. Incremental Vertex Addition
In problems like Codeforces 295B — Greg and Graph, vertices are added one by one. After adding vertex k, run just the k-th iteration of Floyd-Warshall (the outer loop for that single k). This updates all-pairs distances incrementally in O(V²) per vertex addition.
5. Detecting Negative Arbitrage Cycles
Model currency exchange rates as edge weights (use −log(rate) to convert multiplication to addition). Run Floyd-Warshall and check if any dist[i][i] < 0, indicating an arbitrage opportunity.
Practice Problems
Test your understanding with these problems, ordered by difficulty:
Shortest Routes II (CSES)
Direct application: given a weighted graph with up to 500 nodes, answer Q shortest-path queries. Textbook Floyd-Warshall.
Find the City (LeetCode 1334)
Run Floyd-Warshall, then for each city count how many other cities are within the distance threshold. Return the city with the smallest count.
Course Schedule IV (LeetCode 1462)
Transitive closure problem: determine if course A is a prerequisite of course B. Use Warshall’s boolean algorithm.
Evaluate Division (LeetCode 399)
Model variable equations as a weighted graph (a/b = 2.0 means edge a→b with weight 2.0). Use Floyd-Warshall with multiplication instead of addition to answer queries.
Greg and Graph (Codeforces 295B)
Vertices are added in a given order. After each addition, print the sum of all-pairs shortest distances. Solve by adding vertices in reverse and running partial Floyd-Warshall iterations.
Edge Deletion (AtCoder ABC243 E)
Find the maximum number of edges you can delete without changing any shortest-path distance. Run Floyd-Warshall, then check each edge: if an alternative path of the same length exists, the edge is redundant.
Traveling Graph (Codeforces 21D)
Find the shortest closed walk that visits every edge at least once (Chinese Postman Problem). Requires Floyd-Warshall to compute all-pairs shortest paths, then bitmask DP on odd-degree vertices.
Summary
- Floyd-Warshall solves all-pairs shortest paths in O(V³) time and O(V²) space via DP. The k-loop must be outermost for correctness.
- The recurrence
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])considers all possible intermediate vertices. - Path reconstruction uses a
next[i][j]matrix updated alongsidedist. Follownextpointers to recover the actual path. - Negative cycles are detected by checking the diagonal:
dist[i][i] < 0means vertex i is on a negative cycle. - Warshall’s algorithm (boolean variant) computes transitive closure with the same structure, optimisable to O(V³/64) using bitsets.
- Use Floyd-Warshall when V ≤ 500 and you need all-pairs distances. For larger or sparser graphs, prefer repeated Dijkstra or Johnson’s algorithm.
- In competitive programming, the same framework supports minimax paths, widest paths, and incremental vertex addition.