在大屏或电脑上体验更佳——动画和操作区域更宽敞。
输入

Dijkstra 算法

Dijkstra 求非负边权图中,从一个源点到所有其他顶点的最短距离。它维护暂定距离,每次从优先队列取出距离最小的未确定顶点,确定其距离,再松弛出边。

可以把边长看成连接图钉的绳长:从源点提起,近处图钉先离开桌面。算法用距离计算表达这种由近及远的顺序。关键条件是所有边权非负,后续路径只能增加成本,不能绕回来降低已经确定的最小距离。有负边时应考虑 Bellman-Ford;本课负边预设会展示错误发生的位置。

Dijkstra 如何工作

状态包括 dist、visited 和优先队列。源点距离为 0,其余为 ∞;visited 记录距离已确定的顶点;队列存储 (distance, node),每次取最小值。

取出并确定。 弹出未访问顶点 u 后,标记已访问。此时 dist[u] 就是真实最短距离:通往其他未访问顶点的路线仍需越过距离不小于它的搜索前沿,而非负边不能再把成本减小。

松弛。 对边 u → v、权重 w,比较 dist[u] + w < dist[v]。若成立,更新 dist[v],将 u 记为前驱,把新 (dist[v], v) 入队。若不成立,保持原值。

普通二叉堆的懒更新不查找并修改旧条目,而是插入更小的新条目。旧条目成为过期项,之后弹出时若顶点已在 visited,直接丢弃。它们是省去 decrease-key 索引维护的代价。

Dijkstra 逐步演示

默认加权有向图有 A–F 六个顶点,从 A 开始。边为 A→B (2)、A→C (5)、B→C (1)、B→D (7)、C→D (2)、D→E (1)、D→F (3)、E→F (2)。

  1. 初始化 dist[A]=0,其余为 ∞。队列 [(0,A)]。
  2. 确定 A。松弛后 B 为 2,C 为 5。队列 [(2,B), (5,C)]。
  3. 确定 B,距离 2。B→C 提供 2+1=3,把 C 从 5 降到 3;两条边 A→B→C 比一条边 A→C 更便宜,这是只计边数的 BFS 无法判断的。B→D 将 D 设为 9。队列 [(3,C), (5,C), (9,D)],C 同时有新旧条目。
  4. 确定 C,距离 3。C→D 提供 3+2=5,将 D 从 9 降为 5。队列 [(5,C), (5,D), (9,D)]。
  5. 弹出 (5,C),C 已确定,丢弃。队列 [(5,D), (9,D)]。
  6. 确定 D,距离 5。松弛后 E 为 6,F 为 8。队列 [(6,E), (8,F), (9,D)]。
  7. 确定 E,距离 6。E→F 提供 6+2=8,等于旧值,不更新。队列 [(8,F), (9,D)]。
  8. 确定 F,距离 8,没有出边。队列 [(9,D)]。
  9. 弹出过期的 (9,D) 并丢弃,队列空,结束。

最终距离为 A:0, B:2, C:3, D:5, E:6, F:8。确定顺序 A、B、C、D、E、F 对应距离 0、2、3、5、6、8,始终非递减。弹出 C 的 3 时,其余条目都至少为 3,非负边不会把后续路径降到它之下。

时间与空间复杂度

操作次数单次成本总成本
二叉堆入队、出队O(V + E)O(log V)O((V + E) log V)
松弛检查O(E)O(1)O(E)

在本课这种简单图模型下,时间为 O((V + E) log V),空间为 O(V + E)。每条边最多触发一次成功松弛,懒更新堆累计插入 O(E) 个条目;dist 和 visited 占 O(V),堆占 O(E)。严格说堆操作依赖堆大小,为 O(log E),简单图中可写成 O(log V)。

使用支持 decrease-key 的 Fibonacci 堆,理论时间可达到 O(E + V log V),但维护和常数成本可能抵消收益。

边界情况

  • 不可达顶点不入队、不确定,距离保持 ∞。
  • 单个顶点以 0 入队,弹出并确定后结束。
  • 距离相同时,先确定哪一个都不改变最终距离。本课按顶点标识打破平局,以固定轨迹。
  • 零权边合法,顶点可连续以相同距离确定。
  • 只求一个目标时,可在该目标被确定的瞬间停止,其余队列无需处理。

常见错误

  1. 用于负权图。 负边预设先把 B 确定为 2,之后 C→B 权重 −5 才给出 A→C→B 的成本 −2。B 已锁定,改进被拒绝,最终报告 2 而非 −2,画面用红环标出 B。调整队列不能恢复已失效的前提。
  2. 试图修改已确定距离。 在正确的非负权模型中不应出现严格改进;若出现,应检查边权和实现,而非继续使用原来的确定性证明。
  3. 用 BFS 比较不同边权。 BFS 最小化边数,只有各边成本相同等条件下才等价于最低总成本。
  4. 漏掉过期检查。 if u in visited: continue 防止通过旧条目重复处理顶点,保持本课懒删除实现的逻辑。
  5. 入队就标记确定。 入队只表示发现路线,弹出成为当前最小候选时才能确定。

展示中的简化

优先队列画成排序行,便于阅读;真实二叉堆只保证堆顶最小,其余部分有堆序而非全序。展示代码跳过已确定邻居的松弛(v not in visited)。其他实现可省略这项检查并依靠弹出时的过期检查,在非负权下距离一致。更改负权处理方式需要重新证明,不能继续使用 Dijkstra 的原有保证。