Archipelago

Lec 11整数数据结构:模型、前驱、vEB 树、x-fast 树与 y-fast 树

讲义完成更新于 2026-09-17

Lec 11 整数数据结构:模型、前驱、vEB 树、x-fast 树与 y-fast 树(Integer Data Structures: Models, Predecessor, vEB, x-fast & y-fast Trees)

对应 MIT 6.851 Lecture 11 · 讲师:Erik Demaine · 核心文献:van Emde Boas (FOCS 1975), Willard (IPL 1983)

TL;DR

  • 本讲探讨非基于比较的整数数据结构,在 Transdichotomous Word RAM 模型(字长 w≥log⁡nw \ge \log n)下求解前驱问题(Predecessor Problem)。
  • 经典 van Emde Boas 树(vEB 树)通过递归将宇宙规模开方(u→uu \to \sqrt{u}),利用内外分层与空集常数检查实现 O(log⁡log⁡u)=O(log⁡w)O(\log\log u) = O(\log w) 的前驱与更新,但朴素结构空间高达 O(u)O(u)。
  • Willard 提出的 x-fast 树结合 Trie 树与层级哈希表,利用树高的二分搜索实现 O(log⁡w)O(\log w) 查询;进一步通过两级间接寻址(Indirection)构建 y-fast 树,达成 O(n)O(n) 最优空间与 O(log⁡w)O(\log w) 均摊操作时间。

一、计算模型与前驱问题定义

在传统的基于比较的模型中,集合元素的有序维护与前驱查询受限于 Ω(log⁡n)\Omega(\log n) 的下界。然而在实际计算机硬件中,数据以固定长度的二进制字(Word)存储,允许按位进行逻辑与算术运算。利用字的内部并行性与直接寻址能力,我们可以突破比较模型的瓶颈。

1.1 Transdichotomous Word RAM 模型

::: definition Transdichotomous Word RAM 模型

  1. 字长约定:计算机内存被划分为字长为 ww 位的单元,要求字长 w≥log⁡nw \ge \log n(即机器字足以作为指针寻址整个输入规模 nn 的内存空间)。
  2. 支持原语:每个原语操作在 O(1)O(1) 时间内完成,包括标准的 C 语言整数运算:加减乘除(+,−,×,/+,-,\times,/)、按位逻辑运算(AND, OR, XOR, NOT)以及左右位移(<<, >>)。
  3. 空间衡量:通常以使用的机器字(Word)数量来衡量空间复杂度。 :::

1.2 前驱问题(Predecessor Problem)

维护一个容量为 nn 的动态整数集合 S⊆U={0,1,…,u−1}S \subseteq \mathbb{U} = \{0, 1, \dots, u-1\},其中全集大小 u=2wu = 2^w。 数据结构需支持以下核心操作:

  • Insert(x)\text{Insert}(x):向 SS 中添加元素 xx。
  • Delete(x)\text{Delete}(x):从 SS 中移除元素 xx。
  • Predecessor(x)\text{Predecessor}(x):返回 SS 中小于 xx 的最大元素 max⁡{y∈S∣y<x}\max \{y \in S \mid y < x\}。
  • Successor(x)\text{Successor}(x):返回 SS 中大于 xx 的最小元素 min⁡{y∈S∣y>x}\min \{y \in S \mid y > x\}。

目标是将查询与更新时间从 O(log⁡n)O(\log n) 降低至关于字长 ww 的对数级别:O(log⁡w)=O(log⁡log⁡u)O(\log w) = O(\log\log u)。

二、van Emde Boas 树(vEB 树)

Peter van Emde Boas 于 1975 年提出的 vEB 树是整数前驱领域最具里程碑意义的结构,其设计核心是宇宙规模开方递归。

2.1 递归分解与位拆分

对于全集规模为 u=2wu = 2^w 的整数 xx,其二进制表示长度为 ww 位。我们可以将 xx 拆分为高半部分和低半部分:

  • high(x)=⌊x/u⌋\text{high}(x) = \lfloor x / \sqrt{u} \rfloor(高 w/2w/2 位,指示所属的簇索引)。
  • low(x)=x mod u\text{low}(x) = x \bmod \sqrt{u}(低 w/2w/2 位,指示簇内部的偏移量)。
  • index(i,j)=iu+j\text{index}(i, j) = i \sqrt{u} + j(由簇号 ii 与簇内偏移 jj 重组原始整数)。

