ruye07

导航

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行核心)

注意事项

  1. 初始化dist[i][i] = 0,别忘
  2. 重边 — 取 min,两地间可能多条路
  3. INF — 用 10**18,别用 float('inf')(做加法会溢出)
  4. 负权 — Floyd 可以有负权边,但不能有负权环(会无限绕圈)

posted on 2026-05-12 18:25  ruye07  阅读(20)  评论(0)    收藏  举报