Archipelago

Lec 4伸展树与自调整二叉树

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

Lec 4 伸展树与自调整二叉搜索树(Splay Trees & Self-Adjusting Search Trees)

对应核心文献:Sleator, D. D., & Tarjan, R. E. (1985). Self-adjusting binary search trees. Journal of the ACM.

TL;DR

  • 自调整哲学与零额外开销:完全摒弃 AVL 树与红黑树所需的平衡因子或颜色位,通过每次访问时自发将目标节点向上旋转(Splaying)至根部,实现均摊 O(log⁡n)O(\log n) 查找。
  • Zig-Zig 双旋转折半长链:伸展操作在同侧祖先场景下坚持“先旋父节点再旋子节点”,从根本上将退化长链路径长度减半,克服了朴素单旋转反复退化为长链的缺陷。
  • 静态最优性与动态最优性猜想:伸展树的势能分析自然蕴含熵界,能自适应局部热点访问达到信息论最优;其动态最优性猜想是算法理论界四十年来的最高未解之谜之一。

自调整数据结构的设计动机与哲学

在传统平衡二叉搜索树(如 AVL 树、红黑树、Treap)中,维持平衡需要付出清晰的代价:

  1. 显式元数据开销:每个节点必须存储高度差(平衡因子)或颜色位,占用内存字长;
  2. 严苛的最坏情况维护:每次插入或删除后,必须沿着回溯路径执行复杂的条件分支判断与旋转,代码实现繁复;
  3. 无法利用访问的局部性(Locality of Reference):在现实系统中,数据的访问分布往往极不均匀(例如 80% 的请求集中于 20% 的热点数据)。传统平衡树对任何节点的查找时间恒为 Θ(log⁡n)\Theta(\log n),无法利用频繁访问热点来压低实际延迟。

Sleator 与 Tarjan(1985)提出了自调整二叉搜索树(Self-Adjusting Binary Search Tree,即伸展树 Splay Tree): 树中不存储任何平衡元数据,也不保证任何单次操作的最坏情况树高,但在每次访问某个节点后,通过一系列精心编排的旋转将其推升至树根。这一自发的局部调整足以在均摊意义下保证最优性能,并自动将热点数据吸附至根部浅层。

伸展操作(Splaying)与核心旋转规则

朴素单旋转的陷阱

在伸展树诞生前,研究者曾尝试使用朴素的单旋转(Move-to-root by simple rotations)将访问的节点一步步旋转到根节点。

然而,如果初始树退化为一条长为 nn 的单向链表,连续访问最深处的叶节点:单次单旋转虽然将该叶子提升至树根,但原链表上的其他节点仅仅被翻转并横向挂载,整棵树并未被实质性压扁。连续按特定逆序访问节点会导致每次访问均消耗 Θ(n)\Theta(n),使得总均摊复杂度恶化为 Θ(n2)\Theta(n^2)。

伸展操作的三种旋转步

设当前待提升的节点为 xx,xx 的父节点为 pp,pp 的父节点(xx 的祖父节点)为 gg。伸展操作重复执行以下步骤,直至 xx 成为整棵树的根节点:

1. Zig 步(单旋转,终止步)

  • 触发条件:pp 已经是整棵树的根节点(xx 没有祖父节点 gg)。
  • 动作:对边 (x,p)(x, p) 执行一次标准的单旋转(左旋或右旋),使 xx 成为树根。

2. Zig-Zig 步(同向双旋转,压平核心)

  • 触发条件:xx 与 pp 位于同侧(xx 是 pp 的左孩子且 pp 是 gg 的左孩子;或者 xx 是 pp 的右孩子且 pp 是 gg 的右孩子)。
  • 关键动作:先旋转父节点 pp 关于祖父节点 gg,再旋转节点 xx 关于父节点 pp!
  • 重要几何效果:这一反直觉的旋转顺序直接将从 gg 到 xx 的长路径折叠,使路径上所有节点的深度大致减半,从而真正压扁了树的形态。
       g                 p               x
      / \               / \             / \
     p   D   ===>      x   g   ===>    A   p
    / \               / \ / \             / \
   x   C             A  B C  D           B   g
  / \                                       / \
 A   B                                     C   D

3. Zig-Zag 步(异向双旋转)

  • 触发条件:xx 与 pp 位于异侧(例如 xx 是 pp 的右孩子,而 pp 是 gg 的左孩子)。
  • 动作:先旋转 xx 关于 pp(将其转化为同侧关系),再旋转 xx 关于 gg。这与 AVL 树的标准双旋转完全一致。

