想象你是全城唯一的快递员小D。每天清晨从总部出发,你要给六个小区送餐,手里只有一张皱巴巴的路线图,上面标着小区之间的路和距离。你的任务朴素又硬核:找出从总部到每个小区的最短距离——这就是“单源最短路径”问题。你的做法很直接:随身带一本“待办清单”,记着每个小区目前已知的最短距离;每次从清单里挑距离最近的小区先跑一趟,到了就锁定这个数(不可能再短了);然后顺路看看,从这家出来绕到邻居家,会不会让邻居的记录变得更短?会就立刻更新。问题来了:这本“清单”怎么维护,直接决定你一天的工作量。用普通记事本一页页翻最慢,用二叉堆像整齐的卡片盒,用斐波那契堆像懒得整理的桌面,用 Radix Heap 像按公里数分格的货架。本文就借着“快递员的一天”这条主线,帮你把 Dijkstra 和三种优先队列彻底看透。

岗位关联

掌握优先队列选型,是图算法与路径规划工程的“提速钥匙”——它直接决定最短路径计算的性能量级,是算法工程师、竞赛选手和系统架构师的核心竞争力。

目录
  1. 可视化演示
  2. 算法基础:从快递员的日记本说起
  3. 优先队列原理:会自我排序的神秘清单
  4. 二叉堆实现:一丝不苟的卡片盒
  5. 斐波那契堆:懒到极致的账本大师
  6. Radix Heap 基数堆:按公里数分格的货架
  7. 性能与选型:到底该雇谁?
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示Dijkstra最短路径搜索过程,对比二叉堆/斐波那契堆/Radix Heap三种优先队列的堆结构与时间复杂度性能曲线。

本节配有交互动效(进入视口后自动加载,约 3KB)。

图搜索:Dijkstra松弛探索 紫色为源点S(起始节点) 粉色为当前探索节点 绿色为最短路径(S→B→C→T) Relax: if dist[u]+w < dist[v] then dist[v] = dist[u]+w Dijkstra贪心搜索,每次取dist最小顶点松弛邻边,绿色路径为最终最短路径

算法基础:从快递员的日记本说起

小D翻开小本本,上面记着:1号小区目前 2 公里,2号小区 5 公里,3号小区“未知”。他永远只做一件事——找到“已记录距离”里最小的那个小区,立刻出发。这一选,就是 Dijkstra 的“贪心选择”(greedy choice):眼前最近的,就是全局最近的,因为所有道路距离都是非负的,绕路不可能让已经最近的点变得更近。

在图论术语里,小区叫节点(vertex),道路叫边(edge),路上的数字叫边权(weight)。小D的日记本叫距离数组,而“绕到邻居家看看有没有更近的捷径”这个动作,教科书上叫松弛(relaxation)。松弛只有两种结果:新路线更短,则更新记录;否则原样保留。整个过程保证一件事:被标记为“已确定”的节点,它的距离值永远不再变化——因为所有可能更短的路都被排除过了。

小D的整个配送流程,就是“挑当前最近 → 锁定 → 顺路看邻居 → 更新日记本”四步循环。但他很快发现:从日记本里找“最小”如果一页页翻,一上午就废了。他需要一套能自动排序的“清单系统”——于是,优先队列闪亮登场。

if d[v]>d[u]+w(u,v), then d[v]←d[u]+w(u,v)\displaystyle \text{if } d[v] > d[u] + w(u, v), \text{ then } d[v] \leftarrow d[u] + w(u, v)
翻译成大白话:如果“先到 u 再到 v”这条路,比日记本上现在记的到 v 的距离还短,就把日记本改成新值。
  1. 当前已知距离d[v] 表示到小区 v 的现有最短记录,比如“3号小区目前 8 公里”。
    d[v]d[v]
  2. 候选新路线d[u] + w(u,v) 是“先到已确定的 u,再沿路到 v”的总距离,相当于一个绕路方案。
    d[u]+w(u,v)d[u] + w(u, v)
  3. 比较与更新候选值更小就覆盖旧值,否则保持原样;每次更新都在缩小“未知”的边界。
    d[v]←d[u]+w(u,v)d[v] \leftarrow d[u] + w(u, v)

优先队列原理:会自我排序的神秘清单

小D雇了秘书小Q专门管清单。小Q有一项绝活:清单永远保证“最重要”的卡片——也就是距离最短的那张——放在最顶上。小D任何时候伸手就能拿到,从不用翻找。这个“永远把最小放最顶”的抽象数据结构,就叫优先队列(Priority Queue)。

