一文彻底搞懂Floyd算法
2026-07-06 19:42:49
发布于:浙江
一文彻底搞懂Floyd算法(弗洛伊德算法)
1. 算法本质
Floyd算法(又称Floyd-Warshall算法)是解决所有顶点对之间最短路径的动态规划算法。
由Robert Floyd于1962年提出(基于Stephen Warshall的传递闭包思想)。
核心思想:动态规划 + 中间点松弛,逐步允许更多顶点作为中转点。
2. 适用场景与前提
- 适用:带权有向图或无向图(边权可为负,但不能有负权环)。
- 不适用:存在负权环的图(可检测但无法求最短路径)。
- 目标:求任意两点
(i, j)之间的最短路径长度。
3. 核心思想(动态规划定义)
设 dp[k][i][j] 表示从 i 到 j,只允许经过前 k 个顶点(编号 0~k-1)作为中转点的最短路径长度。
状态转移方程:
dp[k][i][j] = min(dp[k-1][i][j], dp[k-1][i][k] + dp[k-1][k][j])
- 不经过顶点
k:保持原路径dp[k-1][i][j] - 经过顶点
k:拆分为i→k和k→j两段
空间优化:只需一个二维矩阵 dist[][],原地更新即可:
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])
4. 算法步骤(三层循环)
- 初始化距离矩阵
dist[][]:dist[i][i] = 0- 若存在边
i→j,则dist[i][j] = weight(i,j) - 否则
dist[i][j] = INF
- 三重循环:
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] - 检查负权环:若存在
dist[i][i] < 0,则图中有负权环。
5. 图解示例(手动推演)
图:
顶点:0, 1, 2
边:0→1(3), 0→2(8), 1→2(2), 2→1(-1)(注意负权但不构成负环)
初始距离矩阵:
0 1 2
0 [ 0, 3, 8 ]
1 [ ∞, 0, 2 ]
2 [ ∞, -1, 0 ]
逐步更新(k=0,1,2):
| 中转点k | 更新后 dist[1][2] | 更新后 dist[2][1] | 其他变化 |
|---|---|---|---|
| 初始 | 2 | -1 | - |
| k=0 | min(2, ∞+∞)=2 | min(-1, ∞+∞)=-1 | 无 |
| k=1 | min(2, 0+2)=2 | min(-1, -1+0)=-1 | 无 |
| k=2 | min(2, -1+2)=1 | min(-1, 2-1)=-1 | dist[1][2] 更新为 1 |
最终距离矩阵:
0 1 2
0 [ 0, 3, 5 ] # 0→2 经过 1:3+2=5
1 [ ∞, 0, 1 ] # 1→2 经过 2→1→2:-1+2=1
2 [ ∞, -1, 0 ]
6. 代码实现
Python 实现(基础版)
def floyd_warshall(n, edges):
INF = float('inf')
# 初始化距离矩阵
dist = [[INF] * n for _ in range(n)]
for i in range(n):
dist[i][i] = 0
# 填充边权
for u, v, w in edges:
dist[u][v] = w # 若有重边可取 min
# 三重循环核心
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]
# 检测负权环
for i in range(n):
if dist[i][i] < 0:
print("存在负权环!")
return None
return dist
# 示例使用
edges = [
(0, 1, 3), (0, 2, 8),
(1, 2, 2), (2, 1, -1)
]
result = floyd_warshall(3, edges)
for row in result:
print(row)
# 输出:
# [0, 3, 5]
# [inf, 0, 1]
# [inf, -1, 0]
Python 实现(带路径还原)
def floyd_with_path(n, edges):
INF = float('inf')
dist = [[INF] * n for _ in range(n)]
nxt = [[-1] * n for _ in range(n)]
for i in range(n):
dist[i][i] = 0
nxt[i][i] = i
for u, v, w in edges:
if w < dist[u][v]: # 处理重边
dist[u][v] = w
nxt[u][v] = v
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]
nxt[i][j] = nxt[i][k] # 关键:路径经过 k
# 检测负权环
for i in range(n):
if dist[i][i] < 0:
print("存在负权环!")
return None, None
return dist, nxt
def print_path(nxt, u, v):
if nxt[u][v] == -1:
return "无路径"
path = [str(u)]
while u != v:
u = nxt[u][v]
if u == -1:
return "路径断裂"
path.append(str(u))
return " -> ".join(path)
# 使用示例
dist, nxt = floyd_with_path(3, edges)
print(print_path(nxt, 0, 2)) # 0 -> 1 -> 2
C++ 实现(基础版)
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int INF = 1e9;
vector<vector<int>> floydWarshall(int n, vector<vector<int>>& edges) {
// 初始化距离矩阵
vector<vector<int>> dist(n, vector<int>(n, INF));
for (int i = 0; i < n; i++) {
dist[i][i] = 0;
}
// 填充边权
for (auto& edge : edges) {
int u = edge[0], v = edge[1], w = edge[2];
dist[u][v] = min(dist[u][v], w); // 处理重边
}
// 三重循环核心
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] + dist[k][j] < dist[i][j]) {
dist[i][j] = dist[i][k] + dist[k][j];
}
}
}
}
// 检测负权环
for (int i = 0; i < n; i++) {
if (dist[i][i] < 0) {
cout << "存在负权环!" << endl;
return {};
}
}
return dist;
}
int main() {
int n = 3;
vector<vector<int>> edges = {
{0, 1, 3}, {0, 2, 8},
{1, 2, 2}, {2, 1, -1}
};
vector<vector<int>> result = floydWarshall(n, edges);
// 输出结果
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (result[i][j] == INF)
cout << "INF ";
else
cout << result[i][j] << " ";
}
cout << endl;
}
// 输出:
// 0 3 5
// INF 0 1
// INF -1 0
return 0;
}
C++ 实现(带路径还原)
#include <iostream>
#include <vector>
#include <algorithm>
#include <stack>
using namespace std;
const int INF = 1e9;
pair<vector<vector<int>>, vector<vector<int>>>
floydWithPath(int n, vector<vector<int>>& edges) {
vector<vector<int>> dist(n, vector<int>(n, INF));
vector<vector<int>> nxt(n, vector<int>(n, -1));
for (int i = 0; i < n; i++) {
dist[i][i] = 0;
nxt[i][i] = i;
}
for (auto& edge : edges) {
int u = edge[0], v = edge[1], w = edge[2];
if (w < dist[u][v]) {
dist[u][v] = w;
nxt[u][v] = v;
}
}
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] + dist[k][j] < dist[i][j]) {
dist[i][j] = dist[i][k] + dist[k][j];
nxt[i][j] = nxt[i][k];
}
}
}
}
// 检测负权环
for (int i = 0; i < n; i++) {
if (dist[i][i] < 0) {
cout << "存在负权环!" << endl;
return {{}, {}};
}
}
return {dist, nxt};
}
string getPath(vector<vector<int>>& nxt, int u, int v) {
if (nxt[u][v] == -1) return "无路径";
vector<int> path;
path.push_back(u);
while (u != v) {
u = nxt[u][v];
if (u == -1) return "路径断裂";
path.push_back(u);
}
string result = to_string(path[0]);
for (int i = 1; i < path.size(); i++) {
result += " -> " + to_string(path[i]);
}
return result;
}
int main() {
int n = 3;
vector<vector<int>> edges = {
{0, 1, 3}, {0, 2, 8},
{1, 2, 2}, {2, 1, -1}
};
auto [dist, nxt] = floydWithPath(n, edges);
cout << "0 -> 2 的最短路径: " << getPath(nxt, 0, 2) << endl;
// 输出: 0 -> 2 的最短路径: 0 -> 1 -> 2
return 0;
}
7. 复杂度分析
| 指标 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(V³) | 三重循环,V 为顶点数 |
| 空间复杂度 | O(V²) | 需要存储距离矩阵 |
| 优化可能性 | 较低 | 适合稠密图,V ≤ 200~300 |
对于稀疏图,可考虑对每个顶点运行 Dijkstra(堆优化),复杂度 O(V·(E log V))。
8. 正确性证明(简要)
- 归纳假设:第
k轮循环后,dist[i][j]表示从i到j,只允许经过前k个顶点(编号 0~k-1)的最短路径。 - 归纳步骤:考虑第
k轮,新路径要么不经过k(旧值),要么经过k(拆分为i→k和k→j,均只使用前k-1个顶点)。 - 由动态规划最优子结构性质,可保证最终结果正确。
9. 与Dijkstra算法的对比
| 特性 | Floyd算法 | Dijkstra算法 |
|---|---|---|
| 目标 | 多源(所有点对) | 单源(一个起点) |
| 算法类型 | 动态规划 | 贪心 |
| 时间复杂度 | O(V³) | O((V+E) log V)(堆优化) |
| 负权边支持 | ✅ 支持(无负权环) | ❌ 不支持 |
| 负权环检测 | ✅ 可检测 | ❌ 不能 |
| 适用场景 | 稠密图、所有点对最短路 | 稀疏图、单源最短路 |
| 空间 | O(V²) | O(V+E) |
10. 常见误区与注意事项
- 中转点循环必须在外层:
k在外层是核心,若交换循环顺序会导致错误。 - INF 选择:必须足够大,防止
INF + INF溢出(C++ 中1e9较安全)。 - 有向图与无向图:无向图看作两条方向相反的有向边处理。
- 负权环检测:若最终
dist[i][i] < 0,表示存在负权环,最短路径无定义(可无限减小)。 - 不可达点:保持
INF,表示无路径。 - 重边处理:初始化时取
min权重。
11. 实际应用场景
- 交通网络:求任意两个城市之间的最短行车距离。
- 社交网络:计算用户之间的最短「关系距离」。
- 路由协议:OSPF 中计算全拓扑最短路径(需考虑链路状态)。
- 传递闭包:求有向图的连通性(将边权设为 1,判断可达性)。
- 游戏开发:计算地图中所有点对之间的最短路径。
12. 优化技巧
- 剪枝优化:若
dist[i][k] == INF,跳过内层循环。 - 对称矩阵优化:无向图只需计算
j >= i部分。 - 使用
short或int:根据权重范围选择合适数据类型。 - 并行化:
k外层无法并行,但i,j内层可并行化。
// 剪枝优化示例(C++)
for (int k = 0; k < n; k++) {
for (int i = 0; i < n; i++) {
if (dist[i][k] == INF) continue; // 剪枝
for (int j = 0; j < n; j++) {
if (dist[k][j] == INF) continue; // 剪枝
if (dist[i][k] + dist[k][j] < dist[i][j]) {
dist[i][j] = dist[i][k] + dist[k][j];
}
}
}
}
13. 总结
| 特点 | 说明 |
|---|---|
| 算法类型 | 动态规划 |
| 适用图 | 有向图/无向图,可含负权边(无负权环) |
| 时间复杂度 | O(V³) |
| 空间复杂度 | O(V²) |
| 优势 | 实现简单、支持负权边、多源全路径 |
| 劣势 | 对大规模图(V>500)运行较慢 |
一句话记忆:
三层循环
k,i,j,每次尝试用k当中转点来松弛i→j的距离,最终得到所有点对的最短路径。
这里空空如也

















有帮助,赞一个