一、简述

红黑树是一种特殊的二叉树,并且是优秀的自平衡查找树,下图为红黑树的示例:

红黑树具有以下几大特性:

1、根节点为黑色。

2、所有节点都是黑色或红色。

3、所有叶子节点(Null)都是黑色。

4、红色节点的子节点一定是黑色的。

5、任意一个节点到其叶子节点的所有路径上的黑色节点数量相同(黑色完美平衡二叉树)。

以上的五大特定也是维持红黑树结构的基本规则,但是明白了这些规则,不代表我们就明白了红黑树的设计原理及规则维持算法。

在我们日常的工作中多多少少都会接触到红黑树,特别是JDK1.8之后hashmap的底层采用了红黑树机构,接下来的博文中我们会一点点弄明白以下几个问题,也是笔者在学习之前不明白的两个问题:

1、红黑树为什么要维持自平衡、自平衡的好处是什么?

2、什么是左旋,右旋,变色?

3、什么条件下需要进行左旋、右旋、变色?

二、红黑树的自平衡

根据上一节红黑树特性第5点可以知道,红黑树是一颗黑色完美平衡二叉树,红黑树从根节点到叶子结点的最长路径不会超过最短路径的2倍

这就保证了红黑树优秀的查找性能,其查找的时间复杂度为O(logn);

当插入或删除阶段操作过程中,会破坏此平衡结构,当平衡遭到破坏,程序会进行一系列操作来重新维持平衡,这一过程就是自平衡;这一系列操作就是左旋、右旋、变色。 

1、左旋:

对当前节点进行左旋:当前节点的右子节点变为父节点,原右子节点的左子节点变为当前节点的右子节点,如图演示(对150节点进行左旋):

上图对150节点左旋:

  1. 把150节点的右子节点170变为其父节点(170变为150的父节点)。
  2. 把150节点的原右子节点170的左子节点160变为其右子节点(160变为150的右子节点)。

注意:

    • 图中只是示意图,在第一步之后,170节点并没有三个指向其他节点的指针,这里只是为了理解起来更清晰
    • 图中只是进行左旋,还没有涉及变色问题,所以会看到父节点与子节点同色问题。
    • 左旋操作涉及到的节点只是当前节点的右子节点和右子节点的左子节点两个节点,其他节点不变。

2、右旋:

对当前节点进行右旋:当前节点的左子节点变为父节点,原左子节点的右子节点变为当前节点的左子节点,如图演示(对150节点进行右旋):

上图对150节点进行右旋:

  1. 把150节点的左子节点130变为其父节点(130变为150的父节点)。
  2. 把150节点的原左子节点130的右子节点null节点变为其左子节点(130的原右子节点null变为150的左子节点)。

 注意:

    • 图中只是示意图,在第一步之后,130节点并没有三个指向其他节点的指针,这里只是为了理解起来更清晰
    • 图中只是进行左旋,还没有涉及变色问题,所以会看到父节点与子节点同色问题。
    • 右旋操作涉及到的节点只是当前节点的左子节点和左子节点的右子节点两个节点,其他节点不变。

3、变色:

节点的变色:就是节点由红色变成黑色或有黑色变成红色。

而在实际的维持自平衡的过程中变色过程有可能是一个连锁反应,而破坏平衡结果的操作有插入和删除。 

三、节点的插入

红黑树新节点的插入有可能会破坏现有的平衡结构,所以就需要进行节点的变色、左旋或者右旋操作来保持红黑树的平衡;但插入操作的情况区分比较多,这也正是红黑树自平衡结构不容易理解的地方之一;

下图就是插入的所有情况,接下来会有针对每种情况进行详细讲解:

其他3种情况非常简单,这里详细说明第四种情况:

C - current 当前节点,P - parent 父节点,PP - 祖父节点,U - uncle 叔叔节点, O - 其他节点 

情况4、P 为红色

4.1、P 为红色且 U 为红色 (如图 4.1)

(1)将 P 设为黑色

(2)将 U 设为黑色

(3)将 PP 设为红色

(4)将 PP 设为当前节点(这时 PP 就为 C 节点了),重复情况4判断

 

 

4.2、P 为红色且 U 不存在或为黑色 

4.2.1、P 是左节点,C 左节点 (P 为红色且 U 不存在或为黑色)(如图4.2.1)

(1)将 P 设为黑色

(2)将 PP 设为红色

(3)对 PP 进行右旋

4.2.2、P 是左节点,C 右节点 (P 为红色且 U 不存在或为黑色)(如图4.2.2)

(1)对 P 进行左旋

(2)设 PP 为当前节点

(3)此时结构变为 4.2.1 结构,继续进行 4.2.1 操作

4.2.3、P 是右节点,C 右节点 (P 为红色且 U 不存在或为黑色)(如图4.2.3)

(1)将 P 设为黑色

(2)将 PP 设为红色

