平衡树Treap【简单介绍】

平衡树

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

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

Treap

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

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

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

我们先看一道例题,这样方便讲解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。

猜你喜欢

转载自blog.csdn.net/qq_41357771/article/details/80212881