访问引理(Access Lemma)与均摊分析

伸展树的优雅完全建立在其势能分析之上。

权重、子树和与秩(Rank)的定义

为整棵树中的每个节点 xx 赋予一个任意的正实数权重 w(x)>0w(x) > 0:

  • 加权子树规模:s(x)=∑y∈subtree(x)w(y)s(x) = \sum_{y \in \mathrm{subtree}(x)} w(y)
  • 节点的秩(Rank):r(x)=log⁡2s(x)r(x) = \log_2 s(x)
  • 全局势能函数:Φ(T)=∑x∈Tr(x)\Phi(T) = \sum_{x \in T} r(x)

注意:这里的权重 w(x)w(x) 纯粹是均摊分析的数学工具,伸展树的数据结构代码中根本不需要存储和维护权重!

访问引理(Access Lemma)

引理:对根为 tt 的伸展树中的任意节点 xx 执行伸展操作,其均摊时间满足:

c^≤3(r(t)−r(x))+1\hat{c} \le 3(r(t) - r(x)) + 1

其中 r(x)r(x) 为伸展开始前 xx 的秩,r(t)r(t) 为伸展完成后根节点的秩。

核心数学不等式(对数的凹性)

对于任意正数 a,ba, b,若 a+b≤ca + b \le c,则根据对数函数的凹性:

log⁡2a+log⁡2b≤2log⁡2(a+b2)≤2log⁡2c−2\log_2 a + \log_2 b \le 2 \log_2 \left(\frac{a + b}{2}\right) \le 2 \log_2 c - 2

关键情形证明概要

设单步旋转前各节点的秩为 rr,旋转后各节点的秩为 r′r'。单次双旋转的实际耗时记为 2。

  • 对于 Zig-Zag 步: 旋转后 xx 取代了 gg 的位置,故 r′(x)=r(g)r'(x) = r(g)。且显然 s′(p)+s′(g)≤s′(x)s'(p) + s'(g) \le s'(x)。 势能变化为 ΔΦ=r′(x)+r′(p)+r′(g)−(r(x)+r(p)+r(g))≤r′(p)+r′(g)−2r(x)\Delta \Phi = r'(x) + r'(p) + r'(g) - (r(x) + r(p) + r(g)) \le r'(p) + r'(g) - 2r(x)。 利用凹性不等式:r′(p)+r′(g)≤2r′(x)−2r'(p) + r'(g) \le 2r'(x) - 2。 代入得均摊代价: c^=2+ΔΦ≤2+2r′(x)−2−2r(x)=2(r′(x)−r(x))≤3(r′(x)−r(x))\hat{c} = 2 + \Delta \Phi \le 2 + 2r'(x) - 2 - 2r(x) = 2(r'(x) - r(x)) \le 3(r'(x) - r(x))

  • 对于 Zig-Zig 步: 旋转后 r′(x)=r(g)r'(x) = r(g),且有 s(x)+s′(g)≤s′(x)s(x) + s'(g) \le s'(x)。 类似地可推导出: c^=2+ΔΦ≤3(r′(x)−r(x))\hat{c} = 2 + \Delta \Phi \le 3(r'(x) - r(x))