(3)对 PP 进行左旋

4.2.4、P 是右节点,C 左节点 (P 为红色且 U 不存在或为黑色)(如图4.2.4)

(1)对 P 进行右旋

(2)设 PP 为当前节点

(3)此时结构变为 4.2.3 结构,继续进行 4.2.3 操作

 

节点插入总结:

    • 当前节点(新插入的节点)会在旋转变色之前就设置为红色;为什么插入节点为红色?因为根据红黑树的特性,当父节点为黑色时,不需要做自平衡操作,如果为黑色,那么插入后褐色层次增加了,破坏特性5。
    • 在4.1情况中(P为红色且U为红色)如果重复递归判断到根节点,把根节点设置成了红色,则根据特性1,必须把根节点变回黑色,此时红黑树的黑色节点层次就增加了一层(自底向上生长)。

 四、节点的删除

红黑树的节点插入比较复杂,删除操作更加复杂,但掌握了规律理解起来就简单多了;

说到节点删除之前,需要先了解一下前继节点和后继节点的概念,笔者学习过程中看到一个非常容易理解的描述,如下图:

把所有的节点投射到X轴上,这时所有的节点都是自左至右排好的,这样某个节点的左边的节点就是它的前继节点,右边的节点就是它的后继节点。

由上图可以看到,后继节点是右子树的最左节点,前继节点是左子树的最右节点。

如150节点的前继节点就是130,后继节点就是160。

节点删除后空出的位置需要找到其后继节点或前继节点进行补位,如果被删除的节点没有子节点还好,要是有子节点,不补位的话树就散了;并且补位之后有可能会破坏红黑树的平衡结构,这时就需要进行自平衡了。

所以节点的删除过程可以理解为寻找后继节点或前继节点(一般习惯用后继几点)进行补位后进行自平衡的过程;这样理解,节点删除问题就可以转换为补位节点(后继节点)的问题,补位完成后节点的颜色变为被删除的颜色

而这一结果再简单就可以理解为:补位节点后继节点)的删除的自平衡问题,后继节点总是在树的最底层。

这样一来就可以区分补位节点的几种情况:

 

R - replace 补位节点,P - parent 父节点,PP - 祖父节点,B - brother 兄弟节点,BL - brother left 兄弟节点左子节点,BR - brother right 兄弟节点右子节点, O - 其他节点 

情况1: R 为红色

情况1比较简单,红色节点不影响红黑树的自平衡结构,所以直接补位,并把颜色转换为被删除节点的颜色。

情况2: R 为黑色

2.1、R 为左子节点

2.1.1、R 为黑色且为左子节点,B 为红色(如图 2.1.1)

(1)、将 B 设为黑***r> (2)、将 P 设为红***r> (3)、对 P 进行左旋,得到 2.1.2.3 情况
(4)、按照 2.1.2.3 情况进行处理

2.1.2、R 为黑色且为左子节点,B 为黑色

2.1.2.1、R 为黑色且为左子节点,B 为黑***R 为红***L 为任意(如图 2.1.2.1)

(1)、将 B 设为 P 的颜***r> (2)、将 P 设为黑***r> (3)、将 BR 设为黑***r> (4)、对 P 进行左旋

在此处有几个问题,注意上图框出的部分

1、为什么此处不符合自平衡结构?

回答:节点的删除是先找到补位节点(后继节点),然后自平衡处理,然后才进行补位,所以此处的 R 之后会移走的,所以还是符合自平衡结构。

2、BL 可能为黑色节点么?

回答:BL 不可能为黑色节点,但其可能为黑色叶子节点(NULL节点),下边用穷举法进行证明;

我们知道,在节点删除之前(第一次删除),红黑树是保持平衡结构的,那么如果 BL 为黑色,那么有一下几种情况:

1)P 为黑***L 为黑色,如下图(1),不平衡,不成立

2)P 为红***L 为黑色,如下图(2),不平衡,不成立

3)P 为红***L 为黑色叶子节点(NULL),如下图(3),平衡,成立

4)P 为黑***L 为黑色叶子节点(NULL),如下图(4),平衡,成立

5)BL 为红色,如下图(5),平衡,成立

综上:BL 只可能为红色节点或黑色叶子节点(NULL)。

2.1.2.2、R 为黑色且为左子节点,B 为黑色,BR 为黑***L 为红色(如图 2.1.2.2)

(1)、将 B 设为红***r> (2)、将 BL 设为黑***r> (3)、对 B 进行右旋,得到 2.1.2.1 情况
(4)、按照 2.1.2.1 情况进行处理

  

2.1.2.3、R 为黑色且为左子节点,B 为黑色,BR 为黑色(NULL),BL 为黑色(NULL)(如图 2.1.2.3)

(1)、将 B 设为红***r> (2)、将 P 作为新的补位节点
(3)、重新进行删除节点处理

 

