“最小生成树与贪心给出正确性,优先队列只负责实现“当前最轻接入边”。在静态邻接表的比较/RAM 模型中,二叉堆给确定性最坏 $O(E\log V)$;Fibonacci 堆利用摊还 $O(1)…”
“Prim 算法在连通无向带权图中维护已纳入顶点集合 $S$,每次选择跨越割 $(S,V\setminus S)$ 的最小权边,把其外端点加入。割性质保证该安全边可属于某棵最小生成树。算法用优…”
Priority queue · Priority queue ADT
按键的优先次序反复访问并移除当前最小元素的抽象数据类型。
最小优先队列是一种抽象数据类型:每个元素带有来自全序键空间的优先级,核心操作为 insert(x,k)、find-min() 与 extract-min(),分别插入带键元素、读取最小键元素、读取并删除最小键元素。算法还可能要求 decrease-key(h,k')、delete(h) 或 meld(Q_1,Q_2);这些操作需要什么句柄、是否允许重复键、空队列如何报告,都属于接口规格。最大优先队列只需反转键的次序。
同一接口可由不同表示实现,典型最坏时间如下;表中有序数组把最小元放在便于从末端删除的一侧,二叉堆的 decrease-key 已知目标位置。
| 实现 | insert |
find-min |
extract-min |
decrease-key |
|---|---|---|---|---|
| 无序数组 | ||||
| 有序数组 | ||||
| 二叉最小堆 |
这些复杂度描述实现而非 ADT 本身;接口只规定可观察行为,除非成本界被明确写入规格。
优先队列不维护完整排序,只维护“现在最该处理谁”这一条信息。普通队列按到达时间服务,优先队列按键服务;两个元素键相同时,谁先出来通常没有默认保证,除非接口另外要求稳定性。它也不是二叉堆的同义词:堆是一种实现,正如数组或平衡树也可实现同一组操作。把接口与表示分开,才能根据工作负载选择结构——插入极多而只在末尾取一次最小值时,无序数组可能比堆更合适。
这一抽象尤其适合“候选会不断出现,但每一步只需要当前最好者”的算法。它避免提前为尚未需要的相对次序付费,又把取最优候选的规则集中在一个接口中。
在非负权图的 Dijkstra 算法中,队列元素是尚未定型的顶点,键是当前暂定距离。每轮 extract-min 取出距离最小的顶点;松弛一条边若降低了邻点距离,具有句柄的实现可调用 decrease-key。此时键不是顶点固有属性,而是算法状态的一部分,随着松弛逐步减小。
若库的优先队列没有 decrease-key,可在距离降低时插入一条新记录,取出记录时再检查它是否等于当前距离;旧记录成为惰性丢弃项。这仍能得到正确的 Dijkstra 实现,但队列中会同时存在同一顶点的多个版本,操作次数与空间上界也随之改变,不能把它当作原接口的零成本替换。
优先级相同不意味着先进先出。若任务调度必须在同优先级内保持提交顺序,可把键扩展为 (priority, sequence_number),或要求稳定优先队列。另一个边界是偏序键:若某些键不可比较,find-min 可能有多个互不支配的候选;本页的单一最小元接口依赖全序,偏序前沿需要另一个 ADT。
二叉堆以紧凑数组和堆序实现常用的对数时间版本。Dijkstra 与 Prim 算法的复杂度会随优先队列实现改变;扫描线则用它按坐标管理尚未发生的几何事件。只有把各算法的操作次数与所选实现成本相乘,复杂度结论才完整。
若算法需要频繁合并队列,meld 的成本会成为选择二项堆等结构的理由;若只需小规模固定键域,桶或原生数组可能已经足够。数据结构应由实际使用的操作组合决定,而不是由“优先队列通常就是堆”的惯性决定。