treap是二叉搜索树和堆的结合,所以先来说一下二叉搜索树。
二叉搜索树有如下性质:
【1】当前节点权值大于左儿子的权值
【2】当前节点权值小于右儿子的权值
【3】中序遍历得到的序列递增
通常一棵二叉搜索树没有重复的节点,如果值相同会累计在一个点上
作用:动态维护一个有序序列
常用基本功能有8个,其中插入、删除、找前驱后继、找最大值最小值都可以用set解决,还有四个功能是求某个值的排名、求排名为k的数是哪个,和lower_bound、upper_bound的实现。
另外,还通常用到pushup来更新信息,用旋转操作来维护堆的性质
旋转操作如下:

(a,b,c表示子树部分)
旋转后,仍然满足中序遍历序列递增的性质,中序遍历序列不变 ,都为 ayzxc
可以看出,旋转父节点x,可以降低父节点的层数,可方便删除,右旋使左儿子变为根,左旋使右儿子变为根
对于某些二叉搜索树,可能出现极限情况如都在一条链上,而一棵随机的二叉搜索树的期望值为logn
所以将堆的性质加入进来,通过堆来维护随机值,使二叉搜索树变得随机。
目录
找前驱后继(lower_bound,upped_bound)
在treap中每个节点维护的信息:
Node
{
int l,r;
int key;//关键字,满足二叉搜索树的性质
int val;//随机值,满足大根堆的性质
}
插入
从根节点开始找,如果节点 key 大于所要插入的 key ,递归左子树,否则递归右子树,相等则累加 cnt
void insert(int &p, int key)
{
if (!p) //空的直接创建
p = get_node(key);
else if (tr[p].key == key)
tr[p].cnt ++ ;
else if (tr[p].key > key)
{
insert(tr[p].l, key);
if (tr[tr[p].l].val > tr[p].val) //维护堆性质
zig(p);
}
else
{
insert(tr[p].r, key);
if (tr[tr[p].r].val > tr[p].val) zag(p);
}
pushup(p);//如果有需要更新的信息需要pushup
}
删除
叶子节点可以直接删除,非叶子节点可以通过旋转操作变为叶子节点后删除
void remove(int &p, int key)
{
if (!p) return;
if (tr[p].key == key)
{
if (tr[p].cnt > 1)
tr[p].cnt -- ;
else if (tr[p].l || tr[p].r)
{
//只有左儿子大于右儿子右旋上去才不会破坏堆的性质
if (!tr[p].r || tr[tr[p].l].val > tr[tr[p].r].val)
{
zig(p);
remove(tr[p].r, key);
}
else//只有左儿子小于右儿子左旋上去才不会破坏堆的性质
{
zag(p);
remove(tr[p].l, key);
}
}
else p = 0;
}
else if (tr[p].key > key)//递归左
remove(tr[p].l, key);
else //递归右
remove(tr[p].r, key);
pushup(p);
}
找前驱后继(lower_bound,upped_bound)
这里的前驱后继指的是在中序遍历中的前一个位置和后一个位置,寻找时充分利用二叉搜索树的性质
以找前驱为例:
如果当前点存在左子树,那么就走到它的左儿子,从左儿子一直向右找的最后一个值就是当前点中序遍历的前驱
如果没有左子树,那么就看它的父亲,如果存在前驱,则此点必定是父亲的右儿子,且父节点的左子树中的所有节点都小于父节点,所以父节点就是当前点的前驱
int get_prev(int p, int key) // 找到严格小于key的最大数
{
if (!p) return -INF;
if (tr[p].key >= key) return get_prev(tr[p].l, key);
return max(tr[p].key, get_prev(tr[p].r, key));
}
求某个值的排名
通过递归实现
int get_rank_by_key(int p, int key) // 通过数值找排名
{
if (!p) return 0;
if (tr[p].key == key)
return tr[tr[p].l].size + 1;//左边都是小的
if (tr[p].key > key)
return get_rank_by_key(tr[p].l, key);
return tr[tr[p].l].size + tr[p].cnt + get_rank_by_key(tr[p].r, key);
}
求排名为k的数是哪个
通过递归实现
int get_key_by_rank(int p, int rank) // 通过排名找数值
{
if (!p) return INF;
if (tr[tr[p].l].size >= rank) return get_key_by_rank(tr[p].l, rank);
//下面的意思是tr[tr[p].l].size不够,加上当前数的个数才够,那么答案是当前数
if (tr[tr[p].l].size + tr[p].cnt >= rank)
return tr[p].key;
return get_key_by_rank(tr[p].r, rank - tr[tr[p].l].size - tr[p].cnt);
}
完整代码
附上一道模板题:253. 普通平衡树 - AcWing题库
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=100010,INF=1e8;
int n;
struct node
{
int l,r;
int key,val;
int cnt,size;
}tr[N];
int root,idx;
void pushup(int p)
{
tr[p].size=tr[tr[p].l].size+tr[tr[p].r].size+tr[p].cnt;
}
int get_node(int key)
{
tr[++idx].key=key;//开一个新点
tr[idx].val=rand();
tr[idx].cnt=tr[idx].size=1;//默认是叶子节点
return idx;
}
void zig(int &p)//右旋
{
int q=tr[p].l;
tr[p].l=tr[q].r,tr[q].r=p,p=q;
pushup(tr[p].r),pushup(p);
}
void zag(int &p)
{
int q=tr[p].r;
tr[p].r=tr[q].l,tr[q].l=p,p=q;
pushup(tr[p].l),pushup(p);
}
void build()
{
//两个哨兵
get_node(-INF),get_node(INF);
root=1,tr[1].r=2;
pushup(root);
if(tr[1].val<tr[2].val)
zag(root);
}
void insert(int &p,int key)
{
if(!p)
p=get_node(key);
else if(tr[p].key==key)
tr[p].cnt++;
else if(tr[p].key>key)
{
insert(tr[p].l,key);
if(tr[tr[p].l].val>tr[p].val)
zig(p);
}
else
{
insert(tr[p].r,key);
if(tr[tr[p].r].val>tr[p].val)
zag(p);
}
pushup(p);
}
void remove(int &p,int key)
{
if(!p)
return;
if(tr[p].key==key)
{
if(tr[p].cnt>1)
tr[p].cnt--;
else if(tr[p].l||tr[p].r)
{
if(!tr[p].r||tr[tr[p].l].val>tr[tr[p].r].val)
{
zig(p);
remove(tr[p].r,key);
}
else
{
zag(p);
remove(tr[p].l,key);
}
}
else//叶子节点直接删除
p=0;
}
else if(tr[p].key>key)
remove(tr[p].l,key);
else
remove(tr[p].r,key);
pushup(p);
}
int get_rank_by_key(int p,int key)//通过数值找排名
{
if(!p)
return 0;
if(tr[p].key==key)
return tr[tr[p].l].size+1;
if(tr[p].key>key)
return get_rank_by_key(tr[p].l,key);
return tr[tr[p].l].size+tr[p].cnt+get_rank_by_key(tr[p].r,key);//最后是右子树的排名
}
int get_key_by_rank(int p,int rank)
{
if(!p)
return INF;
if(tr[tr[p].l].size>=rank)
return get_key_by_rank(tr[p].l,rank);
if(tr[tr[p].l].size+tr[p].cnt>=rank)
return tr[p].key;
return get_key_by_rank(tr[p].r,rank-tr[tr[p].l].size-tr[p].cnt);
}
int get_prev(int p,int key)
{
if(!p)
return -INF;
if(tr[p].key>=key)
return get_prev(tr[p].l,key);
return max(tr[p].key,get_prev(tr[p].r,key));
}
int get_next(int p,int key)
{
if(!p)
return INF;
if(tr[p].key<=key)
return get_next(tr[p].r,key);
return min(tr[p].key,get_next(tr[p].l,key));
}
int main()
{
build();
scanf("%d",&n);
while(n--)
{
int op,x;
scanf("%d%d",&op,&x);
if(op==1)
insert(root,x);
else if(op==2)
remove(root,x);
else if(op==3)
printf("%d\n",get_rank_by_key(root,x)-1);//哨兵
else if(op==4)
printf("%d\n",get_key_by_rank(root,x+1));//哨兵
else if(op==5)
printf("%d\n",get_prev(root,x));
else
printf("%d\n",get_next(root,x));
}
}

168

被折叠的 条评论
为什么被折叠?



