简介
Splay 是一种平衡树,并且是一棵二叉搜索树(BST)。
它满足对于任意节点,都有左子树上任意点的值 < 当前节点的值 < 右子树上任意点的值。
优点:支持多种操作。
缺点:常数较大。
单次操作均摊复杂度 O ( log n ) O(\log n) O(logn)。
(注:关于 Splay 单次操作均摊复杂度的证明见 OI-Wiki)
Splay 基于旋转操作维护树的平衡。旋转分为左旋 (zag) 和右旋 (zig)。
旋转,即在保证平衡树中序遍历不变的前提下,改变整个树的深度。
右旋
对于单次操作 zig(a)
,将节点 a a a 左儿子的左儿子( c c c)接到 a a a 的左儿子处,将 c c c 的右儿子接到 b b b 的左儿子,将 c c c 的右儿子改为 b b b。
这样,我们完成了一次右旋操作,操作前后, a a a 左子树的中序遍历都为 DcEbF
。
左旋
左旋即右旋的逆过程,将每一步反过来即可。
核心思想
每次操作之后,都将被操作的节点旋转至根节点。
这个和均摊时间复杂度有关。
操作
a. Splay
每次调用函数 splay(x, k)
表示把点 x x x 旋转至点 k k k 下方。
特别地,当调用 splay(x, 0)
时,表示把点 x x x 旋转至根节点。
有两种情况:
- x , y , z x, y,z x,y,z 成一条链
- x , y , z x,y,z x,y,z 不成一条链
b. 插入
- 根据 BST 性质,找到该元素所在位置并新建节点。插入后将该节点旋转至根节点。
- 当要求将一个序列插到 y y y 的后面时:
- 找到 y y y 的后继 z z z。
- 将 y y y 转到根。(
splay(y, 0);
) - 将 z z z 转到 y y y 的下方。(
splay(z, y);
)由于 z > y z>y z>y,所以 z z z 是 y y y 的右儿子。 - 显然,此时将要插入的序列接到 z z z 的左儿子(∅)上即可。
c. 删除
- 删除一段区间 [ l , r ] [l,r] [l,r]。
- 分别找到 l l l 的前驱 p p p 和 r r r 的后继 q q q。
- 将 p p p 转到根节点。
- 将 q q q 转到 p p p 下方。由于 p < q p<q p<q,所以 q q q 是 p p p 的左儿子。
- 显然,要删除的区间就是点 p p p 的整个左子树。直接变没即可。
信息的维护
以模板题 AcWing 2437. Splay 为例。
本题要求我们进行区间翻转操作。
因此维护两个值&