Floyd 全源最短路径 学习笔记
Floyd 全源最短路径 学习笔记
学习日期: 2026-05-12
前置知识: Dijkstra(Dijkstra 是单源,Floyd 是全源)
问题场景
想知道任意两个城市之间的最短距离。Dijkstra 只能从一个起点出发,跑 n 遍太慢(稠密图下)。
┌───2───┐
│ │
①│ ③│
│ │
1───────3
④
核心思想
Floyd 只做一件事:尝试用每个点当"中转站"。
问:1 到 3 最短是多少?
直走: 1→3 = 4
绕行: 1→2→3 = 1+3 = 4 没更好
但如果 2→3 = 1:
直走: 1→3 = 4
绕行: 1→2→3 = 1+1 = 2 更短!→ 更新
递推公式:
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])
直走 绕经中转站k
为什么 k 必须放最外层?
k=1: 只允许用点1中转 → 更新所有经过1的路径
k=2: 允许用点1和2中转 → 在上一步基础上,加入点2
k=3: 允许用点1,2,3中转
...
k=n: 允许用全部点中转 → 得到真正的全源最短路
递推依赖: 用节点 k 更新时,dist[i][k] 和 dist[k][j] 必须已经是"只经过 ≤k-1 号点"的最优解。k 在最外层才能保证这个顺序。
k 在内层会怎样?
错误写法 for i,j,k:
i=1, j=3, k=2: 1→2→3 更新成功
i=2, j=4, k=5: 2→5→4 —— 但此时 dist[2][5] 可能还不是最优!
用不是最优的中间结果更新 → 后续结果全错
k 只有在外层,递推才收敛;k 在内层,递推就乱了。
模板代码
import sys
n, m = map(int, input().split())
INF = 10 ** 18
# 初始化
dist = [[INF] * (n + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
dist[i][i] = 0 # 自己到自己是0
# 读边
for _ in range(m):
u, v, w = map(int, input().split())
dist[u][v] = min(dist[u][v], w) # 重边取最小
dist[v][u] = min(dist[v][u], w) # 无向图加两边
# 核心:三重循环
for k in range(1, n + 1): # 中转站 — 必须在最外层
for i in range(1, n + 1): # 起点
for j in range(1, n + 1): # 终点
if dist[i][k] != INF and dist[k][j] != INF:
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])
# 输出任意两点最短距离
# dist[u][v] 就是 u→v 的最短路,INF 表示不可达
对比 Dijkstra
| 特性 | Dijkstra | Floyd |
|---|---|---|
| 目标 | 单源最短路 | 全源最短路(任意两点) |
| 数据结构 | 堆 | 二维数组 |
| 时间复杂度 | O(m log n) | O(n³) |
| 空间复杂度 | O(n + m) | O(n²) |
| 适用 n 范围 | n ≤ 10⁵ | n ≤ 300 |
| 负权边 | 不能 | 可以(但不能有负环) |
| 实现难度 | 中等 | 极简(4行核心) |
注意事项
- 初始化 —
dist[i][i] = 0,别忘 - 重边 — 取
min,两地间可能多条路 - INF — 用
10**18,别用float('inf')(做加法会溢出) - 负权 — Floyd 可以有负权边,但不能有负权环(会无限绕圈)
浙公网安备 33010602011771号