普通平衡树(Treap)

普通平衡树

这里按照大根堆来存储。

存储信息
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 的插入操作和二叉搜索树一样,首先根据有序性找到插入的位置,然后创建新节点插入该位置。

创建新节点时,会给该节点附加一个随机数作为优先级,自底向上检查该优先级是否满足堆性质,若不满足,则需要右旋或左旋,使其满足堆性质。

具体步骤:

  1. 从根节点 p 开始,若 p 为空,则创建新节点,将待插入元素 val 存入新节点,并给新节点附加一个随机数作为优先级。
  2. val 等于 p 的值,则什么都不做,返回。
  3. val 小于 p 的值,则在 p 的左子树中递归插入。回溯时做旋转调整,若 p 的优先级小于其左子节点的优先级,则 p 右旋。
  4. 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;
}

参考资料:

《算法训练营进阶版》陈小玉著。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值