平衡树小结

基础——二叉搜索树(BST)

二叉搜索树的定义:每个节点左儿子权值小于它的权值,右儿子权值大于它的权值。

用 BST 可以很容易维护一个有序的集合(或多重集),并支持插入删除、找排名、找数值、找前驱后继等操作。

在随机数据的情况下,BST 期望树高是 \(O(\log n)\) 的,但是极端情况会退化成链。

于是,平衡树应运而生。

\(\text{Treap}\)

\(\text{Treap}\) 是一种比较基础的平衡树。\(\text{Treap=Tree+Heap}\)

除了满足 BST,\(\text{Treap}\) 给每个节点一个附加权值 \(pri\),并要求 \(pri\) 满足堆的性质。

可以证明,在 \(pri\) 随机的情况下,\(\text{Treap}\) 树高为 \(O(\log n)\)

\(\text{Treap}\) 的实现方式有有旋与无旋两种。

有旋式 \(\text{Treap}\)

以小根堆为例。先放一张图:

treap-zig

左旋操作(zig)右旋同理

每次插入后递归向上处理,查看是否有出现儿子节点 \(pri\) 小于父亲节点 \(pri\) 的情况。如果\(pri_{rc}<pri_{x}\),则左旋 \(x\) 使 \(rc\) 转到 \(x\) 位置;如果 \(pri_{lc}<pri_{x}\),则右旋 \(x\) 使 \(lc\) 转到 \(x\) 位置。

删除分三种情况:无子树、有一棵子树、有两棵子树。前两种都容易处理。第三种则可以通过 zigzag,把左右儿子中 \(pri\) 较小的旋上来,把自己“沉”下去,转化为前两种情况。

无旋式 \(\text{Treap}\)

有两种操作:splitmerge

split 是可以根据某一个键值 \(k\) 或者一个大小 \(sz\) 来将树 \(i\) 分裂为两个树 \(x,\ y\)。其中 \(x\) 中所有值 \(\le k\)\(y\) 中所有值 \(>k\)。按照大小分裂也类似。

merge 是将两棵树 \(x,\ y\) 合并为一棵新树,要求 \(x\) 中所有元素都小于 \(y\) 中所有元素。

平衡树的基本操作都可以使用 splitmerge 配合使用实现。

\(\textbf{Treap}\) 可以可持久化!

\(\text{Splay}\)

\(\text{Splay}\) 是一种保证均摊复杂度 \(O(\log n)\) 的平衡树。

\(\text{Splay}\)rotate

\(\text{Splay}\) 的旋转是把一个节点旋转到它的父亲的位置,splay 操作就是把一个节点不断向上旋转至根(或某个节点的儿子)。

为了保证复杂度,splay 需要如此实现(\(i\) 为当前节点,\(f\) 为父亲,\(gf\) 为祖父):

  • \(f\)\(gf\)\(i\) 共线,rotate(f),rotate(i)

  • \(f\)\(gf\)\(i\) 不共线,rotate(i),rotate(i)

每次插入、删除、查找后把找到的节点 splay 到根即可保证复杂度。

\(\text{AVL}\)

\(\text{AVL}\) 是一种很古老的平衡树,复杂度异常稳定,最坏情况下为 \(O(\log_\phi n)\)

\(\text{AVL}\) 为满足一下性质的 BST:

  1. 定义叶子节点的 \(hgt\)\(1\)
  2. 定义节点的 \(hgt\)\(max(hgt_{lc},hgt_{rc})+1\)
  3. 每个节点满足 \(|hgt_{lc}-hgt_{rc}|\le 1\)

复杂度证明:

\(S(h)\) 为树高为 \(h\)\(\text{AVL}\) 树最少包含的节点数,那么 \(S(h)\) 的反函数 \(H(s)\) 就是节点数为 \(s\) 最大树高。

显然 \(S(h)\) 是一个增函数。

\(\text{AVL}\) 的性质得,若使得 \(S(h)\) 最小,则其两儿子的 \(hgt\)\(h-1,\ h-2\)

所以有:\(S(h)=S(h-1)+S(h-2)+1\)

可得 \(S(h)\) 增长为指数级,所以 \(H(s)\) 增长是对数级的。

\(\text{AVL}\) 的插入删除与 BST 相同,但是需要每次递归自底向上使用左旋和右旋进行调整。

具体情况需要分 LL、LR、RR、RL 四种。

LL:左儿子的左子树插入节点

LL 情况。灰色字为子树的 \(hgt\)

只需执行一次右旋。

LR:左儿子的右子树插入节点

LR 情况。灰色字为子树的 \(hgt\)

需要一次左旋,一次右旋。

\(\text{RBT}\)

红黑树本质是一种 \(\text{B-Tree}\)

红黑树为满足如下性质的 BST:

  1. 节点为红色或黑色;
  2. 根节点为黑色;
  3. 空节点为黑色;
  4. 每个红节点两个儿子均为黑节点;
  5. 每个空节点到根节点路径上经过的黑节点个数(黑高度)相等。

插入时将新节点作为红节点插入,保证满足性质 \(5\),但可能违反性质 \(4\),此时需要 insert-fixup

删除时按照 BST 方式删除。如果删除的是黑节点,可能会违反性质 \(5\),此时需要 remove-fixup

对于这两种 fixup 的讨论,我这里有一种巧妙的说明方法,可惜这里空间太小,写不下。

理论上红黑树可以可持久化!

平衡树的 \(\text{Leafy}\) 变种

我们知道线段树的 pushup 十分方便,因为每个节点的区间就是左右儿子区间的并。

我们知道平衡树的操作十分灵活,因为平衡的维护可以让我们肆无忌惮地插入节点。

那么有没有一种神奇的数据结构,集合了以上两种优点呢?

有,就是 \(\text{Leafy-Tree}\)\(\text{Leafy-Tree}\) 的所有信息存储在叶子上,又有 \(n\) 个辅助节点,牺牲了一些空间换来了小常数和小码量。

一棵 BST 是如何变为 \(\text{Leafy-Tree}\) 的:

可能会有一篇文章专门讲讲 \(\text{Leafy Tree}\)

平衡树的应用

平衡树最模板的应用就是维护有序集合。

但是利用中序遍历不变的特性,平衡树可以支持非常灵活的序列维护。准确来说,线段树可以做的平衡树都可以做,而平衡树在线段树的基础上还可以支持单点插入、单点删除、区间翻转、区间交换等操作,加上可持久化后还可以支持区间拷贝。(见 \(\text{Luogu P5586}\)

平衡树,尤其是 \(\text{Splay}\),可以用于维护多种动态树,如 \(\text{Link-Cut Tree}\)\(\text{Top Tree}\)。它还可以在某些斜率优化 DP 中发挥作用。除此以外,会了平衡树可以让很多题的思维难度极度下降(当然代码量变多,但也没多多少不是吗),如 \(\text{CSP-J 2020 T2}\)\(\text{CSP-J 2021 T2}\)

总结

平衡树用途广泛,变化灵活,思想具有启发性,是必要也值得学习的。

posted @ 2021-11-03 17:04  ExplodingKonjac  阅读(142)  评论(2编辑  收藏  举报