【模板篇】splay(填坑)+模板題(普通平衡樹)

划着划着水一不小心NOIP還考的湊合了…
所以退役的打算要稍微擱置一下了…
要準備準備省選了….
但是自己已經啥也不會了…
所以只能重新拾起來…
從splay開始吧…

splay我以前扔了個板子來着, 之前理解的還是不夠深入於是就一直沒有填坑…
現在又重新拾了一下就來填坑…
不過講平衡樹要畫好多圖的說OvO

BST(二叉查找樹)大家應該都知道吧, 所以就不講了…
大家也都知道BST會被卡成一條鏈, 複雜度動不動就退化成O(n)
所以就出現了各種各樣的二叉平衡樹…

splay是個很年輕的數據結構…好像是什麼1985年由tarjan提出的…
splay的主要特點就是不怎麼平衡, 但是一直在轉所以深度並不會太深…
而且會將最近訪問的條目放在根的附近, 這樣重複訪問的時候效率會加快..
(聽上去好像挺有用, 但實際生活更多用的還是RBT之類的吧?)

splay的主要操作就是rotate和splay(招牌)的說…
其他的操作就是BST的操作了Emmmm…
我們一個一個講:

rotate

rotate跟很多其他什麼二叉平衡樹是一樣的…
splay的旋轉
由題意, 如圖示, 儘管箭頭畫的醜的一筆大小都不一樣但是湊合着看吧… 旋轉完還是保持二叉排序樹性質的~
我們來分析一下旋轉的過程
假如我們要旋轉4號節點(下面稱爲x ), 就是上面左圖箭頭指的那個節點..
因爲他是左兒子, 所以要右旋(有些人喜歡分左右旋, 但是你把左兒子左旋是幾個意思←_←)
然後就是中圖紅色和綠色的拆邊和建邊:
說明好麻煩啊, 上僞代碼吧:

if grandfa->ch[0]==fa fa_dir=0 else fa_dir=1 //確定父親是祖父的哪個兒子
grandfa->ch[fa_dir]=x //將這個兒子置爲x
if fa->ch[0]==x dir=0 else dir=1 //確定x是父親的哪個兒子
fa->ch[dir]=x->ch[dir^1] //將x父親的dir兒子置爲x的dir另一側的兒子 (爲了保持性質,這很顯然)
x->ch[dir^1]=fa //將x變爲x原來父親的父親(沒大沒小..) (爲了保持性質,要放在dir的對側)
//當然這裏要更新所有改變父子關係中所有的父親咯~

以上幾步如果不亂的話應該順序是無所謂的吧…(不過還是形成一個正確的板子背過的好, 不然老是掛(這幾天都不知道把rotate裏面的句子調了多少遍了))
然後我們就能整理出這樣的代碼:

void rotat(node *now){ //這裏強行去掉一個e是防止與STL衝突(雖然好像無所謂??)
    int wh=now->getwh(); node *fa=now->fa,*fafa=fa->fa;
    if(fafa!=null) fafa->ch[fa->getwh()]=now;
    fa->setch(wh,now->ch[wh^1]);    
    now->setch(wh^1,fa); //這裏的setch是包括設置兒子和父親的
    now->fa=fafa;
}

rotate就是這樣…
其實自己多畫一畫就比較方便理解…

splay

splay之所以叫splay是因爲splay有個叫splay的操作..(繞口令)
我們說過, splay會把最近訪問的點轉到根上…(其實就是伸個懶腰….把這個點扭上去)
這裏的splay操作有一種很顯然的做法: 就是不停的rotate
這種操作叫做單旋(敲黑板) 這麼顯然的做法顯然是會被卡的…比如轉着轉着就特別特別不平衡了什麼的…
(喪心病狂的出題人可能會先問這個端點, 再問那個端點, 就炸了…)
這麼寫的做法我們親切地稱爲spaly(來找不同啊)
所以我們爲了保持比較平衡的splay(其實仍然歪七扭八的…) 我們要雙旋(又敲黑板)
Emmmm
雙旋的過程, 我們要判斷x和x的父親是不是同向的(即都是自己父親的左兒子or右兒子)
- 如果是同向的, 就要先轉父親再轉x
- 否則轉2遍x…
其實爲什麼這樣做還是挺明顯的, 可以自己畫一下rotate的過程看看不同的順序會對平衡產生什麼差異(纔不是懶得畫呢╭(╯^╰)╮)
由於rotate已經寫好了, 我們的splay就呼之欲出了

