普通平衡树
题目描述
核心思路
问题1:在根据key值来确定排名时,为什么是get_rank_by_key(root,x)-1而不是get_rank_by_key(root,x),为什么要减去1呢?
因为我们初始化构建BST空树时,构建了正无穷节点、负无穷节点。在根据key值来确定排名时,我们用到了负无穷,即把负无穷也当作了一个key值,那么它就会占用到一个排名,但实际上来说负无穷是毫无意义的,因此,减去1,就是在减去负无穷所占用的这个排名,得到真正意义是的排名。
问题2:在根据排名来确定key值时,get_key_by_rank(root,x+1)而不是get_key_by_rank(root,x),为什么x要加1呢?
因为我们初始化构建BST空树时,构建了正无穷节点、负无穷节点。在根据排名来确定key值,我们用到了负无穷,即负无穷也占用了一个排名。因此,加上1,就是考虑到负无穷用掉了一个排名,这样才能根据真正的排名来得到实际的值。
代码
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=100010,INF=0x7fffffff;
int n;
struct Node{
int l,r; //左右孩子节点在数组中的下标
int key,val;//该节点的关键码 该节点的优先级
int cnt,size;//该节点中包含多少个相同的关键码 比如1号节点中有关键码99 99 99 那么cnt=3 size记录以某个节点为根的子树中总共包含多少个节点
}tr[N];//用数组模拟链表
int root,idx;//root是根节点的下标 idx是节点的下标
//从下往上更新节点信息
void pushup(int p)
{
//tr[p].l是节点p的左子节点的下标 tr[tr[p].l].size是(以节点p的左子节点为根的子树)中节点个数的大小
//tr[p].r是节点p的右子节点的下标 tr[tr[p].r].size是(以节点p的右子节点为根的子树)中节点个数的大小
// tr[p].size是以节点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;//给这个新建的节点的关键码赋值为key
tr[idx].val=rand();//给这个新建的节点随机生成一个优先级
tr[idx].cnt=tr[idx].size=1;//因为只新建了一个节点,因此该节点只有一个相同关键码 因为只新建了一个系节点,那么以这个节点为根的子树就只有它本身,因此大小为1
return idx;//返回这个新建节点的数组下标
}
//这里用引用是因为根节点的指针会改变,因此需要引用带回它后来的地址
void zig(int &p)
{
int q=tr[p].l;//此时q是p的左孩子
tr[p].l=tr[q].r;//右旋后,原来根节点(y)它的左孩子(x)的右子树就成为了原来根节点(y)的左子树
tr[q].r=p;//右旋后,原来的根节点(y)就成为新根节点(x)的右孩子
p=q;//让p重新指向这个子树的新根节点x
//右旋后,只有p节点和tr[p].r节点的信息发生了变化,因此需要对这两个节点由下往上更新节点信息
pushup(p);//对节点p从下往上更新节点信息
pushup(tr[p].r);//对节点p的右子节点从下往上更新节点信息
}
//这里用引用是因为根节点的指针会改变,因此需要引用带回它后来的地址
void zag(int &p)
{
int q=tr[p].r;//此时q是p的右孩子
tr[p].r=tr[q].l;//左旋后,原来根节点(x)它的右孩子(y)的左子树就成为原来根节点(x)的右子树。
tr[q].l=p;//左旋后,原来的根节点(x)就成为新根节点(y)的左孩子
p=q;//让p重新指向这个子树的新根节点y
//左旋后,只有p节点和tr[p].l节点的信息发生了变化,因此需要对这两个节点由下往上更新节点信息
pushup(p);
pushup(tr[p].l);
}
//初始化构建空的BST树
void build()
{
get_node(-INF);//构建一个负无穷的节点
get_node(INF);//构建一个正无穷的节点
root=1;//根节点的下标为1号
tr[1].r=2;//根节点的右子节点的下标为2号
pushup(root);//由下往上更新节点信息
//如果右子节点的优先级大于根节点的优先级,则需要左旋
if(tr[1].val<tr[2].val)
zag(root);
}
//插入函数,最终实现在叶子节点插入关键码为key
void insert(int &p,int key)
{
//如果p就是叶子节点,那正好,直接插入这个关键码为key的叶子节点就行了
if(p==0)
p=get_node(key);
//如果想要插入的这个key值和当前p节点的关键码相等,说明p节点中又多了一个相同的关键码,cnt+1
else if(tr[p].key==key)
{
tr[p].cnt++;
pushup(p);
}
//如果想要插入的这个key值比当前p节点的关键码还小,由二叉搜索树可知,此时key值应该是在p节点的左子树
else if(tr[p].key>key)
{
insert(tr[p].l,key);//tr[p].l是p节点的左子节点的下标 递归去左子树插入这个key值
//如果key值来到左子树后,发现p节点的左子节点的优先级大于p节点的优先级,破坏了堆的性质,则需要右旋
if(tr[tr[p].l].val>tr[p].val)
zig(p);
}
//如果想要插入的这个key值比当前p节点的关键码还大,由二叉搜索树可知,此时key值应该是在p节点的右子树
else
{
insert(tr[p].r,key);//tr[p].r是p节点的右子节点的下标 递归去右子树插入这个key值
//如果key值来到右子树后,发现p节点的右子节点的优先级大于p节点的优先级,破坏了堆的性质,则需要左旋
if(tr[tr[p].r].val>tr[p].val)
zag(p);
}
pushup(p);//插入key值后,由下往上更新节点信息
}
//删除函数,实现删除关键码为key值的节点
void remove(int &p,int key)
{
//如果要删除的这个节点并不存在
if(!p)
return;
//如果检索到了该节点的关键码等于我们想要删除的key值
else if(tr[p].key==key)
{
if(tr[p].cnt>1)//该节点中有多个相同的关键码,按照题目要求,如果题目只要求删除多个相同关键码中的一个,那就删除一个即可
{
tr[p].cnt--;//题目只要求删除一个相同的关键码即可
pushup(p);
}
//如果不是叶子节点,则要通过旋转,把该节点旋转到叶子节点
else if(tr[p].l||tr[p].r)
{
//如果p节点的右子节点为空,或者p的左子节点的优先级大于p的右子节点的优先级
if(tr[p].r==0||tr[tr[p].l].val>tr[tr[p].r].val)
{
zig(p);//先右旋
//此时想要删除的这个节点p转换到了右子树,那就递归右子树,最终使得p节点到达叶子节点,再把它删除
remove(tr[p].r,key);
}
//如果p节点的左子节点为空,或者p的右子节点的优先级大于p的左子节点的优先级
else
{
zag(p);//先左旋
//此时想要删除的这个节点p转换到了左子树,那就递归左子树,最终使得p节点到达叶子节点,再把它删除
remove(tr[p].l,key);
}
}
//这里说明p已经是叶子节点了,那么直接删除它即可
else
p=0;
}
//如果待删除的key值小于当前p节点关键码,由二叉搜索树可知,此时的key值应该是在左子树,那么就递归去左子树中寻找待删除的key值
else if(tr[p].key>key)
remove(tr[p].l,key);//tr[p].l是p节点的左子节点的下标
//如果待删除的key值大于当前p节点关键码,由二叉搜索树可知,此时的key值应该是在右子树,那么就递归去右子树中寻找待删除的key值
else
remove(tr[p].r,key);//tr[p].r是p节点的右子节点的下标
pushup(p);//删除key值后,由下往上更新节点信息
}
//根据值来寻找排名
int get_rank_by_key(int p,int key)
{
//如果要找的key不存在
if(!p)
return 0;
//如果p节点的管家那么和key值相同,那么它的排名就是(以p节点的左子节点为根的子树中的节点总个数)+p节点自身的这个关键码
else if(tr[p].key==key)
return tr[tr[p].l].size+1;
//如果要找的key值小于p节点的关键码,说明key值在p节点的左子树,那么就递归左子树继续寻找
else if(tr[p].key>key)
return get_rank_by_key(tr[p].l,key);
//如果要找的key值大于p节点的关键码,说明key值在p节点的右子树,那么就递归右子树继续寻找
//在递归右子树时,我们已经知道了左子树节点总个数和p节点所含相同关键码的个数,那么要找的这个key值的最终排名就是
//左子树节点总个数+p节点所含相同关键码的个数+在右子树中的位置
else
return tr[tr[p].l].size+tr[p].cnt+get_rank_by_key(tr[p].r,key);
}
//根据排名来确定key 相当于就是在找第rank个数是啥
int get_key_by_rank(int p,int rank)
{
//如果要找的排名不存在
if(!p)
return INF;
//如果(以p节点的左子节点为根的子树中的节点总个数)大于等于我们要找的这个排名,说明rank应该是在左子树,那么我们就去递归左子树继续寻找
else if(tr[tr[p].l].size>=rank)
return get_key_by_rank(tr[p].l,rank);
//如果(以p节点的左子节点为根的子树中的节点总个数)+(p节点所含相同关键码的个数)大于等于我们要找的这个排名,说明rank应该是在p节点中
//(因为如果它在左子树的话,那么它就会递归进入上一个式子了,而不是进入这个递归式子)
else if(tr[tr[p].l].size+tr[p].cnt>=rank)
return tr[p].key;
//否则rank是在右子树,因为我们已经知道了(以p节点的左子节点为根的子树中的节点总个数)和(p节点所含相同关键码的个数),那么要找到的这个rank
//它在右子树中就是第rank-tr[tr[p].l].size-tr[p].cnt个的那个数
else
return get_key_by_rank(tr[p].r,rank-tr[tr[p].l].size-tr[p].cnt);
}
//寻找key的前驱,指的是在BST中关键码严格小于key值的前提下,关键码最大的那个节点。
int get_pre(int p,int key) //寻找严格小于key的最大数
{
//如果要检索的key并不存在,直接返回一个负无穷就好了
if(!p)
return -INF;
//如果要检索的key值小于等于当前p节点的关键码,则说明key在左子树,去左子树递归就好了
if(tr[p].key>=key)
return get_pre(tr[p].l,key);
//如果要检索的key值大于当前p节点的关键码,则说明key在右子树,去右子树递归就好了
//由二叉搜索树性质可知,对它进行中序遍历,得到的是单调递增的序列,那么左子树<根<右子树。现在我们知道想要查找的key是在右子树,那么此时有两种情况
//比key值小的关键码最大的那个节点有可能是在右子树中,也有可能是根节点p,不可能是在左子树中了,因为左子树<根,如果取左子树,那么此时它就不是比key值小的最大关键码(先取到左子树才轮得到根)
//因此,我们要对根节点p和右子树进行取max
else
return max(tr[p].key,get_pre(tr[p].r,key));
}
//寻找key的后继,指的是在BST中关键码严格大于key值的前提下,关键码最小的那个节点。
int get_next(int p,int key) //寻找严格大于key的最小数
{
//如果要检索的key并不存在,直接返回一个正无穷就好了
if(!p)
return INF;
//如果要检索的key值大于等于当前p节点的关键码,则说明key在右子树,去右子树递归就好了
if(tr[p].key<=key)
return get_next(tr[p].r,key);
//如果要检索的key值小于当前p节点的关键码,则说明key在左子树,去左子树递归就好了
//由二叉搜索树性质可知,对它进行中序遍历,得到的是单调递增的序列,那么左子树<根<右子树。现在我们知道想要查找的key是在左子树,那么此时有两种情况
//比key值大的关键码最小的那个节点有可能是在左子树中,也有可能是根节点p,不可能是在右子树中了,因为根<右子树,如果取右子树,那么此时它就不是比key值小的最大关键码(先取到根才轮得到右子树)
//因此,我们要对根节点p和左子树进行取min
else
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);
//根据key值来确定排名
else if(op==3)
printf("%d\n",get_rank_by_key(root,x)-1);
//根据排名来确定key值
else if(op==4)
printf("%d\n",get_key_by_rank(root,x+1));
//寻找x的前驱
else if(op==5)
printf("%d\n",get_pre(root,x));
//寻找x的后继
else
printf("%d\n",get_next(root,x));
}
return 0;
}