Splay

学习自该博客

(dalao讲得真好,我只为了记忆写重点)

Splay是一种数据结构,用于在线调整二叉排序树的操作,用于让树平衡(空树或任意节点的左右两个子树的深度差的绝对值不超过1)转换根节点或者其他操作。

二叉排序树,左子树任意一个点的值小于根节点,右子树任意一个点大于根节点的树。很显然,如果你想把一群点构造成这样一个树,你可以构造出许多不同的、满足关系的树。

(以下所有图片来自网上)

比如下面两个树就是等价的:

不平衡                       平衡

那么Splay要怎么旋转呢?大概是把要旋转的点更改到它的父亲节点的位置。我们发现,要满足关系,如果原来的点在父节点的右子树,旋转后父节点就连接在该节点的左子树上。反之亦然。大概是这样的:

差不多就是这样。

可以用结构体或者数组记录节点信息。我就写个结构体吧因为dalao的是用数组的。

struct tree{
        int val,fa,son[2],cnt,siz;
        //val权 fa父节点 son[0]左节点 son[1]右节点 cnt出现次数 siz树大小(包括子树siz和自身cnt)
}
int siz,root;//siz时间戳,root根节点(因为splay会更改根节点)

插入时用三种情况:空树,直接插入;查询到权相同的点,cnt++;没有查询到点,向下插入新点。

    inline void insert(int x)
    {
        if(siz==0){
            siz++;
            e[siz].son[1]=e[siz].son[0]=e[siz].fa=0;
            e[siz].siz=e[siz].cnt=1;
            root = 1;
            e[siz].val=x;
            return;
        }
        int now=root,f=0;
        while(1){
            if(e[now].val==x){
                e[now].cnt++;
                update(now);
                update(f);
                splay(now,0);
                break;
            }
            f=now;
            now=son[now][x>e[now].val];
            if(!now){
                siz++;
                e[siz].son[1]=e[siz].son[0]=0;
                e[siz].fa=f;
                e[siz].siz=e[siz].cnt=1;
                e[f].son[x>e[now].val]=siz;
                e[siz].val=x;
                update(f);
                splay(siz,0);
                break;
            }
        }
    }

如果没有重复的值,你也可以直接dfs建树。

int build_tree(int l,int r,int f){
    if(l > r)return 0;
    int mid=l+r>>1;
    int now=++siz;
    e[now].fa=f;
    e[now].son[0]=e[now].son[1]=0;
    e[now].cnt++;
    e[now].val=a[mid];
    e[now].siz++;
    e[now].son[0]=build_tree(l,mid-1,now);
    e[now].son[1]=build_tree(mid+1,r,now);
    update(now);
    return now;
}

那么我们刚才说了那么久的旋转代码如下:

inline void rotate(int x)
    {
        int f=e[x].fa,ff=e[f].fa;//记录父节点和祖父节点 
        bool w=which(x);//splay[自动]旋转的奥秘,得到x的位置
        //以下旋转共6步,就是分别记录三个点间的关系,便于记忆 
        e[f].son[w]=e[x].son[w^1];
        e[e[f].son[w]].fa=f;
        e[f].fa=x;
        e[x].son[w^1]=f;
        e[x].fa=ff;
        if(ff)
            e[ff].son[e[ff].son[1]==f]=x;
        
        update(f);
        update(x);
    }

这里有个问题:如果连续的两条边方向相同,若只是将底部的点向上旋转会单旋使树失衡。那么我们只要在旋转之前把父节点旋转一下就好了。具体的图请看dalao博客(不想复制以防侵权)

goal是旋转的目标点,旋转后x节点会在goal的子节点。

inline void splay(int x,int goal){
    for(int i;(i=e[x].fa)!=goal;rotate(x))
    {
        if(e[i].fa!=goal){
            rotate(which(x)==which(i)?i:x);
        }
    }
    if(goal==0) root=x;
}

查询:查询点的位置,我们按从小到大查询。发现因为子树的siz是全部小于节点的,所以如果x(要查询的数)<=siz[son[0]],我们往左查询;否则x-=siz[son[0]],跳到右子树重复查询,直到x==0,返回查到的点就ok了。

inline int find(int x)
{
    int now=root;
    while(1)
    {
        pushdown(now);
        if(x<=e[e[now].son[0]].siz)    now=e[now].son[0];
        else{
            x-=e[e[now].son[0]].siz+1;
            if(!x)return now;
            now=e[now].son[1];
        }
    }
}

查询前驱和后继,就是查询和这个点左右最近的两个点,先把它变为根节点,再查询左子树的最右节点和右子树最左节点:

int findpre(int x)//注意x是编号而不是值,这个查前驱
{
  splay(x,0); //注意变成根节点。如果不是那么下面都是假的
int now=son[x][0]; while(son[now][1])now=son[now][1]; return now; }

后缀一样(当然是相反)。

删除有点麻烦留坑待填.

