• Treap 实现名次树


    在主流STL版本中,set,map,都是BST实现的,具体来说是一种称为红黑树的动态平衡BST;

    但是在竞赛中并不常用,因为红黑树过于复杂,他的插入 5 种,删除 6 中,代码量极大(如果你要改板子的话);

    相比之下有一种Treap的动态平衡BST,却也可以做到插入,删除,查找的期望时间复杂度O(logn);

    结点定义:

    struct Node {
        Node *ch[2];
        int r;  //优先级
        int v;  //
        int s;  //结点总数
        
        Node(int v):v(v) {
            ch[0] = ch[1] = NULL;
            r = rand();
            s = 1;
        }
        
        bool operator < (const Node& rhs) const {
            return r < rhs.r;
        }
        
        int cmp(int x) const {
            if(x==v) return -1;
            return x < v ? 0:1;
        }
        
        void maintain() {
            s = 1;
            if(ch[0]!=NULL) s +=ch[0]->s;
            if(ch[1]!=NULL) s +=ch[1]->s;
        }
    };

    我这里加了一些看似不需要的东西s,而这个 s却是Treap相比BST的闪光点!!!

    动态平衡二叉树 BST 的性质 v,值,根大于左子树,小于右子树; cmp函数,插入,删除时,小于 v,返回 0;

    r   : 堆的性质,大根堆,根优先级最高;

    旋转操作是一个坎,虽然不难,但是好多书籍上面感觉欲言又止;

    左旋: 由于 堆的性质,可能使得 BST 不对(插入,删除),需要旋转,比如说,o点的优先级小于 k 点的优先级,要左旋,(大于,相反)

    这个时候要是还想满足BST的性质,只需要改动几个点,就ok了。

    //旋转
    void rotate(Node* &o,int d) {
        Node* k = o->ch[d^1];
        o->ch[d^1] = k->ch[d];
        k->ch[d] = o;
        o->maintain();
        k->maintain();
        o = k;
    }

    同时,maintain函数,要重新统计节点数。

    插入操作;

    首先按照普通的BST递归插入;

    插入后,发现,此时的堆性质已经不满足了;要进行递归旋转!!!

    //插入
    void insert(Node* &o,int x) {
        if(o==NULL) o = new Node(x);
        else {
            int d = (x< o->v?0:1);  //可能有相同的元素要插入
            insert(o->ch[d],x);
            if(o->ch[d]->r > o->r) 
                rotate(o,d^1);
        }
        o->maintain();
    }

    同样,每次递归到一层,重新维护节点信息;

    删除操作:

    首先递归找到这个结点;

    这个结点如果左子树为空,或者右子树为空,很好解决;相反的子树代替父节点;

    要是两者都有怎么解决?保持堆的性质 和 BST的性质?

    先不急于删去点,首先比较一下左右子树的优先级,把优先级较高的子树旋转到根;

    例如上图中,加入 k 较高,右旋到左边的图;然后递归删除 k ,这样就保证了整个Treap树的性质!!!

    //删除
    void remove(Node* &o,int x) {
        int d = o->cmp(x);
        if(d==-1) {
            Node* u = o;
            if(o->ch[0]!=NULL&&o->ch[1]!=NULL) {
                int d2 = (o->ch[0]->r > o->ch[1]->r ? 1 : 0);
                rotate(o,d2);
                remove(o->ch[d2],x);
            }
            else {
                if(o->ch[0]==NULL) 
                    o = o ->ch[1];
                else o = o ->ch[0];
            }
        }
        else {
            remove(o->ch[d],x);
        }
        if(o!=NULL) o->maintain();
    }

    注意:插入,删除,的时候没有去检查,可以先去检查了一下,这样就完全和set是一样的了

    int find(Node* o,int x) {
        while(o!=NULL) {
            int d = o->cmp(x);
            if(d==-1) return 1; //存在
            else o = o->ch[d];
        }
        return 0;   //不存在
    }

    到了这里就已经完全实现了Treap树了,很happy(^o^)/~

    但是:

    如果说,Treap树和 set 是一样的,那就没必要写 Treap了,举个栗子!

    名次树!!!

    个人柑橘往左子树走很巧妙, (^-^)V

    利用右子树有多少节点而往左子树走;

    //名次树
    Node* root[maxn];
    
    //第 k 大的值
    int kth(Node* o,int k) {
        if(o==NULL||k<=0||k>o->s) return 0;
        int s = (o->ch[1]==NULL ? 0: o->ch[1]->s);
        if(k==s+1) return o->v;
        else if(k<=s) return kth(o->ch[1],k);
        else return kth(o->ch[0],k-s-1);
    }
  • 相关阅读:
    lumen简单使用exel组件
    VIM 批量注释的两种方法 (转)
    linux时间校准 设置时间为上海时区
    lumen发送邮件配置
    centos 下安装redis 通过shell脚本
    shell 脚本接收参数
    linux设置系统变量
    linux通配符
    UCCI协议[转]
    一种编程范式:对拍编程
  • 原文地址:https://www.cnblogs.com/TreeDream/p/6730574.html
Copyright © 2020-2023  润新知