普通平衡树

普通平衡树

题目描述

在这里插入图片描述


核心思路

问题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;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卷心菜不卷Iris

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值