淺談算法——splay

BST(二叉查找樹)是個有意思的東西,種類巨TM多,而後咱們今天不講其餘的,咱們今天就講splayphp


首先,若是你不知道Splay是啥,你也得知道BST是啥ios

如上圖就是一棵優美的BST,它對於每一個點保證其左子樹內全部點小於本身,右子樹內全部點大於本身,並且這棵樹高只有\(\log n\),因此找一個點只須要\(O(\log n)\)的時間數據結構

可是若是這個圖長得極端一點就會變成這樣……函數

這棵樹就很是的不優美,每次查找的複雜度爲\(O(n)\),而後就\(O(n^2)\)了……優化


而後各類大佬們爲了解決這個藍瘦的事情,紛紛想出了一些解決方案,其中有個叫Tarjan的大佬,弄出了一個名叫Splay的玩意,而後咱們來說一下Splay的一些操做ui


1.旋轉
旋轉式BST(Splay是其中的一種)基本上都有此操做,否則不叫做旋轉式,像fhqtreap那種非旋轉式BST則沒有該操做。網上大部分將旋轉分爲兩個,ZIG與ZAG

感受這張圖一點都不清楚。。。實際上是我懶得畫一張了
左邊到右邊的是ZIG(x),右邊到左邊是ZAG(y)
ZIG和ZAG的結合也有幾種狀況


你發現它們這樣轉來轉去,這棵樹依然知足BST性質的,並且上圖ZIG-ZAG操做中,還減小了樹的高度,因此旋轉式BST就是基於ZIG,ZAG以及組合操做,經過不斷旋轉自身來保證其樹高,使得其很是優美spa

可是,寫4個旋轉實在是太麻煩了,因而咱們將其簡化爲一個函數3d

首先咱們須要明白,splay的旋轉操做只會影響到3個點

將x旋到根以後,其父親和其兒子會如上圖般變化,其餘點都不會受到影響。那麼這個旋轉如何用一個函數來實現呢?code

#define ls(x) tree[x][0]
#define rs(x) tree[x][1]
#define T(x) (rs(f[x])==x)
void move(int x){
    int fa=f[x],son=tree[x][T(x)^1];
    tree[x][T(x)^1]=fa;
    tree[fa][T(x)]=son;
    if (son)    f[son]=fa;
    f[x]=f[fa];
    if (f[x])   tree[f[x]][T(fa)]=x;
    f[fa]=x;
}

首先記錄一下當前點的父親,和其拐角後的兒子節點(先無論有沒有父親和兒子),而後將x的兒子改爲fa,把fa的兒子改爲son(記得改爲拐角狀)。blog

而後判斷son是否存在,若存在,則f[son]=fa。

將x的父親指向fa的父親,無論fa的父親是否存在(f[x]爲零也能夠)。

而後判斷是否真正有f[x](連邊以後的父親),若是有,那麼將f[x]的兒子指向x,因爲此時f[x]和fa的關係未斷,所以能夠直接用T(fa),最後將fa的父親指向x便可。


而後咱們來看個有意思的東西

如今咱們要把\(x\)旋到根上去,而後按照以前的旋轉方法就有

而後咱們把圖中的一條鏈標記出來

也就是說你只對着\(x\)旋轉的話,頗有可能被卡,因此咱們就要進行一些優化,咱們在旋轉的時候進行討論,若是x-y-z在同一直線上,咱們就先旋y再旋x,不然直接旋x,這個方法就是雙旋,代碼實現也很是簡單

通常來說,單旋速度優於雙旋,但容易被卡(你能夠試着畫一跳長鏈,而後分別用雙旋和單旋將鏈底的點旋上來,而後看一下樹的形態)

void splay(int x){
    while (f[x]){
        if (f[f[x]])    T(x)==T(f[x])?move(f[x]):move(x);
        move(x);
    }
    root=x;
}

Attention:move函數中的#define,對於以後的函數一直有效


2.插入
插入一個點的時候,從根節點找起,看要插入的點是要插在當前點的左邊仍是右邊,若是要插在某一邊且那一邊恰好空着,就直接加進去便可,不然繼續找。最後把插入的點splay到根,維護平衡

void insert(int x){
    val[++len]=x;
    if (!root){
        size[root=len]=1;
        return;
    }
    int i=root;
    while (true){
        size[i]++;
        if (x<=val[i]){
            if (!ls(i)){f[ls(i)=len]=i;break;}
            i=ls(i);
        }else{
            if (!rs(i)){f[rs(i)=len]=i;break;}
            i=rs(i);
        }
    }
    splay(len);
}

3.找前驅/後繼
因爲splay是一棵BST,所以找前驅的話,咱們只須要找root的左兒子中,最右邊的葉子節點;後繼同理

int get_pre(){
    int x=ls(root);
    while (rs(x))   x=rs(x);
    return x;
}
int get_suc(){
    int x=rs(root);
    while (ls(x))   x=ls(x);
    return x;
}

4.查詢
因爲splay是棵二叉樹,記錄一下size以後即可以很容易找到排第k個的數是誰了

int find(int x,int i){
    if (!i) return 0;
    if (size[ls(i)]+1==x)   return i;
    if (x<=size[ls(i)])  return find(x,ls(i));
    return find(x-size[ls(i)]-1,rs(i));
}

5.刪除
在經過某些特殊的方法獲得須要刪除的點的編號後(特殊方法什麼的根據題意來),現將該點splay到根,若是左右兒子有一個空了,那麼直接將那個沒空的挪上來就好;不然就將其前驅/後繼旋上來,而後判斷一下x是new_root的左兒子仍是右兒子,將x相應方向的兒子和new_root創建新的關係便可

