[Note]Splay学习笔记

开个坑记录一下学习Splay的历程。

模板

板子太长就放在文末了,此处感谢各位发过Splay博客的大佬。

代码分块解释:

节点

struct node {
    int val, f, ch[2], sz, cnt;
} a[N];

节点的定义与普通的二叉搜索树相同,均存储了该节点代表的权值val和出现次数cnt,父节点f,子节点ch[2],子树大小sz

基础

int n, tot;
//int &root = a[0].ch[1];

void update(int x) { 
    a[x].sz = a[a[x].ch[0]].sz + a[a[x].ch[1]].sz + a[x].cnt;
}

int son(int x) {
    return a[a[x].f].ch[1] == x;
}

inline int getroot() { return a[0].ch[1]; }

定义了更新节点子树大小的函数update,判断是否是右儿子的函数son,节点总数tot,空间使用量n,同时约定根为0号节点右儿子(此思想来自@rentenglong),并且写了一个获得根的编号的函数getroot

int crepoint(int v, int fa) {
    n++;
    a[n].val = v;
    a[n].f = fa;
    a[n].sz = a[n].cnt = 1;
    return n;
}

void destroy(int x) {
    a[x].val = a[x].ch[0] = a[x].ch[1] = a[x].sz = a[x].cnt = a[x].f = 0;
    if (x == n) n--;
}

这是为一个节点申请空间和删除一个节点的代码。(更优秀的做法是使用内存池,可是谁会去写呢)

Splay操作

Splay操作即将一个点旋转到根(或其他在该节点和根中间的节点)的位置,同时尽量维持树的平衡。

Splay基于更改节点x和其父亲的操作rotate:

void connect(int x, int f, int d) { 
    a[x].f = f;     // 将x的父亲设为f
    a[f].ch[d] = x; // 将f的d儿子设为x
}

void rotate(int x) { // 儿子变爸爸操作
    int y = a[x].f; // 爸爸
    int mroot = a[y].f; // 爷爷
    int mrootson = son(y), yson = son(x); // 是左儿子还是右儿子
    int B = a[x].ch[yson^1]; 
    connect(B, y, yson);  // 自己的儿子变为爸爸的儿子
    connect(y, x, (yson^1));  // 爸爸变为自己的儿子
    connect(x, mroot, mrootson); // 自己变为爷爷的儿子
    update(y); update(x); // 更新信息
}

emmm,注释写的很详细了虽然有点乱伦,具体内容自己手绘一下即可,注意这个操作并未改变中序遍历。

喜闻乐见的Splay操作:

void splay(int at, int to) {
    to = a[to].f;
    while (a[at].f != to) {
        int up = a[at].f;
        if (a[up].f == to) rotate(at);
        else if (son(up) == son(at)) { // 父子同向
            rotate(up);
            rotate(at);
        } else { // 父子异向
            rotate(at); 
            rotate(at);
        }
    }
}

这是双旋splay(?),理论上复杂度十分优秀,但是好像是可以被卡的较慢的(但是还是最优秀的Splay写法)。以及,无论什么操作,之后都应该添加Splay。

注:这里我尝试画图来说明Splay操作的优秀之处,可是我画了画发现Splay对于旋转的选择是个很迷的事情,有时候还会造成深度的增加,所以就不贴图啦。

对节点操作

查询一个节点是否存在

int find(int v) {
    int now = getroot();
    while (true) {
        if (a[now].val == v) {
            splay(now, getroot());
            return now;
        }
        int next = v < a[now].val ? 0 : 1;
        if (!a[now].ch[next]) return 0;
        now = a[now].ch[next];
    }
}

代码几乎与普通的搜索树相同,仅仅多了一个Splay操作。

插入节点

