Splay

Splay(伸展树)是一种维护二叉搜索树的数据结构,可以用它干一些很神奇的东西,这篇文章先来介绍它的基本操作。

首先定义几个变量:

  • fa[x] 表示 x 的父节点
  • ch[x][y] 表示 x 的儿子节点,y=0 表示左儿子,y=1 表示右儿子
  • cnt[x] 表示 x 这个数出现了几次
  • val[x] 表示 x 节点的权值是多少
  • size[x] 表示以 x 为根的树节点个数(树的大小)
  • tot_size 表示树的总大小
  • root 表示当前根节点是哪个

下面介绍操作:

clear(x)

把 x 节点上的所有信息清空

void clear(int x) {
    fa[x]=ch[x][0]=ch[x][1]=cnt[x]=size[x]=val[x]=0;
}

get(x)

判断 x 节点为它父节点的左儿子还是右儿子(左0右1)

int get(int x) {
    return ch[fa[x]][1]==x;
}

update(x)

维护以 x 为根的树的大小
在下面的操作的时候如果会update很多点,一定要从下往上维护。

void update(int x) {
    if(x) {
        size[x]=cnt[x];
        if(ch[x][0]) size[x]+=size[ch[x][0]];
        if(ch[x][1]) size[x]+=size[ch[x][1]];
    }
}

rotate(x)

Splay中最最最重要的一个环节。
把 x 节点旋转到 x 的父节点的位置。
可是这是二叉树呀,这样操作不就乱了吗?
所以我们要维护某些节点之间的父子关系。

首先我们要明确在这次操作中会涉及到的节点:
1、x,你就是转它肯定会涉及它呀
2、fa[x],你要把 x 转到那里肯定也会涉及到它
3、fa[fa[x]],把 x 转到了 fa[x] 时,fa[fa[x]] 的儿子就不是 fa[x] 了,会变成 x

好了,rotate操作就会涉及到这 3 个节点,每个节点改变它的父亲和儿子,就会有六条语句,其中如果 fa[x] 已经是根了,那么就不用改变 fa[fa[x]] 的儿子了。

最关键的问题来了:父子关系怎么分配呢?
因为我们想把 x 到 fa[x] 的位置,那么它们的的父子关系必然会互换。唯一要确定的就是左右儿子的问题。
假设 x 是 fa[x] 的左儿子,那么 fa[x] 的原本的左儿子 x 将会变成 x 的右儿子(这里为什么是右儿子,因为这样才会保持二叉搜索树的性质)。反之亦然,所以我们用 which 来记录 x 与 fa[x] 的关系,最后维护一下旋转后的树的大小(因为 fa[x] 已经是 x 的儿子了,所以先update(fa[x])),代码如下:

void rotate(int x) {
    int pa=fa[x],papa=fa[pa],which=get(x);
    ch[pa][which]=ch[x][!which];fa[ch[x][!which]]=pa;
    ch[x][!which]=pa;fa[pa]=x;
    fa[x]=papa;if(papa) ch[papa][ch[papa][1]==pa]=x;
    update(pa);update(x);
}

splay(x)

这个函数是通过不断的rotate把 x 转到根的位置。

注意三点一线的时候是先转fa[x]再转x

void splay(int x) {
    for(int f;f=fa[x];rotate(x)) {
        if(fa[f]) rotate(get(f)==get(x)?f:x);
    }
    root=x;
}

insert(x)

插入一个数x。

三种情况:
1、空树。直接改改信息return就好了。
2、x重复。cnt[x]++,维护一下return。
3、找到了最底下。新开节点维护一下return。

这个具体看代码吧,应该很好理解。
下面两种情况不要忘记splay一下。

