zoukankan      html  css  js  c++  java
  • 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值,否则是假的。

  • 相关阅读:
    HIbernate小结
    玩耍Hibernate之缓存
    Java学习之IO流总结
    java基础学习之对象转型
    玩耍Hibernate系列(一)补充--基础知识
    玩耍Hibernate系列(二)--基础知识
    玩耍Hibernate系列(一)--基础知识
    Struts2拦截器的使用 (详解)
    struts1 和 struts2中Action什么时候实例化
    Java基础学习总结--Java对象的序列化和反序列化
  • 原文地址:https://www.cnblogs.com/BrotherHood/p/13328725.html
Copyright © 2011-2022 走看看