void Delete(int x){
    splay(x);
    if (!(ls(x)&&rs(x))){
        f[root=ls(x)+rs(x)]=0;
        clear(x);
        return;
    }
    int i=get_pre();
    splay(i);
    size[f[rs(i)=rs(x)]=i]--;
    clear(x);
}

6.區間操做
多了區間操做的splay,爲了防止越界,咱們能夠在最前面和最後面加上兩個不動點,每次須要對區間[l,r]進行修改時,咱們先把編號爲l的點splay到根,再把編號爲r+2的點splay到根,在旋的時候,l可能會變成r+2的孫子,咱們把l單旋一下便可。這樣子的話,l的左兒子就爲咱們須要求的那段區間了。

雙旋的時候,2可能會變成5的孫子,那麼咱們把2單旋一下便可


splay的基本操做也就這些,下面咱們來說講一個例題

例題

Tyvj 1728 普通平衡樹
Description
您須要寫一種數據結構(可參考題目標題),來維護一些數,其中須要提供如下操做:

  1. 插入x數
  2. 刪除x數(如有多個相同的數,因只刪除一個)
  3. 查詢x數的排名(如有多個相同的數,因輸出最小的排名)
  4. 查詢排名爲x的數
  5. 求x的前驅(前驅定義爲小於x,且最大的數)
  6. 求x的後繼(後繼定義爲大於x,且最小的數)

Input
第一行爲n,表示操做的個數,下面n行每行有兩個數opt和x,opt表示操做的序號(1<=opt<=6)

Output
對於操做3,4,5,6每行輸出一個數,表示對應答案

Sample Input
10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598

Sample Output
106465
84185
492737

HINT
1.n的數據範圍:n<=100000
2.每一個數的數據範圍:[-2e9,2e9]

splay經典板子題,用到以前說的全部操做。刪點的話,先找排名,而後找點刪除。至於查詢排名,因爲BST的優美性質,因此判斷一下往左右遞歸便可

/*program from Wolfycz*/
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
typedef unsigned int ui;
typedef unsigned long long ull;
inline int read(){
    int x=0,f=1;char ch=getchar();
    for (;ch<'0'||ch>'9';ch=getchar())  if (ch=='-')    f=-1;
    for (;ch>='0'&&ch<='9';ch=getchar())  x=(x<<3)+(x<<1)+ch-'0';
    return x*f;
}
inline void write(int x){
    if (x>=10)   write(x/10);
    putchar(x%10+'0');
}
const int N=1e5;
struct Splay{
    #define ls(x) tree[x][0]
    #define rs(x) tree[x][1]
    #define T(x) (rs(f[x])==x)
    int tree[N+10][2],f[N+10],size[N+10],val[N+10],root,len;
    void updata(int x){size[x]=size[ls(x)]+size[rs(x)]+1;}
    void clear(int x){f[x]=size[x]=ls(x)=rs(x)=0;}
    void move(int x){
        int fa=f[x],son=tree[x][T(x)^1];
        tree[x][T(x)^1]=fa;
        tree[fa][T(x)]=son;
        if (son)    f[son]=fa;
        f[x]=f[fa];
        if (f[x])   tree[f[x]][T(fa)]=x;
        f[fa]=x;
        updata(fa),updata(x);
    }
    void splay(int x){
        while (f[x]){
            if (f[f[x]])    T(x)==T(f[x])?move(f[x]):move(x);
            move(x);
        }
        root=x;
    }
    int get_pre(){
        int x=ls(root);
        while (rs(x))   x=rs(x);
        return x;
    }
    int get_suc(){
        int x=rs(root);
        while (ls(x))   x=ls(x);
        return x;
    }
    void Delete(int x){
        splay(x);
        if (!(ls(x)&&rs(x))){
            f[root=ls(x)+rs(x)]=0;
            clear(x);
            return;
        }
        int i=get_pre();
        splay(i);
        size[f[rs(i)=rs(x)]=i]--;
        clear(x);
    }
    void insert(int x){
        val[++len]=x;
        if (!root){
            size[root=len]=1;
            return;
        }
        int i=root;
        while (true){
            size[i]++;
            if (x<=val[i]){
                if (!ls(i)){f[ls(i)=len]=i;break;}
                i=ls(i);
            }else{
                if (!rs(i)){f[rs(i)=len]=i;break;}
                i=rs(i);
            }
        }
        splay(len);
    }
    int find(int x,int i){
        if (!i) return 0;
        if (size[ls(i)]+1==x)   return i;
        if (x<=size[ls(i)])  return find(x,ls(i));
        return find(x-size[ls(i)]-1,rs(i));
    }
    int get_rank(int x){
        int res=0;
        for (int i=root;i;) val[i]<x?res+=size[ls(i)]+1,i=rs(i):i=ls(i);
        return res+1;
    }
    void DELETE(int x){Delete(find(get_rank(x),root));}
    void GETRANK(int x){printf("%d\n",get_rank(x));}
    void RANK_GET(int x){printf("%d\n",val[find(x,root)]);}
    void GET_PRE(int x){printf("%d\n",val[find(get_rank(x)-1,root)]);}
    void GET_SUC(int x){printf("%d\n",val[find(get_rank(x+1),root)]);}
}Tree;
int main(){
    int n=read();
    for (int i=1;i<=n;i++){
        int flag=read(),x=read();
        if (flag==1)    Tree.insert(x);
        if (flag==2)    Tree.DELETE(x);
        if (flag==3)    Tree.GETRANK(x);
        if (flag==4)    Tree.RANK_GET(x);
        if (flag==5)    Tree.GET_PRE(x);
        if (flag==6)    Tree.GET_SUC(x);
    }
    return 0;
}
相關文章
相關標籤/搜索