天天看點

SPlay 伸展樹Splay伸展樹

Splay伸展樹

伸展樹(Splay Tree)是一種二叉排序樹,能在最壞平攤LogN時間内完成插入、查找和删除操作。是一種自調整形式的二叉樹,它會沿着某個節點到跟的路徑,通過一系列旋轉把這個節點搬到根節點去。

Splay樹的基本操作:

1、旋轉和伸展

2、查找

3、插入、删除

4、最大最小值

5、前驅後繼

6、合并、分離

一般Splay的節點資料:

struct node{
    int val,size;       //節點值,節點大小
    node * ch[],*fa;   //左右節點,父節點
}*root=NULL;            
           

Splay旋轉:

void Rotate(Node *&x,int c){//c== 左旋 c==右旋 
    Node *y=x->fa;
    //PushDown(y);
    //PushDown(x);
    //PushDown(x->ch[c]);
    y->ch[!c] = x->ch[c];
    if(x->ch[c] != NULL) x->ch[c]->fa = y;
    x->fa = y->fa;
    if(y->fa != NULL)
        if(y->fa->ch[] == y) y->fa->ch[] = x;
        else y->fa->ch[] = x;
    x->ch[c] = y; y->fa = x;
    //UpDate(y);
    if(y == root) root=x;
}
           
SPlay 伸展樹Splay伸展樹

注意:前方高能

伸展操作:

情況一:節點x的父節點y是根節點。這時,如果x是p的左孩子,我們進行一次Zig(右旋)操作;如果x 是p 的右孩子,則我們進行一次Zag(左旋)操作。經過旋轉,x成為二叉查找樹的根節點,調整結束。即:如果目前結點父結點即為根結點,那麼我們隻需要進行一次簡單旋轉即可完成任務,我們稱這種旋轉為單旋轉。如圖1所示:

SPlay 伸展樹Splay伸展樹

情況二:(一般稱作“一字型旋轉”)節點x 的父節點p 不是根節點,p 的父節點為g,且x 與p 同時是各自父節點的左孩子或者同時是各自父節點的右孩子。這時,我們進行一次Zig-Zig操作或者Zag-Zag操作。即:設目前結點為x , x 的父結點為p ,p 的父結點為g ,如果p 和x 同為其父親的左孩子或右孩子,那麼我們先旋轉p ,再旋轉x 。如圖2所示:

SPlay 伸展樹Splay伸展樹

情況三:(一般稱作“之字型旋轉”)節點x的父節點p不是根節點,p的父節點為g,x與p中一個是其父節點的左孩子而另一個是其父節點的右孩子。這時,我們進行一次Zig-Zag操作或者Zag-Zig 操作。即:這時我們連續旋轉兩次X 。如圖3所示:

SPlay 伸展樹Splay伸展樹

說明:這種情況先将x轉到它的父親的位置上,再與g旋轉

SPLAY伸展操作代碼:

// node 為結點類型,其中ch[]表示左結點指針,ch[]表示右結點指針  
// pre 表示指向父親的指針  
void Rotate(Node *&x,int c){//c== 左旋 c==右旋 
    Node *y=x->fa;
    //PushDown(y);
    //PushDown(x);
    //PushDown(x->ch[c]);
    y->ch[!c] = x->ch[c];
    if(x->ch[c] != NULL) x->ch[c]->fa = y;
    x->fa = y->fa;
    if(y->fa != NULL)
        if(y->fa->ch[] == y) y->fa->ch[] = x;
        else y->fa->ch[] = x;
    x->ch[c] = y; y->fa = x;
    //UpDate(y);
    if(y == root) root=x;
} 
void Splay(Node *&cur,Node *&f){//将cur轉到f的位置 
    for(PushDown(cur); cur != f ;){
        if(cur->fa == f) 
            if(f->ch[] == cur) Rotate(cur,);
            else Rotate(cur,);
        else {
            Node *y = cur->fa,*z = y->fa;
            if(z->ch[] == y)
                if(y->ch[] == cur)
                    Rotate(y,),Rotate(cur,);//yi
                else Rotate(cur,),Rotate(cur,);//zhi
            else if(y->ch[] == cur)
                    Rotate(y,),Rotate(cur,);//yi
                else Rotate(cur,),Rotate(cur,);//zhi
            if(z==f) break;
        }
        //UpDate(cur);
    }
    //UpDate(cur);
} 
           

