zoukankan      html  css  js  c++  java
  • 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
    ", rk(p));
        	if(op == 4) printf("%d
    ", kth(p));
        	if(op == 5) ins(p), printf("%d
    ", v[pre()]), del(p);
        	if(op == 6) ins(p), printf("%d
    ", v[nxt()]), del(p);
        }
        return 0;
    }
    

    E·N·D

    还在继续研究,没更完

    而我们终其一生,都希望能成为更好的人。
  • 相关阅读:
    const修饰指针
    C++调用C中编译过的函数要加extern "C"
    linux常用指令(1)
    链式队列实现
    存储类别和类型限定词
    数组,指针和引用
    字符函数和字符串函数
    C/C++编译的程序占用的内存
    结构体1(嵌套使用)
    输入输出函数小结
  • 原文地址:https://www.cnblogs.com/moziii/p/13406629.html
Copyright © 2011-2022 走看看