T r e a p Treap Treap学习笔记
简介
t r e a p treap treap,顾名思义,就是 t r e e + h e a p tree+heap tree+heap,是一种常见的平衡树。
前置知识
B S T BST BST性质
给定一棵二叉树,树上的每个节点带有一个权值。
对于树上的任意一个节点,满足:
-
该节点的权值大于左子树中任意节点的权值。
-
该节点的权值小于右子树中任意节点的权值。
满足这两条性质的二叉树就是 “二叉查找树”( B S T BST BST)。
H e a p Heap Heap性质
即堆性质。
以大根堆为例,堆中的每一个节点的权值大于子树中任意节点的权值。
平衡树
普通的 B S T BST BST可以支持插入、删除、检索、求前驱/后继等操作。
在随机数据中, B S T BST BST的表现很优秀,单次操作期望时间复杂度为 O ( l o g 2 n ) O(log_2{n}) O(log2n)。
但是,如果数据呈现单调递增的样子,那么 B S T BST BST就会退化成一条链,单次操作 O ( n ) O(n) O(n)。
为了让一条链平衡,诞生了许多 “平衡树”。Treap就是其中一种。
T r e a p Treap Treap
满足 B S T BST BST性质且中序遍历为相同序列的二叉查找树是不唯一的。而这些二叉查找树都是等价的。
我们可以在保持二叉查找树本质不变的情况下,改变它的形态,让它接近一棵 “平衡树”。
改变树的形态的方法就是 “旋转”。“旋转”又分为 “左旋” 和 “右旋”。
从这张图中,我们可以看出:
以右旋为例,将待旋转节点 A A A旋转至其右子树处,然后将原来 A A A节点左儿子的右子树全部接到现在 A A A节点的左儿子处。
void zig(int & p) {
int tmp = tree[p].l;
tree[p].l = tree[tmp].r, tree[tmp].r = p, p = tmp;
push_up(tree[p].r), push_up(p);
}
void zag(int & p) {
int tmp = tree[p].r;
tree[p].r = tree[tmp].l, tree[tmp].l = p, p = tmp;
push_up(tree[p].l), push_up(p);
}
现在,问题在于如何合理地旋转使得树尽可能“平衡”?
之前提到过,在数据随机的情况下, B S T BST BST的期望时间复杂度为 O ( l o g 2 n ) O(log_2{n}) O(log2n)。
我们可以利用“数据随机”的特点创造平衡条件。
对于树上的每一个节点,我们赋予一个随机出来的数值 k e y key key,称为 “键值”.
插入每一个新节点之后,我们自底向上检查,如果 k e y key key不符合 H e a p Heap Heap性质,我们就将其旋转,直至整棵树同时满足 B S T BST BST性质和 H e a p Heap Heap性质。
对于删除操作,我们可以在找到待删除节点之后将其旋转至叶子节点再删除。
对于维护的信息,可能会出现重复的权值。于是对于相同权值我们只记录一次,同时我们记录它的出现次数。
我们可以在一开始就加入 i n f inf inf, − i n f -inf −inf两个数值,避免边界情况的处理。
于是,我们就可以在期望 O ( l o g 2 n ) O(log_2{n}) O(log2n)的时间内实现检索、插入、删除、求前驱/后继。
Code
以模板(普通平衡树)为例,给出 T r e a p Treap Treap的代码:
#include<iostream>
#include<cstdio>
#include<ctime>
#include<cstdlib>
using namespace std;
const int inf = 0x3f3f3f3f, maxn = 100010;
int n, tot, root = 1;
struct treap {
int l, r, size, cnt, val, key;
} tree[maxn];
void push_up(int u) {
tree[u].size = tree[tree[u].l].size + tree[tree[u].r].size + tree[u].cnt;
}
int New(int val) {
tree[++tot].val = val, tree[tot].key = rand(), tree[tot].cnt = tree[tot].size = 1;
return tot;
}
void build() {
New(-inf), New(inf);
tree[1].r = 2;
push_up(1);
}
void zig(int & p) {
int tmp = tree[p].l;
tree[p].l = tree[tmp].r, tree[tmp].r = p, p = tmp;
push_up(tree[p].r), push_up(p);
}
void zag(int & p) {
int tmp = tree[p].r;
tree[p].r = tree[tmp].l, tree[tmp].l = p, p = tmp;
push_up(tree[p].l), push_up(p);
}
void insert(int & p, int val) {
if(p == 0) {
p = New(val);
return;
}
if(val == tree[p].val) {
tree[p].cnt++, tree[p].size++;
return;
}
if(val < tree[p].val) {
insert(tree[p].l, val);
if(tree[p].key < tree[tree[p].l].key) zig(p);
}
else {
insert(tree[p].r, val);
if(tree[p].key < tree[tree[p].r].key) zag(p);
}
push_up(p);
}
void remove(int & p, int val) {
if(p == 0) return;
if(val == tree[p].val) {
if(tree[p].cnt > 1) {
tree[p].cnt--, tree[p].size--;
return;
}
if(tree[p].l || tree[p].r) {
if(tree[p].r == 0 || tree[tree[p].l].key > tree[tree[p].r].key) zig(p), remove(tree[p].r, val);
else zag(p), remove(tree[p].l, val);
push_up(p);
}
else p = 0;
return;
}
if(val < tree[p].val) remove(tree[p].l, val);
else remove(tree[p].r, val);
push_up(p);
}
int get_rank(int p, int val) {
if(p == 0) return 0;
if(tree[p].val == val) return tree[tree[p].l].size + 1;
if(val < tree[p].val) return get_rank(tree[p].l, val);
return get_rank(tree[p].r, val) + tree[tree[p].l].size + tree[p].cnt;
}
int get_val(int p, int rank) {
if(p == 0) return inf;
if(tree[tree[p].l].size >= rank) return get_val(tree[p].l, rank);
if(tree[tree[p].l].size + tree[p].cnt >= rank) return tree[p].val;
return get_val(tree[p].r, rank - tree[tree[p].l].size - tree[p].cnt);
}
int pre(int val) {
int ret = 1, p = root;
while(p) {
if(val == tree[p].val) {
if(tree[p].l > 0) {
p = tree[p].l;
while(tree[p].r) p = tree[p].r;
ret = p;
}
break;
}
if(tree[p].val < val && tree[p].val > tree[ret].val) ret = p;
if(val < tree[p].val) p = tree[p].l; else p = tree[p].r;
}
return tree[ret].val;
}
int next(int val) {
int ret = 2, p = root;
while(p) {
if(val == tree[p].val) {
if(tree[p].r > 0) {
p = tree[p].r;
while(tree[p].l) p = tree[p].l;
ret = p;
}
break;
}
if(tree[p].val > val && tree[p].val < tree[ret].val) ret = p;
if(val < tree[p].val) p = tree[p].l; else p = tree[p].r;
}
return tree[ret].val;
}
int main()
{
srand((unsigned int) time(NULL));
build();
scanf("%d", &n);
while(n--) {
int opt, x;
scanf("%d %d", &opt, &x);
if(opt == 1) insert(root, x);
if(opt == 2) remove(root, x);
if(opt == 3) printf("%d\n", get_rank(root, x) - 1);
if(opt == 4) printf("%d\n", get_val(root, x + 1));
if(opt == 5) printf("%d\n", pre(x));
if(opt == 6) printf("%d\n", next(x));
}
return 0;
}