• 普通平衡树(Treap)


    普通平衡树

    这里按照大根堆来存储。

    存储信息
    struct Node {
        int l, r;  //左右儿子编号
        int val;   //节点的值
        int key;   //堆的关键字
    }tr[N];
    
    • 1
    • 2
    • 3
    • 4
    • 5
    创建新节点
    int newnode(int val) {  //返回创建节点的编号
        int x = ++ idx;
        tr[x].val = val;
        tr[x].key = rand();
        tr[x].l = tr[x].r = 0;
        return x;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    左旋右旋

    左儿子优先级高,右旋。

    右儿子优先级高,左旋。

    • 右旋(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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    无论是右旋还是左旋,旋转后总有一棵子树被抛弃,一个指针空闲,正好配对。

    插入

    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    删除

    具体步骤:

    • 从根节点 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 递归去右子树删除
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    前驱

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    后继

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    参考资料:

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

  • 相关阅读:
    SMTP服务器搭建关键步骤?如何配置服务器?
    基于SSM的药店管理系统
    Zebec Protocol 成非洲利比亚展会合作伙伴,并将向第三世界国家布局
    机器学习 day33(误差分析、添加数据、迁移学习)
    vue3脚手架运行各种报错汇总(持续收集)
    8位bmp文件获取像素
    关于list去除引号+报错invalid literal for int() with base 10:
    沁恒 CH32V203C8T6 RISC-V 单片机无法烧写
    Ubuntu/CentOS 磁盘分区扩展
    如何将 Python 项目打包成 exe,另带卸载功能!
  • 原文地址:https://blog.csdn.net/weixin_60484917/article/details/127591194