• 真·浅谈treap树


    treap树是一种平衡树,它有平衡树的性质,满足堆的性质,是二叉搜索树,但是我们需要维护他

    为什么满足堆的性质?因为每个节点还有一个随机权值,按照随机权值维持这个堆(树),可以用O(logn)的复杂度维护他。树的期望深度是log n。

    平衡树是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。权值:右儿子大于爸爸大于左儿子。

    那么如何维护他呢?

    我们来看看维护treap树的几个操作。

    1.左旋右旋:

    旋转是什么?看图:

    把父亲和他左(右)儿子及其各子树调换(反了!)

    左旋:左儿子上窜(爸爸的左儿子换成儿子的右儿子,儿子的右儿子换成爸爸)

    右旋:右儿子上窜(爸爸的右儿子换成儿子的左儿子,儿子的左儿子换成爸爸)

    左旋右旋看上去没有用,但是他是服务于插入和删除操作的。

    代码:

    void update(int cur){
        sz[cur] = sz[lc[cur]] + sz[rc[cur]] + 1;
    }
    
    void right_rotate(int &Q){
        int P = lc[Q];
        lc[Q] = rc[P];            
        rc[P] = Q;                //爸爸的左儿子换成儿子的右儿子,儿子的右儿子换成爸爸 
        update(Q);                //更新右子树大小 
        update(P);                //更新左子树大小 
        Q = P;                    //把Q换成P 
    }
    
    void left_rotate(int &Q){
        int P = rc[Q];
        rc[Q] = lc[P];
        lc[P] = Q;
        update(Q);
        update(P);
        Q = P;
    }

    2.插入

      插入就需要左旋右旋了,因为我们既维持treap其平衡树的性质,也要维持他堆的性质。

    void insert(int &cur, int v){
        if(!cur)
        {
            cur = ++cnt;            //新加点的编号 
            V[cnt] = v;                //把权值赋给它 
            R[cnt] = rand();        //给出点的随机权值 
            return;
        }
        if(v < V[cur])                //比较新加点与当前点的权值 
        {
            insert(lc[cur], v);        //把当前点放到他左儿子身上,递归下去 
            update(cur);            //更新子树大小 
            if(R[lc[cur]] < R[cur])    //保持堆的性质 
                right_rotate(cur);
        }
        else
        {
            insert(rc[cur], v);        //把当前点放到他左儿子身上,递归下去
            update(cur);            //更新子树大小
            if(R[rc[cur]] < R[cur])    //保持堆的性质 
                left_rotate(cur);
        }
    }

    3.删除

    void del(int &cur, int v){//cur当前节点,v表示要删的点的权值 
        if(!cur)
            return;
        if(V[cur] == v)
        {
            if(lc[cur] && rc[cur])
            {
                left_rotate(cur);
                del(lc[cur], v);
            }
            else 
            {
                cur = lc[cur] | rc[cur];
                update(cur);
                return;
            }
        }
        else if(V[cur] > v)
            del(lc[cur], v);
        else 
            del(rc[cur], v);
        update(cur);
    }

    4.第k大,前驱,后继

    这些都是递归求的。

    比如前驱,如果当前点的权值小于要找的值,那么走它的右子树,直到当前点权值大于要找的值,找它的左子树,因为左子树比当前点权值小。

    int find(int cur, int v)
    {
        if(V[cur] == v)
            return 1;
        else if(V[cur] > v)
            return find(lc[cur], v);
        else
            return sz[lc[cur]] + 1 + find(rc[cur], v);
    }
    int pre(int cur,int v)
    {
        if(!cur)
            return 0xefefefef;
        if(v < V[cur])
            return pre(lc[cur],v);
        return max(V[cur],pre(rc[cur],v));
        
    }
    int post(int cur,int v)
    {
        if(!cur)
            return 0x7fffffff;
        if(v > V[cur])
            return post(rc[cur],v);
        return min(V[cur],post(lc[cur],v));
    }

    差不多就是这些……

    洛谷板题:https://www.luogu.org/problemnew/show/P3369

  • 相关阅读:
    kubernetes集群部署
    kubernetes创建资源的两种方式
    kubernetes架构
    kubernetes简介
    iOS判断对象相等 重写isEqual、isEqualToClass、hash
    iOS宏和__attribute__
    iOS文档注释
    iOS-Runtime知识点整理
    RunLoop和autorelease的一道面试题
    创建控制器的方法、控制器加载view过程、控制器view的生命周期、多控制器组合
  • 原文地址:https://www.cnblogs.com/Ch-someone/p/9325892.html
Copyright © 2020-2023  润新知