7-8 弗洛伊德-沃肖算法(全对最短路径)
弗洛伊德-沃肖算法(Floyd-Warshall — All Pairs Shortest Paths)
弗洛伊德-沃肖算法(Floyd-Warshall Algorithm)是一种求解所有节点对之间最短路径(All-Pairs Shortest Paths)的经典算法。其核心思想是动态规划(Dynamic Programming):逐步考虑每个顶点作为中间节点(Intermediate Vertex),检查通过该中间节点是否能缩短任意两个节点之间的距离。
具体来说,设 dist[i][j] 表示从节点 i 到节点 j 的当前最短距离。对于每个中间节点 k(从 0 到 V-1),更新所有节点对 (i, j):
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])
如果通过节点 k 中转能获得更短的路径,就更新 dist[i][j]。经过 V 轮迭代后,dist[i][j] 就是节点 i 到节点 j 的最短距离。
时间复杂度 O(V^3),空间复杂度 O(V^2),适合稠密图(Dense Graph)或需要所有节点对最短路径的场景。
本文使用如下加权有向图(4 个顶点):
邻接矩阵(Adjacency Matrix):
0 1 2 3
0 [ 0 3 INF 5 ]
1 [ 2 0 INF 4 ]
2 [INF 1 0 INF ]
3 [INF INF 2 0 ]
INF = 99999 (表示不可达)
图的结构:
0 --3--> 1
| |
5 4
| |
v v
3 --2--> 2 --1--> 1
(注意: 2→1 权重1, 1→0 权重2)
图的表示(邻接矩阵)
Floyd-Warshall 算法使用邻接矩阵(Adjacency Matrix)来表示图。adj[i][j] 表示从节点 i 到节点 j 的边权重。若 i == j,权重为 0;若 i 到 j 没有直接边,权重设为一个很大的值(INF)表示不可达。
#include <iostream>
#include <vector>
using namespace std;
const int INF = 99999;
int main() {
// Adjacency matrix for the weighted directed graph
vector<vector<int>> adj = {
{0, 3, INF, 5},
{2, 0, INF, 4},
{INF, 1, 0, INF},
{INF, INF, 2, 0},
};
int n = adj.size();
// Print adjacency matrix
cout << "Adjacency Matrix:" << endl;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (adj[i][j] == INF)
cout << "INF\t";
else
cout << adj[i][j] << "\t";
}
cout << endl;
}
return 0;
}
#include <stdio.h>
#define V 4
#define INF 99999
int main() {
// Adjacency matrix for the weighted directed graph
int adj[V][V] = {
{0, 3, INF, 5},
{2, 0, INF, 4},
{INF, 1, 0, INF},
{INF, INF, 2, 0},
};
// Print adjacency matrix
printf("Adjacency Matrix:\n");
for (int i = 0; i < V; i++) {
for (int j = 0; j < V; j++) {
if (adj[i][j] == INF)
printf("INF\t");
else
printf("%d\t", adj[i][j]);
}
printf("\n");
}
return 0;
}
INF = 99999
def main():
# Adjacency matrix for the weighted directed graph
adj = [
[0, 3, INF, 5],
[2, 0, INF, 4],
[INF, 1, 0, INF],
[INF, INF, 2, 0],
]
# Print adjacency matrix
print("Adjacency Matrix:")
for row in adj:
print("\t".join("INF" if x == INF else str(x) for x in row))
if __name__ == "__main__":
main()
package main
import "fmt"
const INF = 99999
func main() {
// Adjacency matrix for the weighted directed graph
adj := [][]int{
{0, 3, INF, 5},
{2, 0, INF, 4},
{INF, 1, 0, INF},
{INF, INF, 2, 0},
}
// Print adjacency matrix
fmt.Println("Adjacency Matrix:")
for _, row := range adj {
for _, val := range row {
if val == INF {
fmt.Print("INF\t")
} else {
fmt.Printf("%d\t", val)
}
}
fmt.Println()
}
}
上述代码构建了示例加权有向图的邻接矩阵。对角线上的值为 0(节点到自身的距离),INF(99999)表示两个节点之间没有直接边。邻接矩阵是 Floyd-Warshall 算法的天然数据结构,因为算法需要频繁地按索引访问任意两个节点之间的距离。
运行该程序将输出:
Adjacency Matrix:
0 3 INF 5
2 0 INF 4
INF 1 0 INF
INF INF 2 0
Floyd-Warshall 算法实现
Floyd-Warshall 算法的核心是三重循环:
for k = 0 to V-1: // k is the intermediate vertex
for i = 0 to V-1: // i is the source vertex
for j = 0 to V-1: // j is the destination vertex
if dist[i][k] + dist[k][j] < dist[i][j]:
dist[i][j] = dist[i][k] + dist[k][j]
外层循环遍历每个中间节点 k。内层两层循环检查所有节点对 (i, j),判断通过 k 中转是否能缩短 i 到 j 的距离。
迭代过程跟踪:
初始 dist:
0 1 2 3
0 [ 0 3 INF 5 ]
1 [ 2 0 INF 4 ]
2 [INF 1 0 INF ]
3 [INF INF 2 0 ]
k=0: 检查通过节点 0 中转
dist[1][2] = min(INF, dist[1][0]+dist[0][2]) = min(INF, 2+INF) = INF
dist[1][3] = min(4, dist[1][0]+dist[0][3]) = min(4, 2+5) = 4
dist[2][3] = min(INF, dist[2][0]+dist[0][3]) = min(INF, INF+5) = INF
...
k=1: 检查通过节点 1 中转
dist[0][2] = min(INF, dist[0][1]+dist[1][2]) = min(INF, 3+INF) = INF
dist[2][0] = min(INF, dist[2][1]+dist[1][0]) = min(INF, 1+2) = 3 ← 更新!
dist[3][0] = min(INF, dist[3][1]+dist[1][0]) = min(INF, INF+2) = INF
...
k=2: 检查通过节点 2 中转
dist[0][2] = min(INF, dist[0][2]+dist[2][2]) = INF
dist[1][2] = min(INF, dist[1][2]+dist[2][2]) = INF
dist[3][0] = min(INF, dist[3][2]+dist[2][0]) = min(INF, 2+3) = 5 ← 更新!
dist[3][1] = min(INF, dist[3][2]+dist[2][1]) = min(INF, 2+1) = 3 ← 更新!
...
k=3: 检查通过节点 3 中转
dist[0][2] = min(INF, dist[0][3]+dist[3][2]) = min(INF, 5+2) = 7 ← 更新!
...
最终 dist:
0 1 2 3
0 [ 0 3 7 5 ]
1 [ 2 0 7 4 ]
2 [ 3 1 0 5 ]
3 [ 5 3 2 0 ]
C++ 实现
#include <iostream>
#include <vector>
using namespace std;
const int INF = 99999;
void floydWarshall(vector<vector<int>>& dist) {
int n = dist.size();
// k = intermediate vertex
for (int k = 0; k < n; k++) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
// Skip if either segment is unreachable
if (dist[i][k] != INF && dist[k][j] != INF) {
if (dist[i][k] + dist[k][j] < dist[i][j]) {
dist[i][j] = dist[i][k] + dist[k][j];
}
}
}
}
}
}
void printMatrix(const vector<vector<int>>& dist) {
int n = dist.size();
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (dist[i][j] == INF)
cout << "INF\t";
else
cout << dist[i][j] << "\t";
}
cout << endl;
}
}
int main() {
vector<vector<int>> dist = {
{0, 3, INF, 5},
{2, 0, INF, 4},
{INF, 1, 0, INF},
{INF, INF, 2, 0},
};
cout << "Initial distances:" << endl;
printMatrix(dist);
floydWarshall(dist);
cout << "\nShortest distances:" << endl;
printMatrix(dist);
return 0;
}
C 实现
#include <stdio.h>
#define V 4
#define INF 99999
void floydWarshall(int dist[V][V]) {
// k = intermediate vertex
for (int k = 0; k < V; k++) {
for (int i = 0; i < V; i++) {
for (int j = 0; j < V; j++) {
// Skip if either segment is unreachable
if (dist[i][k] != INF && dist[k][j] != INF) {
if (dist[i][k] + dist[k][j] < dist[i][j]) {
dist[i][j] = dist[i][k] + dist[k][j];
}
}
}
}
}
}
void printMatrix(int dist[V][V]) {
for (int i = 0; i < V; i++) {
for (int j = 0; j < V; j++) {
if (dist[i][j] == INF)
printf("INF\t");
else
printf("%d\t", dist[i][j]);
}
printf("\n");
}
}
int main() {
int dist[V][V] = {
{0, 3, INF, 5},
{2, 0, INF, 4},
{INF, 1, 0, INF},
{INF, INF, 2, 0},
};
printf("Initial distances:\n");
printMatrix(dist);
floydWarshall(dist);
printf("\nShortest distances:\n");
printMatrix(dist);
return 0;
}
Python 实现
INF = 99999
def floyd_warshall(dist):
"""Floyd-Warshall algorithm for all-pairs shortest paths."""
n = len(dist)
# k = intermediate vertex
for k in range(n):
for i in range(n):
for j in range(n):
# Skip if either segment is unreachable
if dist[i][k] != INF and dist[k][j] != INF:
if dist[i][k] + dist[k][j] < dist[i][j]:
dist[i][j] = dist[i][k] + dist[k][j]
def print_matrix(dist):
"""Print the distance matrix."""
for row in dist:
print("\t".join("INF" if x == INF else str(x) for x in row))
if __name__ == "__main__":
dist = [
[0, 3, INF, 5],
[2, 0, INF, 4],
[INF, 1, 0, INF],
[INF, INF, 2, 0],
]
print("Initial distances:")
print_matrix(dist)
floyd_warshall(dist)
print("\nShortest distances:")
print_matrix(dist)
Go 实现
package main
import "fmt"
const INF = 99999
func floydWarshall(dist [][]int) {
n := len(dist)
// k = intermediate vertex
for k := 0; k < n; k++ {
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
// Skip if either segment is unreachable
if dist[i][k] != INF && dist[k][j] != INF {
if dist[i][k]+dist[k][j] < dist[i][j] {
dist[i][j] = dist[i][k] + dist[k][j]
}
}
}
}
}
}
func printMatrix(dist [][]int) {
for _, row := range dist {
for _, val := range row {
if val == INF {
fmt.Print("INF\t")
} else {
fmt.Printf("%d\t", val)
}
}
fmt.Println()
}
}
func main() {
dist := [][]int{
{0, 3, INF, 5},
{2, 0, INF, 4},
{INF, 1, 0, INF},
{INF, INF, 2, 0},
}
fmt.Println("Initial distances:")
printMatrix(dist)
floydWarshall(dist)
fmt.Println("\nShortest distances:")
printMatrix(dist)
}
上述代码实现了 Floyd-Warshall 算法。核心是三重嵌套循环:最外层的 k 遍历每个中间节点,内层的 i 和 j 遍历所有节点对。对于每个节点对 (i, j),检查是否存在通过 k 中转的更短路径。在更新前会先检查 dist[i][k] 和 dist[k][j] 是否为 INF,避免溢出导致错误。算法直接在输入矩阵上原地修改,不需要额外的矩阵空间。
运行该程序将输出:
Initial distances:
0 3 INF 5
2 0 INF 4
INF 1 0 INF
INF INF 2 0
Shortest distances:
0 3 7 5
2 0 7 4
3 1 0 5
5 3 2 0
路径还原
Floyd-Warshall 算法不仅计算最短距离,还可以还原具体的路径。为此需要维护一个 next 矩阵:next[i][j] 表示从 i 到 j 的最短路径上,i 的下一个节点。初始化时,如果 i 到 j 有直接边,则 next[i][j] = j,否则为 -1。每当通过中间节点 k 找到更短路径时,更新 next[i][j] = next[i][k]。
路径还原时,从起点 i 出发,沿着 next 矩阵逐步跳转,直到到达终点 j。
C++ 实现
#include <iostream>
#include <vector>
using namespace std;
const int INF = 99999;
void floydWarshallWithPath(vector<vector<int>>& dist, vector<vector<int>>& nxt) {
int n = dist.size();
// Initialize next matrix for path reconstruction
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (dist[i][j] != INF && i != j) {
nxt[i][j] = j; // Direct edge exists: next hop is j
} else {
nxt[i][j] = -1; // No direct edge
}
}
}
// Floyd-Warshall with path tracking
for (int k = 0; k < n; k++) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (dist[i][k] != INF && dist[k][j] != INF) {
if (dist[i][k] + dist[k][j] < dist[i][j]) {
dist[i][j] = dist[i][k] + dist[k][j];
nxt[i][j] = nxt[i][k]; // Reroute through k
}
}
}
}
}
}
void printPath(const vector<vector<int>>& nxt, int src, int dst) {
if (nxt[src][dst] == -1) {
cout << "No path from " << src << " to " << dst;
return;
}
cout << src;
int cur = src;
while (cur != dst) {
cur = nxt[cur][dst];
cout << " -> " << cur;
}
}
int main() {
vector<vector<int>> dist = {
{0, 3, INF, 5},
{2, 0, INF, 4},
{INF, 1, 0, INF},
{INF, INF, 2, 0},
};
int n = dist.size();
vector<vector<int>> nxt(n, vector<int>(n, -1));
floydWarshallWithPath(dist, nxt);
// Print all shortest paths
cout << "Shortest paths:" << endl;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (i != j) {
cout << " " << i << " -> " << j
<< " (distance " << dist[i][j] << "): ";
printPath(nxt, i, j);
cout << endl;
}
}
}
return 0;
}
C 实现
#include <stdio.h>
#define V 4
#define INF 99999
void floydWarshallWithPath(int dist[V][V], int nxt[V][V]) {
// Initialize next matrix for path reconstruction
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; // Direct edge exists
} else {
nxt[i][j] = -1; // No direct edge
}
}
}
// Floyd-Warshall with path tracking
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) {
if (dist[i][k] + dist[k][j] < dist[i][j]) {
dist[i][j] = dist[i][k] + dist[k][j];
nxt[i][j] = nxt[i][k]; // Reroute through k
}
}
}
}
}
}
void printPath(int nxt[V][V], int src, int dst) {
if (nxt[src][dst] == -1) {
printf("No path from %d to %d", src, dst);
return;
}
printf("%d", src);
int cur = src;
while (cur != dst) {
cur = nxt[cur][dst];
printf(" -> %d", cur);
}
}
int main() {
int dist[V][V] = {
{0, 3, INF, 5},
{2, 0, INF, 4},
{INF, 1, 0, INF},
{INF, INF, 2, 0},
};
int nxt[V][V];
floydWarshallWithPath(dist, nxt);
// Print all shortest paths
printf("Shortest paths:\n");
for (int i = 0; i < V; i++) {
for (int j = 0; j < V; j++) {
if (i != j) {
printf(" %d -> %d (distance %d): ", i, j, dist[i][j]);
printPath(nxt, i, j);
printf("\n");
}
}
}
return 0;
}
Python 实现
INF = 99999
def floyd_warshall_with_path(dist):
"""Floyd-Warshall with path reconstruction."""
n = len(dist)
# Initialize next matrix for path reconstruction
nxt = [[-1] * n for _ in range(n)]
for i in range(n):
for j in range(n):
if dist[i][j] != INF and i != j:
nxt[i][j] = j # Direct edge exists
# Floyd-Warshall with path tracking
for k in range(n):
for i in range(n):
for j in range(n):
if dist[i][k] != INF and dist[k][j] != INF:
if dist[i][k] + dist[k][j] < dist[i][j]:
dist[i][j] = dist[i][k] + dist[k][j]
nxt[i][j] = nxt[i][k] # Reroute through k
return nxt
def print_path(nxt, src, dst):
"""Reconstruct and print the path from src to dst."""
if nxt[src][dst] == -1:
print(f"No path from {src} to {dst}", end="")
return
path = [src]
cur = src
while cur != dst:
cur = nxt[cur][dst]
path.append(cur)
print(" -> ".join(map(str, path)), end="")
if __name__ == "__main__":
dist = [
[0, 3, INF, 5],
[2, 0, INF, 4],
[INF, 1, 0, INF],
[INF, INF, 2, 0],
]
nxt = floyd_warshall_with_path(dist)
# Print all shortest paths
print("Shortest paths:")
n = len(dist)
for i in range(n):
for j in range(n):
if i != j:
print(f" {i} -> {j} (distance {dist[i][j]}): ", end="")
print_path(nxt, i, j)
print()
Go 实现
package main
import "fmt"
const INF = 99999
func floydWarshallWithPath(dist [][]int, nxt [][]int) {
n := len(dist)
// Initialize next matrix for path reconstruction
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
if dist[i][j] != INF && i != j {
nxt[i][j] = j // Direct edge exists
} else {
nxt[i][j] = -1 // No direct edge
}
}
}
// Floyd-Warshall with path tracking
for k := 0; k < n; k++ {
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
if dist[i][k] != INF && dist[k][j] != INF {
if dist[i][k]+dist[k][j] < dist[i][j] {
dist[i][j] = dist[i][k] + dist[k][j]
nxt[i][j] = nxt[i][k] // Reroute through k
}
}
}
}
}
}
func printPath(nxt [][]int, src, dst int) {
if nxt[src][dst] == -1 {
fmt.Printf("No path from %d to %d", src, dst)
return
}
fmt.Print(src)
cur := src
for cur != dst {
cur = nxt[cur][dst]
fmt.Printf(" -> %d", cur)
}
}
func main() {
dist := [][]int{
{0, 3, INF, 5},
{2, 0, INF, 4},
{INF, 1, 0, INF},
{INF, INF, 2, 0},
}
n := len(dist)
nxt := make([][]int, n)
for i := range nxt {
nxt[i] = make([]int, n)
}
floydWarshallWithPath(dist, nxt)
// Print all shortest paths
fmt.Println("Shortest paths:")
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
if i != j {
fmt.Printf(" %d -> %d (distance %d): ", i, j, dist[i][j])
printPath(nxt, i, j)
fmt.Println()
}
}
}
}
上述代码在 Floyd-Warshall 算法的基础上增加了路径还原功能。next 矩阵记录了每对节点之间最短路径上的第一个跳转节点。初始化时,如果有直接边则 next[i][j] = j;当通过中间节点 k 找到更短路径时,将 next[i][j] 更新为 next[i][k](即从 i 出发先走向 k 方向的第一步)。路径还原时从起点出发,沿 next 矩阵不断跳转直到终点,逐步输出路径上的所有节点。
运行该程序将输出:
Shortest paths:
0 -> 1 (distance 3): 0 -> 1
0 -> 2 (distance 7): 0 -> 3 -> 2
0 -> 3 (distance 5): 0 -> 3
1 -> 0 (distance 2): 1 -> 0
1 -> 2 (distance 6): 1 -> 3 -> 2
1 -> 3 (distance 4): 1 -> 3
2 -> 0 (distance 3): 2 -> 1 -> 0
2 -> 1 (distance 1): 2 -> 1
2 -> 3 (distance 5): 2 -> 1 -> 3
3 -> 0 (distance 5): 3 -> 2 -> 1 -> 0
3 -> 1 (distance 3): 3 -> 2 -> 1
3 -> 2 (distance 2): 3 -> 2
Floyd-Warshall 的性质
下表总结了 Floyd-Warshall 算法的时间和空间复杂度:
| 指标 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度(Time Complexity) | O(V^3) | 三重嵌套循环,每层 V 次迭代 |
| 空间复杂度(Space Complexity) | O(V^2) | 邻接矩阵 dist[V][V],可选的 next[V][V] 用于路径还原 |
其中 V 是顶点数(Vertex Count)。
Floyd-Warshall 的关键性质:
- 全源最短路径:一次运行即可计算出所有节点对之间的最短距离,这是 Floyd-Warshall 相对于 Dijkstra(需运行 V 次)的核心优势。
- 支持负权边:Floyd-Warshall 可以正确处理负权边(Negative Weight Edge),但不能处理负权环(Negative Weight Cycle)。如果最终
dist[i][i] < 0,说明存在负权环。 - 原地更新:算法直接在距离矩阵上修改,不需要额外的矩阵(但路径还原需要
next矩阵)。 - 对稠密图高效:当图比较稠密(E 接近 V^2)时,Floyd-Warshall 的 O(V^3) 复杂度与运行 V 次 Dijkstra 的 O(V * (V^2)) 相当,且实现更简单。
Floyd-Warshall 与其他最短路径算法的对比:
| 算法 | 适用场景 | 时间复杂度 | 备注 |
|---|---|---|---|
| Dijkstra | 单源,非负权重 | O((V+E) log V) | 使用优先队列 |
| Bellman-Ford | 单源,支持负权边 | O(V * E) | 可检测负权环 |
| Floyd-Warshall | 全源,支持负权边 | O(V^3) | 适合稠密图 |
Floyd-Warshall 的典型应用场景:
| 应用场景 | 说明 |
|---|---|
| 交通网络(Transportation Network) | 计算所有城市之间的最短路线 |
| 网络路由(Network Routing) | 计算路由器之间的最短路径 |
| 传递闭包(Transitive Closure) | 判断图中任意两节点是否可达 |
| 负权环检测 | 如果 dist[i][i] < 0 则存在负权环 |
| 任意两点最短路(Arbitrary Pair Shortest Path) | 需要频繁查询任意两节点间的最短距离 |

浙公网安备 33010602011771号