平衡树讲解【Treap&&非旋Treap】
平衡树【Treap&&非旋Treap】
首先简单介绍一下平衡树,平衡树就是一棵二叉搜索树,但是因为二叉搜索树有时会变成一条链,那么复杂度就得不到优化。
平衡树利用旋转将二叉搜索树旋成平衡,让这棵树尽量保持一颗满二叉树,这样的复杂就是恒定的。
Treap
我现在来讲一下Treap,Treap=Tree+Heap,这是它名字的来源,也就是这棵二叉搜索树要满足堆的性质。(左儿子小于父节点,右儿子大于父节点)且(左儿子大于父节点,右儿子也要大于父节点)。
但是这显然是矛盾的,所以我们要另开一个数组,取一个随机值,用来满足Heap的性质。
所以说Treap的复杂度是很玄学的,你运气不好,就会退化成链,但是几率太小了,所以就算是不可能,理论估计是
我们先看一道例题,这样方便讲解BZOJ 3224
Description
您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
- 插入x数
- 删除x数(若有多个相同的数,因只删除一个)
- 查询x数的排名(若有多个相同的数,因输出最小的排名)
- 查询排名为x的数
- 求x的前驱(前驱定义为小于x,且最大的数)
- 求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 <,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 <,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;
}