int build(int v) {
    tot++;
    if (tot == 1) { // mark
        a[0].ch[1] = crepoint(v, 0); 
    } else {
        int now = getroot();
        while (true) {
            a[now].sz++;
            if (v == a[now].val) {
                a[now].cnt++;
                return now;
            }
            int next = v < a[now].val ? 0 : 1;
            if (!a[now].ch[next]) {
                crepoint(v, now);
                a[now].ch[next] = n;
                return n;
            }
            now = a[now].ch[next];
        }
    }
    return 0;
}

void push(int v) {
    int add = build(v);
    splay(add, getroot());
}

mark处的特判请注意。

删除节点

先找到x,这时x已经是根了,再找到x的前驱(如果没有就可以直接删除x,因为x是根,没有前驱就没有左子树),旋转到根,由于前驱的在二叉树上的位置的特殊性(左儿子的最深右后代),Splay时最后一次一定是父子异向或是直接旋转,这时,x的就是根的右儿子,且x没有左子树(没有小于x且大于其前驱的数),直接删除x即可。

void pop(int v) {
    int deal = find(v);
    if (!deal) return;
    tot--;
    // 如果有多个v,修改计数器即可。
    if (a[deal].cnt > 1) {
        a[deal].cnt--;
        a[deal].sz--;
        return;
    }
    // 只有一个v
    if (!a[deal].ch[0]) { // 没有左子树,直接删除
        a[0].ch[1] = a[deal].ch[1];
        a[getroot()].f = 0;
    } else { // 有左子树
        int left = a[deal].ch[0]; 
        while (a[left].ch[1]) left = a[left].ch[1];
        splay(left, a[deal].ch[0]); // 前驱
        int right = a[deal].ch[1]; // 右子树
        connect(right, left, 1); connect(left, 0, 1);
        update(left);
    }
    destroy(deal);
}

查询一个值的排名和一个排名的值

同一般二叉树。

int rank(int v) {
    int ans = 0, now = getroot();
    while (true) {
        if (a[now].val == v) {
            ans += a[a[now].ch[0]].sz + 1;
            break;
        }
        if (now == 0) break;
        if (v < a[now].val) now = a[now].ch[0];
        else {
            ans += a[a[now].ch[0]].sz + a[now].cnt;
            now = a[now].ch[1];
        }
    }
    if (now) splay(now, getroot());
    return ans;
}

int atrank(int x) {
    if (x > tot) return -INF;
    int now = getroot();
    while (true) {
        int min = a[now].sz - a[a[now].ch[1]].sz;
        if (x > a[a[now].ch[0]].sz && x <= min) break;
        if (x < min) now = a[now].ch[0];
        else {
            x = x - min;
            now = a[now].ch[1];
        }
    }
    splay(now, getroot());
    return a[now].val;
}

查询前驱后继

同一般二叉树

int upper(int v) {
    int now = getroot();
    int res = INF;
    while (now) {
        if (a[now].val>v && a[now].val < res) res = a[now].val;
        if (v < a[now].val) now = a[now].ch[0];
        else now = a[now].ch[1];
    }
    splay(a[now].f, getroot()); // mark
    return res;
}

int lower(int v) {
    int now = getroot();
    int res = -INF;
    while (now) {
        if (a[now].val < v && a[now].val > res) res = a[now].val;
        if (v > a[now].val) now = a[now].ch[1];
        else now = a[now].ch[0];
    }
    splay(a[now].f, getroot());
    return res;
}

之后补play对序列操作。

Code

struct node {
    int val, f, ch[2], sz, cnt;
} a[N];
int n, tot;

void update(int x) {
    a[x].sz = a[a[x].ch[0]].sz + a[a[x].ch[1]].sz + a[x].cnt;
}

int son(int x) {
    return a[a[x].f].ch[1] == x;
}

void connect(int x, int f, int d) {
    a[x].f = f;
    a[f].ch[d] = x;
}

void rotate(int x) {
    int y = a[x].f;
    int mroot = a[y].f;
    int mrootson = son(y), yson = son(x);
    int B = a[x].ch[yson^1];
    connect(B, y, yson); connect(y, x, (yson^1)); connect(x, mroot, mrootson);
    update(y); update(x);
}

