平衡树讲解【Treap&&非旋Treap】

平衡树【Treap&&非旋Treap】

首先简单介绍一下平衡树,平衡树就是一棵二叉搜索树,但是因为二叉搜索树有时会变成一条链,那么复杂度就得不到优化。

平衡树利用旋转将二叉搜索树旋成平衡,让这棵树尽量保持一颗满二叉树,这样的复杂就是恒定的O(nlog2n)

Treap

我现在来讲一下Treap,Treap=Tree+Heap,这是它名字的来源,也就是这棵二叉搜索树要满足堆的性质。(左儿子小于父节点,右儿子大于父节点)且(左儿子大于父节点,右儿子也要大于父节点)。

但是这显然是矛盾的,所以我们要另开一个数组,取一个随机值,用来满足Heap的性质。

所以说Treap的复杂度是很玄学的,你运气不好,就会退化成链,但是几率太小了,所以就算是不可能,理论估计是O(nlog2n)

我们先看一道例题,这样方便讲解BZOJ 3224

Description

您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:

  1. 插入x数
  2. 删除x数(若有多个相同的数,因只删除一个)
  3. 查询x数的排名(若有多个相同的数,因输出最小的排名)
  4. 查询排名为x的数
  5. 求x的前驱(前驱定义为小于x,且最大的数)
  6. 求x的后继(后继定义为大于x,且最小的数)

Input

第一行为n,表示操作的个数,下面n行每行有两个数opt和x,opt表示操作的序号(1<=opt<=6)

Output

对于操作3,4,5,6每行输出一个数,表示对应答案

Sample Input

10

1 106465

4 1

1 317721

1 460929

1 644985

1 84185

1 89851

6 81968

1 492737

5 493598

Sample Output

106465

84185

492737

HINT

1.n的数据范围:n<=100000

2.每个数的数据范围:[-2e9,2e9]

这是道平衡树的板子题,先来介绍一下变量

struct xcw{
    int Son[2],tot,x,Num;
    int &operator [](const int b){return Son[b];}
//  void clean(){Son[0]=Son[1]=tot=x=Num=0;}
}Tre[100005];

tot:这棵子树的节点个数。

Son[2]:左右节点,我用了重载运算符,我觉得比较方便。

x:表示这个节点的权值,也就是满足二叉搜索树的值。

Num:这个变量存的是随机数,为了满足Heap的性质。

下面的代码就是旋转

int Turn(int &x,int p){int t=Tre[x][p];Tre[x][p]=Tre[t][!p];Tre[t][!p]=x;Change(x);Change(t);x=t;}

是不是很方便,直接将左旋和右旋合并了。

Change()是修正节点个数

void Change(int x){Tre[x].tot=Tre[Tre[x][0]].tot+Tre[Tre[x][1]].tot+1;}

还有很重要的一个Random()。因为C++调rand()太慢了,所以手写了一个。

int Random(){
    static int seed=703; //seed可以随便取
    return seed=int(seed*48271LL%2147483647);
}

然后就是插入了,其实和二叉搜索树是一样的,不过加了个旋转罢了。

void Insert(int &x,int p){
    if(!x){x=++Top;Tre[Top].tot=1;Tre[Top].x=p;Tre[Top].Num=Random();return;}
    Tre[x].tot++;
    if(p<=Tre[x].x){Insert(Tre[x][0],p);if(Tre[Tre[x][0]].Num<Tre[x].Num) Turn(x,0);}
    else{Insert(Tre[x][1],p);if(Tre[Tre[x][1]].Num<Tre[x].Num) Turn(x,1);}
}
if(Tre[Tre[x][0]].Num<Tre[x].Num) Turn(x,0);
if(Tre[Tre[x][1]].Num<Tre[x].Num) Turn(x,1);

这就是为了满足堆的性质,跟据这个进行旋转。

下面是删除,将要删除的节点旋到叶,然后删除。

void Del(int &x,int p){
    if(Tre[x].x==p){
        if(Tre[x][0]*Tre[x][1]==0){x=Tre[x][0]+Tre[x][1];return;}
        if(Tre[Tre[x][0]].Num<Tre[Tre[x][1]].Num){Turn(x,0);Del(Tre[x][1],p);}
        else{Turn(x,1);Del(Tre[x][0],p);}
    }else if(Tre[x].x>p) Del(Tre[x][0],p);else Del(Tre[x][1],p);
    Change(x);
}

