zoukankan      html  css  js  c++  java
  • 蒟蒻林荫小复习——Splay

    首先表示对YYB大佬的崇高敬意虽然大佬根本不知道林荫是个神马东西  

    在这里学的:yyb大佬的教程

    好吧,我回来填坑了!

    首先声明一下定义

    struct p
    {
        int v,ff,ch[2],size,cnt;
    };
    p t[150001];

    t数组就是记录整颗树的数组,v代表当前点的权值,ff代表当前点的父亲,ch[0,1]分别代表左右子树(左子树上的元素小于根,右子树则大于),size代表以该节点为根的子树中元素个数,cnt代表当前点上有多少个元素(权值均为v)

    pushup维护size

    void pushup(int x)
    {
        t[x].size=t[t[x].ch[0]].size+t[t[x].ch[1]].size+t[x].cnt;
    }

    下面是一次旋转rotate

    先想一下一次旋转会造成什么影响:假设当前节点为x,父亲为y,将x旋转到y上,x是y的k子树(k=0或1,代表左右子树)

    那么:

    1. x的k^1儿子会变成y(因为(y>x)==k^1,那么假如k=0,y>x,那么将y作为x的右子树保证大于x,若k=1,y<x,将y作为x的左子树仍保证小于x)
    2. 原来x的k^1儿子被过继给y做k儿子(假定该儿子为s,若k=0,s>x且s<y[否则s会存在于y的右子树],作为y的左子树保证小于y
    3. 没了

    然后注意适当的时候维护他们的ff就可以了。

    void rotate(int x)
    {
        int y=t[x].ff;
        int z=t[y].ff;
        int k=(t[y].ch[1]==x);
       t[z].ch[t[z].ch[1]==y]=x; t[y].ch[k]
    =t[x].ch[k^1]; t[t[x].ch[k^1]].ff=y; t[x].ch[k^1]=y; t[x].ff=z; t[y].ff=x; pushup(y); pushup(x); }

    下面就是Splay的核心了,Splay,这也是它快的原因

    Splay的作用是将x旋转成为goal的儿子,若goal==0则将x旋转到root的位置。

    现在先想一种情况:

     好的,图片有点大,图片引用自yyb大佬的博客。

    考虑这种情况,现在我们将x旋转到root(z)的位置,然后我们查询b的位置,旋转前查询路径:Z—》Y—》X—》B

    但是旋转后呢?(自己画个图)路径还是由X—》Z—》Y—》B,因为这个时候,B作为X的右儿子被过继给了Y,而B的养父Y由于被X旋转下来的Z压着,留在了社会的最底层(批斗X)这时,树的深度没有变化。

    然而无论如何向上层社会进步的同时都会付出一定的代价,X向上旋转的时候B一定会被过继给Y,两次连续的旋转后就留在了社会底层,这就不平衡了对吧。那我们就顺便提高一波Y的社会地位,先将Y向上旋转,这个时候Y的左子树是X,右子树是Z,然后再将X旋转上去。

    可能有的小可爱已经发现了一个问题,上面所说的问题出现在Y和X与自己的父亲大小关系相同的情况下,换言之就是X和Y同为自己父亲的左或者右子树。

    因为如果Y和X不同为自己父亲的左或右子树的话,Y不会受到X和Z旋转的影响,也就是说Y和Z在X的不同子树上,不会出现Y被Z压着的情况,这个时候,树的深度就变浅了HHHHH。

    好了,林荫开始杠精了,如果在把X翻上去之后要查询Z的儿子的话怎么办?那就再把Z的儿子翻上来不就好啦,翻着翻着树就变矮变宽了,这也就是平衡树的精髓所在

    对了,这个时候,如果目标goal==0的话,root就是X啦

    void Splay(int x,int goal)
    {
        while(t[x].ff!=goal)
        {
            int y=t[x].ff;
            int z=t[y].ff;
            if(z!=goal)
            {
                (t[y].ch[0]==x)^(t[z].ch[0]==y)?rotate(x):rotate(y);
            }
            rotate(x);
        }
        if(goal==0)
        {
            root=x;
        }
    }

     insert就没啥好说的了,一溜烟往下找,找到了计数器++,找不到自力更生新开个点

    void insert(int x)
    {
        int u=root,ff;
        while(u&&t[u].v!=x)
        {
            ff=u;
            u=t[u].ch[t[u].v<x];
        }
        if(t[u].v==x)
        {
            t[u].cnt++;
        }
        else
        {
            u=++tot;
            t[u].v=x;
            t[u].ff=ff;
            if(ff)
                t[ff].ch[x>t[ff].v]=u;
            t[u].cnt=1;
            t[u].size=1;
            t[u].ch[0]=t[u].ch[1]=0;
        }
        Splay(u,0);
    }

    Find的操作意义是找到权值为x的节点并将其翻到树顶,也没啥可说的,一溜烟往下找,找到了就翻上去即可。

    void Find(int x)
    {
        int u=root;
        while(t[u].ch[x>t[u].v]&&t[u].v!=x)
        {
            u=t[u].ch[x>t[u].v];
        }
        Splay(u,0);
    }

    下面是前驱后缀查询操作,f=0代表查询前驱,=1代表查询后缀。

    int Next(int x,int f)
    {
        Find(x);
        int u=root;
        if(t[u].v>x&&f)
        {
            return u;
        }
        if(t[u].v<x&&!f)
        {
            return u;
        }
        u=t[u].ch[f];
        while(t[u].ch[f^1])
        {
            u=t[u].ch[f^1];
        }
        return u;
    }

    这里有一点需要注意,因为Find(x)中有Splay(u,0)语句了,实际上这个时候u点的权值就是x,这里是yyb大佬博客中的一个小坑,中间两个if是没有意义的。

    下面是Kth(实际上是将序列从小到大排开第K个)这个挺简单的。

    int Kth(int x)
    {
        int u=root;
        if(t[u].size<x)
        {
            return -INF;
        }
        while(1)
        {
            int y=t[u].ch[0];
            if(t[y].size+t[u].cnt<x)
            {
                x-=t[y].size+t[u].cnt;
                u=t[u].ch[1];
            }
            else
            {
                if(t[y].size>=x)
                {
                    u=t[u].ch[0];
                }
                else
                    return t[u].v;
            }
        }
    }

    最后是Delete

    怎样删除一个可爱的节点,那肯定是将其先变成叶子节点。查找这个节点的前驱后继,将前驱旋转到根,后继旋转到成为前驱的儿子,这个时候,目标节点就一定是后继节点的左儿子并且是叶子节点。那么就搞死它啦!

    void Delete(int x)//删除x
    {
        int last=Next(x,0);//查找x的前驱
        int next=Next(x,1);//查找x的后继
        splay(last,0);splay(next,last);
        //将前驱旋转到根节点,后继旋转到根节点下面
        //很明显,此时后继是前驱的右儿子,x是后继的左儿子,并且x是叶子节点
        int del=t[next].ch[0];//后继的左儿子
        if(t[del].cnt>1)//如果超过一个
        {
            t[del].cnt--;//直接减少一个
            splay(del,0);//旋转
        }
        else
            t[next].ch[0]=0;//这个节点直接丢掉(不存在了)
    }//引用自yyb大佬

    完结撒花!

  • 相关阅读:
    菜根谭#188
    菜根谭#187
    Single value range only allowed in SystemVerilog
    LUTs, Flip-Flop, Slice
    FPGA 的 RAM 的 区别
    GPU core clock, shader clock ???
    更改Mac的Terminal 格式
    GPU share memory 的应用 (主要内容转载)
    Mac text edit & pdf reader
    Programming Font
  • 原文地址:https://www.cnblogs.com/XLINYIN/p/11362695.html
Copyright © 2011-2022 走看看