在 Dijkstra 里,优先队列至少干三件事:插入(insert,放一张新卡片进清单)、取出最小(extract-min,拿走最顶上那张)、降低键值(decrease-key,发现更短距离时改写卡片数字,并让它自动飞到合适位置)。不同的“整理策略”就是不同的堆,策略优劣用“操作步数”衡量——计算机科学家用大 O 记号描述它。Dijkstra 的总耗时,就是 n 次取出加上 m 次改写的总和。

同样的待办清单,有人用整齐卡片盒整理(二叉堆),有人攒一大堆等收盘才整理(斐波那契堆),有人按公里数把卡片塞进一格格的货架(Radix Heap)。接下来,我们挨个参观这三位秘书的办公方式。

A[⌊i/2⌋]≤A[i]\displaystyle A[\lfloor i / 2 \rfloor] \le A[i]
大意:在堆数组里,爸爸永远不比儿子大。因此根节点就是整个堆的最小值,取最小只需瞄一眼根。
  1. 父子下标完全二叉树按层序编号后,节点 i 的爸爸位于下标 ⌊i/2⌋。
    ⌊i/2⌋\lfloor i / 2 \rfloor
  2. 堆序约束任何父节点的值不超过子节点,所以最顶上的 A[1] 就是全局最小。
    A[⌊i/2⌋]≤A[i]A[\lfloor i / 2 \rfloor] \le A[i]
  3. 最小堆的意义取最小变成了“看根、删根”,彻底告别全表扫描。
    min⁡=A[1]\min = A[1]

二叉堆实现:一丝不苟的卡片盒

秘书小B的桌上有个透明卡片盒,卡片永远摆成“完全二叉树”形状:最上层一张,第二层两张……每张卡片只记着爸爸的位置。新卡片来时,小B先把它放到最后一排,然后让卡片“上浮”:和爸爸比大小,若比爸爸小就往上换,一路换到不再比爸爸小为止。取走最小卡片后,他把最后一张卡片移到根部,再让它“下沉”:跟两个儿子里较小的比,若比儿子大就换下去。

这套“上浮/下沉”的代价,每次最多等于树的高度。n 张卡片堆起来的完全二叉树,高度是 log₂n,所以插入和取出都是 O(log n)。在 Dijkstra 里,n 次取出约 O(n log n),m 次入堆/更新约 O(m log n),总复杂度:

卡片盒井井有条,但小B的缺点是每张卡片移动都得规规矩矩地交换位置。有没有更偷懒的办法?有——下一位秘书小F,干脆不整理了。

T(n,m)=O(mlog⁡n)\displaystyle T(n, m) = O(m \log n)
含义:对 m 条边、n 个点的图,二叉堆版 Dijkstra 的耗时与“边数 × 对数”同阶,日常工程完全够用。
  1. 取出次数n 个节点各取出一次,每次从堆顶取出并调整需 O(log n)。
    n×O(log⁡n)n \times O(\log n)
  2. 更新次数每条边最多触发一次入堆或键值更新,共 m 次 O(log n)。
    m×O(log⁡n)m \times O(\log n)
  3. 主项合并当 m ≥ n 时,n log n 被 m log n 覆盖,最终写作 O(m log n)。
    O(mlog⁡n)O(m \log n)

斐波那契堆:懒到极致的账本大师

秘书小F从不整理桌面。新订单来了,直接“啪”地把卡片甩在桌上,当成一棵孤零零的小树根——插入几乎零成本。发现某张卡片距离要改小?他直接拿笔把数字划掉,再把这张卡片连同它整棵小树“剪”下来,甩回桌面——这就是“懒策略”的精髓:平时不动,攒着。

这个“桌面”上一堆大小不一的树,就是斐波那契堆(Fibonacci Heap)。它只承诺一件事:取最小的时候认真打扫一次——把同级的树两两合并,挑最小的当根。因为有了摊还分析(amortized analysis)做保证,算总账时,每隔一段时间才来一次的大扫除成本,被平摊到每一次日常操作头上。于是“摊还”之后:插入和降低键值都是 O(1),只有取出最小是 O(log n)。Dijkstra 总复杂度:

不过,小F的桌面乱得像垃圾堆。虽然“总工作量”的理论数字漂亮,但每棵树维护起来的常数开销很大,现实中多数工程反而被小B的卡片盒吊打。千万别被渐近复杂度骗了——实战的账要另算。