//將節點now轉到tar的下一位上(Emmmm..這個地方劃重點,不是轉到tar上哦~) 若tar爲null則表示轉到根上
void splay(node *now,node *tar){
    for(;now->fa!=tar;rotat(now)) //因爲不是轉到tar上所以是fa!=tar...
        if(now->fa->fa!=tar) //防止雙旋轉過,如果只轉一下就到那就不雙旋了
            //反正雙旋的第二步都是轉我們就只轉第一步就好了(因爲第二步被壓到for循環裏去了2333)
            now->getwh()==now->fa->getwh()?rotat(now->fa):rotat(now);
    if(tar==null) rt=now; //轉到根上就要重置根...
}

反正就是這樣… 剩下的就是bst的操作了OvO
用其他的什麼樹也都能搞定了OvO

例題的話我們就用普通平衡樹吧OvO
我們來逐個分析操作:
1.插入一個數x
這不是bst操作麼… 用從根開始找到該插入的位置插入即可…

void insert(int val){
    node *last = null, *now = rt, *nnow = NEW(); //NEW是自定義函數(不太會重載new)
    nnow->val = val; nnow->cnt = nnow->sz = 1; //申請新節點
    while (now != null){
        last = now; //last存儲父親
        if (nnow->val == now->val){ //如果樹上已經有了數值相同的點
            now->cnt++; now->sz++; //直接修改cnt和size就行了
            splay(now, null); return; //把最近訪問的節點旋到根上
        }
        if (nnow->val<now->val) now = now->ch[0]; //如果比當前節點值小顯然要往左走
        else now = now->ch[1]; //否則往右走
    }
    if (last == now) rt = nnow; //如果沒有根當前點就是根
    else if (nnow->val<last->val) last->setch(0, nnow); //如果小於最後走到的位置就設爲左葉子
    else last->setch(1, nnow); //否則右葉子
    splay(nnow, null); //轉到根上
}

2.刪除數x
在此之前我們要先找到x是不是OvO…
所以……bst的查找!!!

node *find(int val){
    node *now = rt;
    while (now != null){
        if (now->val == val) break; //找到了
        if (now->val<val) now = now->ch[1]; //當前節點小於要查詢的值 往右走 
        else now = now->ch[0]; //否則往左走
    }
    if (now != null) splay(now, null); //找到了就轉到根上
    return now;
}

找到這個點的位置之後刪掉就完了OvO.. 不過要分類討論(注意這裏並沒有回收內存所以並不是真的刪掉了OvO 如果開始的時候裏面有點那要池子要開雙倍的)

void delet(int val){ //不要問我爲啥去個e... delete是關鍵字啊= =
    node *tar = find(val); //找到的時候就旋到根上了 這就很和善..
    if (tar == null) return; //沒找到刪個毛線啊
    if (tar->cnt>1){
        tar->cnt--; tar->sz--; return; //如果是重複的減去一個就行了很省事..
    }

    //無聊的分類討論
    if (tar->ch[0] == null&&tar->ch[1] == null)
        rt = null; //如果這就是根 刪了就完了
    else if (tar->ch[0] == null)
        tar->ch[1]->fa = null, rt = tar->ch[1]; //如果沒有左子樹,直接將右兒子作爲根
    else if (tar->ch[1] == null)
        tar->ch[0]->fa = null, rt = tar->ch[0]; //如果沒有右子樹,直接將左兒子作爲根
    else{ //左右兒子都有是最麻煩 但是最常見的..
        node *rch = tar->ch[0];
        while (rch->ch[1] != null) rch = rch->ch[1]; //我們要找到左兒子中最右的兒子來當新根...
        splay(rch, null); //把這個點轉到根上
        rch->setch(1, tar->ch[1]); //把右兒子接到上面
        rch->fa = null; rt = rch; //更新一下新根信息
    }
}

