fhq-Treap原理
fhq-Treap是一种非常优秀的无旋平衡树。下面我们来介绍他的函数原理
我们先介绍他的两个重要函数
1.spilt(分割函数)
我们将整棵树按照值的大小分割成x,y两棵树,x中的值都是小于等于key值的
这样我们就可以通过递归操作来解析,下面看具体代码
void spilt(int p,int key,int &x,int &y){ //按key分裂 if(!p) //分裂完毕赋空值 x=y=0; else{ if(tr[p].key<=key){ //满足左子树条件,所有值小于key x=p; spilt(tr[p].r,key,tr[p].r,y);//递归右边节点 } else{ y=p; spilt(tr[p].l,key,x,tr[p].l);//同理,递归左边 } pushup(p); //无论哪个值取到p,只需要更新p,那就更新完了节点 } }
重点就是如果当前值小于等于key,那么他的左子树都是小于等于key的,右子树有可能也存在,因此将x=p,再将右子树传递,反之同理,结束点是目前这个是空树
2.merge(合并函数)
我们考虑将两个已经分割好的树合并起来,有几个注意点
递归结束点就是存在一棵树已经合并完了,那么只需要返回x+y就是我们当前所需的根节点值
下面我们先来看代码
int merge(int x,int y){ if(!x||!y) return x+y;//返回不为0的值 if(tr[x].val>=tr[y].val){ //根据二叉堆的性质 tr[x].r=merge(tr[x].r,y); //x中值都比y中的小 pushup(x); return x; //父节点是x } else{ tr[y].l=merge(x,tr[y].l); pushup(y); return y; } }
如果左边节点的索引值大于右边,我们又可以知道根据二叉树和二叉堆的共同作用下,x中的树要比y中的树小,但是x又要是根节点,那么y树就要在x的右子树中,反之同理
注意合并完后要更新节点的个数大小
下面我们来分析这两个函数如何完成平衡树的操作
众所周知,平衡树的操作为了获取以下功能
1.insert函数
我们需要将一个新值插入树当中,那么我们可以先将树按这个新值key来切割,左边都是小于等于key,然后我们把x和key生成的节点合并,再把x和y合并就可
int insert(int key){ spilt(root,key,x,y); //按key值分离 root=merge(merge(x,get(key)),y);//合并回来更新root }
2.remove函数(删除一个节点)
我们先按key分离x,z
再按key-1分离x,y
这样y树中值就全都是key了,然后我们将y中左子树和右子树合并,就成功删除了根节点
void remove(int key){ spilt(root,key,x,z); spilt(x,key-1,x,y); y=merge(tr[y].l,tr[y].r); root=merge(merge(x,y),z); }
3.getrank(根据值找排名)
我们只需要按key-1切割树,那么排名就是x树的节点+1
非常简单
int getrank(int key){ spilt(root,key-1,x,y); cout<<tr[x].size+1<<endl; root=merge(x,y); }
4.getnum(根据排名找值)
这个操作就复杂一些了,我们这次使用while循环求解
我们知道如果左子树的排名+1等于rank,那么我们就找到这个值了
如果不是,如果左子树排名大于等于rank,我们就去左子树找,否则就去右子树
本题当中一定能找到答案,因为题目说明
int getnum(int rank){ int p=root; while(p){ if(tr[tr[p].l].size+1==rank) break; else if(tr[tr[p].l].size>=rank) p=tr[p].l; else{ rank-=tr[tr[p].l].size; rank-=1; p=tr[p].r; } } cout<<tr[p].key<<endl; }
5.寻找前驱(指的是比key小的最大值)
这只需要先按key-1切割,然后一直递归右节点就能找到答案
6.寻找后继
同上一直递归左节点就行
int pre(int key){ spilt(root,key-1,x,y); int p=x; while(tr[p].r){ p=tr[p].r; } cout<<tr[p].key<<endl; root=merge(x,y); } int nxt(int key){ spilt(root,key,x,y); int p=y; while(tr[p].l){ p=tr[p].l; } cout<<tr[p].key<<endl; root=merge(x,y); }
--------------------------------------结束线
完结撒花,至此,fhq-Treap解决了所有普通平衡树需要的操作,非常完美,代码简单,没有旋转,神牛果然是神牛
下面我给出洛谷p3369的AC代码,这也是一道模板题
我们学会了基础操作,但是还远远不够,继续加油,解决更难的题目!!!
#include<iostream> #include<cstdio> #include<cmath> #include<cstring> #include<algorithm> #include<map> #include<vector> using namespace std; typedef long long ll; const int N=5e5+10; const int inf=0x3f3f3f3f; int root,idx; int x,y,z; struct node{ int l,r; int key; int val; int size; }tr[N]; int get(int key){ tr[++idx].key=key; tr[idx].size=1; tr[idx].val=rand(); return idx; } void pushup(int u){ tr[u].size=tr[tr[u].l].size+tr[tr[u].r].size+1; } void spilt(int p,int key,int &x,int &y){ //按key分裂 if(!p) //分裂完毕赋空值 x=y=0; else{ if(tr[p].key<=key){ //满足左子树条件,所有值小于key x=p; spilt(tr[p].r,key,tr[p].r,y);//递归右边节点 } else{ y=p; spilt(tr[p].l,key,x,tr[p].l);//同理,递归左边 } pushup(p); //无论哪个值取到p,只需要更新p,那就更新完了节点 } } int merge(int x,int y){ if(!x||!y) return x+y;//返回不为0的值 if(tr[x].val>=tr[y].val){ //根据二叉堆的性质 tr[x].r=merge(tr[x].r,y); //x中值都比y中的小 pushup(x); return x; //父节点是x } else{ tr[y].l=merge(x,tr[y].l); pushup(y); return y; } } int insert(int key){ spilt(root,key,x,y); //按key值分离 root=merge(merge(x,get(key)),y);//合并回来更新root } void remove(int key){ spilt(root,key,x,z); spilt(x,key-1,x,y); y=merge(tr[y].l,tr[y].r); root=merge(merge(x,y),z); } int getrank(int key){ spilt(root,key-1,x,y); cout<<tr[x].size+1<<endl; root=merge(x,y); } int getnum(int rank){ int p=root; while(p){ if(tr[tr[p].l].size+1==rank) break; else if(tr[tr[p].l].size>=rank) p=tr[p].l; else{ rank-=tr[tr[p].l].size; rank-=1; p=tr[p].r; } } cout<<tr[p].key<<endl; } int pre(int key){ spilt(root,key-1,x,y); int p=x; while(tr[p].r){ p=tr[p].r; } cout<<tr[p].key<<endl; root=merge(x,y); } int nxt(int key){ spilt(root,key,x,y); int p=y; while(tr[p].l){ p=tr[p].l; } cout<<tr[p].key<<endl; root=merge(x,y); } int main(){ int t; cin>>t; while(t--){ int opt,x; cin>>opt>>x; switch(opt){ case 1: insert(x); break; case 2: remove(x); break; case 3: getrank(x); break; case 4: getnum(x); break; case 5: pre(x); break; case 6: nxt(x); break; } } }