zoukankan      html  css  js  c++  java
  • Treap

     Treap=Tree+Heap,即在普通二叉查找树的基础上每个节点有了一个新值域:强化值(因为它将普通二叉查找树强化为treap就自己起了这个名字,是用来满足堆性质的,即后文说满足堆性质都指强化值满足堆性质)。要求这个树节点的键值(即要代表的数)满足BST的性质、强化值满足小跟堆的性质(你非得大根堆也没什么)。强化值由建立节点时由一个随机算法(rand())给出,在一个以随机数据建成的堆的加持下,treap的期望高度被证明为logn,故是一个平衡树。

    代码请看文末

    核心操作:旋转

      左旋(zig):左旋一棵子树,它的根变为新子树的左儿子、根的右儿子变为新子树的根,那么根的右儿子的新左儿子是根了,原来的左儿子怎么办?交给根刚好。这样操作会发现,BST的性质仍然满足(相对左右位置未变),整个树宏观上向左“转动”了一下。

      右旋(zig):右旋一棵子树,它的根变为新子树的右儿子、根的左儿子变为新子树的根,根的右儿子的原左儿子也可交给根。这样操作会发现,BST的性质仍然满足(相对左右位置未变),整个树宏观上向右“转动”了一下。

      故:旋转不改变BST性质,但会改变父祖关系。同时不改变如图x、y、z三部分的堆性质(不特指某种旋转。红点为即将认父的根,绿点为即将认儿的子节点。x为会变为绿点的外侧子树,z代表原根的另一个子树,y代表内侧绿点会给红点的子树)

    泛用操作:

      1、插入x数:

        按照普通二叉查找树插入方式新建节点后使其获得强化值。回溯过程中看儿子:左儿子强化值小于自己——右旋(让他当新爹,小根堆嘛);右儿子强化值小于自己——左旋。

        解答为何用旋转方式维护堆性质:首先旋转不改变BST性质,但可以改变父子关系。看上图,若绿点强化值小于红点,有堆性质得,绿点要当红点父亲才行。一开始绿点子树一定是满足堆性质的(只有它一个点),因为绿点强化值小于红点,所以绿点完全可以当红点子树的根。由于红点子树在插入值前满足堆性质,而绿点一定是旋转上来的,所以红点可以当除绿点外子树所有点的父亲/祖先,故旋转完成后,红点为首的子树变为绿点为首的子树,同时整个子树都满足堆性质了。

      2、删除x数:

        思路类似普通二叉查找树,找到节点后,若cnt(为了考虑重复值而设的)>1,则cnt--,否则,若:

          其最多只有一个子节点:让子节点代替他(若有的话),若没有,直接删就好。

          有两个子节点:旋转,让强化值小的子节点当新爹,把原爹(即要删的)旋下去,直到删它的情况变为第一种。

            解答这里为何旋转:强化值小的子节点可以当原子树内除原爹外所有点的父亲/祖先,旋转后子树不含原爹为首新子树的部分仍满足堆性质。以原爹为首的新子树除了原爹,强化值最小的(即原爹的原另一个强化值较大的子节点)点也没原爹现在的新爹(即原爹的原强化值较小的子节点)小,故可预知删除原爹后,整个树仍满足堆性质。

      3、查询x数的排名   4、查询排名为x的数   5、求x的前驱   6、求x的后继   这些操作与二叉查找树的操作无异,见:二叉查找树

      7、分离:

        要把一个Treap按大小分成两个Treap,即大于等于x的分离成一个treap,剩下的也成一个treap,只要加一个虚拟节点(在需要分开的两点间,或某个叶子结点的儿子,看实际情况。怎么找?前驱或后继),然后将虚拟节点旋至根节点删除,左右两个子树就是得出的两个Treap了。根据二叉排序树的性质,这时左子树的所有节点都小于右子树的节点。时间相当于一次插入操作的复杂度,也就是 log( n )

      8、合并:

        合并是指把两个Treap合并成一个Treap,本文指其中第一个Treap的所有节点都必须小于或等于第二个Treap中的所有节点,先不涉及两个普通treap的合并。只要加一个虚拟的根,把两棵树分别作为左右子树,然后把根删除就可以了。时间复杂度和删除一样,也是期望O(log n)

     练习题:

      洛谷P3369 【模板】普通平衡树

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    //#include<cstdlib>
    using namespace std;
    
    const int N=100005;
    
    int n,root,bnt;
    
    struct node{
        int ls,rs,cnt,siz,val,dev;
    }tre[N];
    
    char ch;
    
    bool f;
    
    inline int read()
    {
        int x=0;
        f=0;
        ch=getchar();
        while(!isdigit(ch))
            f|=ch=='-',ch=getchar();
        while(isdigit(ch))
            x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
        return f?-x:x;
    }
    
    inline void upt(int u)
    {
        tre[u].siz=tre[tre[u].ls].siz+tre[tre[u].rs].siz+tre[u].cnt;
    }
    
    inline void zig(int &u)
    {
        int v=tre[u].rs;
        tre[u].rs=tre[v].ls;
        tre[v].siz=tre[u].siz;
        tre[v].ls=u;
        upt(u);
        u=v;
    }
    
    inline void zag(int &u)
    {
        int v=tre[u].ls;
        tre[u].ls=tre[v].rs;
        tre[v].siz=tre[u].siz;
        tre[v].rs=u;
        upt(u);
        u=v;
    }
    
    void insert(int &u,const int &val)
    {
        if(!u)
        {
            u=++bnt;
            tre[u].val=val;
            tre[u].cnt=tre[u].siz=1;
            tre[u].dev=rand();
            return;
        }
        tre[u].siz++;
        if(tre[u].val==val)
        {
            tre[u].cnt++;
            return;
        }
        if(val<tre[u].val)
        {
            insert(tre[u].ls,val);
            if(tre[tre[u].ls].dev<tre[u].dev)
                zag(u);
        }
        else
        {
            insert(tre[u].rs,val);
            if(tre[tre[u].rs].dev<tre[u].dev)
                zig(u);
        }
    }
    
    void del(int &u,const int &val)
    {
        if(!u)    
            return;
        if(tre[u].val==val)
        {
            if(tre[u].cnt>1)
            {
                tre[u].cnt--;
                tre[u].siz--;
            }
            else
            {
                if(tre[u].ls&&tre[u].rs)
                {
                    if(tre[tre[u].ls].dev<=tre[tre[u].rs].dev)
                    {
                        zag(u);
                        tre[u].siz--;
                        del(tre[u].rs,val);
                    }
                    else
                    {
                        zig(u);
                        tre[u].siz--;
                        del(tre[u].ls,val);
                    }
                }
                else 
                    if(tre[u].ls||tre[u].rs)
                        u=tre[u].ls+tre[u].rs;
                    else
                        u=0;
            }
            return;
        }
        tre[u].siz--;
        if(val<tre[u].val)
            del(tre[u].ls,val);
        else
            del(tre[u].rs,val);
    }
    
    int finrank(const int &u,const int &val)
    {
        if(!u)
            return 1;
        if(tre[u].val==val)
            return tre[tre[u].ls].siz+1;
        if(val<tre[u].val)
            return finrank(tre[u].ls,val);
        else
            return finrank(tre[u].rs,val)+tre[tre[u].ls].siz+tre[u].cnt;
    }
    
    int finval(const int &u,const int &rnk)
    {
        if(rnk<=tre[tre[u].ls].siz)
            return finval(tre[u].ls,rnk);
        if(rnk>tre[tre[u].ls].siz+tre[u].cnt)
            return finval(tre[u].rs,rnk-tre[tre[u].ls].siz-tre[u].cnt);
        return tre[u].val;
    }
    
    int finpre(const int &u,const int &val)
    {
        if(!u)
            return -99999999;
        if(tre[u].val<val)
        {
            int k=finpre(tre[u].rs,val);
            return max(k,tre[u].val);
        }
        else
            return finpre(tre[u].ls,val);
    }
    
    int finnxt(const int &u,const int &val)
    {
        if(!u)
            return 99999999;
        if(tre[u].val>val)
        {
            int k=finnxt(tre[u].ls,val);
            return min(k,tre[u].val);
        }
        else
            return finnxt(tre[u].rs,val);
    }
    
    int main()
    {
        srand(9999);
        int n=read();
        int opt,x;
        while(n--)
        {
            opt=read(),x=read();
            switch(opt)
            {
                case 1:
                    insert(root,x);
                    break;
                case 2:
                    del(root,x);
                    break;
                case 3:
                    printf("%d
    ",finrank(root,x));
                    break;
                case 4:
                    printf("%d
    ",finval(root,x));
                    break;
                case 5:
                    printf("%d
    ",finpre(root,x));
                    break;
                case 6:
                    printf("%d
    ",finnxt(root,x));
                    break;
            }
        }
        return 0;
    }
    既是示例代码也是题解

    结语:维持Treap的平衡性,强化值有着决定性的作用,故有时脸黑TLE也不是没有可能的。。。

    补充:普通treap是不能O(log n)做区间操作的。为什么?因为你对al~ar没法快速标记。如确实想用treap做区间操作,移步fhq treap。因为fhq treap可将一个区间内的点全都裂成一个树,给这个树根打标记就完成了对整个区间的标记。

    EX: 洛谷日报:不用旋转的treap?——fhq treap bug贼多不推荐

    参考资料:

    Treap百度百科

  • 相关阅读:
    iPhone网络编程之--Reachability
    ASIHTTPRequest 详解, http 请求终结者2
    什么情况下使用break关键字? 什么情况下使用Continue关键字? Java如何声明一个数组?JS如何声明一个数组?如何获取数组长度? 如何遍历数组?
    说说三元运算和if...else的相同之处? Switch语句的条件只能接受什么类型的值? 说说do...while和while的区别? 说说for循环的两种写法?
    String类的常用方法
    逻辑结算的结果是什么类型? 比较运算的值是什么类型? 声明字符串有哪几种方式?怎么写? Math类有哪些常用的方法? 三元运算怎么写?
    算术运算有哪些?逻辑运算有哪些?比较运算有哪些?
    Java中8种基本数据类型是哪些?
    Java如何声明变量?JS如何声明变量?
    回顾之前知识: 注释
  • 原文地址:https://www.cnblogs.com/InductiveSorting-QYF/p/12966808.html
Copyright © 2011-2022 走看看