大概的思路:

  1. if节点有多个数,即cnt>1,直接减去就好了。
  2. else if节点没有子树,直接删掉就好了。
  3. else if只有左子树,用左儿子替代之。
  4. else if只有右子树,用右儿子替代之。
  5. else if左右子树都有,我们把它的前驱作为新根,把它splay一下,然后把原来点的右儿子连成它的右儿子就OK了。

写个例题吧:

P3391文艺平衡树

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<ctype.h>
#define INF 0x3f3f3f3f
using namespace std;
inline int read()
{
    int w=0,x=0;char c=getchar();
    while(!isdigit(c))w|=c=='-',c=getchar();
    while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return w?-x:x;
}
namespace star
{
    const int maxn=1e5+10;
    int n,m,root,siz,a[maxn];
    struct node{
        int son[2],siz,fa,tag,cnt,val;
    }e[maxn];
    
    inline bool which(int x){return e[e[x].fa].son[1]==x;}
    
    inline void update(int x){
        if(!x)return;
        e[x].siz=e[x].cnt;
        if(e[x].son[0])e[x].siz+=e[e[x].son[0]].siz;
        if(e[x].son[1])e[x].siz+=e[e[x].son[1]].siz;
    }
    
    inline void pushdown(int x){
        if(x and e[x].tag){
            e[e[x].son[0]].tag^=1;
            e[e[x].son[1]].tag^=1;
            e[x].tag=0;
            swap(e[x].son[0],e[x].son[1]);
        }
    }
    
    inline void rotate(int x)
    {
        int f=e[x].fa,ff=e[f].fa;//记录父节点和祖父节点 
        pushdown(x),pushdown(f);
        bool w=which(x);//splay[自动]旋转的奥秘,得到x的位置
        //以下旋转共6步,就是分别记录三个点间的关系,便于记忆 
        e[f].son[w]=e[x].son[w^1];
        e[e[f].son[w]].fa=f;
        e[f].fa=x;
        e[x].son[w^1]=f;
        e[x].fa=ff;
        if(ff)
            e[ff].son[e[ff].son[1]==f]=x;
        
        update(f);
        update(x);
    }
    
    inline void splay(int x,int goal){
        for(int i;(i=e[x].fa)!=goal;rotate(x))
        {
            if(e[i].fa!=goal){
                rotate(which(x)==which(i)?i:x);
            }
        }
        if(goal==0) root=x;
    }
    
    int build_tree(int l,int r,int f){
        if(l > r)return 0;
        int mid=l+r>>1;
        int now=++siz;
        e[now].fa=f;
        e[now].son[0]=e[now].son[1]=0;
        e[now].cnt++;
        e[now].val=a[mid];
        e[now].siz++;
        e[now].son[0]=build_tree(l,mid-1,now);
        e[now].son[1]=build_tree(mid+1,r,now);
        update(now);
        return now;
    }
    
    inline int find(int x)
    {
        int now=root;
        while(1)
        {
            pushdown(now);
            if(x<=e[e[now].son[0]].siz)    now=e[now].son[0];
            else{
                x-=e[e[now].son[0]].siz+1;
                if(!x)return now;
                now=e[now].son[1];
            }
        }
    }
    
    inline void reverse(int x,int y)
    {
        int l=find(x-1),r=find(y+1);
        splay(l,0);
        splay(r,l);
        int pos=e[root].son[1];
        pos=e[pos].son[0];
        e[pos].tag^=1;
    }
    
    inline void dfs(int now){
        pushdown(now);
        if(e[now].son[0])dfs(e[now].son[0]);
        if(e[now].val!=INF and e[now].val!=-INF)printf("%d ",e[now].val);
        if(e[now].son[1])dfs(e[now].son[1]);
    }
/*
    inline void insert(int x)
    {
        if(siz==0){
            siz++;
            e[siz].son[1]=e[siz].son[0]=e[siz].fa=0;
            e[siz].siz=e[siz].cnt=1;
            root = 1;
            e[siz].val=x;
            return;
        }
        int now=root,f=0;
        while(1){
            if(e[now].val==x){
                e[now].cnt++;
                update(now);
                update(f);
                splay(now,0);
                break;
            }
            f=now;
            now=son[now][x>e[now].val];
            if(!now){
                siz++;
                e[siz].son[1]=e[siz].son[0]=0;
                e[siz].fa=f;
                e[siz].siz=e[siz].cnt=1;
                e[f].son[x>e[now].val]=siz;
                e[siz].val=x;
                update(f);
                splay(siz,0);
                break;
            }
        }
    }
*/
    inline void work()
    {
        n=read(),m=read();
        a[1]=-INF,a[n+2]=INF;
        for(int i=1;i<=n;i++)a[i+1]=i;
        root=build_tree(1,n+2,0);
        for(int i=1;i<=m;i++){
            int x=read(),y=read();
            reverse(x+1,y+1);
        }
        dfs(root);
    }
}
int main()
{
    star::work();
    return 0;
}

好长丫~~~

 注意,每次更新完都splay是必要的,这会更新所有节点的siz值,否则是假的。

原文地址:https://www.cnblogs.com/BrotherHood/p/13328725.html