zoukankan      html  css  js  c++  java
  • 树堆(Treap)学习笔记

    首先,我么要知道:Treap=Tree+Heap。
    这里:

    • Tree指的是二叉排序树;
    • Heap指的是堆。

    所以在阅读这篇文章之前需要大家对 二叉查找树堆(Heap) 有一定的认识。

    Treap支持如下操作:

    1. 插入x数
    2. 删除x数(若有多个相同的数,应只删除一个)
    3. 查询x数的排名(排名定义为比当前数小的数的个数+1。若有多个相同的数,应输出最小的排名)
    4. 查询排名为x的数
    5. 求x的前驱(前驱定义为小于x,且最大的数)
    6. 求x的后继(后继定义为大于x,且最小的数)

    二叉排序树是这样的一棵树:

    • 它是一棵二叉树;
    • 任意节点的左儿子(如果有)的权值都小于该节点的权值;
    • 任意节点的右儿子(如果有)的权值都大于该节点的权值。

    二叉排序树可以实现上述6个功能,但是最坏情况下每一步操作的时间复杂度都会达到 (O(n))

    所以我们需要在二叉查找树的基础上引入堆的性质,形成一个 (Rightarrow) Treap。

    Treap的基本内容

    首先,我们需要开一些数组来保存信息:

    • size[i]:以i为根节点的子树的节点总数;
    • v[i]:i节点的权值;
    • num[i]:由于可能有多个节点具有相同的权值,所以,我们将权值一样的存在同一个节点里面,num[i]存放的是i节点存的数的个数(即:num[i]表示有多少个数的权值为v[i]);
      sum[i][2]:用于存储i节点的儿子编号,其中:son[i][0]表示i节点的左儿子,son[i][1]表示i节点的右儿子;
    • rd[i]:i节点的随机值。

    那么,rd[i]的左右是什么呢?
    每次创建一个新节点i的时候,都会为i节点分配一个随机值 rd[i]
    堆就是在这里派上用场的——我们要让全部节点按照这个随机值排成一个堆。
    这就引出了平衡树中最重要的一个概念——旋转。

    rotate操作——旋转

    旋转分两种:左旋和右旋,它们的共同特点是不改变Treap的二叉查找树的性质,同时让Treap更加平衡。

    旋转可以维护Treap堆的性质,然后巧妙地防止Treap退化成链,使得操作的时间复杂度趋于 (O(log n))

    Treap的基本操作

    void pushup(int p) {
        sz[p] = sz[ son[p][0] ] + sz[ son[p][1] ] + num[p];
    }
    

    用于重新统计以p为根节点的子树中元素个数。

    void rot(int &p, int d) {
        int k = son[p][d^1];
        son[p][d^1] = son[k][d];
        son[k][d] = p;
        pushup(p);
        pushup(k);
        p = k;
    }
    

    旋转操作,d0:左旋;d1:右旋。

    1. 插入一个数x

    void ins(int &p, int x) {
        if (!p) {
            p = ++sum;
            sz[p] = num[p] = 1;
            v[p] = x;
            rd[p] = rand();
            return;
        }
        if (v[p] == x) {
            num[p] ++;
            sz[p] ++;
            return;
        }
        int d = (x > v[p]);
        ins(son[p][d], x);
        if (rd[p] < rd[ son[p][d] ]) rot(p, d^1);
        pushup(p);
    }
    

    如果 p==0 (即 !p),那么就说明当前节点是一个空节点,此时我们开辟一个新节点;
    如果 v[p]==x,那么就说明当前要插入的位置p上面刚好存了一个x,直接放到这个点上面就OK了;
    否则,我们需要递归的进一个子树进行插入,当 x<v[p] 时, d=0,进左子树;当 x>v[p] 时,d=1,进右子树递归地插入。
    如果进左儿子插入x后,p节点的rd值小于它左儿子的rd值(即:(rd[p] lt rd[son[p][0]])),则右旋;
    如果进右儿子插入x后,p节点的rd值小于它右儿子的rd值(即:(rd[p] lt rd[son[p][1]])),则左旋。
    重点,想一想,为什么这样转不破坏堆的性质 )(我暂时还没有想明白~)

    2. 删除一个数x

    void del(int &p, int x) {
        if (!p) return;
        if (x < v[p]) del(son[p][0], x);
        else if (x > v[p]) del(son[p][1], x);
        else {
            if (!son[p][0] && !son[p][1]) {
                num[p] --; sz[p] --;
                if (!num[p]) p = 0;
            }
            else if (!son[p][1]) {
                rot(p, 1);
                del(son[p][1], x);
            }
            else if (!son[p][0]) {
                rot(p, 0);
                del(son[p][0], x);
            }
            else {
                int d = (rd[ son[p][0] > rd[ son[p][1] ] ]);
                rot(p, d);
                del(son[p][d], x);
            }
        }
        pushup(p);
    }
    

    如果是空节点,则直接返回;
    如果 xp[x] 不相等,直接去相应子树递归删除;
    如果 x==v[p],则:

    • 如果x是叶子结点,直接扣掉个数,如果个数变为0则删掉节点;
    • 如果x只有一个子节点,直接把子节点旋转上来,然后去相应子树解决;
    • 如果有两个子节点,把大的那个转上来,然后去另一个子树解决。

    3. 查询x数的排名

    int get_rank(int p, int x) {
        if (!p) return 0;
        if (v[p] == x) return sz[ son[p][0] ] + 1;
        if (v[p] < x) return sz[ son[p][0] ] + num[p] + get_rank(son[p][1], x);
        if (v[p] > x) return get_rank(son[p][0], x);
    }
    

    如果不存在这个节点(到达了一个空节点),直接返回0;
    如果x==v[p],那么左子树的全部节点都必定小于x,直接返回左子树节点数+1;
    如果x>v[p],则x位于右子树,答案就是左子树元素个数+该节点元素个数+右子树中x的排名;
    如果x<v[p],则x位于左子树,答案就是x在左子树的排名。

    4. 查询排名为x的数

    int func_find(int p, int x) {
        if (!p) return 0;
        if (sz[ son[p][0] ] >= x) return func_find(son[p][0], x);
        else if (sz[ son[p][0] ] + num[p] < x)
            return func_find(son[p][1], x-num[p]-sz[ son[p][0] ]);
        else return v[p];
    }
    

    空节点没有排名,直接返回0;
    如果左子树中节点个数大于x,则进左子树找;
    否则,如果左子树加根节点的个数大于等于x,直接返回根节点的值v[p]
    否则,说明左子树加根节点的个数小于x,进右子树找第 (x-最节点和根节点元素个数) 个元素。

    5. 求x的前驱

    int pre(int p, int x) {
        if (!p) return -INF;
        if (v[p] >= x) return pre(son[p][0], x);
        else return max(v[p], pre(son[p][1], x));
    }
    

    如果是空节点,则没有前驱;
    如果x是根或在右子树,去左子树找;
    否则要么是根要么右子树,取一个max就可以了(前驱定义为小于x,且最大的数)。

    6. 求x的后缀

    int suc(int p, int x) {
        if (!p) return INF;
        if (v[p] <= x) return suc(son[p][1], x);
        else return min(v[p], suc(son[p][0], x));
    }
    

    如果是空节点,则没有后缀;
    如果在根或者左子树,去右子树找;
    否则要么根要么左子树,取min就可以了(后继定义为大于x,且最小的数)。
    洛谷上面有一道题是专门用于练习左偏树的题目:洛谷 P3369
    实现代码如下:

    #include <bits/stdc++.h>
    using namespace std;
    #define INF INT_MAX
    const int maxn = 100010;
    int sum = 0, R = 0;
    int sz[maxn], v[maxn], num[maxn], rd[maxn], son[maxn][2];
    // 用于重新统计以p为根节点的子树中元素个数
    void pushup(int p) {
        sz[p] = sz[ son[p][0] ] + sz[ son[p][1] ] + num[p];
    }
    // 左旋(d==0时),右旋(d==1时)
    void rot(int &p, int d) {
        int k = son[p][d^1];
        son[p][d^1] = son[k][d];
        son[k][d] = p;
        pushup(p);
        pushup(k);
        p = k;
    }
    // 插入一个数x
    void ins(int &p, int x) {
        if (!p) {
            p = ++sum;
            sz[p] = num[p] = 1;
            v[p] = x;
            rd[p] = rand();
            return;
        }
        if (v[p] == x) {
            num[p] ++;
            sz[p] ++;
            return;
        }
        int d = (x > v[p]);
        ins(son[p][d], x);
        if (rd[p] < rd[ son[p][d] ]) rot(p, d^1);
        pushup(p);
    }
    // 删除一个数x
    void del(int &p, int x) {
        if (!p) return;
        if (x < v[p]) del(son[p][0], x);
        else if (x > v[p]) del(son[p][1], x);
        else {
            if (!son[p][0] && !son[p][1]) {
                num[p] --; sz[p] --;
                if (!num[p]) p = 0;
            }
            else if (!son[p][1]) {
                rot(p, 1);
                del(son[p][1], x);
            }
            else if (!son[p][0]) {
                rot(p, 0);
                del(son[p][0], x);
            }
            else {
                int d = (rd[ son[p][0] > rd[ son[p][1] ] ]);
                rot(p, d);
                del(son[p][d], x);
            }
        }
        pushup(p);
    }
    // 查询x数的排名
    int get_rank(int p, int x) {
        if (!p) return 0;
        if (v[p] == x) return sz[ son[p][0] ] + 1;
        if (v[p] < x) return sz[ son[p][0] ] + num[p] + get_rank(son[p][1], x);
        if (v[p] > x) return get_rank(son[p][0], x);
    }
    // 查询排名为x的数
    int func_find(int p, int x) {
        if (!p) return 0;
        if (sz[ son[p][0] ] >= x) return func_find(son[p][0], x);
        else if (sz[ son[p][0] ] + num[p] < x)
            return func_find(son[p][1], x-num[p]-sz[ son[p][0] ]);
        else return v[p];
    }
    // 求x的前驱
    int pre(int p, int x) {
        if (!p) return -INF;
        if (v[p] >= x) return pre(son[p][0], x);
        else return max(v[p], pre(son[p][1], x));
    }
    // 求x的后缀
    int suc(int p, int x) {
        if (!p) return INF;
        if (v[p] <= x) return suc(son[p][1], x);
        else return min(v[p], suc(son[p][0], x));
    }
    
    int T, op, x;
    int main() {
        cin >> T;
        while (T --) {
            cin >> op >> x;
            if (op == 1) ins(R, x);
            else if (op == 2) del(R, x);
            else if (op == 3) cout << get_rank(R, x) << endl;
            else if (op == 4) cout << func_find(R, x) << endl;
            else if (op == 5) cout << pre(R, x) << endl;
            else if (op == 6) cout << suc(R, x) << endl;
        }
        return 0;
    }
    

    然后我又用类封装了一下,C++类封装的代码如下:

    #include <bits/stdc++.h>
    using namespace std;
    #define INF INT_MAX
    const int maxn = 100010;
    class Treap {
    private:
        int sum, R, sz[maxn], v[maxn], num[maxn], rd[maxn], son[maxn][2];
        void pushup(int p) {
            sz[p] = sz[ son[p][0] ] + sz[ son[p][1] ] + num[p];
        }
        void rot(int &p, int d) {
            int k = son[p][d^1];
            son[p][d^1] = son[k][d];
            son[k][d] = p;
            pushup(p);
            pushup(k);
            p = k;
        }
        void ins(int &p, int x) {
            if (!p) {
                p = ++sum;
                sz[p] = num[p] = 1;
                v[p] = x;
                rd[p] = rand();
            }
            else if (v[p] == x) {
                num[p] ++;
                sz[p] ++;
            }
            else {
                int d = (x > v[p]);
                ins(son[p][d], x);
                if (rd[p] < rd[ son[p][d] ]) rot(p, d^1);
                pushup(p);
            }
        }
        void del(int &p, int x) {
            if (!p) return;
            if (x < v[p]) del(son[p][0], x);
            else if (x > v[p]) del(son[p][1], x);
            else {
                if (!son[p][0] && !son[p][1]) {
                    num[p] --; sz[p] --;
                    if (!num[p]) p = 0;
                }
                else if (!son[p][1]) {
                    rot(p, 1);
                    del(son[p][1], x);
                }
                else if (!son[p][0]) {
                    rot(p, 0);
                    del(son[p][0], x);
                }
                else {
                    int d = (rd[ son[p][0] > rd[ son[p][1] ] ]);
                    rot(p, d);
                    del(son[p][d], x);
                }
            }
            pushup(p);
        }
        int get_rank(int p, int x) {
            if (!p) return 0;
            if (v[p] == x) return sz[ son[p][0] ] + 1;
            if (v[p] < x) return sz[ son[p][0] ] + num[p] + get_rank(son[p][1], x);
            if (v[p] > x) return get_rank(son[p][0], x);
        }
        int func_find(int p, int x) {
            if (!p) return 0;
            if (sz[ son[p][0] ] >= x) return func_find(son[p][0], x);
            else if (sz[ son[p][0] ] + num[p] < x)
                return func_find(son[p][1], x-num[p]-sz[ son[p][0] ]);
            else return v[p];
        }
        int pre(int p, int x) {
            if (!p) return -INF;
            if (v[p] >= x) return pre(son[p][0], x);
            else return max(v[p], pre(son[p][1], x));
        }
        int suc(int p, int x) {
            if (!p) return INF;
            if (v[p] <= x) return suc(son[p][1], x);
            else return min(v[p], suc(son[p][0], x));
        }
    public:
        Treap() {}
        void Init() {
            sum = R = 0;
            memset(sz, 0, sizeof(sz));
            memset(v, 0, sizeof(v));
            memset(num, 0, sizeof(num));
            memset(rd, 0, sizeof(rd));
            memset(son, 0, sizeof(son));
        }
        void Insert(int x) { ins(R, x); }
        void Delete(int x) { del(R, x); }
        int GetRank(int x) { return get_rank(R, x); }
        int Find(int x) { return func_find(R, x); }
        int Pre(int x) { return pre(R, x); }
        int Suc(int x) { return suc(R, x); }
    } treap;
    int main() {
        treap.Init();
        int T, op, x;
        cin >> T;
        while (T --) {
            cin >> op >> x;
            switch (op) {
                case 1: treap.Insert(x); break;
                case 2: treap.Delete(x); break;
                case 3: cout << treap.GetRank(x) << endl; break;
                case 4: cout << treap.Find(x) << endl; break;
                case 5: cout << treap.Pre(x) << endl; break;
                case 6: cout << treap.Suc(x) << endl; break;
                default: break;
            }
        }
        return 0;
    }
    
  • 相关阅读:
    分布式事务解决方案之可靠消息最终一致性(四)
    分布式事务解决方案之TCC(三)
    分布式事务解决方案之2PC(二)
    分布式事务初始(一)
    Spring Cloud Alibaba Sentinel 初始
    Spring Cloud Alibaba Nacos 初始
    JAVA 札记
    SpringBoot JPA 札记
    spring boot 札记
    @RequestMapping 和 @GetMapping @PostMapping 区别
  • 原文地址:https://www.cnblogs.com/quanjun/p/13365883.html
Copyright © 2011-2022 走看看