SBT 树原理和实战

一 基本概念

SBT(Size Balanced Tree,节点大小平衡树)是一种自平衡二叉查找树,通过子树的大小来保持平衡。与红黑树、AVL 树等自平衡二叉查找树相比,SBT更易于实现。SBT 可以在 O (logn) 时间内完成所有二叉搜索树的相关操作,与普通二叉搜索树相比,SBT 仅加入了简洁的核心操作 maintain。由于 SBT 保持平衡的是size 域而不是其他“无用”的域,所以可以方便地实现动态顺序统计中的第 k 小和排名操作。

对 SBT 的每个节点 T,节点 L 和 R 分别是节点 T 的左右儿子,子树 A、B、C 和 D 分别是节点 L 和 R 的左右子树。T 右子树的大小都大于或等于 T 左子树两个子节点的大小,size[R]≥size[A],size[R]≥size[B];T 左子树的大小都大于或等于 T 右子树两个子节点的大小,size[L] ≥ size[C],size[L] ≥ size[D]。也就是说,“叔叔”≥“侄子”。

SBT 树原理和实战_第1张图片

二 基础操作

1 右旋和左旋

SBT 的旋转也是以右旋和左旋为基础的,旋转也是 maintain 操作的基础。

(1)右旋。

x 右旋时,携带自己的右子节点向右旋转到 y 的右子树位置,y 的右子树被抛弃,x 右旋后左子树正好空闲,将 y 的右子树放在 x 的左子树上。更新 y 子树的大小等于 x 子树的大小,x 子树

的大小为其左右子树大小之和加 1。

SBT 树原理和实战_第2张图片

(2)左旋。

x 左旋时,携带自己的左子节点向左旋转到 y 的左子树位置,y 的左子树被抛弃,x 左旋后右子树正好空闲,将 y 的左子树放在 x 的右子树上。更新 y 子树的大小等于 x 子树的大小,x 子树

的大小为其左右子树大小之和加 1。

2 维护

(1)LL型

一棵 SBT,若将新节点 x 插入节点 A(T 左子树的左子树)之后,节点 T 出现了不平衡(size[A]>size[R],即“侄子”大于“叔叔”),则该树属于 LL 型不平衡,需要将节点 T 右旋。

SBT 树原理和实战_第3张图片

旋转之后 , A 、 B 和 R 仍 是 SBT 。 L 的右子树T可能会出现 size[C] > size[B] 或 size[D] > size[B] 的情况,即 RL、RR 型不平衡。因为 size[B] ≤ size[R],所以不会出现 B 的子树比 R 大的情况,即 T 不会出

现 LR、LL 型不平衡。因此 L 的右子树只需向右判断两种不平衡 RL、RR 即可。旋转后,L 自身也可能出现不平衡,需要继续调整平衡。

(2)LR型

有一棵 SBT,若新节点 x 插入节点B(T 左子树的右子树)之后,节点 T 出现了不平衡(size[B]>size[R]),则属于 LR 型不平衡,需要先将节点 L 左旋,然后将节点 T 右旋。

两次旋转之后,A、E、F 和 R 仍是 SBT。B 的右子树 T可能会出现 size[C]>size[F] 或 size[D]>size[F],即 RL、RR 型不平衡。因为在插

入节点之前 , size[B]≤size[R] , 插入新节点之后 ,size[B] > size[R] 。 实际上 , size[B] 最多比 size[R] 大 1 ,size[B]=size[E]+size[F]+1=size[R]+1,因此 size[F] ≤ size[R],不会出现 F 的子树比 R 大的情况,即T 不会出现 LR、LL 型不平衡。因此 B 的右子树只需向右判断两种不平衡 RL、RR 即可。

B 的左子树 L,size[E] ≤ size[A],因此 B 的左子树只需向左判断两种不平衡 LR、LL 即可。旋转后 B 自身都有可能出现不平衡,需要继续调整平衡。

总结:旋转之后,树根的左子树只需向左判断调整平衡,树根的右子树只需向右判断调整平衡,树根需要在两个方向判断调整平衡。

(3)RR型

该类型与 LL 型对称。

(4)RL型

该类型与 LR 型对称。

三 基本操作

SBT 的 9 种基本操作:插入、删除、查找、最小值/最大值、前驱/后继、排名、第k 小。

(1)插入

从树根开始,若当前节点为空,则创建一个新节点,否则当前节点的大小加1。若待插入元素 val 小于当前节点的值,则插入当前节点的左子树,否则插入当前节点的右子树,然后根据插入子树的不同进行维护。

(2)删除

删除操作和二叉搜索树的删除方法相同。删除节点之后,虽然不能保证这棵树是 SBT,但是整棵树的最大深度并没有变化,所以时间复杂度不会增加。这时,maintain 操作显得多余,因此删除操作没有调整平衡。

(3)查找