3.查詢x數的排名
這個嘛= = 我們要維護每個點子樹的大小…
我們寫一個update函數…

void node::update(){
    sz=ch[0]->sz+ch[1]->sz+cnt; //左子樹大小+右子樹大小+該數值的個數
}

只要改變樹形態(修改兒子的時候)調用就好了…(常數在哭泣…)
查排名的時候就可以:

int rank(int val){
    int ls = 0; //記錄比走到當前節點前一定更小的數的個數..
    node *now = rt;
    while (now != null){
        if (now->val == val){
            //找到這個值就把小的數的個數和他左子樹的大小+1作爲名次..
            int ans = ls + now->ch[0]->sz + 1;
            //把當前查詢的點轉到根上..
            splay(now, null);
            return ans;
        }
        //還是比較大小然後分往左右走..
        if (now->val<val) ls += now->ch[0]->sz + now->cnt, now = now->ch[1];
        else now = now->ch[0];
    }
    return -1;
}

4.查排名爲x的數
不想寫文字說明了OvO

int pos(int k){
    int ls = 0; node *now = rt;
    while (now != null){
        int po = ls + now->ch[0]->sz;
        if (po + 1 <= k&&k <= po + now->cnt){
            splay(now, null); return now->val; //如果當前點符合要求就是他了(記得轉到根上)
        }
        //還是判斷左右...
        if (po<k) ls = po + now->cnt, now = now->ch[1];
        else now = now->ch[0];
    }
    return -1;
}

5.查詢前驅(後繼一樣我就一起寫了)

int pre(int val){
    int ans = -INF; node *now = rt;
    while (now != null){
        //一路左右找就行...反正相等也不滿足條件...
        if (now->val<val)
            ans = max(ans, now->val), now = now->ch[1];
        else now = now->ch[0];
    }
    return ans;
}
int nxt(int val){
    int ans = INF; node *now = rt;
    while (now != null){
        if (now->val <= val) now = now->ch[1];
        else ans = min(ans, now->val), now = now->ch[0];
    }
    return ans;
}

就這樣咯~ 下面附上完整的代碼:

