Treap平衡树

1 二叉树搜索树与平衡树

二叉搜索树满足性质:

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

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

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

 

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

 

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

 

其中的Treap有是一种优化方案,它会随机生成优先值,令以优先值满足小根堆heap的性质,其关键值为二叉搜索树tree的性质

 

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

 

2 Treap可实现操作与代码

2.0 有关数组的定义

对于每个节点,我们需要记录它四个信息,分别如下:

int val[NUM];//该点的数值
int wei[NUM];//随机生成的值,满足heap性质
int ch[NUM][3];//该节点的左右孩子
int size[NUM];//该节点的总子树大小(包括自己)

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


 

初始树:

当分裂值为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的大小 
}

 

2.2 合并:以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;
    }
}

 

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


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

三棵树的树根分别为$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树即可(不再模拟)

 

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


用于插入一个权值为$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 );
}

 

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


如果我们要查询数值为$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;
}

 

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


不再解释

上代码:

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

 

2.7 求前驱后继


也基本就是二叉搜索树

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

代码:

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;
}

 

3 总代码

有一些第一遍打出锅了的地方,要注意昂

#include<iostream>
#include<stdlib.h>
#include<cstdio>
#include<time.h>
#define NUM 1000010
#define INF 0x7f7f7f
using namespace std;

int wei[NUM],val[NUM],size[NUM];
int ch[NUM][3];
int cnt,n,rt;

void pushup( int p ){
    size[p] = size[ch[p][0]] + size[ch[p][1]] + 1;//不知为何某人总不写这几个字符,反而写return
}
void split( int p,int v,int &x,int &y ){ //**
    if( !p ){
        x = y = 0;
        return;
    }
    if( v < val[p] ){ //如果当前节点大 
        y = p;
        split( ch[p][0],v,x,ch[p][0] );
    }else{
        x = p;
        split( ch[p][1],v,ch[p][1],y );
    }
    pushup( p );//我让你不写这句话! 
}
int merge( int x,int y ){ //**
    if( !x || !y ) return x|y;//if(!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;
    }
}
int newnode( int v ){
    val[++cnt] = v;
    wei[cnt] = rand();
    size[cnt] = 1;
    return cnt;
}
void add( int v ){
    int x,y; 
    int z = newnode( v );
    split( rt,v,x,y );
    x = merge( x,z );
    rt = merge( x,y );
}
void del( int v ){
    int x,y,z;
    split( rt,v,x,z );
    split( x,v-1,x,y );
    if( y )
        y = merge( ch[y][0],ch[y][1] );
    x = merge( x,y );
    rt = merge( x,z );
}
int getrank( int v ){ //**
    int x,y;
    split( rt,v-1,x,y );
    int ans = size[x]+1;
    rt = merge( x,y );
    return ans;
}
int getval( int rank ){
    int now = rt;
    while( now ){
        if( size[ch[now][0]]+1 == rank ) //是比自己小的,if条件写错啦! 
            return val[now];
        if( size[ch[now][0]]+1 < rank ){ //这里也是错了 
            rank -= size[ch[now][0]]+1;//是左子树的大小! 
            now = ch[now][1];
        }else
            now = ch[now][0];
    }
}
int getfre( int v ){
    int x,y;
    split( rt,v-1,x,y );
    int now = x;
    while( ch[now][1] )
        now = ch[now][1];
    int ans = val[now];
    rt = merge( x,y );
    return ans;
}
int getnxt( int v ){
    int x,y;
    split( rt,v,x,y );
    int ans,now = y;
    while( ch[now][0] )
        now = ch[now][0];
    ans = val[now];
    rt = merge( x,y );//让你不写rt =  
    return ans;
}

int main(){
srand( time(
0) ); cin >> n; int op,v; while( n-- ){ cin >> op >> v; if( op == 1 ) add( v ); else if( op == 2 ) del( v ); else if( op == 3 ) cout << getrank( v ) << endl; else if( op == 4 ) cout << getval( v ) << endl; else if( op == 5 ) cout << getfre( v ) << endl; else if( op == 6 ) cout << getnxt( v ) << endl; }
return 0; }

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

 

posted @ 2022-07-27 14:28  little_sheep_xiaoen  阅读(35)  评论(0编辑  收藏  举报