查找操作和二叉搜索树的查找方法一样,从树根开始,若当前节点为空或待查找元素 val 等于当前节点的值,则返回当前节点;若 val 小于当前节点的值,则到当前节点的左子树中查找,否则到当前节点的右子树中查找。

(4)最小值

SBT 是一棵二叉搜索树,满足中序有序性,因此从根开始一直向左,找到的最左节点就是最小节点。

(5)最大值

根据 SBT 的中序有序性,从根开始找到的最右节点就是最大节点。

(6)前驱

求 val 的前驱,从根开始,用 p 记录当前节点,用 q 记录查找路径上的前一个节点。若 val 大于当前节点的值,则到右子树中搜索,否则到左子树中搜索,当 p 为空时返回的 q 节点的值,即为 val 的前驱。

(7)后继

求 val 的后继,从根开始,p 记录当前节点,q 记录查找路径上的前一个节点。若 val 小于当前节点的值,则到左子树中搜索,否则到右子树中搜索,当 p 为空时返回的 q 节点的值,即为 val 的后继。

(8)排名

求 val 的排名,从根开始,若 val 小于当前节点的值,则返回在左子树中的排名;若 val 大于当前节点的值,则返回在右子树中的排名+左子树的大小+1;若 va l等于当前节点的值,则返回左子树的大小+1。

(9)第 k 小。、

从根开始,用 s 记录左子树的大小+1,若 s 等于 k,则返回当前节点的值;若s 小于 k ,则到右子树中查找第 k -s 个节点,否则到左子树中查找第 k 个节点。   

四 代码

package com.platform.modules.alg.alglib.p434;

public class P434 {
    public String output = "";

    private int maxn = 100005;
    int n, cnt; // 结点数,结点存储下标累计

    node tr[] = new node[maxn];

    public P434() {
        for (int i = 0; i < tr.length; i++) {
            tr[i] = new node();
        }
    }

    // 树根
    nodePtr root = new nodePtr();

    public String cal(String input) {
        cnt = 0;
        int n, x;
        int count = 0;
        while (true) {
            String[] line = input.split("\n");
            String[] command = line[count++].split(" ");
            n = Integer.parseInt(command[0]);

            switch (n) {
                case 0:
                    return output;
                case 1: // 插入
                    x = Integer.parseInt(command[1]);
                    insert(root, x);
                    break;
                case 2: // 删除
                    x = Integer.parseInt(command[1]);
                    remove(root, x);
                    break;
                case 3: // 查找
                    x = Integer.parseInt(command[1]);
                    if (find_v(root.ptr, x) > 0)
                        output += "find success!\n";
                    else
                        output += "find fail!\n";
                    break;
                case 4: // 最小值
                    output += get_min() + "\n";
                    break;
                case 5: // 最大值
                    output += get_max() + "\n";
                    break;
                case 6: // 前驱
                    x = Integer.parseInt(command[1]);
                    output += get_pre(root, 0, x) + "\n";
                    break;
                case 7: // 后继
                    x = Integer.parseInt(command[1]);
                    output += get_next(root, 0, x) + "\n";
                    break;
                case 8: // 排名
                    x = Integer.parseInt(command[1]);
                    output += get_rank(root, x) + "\n";
                    break;
                case 9: // 第K小
                    x = Integer.parseInt(command[1]);
                    output += get_kth(root, x) + "\n";
                    break;
                case 10: // 输出
                    in_order(root.ptr);
                    break;
            }
        }
    }

    void R_rotate(nodePtr x) {
        int y = tr[x.ptr].lc.ptr;
        tr[x.ptr].lc.ptr = tr[y].rc.ptr;
        tr[y].rc.ptr = x.ptr;
        tr[y].size = tr[x.ptr].size;
        tr[x.ptr].size = tr[tr[x.ptr].lc.ptr].size + tr[tr[x.ptr].rc.ptr].size + 1;
        x.ptr = y;
    }

    void L_rotate(nodePtr x) {
        int y = tr[x.ptr].rc.ptr;
        tr[x.ptr].rc.ptr = tr[y].lc.ptr;
        tr[y].lc.ptr = x.ptr;
        tr[y].size = tr[x.ptr].size;
        tr[x.ptr].size = tr[tr[x.ptr].lc.ptr].size + tr[tr[x.ptr].rc.ptr].size + 1;
        x.ptr = y;
    }

    void maintain(nodePtr p, boolean flag) {
        if (p.ptr == 0) return;
        if (!flag) {
            if (tr[tr[tr[p.ptr].lc.ptr].lc.ptr].size > tr[tr[p.ptr].rc.ptr].size) // LL
                R_rotate(p);
            else if (tr[tr[tr[p.ptr].lc.ptr].rc.ptr].size > tr[tr[p.ptr].rc.ptr].size) { // LR
                L_rotate(tr[p.ptr].lc);
                R_rotate(p);
            } else return;
        } else {
            if (tr[tr[tr[p.ptr].rc.ptr].rc.ptr].size > tr[tr[p.ptr].lc.ptr].size)//RR
                L_rotate(p);
            else if (tr[tr[tr[p.ptr].rc.ptr].lc.ptr].size > tr[tr[p.ptr].lc.ptr].size) {//RL
                R_rotate(tr[p.ptr].rc);
                L_rotate(p);
            } else return;
        }
        maintain(tr[p.ptr].lc, false);
        maintain(tr[p.ptr].rc, true);
        maintain(p, false);
        maintain(p, true);
    }