void splay(int at, int to) {
    to = a[to].f;
    while (a[at].f != to) {
        int up = a[at].f;
        if (a[up].f == to) rotate(at);
        else if (son(up) == son(at)) {
            rotate(up);
            rotate(at);
        } else {
            rotate(at); 
            rotate(at);
        }
    }
}

int crepoint(int v, int fa) {
    n++;
    a[n].val = v;
    a[n].f = fa;
    a[n].sz = a[n].cnt = 1;
    return n;
}

void destroy(int x) {
    a[x].val = a[x].ch[0] = a[x].ch[1] = a[x].sz = a[x].cnt = a[x].f = 0;
    if (x == n) n--;
}

inline int getroot() { return a[0].ch[1]; }

int find(int v) {
    int now = getroot();
    while (true) {
        if (a[now].val == v) {
            splay(now, getroot());
            return now;
        }
        int next = v < a[now].val ? 0 : 1;
        if (!a[now].ch[next]) return 0;
        now = a[now].ch[next];
    }
}

int build(int v) {
    tot++;
    if (tot == 1) {
        a[0].ch[1] = crepoint(v, 0); // mark
    } else {
        int now = getroot();
        while (true) {
            a[now].sz++;
            if (v == a[now].val) {
                a[now].cnt++;
                return now;
            }
            int next = v < a[now].val ? 0 : 1;
            if (!a[now].ch[next]) {
                crepoint(v, now);
                a[now].ch[next] = n;
                return n;
            }
            now = a[now].ch[next];
        }
    }
    return 0;
}

void push(int v) {
    int add = build(v);
    splay(add, getroot());
}

void pop(int v) {
    int deal = find(v);
    if (!deal) return;
    tot--;
    if (a[deal].cnt > 1) {
        a[deal].cnt--;
        a[deal].sz--;
        return;
    }
    if (!a[deal].ch[0]) {
        a[0].ch[1] = a[deal].ch[1];
        a[getroot()].f = 0;
    } else {
        int left = a[deal].ch[0];
        while (a[left].ch[1]) left = a[left].ch[1];
        splay(left, a[deal].ch[0]);
        int right = a[deal].ch[1];
        connect(right, left, 1); connect(left, 0, 1);
        update(left);
    }
    destroy(deal);
}

int rank(int v) {
    int ans = 0, now = getroot();
    while (true) {
        if (a[now].val == v) {
            ans += a[a[now].ch[0]].sz + 1;
            break;
        }
        if (now == 0) break;
        if (v < a[now].val) now = a[now].ch[0];
        else {
            ans += a[a[now].ch[0]].sz + a[now].cnt;
            now = a[now].ch[1];
        }
    }
    if (now) splay(now, getroot());
    return ans;
}

int atrank(int x) {
    if (x > tot) return -INF;
    int now = getroot();
    while (true) {
        int min = a[now].sz - a[a[now].ch[1]].sz;
        if (x > a[a[now].ch[0]].sz && x <= min) break;
        if (x < min) now = a[now].ch[0];
        else {
            x = x - min;
            now = a[now].ch[1];
        }
    }
    splay(now, getroot());
    return a[now].val;
}

int upper(int v) {
    int now = getroot();
    int res = INF;
    while (now) {
        if (a[now].val>v && a[now].val < res) res = a[now].val;
        if (v < a[now].val) now = a[now].ch[0];
        else now = a[now].ch[1];
    }
    splay(a[now].f, getroot()); // mark
    return res;
}

int lower(int v) {
    int now = getroot();
    int res = -INF;
    while (now) {
        if (a[now].val < v && a[now].val > res) res = a[now].val;
        if (v > a[now].val) now = a[now].ch[1];
        else now = a[now].ch[0];
    }
    splay(a[now].f, getroot());
    return res;
}

长度约4k(极限压缩版约2k),功能较齐全。

猜你喜欢

转载自www.cnblogs.com/wyxwyx/p/splay.html
今日推荐