2.2 树结构与 Min/Max 设计

一个规模为 uu 的 vEB 结构包含:

  1. summary\text{summary}:一棵规模为 u\sqrt{u} 的 vEB 树,记录哪些子簇(cluster)当前非空。
  2. cluster\text{cluster}:一个包含 u\sqrt{u} 个子指针的数组,第 ii 个指针指向一棵规模为 u\sqrt{u} 的 vEB 树,存储所有 high(x)=i\text{high}(x) = i 的元素。
  3. min⁡\min 与 max⁡\max 属性:
    • 记录当前结构中的最小值与最大值。
    • 核心设计绝妙之处:min⁡\min 元素仅存储在该层节点的属性中,不递归插入到下层的任何子簇内;而 max⁡\max 既保存在该属性中,同时也实际存在于对应的子簇中。
                       vEB(u)
             ┌─────────────────────────┐
             │ min: 2        max: 61   │
             └────────────┬────────────┘
                          │
           ┌──────────────┴──────────────┐
           ▼                             ▼
    summary: vEB(√u)               clusters: array[√u] of vEB(√u)
 ┌─────────────────────┐       ┌──────────┬──────────┬─────┬──────────┐
 │ 追踪非空 cluster 号 │       │ cluster[0]│cluster[1]│ ... │cluster[k]│
 └─────────────────────┘       └──────────┴──────────┴─────┴──────────┘

2.3 操作实现与复杂度分析

查询操作 Successor(x)\text{Successor}(x)

  1. 若 x<min⁡x < \min,直接返回 min⁡\min。
  2. 考察 xx 所在的子簇 i=high(x)i = \text{high}(x):
    • 若 xx 小于该子簇的最大值(即 low(x)<cluster[i].max⁡\text{low}(x) < \text{cluster}[i].\max),说明其后继仍在当前簇内,递归调用 Successor\text{Successor} 进入 cluster[i]\text{cluster}[i]。
    • 否则,后继必定位于之后的某个非空簇中。通过 summary.Successor(i)\text{summary}.\text{Successor}(i) 在 O(1)O(1) 递归步内找到下一个非空簇号 jj,并直接返回 index(j,cluster[j].min⁡)\text{index}(j, \text{cluster}[j].\min)。
  3. 任何分支在每层均只做至多一次子结构递归:
T(u)=T(u)+O(1)  ⟹  T(2w)=T(2w/2)+O(1)  ⟹  T(u)=O(log⁡log⁡u)=O(log⁡w)T(u) = T(\sqrt{u}) + O(1) \implies T(2^w) = T(2^{w/2}) + O(1) \implies T(u) = O(\log\log u) = O(\log w)

插入操作 Insert(x)\text{Insert}(x)

  1. 若当前结构为空(min⁡=nil\min = \text{nil}),直接令 min⁡=max⁡=x\min = \max = x,立即返回。
  2. 若 x<min⁡x < \min,交换 xx 与 min⁡\min(确保旧的 min⁡\min 继续向下插入,新的 min⁡\min 停留在当前层)。
  3. 检查 xx 所属子簇 cluster[high(x)]\text{cluster}[\text{high}(x)] 是否为空:
    • 若为空:首先递归调用 summary.Insert(high(x))\text{summary}.\text{Insert}(\text{high}(x));然后由于该子簇此前为空,将 xx 放入子簇仅需更新该子簇的 min⁡=max⁡=low(x)\min = \max = \text{low}(x),耗时 O(1)O(1),无需进一步递归!
    • 若非空:直接递归调用 cluster[high(x)].Insert(low(x))\text{cluster}[\text{high}(x)].\text{Insert}(\text{low}(x))。
  4. 同样保证了每层至多产生一次规模为 u\sqrt{u} 的递归,单次插入严格为 O(log⁡w)O(\log w)。

2.4 空间优化:从指数空间到线性空间

朴素 vEB 树为每一层分配大小为 u\sqrt{u} 的静态指针数组。由于空间递归方程为:

S(u)=(u+1)S(u)+O(u)S(u) = (\sqrt{u} + 1) S(\sqrt{u}) + O(\sqrt{u})