T(n,m)=O(m+nlog⁡n)\displaystyle T(n, m) = O(m + n \log n)
含义:当图非常稠密时,斐波那契堆把“乘积”变成了“求和”,理论上是三种堆里最优的。
  1. m 次近乎免费的操作每次松弛 = 一次降低键值,摊还 O(1),一共 O(m)。
    m×O(1)=O(m)m \times O(1) = O(m)
  2. n 次稍贵的取出每次取出最小摊还 O(log n),n 个节点共 O(n log n)。
    n×O(log⁡n)=O(nlog⁡n)n \times O(\log n) = O(n \log n)
  3. 合并为和式两项相加得到 O(m + n log n)——这就是斐波那契堆理论价值的全部来源。
    O(m)+O(nlog⁡n)O(m) + O(n \log n)

Radix Heap 基数堆:按公里数分格的货架

第三位秘书小R更是奇才。他先问小D:你的距离是不是整数公里?得到肯定答复后,他订制了一排货架:第 0 格放“0 公里”的卡片,第 1 格放“1 公里”,第 2 格放“2~3 公里”,第 3 格放“4~7 公里”……每格覆盖的区间翻倍扩大。这就是Radix Heap(基数堆),它利用“整数距离”这个特性,把卡片按距离区间塞进不同的桶。

因为同一格里的卡片距离都在同一区间,取最小时,小R只需从左往右扫,找到第一个非空格子——里面任意一张卡片就是当前最小(同区间内部不关心顺序,反正都会被取到)。货架整理时要做“批量迁移”:把卡片从宽区间格子挪到更精确的窄区间格子。货架格子数约 O(log C),C 是最大边权;每张卡片被往左迁移的次数不超过 O(log C) 次。于是 Dijkstra 用 Radix Heap 的总代价:

小R这套玩法的唯一门槛:距离必须是非负整数,而且得预先知道大致范围。一旦满足,他送快递的速度快得像闪电;若边权是浮点数或负数,货架就会当场失灵。

T(n,m)=O(m+nlog⁡C)\displaystyle T(n, m) = O(m + n \log C)
含义:若最大边权 C 不大,log C 接近常数,Radix Heap 能让 Dijkstra 达到近乎线性的性能,是竞赛圈和路由引擎的“秘密武器”。
  1. 货架总数格子按 2 的幂划分区间,最高覆盖到最大边权 C,格子数约 ⌈log₂C⌉ 个。
    ⌈log⁡2C⌉ 个桶\lceil \log_2 C \rceil \text{ 个桶}
  2. 每节点的迁移预算一张卡片每次迁移都进入更窄的区间,最多 log C 次,n 个节点合计 O(n log C)。
    n×O(log⁡C)n \times O(\log C)
  3. 每条边仍处理一次松弛照旧执行,把新距离放进对应桶是 O(1),共 O(m)。
    m×O(1)m \times O(1)

性能与选型:到底该雇谁?

三位秘书各有绝活:小B规矩可靠,小F理论漂亮,小R条件苛刻。快递站站长拍板:看订单结构选人。若城市路网是“稀疏图”——n 个路口大约只有 n 条路,m 与 n 同阶,此时二叉堆 O(m log n) ≈ O(n log n) 已经极快,斐波那契堆的常数反而拖后腿;若图是“稠密图”——每个节点几乎直连所有其他节点,m 接近 n²,斐波那契堆的 O(m + n log n) 才真正显出含金量。

如果边权是“公里数”这类非负整数且范围有限,Radix Heap 的 O(m + n log C) 几乎线性,在路由表计算、算法竞赛中是常见杀手锏。还要牢记:Dijkstra 本身要求边权非负;一旦出现负权边,贪心前提崩塌,得换贝尔曼-福特算法。选型判据可以浓缩成:

最终快递站的配置单是:日常配送(稀疏道路网)用二叉堆;科研验证理论极限用斐波那契堆;系统里全是整数开销(比如路由器跳数)就上 Radix Heap。记住,在真实工程里,“够快、简单、Bug 少”往往比“渐近复杂度最优”更重要。

