【Splay】学习笔记

版权声明:2018/4/10重启blog;转载请注明出处 https://blog.csdn.net/zhaiqiming2010/article/details/82192653

Splay伸展树是一种特殊的BST,Splay能在Log(n)的均摊时间复杂度内完成插入、查找,删除等基本操作。

基本结构

struct SplayTree
{
    int val,sz,cnt;//val为值 sz为子树大小,cnt此位置的重叠节点个数
    int s[2],f;//s[0]为左儿子 s[1]为右儿子 f为父亲
};

旋转

1.左右儿子判断:如果 当前节点 为 当前节点的父节点  的左儿子,返回0,若右儿子则返回1

bool son(int x) 
{
    return a[a[x].f].s[1] == x;
}

2.将y节点插入到x节点的下面:z为0插入左儿子的位置,z为1插入右儿子的位置

void insert(int x , int y , int z)
{
   a[x].s[z] = y;
   a[y].f = x;
}

3.旋转(rotate)

左旋(当前节点为父节点的左孩子)

①将祖父节点与自身链接

②用自身的右孩子代替的自身父亲的左节点

③用自身的父节点代替自身的右孩子

右旋(当前节点为父节点的右孩子)

①将祖父节点与自身链接

②用自身的左孩子代替的自身父亲的右节点

③用自身的父节点代替自身的左孩子

左旋(图片来自oi.men.ci):

splay

旋转操作可以通过 异或运算 轻松实现

void rot(int x)
{
    int p = a[x].f;
    bool d = son(x);
    insert(a[p].f,x,son(p));//第一步
    insert(p,a[x].s[d^1],d);//第二步
    insert(x,p,d^1);//第三步
    a[p].sz = a[a[p].s[0]].sz + a[a[p].s[1]].sz + a[p].cnt;//重新计算子树大小
    a[x].sz = a[a[x].s[0]].sz + a[a[x].s[1]].sz + a[x].cnt;//重新计算子树大小
    if(a[x].f == 0)
    root = x;//root是根节点的编号
}

Splay如何实现平衡

Spaly每插入一个新节点,就将新插入的节点旋转到根节点

1.如果当前点是根节点,则直接结束。

2.如果当前点的父亲是根节点,直接将节点旋转到根节点

3.如果上述两条都不满足,设x为当前节点

①利用son()函数,得出son(x)和son(x.f)

②若son(x) == son(x.f) 那么先rotate(x.f) 然后 rotate(x)

③若son(x) != son(x.f) 那么先rotate(x) 然后重复执行一次 rotate(x)

void splay(int x)
{
    while(a[x].f != 0)
    {
        if(a[a[x].f].f == 0)
            rot(x);
        else
        {
            if(son(x) == son(a[x].f))
            {
                rot(a[x].f);
                rot(x);
            }
            else
            {
                rot(x);
                rot(x);
            }
        }
    }
}

插入

Spaly是平衡的BST,所以插入操作和普通BST相同。

每次插入完成,将新插入的节点Rotate到根节点

void ins(int x)
{
    int w = root,f = 0;
    int p = findn(x);
    if(p)//如果这个值已经存在,那么就直接给这个节点+1吧
    {
        a[p].cnt ++;
        while(p != root)
        {
            a[p].sz = a[a[p].s[0]].sz + a[a[p].s[1]].sz + a[p].cnt;//重新计算子树大小
            p = a[p].f;
        }
        a[root].sz = a[a[root].s[0]].sz + a[a[root].s[1]].sz + a[root].cnt;//重新计算子树大小
        return ;
    }
    while(w)
    {
        f = w;
        if(x < a[w].val)
            w = a[w].s[0];
        else
            w = a[w].s[1];
    }

    a[++tot].val = x;
    a[tot].cnt = 1;//新建节点
    if(f == 0)//如果这个点是根节点的话,直接插入
    {
        root = tot;
        rejs(root);
        return ;
    }
    ////////////   ↑ ↑
    //普通BST的插入 ↑ ↑
    ////////////   ↑ ↑
   
    ///////////////↓ ↓
    //实现平衡的操作↓ ↓
    ///////////////↓ ↓
    if(x < a[f].val)
        point(f,tot,0);
    else
        point(f,tot,1);
    splay(tot);
}

查找

查找操作与普通BST相同


bool Find(int pos , int x)//查找的大概逻辑
{
    if(a[pos].size == 0) return false;
    if(a[pos].val == x) return true;
    if(x > a[pos].val) return Find(a[pos].s[1] , x);
    else return Find(a[pos].s[0] , x);
}

排名

一个值在BST中的排名就是二叉树中序遍历的时候,该值第一次出现的位置。我们可以一次中序遍历求出所有值得排名。

在Splay中,一个值的排名,就是将该值所在的节点Rotate()到根节点,此时其左子树的size+1就是排名

查询排名

与普通BST类似

节点的前驱和后继

前驱:中序遍历中第一个小于该节点的值的节点

后继:中序遍历中第一个大于该节点的值得节点
 

求节点的前驱:

①找左子树中最大的,若左子树为空,则执行②

②向上查找,若当前节点是上一个节点的右子树,则返回当前节点的父亲

③若查找到根节点,则代表没有前驱

求节点的后继

①找右子树中最小的,若右子树为空,则执行②

②向上查找,若当前节点是上一个节点的左子树,则返回当前节点的父亲

③若查找到根节点,则代表没有后继

int near(int x,bool d)//0代表求前驱;1代表后继
{
    if(a[x].s[d] != 0){
        int p = a[x].s[d];
        while(a[p].s[d^1])
            p = a[p].s[d^1];
        return p;
    }
    else if(son(x) == d^1)
        return a[x].f;
    else{
        int p = a[x].f;
        while(son(p) == d){
            if(p == root)
                return 0;
            p = a[p].f;
        }
        return a[p].f;
    }
}

值的前驱和后继

若欲求前驱和后继的值在BST中,直求节点的前驱和后继。

若不在,首先插入一个值与欲求前驱后继的值相等的节点,然后求节点的前驱和后继,求完之后将插入的节点删除。

删除

首先Splay(x的前驱) , 然后Splay(x的后继) , 完成这两个操作后x的后继会变成根节点,x的前驱会变成根节点的左儿子,x变成了根节点的右子树。删除右子树,就删除了x节点。

删除区间的时候也可以这样:首先Splay(区间左边界的前驱) , 然后Splay(区间右边界的后继),操作完成后整个区间就变成了 区间左边界前驱的右子树。

void cle(int x)//cle操作是清除一个点
{
    a[a[x].f].s[son(x)] = 0;
    a[x].val = 0;
    a[x].sz = 0;
    rejs(a[x].f);
    a[x].f = 0;
}

void del(int x)
{
    int p = near(x,0);
    int q = near(x,1);
    if(!p || !q)
    {
        splay(x);
        if(p == 0)
        {
            root = a[x].s[1];
            a[a[x].s[0]].f = 0;
        }
        else
        {
            root = a[x].s[0];
            a[a[x].s[0]].f = 0;
        }
        return ;
    }
    splay(p);
    splay(q);
    rot(p);
    cle(x);
}

参考资料:《Splay学习笔记》 《Splay 学习笔记(一)》 《伸展树的基本操作与应用》

猜你喜欢

转载自blog.csdn.net/zhaiqiming2010/article/details/82192653