Shortest Path Algorithm: Floyd-Warshall Algorithm
Floyd-Warshall is a dynamic programming algorithm used to compute the shortest paths between all pairs of vertices in a weighted graph. Unlike Dijkstra's algorithm (which computes single-source shortest paths) or the Bellman-Ford algorithm, Floyd-Warshall can handle graphs with negative-weight edges (but not graphs containing negative-weight cycles) and finds the shortest distances between all vertices in one go.
Core Idea and Dynamic Programming State Definition
The core idea of the algorithm is: for any two vertices i and j in the graph, consider all other vertices as potential "intermediate stops." We iteratively test whether introducing an intermediate vertex k can make the path from i to j shorter.
We define a two-dimensional array dist as our dynamic programming state:
dist[k][i][j]represents the shortest path distance from vertex i to vertex j when considering the first k vertices (numbered 0, 1, ..., k-1) as possible intermediate vertices.
However, observation shows that state dist[k] depends only on state dist[k-1]. Therefore, we can "flatten" the three-dimensional array into a two-dimensional array through overwriting to save space. We ultimately use a two-dimensional array dist[i][j] and continuously update it during the algorithm. Its meaning evolves to: at the current stage, the known shortest distance from vertex i to vertex j.
Detailed Algorithm Steps
Assume the graph has n vertices (numbered from 0 to n-1) and is represented by an n×n adjacency matrix graph. graph[i][j] represents the weight of the edge from vertex i to vertex j. If there is no direct edge between i and j, it is represented by infinity (∞).
Step 1: Initialization
Create a distance matrix dist of size n×n. Copy the graph's adjacency matrix directly into dist as the initial state.
- For each vertex pair (i, j):
- If i equals j, then the distance from a vertex to itself
dist[i][j]= 0. - Otherwise,
dist[i][j]=graph[i][j](i.e., the edge weight, or ∞ if no direct edge exists).
- If i equals j, then the distance from a vertex to itself
This initial state dist[i][j] represents the shortest path from i to j when no intermediate vertices are allowed (i.e., taking the direct edge if it exists, or no path otherwise).
Step 2: Core Dynamic Programming Iteration
Now, we start gradually allowing each vertex as an intermediate vertex. We perform n loops, with k ranging from 0 to n-1. In each loop for k, we consider adding vertex k to the set of allowed intermediate vertices.
For each vertex pair (i, j), we ask the question: "Now that we are allowed to pass through vertex k, could the path from i to j become shorter by going through k?"
The specific update rule is as follows:
For all vertex pairs (i, j), check:
dist[i][j] = min( dist[i][j], dist[i][k] + dist[k][j] )
The meaning of this formula is:
dist[i][j]: The currently known shortest distance from i to j (when allowed to use the first k-1 vertices as intermediates).dist[i][k]: The shortest distance from i to k, computed in a previous state using only the first k-1 intermediate vertices.dist[k][j]: The shortest distance from k to j, similarly computed using only the first k-1 intermediate vertices.dist[i][k] + dist[k][j]: This path represents going from i to k, then from k to j. This path is allowed to use vertex k as an intermediate.
We compare the "current shortest path without using k" with the "new path through k" and update dist[i][j] to the smaller value.
Important Detail: When calculating dist[i][k] and dist[k][j], since k is the intermediate vertex currently being considered, we use the values computed in the previous round (stage k-1). This ensures we do not create a path that passes through the same vertex k multiple times, adhering to the "non-aftereffect" property of dynamic programming.
Step 3: Completion of Iteration
After iterating k from 0 to n-1, the values in the matrix dist (i.e., dist[i][j]) represent the true shortest path distances from vertex i to vertex j. This is because we have now considered all n vertices in the graph as potential intermediate vertices.
Algorithm Demonstration (A Simple Example)
Consider a graph with 3 vertices (0, 1, 2), with the following adjacency matrix (∞ represents no direct edge):
Initial graph:
0 1 2
0 0 3 ∞
1 ∞ 0 1
2 2 ∞ 0
-
Initialize dist matrix: Direct copy of graph.
k = -1 (Initial state, no intermediate vertices allowed) dist: 0 1 2 0 0 3 ∞ 1 ∞ 0 1 2 2 ∞ 0 -
First iteration (k=0): Allow vertex 0 as an intermediate.
Check all (i, j) pairs:- (1,2): dist[1][2] = min(∞, dist[1][0] + dist[0][2]) = min(∞, ∞ + ∞) = ∞ -> unchanged.
- (2,1): dist[2][1] = min(∞, dist[2][0] + dist[0][1]) = min(∞, 2 + 3) = 5 -> updated to 5!
Updated dist matrix:
k = 0 (Allowed intermediate vertex: 0) dist: 0 1 2 0 0 3 ∞ 1 ∞ 0 1 2 2 5 0Now we know that from 2 to 1, we can go via 0 with distance 5, which is shorter than the previous ∞ (no direct path).
-
Second iteration (k=1): Allow vertices 0 and 1 as intermediates.
Check all (i, j) pairs:- (0,2): dist[0][2] = min(∞, dist[0][1] + dist[1][2]) = min(∞, 3 + 1) = 4 -> updated to 4!
- (2,0): dist[2][0] = min(2, dist[2][1] + dist[1][0]) = min(2, 5 + ∞) = 2 -> unchanged.
- (2,2): dist[2][2] = min(0, dist[2][1] + dist[1][2]) = min(0, 5+1)=0 -> unchanged.
Updated dist matrix:
k = 1 (Allowed intermediate vertices: 0,1) dist: 0 1 2 0 0 3 4 1 ∞ 0 1 2 2 5 0Now we know that from 0 to 2, we can go via 1 with distance 4.
-
Third iteration (k=2): Allow all vertices (0,1,2) as intermediates.
Check all (i, j) pairs:- (0,0): dist[0][0] = min(0, dist[0][2] + dist[2][0]) = min(0, 4+2)=0 -> unchanged.
- (0,1): dist[0][1] = min(3, dist[0][2] + dist[2][1]) = min(3, 4+5)=3 -> unchanged.
- (1,0): dist[1][0] = min(∞, dist[1][2] + dist[2][0]) = min(∞, 1+2)=3 -> updated to 3!
- (1,1): dist[1][1] = min(0, dist[1][2] + dist[2][1]) = min(0, 1+5)=0 -> unchanged.
Updated dist matrix (final result):
k = 2 (Allowed all intermediate vertices) dist: 0 1 2 0 0 3 4 1 3 0 1 2 2 5 0
Finally, the dist matrix contains the shortest path distances between all pairs of vertices.
Time and Space Complexity
- Time Complexity: O(n³). Because there are three nested loops iterating over k, i, j, each running n times.
- Space Complexity: O(n²). Only need to store an n×n distance matrix.
Algorithm Characteristics and Considerations
- Negative-weight edges: Floyd-Warshall can handle graphs with negative-weight edges.
- Negative-weight cycles: The algorithm cannot handle graphs containing negative-weight cycles. A negative-weight cycle means one can traverse it infinitely, making path lengths approach negative infinity. This can be detected by checking the main diagonal of the final matrix: if any
dist[i][i] < 0exists, then there is a negative-weight cycle starting and ending at i. - Path reconstruction: If the specific shortest path (not just the distance) needs to be output, a
nextmatrix can be maintained to record the successor of i on the shortest path from i to j. Whendist[i][j]is updated because a shorter path is found, also updatenext[i][j] = next[i][k].