FHQ Treap
就是非旋Treap啦
treap=tree+heap,既满足二叉搜索树的性质也满足堆的性质
是一个非常好用的平衡树,实现不难,常数一般,还可以可持久化
特别有意思。
这个玩意儿有什么用呢?
就是平衡树的作用啊
反正我觉得这个东西比普通treap有用,比splay好理解而且好写
详细的实现可以看下面
FHQ_treap的实现
FHQ_treap可以实现以下几个基本操作:
1、在某个序列中插入一个数
2、在某个序列中删除一个数
3、查询排名为i的数的值
4、查询值为i的数的排名
5、求i的前驱(定义为比i小且最大的数)
6、求i的后继(比i大且最小的数)
我们通过一道模板来看一看怎么写这个平衡树吧
洛谷P3369 【模板】普通平衡树
定义
首先看一下这个FHQ_treap的节点的定义
struct treap{
int lch,rch;//左右儿子
int siz;//子树大小(儿子节点数量)
int key;//key值满足堆的性质
int val;//每个节点存储的权值,满足二叉搜索树的性质
}T[200001];
其中的key值是一个随机的值。
在这棵平衡树中,左儿子的val值都比自己低,右儿子都比自己高
对于子树大小,我们有如下的维护代码:
void update(int x){
T[x].siz=T[T[x].lch].siz+T[T[x].rch].siz+1;
}
维护子树大小是为了查询排名
所以为什么要用key维护堆的性质呢?这样可以让整棵树的层数变小,尽量靠近logn,然后操作时就可以快一些
接下来看看基本操作怎么实现吧
基本操作:split和merge
首先说一下split
split就是把原来的一棵大树分成两棵小树,其中左边的小树的值全部小于右边的小树
split的思想就是按照val值来分裂,详细的实现可以看代码
void split(int now,int &a,int &b,int val){//now是准备要分裂的节点,a和b是两棵小树,a小树的val值全部小于等于val,b小树的val值全部大于val
if(now==0){//如果没得分了,直接停止返回就好了
a=b=0;
return;
}
if(T[now].val<=val){//如果现在这个节点的val值是小于或等于val的,我们把它分到a小树里头
a=now;
split(T[now].rch,T[a].rch,b,val);//然后把它的右子树,即比它的val值还要大的节点,拆成a的右子树以及b,因为a的右子树和b都比当前节点的val值还要大
}
else {//否则就反之嘛,一样的
b=now;
split(T[now].lch,a,T[b].lch,val);
}
update(now);//维护子树大小
}
然后是merge。
merge就是把两棵树合并到同一棵树上去
但是我们发现,如果直接在中间添加一个根节点然后连起来,很可能会不满足堆的性质
所以我们按照key值来合并,详细实现代码如下:
void merge(int &now,int a,int b){//now是我合并出来的新节点
if(a==0||b==0){
now=a+b;//直接当成a或b
return;
}
if(T[a].key<T[b].key){//按key值合并以维护堆的性质
now=a;//如果a的key值比b的key值小,我们就把a的右儿子(即val值比a大的元素)与b继续合并
merge(T[now].rch,T[a].rch,b);
}
else {
now=b;
merge(T[now].lch,a,T[b].lch);//反之亦然
}
update(now);//一样的,维护子树大小
}
组合操作
1、插入与删除
插入的话我们就新建一个节点,设其值为val,然后把原树拆成两棵,一棵小于等于val,一棵大于val,
然后把新节点与小于等于val的树拼起来,最后整一个拼起来就好了。
删除,设这个要删除的节点为val,那么我们把原树拆成两棵,一个小于等于val一个大于val,
然后在小于等于val的树上面拆成一棵小于等于val-1的一棵大于val-1(即等于val的)
然后假装等于val的那个节点不存在,把原本等于val的那棵树的左儿子和右儿子连起来,中间等于val的节点就不见了
最后全部树连起来即可
void insert(int val){
int x=0,y=0,z=newnode(val);
split(root,x,y,val);
merge(x,x,z);
merge(root,x,y);
}
void del(int val){
int x=0,y=0,z=0;
split(root,x,y,val);
split(x,x,z,val-1);
merge(z,T[z].lch,T[z].rch);
merge(x,x,z);
merge(root,x,y);
}
2、查排名/权值
查排名,我们把原树拆成两棵,一棵小于val一棵大于等于val
然后我们惊奇地发现val的排名就是小于val的节点的数量+1
最后记得要再拼回去
查权值:
对于当前节点,如果它的左子树的siz+1刚好是rank,那么这个节点的排名就是rank,刚好是我们想要的
否则,假如siz比rank大,我们就进入右子树找,反之进入左子树找
int find(int now,int rank){
while(T[T[now].lch].siz+1!=rank){
if(T[T[now].lch].siz>=rank)now=T[now].lch;
else {
rank-=(T[T[now].lch].siz+1);//为什么要减可以自己想一想
now=T[now].rch;
}
}
return T[now].val;
}
int getrank(int val){
int x=0,y=0;
split(root,x,y,val-1);
int ans=T[x].siz+1;
merge(root,x,y);
return ans;
}
int getval(int rank){
return find(root,rank);
}
3、求前驱/后继
首先找前驱,我们拆成两棵树,然后在权值小于等于val-1的那棵树上找最大的即可
后继就在权值大于val的那棵树上找到最小的就可以了
int getpre(int val){
int x=0,y=0;
split(root,x,y,val-1);
int ans=find(x,T[x].siz);
merge(root,x,y);
return ans;
}
int getnxt(int val){
int x=0,y=0;
split(root,x,y,val);
int ans=find(y,1);
merge(root,x,y);
return ans;
}
其他的东西(注意项)
1、一开始一定要新建一个虚拟的根节点
2、假如我们要维护的序列是有顺序的,那么我们就把val值改一改,改成位置就好了
完整代码
#include<bits/stdc++.h>
using namespace std;
#define xyjakioi 0x7fffffff
struct treap{
int lch,rch;
int siz;
int key;
int val;
}T[200001];
int tot;
int root=1;
int seed=19260817;
int ran(){
return seed=int(seed*2333ll%2147483647);
}
int newnode(int val){
T[++tot].siz=1;
T[tot].val=val;
T[tot].key=ran();
T[tot].lch=0;
T[tot].rch=0;
return tot;
}
void update(int x){
T[x].siz=T[T[x].lch].siz+T[T[x].rch].siz+1;
}
void split(int now,int &a,int &b,int val){
if(now==0){
a=b=0;
return;
}
if(T[now].val<=val){
a=now;
split(T[now].rch,T[a].rch,b,val);
}
else {
b=now;
split(T[now].lch,a,T[b].lch,val);
}
update(now);
}
void merge(int &now,int a,int b){
if(a==0||b==0){
now=a+b;
return;
}
if(T[a].key<T[b].key){
now=a;
merge(T[now].rch,T[a].rch,b);
}
else {
now=b;
merge(T[now].lch,a,T[b].lch);
}
update(now);
}
int find(int now,int rank){
while(T[T[now].lch].siz+1!=rank){
if(T[T[now].lch].siz>=rank)now=T[now].lch;
else {
rank-=(T[T[now].lch].siz+1);
now=T[now].rch;
}
}
return T[now].val;
}
void insert(int val){
int x=0,y=0,z=newnode(val);
split(root,x,y,val);
merge(x,x,z);
merge(root,x,y);
}
void del(int val){
int x=0,y=0,z=0;
split(root,x,y,val);
split(x,x,z,val-1);
merge(z,T[z].lch,T[z].rch);
merge(x,x,z);
merge(root,x,y);
}
int getrank(int val){
int x=0,y=0;
split(root,x,y,val-1);
int ans=T[x].siz+1;
merge(root,x,y);
return ans;
}
int getval(int rank){
return find(root,rank);
}
int getpre(int val){
int x=0,y=0;
split(root,x,y,val-1);
int ans=find(x,T[x].siz);
merge(root,x,y);
return ans;
}
int getnxt(int val){
int x=0,y=0;
split(root,x,y,val);
int ans=find(y,1);
merge(root,x,y);
return ans;
}
int main(){
//freopen("in.txt","r",stdin);
newnode(xyjakioi);//必须,一定,肯定,要新建节点!
int n;
scanf("%d",&n);
for(int i=1;i<=n;++i){
int opt,x;
scanf("%d%d",&opt,&x);
if(opt==1){
insert(x);
}
if(opt==2){
del(x);
}
if(opt==3){
printf("%d\n",getrank(x));
}
if(opt==4){
printf("%d\n",getval(x));
}
if(opt==5){
printf("%d\n",getpre(x));
}
if(opt==6){
printf("%d\n",getnxt(x));
}
}
}
总结
FHQ_treap是一个好理解、好实现、常数不算大的平衡树,非常推荐大家学哦
是进阶数据结构的起点,成长的神犇的必经之路!