需要注意:

    • 这时 P 就是 将要被移走的 R 的补位节点(后继节点)。
    • 此种情况下其实 BL,BR 都是黑色的叶子节点(NULL)。
    • B 变为红色是的原因是整颗子树都是黑色的节点,R 移走后,无论如何操作都没办法在子树内自平衡,所以,最简单的操作把 B 变为红色,这样就自平衡了;但是这样一来 子树内部的黑色节点层数少了一层,这样从 P 子树的上一层数来看就是不平衡的了,所以要将 P 看成新的补位节点,进行自平衡操作,自底向上,直至根节点。

2.2、R为右子节点

2.2.1、R 为黑色且为右子节点,B 为红色(如图 2.2.1)

(1)、将 B 设为黑***r> (2)、将 P 设为红***r> (3)、对 P 进行右旋,得到 2.2.2.3 情况
(4)、按照 2.2.2.3 情况进行处理

2.2.2、R 为黑色且为右子节点,B 为黑色

2.2.2.1、R 为黑色且为右子节点,B 为黑***L 为红***R 为任意(如图 2.2.2.1)

(1)、将 B 设为 P 的颜***r> (2)、将 P 设为黑***r> (3)、将 BL 设为黑***r> (4)、对 P 进行右旋

注意:

此种情况请参考 2.1.2.1中的问题与回答。 

2.2.2.2、R 为黑色且为右子节点,B 为黑***L 为黑***R 为红色(如图 2.2.2.2)

(1)、将 B 设为红***r> (2)、将 BR 设为黑***r> (3)、对 B 进行左旋,得到 2.2.2.1 情况
(4)、按照 2.2.2.1 情况进行处理

 

2.2.2.3、R 为黑色且为右子节点,B 为黑***L 为黑色(NULL),BR 为黑色(NULL)(如图 2.2.2.3)

(1)、将 B 设为红***r> (2)、将 P 作为新的补位节点
(3)、重新进行删除节点处理

 

需要注意:

参考 2.1.2.3 下方的需要注意说明

节点删除总结:

  1. 节点的删除情况比较多,但左右子节点情况是对称的,理解了其中一种,另一种也很快会理解。
  2. 补位节点可以用前继节点代替,也可以用后继节点代替,一般习惯用后继节点。
  3. 节点删除过程是先找到补位节点,然后进行自平衡处理,然后才会移除补位节点,即带补位节点的自平衡。
  4. 在全黑节点的情况下,可能发类似递归的生自底向上的寻找补位节点的过程,到根节点为止。
  5. 自平衡的顺序可以理解为,先自己处理,处理不了找兄弟节点参与,还处理不了找父节点参与,还处理不了让父节点找父节点的兄弟处理,以此类推。

五、红黑树总结 

通过上边对红黑树的详细谅解,我们就可以回答开始提出的问题了。

1、红黑树为什么要维持自平衡、自平衡的好处是什么?

答:红黑树是一个高效的查询树,保持平衡结构,可以保证从根节点到叶子节点的最长路径不超过最短路径的两倍,可以保证查询效率。

2、什么是左旋,右旋,变色?

答:左旋、右旋、变色都是对节点所在子树的操作,以节点为基进行变化保持树的平衡的操作。

3、什么条件下需要进行左旋、右旋、变色?

答:当发生节点插入或删除操作时,红黑树的平衡被破坏,这是就要根据具体的情况进行自平衡操作,即左旋、右旋或变色。

  • 红黑树的结构比较复杂,无论是节点的插入还是删除,都有可能破坏自平衡结构,而自平衡过程最复杂情况可能是自底向上处理,直到根节点。
  • 红黑树是平衡二叉树,但不是完美的平衡二叉树,只是黑色完美的平衡二叉树(性质5)
  • 红黑树的五条性质任意一套被破坏都触发自平衡操作。
  • 红色节点的子节点一定是黑色,但黑色节点的子节点则可以是红色和黑色任意一种,即可以有相邻的两层节点都是黑色的情况,如图:

 

4.结尾

我不能保证写的每个地方都是对的,但是至少能保证不复制、不黏贴,保证每一句话、每一行代码都经过了认真的推敲、仔细的斟酌。每一篇文章的背后,希望都能看到自己对于技术、对于生活的态度,然后小编会持续更新的噢,喜欢的朋友可以关注下我。

我相信乔布斯说的,只有那些疯狂到认为自己可以改变世界的人才能真正地改变世界。面对压力,我可以挑灯夜战、不眠不休;面对困难,我愿意迎难而上、永不退缩。

其实我想说的是,我只是一个程序员,这就是我现在纯粹人生的全部。

最后希望大家多多支持 一键三连+评论下 并献上我自己整理的“大厂真题+微服务+MySQL+分布式+SSM框架+Java+Redis+数据结构与算法+网络+Linux+Spring全家桶+JVM+高并发+各大学习思维脑图+面试集合”