目录
- 写在前面
- 优点
- 缺点
- 操作
- 节点信息
- 分裂(spilt)
- 合并(merge)
- 插入
- 删除
- 查询值的排名
- 查询排名的值
- 前驱/后继
写在前面
为什么要先写\(fhq\ treap\)呢?
因为它好理解,而且好写啊(破音)!
一种依靠分裂(\(spilt\))和合并(\(merge\))操作实现的平衡树,由大神范浩强发明,所以叫\(fhq\ treap\)
优点
码量小而且核心操作是复读机(就是直接复制改一点东西然后就行)
易于理解
缺点
常数略大
操作
节点信息
左右子树编号 ,值,索引(就是\(rand()\)的值),子树大小(找排名,\(pre\)和\(next\)时用)
struct node
{
int l,r;
int val,key;
int size;
}fhq[N];
int cnt,root;
int nownode(int val)
{
fhq[++cnt].val=val;
fhq[cnt].size=1;
fhq[cnt].key=rand();
return cnt;
}
分裂(spilt)
分裂由两种:按值分裂和按大小分裂
这里仅介绍按值分裂,按大小分裂(可以维护区间分裂)在我做完P3391 【模板】文艺平衡树的时候会补上
按值分裂:把树拆成两棵树,拆出来的一棵树的值全部小于等于给定的值,另外一部分的值全部大于给定的值
int spilt(int now,int val,int &x,int &y) {//x和y是拆分后两树的根节点
if(!now) x=y=0;
else {
if(fhq[now].val<=val) {
x=now;
spilt(fhq[now].r,val,fhq[now].r,y);//拆分右子树,因为右子树中可能还存在比val小的节点
} else {
y=now;
spilt(fhq[now].l,val,x,fhq[now].l);//复读机
}
update(now);
}
}
合并(merge)
合并就是把两棵树\(x,y\)合并成一棵树,其中x树上的所有值都小于\(y\)树上的所有值。而且新合并出来的树依旧满足\(Treap\)的性质
int merge(int x,int y) {
if(!x||!y) return x+y;
if(fhq[x].key>fhq[y].key) { //维护根的性质
//根据堆的性质得到的新树x在y上面,而且根据二叉搜索树的性质y一定在x下面,所有y在x右下
fhq[x].r=merge(fhq[x].r,y);
update(x);
return x;
} else { //复读机
fhq[y].l=merge(x,fhq[y].l);
update(y);
return y;
}
}
插入
插入权值val
按值\(val\)把树分裂成\(x\)和\(y\)
合并\(x\),新节点,\(y\)
只需要两行代码
解释一下为什么可以这样
按值权值\(val\)分裂得到了两棵树\(x\),\(y\)
那么x树上的所有值一定小于等于\(val\),y树上所有值一定大于\(val\),所以就可以直接合并\(x\),新节点,\(y\)
void ins(int val) {
spilt(root,val,x,y);
root=merge(merge(x,nownode(val)),y);//这里忘给root负值了
}
删除
只需要四行代码
先按照\(val\)把树分裂成\(x\)和\(z\)
然后按照\(val-1\)把树\(x\)分裂成\(x\)和\(y\)
那么此时\(y\)树上所以值都是等于\(val\)的,我们去掉它的根节点(让\(y\)等于合并\(y\)的左子树和右子树)
void del(int val) {
spilt(root,val,x,z);
spilt(x,val-1,x,y);
y=merge(fhq[y].l,fhq[y].r);
root=merge(merge(x,y),z);
}
查询值的排名
按照\(val-1\)分裂成\(x\)和\(y\)
\(x\)的大小+1就是\(val\)的排名
最后把x和y合并
int getrank(int val) {
spilt(root,val-1,x,y);
root=merge(x,y);
return fhq[x].size+1;
}
其实上面是错的
int getrank(int val) {
spilt(root,val-1,x,y);
int ret=fhq[x].size+1;
root=merge(x,y);//先return再合并
return ret;
}
查询排名的值
不写了,看代码也很好理解
int getnum(int pm) {
int now=root;
while(now) {
if(fhq[fhq[now].l].size+1==pm) break;
else if(fhq[fhq[now].l].size>=pm) now=fhq[now].l;
else {
pm-=fhq[fhq[now].l].size+1;
now=fhq[now].r;
}
}
return fhq[now].val;
}
前驱/后继
不知道是什么的看我上一篇博客
前驱:按照\(val-1\)分裂成\(x\)和\(y\),则\(x\)里面最右的数就是\(val\)的前驱
前驱:按照\(val\)分裂成\(x\)和\(y\),则\(y\)里面最左的数就是\(val\)的前驱
int pre(int val) {
spilt(root,val-1,x,y);
int now=x;
while(fhq[now].r) now=fhq[now].r;
root=merge(x,y);
return fhq[now].val;
}
int Next(int val) {
spilt(root,val,x,y);
int now=y;
while(fhq[now].l) now=fhq[now].l;
root=merge(x,y);
return fhq[now].val;
}