平衡树(Splay) 服务:第二弹——插入,删除,查询

本文介绍了Splay平衡树的插入、删除和查询操作。在插入时,根据权值找到合适位置并创建新节点,然后将其旋转到根节点。删除操作分为多种情况,包括调整节点的cnt值和size值。查询排名通过找到对应权值的节点并计算其左子树大小。此外,文章还讨论了如何查询第k小数、前驱和后驱的方法。最后提供了完整的代码实现。
摘要由CSDN通过智能技术生成

平衡树(Splay) 服务:第二弹——插入,删除,查询

0.前言

今天有奥赛课,所以我又回来了

今天会把普通平衡树的操作讲完

1.插入

首先,在splay中,是不会有权值(也就是平衡树排大小的关键字)重复的结点的。取而代之的,是表示这个值出现次数的附加值cnt.

插入操作可以分为几种情况:

1.平衡树中什么也没有,这个时候我们只要新建第一个结点,并把它设为根节点就可以啦。

2.平衡树中已经出现过了这个权值。我们找到这个权值所在的位置,把这个结点的size值和cnt值都加一即可

3.这个权值在平衡树中还没有出现过。我们找到这个权值所应该在的位置,并在这个位置上新建一个结点,并把size值和cnt值都初始化为1

2,3的最后,都要把找到或新建的结点旋转到根节点

如何找出要插入到权值所应该在的位置?

我们从根结点往下找,判断要插入的权值和当前结点权值的大小。如果大于当前结点,就去它的右子树,否则就去它的左子树,直到两个权值相等或当前结点不存在

建立新结点的函数代码



void nowPoint(int val, int fa, int nxt) //新建一个权值为val,是fa结点的nxt孩子的结点
{
   
    tot++;
    fa(tot) = fa, size(tot) = cnt(tot) = 1, val(tot) = val;
    son(fa, nxt) = tot;
}

插入函数的代码


void insert(int val) //插入一个值为val的数
{
   
    if (root == 0)
    {
   
        nowPoint(val, 0, 1);
        root = tot;
        return;
    }

    int now = root;//查找部分在下面查找部分有比较详细的注释来解释
    while (1)
    {
   
        size(now)++;//让所有子树包含val结点的结点size值加1
        if (val(now) == val)
        {
   
            cnt(now)++, size(now)++;
            splay(now,root);
            return;
        }
        int nxt = val(now) < val, son = son(now, nxt);
        if (!son)
        {
   
            nowPoint(val, now, nxt);
            splay(tot, root);
            return;
        }
        now = son;
    }
}


2.删除

接下来就是splay最恶心最容易出锅的地方了(至少对我来说是这样)

什么是删除?

我以前听过一句话:一个人一生会经历三次死亡。第一次是生理上的死亡,第二次是最后一个认识你的人的死亡,第三次是所有能证明你存在的事物的消失。

在删除操作上也是同理:如何证明一个点被删除了?我们只要让它不是其他任何一个点的父节点,左孩子和右孩子就可以了。

在删除之前,我们首先要实现一个函数:find()。它的功能是查找一个权值为val的结点的编号,并把它旋转到根节点。如果不存在,就返回0

插入中有类似的代码,我就不再过多解释了

查询函数的代码


int find(int val)//查找val值对应的编号并旋转到根节点
{
   
    int now = root//从根结点向下查找
    while (1)
    {
   
        if (!now)//结构体中的值默认为0.如果now值为0,说明查找到了一个不存在的结点,也就是说这个权值在以前并没有被插入过,因此我们可以直接返回0
            return 0;
        if (val(now) == val)//如果有我们查到了这个权值
        {
   
            splay(now, root);//就把这个权值对应的结点旋转到根节点
            return now;//返回这个结点对应的编号
        }
        int nxt = val(now) < val, son = son(now, nxt);//如果val(now)<val,val对应的结点一定在右子树中,而此时val<val(now)的返回值恰好为1,也正表示右孩子
        now = son;
    }
}

我们进行删除操作的第一步,就是find一下val的编号。如果需要删除一个不存在的值,那么直接退出。

在find中,我们已经把val旋转到了根节点。现在,比val小的数都在根节点的左子树,比find大的数都在根节点的右子树。

我们可以把删除操作分成以下几种情况。

1.val结点出现过多次,我们只把它的cnt值和size值-1就可以了

2.val既没有左子树,也没有右子树,此时整棵树中只有val一个数,此时我们直接把root赋为0,也就是整颗树还没有结点的初始状态

3.val有左子树,但没有右子树。我们直接把val的左结点的父亲暴力改成0结点,把val的左结点暴力改成根结点就可以了。现在,val不是根节点,无法被直接选择,也无法由其他任何一个结点所遍历到,那么它就相当于被删除了。就像这样

图中原来的1号结点实质上已经被删除了( 但是编号并没有被回收

4.val有右子树,但没有左子树,此时同2

5.val既有左子树,又有右子树

我们此时找出val的左子树中最大的一个点pos,把它移动到根节点。

那么此时pos的左子树为之前val除pos以外的左子树(pos为之前val左子树中的最大值,因此在旋转的过程中它不会有左孩子,只有在倒数第二部它旋转到了val的左孩子,再次旋转时val就会变成pos的右孩子),右子树为val和它以前的右子树(pos是小于val的最大的结点,显然没有一个结点的权值在大于pos而小于val,因此val不可能有左孩子)。

我们在暴力把val的右孩子和pos连在一起,val就相当于被孤立了。我们放图来演示一下这个过程:

开始时

pos旋转到了val的左孩子

pos旋转到了根节点

val被删除了

删除之后,记得更新pos结点的size值

删除函数的代码


void delet(int val)//删除权值为val的结点
{
   
    int now = find(val);
    if (!now) return;
    if (cnt(now) > 1)//第一种
    {
   
        cnt(now)--, size(now)--;
        return;
    }
    if (!ls(now) && !rs(now))//第二种
    {
   
        root = 0;
    }
    else if (!ls(now))//第三种
    {
   
        root = rs(root);
        fa(root) = 0;
    }
    else if (!rs(now))//第四种
    {
   
        root 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值