其他分享
首页 > 其他分享> > Treap平衡树

Treap平衡树

作者:互联网

1.1

treap随机生成优先值

令以优先值为小根堆heap

其关键值为二叉搜索树tree

 

二叉搜索树满足性质:

  节点p的左子树内所有的关键值都小于等于p的关键值,

  节点p的右子树内所有的关键值都大于p的关键值。

二叉搜索树可以方便地查到节点p的排名,以及查排名为k的节点编号

 

问题在于,如果有序地输入一个序列,那树就会退化为一条链,树的深度变为$n$,每次的操作复杂度变为$O(n)$

 

对此,出现了二叉搜索树的优化——平衡树。平衡树可以通过一些操作使得树的深度稳定在$O(log n)$

 

 

Treap通过两个操作进行优化,spilt(分裂)和merge(合并)

 

分裂操作:保证堆到左边的点的数值都小于等于分裂值,右边的点都大于分裂值


 

初始树:

当分裂值为3时,分裂的步骤如下:

第一步:找到当前的根:

 

由于节点$1$的数值为$5$,大于分裂值$3$,所以节点$1$及其右子树堆到右边去

 

第二步:由于上一步的根大于分裂值,所以来到了上一步根的左子树  

来到了$2$号节点,由于$2$号节点的数值为$3 = 3$,所以其与其左子树一起堆到左边

第三步:由于上一步的根小于等于分裂值,所以来到了上一步根的右子树

找到了节点$5$,由于其值为$4$,大于分裂值$3$,所以将其堆到右边 

 

将节点$5$接到左子树上

这样就分裂完毕,得到了两棵树

代码如下:

void split(int p,int v,int &x,int &y){ //p这棵树按照v分裂,x和y是下一层分裂后两棵树的树根 
    if( !p ){ //如果没有这棵树根 
        x = y = 0;//没有分裂后的两棵树 
        return;
    } 
    if( v < val[p] ){ //如果节点p的数值比按照值大 
        y = p;//改了上一层的ch[p][0]
        split( ch[p][0],v,x,ch[p][0]);//分裂左子树,左堆不动,右堆
    }else{ 
        x = p;//同上 
        split( ch[p][1],v,ch[p][1],y );
    }
    pushup(p);//重新计算p的大小 
}

 

合并:以wei[ ]数组按照heap的性质合并


如对于上图的结果,将两棵树合并的过程如下:

第一步:比较两树树根的$wei$值

其中节点$2$的$wei[2] = 10,wei[5] = 7$,所以新树的根为节点$1$,并且把其右子树也一起搞过去

 

 第二步:比较 右堆的左子树 与 左堆的根 的$wei$的值

     因为$wei[5] = 21,wei[2] = 10 -> $,节点$2$的值小,所以将左堆的根与其左子树一起接到新树的左子树上

 第三步:再比较 左堆的右子树 与 右堆的左子树 的$wei$值

     由于左堆已经没有了右子树,所以直接将右堆的$5$号点加进当前节点($2$号点)的右子树

代码如下:

int merge(int x,int y){
    if( !x || !y ) return x|y;//如果有一棵子树缺失,返回另一棵子树 
    if( wei[x] < wei[y] ){ //左边的优先值小的话 
        ch[x][1] = merge( ch[x][1],y );//将左边的右子树与右边合并,左子树照常不管了 
        pushup(x);//重新计算左边的大小 
        return x;//左堆的根当做新树的当前的根 
    }else{ //右边优先值小 
        ch[y][0] = merge( x,ch[y][0] );//同理,将右边的左子树跟左堆 
        pushup(y);
        return y;
    }
}

 

删除:将整个树按照三个类型,拆成三个部分,再合并


我们将可以这个树按照三个标准:“小于删除值”、“等于删除值”、“大于删除值”分为三棵树

三棵树的树根分别为$x$,$y$,$z$,那么我们就只需要删除$y$树的根节点啦

先上代码:

void del( int p ){
    int x,y,z; 
    split( rt,p,x,z );
    split( x,p-1,x,y );
    if( y ){ //如果存在我们要删除的这个元素 
        y = merge( ch[y][0],ch[y][1] );
        //去掉根节点,也就是让这个树的左右子树合并
    }
    x = merge( x,y );
    rt = merge( x,z );
}

解释一下代码:

比如下面这个图,我们要删除$3$这个节点

先按照$3$拆第一步:

 

再按照$2$拆第二步

 

合并y树的两个儿子节点

 

 合并x,y树:

 

再合并一下x,z树即可(不再模拟)

 

插入:将原来的树分为两部分,再与新节点合并


用于插入一个权值为$p$的节点

我们按照$p$将原来的树分裂,这时$x$中的节点都小于等于$p$,$y$中的节点都大于$p$

再将新节点与$x,y$两棵树合并即可

代码如下:

void add( int p ){ //插入节点p 
    int x,y;
    split( rt,p,x,y );
    x = merge( nownode(p),x );
    rt = merge( x,y );
}

 

给数值求排名:类似搜索树,但不完全是


如果我们要查询数值为$p$的节点的排名,可以将树按照$p$分裂

然后答案就是$x$树的大小+1(加上自己才是最终排名)

代码如下:

int getrank( int p ){ //查询值为p的排名 
    int x,y;
    split( rt,p,x,y );
    int rank = size[x]+1;
    rt = merge( x,y );
    return ans;
}

 

给排名求数值:完全就是二叉搜索树


不再解释

上代码:

int getval( int rank ){
    int now = rt;
    while( now ){
        if( size[now] == rank ) return val[now];
        if( size[now] > rank )
            now = ch[now][0];//如果排名大了,说明前面的人多,往左 
        else{
            //如果排名小了,说明前面的少,往右
            rank -= size[ ch[now][0] ] + 1;
            //往右走说明当前节点以及其左子树必然被包含
            //往右走后树内将不再包含当前节点以及其左子树
            //所以rank要减去当前节点及其左子树的大小 
            now = ch[ch][1];
        }
    }
    return INF;//没有查到目标排名,返回没有节点 
}

 

求前驱后继


也基本就是二叉搜索树

分裂后一直往一个方向搜即可

代码:

int getfre( int p ){
    int x,y;
    splite( rt,p-1,x,y );
    int now = x;
    while( ch[now][1] ) now = ch[now][1];
    ans = val[now];
    rt = merge( x,y );
    return ans;
}
int getnxt( int p ){
    int x,y;
    splite( rt,p,x,y );
    int now = y;
    while( ch[now][0] ) now = ch[now][0];
    ans = val[now];
    rt = merge( x,y );
    return ans;
}

 

标签:左子,ch,merge,int,Treap,平衡,now,节点
来源: https://www.cnblogs.com/xiao-en/p/16511143.html