Splay學習筆記


Splay簡述&結構

BST -- 二叉查找樹,任意左兒子 < 父親節點 , 任意右兒子 > 父親節點(對每個節點都符合

Splay -- 是一種BST,它通過不斷將某個節點旋轉到根節點,使得整棵樹仍然滿足二叉查找樹的性質,並且保持平衡而不至於退化為鏈,它由 Daniel Sleator 和 Robert Tarjan 發明,Tarjan np!!

維護的信息

\(rt\) \(b\) \(f[i]\) \(o[i][0/1]\) \(v[i]\) \(u[i]\) \(sz[i]\)
根節點編號 節點個數 父親 左/右兒子編號 節點權值 權值出現次數 子樹大小

因為存在 左子樹任意節點的值 < 根節點的值 < 右子樹任意節點的值 的性質,我們能從這棵樹上查找某個值

Various operations

the basic

  • \(maintain(p)\) : 改變節點位置后更新節點的\(size\)
  • \(get(p)\) : 判斷某節點為左兒子還是右兒子
  • \(clear(p)\) : 銷毀節點
void mtn(int p) { sz[p] = sz[o[p][0]] + sz[o[p][1]] + u[p]; }
bool get(int p) { return p == o[f[p]][1]; }
void clr(int p) { o[p][0] = o[p][1] = f[p] = v[p] = sz[p] = u[p] = 0; }

rotate

旋轉操作>>>>

以左圖到右圖為例,現在要將p節點向上移一個位置,將1-p-fp-3這條鏈往右移一個位置,2的父親由右上的p換為左上的fp(旋轉之后的結果),,過程中我們要維護這棵樹BST的性質,

過程:

  1. fp 為 p 的父親,ffp 為 fp 的父親,oo 表示 p 為左兒子還是右兒子
  2. 讓 fp 的兒子 o[fp][oo]變為 2 o[p][oo ^ 1],讓 2 f[o[p][oo ^ 1]] 的父親變為 fp
  3. 讓 p 的兒子 o[p][oo ^ 1] 變為 fp ,讓 fp 的父親 f[fp] 變為 p
  4. 如果存在 fp 的父親 ffp(fp不是根節點),讓 ffp 的兒子 o[ffp][get[fp]] 變為 p,p 的父親變為 ffp ,其實不存在父親的話也沒關系反正也是0

img \(\lll-相互-\ggg\) img

void rotate(int p) {
    int fp = f[p], ffp = f[fp], oo = get(p);
    o[fp][oo] = o[p][oo ^ 1];
    f[o[p][oo ^ 1]] = fp;
    o[p][oo ^ 1] = fp;
    f[fp] = p, f[p] = ffp;
    if(ffp) o[ffp][fp == o[ffp][1]] = p;
    mtn(p), mtn(fp);
}

Splay

每訪問到一個節點都強制把它旋轉到根節點 ,分6種情況,其中 x 是要被旋轉到根節點的節點

imgimgimgimgimgimg

過程:

  1. 圖1&2:x的父親是根節點,直接將x左/右旋
  2. 圖3&4:x和他父親都是左/右兒子,先旋父親再旋x
  3. 圖5&6:x和他父親不是同一種兒子,x先右/旋再左/右旋
void splay(int p) {
    for(int fp = f[p]; fp = f[p], fp; rotate(p))
        if (f[fp]) rotate(get(p) == get(fp) ? fp : p);
    rt = p;
}

不明白手跑一遍就好了

insert

插入一個節點

過程:

  1. 若樹空則直接插入然后結束
  2. 如果當前節點權值等於要插入的權值,就增加當前節點大小並更新節點和父親的信息,並將當前節點Splay
  3. 否則按BST的性質向下找,找到一個空節點插入就行了,記得Splay一下
void ins(int p) {
    if(!rt) { v[++ b] = p; u[b] ++; rt = b; mtn(rt); return; }
    int e = rt, c = 0;
    while(1) {
        if(v[e] == p) { u[e]++; mtn(e); mtn(c); splay(e); break; }
        c = e; e = o[e][v[e] < p];
        if(!e) {
      	    v[++ b] = p; u[b] ++;
        	f[b] = c; o[c][v[c] < p] = b;
        	mtn(b), mtn(c); splay(b);
        	break;
        }
    }
}

p's rank

查詢 p 的排名 您老幾?

過程:

  1. 如果 p 比當前節點權值小,就去左子樹找
  2. 否則將答案加上左子樹大小和當前節點大小,再向右子樹找
  3. 如果x與當前節點權值相同,答案+1並return
  4. 別忘splay?
int rk(int p) { 
    int e = rt, c = 0;
    while(1) {
        if(p < v[e]) e = o[e][0];
        else {
        	c += sz[o[e][0]];
        	if(p == v[e]) { splay(e); return c + 1; }
        	c += u[e]; e = o[e][1];
        }
    }
}

node ranked p

查詢排名為 p 的數

過程:

  1. 如果左子樹非空且剩余排名 p 不大於左子樹大小,就去左子樹找
  2. 否則將 p 減去左子樹和根的大小,若此時 p 的值 <= 0,則返回根節點的值,不然就接着向右子樹找
int kth(int p) {
    int e = rt;
    while(1) {
        if(o[e][0] and p <= sz[o[e][0]]) e = o[e][0];
        else {
        	p -= u[e] + sz[o[e][0]];
        if (p <= 0) { splay(e); return v[e]; }
        e = o[e][1];
      }
    }
}

precursor

查詢節點 p 的前驅

我們可以把 p 插入,那前驅就是 p 左子樹中最右邊的節點,再將 p 消除

int pre() {
    int e = o[rt][0];
    while (o[e][1]) e = o[e][1];
    splay(e);
    return e;
}

next

查詢 p 節點的后繼

也是先插入 p ,然后找到 p 的右子樹最左邊的節點,然后銷毀 p

int nxt() {
    int e = o[rt][1];
    while (o[e][0]) e = o[e][0];
    splay(e);
    return e;
}

merge two tree

合並兩棵splay,將其中一棵連根拔起然后種到另一棵旁邊就可以了, 真的是十分方便呢 設兩樹分別為 A 樹和 B 樹,現在要求A樹中最大值 <B樹中最小值

過程:

  1. 若有一棵空樹或都是空樹,直接返回不空樹的根節點或隨便一棵空樹
  2. 否則將A樹的最大值splay到根,將他的右子樹設為B並更新節點信息,然后返回這個節點

delete

刪除一個節點 p

過程:

  1. 將 p 旋到根節點的位置
  2. cnt[p]>1 ,也就是有很多個 p,就將 cnt[p] -= 1; return;
  3. 否則合並它的兩棵子樹
void del(int p) {
    rk(p);
    if(u[rt] > 1) { u[rt]--; mtn(rt); return; }
    if(!o[rt][0] and !o[rt][1]) { clr(rt); rt = 0; return; }
    if(!o[rt][0]) { int e = rt; rt = o[rt][1]; f[rt] = 0; clr(e); return; }
    if(!o[rt][1]) { int e = rt; rt = o[rt][0]; f[rt] = 0; clr(e); return; }
    int e = rt, c = pre();
    splay(c);
    f[o[e][1]] = c;
    o[c][1] = o[e][1];
    clr(e), mtn(rt);
}

沒啦

放上完整代碼

#include <cstdio>
using namespace std;
const int N = 100005;
int rt, b, f[N], o[N][2], v[N], u[N], sz[N];

void mtn(int p) { sz[p] = sz[o[p][0]] + sz[o[p][1]] + u[p]; }
bool get(int p) { return p == o[f[p]][1]; }
void clr(int p) { o[p][0] = o[p][1] = f[p] = v[p] = sz[p] = u[p] = 0; }

void rotate(int p) {
    int fp = f[p], ffp = f[fp], oo = get(p);
    o[fp][oo] = o[p][oo ^ 1];
    f[o[p][oo ^ 1]] = fp;
    o[p][oo ^ 1] = fp;
    f[fp] = p, f[p] = ffp;
    if(ffp) o[ffp][fp == o[ffp][1]] = p;
    mtn(p), mtn(fp);
}

void splay(int p) {
    for(int fp = f[p]; fp = f[p], fp; rotate(p))
        if (f[fp]) rotate(get(p) == get(fp) ? fp : p);
    rt = p;
}

void ins(int p) {
    if(!rt) { v[++ b] = p; u[b] ++; rt = b; mtn(rt); return; }
    int e = rt, c = 0;
    while(1) {
        if(v[e] == p) { u[e]++; mtn(e); mtn(c); splay(e); break; }
        c = e; e = o[e][v[e] < p];
        if(!e) {
      	    v[++ b] = p; u[b] ++;
        	f[b] = c; o[c][v[c] < p] = b;
        	mtn(b), mtn(c); splay(b);
        	break;
        }
    }
}

int rk(int p) { 
    int e = rt, c = 0;
    while(1) {
        if(p < v[e]) e = o[e][0];
        else {
        	c += sz[o[e][0]];
        	if(p == v[e]) { splay(e); return c + 1; }
        	c += u[e]; e = o[e][1];
        }
    }
}

int kth(int p) {
    int e = rt;
    while(1) {
        if(o[e][0] and p <= sz[o[e][0]]) e = o[e][0];
        else {
        	p -= u[e] + sz[o[e][0]];
        if (p <= 0) { splay(e); return v[e]; }
        e = o[e][1];
      }
    }
}

int pre() {
    int e = o[rt][0];
    while (o[e][1]) e = o[e][1];
    splay(e);
    return e;
}

int nxt() {
    int e = o[rt][1];
    while (o[e][0]) e = o[e][0];
    splay(e);
    return e;
}

void del(int p) {
    rk(p);
    if(u[rt] > 1) { u[rt]--; mtn(rt); return; }
    if(!o[rt][0] and !o[rt][1]) { clr(rt); rt = 0; return; }
    if(!o[rt][0]) { int e = rt; rt = o[rt][1]; f[rt] = 0; clr(e); return; }
    if(!o[rt][1]) { int e = rt; rt = o[rt][0]; f[rt] = 0; clr(e); return; }
    int e = rt, c = pre();
    splay(c);
    f[o[e][1]] = c;
    o[c][1] = o[e][1];
    clr(e), mtn(rt);
}

int main() {
    int n, op, p;
    for(scanf("%d", &n); n; --n) {
		scanf("%d%d", &op, &p);
    	if(op == 1) ins(p);
    	if(op == 2) del(p);
    	if(op == 3) printf("%d\n", rk(p));
    	if(op == 4) printf("%d\n", kth(p));
    	if(op == 5) ins(p), printf("%d\n", v[pre()]), del(p);
    	if(op == 6) ins(p), printf("%d\n", v[nxt()]), del(p);
    }
    return 0;
}

E·N·D

還在繼續研究,沒更完


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM