Treap是一种弱平衡的二叉搜索树,它同时符合二叉搜索树和堆的性质:
二叉搜索树:右子节点值>父节点>左子节点
堆:子节点的优先值比父节点值小
我们让每个节点的值符合二叉搜索树的性质,同时给每个节点赋一个随机的优先值,让优先值满足堆的性质。
由于优先值是随机的,一旦不满足堆性质就会产生旋转,所以就很难出现退化成链的情况,达到平衡的效果。
Treap分为旋转Treap和无旋treap两种,旋转treap的旋转操作分为右旋和左旋:
![image-20220703180723552](【集训整理】 旋转treap模板.assets/image-20220703180723552.png)
写成代码如下:
void left_rotate(int& id) { //左旋
int tmp = rchild[id];
rchild[id] = lchild[tmp];
lchild[tmp] = id;
id = tmp;
push_up(lchild[id]);
push_up(id);
}
void right_rotate(int& id) { //右旋
int tmp = lchild[id];
lchild[id] = rchild[tmp];
rchild[tmp] = id;
id = tmp;
push_up(rchild[id]);
push_up(id);
}
旋转操作其实只涉及两个点,交换下孩子即可。
树的储存,使用数组模拟结构体:
int lchild[maxn];
int rchild[maxn];
int val[maxn],prio[maxn];
int siz[maxn], cnt[maxn];
int tot, root;
val
存值,prio
存节点的优先值,siz
记录某节点的子节点的数量,cnt
记录某节点的值出现的次数,用于处理重复元素的情况。
创建节点:
int create(int v) {
//创建节点,并给予随机优先值
tot++;
val[tot] = v;
prio[tot] = rand();
siz[tot] = 1;
cnt[tot] = 1;
return tot;
}
用rand函数给prio赋随机值。
更新siz的push_up操作:
void push_up(int id) {
//用于旋转后重新计算size
siz[id] = siz[lchild[id]] + siz[rchild[id]] + cnt[id];
}
在旋转/插入后siz可能会变化,要push_up从下到上更新。
初始化:
void build() { //初始化,插入一个正无穷和一个负无穷
root = create(-inf);
rchild[root] = create(inf);
push_up(root);
}
初始化要先往里边塞一个正无限和一个负无限节点。
插入:
在二叉查找树上找位置插入,找到树缺位或已有节点修改。每次插完都要检测符不符合堆的性质,如果不符合就用旋转的方式进行平衡。
注意,每层插入递归结束后都要进行一次push_up重新计算size值。
void insert(int& id, int v) {
if (!id) { //找到树缺位(引用指向的变量(l/rchild)为0)
id = create(v);
return;
}
if (v == val[id]) cnt[id]++;
else { //遍历查找树,知道找到他该在的位置
if (v < val[id]) {
insert(lchild[id], v);
//每次插完都要检测符不符合堆的性质,不符合就平衡
if (prio[id] < prio[lchild[id]]) right_rotate(id);
}
else {
insert(rchild[id], v);
if (prio[id] < prio[rchild[id]]) left_rotate(id);
}
}
push_up(id); //插入后size会变,沿路更新
}