接下来就是查找了

int Fnd(int x,int p){//查找p数的排名 
    if(!x) return 1;
    if(Tre[x].x>=p) return Fnd(Tre[x][0],p);
    else return Fnd(Tre[x][1],p)+Tre[Tre[x][0]].tot+1;
}
int Ask(int x,int p){//查找排名为p的数 
    if(Tre[Tre[x][0]].tot==p-1) return Tre[x].x;
    if(Tre[Tre[x][0]].tot>=p) return Ask(Tre[x][0],p);
    else return Ask(Tre[x][1],p-Tre[Tre[x][0]].tot-1);//在右子树内的排名要减去左子树的个数,应该好理解。
}
int FndMax(int x,int p){//找前驱
    if(!x) return -(1e9);
    if(Tre[x].x<p) return max(Tre[x].x,FndMax(Tre[x][1],p));
    else return FndMax(Tre[x][0],p);
}
int FndMin(int x,int p){//找后继 
    if(!x) return 1e9;
    if(Tre[x].x>p) return min(Tre[x].x,FndMin(Tre[x][0],p));
    else return FndMin(Tre[x][1],p);
}

Treap就讲完了,十分简单,完全就是一颗二叉搜索树。

完整代码详见我的博客

然后推荐几道例题:

BZOJ 1208,BZOJ 1503,BZOJ 1588。


非旋Treap

其实很简单的,只不过是通过分裂和合并使这个二叉树平衡罢了。

但是可以进行区间旋转,效率高于Splay,而且代码短,但是有点难理解。

我理解的也不是很好,如果理解有误,请大家留言,谢谢。

变量还是不变

struct xcw{
    int Son[2],Num,Siz,x;
    int &operator [](const int b){return Son[b];}
}Tre[100005];

插入点

void Add(int &x,int p){
    Tre[++tot][0]=Tre[tot][1]=0;
    Tre[tot].Siz=1,Tre[tot].Num=Random(),Tre[tot].x=p;
    x=tot;
}

接下来就是重要的拆分和合并了。

拆分

根据p将这棵树分成lt和rt,使任何lt中权值 Tre[x].x < p,任何rt中权值 Tre[x].x >= p。

void Split(int x,int &lt,int &rt,int p){
    if(!x){lt=rt=0;return;}
    if(p<Tre[x].x) rt=x,Split(Tre[x][0],lt,Tre[x][0],p);//向左走,把x这个归于右树
    else lt=x,Split(Tre[x][1],Tre[x][1],rt,p);//向右走,把x这个归于左树
    Change(x);//Change还是不变
}

合并

void Merge(int &x,int lt,int rt){
    if(!lt||!rt){x=lt+rt;return;}
    if(Tre[lt].Num<Tre[rt].Num) x=lt,Merge(Tre[x][1],Tre[lt][1],rt);//根据随机值,决定合并的先后顺序
    else x=rt,Merge(Tre[x][0],lt,Tre[rt][0]);
    Change(x);
}

其实这段我也没有理解的很透彻,就是背板子就可以了。

然后是插入

先拆分,然后将这个点加入x这棵子树,再合并两棵子树。

先解释一下Split():

Split(Root,x,y,p),表示将以Root为根的树按照p拆成以x为根的子树和以y为根的子树

void Insert(int p){
    int x=0,y=0,z=0;
    Add(z,p),Split(Root,x,y,p);
    Merge(x,x,z),Merge(Root,x,y);
}

删除

先拆分树,然后将这个点拆出来,将这个点z的子树重新整理接上,然后合并回去。

void Del(int p){
    int x=0,y=0,z=0;
    Split(Root,x,y,p);Split(x,x,z,p-1);
    Merge(z,Tre[z][0],Tre[z][1]);//整理z的两棵子树,重新成为以z为根的子树
    Merge(x,x,z);Merge(Root,x,y);
}

查询p数的排名

找到p小的一个数的根,然后这棵树的子树个数加上p本身就是p的排名。

int Fnd(int p){
    int x=0,y=0;
    Split(Root,x,y,p-1);
    int ret=Tre[x].Siz+1;
    Merge(Root,x,y);
    return ret;
}

查询排名为p的数