void insert(int v) {
    if(root==0) {
        tot_size++;
        ch[tot_size][0]=ch[tot_size][1]=fa[tot_size]=0;
        val[tot_size]=v;
        cnt[tot_size]=size[tot_size]=1;
        root=tot_size;
        return;
    }
    int f=0,now=root;
    while(true) {
        if(val[now]==v) {
            cnt[now]++;
            update(now);
            update(f);
            splay(now);
            return;
        }
        f=now;
        now=ch[now][val[now]

find(x)

查找x这个数的排名

就按照二叉搜索树的性质往下查找,注意我们在往左子树找的时候是不用累加结果的,因为最左边的就是第一个,在往右边找的时候再加上左子树的大小,找到的时候别忘了把 x splay到根方便以后的操作。

int find(int x) {
    int res=0,now=root;
    while(true) {   
        if(x

findx(x)

查找排名为x的树的节点

和find类似无非就是多判断一下子树的大小看看能否继续查找,temp表示的是已经搜了多少个节点。

int findx(int p) {
    int now=root;
    while(true) {
        if(ch[now][0] && p<=size[ch[now][0]]) {
            now=ch[now][0];
        }
        else {
            int temp=size[ch[now][0]]+cnt[now];
            if(p<=temp) return val[now];
            p-=temp;
            now=ch[now][1];
        }
    }
}

pre() 和 next()

查找根节点的前驱和后继节点

如果要查找x的前驱或后继的话,就先insert(x),把它转到根,再del(x),删除。

这个操作很简单,根节点的前驱就是根节点左子树中最靠右的那个,后继就是右子树中最靠左的那个,想一想,为什么?

int pre() {
    int now=ch[root][0];
    while(ch[now][1]) now=ch[now][1];
    return now;
}

int next() {
    int now=ch[root][1];
    while(ch[now][0]) now=ch[now][0];
    return now;
}

del(x)

删除大小为x的节点

首先我们随便find一下,目的是让x转到根节点,现在root就是x。

然后就会出现下面几种情况:
1、x有重复。那么直接cnt[root]--,return就好了。
2、root没有儿子了,即树上只有x一个节点。那么直接删除根节点,return。
3、root只有左儿子或只有右儿子。那就把它的这个儿子变成父亲,然后删除父亲,return。
4、root有两个儿子。那么为了满足二叉搜索树的性质,我们把根的前驱变成新的根,再把原来根的右子树接到新根的右儿子上,最后删除原来的根,维护一下新根,return。

void del(int x) {
    int gg=find(x);
    if(cnt[root]>1) {
        cnt[root]--;
        return;
    }
    if(!ch[root][0] && !ch[root][1]) {
        clear(root);
        root=0;
        return;
    }
    if(!ch[root][0]) {
        int oldroot=root;
        root=ch[root][1];
        fa[root]=0;
        clear(oldroot);
        return;
    }
    else if(!ch[root][1]) {
        int oldroot=root;
        root=ch[root][0];
        fa[root]=0;
        clear(oldroot);
        return;
    }
    int oldroot=root;
    splay(pre());
    fa[ch[oldroot][1]]=root;
    ch[root][1]=ch[oldroot][1];
    clear(oldroot);
    update(root);
    return;
}

最后整合成一道模板题。

#include 
#include 
#include 
#include 
#define MAXN 1000005

using namespace std;

int read() {
    int x=0,f=1;char ch=getchar();
    while(ch<'0' || ch>'9') {if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0' && ch<='9') {x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

int fa[MAXN],cnt[MAXN],ch[MAXN][2],size[MAXN],val[MAXN],tot_size,root;

void clear(int x) {
    fa[x]=ch[x][0]=ch[x][1]=cnt[x]=size[x]=val[x]=0;
}

int get(int x) {
    return ch[fa[x]][1]==x;
}

void update(int x) {
    if(x) {
        size[x]=cnt[x];
        if(ch[x][0]) size[x]+=size[ch[x][0]];
        if(ch[x][1]) size[x]+=size[ch[x][1]];
    }
}

void rotate(int x) {
    int pa=fa[x],papa=fa[pa],which=get(x);
    ch[pa][which]=ch[x][!which];fa[ch[x][!which]]=pa;
    ch[x][!which]=pa;fa[pa]=x;
    fa[x]=papa;if(papa) ch[papa][ch[papa][1]==pa]=x;
    update(pa);update(x);
}

void splay(int x) {
    for(int f;f=fa[x];rotate(x)) {
        if(fa[f]) rotate(get(f)==get(x)?f:x);
    }
    root=x;
}

void insert(int v) {
    if(root==0) {
        tot_size++;
        ch[tot_size][0]=ch[tot_size][1]=fa[tot_size]=0;
        val[tot_size]=v;
        cnt[tot_size]=size[tot_size]=1;
        root=tot_size;
        return;
    }
    int f=0,now=root;
    while(true) {
        if(val[now]==v) {
            cnt[now]++;
            update(now);
            update(f);
            splay(now);
            return;
        }
        f=now;
        now=ch[now][val[now]1) {
        cnt[root]--;
        return;
    }
    if(!ch[root][0] && !ch[root][1]) {
        clear(root);
        root=0;
        return;
    }
    if(!ch[root][0]) {
        int oldroot=root;
        root=ch[root][1];
        fa[root]=0;
        clear(oldroot);
        return;
    }
    else if(!ch[root][1]) {
        int oldroot=root;
        root=ch[root][0];
        fa[root]=0;
        clear(oldroot);
        return;
    }
    int oldroot=root;
    splay(pre());
    fa[ch[oldroot][1]]=root;
    ch[root][1]=ch[oldroot][1];
    clear(oldroot);
    update(root);
    return;
}

int main() {
    int n=read();
    while(n--) {
        int opt=read(),x=read();
        if(opt==1) insert(x);
        if(opt==2) del(x);
        if(opt==3) printf("%d\n",find(x));
        if(opt==4) printf("%d\n",findx(x));
        if(opt==5) {
            insert(x);
            printf("%d\n",val[pre()]);
            del(x);
        }
        if(opt==6) {
            insert(x);
            printf("%d\n",val[next()]);
            del(x);
        }
    }
    return 0;
}

你可能感兴趣的:(Splay)