普通平衡树
这里按照大根堆来存储。
存储信息
struct Node {
int l, r; //左右儿子编号
int val; //节点的值
int key; //堆的关键字
}tr[N];
创建新节点
int newnode(int val) { //返回创建节点的编号
int x = ++ idx;
tr[x].val = val;
tr[x].key = rand();
tr[x].l = tr[x].r = 0;
return x;
}
左旋右旋
左儿子优先级高,右旋。
右儿子优先级高,左旋。
- 右旋(zig):节点
p
右旋时,会携带自己的右子树,向右旋转到q
的右子树位置,q
的右子树被抛弃,此时p
右旋后左子正好空闲,将q
的右子树放在p
的左子树位置,旋转后的树根为q
。 - 左旋(zag):节点
p
左旋时,携带自己的左子树,向左旋转到q
的左子树位置,q
的左子树被抛弃,此时p
左旋后右子树正好空闲,将q
的左子树放在p
的右子树位置,旋转后的树根为q
。
void zig(int &p) { //右旋
int q = tr[p].l;
tr[p].l = tr[q].r;
tr[q].r = p;
p = q; //现在树根是 q
}
void zag(int &p) { //左旋
int q = tr[p].r;
tr[p].r = tr[q].l;
tr[q].l = p;
p = q; //现在树根是 q
}
无论是右旋还是左旋,旋转后总有一棵子树被抛弃,一个指针空闲,正好配对。
插入
T r e a p Treap Treap 的插入操作和二叉搜索树一样,首先根据有序性找到插入的位置,然后创建新节点插入该位置。
创建新节点时,会给该节点附加一个随机数作为优先级,自底向上检查该优先级是否满足堆性质,若不满足,则需要右旋或左旋,使其满足堆性质。
具体步骤:
- 从根节点
p
开始,若p
为空,则创建新节点,将待插入元素val
存入新节点,并给新节点附加一个随机数作为优先级。 - 若
val
等于p
的值,则什么都不做,返回。 - 若
val
小于p
的值,则在p
的左子树中递归插入。回溯时做旋转调整,若p
的优先级小于其左子节点的优先级,则p
右旋。 - 若
val
大于p
的值,则在p
的右子树中递归插入。回溯时做旋转调整,若p
的优先级小于其右子节点的优先级,则p
左旋。
void insert(int &p, int val) { //在 p 的子树中插入 val
if (!p) {
p = newnode(val);
return;
}
if (val == tr[p].val) return; //树中存在该数不插入
if (val < tr[p].val) {
insert(tr[p].l, val);
if (tr[p].key < tr[tr[p].l].key) zig(p);
} else {
insert(tr[p].r, val);
if (tr[p].key < tr[tr[p].r].key) zag(p);
}
}
删除
具体步骤:
- 从根节点
p
开始,若待删除元素val
等于p
的值,则:若p
只有左子树或只有右子树,则令其子树子承父业代替p
,返回;若p
的左子节点优先级大于右子节点的优先级,则p
右旋,继续在p
的右子树中递归删除;若p
的左子节点的优先级小于右子节点的优先级,则p
左旋,继续在p
的左子树中递归删除。 - 若
val
小于p
的值,则在p
的左子树中递归删除。 - 若
val
大于p
的值,则在p
的右子树中递归删除。
void del(int &p, int v) {//在 p 的子树中删除 val
if (!p) return;
if (v == tr[p].val) {
if (!tr[p].l || !tr[p].r) {
p = tr[p].l + tr[p].r; //只有一个儿子,子承父业
} else if (tr[tr[p].l].key > tr[tr[p].r].key) { //左子优先级高 右旋
zig(p);
del(tr[p].r, v);
} else if (tr[tr[p].r].key > tr[tr[p].l].key) { //右子优先级高 左旋
zag(p);
del(tr[p].l, v);
}
}
if (v < tr[p].val) del(tr[p].l, v); //v 小于 p 递归去左子树删除
else del(tr[p].r, v); //v 大于 p 递归去右子树删除
}
前驱
在
T
r
e
a
p
Treap
Treap 中求一个节点 val
的前驱时,首先从树根开始,若当前节点的值小于 val
,则用 res
暂存该节点的值,在当前节点的右子树中寻找, 否则在当前节点的左子树中寻找,直到当前节点为空,返回 res
,即为 val
的前驱。
int getpre(int v) {
int p = rt;
int res = -1;
while (p) {
if (tr[p].val < v) {
res = tr[p].val;
p = tr[p].r;
} else p = tr[p].l;
}
return res;
}
后继
在
T
r
e
a
p
Treap
Treap 中求一个节点 val
的后继时,首先从树根开始,若当前节点 的值大于 val
,则用 res
暂存该节点的值,在当前节点的左子树中寻找, 否则在当前节点的右子树中寻找,直到当前节点为空,返回 res
,即为 val
的后继。
int getne(int v) {
int p = rt;
int res = -1;
while (p) {
if (tr[p].val > v) {
res = tr[p].val;
p = tr[p].l;
} else p = tr[p].r;
}
return res;
}
参考资料:
《算法训练营进阶版》陈小玉著。