红黑树简明讲解

本文基于TreeMap的源码,阐述红黑树的运作机制,尽量遵循由简到难,循序渐进的原则。能搞定红黑树,就再也不会担心数据结构的问题了。
定义
1.每个节点或者是黑色,或者是红色。
2.根节点是黑色。
3.每个叶子节点是黑色。(默认NULL节点是黑色的)
4.如果一个节点是红色的,则它的子节点必须是黑色的。
5.从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。

发明红黑树的人肯定是天才,为什么是这个样子可能会有数学证明,不深究。所有关于红黑树的分析都是以上述结论为基础。看了一些关于红黑树的分析,情况相当复杂,尤其是删除的部分。如果沉迷于其中的细节或死磕代码,很容易心态崩溃最后放弃,有种“不识庐山真面目,只缘身在此山中”的感觉,即使看懂,也很容易忘记,因此应尽量先在宏观层面去理解,不要深究细节。
红黑树的操作与二叉查找树的操作没有本质区别,对用户来说无非就是插入、删除和检索。红黑树的这种机制(5个特性),使得它在最坏的情况下也不会像二叉查找树那样退化成单链表,能够保持检索时间复杂度为O(logn)。在实际使用中,红黑树会比普通的平衡二叉树(AVL树)搜索效率要低一些,插入和删除相较于AVL树的性能会高一些,在实际应用中也经受住了考验,因此得以广泛应用,如Java 的 TreeMap、TreeSet以及Java8的HashMap都是基于红黑树实现的。
可以看出,红黑树的特性给予它优异的使用性能,而红黑树的增删操作完成后,都需要对树进行调整,以使之仍然符合红黑树的特性,这就是红黑树复杂的根源(尤其是4、5特性的维护)。红黑树维持特性的手段有三种:左旋、右旋、变色,而增删节点时,操作节点的父、子、叔叔、侄子节点的颜色不同,都会对应不同的三种操作的组合,因此源码实现也略显复杂。

红黑树的自旋
旋转操作分为左旋和右旋,左旋是将某个节点旋转为其右孩子的左孩子,而右旋是节点旋转为其左孩子的右孩子,下图左旋和右旋的示意图:
在这里插入图片描述
以右旋为例进行说明,右旋节点 A 的步骤如下:
将节点 A 的左孩子引用指向节点 B 的右孩子
将节点 B 的右孩子引用指向节点 A,完成旋转
在这里插入图片描述
左旋和右旋是对称的,如果其中一个节点先左旋再右旋(或先右旋再左旋)都能恢复原状。
红黑树的新增
首先,被插入的节点一定是红色,此时所有路径上的黑色节点数量不变,仅可能会出现两个连续的红色节点的情况。这种情况下,通过变色和旋转进行调整即可,相对比较简单。
case1:
插入的新节点 N 是红黑树的根节点,这种情况下,我们把节点 N 的颜色由红色变为黑色即可
在这里插入图片描述
case2:
N 的父节点是黑色,这种情况下,性质4(每个红色节点必须有两个黑色的子节点)和性质5没有受到影响,不需要调整。
在这里插入图片描述
case3:
N 的父节点是红色(节点 P 为红色,其父节点必然为黑色),叔叔节点 U 也是红色。由于 P 和 N 均为红色,性质4被打破,此时需要进行调整。这种情况的特征在于,父子叔叔N、P、U均为红色。
在这里插入图片描述

祖父节点G变成红色,需要递归向上调整,根据不同的颜色组合进行进一步处理,但必然属于五种情况之一。
case4:
假设原始红黑树如下:
在这里插入图片描述
在完成case3调整过程后,G 的父节点F为红色,叔叔节点J为黑色,G是左孩子。此时对 I 进行右旋,F和I互换颜色完成调整。
在这里插入图片描述
case5:
假设原始红黑树如下:
在这里插入图片描述
在完成case3调整过程后,G 的父节点F为红色,叔叔节点J为黑色,G是右孩子。这种情况下,首先F进行一次左旋,变成了与case4相同的情况,然后如法炮制,I进行右旋并与G交换颜色即可完成调整。
在这里插入图片描述

红黑树的删除

