zoukankan      html  css  js  c++  java
  • 2017.2.10 Splay总结

    平衡树一直是自己的一大痛点……原因有两点:

    1.原理有点绕,容易忘记

    2.代码长,难码难调

    不过遇到难点就退缩不是好的选择。于是在这里记录一下自己对于Splay的傻逼理解,以免自己忘记。

    参考博客:http://blog.csdn.net/ric_shooter/article/details/20636487

    首先要明确一点:平衡树(伸展树)的本质是二叉查找树——所以它查找节点位置和前驱(比自己小的最大的数)和后继(比自己大的最小的数)等等基本都是一样的,功能和二叉查找树基本一样。但二叉查找树的极限效率和暴力没有区别(链),所以要通过一种”旋转操作来保证树的层数,从而保证复杂度为log--------至于为什么要这样旋转,别问我,网上也没有,问tarjian

    那么关键就是理解旋转了。下面这张图很典型:

    现在目标是对A和B进行旋转。

    如果要把B旋转到父亲,对于B来说,它是A的子树,所以。首先,根据二叉搜索树的定义,A比B大,所以旋转后A是B的右儿子,D是B的左儿子不变。C同理不变。那么只剩E了:显然应该放在A的左儿子。对应到操作,应该如下:

    1)把A的size减去D的size,因为它不再在A的子树中

    2)把B的size加上E的size,因为它变到了A的子树里(注意处理A、B自己也要算在size里)

    3)E的父亲设为A,A的父亲设为B,B的父亲设为A的父亲

    是不是很简单?但打到代码里其实细节非常多一定要细心,这也是为什么splay的常数巨大,代码长(常数小王子)

    如果要把A旋转到父亲,对于A来说,它是B的子树,所以。其实会发现思路与代码和右旋一模一样,所以只要写一个rotate函数并且把”左和右“设置为”正方向和反方向即可“。具体实现看代码。

    还有一个比较重要操作是splay,也就是判断是用zig还是zag还是zig-zig还是zig-zag还是zag-zig还是zag-zag。对于还未旋转到根的节点,两步合在一起能节省复杂度,原理问tarjian。根据所处位置判断即可。

    每一次旋转都是要把一个节点旋转到根的。一定要记得什么时候要旋转,这样才正确!!!!

    具体看代码,注释很 详 细。

    (裸题bzoj 3224)

    #include<cmath>
    #include<queue>
    #include<cstdio>
    #include<vector>
    #include<cstdlib>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #define N 100010
    #define RG register
    #define inf 0x3f3f3f3f
    #define Inf 99999999999999999LL
    using namespace std;
    typedef long long LL;
    int n,root,sum,opt,x,fa[N],siz[N],w[N],val[N],son[N][2];
    inline int Abs(RG const int &a){return a>0?a:-a;}
    inline int Max(RG const int &a,RG const int &b){return a>b?a:b;}
    inline int Min(RG const int &a,RG const int &b){return a>b?b:a;}
    inline int gi(){
        RG int x=0;RG bool flag=0;RG char c=getchar();
        while((c<'0'||c>'9')&&c!='-') c=getchar();
        if(c=='-') c=getchar(),flag=1;
        while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
        return flag?-x:x;
    }
    inline void rotate(RG int x,RG int way){//旋转最关键,一定要记住它的原理啊啊啊啊啊!!!!
        //牵涉到的东西有祖先、父亲、当前节点、当前节点兄弟,左儿子,右儿子
        //way为正方向,1-way为反方向
        int f=fa[x];
        siz[f]+=siz[son[x][way]]-siz[x];//把父亲的siz减去当前节点及反方向儿子的siz(旋转后这部分不再在父亲管辖以内)
        siz[x]+=siz[f]-siz[son[x][way]];//把儿子的siz加上兄弟的siz,因为旋转以后他就变成当前节点的孙子了
        son[f][1-way]=son[x][way];      //父亲反方向儿子就便成当前节点正方向儿子了
        if(son[x][way]) fa[son[x][way]]=f;
        fa[x]=fa[f];
        if(fa[f]){
        if(f==son[fa[f]][0]) son[fa[f]][0]=x;
        else                 son[fa[f]][1]=x;
        }
        fa[f]=x;
        son[x][way]=f;
    }
    inline void splay(RG int x){
        while(fa[x]){
        RG int f=fa[x];
            if(!fa[f]){
            if(x==son[f][0]) rotate(x,1);
            else             rotate(x,0);//若当前节点为根节点的儿子,则直接旋转(zig or zag)
        }
        else{//至少有两层,直接zig-zig or zag-zag or zig-zag or zag-zig
            if(f==son[fa[f]][0]){//重要!!!!在左边就右旋,在右边就左旋(与当前方向相反)
            if(x==son[f][0]){
                rotate(f,1);
                rotate(x,1);//玄学的力量:zig-zig,zag-zag先旋转father在旋转自己效率更高
            }
            else{
                rotate(x,0);
                rotate(x,1);//但zig-zag和zag-zig必须从自己开始!
            }
            }
            else{
            if(x==son[f][0]){
                rotate(x,1);
                rotate(x,0);
            }
            else{
                rotate(f,0);
                rotate(x,0);
            }
            }
            }
        }
        root=x;
    }
    inline int findloc(RG int x,RG int goal){
        while(w[x]!=goal){
        if(goal<w[x]){
            if(!son[x][0]) break;
            x=son[x][0];
        }
        else{
            if(!son[x][1]) break;
            x=son[x][1];
        }
        }
        return x;
    }
    inline void ins(RG int x){
        if(!sum){
        sum=1;w[1]=x;
        root=1;fa[1]=0;
        siz[1]=1;val[1]=1;
        return;
        }
        RG bool flag=0;
        RG int nowloc=findloc(root,x),hold;
        if(w[nowloc]==x){
        flag=1;
        hold=nowloc;
        ++val[nowloc];
        }
        else{
        ++sum;
        siz[sum]=1;val[sum]=1;
        w[sum]=x;fa[sum]=nowloc;
        if(w[nowloc]>x) son[nowloc][0]=sum;
        else            son[nowloc][1]=sum;
        }
        while(nowloc){
        ++siz[nowloc];
        nowloc=fa[nowloc];
        }
        if(flag) splay(hold);
        else     splay(sum);
    }//插入操作与普通的二叉搜索树完全一样,只是最后指向splay旋转
    inline int bignum(RG int x,RG int way){
        const int hehe[2]={inf,-inf};
        RG int nowloc=findloc(x,hehe[way]);//找到对应路径的权值最大(小)节点 大对应左子树 小对应右子树
        RG int ww=w[nowloc];
        splay(nowloc);//将这个点转到最顶端
        return ww;
    }
    inline void del(RG int x){
        int nowloc=findloc(root,x),f;
        splay(nowloc);//已经splay,当前节点在树顶
        if(w[nowloc]!=x) return;
        --val[nowloc];
        --siz[nowloc];
        if(val[nowloc]) return;//节点不止一个,直接删除即可;
        else{
        w[nowloc]=0;
            if(!son[nowloc][0]){
            f=son[nowloc][1];
            son[nowloc][1]=0;
            root=f;
            fa[root]=0;//没有左子树,又子树即为根
            }
            else{
                fa[son[nowloc][0]]=0;//切断左子树与根节点的关联(此时左边完全独立出来)
            f=bignum(son[nowloc][0],0);//左子树构成了一颗全新splay
            son[root][1]=son[nowloc][1];//其右儿子即为当前节点右儿子——左右子树合并
            siz[root]+=siz[son[nowloc][1]];
            if(son[root][1]) fa[son[root][1]]=root;
            son[nowloc][0]=son[nowloc][1]=0;
        }
        }
    }
    inline int numrank(RG int x){
        int nowloc=findloc(root,x);
        splay(nowloc);
        root=nowloc;
        return siz[son[nowloc][0]]+1;
    }
    inline int ranknum(RG int x,RG int way){
        int now=root,hold;
        while(!((x>=siz[son[now][way]]+1)&&(x<=siz[son[now][way]]+val[now])&&now)){//如果不满足条件
        if(x>siz[son[now][way]]+val[now]){//如果左边还要大于所求区间,往右边找
            x-=siz[son[now][way]]+val[now];
            now=son[now][1-way];
        }
        else now=son[now][way];//x过小,往左找
        }
        hold=now;
        splay(now);
        return hold;
    }
    inline int pre(RG int x){
        RG int nowloc=findloc(root,x);
        splay(nowloc);
        if(w[nowloc]<x) return w[nowloc];
        return bignum(son[nowloc][0],0);
    }
    inline int back(RG int x){
        RG int nowloc=findloc(root,x);
        splay(nowloc);
        if(w[nowloc]>x) return w[nowloc];
        return bignum(son[nowloc][1],1);
    }//类似普通二叉搜索树查找,只不过记得翻转保证复杂度
    inline void work(){
        n=gi();
        for (RG int i=1;i<=n;++i){
        opt=gi();x=gi();
        switch(opt){
            case 1 : ins(x);break;
            case 2 : del(x);break;
            case 3 : printf("%d
    ",numrank(x));break;
            case 4 : printf("%d
    ",w[ranknum(x,0)]);break;
            case 5 : printf("%d
    ",pre(x));break;
            case 6 : printf("%d
    ",back(x));break;
                default : break;
        }
        }
    }
    int main(){
        freopen("3224.in","r",stdin);
        freopen("3224.out","w",stdout);
        work();
        fclose(stdin);
        fclose(stdout);
        return 0;
    }

    数据结构这种东西从来都是难写难调的。一定要细心。学会造数据调试。勤加练习。

  • 相关阅读:
    第09组 Alpha冲刺 (2/6)
    第08组 Beta冲刺 (1/5)
    第08组 Alpha冲刺 总结
    第08组 Alpha冲刺 (6/6)
    第08组Alpha冲刺(5/6)
    第08组 Alpha冲刺 (4/6)
    第08组 Alpha冲刺 (3/6)
    第08组 Alpha冲刺 (2/6)
    第08组 Alpha冲刺 (1/6)
    第12组 Beta冲刺(2/5)
  • 原文地址:https://www.cnblogs.com/Super-Nick/p/6387968.html
Copyright © 2011-2022 走看看