无旋平衡树(treap)
引子
在平衡树算法中,大多需要进行旋转操作,但旋转操作细节较多,容易写错,并且不好调试。所以我们需要不用旋转的平衡树——分裂合并 treap
那么这种平衡树是如何维持平衡的呢?容易想到,在建立包含一个序列中的数字的树时,如果能够随机选择元素插入的次序,那么所建立的树应当是接近平衡的,而由于我们建立的树需要支持插入,删除等操作,因此需要在每个节点建立时随机选择一个平衡因子,在之后的操作中维持树上的平衡因子最小堆或最大堆的性质,这样就可以实现期望平衡
实现
基本操作
人如其名,分裂合并treap的核心操作就是分裂与合并。
Split
在分裂操作中,我们所做的操作是对于给定的权值k,将当前的树分裂为权值大于k和小于等于k两部分。
void split(int now,int k,int &x,int &y)\\x,y分别表示当前权值小于等于k的子树的根节点和大权值大于k的子树的根节点
{
if(!now)x=y=0;//若已经搜索到树叶,则直接返回
else
{
if(val[now]<=k)
x=now,split(ch[now][1],k,ch[now][1],y);//如果当前节点的权值小于等于k,则小于等于k的子树的根节点即为当前节点,同时继续遍历右子树
else
y=now,split(ch[now][0],k,x,ch[now][0]);//同上
update(now);
}
}
Merge
在合并操作中,所做的操作是对于两棵树x,y(x中所有节点的权值小于y中所有结点的权值
int merge(int x,int y)//y中的所有权值大于x中的权值
{
if(!x||!y)return x+y;//如果需要合并的一个树为空,则直接返回另一个待合并树
else
{
if(pri[x]<pri[y])//维护平衡因子小根堆性质
{
ch[x][1]=merge(ch[x][1],y);//将y与x右子树合并
update(x);return x;
}
else
{
ch[y][0]=merge(x,ch[y][0]);//将x与y左子树合并
update(y);return y;
}
}
}
题目实现
基本操作讲完了,接下来要做
的就是通过这两种操作来实现题目要求的各种功能。
Insert
在插入操作中,对于需要插入的x,为了满足merge操作中需要合并的两个树权值所满足的性质,我们首先将原树分裂为大于x和小于等于x两个部分,在将x与两者依次合并
void insert(int v)
{
int x,y;
split(root,v,x,y);
root=merge(merge(x,new_node(v))),y);
}
Delete
删除权值为v的点,把树按照v分成两个a,b,再把a按照v-1分成c,d。把c的两个子树合并起来,再将得到的三个树依次合并
void del(int v)
{
int x,y,z;
split(root,v,x,z);
split(x,v-1,x,y);
y=merge(ch[y][0],ch[y][1]);
root=merge(merge(x,y),z);
}
Rank
将原树分裂为大于v-1和小于等于v-1两个部分,将小于等于v-1的子树元素数量加一即为排名
int rank(int v)
{
int x,y;
split(root,v-1,x,y);
int rank=size[x]+1;
root=merge(x,y);
return rank;
}
前驱,后驱和排名和普通二叉搜索树类似,略
完整代码
#include<ctime>
#include<cstdlib>
#include<iostream>
using namespace std;
const int N=500005;
int val[N],ch[N][2],pri[N],siz[N],sz,root;
void update(int now)
{
size[now]=size[ch[now][0]]+size[ch[now][1]]+1;
}
int new_node(int v)
{
size[++sz]=1;
val[sz]=v;
pri[sz]=rand();
return sz;
}
void split(int now,int k,int &x,int &y)
{
if(!now)x=y=0;
else
{
if(val[now]<=k)
x=now,split(ch[now][1],k,ch[now][1],y);
else
y=now,split(ch[now][0],k,x,ch[now][0]);
update(now);
}
}
int merge(int x,int y)//y中的所有权值大于x中的权值
{
if(!x||!y)return x+y;
else
{
if(pri[x]<pri[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 insert(int v)
{
int x,y;
split(root,v,x,y);
root=merge(merge(x,new_node(v))),y);
}
void del(int v)
{
int x,y,z;
split(root,v,x,z);
split(x,v-1,x,y);
y=merge(ch[y][0],ch[y][1]);
root=merge(merge(x,y),z);
}
int rank(int v)
{
int x,y;
split(root,v-1,x,y);
int rank=size[x]+1;
root=merge(x,y);
return rank;
}
int kth(int k)
{
while(true)
{
if(k<=size[ch[now][0]])
now=ch[now][0];
else if(k==size[ch[now][0]]+1)
return now;
else
k-=size[ch[now][0]]+1,now=ch[now][1];
}
}
int precursor(int v)
{
int x,y;
split(root,v-1,x,y);
int ans=val[kth(x,size[x])];
root=merge(x,y);
return ans;
}
int successor(int v)
{
int x,y;
split(root,v,x,y);
int ans=val[kth(y,1)];
root=merge(x,y);
return ans;
}
int main()
{
int T,v,opt;
srand((unsigned)time(NULL));
cin>>T;
while(T--)
{
cin>>v>>opt;
if(opt==1)
insert(v);
else if(opt==2)
del(v);
else if(opt==3)
cout<<rank(v)<<endl;
else if(opt==4)
cout<<val[kth(root,v)]<<endl;
else if(opt==5)
cout<<precursor(v)<<endl;
else if(opt==6)
cout<<successor(v)<<endl;
}
}