伸展树

  伸展树(Splay Tree),也叫分裂树,是一种二叉排序树,它能在O(log n)内完成插入、查找和删除操作。

  旋转操作(左旋右旋)和前面的treap一样,伸展树最重要的操作是伸展操作,它把一个指定结点x自底向上旋转到根结点。分三种情况:

 

  情况一:节点x的父节点y是根节点。这时,如果x是y的左孩子,我们进行一次Zig(右旋)操作;如果x是y的右孩子,则我们进行一次Zag(左旋)操作。经过旋转,x成为二叉查找树S的根节点,调整结束。即:如果当前结点父结点即为根结点,那么我们只需要进行一次简单旋转即可完成任务,我们称这种旋转为单旋转。

  情况二:节点x的父节点y不是根节点,y的父节点为z,且x与y同时是各自父节点的左孩子或者同时是各自父节点的右孩子。这时,我们进行一次Zig-Zig操作或者Zag-Zag操作。即:设当前结点为X,X的父结点为Y,Y的父结点为Z,如果Y和X同为其父亲的左孩子或右孩子,那么我们先旋转Y,再旋转X。我们称这种旋转为一字形旋转。

  情况三:节点x的父节点y不是根节点,y的父节点为z,x与y中一个是其父节点的左孩子而另一个是其父节点的右孩子。这时,我们进行一次Zig-Zag操作或者Zag-Zig操作。即:这时我们连续旋转两次X。我们称这种旋转为之字形旋转。
 

 

struct Node{
    Node* ch[2];
    int s;
    int v;
    int flip;

    int cmp(int k) const{
        int d=k-ch[0]->s;
        if(d==1) return -1;
        return d<=0?0:1;
    }
    void maintain(){
        s=ch[0]->s+ch[1]->s+1;
    }
};

伸展操作,利用刚才那三种操作,把第k个元素旋转到根。

//k>=1
void splay(Node*& o,int k){
    o->pushdown();
    int d=o->cmp(k);
    if(d==1) k-=o->ch[0]->s+1;
    if(d!=-1){
        Node* p=o->ch[d];
        p->pushdown();
        int d2=p->cmp(k);
        int k2=(d2==0?k:k-p->ch[0]->s-1);
        if(d2!=-1){
            splay(p->ch[d2],k2);
            if(d2==d) rotate(o,d^1);
            else rotate(o->ch[d],d);
        }
        rotate(o,d^1);
    }
}

merge操作,把序列S1,S2连接在一起,S1在左边,S2在右边,返回新序列。

Node* merge(Node* left,Node* right){
    splay(left,left->s);
    left->ch[1]=right;
    left->maintain();
    return left;
}

split操作,把S分裂成两个连续的子序列,左子序列边包含左边k个元素,右子序列包含其余元素。k>=1,也就是说左边至少有一个元素。

void split(Node* o,int k,Node*& left,Node*& right){
    splay(o,k);
    left=o;
    right=o->ch[1];
    o->ch[1]=null;
    left->maintain();
}

 
range(L,R),截出[L,R)这一段放在root->ch[1]->ch[0],[L,R)是加了一个起始虚拟结点之后的区间。

Node*& range(int L,int R){
    splay(root,L);
    splay(root->ch[1],R-L+1);
    return root->ch[1]->ch[0];
}


 由于split左边不能为空,因此解决问题时通常加一个开始结点和一个结束结点。

伸展树用于处理序列问题,插入删除旋转之类的问题。

你可能感兴趣的:(伸展树)