如果說要把某個節點轉到根的位置,你可以假設還有一個須根,目前樹的根永遠吊在須根上!!(個人心得、僅供參考)

Splay樹的遞歸建立

當建樹節點的值是有序的時候,可以利用遞歸方式建樹,類似于線段樹的建樹方式,效率還是比較高的。

void build(node *fa,node *&cur,int l,int r){
    if(l>r)return;
    int mid=(l+r)>>;
    cur=newnode(sz[mid]);
    cur->pre=fa;
    build(cur,cur->ch[],l,mid-);
    build(cur,cur->ch[],mid+,r);
    update(cur);
}
           

Splay的節點查找

首先按照二叉排序樹的性質(左子樹都比它小,右子樹都比它大),然後将該節點伸展到根節點。。

//查找鍵值為X的節點的位置并将其旋轉到根節點 
node *bst_search(node *cur,int x){
    if(!cur) return NULL;
    if(cur->sh == x) return cur;
    else if(x > cur->sh)
        return bst_search(cur->ch[],x);
    else if(x < cur->sh)
        return bst_search(cur->ch[],x);
}
//bst_search 傳回的是鍵值為x的元素的位置 
node *search(node *cur,int x){
    node *p=bst_search(cur,x);
    //此時p即為元素x的位置
    splay(p,cur->pre);
    return p;
}
           

找到處在中序周遊第k 個結點,并将其旋轉到結點f 的下面:

void Select(int k, node *&f){
    int tmp;
    node *t;
    for(t=root;;) { // 從根結點開始
        Push_Down(t); // 由于要通路t 的子結點,将标記下傳
        tmp = t->ch[]->size;//得到t 左子樹的大小
        if (k == tmp+) break;//得出t 即為查找結點,退出循環
        if (k <= tmp) // 第k 個結點在t 左邊,向左走
            t = t->ch[];
        else // 否則在右邊,而且在右子樹中,這個結點不再是第k 個
            k -= tmp + , t = t->ch[];
    }
    Push_Down(t);
    splay(t,f); // 執行旋轉
}
           

Splay節點插入:

按照二叉排序樹插入,然後将其旋轉到根節點下

結構體裡寫上這個函數:

node *newnode(int x){
    node *cur=new node;
    cur->val = x;
    cur->ch[] = cur->ch[] = cur->pre = NULL;
}
           

然後是插入函數:

//插入值為x的一個新節點 
void insert(node * cur,int x){
    if(root==NULL){//cur不可改變,root要特判
        root=newnode(x);
        return ;
    }
    node *p;
    while(cur!=NULL){
        p=cur;
        if(cur->val>x) cur = cur->ch[];
        else cur = cur->ch[];
    }
    //在 while 過程當中 p始終保持是cur的父節點 
    cur=newnode(x);
    if(cur->val < p->val) p->ch[]=cur; //注意判讀條件,不可用cur==p->ch[0]
    else p->ch[]=cur;
    cur->pre = p;
    splay(cur,NULL);
}
           

Splay的合并:

将倆棵伸展樹合并必須保證第二課伸展樹的值都大于第一棵伸展樹,隻需要将第一棵伸展樹的最大節點轉到根的位置上,這時候該節點的有孩子為空(因為:該節點是最大的節點,如果他還有右孩子的話根據二叉排序樹的定義他的右孩子比他大),直接将第二棵伸展樹挂到第一棵伸展樹的最大節點的右孩子上。。

node *merge(node *x,node *y){
    if(!x) return y;
    if(!y) return x;
    while(x->ch[]) x = x->ch[];
    splay(x,NULL);
    x->ch[] = y;
    y->pre = x;
    return x;
}
           

Splay分離:

方法:查找給定值得節點并伸展到根,傳回它的左右孩子

//Splay 分離
void split(node *cur,int x,node *&x,node *&y){
    node *t=search(cur,x);
    x=cur->ch[];x->pre=NULL;
    y=cur->ch[];y->pre=NULL;
}
           

Splay節點删除:

//值為x的節點删除 
node *del(node *cur,int x){
    node *t=search(cur,x);
    return merge(cur->ch[],cur->ch[]);
}
           

上面的内容看看還行,代碼就算了,代碼是我粘的我們呂老師的,指針寫的是又臭又長又醜,代碼還的看我的。。

繼續閱讀