{m≈n:二叉堆m≈n2:斐波那契堆(理论更优)w∈Z,  0≤w≤C:Radix Heap 基数堆\displaystyle \begin{cases} m \approx n: & \text{二叉堆} \\ m \approx n^2: & \text{斐波那契堆(理论更优)} \\ w \in \mathbb{Z}, \; 0 \le w \le C: & \text{Radix Heap 基数堆} \end{cases}
白话版:路不多用卡片盒,路太密看理论,距离整数上货架。
  1. 稀疏图情形m≈n 时三种堆的理论差距被常数掩盖,二叉堆实现简单、缓存友好,是工程首选。
    O(mlog⁡n)O(m \log n)
  2. 稠密图情形m≈n² 时,m log n 明显大于 m + n log n,斐波那契堆的理论复杂度占优。
    O(n2log⁡n) vs O(n2+nlog⁡n)O(n^2 \log n) \text{ vs } O(n^2 + n \log n)
  3. 整数权值情形能上 Radix Heap 就上,几乎线性且常数小,直接绕开堆操作的开销。
    O(m+nlog⁡C)O(m + n \log C)

AI 应用场景

地图导航与路径规划(高德、Google Maps 底层)

阶段:图搜索与决策(路网图构建 → 最短路径查询 → 路径渲染)

解决的问题:城市路网是典型稀疏图,但节点规模达百万级。传统朴素数组版 Dijkstra 是 O(n²),地图 App 无法承受;用二叉堆实现 Dijkstra 或 A*,可在稀疏路上把单次查询压到毫秒级,支撑高峰期每秒成千上万的路线请求。

具体案例:导航 App 将道路交叉口建模为节点、路段建模为边,边权是预估通行时长。早晚高峰时,系统用实时拥堵数据更新边权,再跑一遍 Dijkstra 重新规划;堆的选择直接决定了“重新规划”能否在两三秒内完成。

网络路由协议 SPF 计算(OSPF / IS-IS)

阶段:链路状态数据库同步 → 最短路径树计算 → 路由表下发

解决的问题:路由器要周期性计算全网最短路径树,边权是接口开销(整数、范围小且固定)。Radix Heap 的整数分桶特性让 SPF 计算接近线性,大幅压缩收敛时间——从秒级降到毫秒级。

具体案例:运营商骨干网一台上千路由器的设备,OSPF 每 30 分钟重算一次 SPF;改用整数桶式优先队列后,CPU 占用显著下降,网络故障时的路由倒换时间明显缩短。

游戏 AI 寻路与机器人导航

阶段:环境建模 → 导航网格构建 → 路径搜索

解决的问题:游戏地图上成千上万个单位同时请求寻路,优先队列是热路径上的核心数据结构。稀疏导航图用二叉堆,配合 A* 的启发式函数(本质是带优先级的 Dijkstra),能保证游戏帧率稳定。

具体案例:RTS 游戏中 5000 个士兵同时寻路,底层采用二叉堆加分组寻路优化,每帧寻路预算控制在 2ms 内,玩家在千军万马中框选移动也不卡顿。

Python 演示

python73 行
import heapqimport randomimport time  def dijkstra_binary_heap(n, edges, src=0):    # 二叉堆版 Dijkstra    g = [[] for _ in range(n)]    for u, v, w in edges:        g[u].append((v, w))        g[v].append((u, w))    dist = [float('inf')] * n    dist[src] = 0    pq = [(0, src)]    while pq:        d, u = heapq.heappop(pq)        if d > dist[u]:            continue        for v, w in g[u]:            nd = d + w            if nd < dist[v]:                dist[v] = nd                heapq.heappush(pq, (nd, v))    return dist  def dijkstra_radix_heap(n, edges, src=0, max_w=100):    # 整数桶式优先队列版 Dijkstra (Dial Algorithm 思想)    g = [[] for _ in range(n)]    for u, v, w in edges:        g[u].append((v, w))        g[v].append((u, w))    dist = [float('inf')] * n    dist[src] = 0    buckets = {0: [src]}    while buckets:        cur_d = min(buckets)        node = buckets[cur_d].pop()        if not buckets[cur_d]:            del buckets[cur_d]        if cur_d > dist[node]:            continue        for v, w in g[node]:            nd = cur_d + w            if nd < dist[v]:                dist[v] = nd                buckets.setdefault(nd, []).append(v)    return dist  def random_graph(n, m, max_w=100):    # 生成无重边的随机无向图    edges = set()    while len(edges) < m:        u = random.randrange(n)        v = random.randrange(n)        if u != v and (min(u, v), max(u, v)) not in edges:            a, b = min(u, v), max(u, v)            edges.add((a, b))            yield a, b, random.randrange(1, max_w + 1)  if __name__ == '__main__':    n, m = 3000, 12000    edges = list(random_graph(n, m))    t0 = time.perf_counter()    d1 = dijkstra_binary_heap(n, edges)    t1 = time.perf_counter()    d2 = dijkstra_radix_heap(n, edges)    t2 = time.perf_counter()    print('二叉堆版本耗时 %.4f 秒' % (t1 - t0))    print('桶式版本耗时 %.4f 秒' % (t2 - t1))    print('两种算法结果一致:', d1 == d2)
目的:在同一张随机稀疏图上,对比“二叉堆”与“整数桶式”两种优先队列实现 Dijkstra 的耗时与正确性。
  1. pq = [(0, src)]把源点放进堆,距离 0 的元组位于堆顶。
  2. d, u = heapq.heappop(pq)取出当前距离最小的节点,对应 extract-min。
  3. if d > dist[u]: continue惰性删除:堆里过时的旧记录直接跳过。
  4. if nd < dist[v]: dist[v] = nd松弛:发现更短路线就立即更新距离表。
  5. heapq.heappush(pq, (nd, v))新距离入堆,等价于一次堆插入。
  6. buckets = {0: [src]}桶式队列初始化:距离 0 的节点塞进 0 号桶。
  7. cur_d = min(buckets)找最小非空桶,模拟 Radix 的“从左往右找第一格”。
  8. del buckets[cur_d]桶取空就删除,保证字典永远只含待处理的桶。
  9. buckets.setdefault(nd, []).append(v)新最短距离放进对应桶,平均 O(1)。
  10. while len(edges) < m:循环生成直到凑够 m 条无重边,保证测试公平。

常见误区

误区Dijkstra 最终输出都一样,用 `priority_queue` 就行,性能差别不大。

正解结果确实一样,但开销天差地别。朴素数组版是 O(n²),稀疏图上百万条边时比二叉堆慢几个数量级;若边权是整数,Radix Heap 又能再快数倍。同样是送快递,骑自行车和开飞机都能到达,但一天能送的件数完全不同。

为何常见:教材常以朴素数组讲解 Dijkstra,隐藏了优先队列的实现细节,读者自然误以为“队列只是细节”。

误区复杂度是 O(m + n log n) 最低,斐波那契堆肯定是最快的选择。

正解摊还复杂度的“常数因子”巨大——每棵树要维护孩子链表、标记懒惰删除、做 merge 操作,真实运行往往比二叉堆慢数倍。尤其在稀疏图上 m≈n 时,O(m log n) 与 O(m + n log n) 差距微乎其微,而常数差距却被放大。工程上几乎没人裸写斐波那契堆。

为何常见:只盯着渐进记号,忽略常数因子与实现复杂度,是算法学习者最经典的思维盲区。

误区Radix Heap 那么快,干脆所有图都用它。

正解Radix Heap 的前提是边权为非负整数且范围有限(存在上界 C)。浮点权值无法映射到整数桶,负权会破坏桶区间单调性,无界整数会让桶个数爆炸。它是“特殊场景的特种武器”,不是通用银弹。

为何常见:把一个针对整数图场景的优化,误当成了放之四海皆准的通用方案。

误区二叉堆库函数没有 decrease-key 接口,所以必须自己写斐波那契堆,否则算法不完整。

正解现代工程普遍用“惰性删除”替代 decrease-key:直接把新的 (nd, v) 推入堆,旧的 (d, v) 留作垃圾,等弹出时用 if d > dist[v]: continue 跳过。代价是堆大小退化为 O(m),但实现极简、常数小,稀疏图下依然飞快。

为何常见:教材的严格复杂度推导让人以为 decrease-key 是必选项,忽略了现实中“用空间换实现复杂度”的工程智慧。

面试题

Q1. 在 Dijkstra 算法中,每次从优先队列取出的节点应当满足什么条件?

单选题 · easy

  • A.当前距离最小的未确定节点← 正确答案
  • B.边权最小的任意节点
  • C.距离最大的已确定节点
  • D.队列中任意一个未访问节点

答案:A。Dijkstra 的贪心核心是每次取出“当前 dist 最小且尚未确定”的节点,取出即锁定。B 混淆了“边权最小”与“路径距离最小”,C 完全相反,D 若取任意节点贪心前提失效。就像快递员永远先送“已知最近”的小区,而不是随便挑一家。

Q2. 二叉堆实现的优先队列,插入和取出最小元素的时间复杂度分别为?

单选题 · easy

  • A.O(1) 和 O(n)
  • B.O(log n) 和 O(log n)← 正确答案
  • C.O(n) 和 O(1)
  • D.O(log n) 和 O(1)

答案:B。二叉堆的上浮/下沉沿树高进行,n 个节点的完全二叉树高度为 log₂n,所以插入和取最小都是 O(log n)。就像卡片盒里新卡上浮、取根后最后一张下沉,每步最多交换 log n 次。

Q3. 对于 n 个节点、m 条边的图,二叉堆版 Dijkstra 的总时间复杂度是?

单选题 · mid

  • A.O(n²)
  • B.O(m log n)← 正确答案
  • C.O(m + n log n)
  • D.O(n)

答案:B。n 次取出各 O(log n),m 次入堆/更新各 O(log n),总 O((n+m) log n);当 m ≥ n 时省略低阶项写作 O(m log n)。C 是斐波那契堆的理论复杂度,A 是朴素数组版。

Q4. 斐波那契堆中 decrease-key 的摊还时间复杂度是?

单选题 · mid

  • A.O(log n)
  • B.O(1)← 正确答案
  • C.O(n)
  • D.O(n log n)

答案:B。斐波那契堆的“懒账本”策略把改键变成“剪下子树甩回桌面”,只标记不整理;通过摊还分析,每次 decrease-key 平均 O(1)。大扫除的代价被摊到多次日常操作里,这正是 Dijkstra 得到 O(m + n log n) 的原因。

Q5. 关于 Radix Heap,下列说法错误的是?

单选题 · mid

  • A.它要求边权为非负整数
  • B.它按距离区间分桶存储节点
  • C.它可以直接处理浮点边权← 正确答案
  • D.它能让整数权值上的 Dijkstra 接近线性

答案:C。Radix Heap 的核心是“整数距离分桶”,桶区间按 2 的幂划分,浮点数无法稳定映射到桶区间,所以 C 是错误说法。A、B、D 都是 Radix Heap 的正确特性。

Q6. 稀疏图(m≈n)上,实战中最常用的优先队列是?

单选题 · mid

  • A.斐波那契堆
  • B.Radix Heap
  • C.二叉堆← 正确答案
  • D.按权值排序的链表

答案:C。稀疏图上 m≈n,二叉堆 O(m log n)≈O(n log n) 已足够快,且实现简单、缓存友好;斐波那契堆常数过大,Radix Heap 有整数限制,排序链表取出最小最坏 O(n)。工程默认选择二叉堆。

Q7. 使用二叉堆并采用“惰性删除”(新距离直接入堆,旧记录留在堆中)会导致堆的大小最多膨胀为?

单选题 · hard

  • A.O(1)
  • B.O(n)
  • C.O(m)← 正确答案
  • D.O(m log n)

答案:C。每条边可能触发一次入堆,最多 m 次 push 且其中许多是过时记录,堆里新旧数据并存,堆大小可达到 O(m)。弹出时通过 if d > dist[u]: continue 丢弃旧记录,空间换实现简单。

Q8. 下列哪些数据结构可以作为 Dijkstra 的优先队列使用?

多选题 · mid

  • A.二叉堆← 正确答案
  • B.斐波那契堆← 正确答案
  • C.Radix Heap(整数边权下)← 正确答案
  • D.无序数组

答案:ABC。二叉堆、斐波那契堆、Radix Heap 都支持“取最小 + 更新距离”,可以让 Dijkstra 高效运行;无序数组每次取最小要 O(n) 扫描,总复杂度 O(n²),仅适合稠密小图,不是实用优先队列。

Q9. 关于 Dijkstra 与优先队列的复杂度分析,下列说法正确的有?

多选题 · hard

  • A.二叉堆版总复杂度为 O(m log n)← 正确答案
  • B.斐波那契堆版总复杂度为 O(m + n log n)← 正确答案
  • C.斐波那契堆的 extract-min 摊还 O(log n)← 正确答案
  • D.Radix Heap 对浮点边权也能达到 O(m + n log C)

答案:ABC。A、B 是两种经典实现的总复杂度,C 是斐波那契堆的摊还结论,均正确。D 错误:Radix Heap 依赖整数桶划分,浮点边权无法直接使用,“整数”是它成立的前提。

Q10. 关于 Dijkstra 算法的适用条件,以下哪些说法正确?

多选题 · easy

  • A.要求边权非负← 正确答案
  • B.要求图中不能有环
  • C.可以处理正权有向图← 正确答案
  • D.要求边权必须都是正整数

答案:AC。Dijkstra 的正确性依赖非负边权,正权有向图当然可以处理。图允许存在非负环,D 把范围错误收窄——非负实数即可,不要求整数。若出现负权边,贪心锁定“当前最小”会失效,需改用 Bellman-Ford。