1. Introduction
The Floyd-Warshall algorithm, independently discovered by Robert Floyd (1962) and Stephen Warshall (1962), solves the all-pairs shortest path problem. Given a weighted directed graph, it computes the shortest path distances between every pair of vertices simultaneously. This stands in contrast to single-source algorithms like Dijkstra's or Bellman-Ford, which must be run once per source vertex.
The algorithm is based on dynamic programming and uses an elegant formulation: it considers whether routing a path through an intermediate vertex k produces a shorter path than the current best known path. By systematically considering all possible intermediate vertices, the algorithm builds up optimal solutions from smaller subproblems.
"The Floyd-Warshall algorithm exemplifies how dynamic programming can elegantly solve complex optimization problems through a simple recurrence relation." -- Robert Sedgewick, Algorithms
2. How It Works
The algorithm maintains a distance matrix dist[i][j] that stores the shortest known distance from vertex i to vertex j. It iterates through all vertices as potential intermediate points on paths.
Dynamic Programming Recurrence
Let dist_k[i][j] denote the shortest path from i to j using only vertices {1, 2, ..., k} as intermediate vertices. The recurrence is:
dist_k[i][j] = min(dist_{k-1}[i][j], dist_{k-1}[i][k] + dist_{k-1}[k][j]) This captures two possibilities: either the shortest path from i to j does not pass through vertex k (first term), or it does pass through k (second term, which is the sum of the shortest path from i to k and from k to j).
High-Level Steps
- Initialize: Set
dist[i][j]to the direct edge weight if an edge exists, 0 fori = j, and infinity otherwise. Initialize the next-hop matrix for path reconstruction. - Iterate over intermediates: For each vertex
kfrom 1 to V, check whether routing throughkimproves the shortest path between every pair(i, j). - Detect negative cycles: After completion, if any diagonal element
dist[i][i]is negative, a negative-weight cycle exists through vertexi.
3. Algorithm and Pseudocode
functionFloydWarshall(Graph): let V = number of vertices let dist[V][V] = INFINITY let next[V][V] = NULL // Step 1: Initialize with direct edgesfor each vertex v: dist[v][v] = 0for each edge (u, v, w): dist[u][v] = w next[u][v] = v // Step 2: Consider each vertex as intermediatefor k = 1to V: for i = 1to V: for j = 1to V: 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] // Step 3: Detect negative cyclesfor i = 1to V: if dist[i][i] < 0: return"Negative cycle detected"return dist[][], next[][]Path Reconstruction
functionReconstructPath(next, u, v): if next[u][v] == NULL: return"No path exists" path = [u] while u != v: u = next[u][v] path.append(u) return path4. Complexity Analysis
| Metric | Complexity | Explanation |
|---|---|---|
| Time Complexity | O(V^3) | Three nested loops, each iterating V times |
| Space Complexity | O(V^2) | Distance matrix and next-hop matrix |
| Path Reconstruction | O(V) | Following the next-hop pointers |
The O(V^3) time complexity is the same regardless of the number of edges. This makes Floyd-Warshall particularly efficient for dense graphs where running Dijkstra's algorithm from each vertex would yield O(V * (V + E) log V), which for dense graphs is O(V^3 log V).
Space optimization: The algorithm can be performed in-place on the distance matrix, so no additional V x V matrix for intermediate results is needed. The updates for intermediate vertex k do not affect the rows or columns of k itself.
5. Worked Example
Consider a directed graph with 4 vertices (1, 2, 3, 4):
| Edge | Weight |
|---|---|
| 1 -> 2 | 3 |
| 1 -> 3 | 8 |
| 2 -> 3 | 2 |
| 3 -> 4 | 1 |
| 4 -> 1 | 7 |
| 2 -> 4 | 10 |
Initial Distance Matrix
| 1 | 2 | 3 | 4 | |
|---|---|---|---|---|
| 1 | 0 | 3 | 8 | inf |
| 2 | inf | 0 | 2 | 10 |
| 3 | inf | inf | 0 | 1 |
| 4 | 7 | inf | inf | 0 |
k = 1 (using vertex 1 as intermediate)
Check all pairs (i,j) to see if going through vertex 1 improves the distance:
- dist[4][2]: via 1: dist[4][1] + dist[1][2] = 7 + 3 = 10 < inf. Update to 10.
- dist[4][3]: via 1: dist[4][1] + dist[1][3] = 7 + 8 = 15 < inf. Update to 15.
k = 2 (using vertex 2 as intermediate)
- dist[1][3]: via 2: dist[1][2] + dist[2][3] = 3 + 2 = 5 < 8. Update to 5.
- dist[1][4]: via 2: dist[1][2] + dist[2][4] = 3 + 10 = 13 < inf. Update to 13.
- dist[4][3]: via 2: dist[4][2] + dist[2][3] = 10 + 2 = 12 < 15. Update to 12.
- dist[4][4]: via 2: dist[4][2] + dist[2][4] = 10 + 10 = 20 (no improvement).
k = 3 (using vertex 3 as intermediate)
- dist[1][4]: via 3: dist[1][3] + dist[3][4] = 5 + 1 = 6 < 13. Update to 6.
- dist[2][4]: via 3: dist[2][3] + dist[3][4] = 2 + 1 = 3 < 10. Update to 3.
- dist[4][4]: via 3: dist[4][3] + dist[3][4] = 12 + 1 = 13 (no improvement over 0).
k = 4 (using vertex 4 as intermediate)
- dist[1][1]: via 4: dist[1][4] + dist[4][1] = 6 + 7 = 13 (no improvement over 0).
- dist[2][1]: via 4: dist[2][4] + dist[4][1] = 3 + 7 = 10 < inf. Update to 10.
- dist[3][1]: via 4: dist[3][4] + dist[4][1] = 1 + 7 = 8 < inf. Update to 8.
- dist[3][2]: via 4: dist[3][4] + dist[4][2] = 1 + 10 = 11 < inf. Update to 11.
Final Distance Matrix
| 1 | 2 | 3 | 4 | |
|---|---|---|---|---|
| 1 | 0 | 3 | 5 | 6 |
| 2 | 10 | 0 | 2 | 3 |
| 3 | 8 | 11 | 0 | 1 |
| 4 | 7 | 10 | 12 | 0 |
No diagonal element is negative, confirming there are no negative-weight cycles.
6. Path Reconstruction
The next matrix stores, for each pair (i, j), the first vertex on the shortest path from i to j. By repeatedly looking up the next vertex, the full path can be reconstructed in O(V) time.
Example: Path from 1 to 4
- next[1][4] = 2 (go to vertex 2 first)
- next[2][4] = 3 (go to vertex 3)
- next[3][4] = 4 (arrive at vertex 4)
Reconstructed path: 1 -> 2 -> 3 -> 4 with total distance 6.
Transitive closure: A variant of Floyd-Warshall (Warshall's algorithm) computes the transitive closure of a graph -- determining whether a path exists between every pair of vertices, regardless of distance -- by using boolean OR and AND operations instead of addition and minimum.
7. Advantages and Disadvantages
Advantages
- All-pairs solution: Computes shortest paths between every pair of vertices in a single execution.
- Handles negative weights: Correctly processes graphs with negative edge weights.
- Simplicity: The algorithm consists of three nested loops and is very easy to implement.
- Negative cycle detection: Negative cycles are indicated by negative values on the diagonal of the distance matrix.
- Path reconstruction: The next-hop matrix enables efficient reconstruction of any shortest path.
Disadvantages
- Cubic time complexity:
O(V^3)makes it impractical for very large graphs. - Quadratic space: Requires
O(V^2)memory for the distance and next-hop matrices. - Inefficient for sparse graphs: For sparse graphs with few edges, running Dijkstra's algorithm from each vertex is faster.
- Overkill for single-source: If only shortest paths from one source are needed, single-source algorithms are more efficient.
8. Comparison with Other Algorithms
| Feature | Floyd-Warshall | Dijkstra (all sources) | Johnson's Algorithm |
|---|---|---|---|
| Problem | All-pairs | All-pairs (via V runs) | All-pairs |
| Negative Weights | Yes | No | Yes |
| Time Complexity | O(V^3) | O(V(V+E) log V) | O(V^2 log V + VE) |
| Best For | Dense graphs | Sparse, non-negative | Sparse, negative weights |
| Approach | Dynamic Programming | Greedy | Reweighting + Greedy |
9. Applications
- Network analysis: Computing the diameter (longest shortest path) and center of a network.
- Transitive closure: Determining reachability between all pairs of vertices in a directed graph.
- Optimal routing: Pre-computing routing tables for small to medium-sized networks where all-pairs information is needed.
- Shortest path in games: Computing movement costs between all locations on small game maps.
- Detecting arbitrage: Similar to Bellman-Ford, the all-pairs version can find arbitrage opportunities across all currency pairs simultaneously.
- Minimum weight cycle: Finding the minimum weight cycle in a graph by checking the diagonal after execution.
10. References
- Floyd, R. W. (1962). "Algorithm 97: Shortest Path." Communications of the ACM, 5(6), 345.
- Warshall, S. (1962). "A Theorem on Boolean Matrices." Journal of the ACM, 9(1), 11-12.
- Cormen, T. H., Leiserson, C. E., Rivest, R. L., and Stein, C. (2009). Introduction to Algorithms (3rd ed.). MIT Press. Chapter 25.
- Sedgewick, R. and Wayne, K. (2011). Algorithms (4th ed.). Addison-Wesley. Section 4.4.
- Kleinberg, J. and Tardos, E. (2005). Algorithm Design. Addison-Wesley. Chapter 6.