将伸展操作路径上的所有 Zig-Zig 和 Zig-Zag 步的均摊代价累加,中间项裂项相消(Telescoping),最后加上至多一步 Zig 步(代价 ≤3(r′(x)−r(x))+1\le 3(r'(x) - r(x)) + 1),即完成访问引理的证明。

伸展树的优越性质与定理集群

通过为节点赋予不同的数学权重分配,访问引理能够推导出一系列惊人的理论性质:

1. 平衡定理(Balance Theorem)

令所有节点权重恒为 w(x)=1w(x) = 1。 则总权重 W=nW = n,s(t)=n  ⟹  r(t)=log⁡2ns(t) = n \implies r(t) = \log_2 n。由于对任意节点 s(x)≥1  ⟹  r(x)≥0s(x) \ge 1 \implies r(x) \ge 0。 单次伸展均摊时间满足:

c^≤3log⁡2n+1=O(log⁡n)\hat{c} \le 3 \log_2 n + 1 = O(\log n)

这证明了:没有任何平衡因子的伸展树,在均摊意义下完全能够达到标准平衡二叉树的 O(log⁡n)O(\log n) 性能。

2. 静态最优性定理(Static Optimality Theorem)

设每个元素 ii 的访问频次(或概率)为 pip_i。为每个节点赋予权重 w(i)=piw(i) = p_i。 单次访问元素 ii 的均摊代价为:

c^i≤3(log⁡2W−log⁡2pi)+1=O(1+log⁡21pi)\hat{c}_i \le 3\left(\log_2 W - \log_2 p_i\right) + 1 = O\left(1 + \log_2 \frac{1}{p_i}\right)

对包含 mm 次访问的序列,总耗时为:

O(m(1+∑i=1npilog⁡21pi))=O(m(1+H(P)))O\left(m \left(1 + \sum_{i=1}^n p_i \log_2 \frac{1}{p_i}\right)\right) = O\left(m (1 + H(P))\right)

其中 H(P)H(P) 是访问分布的信息熵(Entropy)! 这表明:伸展树在任意访问序列上的表现,不劣于由已知概率预先静态构建的最优二叉搜索树(Optimal BST),且伸展树是在完全不知道概率分布的情况下在线自发达成的!

3. 工作集定理(Working Set Theorem)

每次访问元素 xx 时,设自 xx 上一次被访问以来,其间被访问的不同元素总数为 kk。 伸展树执行本次访问的均摊代价为:

O(log⁡k)O(\log k)

这意味着,如果程序维护一个局部热点工作集,伸展树的访问开销只取决于工作集的大小,而不是整棵树的大小 nn。

4. 动态指引定理(Dynamic Finger Theorem)

若连续访问两个键值分别为 xx 和 yy 的节点,伸展树的均摊访问代价仅为:

O(log⁡∣x−y∣+1)O(\log |x - y| + 1)

在有序扫描或局部跳跃访问时,伸展树几乎展现出双向链表般的近常数性能。

动态最优性猜想(Dynamic Optimality Conjecture)

在所有关于自调整二叉树的理论探讨中,最著名的莫过于 Sleator 与 Tarjan 在 1985 年提出的动态最优性猜想:

猜想内容:是否存在一个全局常数 CC,使得对于任意给定的访问序列 SS,在线自调整的伸展树所花费的总代价,不超过任何能够预知未来的最优离线自调整二叉搜索树算法(Offline Optimal BST)所需代价的 CC 倍?

该猜想等价于断言:伸展树是一个 O(1)O(1)-竞争比(Competitive Ratio)的在线算法。 尽管四十年来数学家与理论计算机科学家(包括 Demaine 等人提出的 Tango Tree 达到 O(log⁡log⁡n)O(\log\log n) 竞争比)取得了重大进展,但关于原始伸展树是否具备 O(1)O(1) 动态最优性,至今仍是理论计算机科学界最著名的未解猜想之一。

基本集合操作的伸展实现

借助 Splay(x) 操作,二叉搜索树的所有常用操作都变得极其优雅统一:

  • Find(x):标准 BST 查找。找到节点后执行 Splay(x) 移至根;若未找到,则对访问路径上的最后一个节点执行伸展。
  • Insert(x):先查找 xx 并伸展。此时树根为 xx 的前驱或后继。以树根为界将树断开,将 xx 作为新根插入。
  • Delete(x):查找并执行 Splay(x) 将其提升到树根。将其左右子树断开;在左子树中查找最大元素并伸展(此时该最大元素无右孩子),将原右子树直接挂接为其右子树。
  • Split(x) / Join(T1, T2):伸展天然支持在 O(log⁡n)O(\log n) 均摊时间内将树按键分裂或合并。

我的理解

我的理解

自调整是将“静态结构约束”升华为“动态时序适应”: AVL 树和红黑树代表了经典的结构力学思维:通过在每个节点砌筑刚性的几何约束(高度差 ≤1\le 1、黑高度恒定),确保任何时候都四平八稳。但这种刚性无法感知外界访问的温度,即使同一个热点被访问一万次,它依然深埋在树底。

伸展树则展现了热力学式的生命力:彻底放弃对瞬时形态的执念,允许在对抗性序列下暂时退化为长链。但关键在于 Zig-Zig 步中“先旋父再旋子”的精妙几何重组,使得每一次对深渊的探寻都伴随着对整条路径的“压平改造”。热点频繁浮动于根部,冷点退居边缘。它不靠外在的平衡指标,而仅仅依靠与访问流的共舞,就自发逼近了信息论的熵界。