#include <cstdio>
const int N = 100010;
const int INF = ~0U >> 1;
inline int gn(int a = 0, char c = 0, int f = 1){
    for (; (c<'0' || c>'9') && c != '-'; c = getchar()); if (c == '-') c = getchar(), f = -1;
    for (; c >= '0'&&c <= '9'; c = getchar()) a = a * 10 + c - '0'; return a*f;
}
inline int max(const int &a, const int &b){ return a>b ? a : b; }
inline int min(const int &a, const int &b){ return a<b ? a : b; }
struct node{
    int sz, val, cnt;
    node *ch[2], *fa;
    void update();
    int getwh();
    void setch(int wh, node *child);
}*rt, *null, pool[N]; int tot = 0;
void node::update(){
    sz = ch[0]->sz + ch[1]->sz + cnt;
}
int node::getwh(){
    return fa->ch[0] == this ? 0 : 1;
}
void node::setch(int wh, node *child){
    ch[wh] = child;
    if (child != null) child->fa = this;
    update();
}
node *NEW(){
    node *now = pool + ++tot;
    now->sz = now->cnt = now->val = 0;
    now->ch[0] = now->ch[1] = now->fa = null;
    return now;
}
void init(){
    null = pool;
    null->ch[0] = null->ch[1] = null;
    null->cnt = null->sz = null->val = 0;
    rt = null;
}
void rotate(node *now){
    node *fa = now->fa, *fafa = fa->fa;
    int wh = now->getwh();
    if (fafa != null) fafa->ch[fa->getwh()] = now;
    fa->setch(wh, now->ch[wh ^ 1]);     
    now->setch(wh ^ 1, now->fa);
    now->fa = fafa;
}
void splay(node *now, node *tar){
    for (; now->fa != tar; rotate(now))
    if (now->fa->fa != tar)
        now->getwh() == now->fa->getwh() ? rotate(now->fa) : rotate(now);
    if (tar == null) rt = now;
}
node *find(int val){
    node *now = rt;
    while (now != null){
        if (now->val == val) break;
        if (now->val<val) now = now->ch[1];
        else now = now->ch[0];
    }
    if (now != null) splay(now, null);
    return now;
}
void insert(int val){
    node *last = null, *now = rt, *nnow = NEW();
    nnow->val = val; nnow->cnt = nnow->sz = 1;
    while (now != null){
        last = now;
        if (nnow->val == now->val){
            now->cnt++; now->sz++;
            splay(now, null); return;
        }
        if (nnow->val<now->val) now = now->ch[0];
        else now = now->ch[1];
    }
    if (last == now) rt = nnow;
    else if (nnow->val<last->val) last->setch(0, nnow);
    else last->setch(1, nnow);
    splay(nnow, null);
}
void delet(int val){
    node *tar = find(val);
    if (tar == null) return;
    if (tar->cnt>1){
        tar->cnt--; tar->sz--; return;
    }

    //無聊的分類討論
    if (tar->ch[0] == null&&tar->ch[1] == null)
        rt = null;
    else if (tar->ch[0] == null)
        tar->ch[1]->fa = null, rt = tar->ch[1];
    else if (tar->ch[1] == null)
        tar->ch[0]->fa = null, rt = tar->ch[0];
    else{
        node *rch = tar->ch[0];
        while (rch->ch[1] != null) rch = rch->ch[1];
        splay(rch, null);
        rch->setch(1, tar->ch[1]);
        rch->fa = null; rt = rch;
    }
}
int pre(int val){
    int ans = -INF; node *now = rt;
    while (now != null){
        if (now->val<val)
            ans = max(ans, now->val), now = now->ch[1];
        else now = now->ch[0];
    }
    return ans;
}
int nxt(int val){
    int ans = INF; node *now = rt;
    while (now != null){
        if (now->val <= val) now = now->ch[1];
        else ans = min(ans, now->val), now = now->ch[0];
    }
    return ans;
}
int rank(int val){
    int ls = 0; node *now = rt;
    while (now != null){
        if (now->val == val){
            int ans = ls + now->ch[0]->sz + 1; 
            splay(now, null);
            return ans;
        }
        if (now->val<val) ls += now->ch[0]->sz + now->cnt, now = now->ch[1];
        else now = now->ch[0];
    }
    return -1;
}
int pos(int k){
    int ls = 0; node *now = rt;
    while (now != null){
        int po = ls + now->ch[0]->sz;
        if (po + 1 <= k&&k <= po + now->cnt){
            splay(now, null); return now->val;
        }
        if (po<k) ls = po + now->cnt, now = now->ch[1];
        else now = now->ch[0];
    }
    return -1;
}
void debugtree(node *nod){
    if (nod->ch[0] != null) debugtree(nod->ch[0]);
    printf("%d\n", nod->val);
    if (nod->ch[1] != null) debugtree(nod->ch[1]);
}
int main(){
    init(); int n = gn();
    for (int i = 1; i <= n; ++i){
        int opt = gn(), x = gn();
        switch (opt){
        case 1:insert(x);break;
        case 2:delet(x); break;
        case 3:printf("%d\n", rank(x));  break;
        case 4:printf("%d\n", pos(x));   break;
        case 5:printf("%d\n", pre(x));   break;
        case 6:printf("%d\n", nxt(x));   break;
        }
        //puts("*********");
        //debugtree(rt);
    }
}

就這樣吧…
終於把自己挖的坑填上了…
自己挖的坑跪着也要填完。。(累覺不愛

發佈了72 篇原創文章 · 獲贊 17 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章