Treap解释:
Treap可以理解为Tree + heap(树加堆),这里的树是二叉搜索树(BST)堆是大根堆或者小根堆都可以,这个模板是大根堆
这个数据结构最核心的理解就是一颗二叉搜索树根据大根堆的限制排成一颗深度大约为 log(n) 近似平衡的BST,也就是通过大根堆的限制可以很好地降低这颗树的层数,进而达到降低复杂度的效果
这个模板算是维护了数节点中的cnt(当前这个key值得个数),size但实质是都是为了维护size(也就是这节点和他的儿子总共的个数)
旋转
- 既然是可以近似平衡,当然是根据左旋右旋来让这颗树保持平衡,先来看它的旋转操作:
这里的旋转可以理解为交换父子节点的相对位置(儿子变为父亲的父亲,父亲变为儿子的儿子),但不能影响二叉搜索树的性质(中序遍历有序),也就是在不改变这棵树中序遍历的基础上改变儿子节点和父亲节点的相对位置,也可以理解为不改变他们的投影(也就是不改变全部节点之间的相对位置)比如改变前D在E左边,改变后也满足这个相对位置。
//这里传进来的p是当前分支的父亲节点,因为要改变当前分支的父亲节点,所以传的它的引用
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);//tr[1].key=-inf
get_node(INF);//tr[2].key=inf
root = 1;//让key值为-inf的节点当作父节点
tr[1].r = 2;//两个哨兵,让所有的节点都在他俩之间
pushup(root);//维护size
if(tr[1].val < tr[2].val)//如果他俩不满足大根堆性质就旋转
zag(root);
}
int get_node(int key)
{
tr[++idx].key = key;
tr[idx].val = rand();//让val(维护大根堆的依据)等于随机数,来降低复杂度
tr[idx].size = tr[idx].cnt=1;
return idx;
}
Pushup
- 维护size
void pushup(int p)
{
tr[p].size = tr[tr[p].l].size + tr[tr[p].r].size + tr[p].cnt;
return ;
}
插入
- 递归实现
- 合理利用引用,直接给tr[i]的左儿子或者右儿子赋值,不再有tr[i].l=*的赋值形式
void insert(int &p, int key)//都是从根节点开始向下查找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[p].val < tr[tr[p].l].val)//回溯时调整树形,让其满足大根堆
zig(p);
}
else
{
insert(tr[p].r, key);
if(tr[p].val < tr[tr[p].r].val)
zag(p);
}
pushup(p);//每回溯一次就更新一次变动节点p的size
return ;
}
删除
- 递归实现
- 也是通过引用直接修改节点的数值
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].l || tr[tr[p].l].val < tr[tr[p].r].val)
//当这个要被删除的节点没有左儿子,也就是只能通过左旋来使他到左儿子位置上
//当这个节点两个儿子都存在时,当右儿子的val大于左儿子val(满足大根堆性质),这样就可以左旋让右儿子当父亲,父亲跑到左儿子节点上,这样一步步跑到叶子节点被干掉
{
zag(p);
remove(tr[p].l, key);
}
else
{
zig(p);
remove(tr[p].r, key);
}
}
else p = 0;
}
else if(tr[p].key > key)
{
remove(tr[p].l, key);
}
else remove(tr[p].r, key);
pushup(p);//这个也是回溯时更新改变节点维护的size
}
通过rank找Key值,通过Key值找rank(这里的排名是第几小,和往常的不太一样)
- 递归实现
- 既然是找,就不需要改变什么了,所以不传引用了
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;//既然是第二个if肯定不满足第一个,那就是左儿子的size<rank,然而加上父亲的cnt就>=rank,那么rank排名肯定是父亲的key值
return get_key_by_rank(tr[p].r, rank - tr[tr[p].l].size - tr[p].cnt);
//既然是第三个if那它必然不满足前两个,也就是左儿子加上父亲都小于rank那么只能向右儿子找了,但在右儿子中要找的rank就要剪掉他俩的个数了,因为右儿子的值肯定大于他俩,就相当于在大于左儿子和父亲的基础上找大rank-左儿子个数-父亲个数 的节点
}
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);
//前面的if都不满足的话,也就是p.key<key 这时候需要往右儿子那找,既然是找有多少比他小的数,那左儿子和父亲的key都满足,只需要加上他们个数,然后再在右儿子里找。
}
找前驱和后继
- 前驱就是严格小于他的最大的数
- 后继就是严格大于他的最小的数
- 递归实现
//严格大于就是大于等于不算大于
int get_pre(int p, int key) //严格小于它的最大值
{
if(!p) return -INF;//不存在就返回负无穷
if(tr[p].key >= key)
{
return get_pre(tr[p].l, key);//二分
}
return max(tr[p].key, get_pre(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));//找满足条件的最小值
}
完整代码
- 例题:AcWing_253
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=100010, INF=1e8;
struct node{
int l, r;
int val, key;
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;
return ;
}
int get_node(int key)
{
tr[++idx].key = key;
tr[idx].val = rand();
tr[idx].size = tr[idx].cnt=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[p].val < tr[tr[p].l].val)
zig(p);
}
else
{
insert(tr[p].r, key);
if(tr[p].val < tr[tr[p].r].val)
zag(p);
}
pushup(p);
return ;
}
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].l || tr[tr[p].l].val < tr[tr[p].r].val)
{
zag(p);
remove(tr[p].l, key);
}
else
{
zig(p);
remove(tr[p].r, 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_pre(int p, int key) //严格小于它的最大值
{
if(!p) return -INF;
if(tr[p].key >= key)
{
return get_pre(tr[p].l, key);
}
return max(tr[p].key, get_pre(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()
{
int n;
cin >> n;
build();
while(n --)
{
int opt, x;
cin >> opt >> x;
switch (opt){
case 1:{
insert(root, x);
break;
}
case 2:{
remove(root, x);
break;
}
case 3:{
printf("%d\n",get_rank_by_key(root, x)-1);
break;
}
case 4:{
printf("%d\n",get_key_by_rank(root, x+1));
break;
}
case 5:{
printf("%d\n",get_pre(root, x));
break;
}
case 6:{
printf("%d\n",get_next(root, x));
break;
}
}
}
return 0;
}