其总空间复杂度为 O(u)=O(2w)O(u) = O(2^w),对大字长(如 w=64w=64)完全无法接受。

哈希动态化:改用动态散列表(如 Cuckoo Hashing)存储非空簇指针。只有在子簇插入首个元素时才分配对应节点。经过哈希优化后,空子簇不占存储,总节点数与集合元素量成正比,空间降至 O(n)O(n),且查询期望时间保持 O(log⁡w)O(\log w)。

三、x-fast 树(x-fast Trees)

Willard 在 1983 年提出了基于 Trie 树与层级哈希的确定性解法,将树高二分与前缀匹配结合。

3.1 结构定义

  1. 底层满二叉 Trie:构建一棵高度为 ww 的完全二叉字典树。根节点深度为 0,叶子节点深度为 ww。
  2. 叶子双向链表:所有实际存在的集合元素对应深度为 ww 的叶子,这些叶子按从小到大的顺序由双向链表串联。
  3. 层级哈希表:对于树的每一层 i∈[0,w]i \in [0, w],维护一个哈希表 LiL_i。哈希表中仅存储该层所有拥有后代叶子节点的内部节点(以从根到该节点的路径前缀作为键)。
        Level 0 (根)          [ L0: {root} ]
            /    \
        Level 1               [ L1: {0, 1} ]
        /   \     / \
      ...   ...  ... ...
      /       \
  Leaf(4) <-> Leaf(9) <-> Leaf(15)  (双向链表)

3.2 前驱查询:对层级进行二分搜索

在 ww 层的 Trie 中,要找到 xx 的前驱,关键在于定位 xx 与集合中已有元素在 Trie 中的最长公共前缀(Lowest Common Ancestor, LCA)。

  1. 在层数序列 [0,1,…,w][0, 1, \dots, w] 上进行二分查找(Binary Search on Levels):
    • 取中间层 mid=⌊(low+high)/2⌋mid = \lfloor (low + high) / 2 \rfloor。
    • 提取 xx 的前 midmid 位前缀,查询哈希表 LmidL_{mid} 是否存在该前缀。
    • 若存在:说明在深度 midmid 处有后代节点与 xx 匹配,最长前缀至少在深度 midmid,令 low=midlow = mid。
    • 若不存在:说明最长前缀深度小于 midmid,令 high=mid−1high = mid - 1。
  2. 二分查找耗时 O(log⁡w)O(\log w) 步,最终确定最深存在祖先节点 vv。
  3. 节点 vv 必然恰有一个分支缺失(即 xx 离开已有路径的分叉点)。利用 vv 维护的后代极值指针跳转到底层叶子,并在叶子链表上左移或右移一步,即在 O(1)O(1) 时间内确定 xx 的精确前驱与后继!
  4. 查询总时间:O(log⁡w)O(\log w)。

3.3 空间与更新代价

  • 空间:每个插入的元素在每一层最多贡献一个祖先节点,深度为 ww。因此 nn 个元素在整棵树中最多产生 O(nw)O(nw) 个节点。总空间为 O(nw)O(nw)。
  • 更新:插入或删除一个元素需沿路径更新 ww 个哈希表和叶子指针,耗时 O(w)O(w)。

四、y-fast 树与两级间接寻址(Indirection)

x-fast 树在查询时间上达到了优秀的 O(log⁡w)O(\log w),但其空间开销 O(nw)O(nw) 偏大,且更新时间 O(w)O(w) 较慢。Willard 运用**间接寻址(Indirection)**技术,构造出在空间和时间上均达到最优的 y-fast 树。

4.1 间接寻址架构

  1. 分块划分:
    • 将 nn 个元素划分为 Θ(n/w)\Theta(n / w) 个连续的有序小组(Group)。
    • 每个小组的大小维持在 [w/2,2w][w/2, 2w] 之间。
  2. 底层局部平衡树:
    • 每个小组内部使用一棵传统的红黑树(或 AVL 树)存储其实际元素。
    • 节点总数为 Θ(w)\Theta(w),因此组内红黑树的高度为 O(log⁡w)O(\log w)。
  3. 顶层代表元索引:
    • 从每个小组中选出一个代表元(如该组的最大元素)。
    • 将所有代表元(共 Θ(n/w)\Theta(n / w) 个)存入一棵顶层 x-fast 树 中。
           [ 顶层 x-fast 树 ]
         (仅存 Θ(n/w) 个代表元)
         /         |         \
   Rep_1         Rep_2        Rep_k
     │             │            │
     ▼             ▼            ▼
 ┌───────┐     ┌───────┐    ┌───────┐
 │ Group │     │ Group │    │ Group │   <- 底层红黑树 (每组大小 Θ(w))
 └───────┘     └───────┘    └───────┘

