zoukankan      html  css  js  c++  java
  • 学习笔记--(平衡树)splay

    坑爹的splay,毁我青春,耗我钱财,颓我精力
    

    是一种用于保存有序集合的简单高效的数据结构。伸展树实质上是一个二叉查找树。允许查找,插入,删除,删除最小,删除最大,分割,合并等许多操作,这些操作的时间复杂度为O(logN)。

    伸展树的时间复杂度边界是均摊的。尽管一个单独的操作可能很耗时,但对于一个任意的操作序列,时间复杂度可以保证为O(logN)。

    在一颗二叉树中访问一个节点的时间复杂度是这个节点的深度。因此,我们可以重构树的结构,使得被经常访问的节点朝树根的方向移动。尽管这会引入额外的操作,但是经常被访问的节点被移动到了靠近根的位置,因此,对于这部分节点,我们可以很快的访问。根据上面的90-10法则,这样做可以提高性能。
    为了达到上面的目的,我们需要使用一种策略──旋转到根(rotate-to-root)。具体实现如下:
    旋转分为左旋和右旋,这两个是对称的。图示:
    这里写图片描述
    基本的自底向上伸展树:
    应用伸展(splaying)技术,可以得到对数均摊边界的时间复杂度。
    在旋转的时候,可以分为三种情况:
    1、zig情况。
    X是查找路径上我们需要旋转的一个非根节点。
    如果X的父节点是根,那么我们用下图所示的方法旋转X到根:
    这里写图片描述
    这和一个普通的单旋转相同。
    2、zig-zag情况。
    在这种情况中,X有一个父节点P和祖父节点G(P的父节点)。X是右子节点,P是左子节点,或者反过来。这个就是双旋转。
    先是X绕P左旋转,再接着X绕G右旋转。
    如图所示:
    这里写图片描述
    3、zig-zig情况。
    这和前一个旋转不同。在这种情况中,X和P都是左子节点或右子节点。
    先是P绕G右旋转,接着X绕P右旋转。
    如图所示:
    这里写图片描述

    基本伸展树操作:
    1、插入:
    当一个节点插入时,伸展操作将执行。因此,新插入的节点在根上。
    2、查找:
    如果查找成功(找到),那么由于伸展操作,被查找的节点成为树的新根。
    如果查找失败(没有),那么在查找遇到NULL之前的那个节点成为新的根。也就是,如果查找的节点在树中,那么,此时根上的节点就是距离这个节点最近的节点。
    3、查找最大最小:
    查找之后执行伸展。
    4、删除最大最小:
    a)删除最小:
    首先执行查找最小的操作。
    这时,要删除的节点就在根上。根据二叉查找树的特点,根没有左子节点。
    使用根的右子结点作为新的根,删除旧的包含最小值的根。
    b)删除最大:
    首先执行查找最大的操作。
    删除根,并把被删除的根的左子结点作为新的根。
    5、删除:
    将要删除的节点移至根。
    删除根,剩下两个子树L(左子树)和R(右子树)。
    使用DeleteMax查找L的最大节点,此时,L的根没有右子树。
    使R成为L的根的右子树。
    如下图示:
    这里写图片描述
    自顶向下的伸展树:
    在自底向上的伸展树中,我们需要求一个节点的父节点和祖父节点,因此这种伸展树难以实现。因此,我们可以构建自顶向下的伸展树。
    当我们沿着树向下搜索某个节点X的时候,我们将搜索路径上的节点及其子树移走。我们构建两棵临时的树──左树和右树。没有被移走的节点构成的树称作中树。在伸展操作的过程中:
    1、当前节点X是中树的根。
    2、左树L保存小于X的节点。
    3、右树R保存大于X的节点。
    开始时候,X是树T的根,左右树L和R都是空的。和前面的自下而上相同,自上而下也分三种情况:
    1、zig:
    这里写图片描述
    如上图,在搜索到X的时候,所查找的节点比X小,将Y旋转到中树的树根。旋转之后,X及其右子树被移动到右树上。很显然,右树上的节点都大于所要查找的节点。注意X被放置在右树的最小的位置,也就是X及其子树比原先的右树中所有的节点都要小。这是由于越是在路径前面被移动到右树的节点,其值越大。读者可以分析一下树的结构,原因很简单。
    2、zig-zig:
    这里写图片描述
    在这种情况下,所查找的节点在Z的子树中,也就是,所查找的节点比X和Y都小。所以要将X,Y及其右子树都移动到右树中。首先是Y绕X右旋,然后Z绕Y右旋,最后将Z的右子树(此时Z的右子节点为Y)移动到右树中。注意右树中挂载点的位置。
    3、zig-zag:
    这里写图片描述
    在这种情况中,首先将Y右旋到根。这和Zig的情况是一样的。然后变成上图右边所示的形状。接着,对Z进行左旋,将Y及其左子树移动到左树上。这样,这种情况就被分成了两个Zig情况。这样,在编程的时候就会简化,但是操作的数目增加(相当于两次Zig情况)。
    最后,在查找到节点后,将三棵树合并。如图:
    这里写图片描述
    将中树的左右子树分别连接到左树的右子树和右树的左子树上。将左右树作为X的左右子树。重新最成了一所查找的节点为根的树。
    右连接:将当前根及其右子树连接到右树上。左子结点作为新根。
    左连接:将当前根及其左子树连接到左树上。右子结点作为新根。

    最后是模板大全:(感谢TimeMachine爷提供的模板)

    /*(BZOJ 1861)*/
    #include <cstdio>
    #include <iostream>
    #include <cstring>
    #include <cmath>
    
    using namespace std;
    
    const int N=80012;
    int n,m,x,y,z;
    char opt,ch;
    struct Snode 
      {
        Snode *lch,*rch,*p;
        int id,size;
        inline void update(){size=lch->size+rch->size+1;}
        inline bool is_l() {return this==p->lch;}
        inline void set_l(Snode *x) {lch=x;x->p=this;}
        inline void set_r(Snode *x) {rch=x;x->p=this;}
        inline void left()
          {
            Snode *fa=p;
            if (fa->is_l()) fa->p->set_l(this);
              else fa->p->set_r(this);
            fa->set_r(lch);set_l(fa);fa->update();
          }
        inline void right()
          {
            Snode *fa=p;
            if (fa->is_l()) fa->p->set_l(this);
              else fa->p->set_r(this);
            fa->set_l(rch);set_r(fa);fa->update();
          }
        inline Snode *rank(int k)
          {
            if (lch->size+1==k) return this;
            if (lch->size+1<k) return rch->rank(k-lch->size-1);
              else return lch->rank(k);
          }
      } T[2*N],*P=T,*nil=T,*root,*pos[N],*ld,*rd;
    inline void newnode(Snode *&node,int x)
      {
        node=++P;
        node->lch=node->rch=node->p=nil;
        node->size=1;node->id=x;
      }
    inline void splay(Snode *&root,Snode *x,Snode *y)
      {
        while (x->p!=y)
          {
            if (x->p->p==y)
              {
                if (x->is_l()) x->right();else x->left();
                break;
              }
            if (x->p->is_l())
              if (x->is_l()) x->p->right(),x->right();
                else x->left(),x->right();
            else
              if (x->is_l()) x->right(),x->left();
                else x->p->left(),x->left();
          }
        x->update();
        if (y==nil) root=x;
      }
    inline Snode *pred(Snode *node)
      {
        splay(root,node,nil);
        Snode *last=nil,*now=node->lch;
        while (now!=nil) last=now,now=now->rch;
        return last;
      }
    inline Snode *succ(Snode *node)
      {
        splay(root,node,nil);
        Snode *last=nil,*now=node->rch;
        while (now!=nil) last=now,now=now->lch;
        return last;
      }
    inline void insert(Snode *node,int k)
      {
        Snode *t1=root->rank(k-1),*t2=root->rank(k);
        splay(root,t2,nil);splay(root,t1,root);
        root->lch->set_r(node);root->lch->update();root->update();
      }
    inline void remove(Snode *&node)
      {
        Snode *t1=pred(node),*t2=succ(node);
        splay(root,t2,nil);splay(root,t1,root);
        root->lch->set_r(nil);root->lch->update();root->update();
        node=nil;
      }
    inline int order(Snode *node)
      {
        splay(root,node,nil);return root->lch->size+1;
      }
    int main()
      {
        nil->id=-1;
        newnode(ld,0);newnode(rd,0);
        root=rd;rd->set_l(ld);ld->update();rd->update();
        scanf("%d%d",&n,&m);
        for (int i=1;i<=n;++i)
          {
            scanf("%d",&x);Snode *tmp=pred(rd);
            splay(root,rd,nil);splay(root,tmp,root);
            newnode(pos[x],x);root->lch->set_r(pos[x]);
            root->lch->update();root->update();
            splay(root,pos[x],nil);
          }
        for (int i=1;i<=m;++i)
          {
            do opt=getchar();while (opt=='
    ' || opt==' ');
            do ch=getchar();while (ch!=' ');
            if (opt=='T') scanf("%d",&x),remove(pos[x]),newnode(pos[x],x),insert(pos[x],2);
            if (opt=='B') scanf("%d",&x),remove(pos[x]),newnode(pos[x],x),insert(pos[x],n+1);
            if (opt=='I') scanf("%d%d",&x,&y),z=order(pos[x]),remove(pos[x]),newnode(pos[x],x),insert(pos[x],z+y);
            if (opt=='A') scanf("%d",&x),printf("%d
    ",order(pos[x])-2);
            if (opt=='Q') scanf("%d",&x),printf("%d
    ",root->rank(x+1)->id);
          }
      }      
  • 相关阅读:
    Ocelot(一)- .Net Core开源网关
    Extensions for Vue
    Vue Study [2]: Vue Router
    Vue Study [1]: Vue Setup
    获取当月的第一天和最后一天示例
    常规正则验证表达式
    当需要向数据库插入空值时,sql语句的判断
    让 IE支持圆角的方法
    服务器上传图片案例
    validatebox相关验证
  • 原文地址:https://www.cnblogs.com/DaD3zZ-Beyonder/p/5346211.html
Copyright © 2011-2022 走看看