每日算法总结——有序表系列——SBT与红黑树简介(Java)

SBT

全称:Size Balance Tree

SBT 本质上是一棵二叉搜索树,即对于每一个节点都有:以它右孩子构成的右子树中的所有元素大于它,左孩子为根构成的左子树所有元素都小于它。

与AVL树对比

与 AVL 树不同之处在于 平衡标准

  • AVL 树要求左右两棵子树的高度差不超过1
  • SB 树则要求每棵子树的大小,不小于其兄弟的子树大小,即每棵叔叔树的大小,不小于其任何侄子树的大小
    • 这里的大小size,指的是以它为根的子树中节点的数量
需要平衡的四种情况

在这里插入图片描述

观察一下上面的图,我们想知道T这棵树是否平衡,为什么不判断 L.size == R.size ?这其实是有问题的,因为可能L的节点都落在了A上,R的节点都落在了C上。这同样是不平衡的,但这种情况除非我们继续往下递归,否则很难识别。

所以这里换了一种方法,我们判断R和A、B的size的关系,以及L和C、D size的关系。我们要求L.size >= C.size, D.sizeR.size >= A.size, B.size。这样就能保证两颗树的规模差不多

了解了判断平衡的方法,我们就可以推出不平衡的情况,一共有四种:

  1. Size of A > size of R
  2. Size of B > size of R
  3. Size of C > size of L
  4. Size of D > size of L

由于我们使用递归来维护树的平衡性的时候,是从底往上的。因此我们可以假设ABCDLR这六棵子树都是平衡的,这样可以简化我们的分析。

我们假设我们现在有了一个函数叫做 maintain,它可以将一棵不平衡的子树旋转到平衡状态,什么时候调用maintain某个节点所在子树大小发生变化时(因为子树大小发生变换很可能会导致不平衡现象的出现)。

和 AVL 树一样,SBT 也有 LL、LR、RL、RR 四种不平衡的情况

  • LL型:当前节点T的左孩子L的左孩子A比右孩子R

    • 需要进行一次右旋操作

    在这里插入图片描述

    • 可以看到,在旋转过程中,节点LT所在子树大小发生变化了的,所以我们需要再针对它们调用一次maintain,伪代码:

      rightRotate(T);
      maintain(T);
      maintain(L);
      
  • LR型:当前节点T的左孩子L的右孩子B的大小比右孩子R

    • 需要先左旋,再右旋

    在这里插入图片描述

    • 可以看到,在旋转过程中,节点LTB所在子树大小发生变化,所以需要再调用三次maintain,伪代码:

      leftRotate(L);
      rightRotate(T);
      maintain(L);
      maintain(T);
      maintain(B);
      
  • RL型:当前节点T的右孩子R的左孩子C的大小比左孩子L

    • 操作同 LR 型
  • RR型:当前节点T的右孩子R的右孩子D的大小比左孩子L

    • 操作同 LL 型
代码实现
public class SizeBalancedTreeMap {

    public static class SbtNode<K extends Comparable<K>, V> {
        public K key;
        public V value;
        public SbtNode<K, V> left;
        public SbtNode<K, V> right;
        public int size;

        public SbtNode(K key, V value) {
            this.key = key;
            this.value = value;
            size = 1;
        }
    }

    public static class SizeBalancedTree<K extends Comparable<K>, V> {
        private SbtNode<K, V> root;

        /**
         * 右旋操作
         * @param cur 右旋之前的根节点
         * @return 右旋之后的根节点
         */
        private SbtNode<K, V> rightRotate(SbtNode<K, V> cur) {
            SbtNode<K, V> leftNode = cur.left;
            cur.left = leftNode.right;
            leftNode.right = cur;
            leftNode.size = cur.size;
            cur.size = (cur.left != null ? cur.left.size : 0) + (cur.right != null ? cur.right.size : 0);
            return leftNode;
        }

        /**
         * 左旋操作
         * @param cur 左旋之前的根节点
         * @return 左旋之后的根节点
         */
        private SbtNode<K, V> leftRotate(SbtNode<K, V> cur) {
            SbtNode<K, V> rightNode = cur.right;
            cur.right = rightNode.left;
            rightNode.left = cur;
            rightNode.size = cur.size;
            cur.size = (cur.right != null ? cur.right.size : 0) + (cur.left != null ? cur.left.size : 0);
            return rightNode;
        }

        /**
         * 如果失衡,则进行平衡性调整
         * @param cur 要调整的子树根节点
         * @return 新的子树根节点
         */
        private SbtNode<K, V> maintain(SbtNode<K, V> cur) {
            if (cur == null) {
                return null;
            }
            if (cur.left != null && cur.left.left != null && cur.right != null
            && cur.left.left.size > cur.right.size) {
                // LL
                cur = rightRotate(cur);
                cur.right = maintain(cur.right);
                cur = maintain(cur);
            } else if (cur.left != null && cur.left.right != null && cur.right != null
            && cur.left.right.size > cur.right.size){
                // LR
                cur.left = leftRotate(cur.left);
                cur = rightRotate(cur);
                cur.left = maintain(cur.left);
                cur.right = maintain(cur.right);
                cur = maintain(cur);
            } else if (cur.right != null && cur.right.right != null && cur.left != null
            && cur.right.right.size > cur.left.size) {
                // RR
                cur = leftRotate(cur);
                cur.left = maintain(cur.left);
                cur = maintain(cur);
            } else if (cur.right != null && cur.right.left != null && cur.left != null
            && cur.right.left.size > cur.left.size) {
                // RL
                cur.right = rightRotate(cur.right);
                cur = leftRotate(cur);
                cur.right = maintain(cur.right);
                cur.left = maintain(cur.left);
                cur = maintain(cur);
            }
            return cur;
        }
    }
}

红黑树

与 Avl 树的高度、SBT 的大小(规模)一样,红黑树节点也记录着一个标签:颜色。

红黑树规则

  1. 每一个节点要么是黑色,要么是红色
  2. 根节点和叶节点(即最底层的 null 节点)都是黑色
  3. 红节点不相邻
  4. 从某一个头部cur出发,每条到结束节点的路径上,黑色节点的数量一样多

不过红黑树操作太过复杂,面试基本不会考,简单了解一下就行

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值