Lec 6整数优先队列:van Emde Boas 树与基数堆
当前课程暂无后续已收录内容。
Lec 6 整数优先队列:van Emde Boas 树与基数堆(Integer Priority Queues: van Emde Boas Trees & Radix Heaps)
TL;DR
- 全域开方分治实现双重对数界:van Emde Boas 树将大小为 的全域划分为 个大小为 的子簇,通过顶层摘要与递归寻址,将搜索与动态维护时间降至严格的 。
- 极值缓存确保单次递归执行:将子树最小值与最大值显式缓存在节点中,且最小值不递归向下存储;对空树的插入与提取退化为 ,从而在结构上保证每次操作最多只触发一次非平凡的递归调用。
- 基数堆(Radix Heaps)在单调场景下的轻量优化:利用边权上界 建立 个按二进制位跨度划分的动态桶,配合单调性势能摊还,实现减键 、提取最小值 ,达到 的最短路性能。
整数优先队列与超对数加速
在标准比较模型中,信息论给出了二叉搜索树与优先队列的天然下界:任何基于两两比较的动态查找结构,单次操作至少需要 时间。
然而在计算机的字 RAM 模型(Word RAM)中,键值是长度为 比特的非负整数,全域规模 。计算机硬件的算术逻辑单元(ALU)不仅支持比较,还支持在 时间内执行按位与、位移、加减法与数组基地址变址寻址。
这种计算能力的释放催生了两类突破性结构:
- van Emde Boas 树(1977):面向全域 上的全功能动态优先队列与前驱/后继查找,将复杂度推进至 ;
- 基数堆(Radix Heaps,Ahuja et al. 1990):针对 Dijkstra 单调优先队列场景,以极其紧凑的二进制动态桶结构实现 最短路径。
van Emde Boas 树的核心架构
全域递归分解:高半字与低半字
设全域大小为 (若非二次幂可通过补齐比特位向上取整)。任何属于 的整数 在二进制下均由高半字和低半字构成:
- 决定了 属于哪一个规模为 的子簇(Cluster);
- 决定了 在该子簇内部的具体位置;
- 复合重构公式为:。
节点组成与极值缓存设计
一个规模为 的 van Emde Boas 树节点 包含以下组件:
min与max缓存:- 存储当前以 为根的结构中所包含的最小键值与最大键值;
- 核心约束(Crucial Invariant):
V.min中存储的元素绝不向下递归存储在任何子簇中! - 如果 只包含一个元素,则该元素仅记录在
V.min中,其V.max亦等于该元素,而下属的所有子簇全部为空。
- 子簇数组
cluster[0 ... \sqrt{U}-1]:- 包含 个指针,每个指针指向一个规模为 的子 vEB 树。
- 摘要结构
summary:- 是一棵独立的、规模为 的 vEB 树;
- 记录所有子簇的状态:当且仅当子簇
cluster[i]包含至少一个元素(除去全局V.min外)时,下标 才存在于V.summary中。
单递归定理与操作算法
vEB 树之所以能达到 ,根本在于其递归关系式:
令全域位宽为 ,则 。 实现该递推式的铁律是:任何公共操作在每一层分解中,绝不能产生两次以上规模为 的递归调用。
1. 查找前驱与后继(Successor)
要在树 中查找严格大于 的最小元素 Successor(V, x):
- 边界检查:若 为空或 ,返回不存在;若 ,直接返回 (耗时 )。
- 簇内查找:
- 分解 ,;
- 检查 所在子簇的极大值:若 ,说明后继必在该子簇内部,递归调用
Successor(V.cluster[h], l);
- 跨簇查找:
- 若 ,说明后继位于后续的某一个非空子簇中;
- 在摘要树中查询下一个非空子簇号:;
- 后继元素就是该非空簇中的最小值:;
- 返回合成键值 。
在上述逻辑中,分支 2 与分支 3 是严格互斥的,单次查询至多发生一次 规模的递归,总运行时间严格为 。
2. 插入元素(Insert)
向树 中插入元素 :
- 空树插入:若当前 为空(即
V.min == null),令 ,直接返回(耗时 )。 - 极小值交换:若 ,将 与 互换。此时原先的 成为待插入到子簇中的新元素。
- 分解与递归插入:
- 设待插入元素为 ,分解为 与 ;
- 分支 A(目标子簇为空):
- 若 ,说明该子簇之前为空;
- 必须将子簇号 插入到摘要树中:
Insert(V.summary, h); - 随后将 插入目标子簇:
Insert(V.cluster[h], l)。 - 关键洞察:向空子树插入元素只需更新其
min和max,在步骤 1 立即以 结束!因此这里虽然有两次形式上的函数调用,但只有对summary的调用是真正的非平凡递归!
- 分支 B(目标子簇非空):
- 直接递归调用
Insert(V.cluster[h], l),而无需触碰V.summary。递归调用同样只有一次!
- 直接递归调用
- 更新极大值:若 ,更新 。
3. 提取最小值与删除元素(Delete)
删除操作同样巧妙利用极值缓存避免多重递归:
- 若删除的是
V.min,需要从非空子簇中找出第二小的元素提升为新的V.min; - 通过 得到首个非空子簇;
- 取得新最小值 ,将其填入
V.min,随后从该子簇中递归删除 ; - 若删除导致该子簇变空,则递归从
summary中剔除 。由于子簇变空时其大小为 1,底层删除也是常数时间完成。 - 总删除时间严格维持为 。
空间压缩:从 到
朴素 vEB 树预先分配所有子簇指针,递归空间递推为 ,解得空间为 。当字长 时,,物理上无法预分配。
优化方案: 利用动态完美哈希或哈希表按需分配子簇(仅当子簇非空时才建立映射)。这种被称为 x-fast trie 与 y-fast trie(Willard 1983)的衍生结构,在期望 或最坏 线性空间下完整保留了 的查询时间。
基数堆(Radix Heaps)与单调单源最短路
在单源最短路径(Dijkstra)问题中,边权上限为 。vEB 树虽然能给出 的极快时间,但指针层级较多且依赖动态分配。Ahuja、Mehlhorn、Orlin 与 Tarjan(1990)提出了基于二进制位动态重分配的基数堆(Radix Heaps)。
结构设计与二进制动态区间
设图的最大边权为 ,定义 。基数堆维护 个桶 。
每个桶 关联一个闭区间 ,负责接纳键值落在该区间内的所有节点。桶的区间大小随着当前堆顶提取的最小值单调动态调整:
- 桶 0:始终只容纳唯一确定的单个键值(当前全局最小值);
- 桶 1:容纳下一个连续的单个键值;
- 桶 ():区间长度为 ,覆盖从 到 的键值范围。
操作流程与桶的动态重分发(Rebucketing)
Insert(x)/Decrease-Key(x):- 根据新键值直接通过位运算(异或与最高有效位查找 )定位所在桶;
- 直接插入对应桶的双向链表,耗时 。
Extract-Min():- 若桶 0 非空,直接弹出其中的节点();
- 若桶 0 为空,寻找最小的非空桶 ;
- 线性扫描桶 找出其中的最小键值 ;
- 动态重分发(Rebucketing):
- 将当前基准基数更新为 ;
- 重新划分桶 的区间界限(以 为起点重新展开指数区间);
- 将桶 中的所有剩余元素依次取出,根据其新差值重新分发落入到更低编号的桶中!
势能分析与复杂度
为什么重分发不会拖垮时间?关键依然是单调性:
- 在 Dijkstra 推进过程中,全局最小值单调上升;
- 每次某个元素从桶 中被倒出重新投递时,它必然落入编号严格小于 的更低桶中!
- 每个元素从初次进入堆到最终被弹出,最多只能从高编号桶向下移动到低编号桶。由于总桶数仅为 ,任何单个元素在整个算法生命周期内的总下移次数至多为 次!
将重分发下推代价均摊到元素自身:
Decrease-Key:均摊代价 ;Extract-Min:均摊代价 ;- 单源最短路径总运行时间为:
基数堆无需复杂的指针树递归,仅使用一维数组加双向链表,在工程实践与理论分析中均展现出极致的简洁与高效。
优先队列技术横向综合对比
| 数据结构 | 最小键查询 | 提取最小值 | 减小键值 | 插入操作 | 空间复杂度 | 最短路总时间 |
|---|---|---|---|---|---|---|
| 二叉堆 (Binary Heap) | ||||||
| Fibonacci 堆 | ||||||
| Dial 算法 | ||||||
| Denardo-Fox 惰性字典树 | ||||||
| 基数堆 (Radix Heap) | ||||||
| van Emde Boas 树 |
我的理解
我的理解
vEB 树与基数堆是对“位”(Bit)信息的两种极高维利用: 传统数据结构将数字视为黑盒原子,只能通过大于小于进行探针式试探;而整数算法将数字视为二进制的几何坐标。
van Emde Boas 树的非凡在于将维度折半——从 位空间降解为 的递归笛卡尔积,并借助“极值不递归存储”这一神来之笔,硬生生把两次分支砍成了一次有效调用,让时间的增长速度比对数还要慢一个层级()。
基数堆则是在单调性约束下的实用主义巅峰:它不强求在全域上处处开花,而是用对数个指数扩张的二进制桶充当“差值过滤器”。全局最小值的单向推移赋予了元素下落的单向势能,使得复杂的基数排序转化为平滑的单调均摊流动。