splay - tree 伸展树

Splay是一种自平衡二叉树,它不需要刻意的去调整,要求也没有AVL树那么苛刻,代码量相对AVL树也短了不少,最重要的是它的操作非常灵活,可是基本实现线段树的功能.
在实际的应用中刚刚被访问过的数据极有可能会在很短的时间内再次被访问,所以我们只需要把刚刚查询过的节点旋转到树的顶端就行了,刚刚也提到了Splay是一种自平衡的二叉树,所谓的自平衡就是我们在旋转节点的时候把树的深度减少.要想达到减小深度的效果我们就需要一点小技巧了,我们不能一层一层的往上旋转,而是需要判断上面两层的状态,然后决定旋转方案.
我们先来看看Splay的两种旋转方式.
这里写图片描述 这里写图片描述
再看一张图,看看单旋转和双旋转的区别.
这里写图片描述
很容易看出 单旋转的情况下可能会被某些恶意的数据卡死.而双旋转的情况下经过把节点调整到根的过程中使得书的深度减少为原来的一半.
当然不是所有的情况都需要双旋转,当旋转方案是zig-zag或者zag-zig的时候如果我们继续用双旋转的方式先旋转祖父节点的话,那么旋转就会发生错误,因为通过第一次旋转之后如果目标节点是左孩子那么旋转后就变成了右孩子,如果目标节点是右孩子那么旋转之后就会变成左孩子.从而无法进行下一步的旋转.
先上结构体的定义

struct node
{
    int pre,sum,chd[2];         //父节点,以当前节点为根的结点数,左右孩子
};
    辅助部分代码
inline int F(int rt){       //判断是那个孩子
    return tr[tr[rt].pre].chd[1] == rt;
}


inline int push_up(int rt) //往上传递更新
{
    if(0 == rt)
        return 0;
    tr[tr[rt].chd[0]].pre = rt;
    tr[tr[rt].chd[1]].pre = rt;
    tr[rt].sum = tr[tr[rt].chd[0]].sum+tr[tr[rt].chd[1]].sum+1;
    return 0;
}
    下面是判断旋转方式的代码

int splay(int rt,int rw)
{
    int pre;
    while(tr[rw].pre != rt)
    {
        pre=tr[rw].pre;
        if(F(rw) != F(pre) || tr[pre].pre == rt)   //单旋转
            Rotate(rw);
        else{                                      //双旋转
            Rotate(pre);
            Rotate(rw);
        }
    }
    if(rt)                                        //向上传递更新
        push_up(rt);
    return rw;
}

旋转部分的代码


inline int Rotate(int rt)
{
    int chd = F(rt);
    int pre = tr[rt].pre;
    tr[tr[rt].chd[!chd]].pre = pre;
    tr[pre].chd[chd] = tr[rt].chd[!chd];
    tr[rt].pre = tr[pre].pre;
    tr[tr[pre].pre].chd[F(pre)] = rt;
    tr[pre].pre= rt;
    tr[rt].chd[!chd] = pre;
    push_up(pre);     //更新
    push_up(rt);       //更新
    return 0;
}

查询第x大的节点

int Find(int rt,int x)
{
    if(tr[tr[rt].chd[0]].sum >= x)
        return Find(tr[rt].chd[0],x);
    else if(tr[tr[rt].chd[0]].sum+1 == x)
        return rt;
    else
        return Find(tr[rt].chd[1],x-tr[tr[rt].chd[0]].sum-1);
}

删除特定的节点只需要将节点旋转到叶节点就行,而删除一个区间[a,b]只需要插入a-1将a-1移动到根节点,插入b+1然后将b+1移动至a-1的下面,然后删除b+1的左子树就行了.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

achonor

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

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

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

打赏作者

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

抵扣说明:

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

余额充值