FHQ
FHQ是一种非旋Treap。没有了复杂的左/右旋操作所以十分适合菜鸡。
其保证复杂度的方法和Treap一样:给每一节点一个随机权记为(rad[i]),然后使平衡树满足是关于(rad[i])的堆。构建二差搜索树时,树的形态唯一而且期望深度为((logn))
*复杂度小证明:
这个思路来自于[一篇博客](关于非旋FHQ Treap的复杂度证明 - 谁是鸽王 - 博客园 (cnblogs.com))
(默认是大根堆)
将节点编号((i))按题中数据(记为(val[i]))从小到大排序得到序列(A)
将节点编号按(rad[i])从小到大排序得到序列(B)
发现(A)、(B)有如下性质:1、A就是这棵树的中序遍历。2、B的最后一个数是所有其他的祖宗(根)。
那么这棵树的形态如何确定?显然这是一道普及的题。
(B)中取最后一个数(root),然后在(A)中找到(root)。显然(root)左边是其左子树右边是其右子树。从(B)中挑出左右子树然后递归即可。
显然递归深度就是平衡树深度。
考虑这个过程的本质。对于(B)来说就是找到最后一个数(root),然后把(val[i])小于(val[root])的放在一边,大于的放在另一边,然后递归。
这不就是快速排序吗?只是参考点为最后一个罢了。由于(rad[i])是随机值所以B序列是随机的。好的,快排对于随机数据的期望复杂度是(O(nlogn))(递归期望深度为(logn)),所以由此法构建的平衡树期望深度为(logn)。仔细思考构建过程发现平衡树的形态也是唯一的。
代码
FHQ是怎么维护的呢?
FHQ的维护基于两个操作:merge、split。merge负责把两棵树合并。split负责把一棵树裂开,裂成一边小于(小于等于)a,一边大于等于(大于)a。
#includ<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<ctime>
using namespace std;
#define N 101001
int tot,size[N],ch[N][2],rad[N],val[N],root;
int new_node(int x){
int now=++tot;
rad[now]=rand();
val[now]=x;
size[now]=1;
return now;
}
void update(int now){
size[now]=size[ch[now][1]]+size[ch[now][0]]+1;
}
int merge(int x,int y){
if(!x||!y)return x+y;
if(rad[x]>rad[y]){
ch[x][1]=merge(ch[x][1],y);
update(x);
return x;
}
else {
ch[y][0]=merge(x,ch[y][0]);
update(y);
return y;
}
}
void split(int &x,int &y,int now,int w){
if(!now){
x=y=0;
return;
}
if(val[now]<=w){
x=now;
split(ch[x][1],y,ch[now][1],w);
}
else{
y=now;
split(x,ch[y][0],ch[now][0],w);
}
update(now);
}
int kth(int now,int k){
int l=ch[now][0];
if(size[l]>=k)return kth(l,k);
else if(size[l]+1==k)return now;
else return kth(ch[now][1],k-size[l]-1);
}
int read(){
int sum=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){sum=sum*10+ch-'0';ch=getchar();}
return sum*f;
}
int main(){
srand(time(NULL));
int T=read();
while(T--){
int type=read();
int a=read();int x,y,z;
if(type==1){
split(x,y,root,a);
root=merge(merge(x,new_node(a)),y);
}
if(type==2){
split(x,z,root,a);
split(x,y,x,a-1);
y=merge(ch[y][0],ch[y][1]);
root=merge(merge(x,y),z);
}
if(type==3){
split(x,y,root,a-1);
printf("%d
",size[x]+1);
root=merge(x,y);
}
if(type==4)printf("%d
",val[kth(root,a)]);
if(type==5){
split(x,y,root,a-1);
printf("%d
",val[kth(x,size[x])]);
root=merge(x,y);
}
if(type==6){
split(x,y,root,a);
printf("%d
",val[kth(y,1)]);
root=merge(x,y);
}
}
return 0;
}
普通treap也提一下
插入操作:按普通的二叉搜索树来插,然后回溯时看到不符合堆的性质就把大的旋上来。
删除操作:找到待删点然后把它旋到叶子,然后删除就好了。