十四、红黑树真的有那么难?

1,362 阅读5分钟

红黑树

目标:

  • 2-3树
  • 红黑树
    • 添加元素
  • 性能测试
  • 其他话题

1.1 2-3树

  • 特点

    • 满足二分搜索树的基本性质

    • 节点可以存放一个元素或者两个元素

      image-20200916101400332

      3节点理解:左节点元素比b小,中间节点位于b和c之间,右节点大于c。

    • 每个节点有2个或者3个孩子——2-3树

      image-20200916102051122
    • 2-3树是一棵绝对平衡的树

  • 绝对平衡

    • 在2-3树中添加元素,绝对不会添加到空节点的位置上去 ,只会跟叶子节点做融合

      • 插入3-节点
      image-20200916103620682
      • 插入3-节点 父亲节点是2-节点

        image-20200916105428420
      • 插入3-节点 父亲节点是3-节点

        image-20200916110512173
  • 红黑树和2-3树的等价性

    image-20200916111336327

    引入红色节点,表示的意思是,和它的父亲节点一起表示2-3树中的3节点。在红黑树中,所有的红色节点都是左倾斜的。那么之前的2-3树,转换为红黑树即为:

    image-20200916112427867

    对于任意一棵2-3树,都可以按照这种等价规则转化为红黑树。

1.2 红黑树

  • 基本性质(算法导论)

    • 每个节点或者是红色的,或者是黑色的
    • 根节点是黑色的(特殊:空树)
    • 每一个叶子节点(最后的空节点)是黑色的
    • 如果一个节点是红色的,那么他的孩子节点都是黑色的
    • 从任意一个节点到叶子节点,经过的黑色节点都是一样的
  • 复杂度分析

    • 红黑树是保持“黑平衡”的二叉树,严格意义上,不是平衡二叉树
    • 最大高度:2logn,但是复杂度依旧是O(logn)
    • 不会像二分搜索树一样退化为链表,其增删查改的操作都是O(logn)。
    • 如果添加、删除元素频繁,那么相比于AVL来说,红黑树会更好。但如果更侧重于查询来说,AVL可能会更好。
  • 添加元素

    正常面试中对于红黑树,一般来说知道原理即可,很少有手推导的,这是因为,红黑树的添加元素等操作,不是一般的难……而且,考察不了对数据结构真正的理解和认识。

    • 2-3树中添加元素

      • 或者添加进2-节点,形成一个3-节点

      • 或者添加进3-节点,暂时形成一个4-节点

      • 永远添加红色节点

      • LL

        但是,当一棵空树添加元素,需要保持根节点为黑色。当在根节点左侧添加元素(小于根节点),此时符合要求。不过当在根节点右侧添加元素,例如根节点为37,添加42,那么此时42为红色,而我们知道红色节点必须在黑色节点的左侧,所以我们需要进行左旋转的操作。如下:

      image-20200916135250469

      ​ 左旋转代码如下:

      // 红黑树中左旋转的过程
      //   node                     x
      //  /   \     左旋转         /  \
      // T1   x   --------->   node   T3
      //     / \              /   \
      //    T2 T3            T1   T2
      private Node leftRotate(Node node){
          Node x = node.right;
      
          // 左旋转
          node.right = x.left;
          x.left = node;
      
          x.color = node.color;
          node.color = RED;
      
          return x;
      }
      
      • 颜色翻转

        比如当根节点为42,此时添加左节点37和右节点66,那么颜色变化为

        image-20200916141548842

        结合上面2-3树中,插入3节点示意图看出,临时4-节点变为3个2-节点的子树,也就意味着这3个2-节点都是黑色的节点,代表单独的2-节点。此时只需要变为黑色节点即可。然后,根节点42,需要继续向上与父节点进行融合,融合的过程也就是42变为红色。颜色变化为:

        image-20200916142132080

        实现代码如下:

        // 颜色翻转
        private void flipColors(Node node){
            node.color = RED;
            node.left.color = BLACK;
            node.right.color = BLACK;
        }
        
      • RR

        举例说明:此时树结构为根节点42,左子节点为37,现在新添加元素12,颜色为:

        image-20200916142650727

        那么此时,需要进行右旋转,旋转完后,颜色变换如下:

        image-20200916142937863

        此时,37作为根节点,继续向上融合,然后再进行上面说的颜色变换。37变为红色,12和42变为黑色。右旋转代码如下:

        // 红黑树右旋转的过程
        //     node                   x
        //    /   \     右旋转       /  \
        //   x    T2   ------->   y   node
        //  / \                       /  \
        // y  T1                     T1  T2
        private Node rightRotate(Node node){
            Node x = node.left;
        
            // 右旋转
            node.left = x.right;
            x.right = node;
        
            x.color = node.color;
            node.color = RED;
            
            return x;
        }
        
      • LR

        举例说明:根节点为42,左子节点为37,现在添加元素40

        image-20200916145815210

        那么此时需要先左旋转

        image-20200916145852425

        然后进行右旋转,再转换颜色

        image-20200916145922888
      • 汇总:

        汇总以上情况可以总结如下图:

        image-20200916151314729

        维护的时机与AVL树一样,添加节点后回溯向上维护。

1.3 性能测试

  • 对于完全随机的数据,普通的二分搜索树就很好用
    • 缺点:极端情况退化为链表(或者高度不平衡)
  • 对于查询较多的使用情况,AVL树很好用
    • get
    • contains
    • set
  • 红黑树牺牲了平衡性(2logn的高度)
    • 统计性能更优(综合增删改查所有的操作)
    • Java.util中的TreeMap、TreeSet基于红黑树

1.4 其他话题

  • 删除节点
  • 右倾红黑树
  • Splay Tree(伸展树)
    • 局部性原理:刚被访问的内容下次高概率被再次访问
  • 算法导论中红黑树的实现