交互式图论学习
交互式图论学习
Guest User
Using app without sign in
所有点对最短路径计算器
找到所有顶点对之间的最短路径
选择算法并生成步骤以开始可视化
Floyd-Warshall 算法一次运行即可计算加权图中所有顶点对之间的最短路径。它是图上动态规划的经典范例,只要没有负环,就能处理负边权。
算法对每个顶点 k 迭代,并对每一对 (i, j) 询问:经过 k 时从 i 到 j 的路径是否更优。更新式 dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]) 应用于所有对,逐个扩大允许的中间顶点集合。对顶点的三重嵌套循环给出 O(V 的三次方) 时间和 O(V 的平方) 空间,适用于至多数千节点的稠密图。
Floyd-Warshall 回答路线规划中的全顶点对距离查询、计算关系的传递闭包、求图的直径,并支持同时对所有货币对进行套利检测。它是考查图上动态规划直觉的热门面试题。
三层嵌套循环和一行更新语句。全部精妙之处都在循环顺序上:k 必须是最外层循环,弄错这一点正是经典错误。
FloydWarshall(图):
dist = V 乘 V 的矩阵,全部置为无穷大
对每个结点 v: dist[v][v] = 0
对每条边 (u,v,w): dist[u][v] = w
对 k 遍历所有结点: // 中转点
对 i 遍历所有结点: // 起点
对 j 遍历所有结点: // 终点
若 dist[i][k] + dist[k][j] < dist[i][j]:
dist[i][j] = dist[i][k] + dist[k][j]
next[i][j] = next[i][k] // 用于还原路径
// 存在负环当且仅当某个 v 满足 dist[v][v] < 0不变式是:在 k 这一轮结束之后,dist[i][j] 表示只允许使用前 k 个结点作为中转时,从 i 到 j 的最短路径。要让这一点成立,k 就必须在最外层:正是它每次把允许的中转结点集合扩大一个。把 k 放到内层同样会终止,而且给出看起来合理的数字,这恰恰是该错误极难被发现的原因。
在一个小型有向图上运行 Floyd-Warshall,观察某个表项如何随着允许中转结点集合的扩大而两次改进。
示例图: 有向边 A 到 B (3)、A 到 C (8)、B 到 C (2)、B 到 D (7) 和 C 到 D (1)。
从 A 出发的最终距离是到 B 为 3、到 C 为 5、到 D 为 6。A 到 D 这一项改进了两次,从无穷大到 10 再到 6,这直接展示了它的分层特性:k = C 那一轮之所以能找到更好的路线,正是因为 k = B 那一轮已经先改进了 A 到 C。这种依赖关系就是 k 必须放在最外层循环的原因。
时间: O(V^3) · 空间: O(V^2)
三层嵌套循环遍历所有结点,恰好产生 V 的立方次迭代,每次做常数级的工作。既没有提前退出,也不依赖边的数量,因此这个算法在稀疏图上和在稠密图上的代价完全相同。空间是 V 乘 V 的距离矩阵;若还想还原路径而不只是路径长度,则再加一个矩阵。实践中 V 的立方在几千个结点以内还可以接受;到了 5000 个结点就是一千二百五十亿次操作,此时在稀疏图上从每个结点跑一次 Dijkstra,即 O(V·E·log V),反而成为更好的选择。
Floyd-Warshall 胜在稠密与简洁,在大规模稀疏图上则明显落败。
| 替代算法 | 以下情况更合适 | 代价 |
|---|---|---|
| 从每个结点跑 Dijkstra | 稀疏图且没有负权。当 E 远小于 V 的平方时快得多。 | O(V·E·log V) |
| Johnson 算法 | 带负权的稀疏图。先用 Bellman-Ford 重赋权,再从每个结点跑 Dijkstra。 | O(V·E + V^2·log V) |
| 从每个结点跑 BFS | 图无权重,你只需要所有点对之间的跳数。 | O(V·(V + E)) |
| 传递闭包 | 你只关心可达性而不是距离。同样的三重循环换成布尔或运算,也就是 Warshall 最初的算法。 | O(V^3) |