记忆中的Splay

本文重新探讨了数据结构中的Splay树,一种排序二叉树,通过Splay操作保持平均复杂度为O(logn)。Splay不仅可以避免退化,还能方便地应用于数列操作,模拟线段树的功能。文章详细介绍了Splay操作的实现,包括单旋、共线与不共线情况的处理,以及插入和删除节点的过程。此外,提出了Splay树在维护lazy标记后可支持区间操作的可能性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天借着期末复习数据结构,又重新学习了一下splay,第二遍学感觉之前白书讲的完全框死了思维,其实splay是一个很好写也很好理解的数据结构.

  • splay是一种排序二叉树,即左边节点的 val<=val<=val
  • 我们知道,一般的排序二叉树会退化,与treap给节点赋随机权值的方法不同,splay使用splay操作来进行平衡,使得摊还的平均复杂度为 O(logn) ,也就是说,每次操作完splay一下某个被操作的节点,不会使得复杂度变坏
  • splay可以很方便的用在数列之中,将数列按照位置作为权值建立splay,中序遍历这棵splay即可得到这个数列。通过这种方式,splay可以完成几乎所有的线段树操作

下面稍微记一下splay操作如何实现
splay(Nodeu) 即把u节点转到根
总共分三种情况讨论一下

  1. u的父亲是根节点。这时只要进行一次单旋即可。
  2. u的父亲与u的祖父与u共线。这时候做法是先旋转u的祖父,再旋转u的父亲
  3. u的父亲和u的祖父和u不共线。这时做法是先旋转u的父亲,再旋转u的祖父.

真正要记一下的就是共线与不共线两种情况,虽然对应的策略都是为了让二叉树显得更加平衡,但现在不能对此有直观的理解。但是代码还是很简单的。

void Splay(Node *u)
{
    while(u->p!=null)//rt->p=null
    {
        Node *p=u->p;
        int d=p->ch[1]==u;
        if(p->p==null)rotate(p,d);
        else
        {
            Node *pp=p->p;
            int d2=pp->ch[1]==p;
            if(d2==d)
            {
                rotate(pp,d2);
                rotate(p,d);
            }
            else
            {
                rotate(p,d);
                rotate(pp,d2);
            }
        }
    }
    u->push_up();
    rt=u;
}

由于u节点一直在旋转,所以中间过程可以对其不进行push_up()操作而只在最后转到根了再对其进行push_up()操作.再给出rotate和Node节点的代码.

struct Node
{
    int sz,val;
    Node *ch[2],*p;
    void push_up()
    {
        sz=ch[0]->sz+ch[1]->sz+1;
    }
    void setc(int d,Node *v)
    {
        ch[d]=v;v->p=this;
    }
};
void rotate(Node *u,int d)
{

    Node *v=u->ch[d];
    u->p->setc(u->p->ch[1]==u,v);
    u->setc(d,v->ch[d^1]);
    u->push_up();
    v->setc(d^1,u);
}

接着就是考虑插入和删除节点。
插入节点比较容易,一般就是找到权值合适的位置,然后只要修改那个节点就可以了。为了让之后操作的复杂度更平均些,新插入的节点我们一般将其旋转到根。

void insert(int x)//插入一个权值为x的节点
{
    Node*u=rt;
    int d=x>u-val;
    while(u->ch[d]!=null)
    {
        u=u->ch[d];
        d=x>u->val;
    }
    Node *t=&nd[cnt++];//nd为内存池,cnt为使用的内存
    t->init(x);//对新节点进行初始化
    u->setc(d,t);
    splay(t);
}

与treap稍微有些不同,splay的删除操作更加直观,简单。因为spaly操作并不会为复杂度带来负担,为什么不把节点放到根上去进行操作呢?删除就是先找到待删除的点,将其旋转到根,然后找到这个点的后继,再将后继旋转到根,这时候注意到待删除的点必然没有右子树,因此简单的把他的左子树设成根的左儿子就可以了。

void Delete(Node*u)
{
    splay(u);
    if(u->ch[0]==null)setrt(u->ch[1]);
    else if(u->ch[1]==null)setrt(u->ch[0]);
    else
    {
        Node *v=u->ch[1];
        while(v->ch[0]!=null)v=v->ch[0];
        splay(v);
        v->setc(0,u->ch[0]);
    }
}

至此,插入、删除就都实现了。其实仔细思考一下,假如在splay的同时维护lazy标记,splay是可以进行区间操作的。具体要注意的之后再补。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值