ruye07

导航

Dijkstra 最短路径 学习笔记

Dijkstra 最短路径 学习笔记

学习日期: 2026-05-12
前置知识: Prim 算法(Dijkstra 只比 Prim 改一行)


问题场景

从起点城市 1 出发,求到所有其他城市的最短距离。

          ⑧
    1 ─────────→ 3
    │         ╱  │  ╲
  ② │     ③ ╱   ①│   ③
    │     ╱      │    ╲
    ↓   ╱        ↓     ╲
    2 ─────────→ 4 ───→ 5
          ④           ⑥

Prim vs Dijkstra 对比

Prim Dijkstra
入堆值 (边权w, 邻居v) (dist[u] + w, v)
含义 只看"这条边本身多贵" 看"从起点走到这里累计多远"

就这一行区别!其他完全一样。


完整过程图解

初始化

dist = [∞, ∞, ∞, ∞, ∞]   (dist[1] = 0)
堆   = [(0, 1)]
已确定 = [否, 否, 否, 否, 否]

第1步:弹出 (0, 1),确定城市1

从1出发:
  1→2 距离2    0+2=2 < ∞ ✓  → 堆加 (2, 2)
  1→3 距离8    0+8=8 < ∞ ✓  → 堆加 (8, 3)

城市:     1      2      3      4      5
距离:    0✓    2      8      ∞      ∞
已确定:   ✅    否     否     否     否

堆: [(2,2), (8,3)]

第2步:弹出 (2, 2),确定城市2

从2出发:
  2→3 距离3    2+3=5 < 8 ✓  → 堆加 (5, 3)  ← 比之前(8,3)更优!
  2→4 距离4    2+4=6 < ∞ ✓  → 堆加 (6, 4)

城市:     1      2      3      4      5
距离:    0✓    2✓    5      6      ∞
已确定:   ✅    ✅    否     否     否

堆: [(5,3), (8,3), (6,4)]

城市3有两条进入路径:1→3 直接走 8,1→2→3 绕路走 5。堆自动弹出更便宜的!

第3步:弹出 (5, 3),确定城市3

从3出发:
  3→4 距离1    5+1=6  不比现6好 → 不更新
  3→5 距离3    5+3=8 < ∞ ✓  → 堆加 (8, 5)

城市:     1      2      3      4      5
距离:    0✓    2✓    5✓    6      8
已确定:   ✅    ✅    ✅    否     否

第4步:弹出 (6, 4),确定城市4

从4出发:
  4→5 距离6    6+6=12  不比现8好 → 跳过

城市:     1      2      3      4      5
距离:    0✓    2✓    5✓    6✓    8
已确定:   ✅    ✅    ✅    ✅    否

第5步:弹出 (8, 5),确定城市5

全部确定!

最短路径树

           1
           │ (2)
           2
           │ (3)
           3
          ╱ ╲
       (1)   (3)
        ╱     ╲
       4       5

最终结果:

起点 → 终点 最短距离 路径
1 → 1 0 原地
1 → 2 2 1→2
1 → 3 5 1→2→3(不是 1→3 直走 8)
1 → 4 6 1→2→3→4 或 1→2→4
1 → 5 8 1→2→3→5

模板代码

import heapq

def dijkstra():
    n, m = map(int, input().split())

    # 建图 —— 邻接表(和 Prim 完全一样)
    g = [[] for _ in range(n + 1)]
    for _ in range(m):
        u, v, w = map(int, input().split())
        g[u].append((w, v))
        # 无向图加双向,有向图只加一边 ← 注意!

    INF = 10 ** 18
    dist = [INF] * (n + 1)
    dist[1] = 0                             # 起点距离为 0
    pq = [(0, 1)]                           # (累计距离, 点)

    while pq:
        d, u = heapq.heappop(pq)
        if d > dist[u]:                     # 不是最优路径,跳过
            continue
        for w, v in g[u]:
            nd = dist[u] + w                # ← 唯一和 Prim 不同的地方!
            if nd < dist[v]:
                dist[v] = nd
                heapq.heappush(pq, (nd, v))

    for i in range(1, n + 1):
        print(dist[i] if dist[i] != INF else -1, end=' ')

对比总表

特性 Prim Dijkstra
目标 连通所有点的最小总花费 起点到各点的最短距离
入堆值 边权 w 累计距离 dist[u] + w
结果 ans(总花费) dist[](各点最短路)
贪心依据 割性质 最短路径的子路径也最短
判别条件 cnt == n 全连通 堆为空 或 所有点已确定
负权边 可以 不行!(该用 SPFA/Bellman-Ford)

Dijkstra 为什么不能有负权边?

因为一旦一个点"确定"了最短距离,后面就不能再改了。如果有负权边,后面可能出现更短的路径,推翻已确定的结论。

反例:  A → B: 2,  A → C: 3,  C → B: -100

第1步: A 确定, B 暂定 2, C 暂定 3
第2步: B 确定 = 2   ← 但其实 A→C→B = 3+(-100) = -97 更短!
       B 已经被错误确定了。

posted on 2026-05-12 17:09  ruye07  阅读(16)  评论(0)    收藏  举报