    void insert(nodePtr p, int val) {
        if (p.ptr == 0) {
            p.ptr = ++cnt;
            tr[p.ptr].lc.ptr = tr[p.ptr].rc.ptr = 0;
            tr[p.ptr].size = 1;
            tr[p.ptr].val = val;
        } else {
            tr[p.ptr].size++;
            if (val < tr[p.ptr].val) insert(tr[p.ptr].lc, val);
            else insert(tr[p.ptr].rc, val);
            maintain(p, val >= tr[p.ptr].val);
        }
    }

    int find_v(int p, int val) {
        if (p == 0 || tr[p].val == val) return p;
        if (val < tr[p].val) return find_v(tr[p].lc.ptr, val);
        else return find_v(tr[p].rc.ptr, val);
    }

    void remove(nodePtr p, int val) {
        if (p.ptr == 0) return;
        tr[p.ptr].size--;
        if (tr[p.ptr].val == val) {
            if (tr[p.ptr].lc.ptr == 0 || tr[p.ptr].rc.ptr == 0)
                p.ptr = tr[p.ptr].lc.ptr + tr[p.ptr].rc.ptr; // 有一个儿子为空,直接用儿子代替
            else { // 找后继,右子树最左节点
                int temp = tr[p.ptr].rc.ptr;
                while (tr[temp].lc.ptr > 0)
                    temp = tr[temp].lc.ptr;
                tr[p.ptr].val = tr[temp].val;
                remove(tr[p.ptr].rc, tr[temp].val);
            }
        } else if (val < tr[p.ptr].val) remove(tr[p.ptr].lc, val);
        else remove(tr[p.ptr].rc, val);
    }

    int get_min() {
        int p = root.ptr;
        while (tr[p].lc.ptr > 0) p = tr[p].lc.ptr;
        return tr[p].val;
    }

    int get_max() {
        int p = root.ptr;
        while (tr[p].rc.ptr > 0) p = tr[p].rc.ptr;
        return tr[p].val;
    }

    int get_pre(nodePtr p, int q, int val) { // 求val的前驱
        if (p.ptr == 0) return tr[q].val;
        if (tr[p.ptr].val < val)
            return get_pre(tr[p.ptr].rc, p.ptr, val);
        else return get_pre(tr[p.ptr].lc, q, val);
    }

    int get_next(nodePtr p, int q, int val) { // 求 val 的后继
        if (p.ptr == 0) return tr[q].val;
        if (tr[p.ptr].val > val)
            return get_next(tr[p.ptr].lc, p.ptr, val);
        else return get_next(tr[p.ptr].rc, q, val);
    }

    int get_rank(nodePtr p, int val) { // 求 val 的排名
        if (val < tr[p.ptr].val)
            return get_rank(tr[p.ptr].lc, val);
        else if (val > tr[p.ptr].val)
            return get_rank(tr[p.ptr].rc, val) + tr[tr[p.ptr].lc.ptr].size + 1;
        return tr[tr[p.ptr].lc.ptr].size + 1;
    }

    int get_kth(nodePtr p, int k) { // 求第 k 小数,select
        int s = tr[tr[p.ptr].lc.ptr].size + 1;
        if (s == k) return tr[p.ptr].val;
        else if (s < k) return get_kth(tr[p.ptr].rc, k - s);
        else return get_kth(tr[p.ptr].lc, k);
    }

    void in_order(int p) {
        if (p == 0) return;
        in_order(tr[p].lc.ptr);
        output += tr[p].val + " " + tr[p].size + " " + tr[tr[p].lc.ptr].val + " " + tr[tr[p].rc.ptr].val + " " + "\n";
        in_order(tr[p].rc.ptr);
    }
}

class node {
    nodePtr lc = new nodePtr(); // 左孩子
    nodePtr rc = new nodePtr(); // 右孩子
    int val; // 值
    int size; // 子树大小
}

class nodePtr {
    int ptr;
}

五 测试

1 输入

1 1

1 2

1 3

1 4

1 5

1 6

1 7

1 8

1 9

2 5

3 4

4

5

6 3

7 3

8 9

9 3

10

0

2 输出

find success!

1

9

2

4

8

3

1 1 0 0

2 3 1 3

3 1 0 0

4 8 2 6

6 4 0 8

7 1 0 0

8 3 7 9

9 1 0 0

你可能感兴趣的:(数据结构与算法,数据结构,算法)