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百度百科

原文地址:https://www.cnblogs.com/InductiveSorting-QYF/p/12966808.html