4.2 性能全面优化

空间复杂度:达到严格 O(n)O(n)

  • 顶层 x-fast 树仅包含 N=O(n/w)N = O(n / w) 个元素,根据 x-fast 树空间分析,其占用空间为 O(N⋅w)=O((n/w)⋅w)=O(n)O(N \cdot w) = O((n/w) \cdot w) = O(n) 个字。
  • 底层各组红黑树节点总和为 nn,占用空间 O(n)O(n)。
  • 总空间:O(n)O(n) 机器字!

查询复杂度:严格 O(log⁡w)O(\log w)

  1. 在顶层 x-fast 树中查询 xx 的代表元后继,耗时 O(log⁡w)O(\log w)。
  2. 根据代表元定位到对应的底层候选红黑树(以及相邻的小组,至多常数个)。
  3. 在大小为 O(w)O(w) 的平衡二叉树内搜索 xx 的前驱,耗时 O(log⁡w)O(\log w)。
  4. 总查询时间:O(log⁡w)+O(log⁡w)=O(log⁡w)O(\log w) + O(\log w) = O(\log w)。

更新复杂度:均摊 O(log⁡w)O(\log w)

  1. 插入新元素时,首先找到对应小组,向该组红黑树插入,耗时 O(log⁡w)O(\log w)。
  2. 若某小组元素个数超过 2w2w,将其分裂为两个大小均为 ww 的小组,并向顶层 x-fast 树插入一个新代表元:
    • x-fast 树插入耗时 O(w)O(w)。
    • 但小组从规模 ww 增长到 2w2w 至少需要经过 Ω(w)\Omega(w) 次插入!
    • 将 O(w)O(w) 的分裂开销均摊到这 Ω(w)\Omega(w) 次插入上,每次更新的分摊成本仅为 O(1)O(1)。
  3. 删除操作对称地通过合并与借位实现,同样具有均摊 O(1)O(1) 的顶层开销。
  4. 总均摊更新时间:O(log⁡w)O(\log w)。

五、方案综合对比

数据结构查询时间更新时间空间复杂度核心机制
平衡二叉搜索树O(log⁡n)O(\log n)O(log⁡n)O(\log n)O(n)O(n)基于比较的三向分支
朴素 vEB 树O(log⁡w)O(\log w)O(log⁡w)O(\log w)O(2w)O(2^w)宇宙规模开方递归、Min/Max 隔离
动态哈希 vEB 树O(log⁡w)O(\log w)O(log⁡w)O(\log w) 期望O(n)O(n)动态散列压缩空子簇
x-fast 树O(log⁡w)O(\log w)O(w)O(w)O(nw)O(nw)层级哈希表 + 树高二分查找
y-fast 树O(log⁡w)O(\log w)O(log⁡w)O(\log w) 均摊O(n)O(n) 最优两级间接寻址:x-fast 索引 + 组红黑树

六、我的理解

整数前驱结构的精妙之处在于对“规模”这一维度的变换:

  1. vEB 树的分治发生在“数值位宽”上:普通二分把元素数量减半,而 vEB 把位宽 ww 减半(u→uu \to \sqrt{u}),将原本属于搜索树的几何级数压缩为超几何级数,从而把 log⁡u\log u 降为 log⁡log⁡u\log\log u。
  2. y-fast 树的间接寻址是权衡艺术的典范:x-fast 树虽然空间膨胀了 ww 倍、更新慢了 ww 倍,但查询极快。间接寻址通过在底层挂接大小为 ww 的粗粒度块,巧妙地让 ww 倍的开销与 1/w1/w 的代表元数量精准对消!原本高昂的 O(w)O(w) 重构代价被 Ω(w)\Omega(w) 的局部操作均摊吸收,最终以极简的形式达成了时空双优。 :::