这个没有变化,还是普通的递归求解。

int Ask(int x,int p){
    if(Tre[Tre[x][0]].Siz+1==p) return Tre[x].x;
    if(Tre[Tre[x][0]].Siz>=p) return Ask(Tre[x][0],p);
    else return Ask(Tre[x][1],p-Tre[Tre[x][0]].Siz-1);
}

查询p的前驱

先找到p数的排名,然后找到排名为(p数的排名)的数。

int FndMin(int p){
    int x=0,y=0;
    Split(Root,x,y,p-1);
    int ret=Ask(x,Tre[x].Siz);
    Merge(Root,x,y);
    return ret;
}

查询p的后继

一样的想法,就是反了过来。

int FndMax(int p){
    int x=0,y=0;
    Split(Root,x,y,p);
    int ret=Ask(y,1);
    Merge(Root,x,y);
    return ret;
}

操作都讲完了,下面就是这题的完整代码

#include<cstdio>
using namespace std;
int tot,Root,Q;
struct xcw{
    int Son[2],Num,Siz,x;
    int &operator [](const int b){return Son[b];}
}Tre[100005];
int Random(){
    static int Sed=703;
    return Sed=int(Sed*48271LL%2147483647);
}
void Add(int &x,int p){
    Tre[++tot][0]=Tre[tot][1]=0;
    Tre[tot].Siz=1,Tre[tot].Num=Random(),Tre[tot].x=p;
    x=tot;
}
void Change(int x){Tre[x].Siz=Tre[Tre[x][0]].Siz+Tre[Tre[x][1]].Siz+1;}
void Split(int x,int &lt,int &rt,int p){
    if(!x){lt=rt=0;return;}
    if(p<Tre[x].x) rt=x,Split(Tre[x][0],lt,Tre[x][0],p);
    else lt=x,Split(Tre[x][1],Tre[x][1],rt,p);
    Change(x);
}
void Merge(int &x,int lt,int rt){
    if(!lt||!rt){x=lt+rt;return;}
    if(Tre[lt].Num<Tre[rt].Num) x=lt,Merge(Tre[x][1],Tre[lt][1],rt);
    else x=rt,Merge(Tre[x][0],lt,Tre[rt][0]);
    Change(x);
}
void Insert(int p){
    int x=0,y=0,z=0;
    Add(z,p),Split(Root,x,y,p);
    Merge(x,x,z),Merge(Root,x,y);
}
void Del(int p){
    int x=0,y=0,z=0;
    Split(Root,x,y,p);Split(x,x,z,p-1);
    Merge(z,Tre[z][0],Tre[z][1]);Merge(x,x,z);Merge(Root,x,y);
}
int Fnd(int p){//查找p数的排名
    int x=0,y=0;
    Split(Root,x,y,p-1);
    int ret=Tre[x].Siz+1;
    Merge(Root,x,y);
    return ret;
}
int Ask(int x,int p){
    if(Tre[Tre[x][0]].Siz+1==p) return Tre[x].x;
    if(Tre[Tre[x][0]].Siz>=p) return Ask(Tre[x][0],p);
    else return Ask(Tre[x][1],p-Tre[Tre[x][0]].Siz-1);
}
int FndMin(int p){
    int x=0,y=0;
    Split(Root,x,y,p-1);
    int ret=Ask(x,Tre[x].Siz);
    Merge(Root,x,y);
    return ret;
}
int FndMax(int p){
    int x=0,y=0;
    Split(Root,x,y,p);
    int ret=Ask(y,1);
    Merge(Root,x,y);
    return ret;
}
int main(){
    #ifndef ONLINE_JUDGE
    freopen("prob.in","r",stdin);
    freopen("prob.out","w",stdout);
    #endif
    scanf("%d",&Q);
    for(int i=1;i<=Q;i++){
        int opt,x;scanf("%d%d",&opt,&x);
        if(opt==1) Insert(x);else
        if(opt==2) Del(x);else
        if(opt==3) printf("%d\n",Fnd(x));else
        if(opt==4) printf("%d\n",Ask(Root,x));else
        if(opt==5) printf("%d\n",FndMin(x));else printf("%d\n",FndMax(x));
    }
    return 0;
}
posted @ 2018-05-06 11:29  XSamsara  阅读(230)  评论(0编辑  收藏  举报