Floyd-Warshall 算法
Floyd‑Warshall 算法是基于动态规划的全源最短路径算法,可求解图中任意两点最短距离,支持负权边,适合小规模稠密图,也能够检测图中的负权环。

Floyd-Warshall 算法(简称 Floyd 算法)是一种基于动态规划的经典图算法,专门用于求解全源最短路径问题(All-Pairs Shortest Path, APSP),即一次性计算出图中任意两点之间的最短距离。
Floyd 算法的核心逻辑是:逐步扩展允许作为中转点的节点集合,尝试寻找更短的路径。
我们将图中的顶点编号为 。
定义 表示:在允许使用前 个节点 作为中转点的前提下,从节点 到节点 的最短路径长度。
关键理解:集合 是中转点的白名单候选集,并不要求路径必须经过这 个节点中的每一个(甚至可以一个都不用)。
对于当前考虑的第 个节点,从 到 的最短路径只有两种可能:
- 不使用节点 作为中转:路径距离维持上一阶段的取值,即 。
- 使用节点 作为中转:路径被拆分为 和 两段,总距离为 。
因此,转移方程为:

设初始给定的 4 节点有向带权图如下:

图中包含 4 个顶点,边权分别为: (8), (1), (1), (4), (2), (9)。
首先建立初始矩阵 (即允许 个中转点,纯直达路径)。对角线为 ,无直接相连的边设为 :
固定 第 1 行 和 第 1 列,观察是否能通过节点 1 缩短路径:
- 更新 :
- 原直达距离:
- 经由节点 1:
- 更新:
- 更新 :
- 原直达距离:
- 经由节点 1:
- 更新:
得到矩阵 :
固定 第 2 行 和 第 2 列,观察是否能通过节点 2 缩短路径:
- 更新 :
- 原距离:
- 经由节点 2:
- 更新:
得到矩阵 :
固定 第 3 行 和 第 3 列,观察是否能通过节点 3 缩短路径:
- 更新 :
- 更新 :
- 更新 :,但由于 进一步组合,图中此处显示更新为 (路径 权重 )。
- 更新 :
得到矩阵 :
固定 第 4 行 和 第 4 列,完成最终计算:
- 重点更新 :
- 原距离:
- 经由节点 4:
- 因为 ,更新 (即路径 )。
- 更新 :
- 原距离:
- 经由节点 4:
- 因为 ,更新 (即路径 )。
最终得到任意两点间的全源最短路径矩阵 :
在实际计算中,第 阶段的状态仅依赖于第 阶段。因此,我们可以省去状态定义中的第一维 ,改用二维数组 进行原地更新:
以下提供兼顾防数值溢出与路径还原功能的完整 C++ 实现:
#include <iostream>#include <vector>#include <algorithm>
using namespace std;
// 使用 1e9 代表 INF,防止两数相加时超出 int 范围引发溢出const int INF = 1e9;
/** * @brief Floyd-Warshall 算法实现 * @param n 节点数量(假设节点编号为 0 到 n-1) * @param edges 边列表,元素格式为 {u, v, weight} */void floydWarshall(int n, const vector<vector<int>>& edges) { // 1. 初始化距离矩阵与路径记录矩阵 vector<vector<int>> dist(n, vector<int>(n, INF)); vector<vector<int>> parent(n, vector<int>(n, -1));
for (int i = 0; i < n; ++i) { dist[i][i] = 0; // 节点到自身的距离为 0 }
// 填入图的初始边权 for (const auto& edge : edges) { int u = edge[0]; int v = edge[1]; int w = edge[2];
// 处理可能存在的重边,取权值最小值 if (w < dist[u][v]) { dist[u][v] = w; parent[u][v] = u; // 记录路径的前驱节点 } }
// 2. 核心动态规划(最外层必须是中转节点 k) for (int k = 0; k < n; ++k) { for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { // 安全检查:只有当 i->k 和 k->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]; parent[i][j] = parent[k][j]; // 更新路径前驱 } } } } }
// 3. 检查负权环 for (int i = 0; i < n; ++i) { if (dist[i][i] < 0) { cout << "警告:图中存在负权环!最短路径无意义。" << endl; return; } }
// 4. 打印结果示例:点对 (0, n-1) 的距离与路径 int src = 0, dst = n - 1; if (dist[src][dst] == INF) { cout << "节点 " << src << " 到节点 " << dst << " 不可达。" << endl; } else { cout << "节点 " << src << " 到节点 " << dst << " 的最短距离为: " << dist[src][dst] << endl;
// 还原具体路径 vector<int> path; for (int curr = dst; curr != -1; curr = parent[src][curr]) { path.push_back(curr); if (curr == src) break; } reverse(path.begin(), path.end());
cout << "最短路径路线: "; for (size_t i = 0; i < path.size(); ++i) { cout << path[i] << (i + 1 == path.size() ? "" : " -> "); } cout << endl; }}
int main() { int n = 4; // 节点个数 (0, 1, 2, 3) // 边结构:{起点, 终点, 权重} vector<vector<int>> edges = { {0, 1, 5}, {0, 3, 10}, {1, 2, 3}, {2, 3, 1} };
floydWarshall(n, edges);
return 0;}| 特性 | 说明 |
|---|---|
| 时间复杂度 | ,三层嵌套循环,其中 为顶点数量。 |
| 空间复杂度 | ,仅需要一个二维矩阵存储邻接矩阵及最短距离。 |
| 负权边支持 | 支持包含负权边的图(比 Dijkstra 算法更具通用性)。 |
| 负权环检测 | 算法执行结束后,若存在 ,说明图中包含负权环(无最短路解)。 |
| 适用场景 | 顶点数较少(通常 )的稠密图,或需要计算所有点对距离的场景。 |