尽量去理解一下。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
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;
}
//右旋和左旋的目的是:交换子结点和父节点之间的位置,并不改变其中序的顺序。
//使用的条件一般在插入一个数或者删除一个数。因为在进行操作时,子结点和父节点
//之间的val会发生改变,但为了形成平衡树,需要进行左旋和右旋
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;//根节点1号点,1号点的右儿子2号点
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);//如果左子树val大于root.val 就换上来
}
else //否则insert右子树
{
insert(tr[p].r,key);
if(tr[tr[p].r].val>tr[p].val) zag(p);//如果右子树val大于root.val 就换上来
}
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; //不存在 直接返回0。
if(tr[p].key==key) return tr[tr[p].l].size+1;
//如果key和当前节点p的key值相等, 排名=左子树数的多少(数量)+1;(因为它满足两个条件)
if(tr[p].key>key) return get_rank_by_key(tr[p].l,key);
//如果当前节点p的key值大于key值,那么递归到当前节点的左子树中进行搜索
return tr[tr[p].l].size+tr[p].cnt+get_rank_by_key(tr[p].r,key);
//否则的话,就是递归到当前节点的右子树中进行。
//排名=当前节点的左子树中数的count + 根节点root的个数 + 它在右子树中的排名
}
int get_key_by_rank(int p,int rank)//通过排名找数值
{
if(!p) return INF; //不存在p,返回不可能存在的排名
if(tr[tr[p].l].size>=rank) return get_key_by_rank(tr[p].l,rank);
//如果rank小于等于当前左子树中数的个数,则递归搜索当前节点的左子树
if(tr[tr[p].l].size+tr[p].cnt>=rank) return tr[p].key;
//如果rank小于等于(当前节点的左子树中数的count+当前节点数的个数count),则就是当前节点。
return get_key_by_rank(tr[p].r,rank-tr[tr[p].l].size-tr[p].cnt);
//左子树+当前节点的个数<rank,ze递归右子树,所以现在在右子树的排名为rank-左子树-当前节点的个数
}
//前驱和后继:分别是相对于中序队列而言的,中序队列可以想象为在竖直方向上的映射
int get_prev(int p,int key)//找到严格小于key的shu中的最大数,
{
if(!p) return -INF;
if(tr[p].key>=key) return get_prev(tr[p].l,key);
//如果key小于当前节点的key值,则递归当前节点的左子树去寻找
return max(tr[p].key,get_prev(tr[p].r,key));
//否则的话,去遍历当前节点的右子树去寻找
}
int get_next(int p,int key)//找到严格大于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 opt,x;
scanf("%d%d",&opt,&x);
if(opt==1) insert(root,x);
else if(opt==2) remove(root,x);
else if(opt==3) printf("%d\n",get_rank_by_key(root,x)-1);
else if(opt==4) printf("%d\n",get_key_by_rank(root,x+1));
else if(opt==5) printf("%d\n",get_prev(root,x));
else printf("%d\n",get_next(root,x));
}
return 0;
}