红黑树的删除复杂一些,主要分为两步,第一步是选择继承节点删除原有节点;第二步是根据删除的节点颜色,视情进行红黑树的调整。
具体到第一步又分为三种情况:
1、待删除的节点是叶子节点,直接删除
2、待删除的节点只有一个子节点,那么删除该节点,让它唯一的子节点顶替它的位置,如下图所示
在这里插入图片描述
3、待删除的节点F含有两个子节点,此时选取其右子树G的最左节点P(右子树最小值),将P的值复制到原来待删除的节点F(注意颜色不能变!),转而删除原来的P,而P最多只有一个右孩子节点,这时候转化到了第1种或第2种场景。将未知问题转化为已知问题,这是解决问题的重要手段!
在这里插入图片描述
由红黑树的性质4以及删除的规则可以推断,如果删除的节点为红色,它必然是叶子节点,无需调整红黑树,如果最后删除了黑色节点,红黑树就要调节平衡:
在这里插入图片描述
此时已经删除黑色节点X,它的子节点N顶替它的位置后,以节点N为中心对红黑树进行调整。下面的举例均针对N是左子树的情况,右子树时镜像处理即可。
case1:
N是根节点,无需调整,直接完成。
case2:
N的父节点P是红色,S 和 S 孩子为黑色。此时仅需要交换S和P的颜色,以P为根节点的子树即可保证性质5,因此,且经过P到达叶子节点所经过的黑色节点数目与删除X时一致,因此调整结束。
在这里插入图片描述
case3:
N的兄弟节点S为黑色且S的右孩子SR为红色,其他N、P、SL颜色随意。按下图操作即可直接完成调整。这是因为左子树直接增加了一个黑色节点补偿了已经删除的X,右子树因为SR的升级和变色,使得右子树到叶子节点的黑色节点数目也没有发生变化。这真的是一个很理想的局面。
在这里插入图片描述
case4:
S为黑色,S 的左孩子为红色,右孩子为黑色,N、P颜色随意。此时S右旋,并互换 S 和 SL 的颜色,那么P的右子树依然满足性质5,此时变成了case3的情况,执行一次case3即可完成调整。
在这里插入图片描述
case5:
N的父节点P,兄弟节点 S 和 S 的孩子节点均为黑色。此时将S变成红色,则P子树满足性质5,但是所有经过P到叶子节点的路径上黑色节点都比删除X前少了一个,所以除非P是根节点,否则需要向上递归(P的地位变成了原来的N)处理。这是一个比较差的情况。
在这里插入图片描述
case6:
S为红色,其他节点为黑色。这种情况下可以对 N 的父节点P(必为黑色)进行左旋操作,然后互换 P 与 S 颜色。此时case6转换成了其他5种情况中的一种,如本例就转化成了case5,再继续按照其他情况进行操作即可。
在这里插入图片描述
梳理出以上步骤,一个熟练的码农应该可以实现一个基本可用的红黑树了。事实上这些情况的分析构成了TreeMap新增、删除节点时复杂条件判断分支,把这些条件的判断进行合并,调整顺序精简代码,你会发现最终的实现代码和TreeMap差不多。
小结
1.红黑树的新增处理的核心问题是插入后出现父子连续红色节点(不满足性质4)的问题,需要根据插入节点的父节点、叔叔节点的颜色位置关系,按上述归纳的情况进行处理,同时向上递归。
2.红黑树的删除处理的核心问题是删除黑色节点后红黑树不满足性质5的问题,首先应该在以删除节点的父节点为根节点的子树范围内进行局部调整,使子树满足红黑树的性质,然后评估子树的改变对整个红黑树的影响,如无影响则调整结束,否则递归向上调整。删除节点的兄弟节点与侄子节点的位置、颜色决定了调整的策略。
3.在进行红黑树调节的过程中要注意将未知的情况转换成已知的情况。
4.以上关于新增和删除都是发生在红黑树的左子树,如发生在右子树,镜像处理即可。

参考文献:
专题1 写的比较生动,说明红黑树的的由来,以及它与2-3树密切相关,没有涉及红黑树增删问题
专题2 基于TreeMap源码对红黑树进行了细致的分析,但是显得有点冗长
专题3 总结还是很到位的,也是本文参考最多的文章,只是一些地方看起来不是很连贯,一些附图存在笔误

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值