点击关注上方“五分钟学算法”,
设为“置顶或星标”,第一时间送达干货。
转自景禹
说到红黑树的删除操作,不得不提 图解:什么是红黑树?(中篇)中所讲的红黑树的插入操作。与红黑树插入操作类似,红黑树的删除操作也是通过 重新着色(recoloring) 和 旋转(rotation) 来保证每一次删除操作后依旧满足红黑树的属性的。
在插入操作中,通过判断插入结点 x 的叔叔结点 u 的颜色来确定恰当的平衡操作。而删除操作中,是通过检查兄弟结点的颜色来决定恰当的平衡操作。
红黑树中插入一个结点最容易出现两个连续的红色结点,违背红黑树的性质3(红黑树中不存在两个相邻的红色结点)。而删除操作,最容易造成子树黑高(Black Height)的变化(删除黑色结点可能导致根结点到叶结点黑色结点的数目减少,即黑高降低)。关于黑高不清楚的可以看这篇文章 图解:什么是红黑树?(上篇) 。
与插入操作相比,红黑树的删除操作相对复杂一点,但多点儿耐心,还是没有问题的。为了理解删除操作,我们先来看一个 双黑(Double Black) 的概念(这里双黑可不是漫画《文豪野犬》中的黑手党成员奥)。
当删除结点 v 是黑色结点,且其被其黑色子节点替换时,其子结点就被标记为 双黑

删除操作最主要的任务就可以转化为将双黑结点转化为我们普通黑色结点。
红黑树的删除操作
删除操作总体上分为三步,我们先提高挈领地看一下,有个宏观概念,然后步步为营,攻陷删除。
首先我们假定要删除的结点为 v ,u 是用来替换 v 的孩子结点(注意,当 v 是叶结点时, u 是 NULL结点,且NULL结点我们还是当做黑色结点处理)。
删除操作总纲:
执行标准的 BST 的删除操作
简单情况:u 或者 v 是红色
复杂情况:u 和 v 都是黑色结点。
其中第3步又包含三种情况:

其中的3.2又分为三种情况进行处理:

上图中的(a)和(c)又分别包含 4 种和 2 种情况需要处理。
接下来,我们分别对每一步深挖。
1. 执行标准的BST删除操作
对于二叉排序树的删除操作,我假设你已经很清楚了(否则回头看看 图解:什么是二叉排序树? 这篇文章)。在标准的 BST 删除操作中,我们最终都会以删除一个叶子结点或者只有一个孩子的结点而结束(对于内部节点,就是要删除结点左右孩子都存在的情况,最终都会退化到删除结点是叶子结点或者是只有一个孩子的情况)。所以我们仅需要处理被删除结点是叶结点或者仅有一个孩子的情况。
2. 简单情况:u 或者 v 是红色
如果 u 或者 v 是红色,我们将替换结点 v 的结点 u 标记为黑色结点(这样黑高就不会变化)。注意这里是 u 或者 v 是红色结点,因为在一棵红黑树中,是不允许有两个相邻的红色结点的,而结点 v 是结点 u 的父结点,因此只能是 u 或者 v 是红色结点。
删除结点 v 为黑色结点 10 ,替换结点 v 的结点 u 为红色结点 9 的情况:

删除结点 v 为红色结点 20 ,替换结点 v 的结点 u 为黑色NULL结点 h 的情况:

3. 复杂情况:u 和 v 都是黑色结点
当 u 和 v 都是黑色结点时,分为三种情况进行处理:
3.1 结点 u 是双黑结点
当要删除结点 v 和孩子结点 u 都是黑色结点,删除结点 v ,导致结点 u 变为双黑结点。当 u 变成双黑结点时,我们的主要任务将变成将该双黑结点 u 变成普通的单黑结点。一定要特别注意,我们在上篇就提到的,NULL结点为黑色结点 , 所以删除黑色的叶子结点就会产生一个双黑结点。
3.2 当前结点 u 是双黑结点且不是根结点
当前结点 u 是双黑结点且不是根结点,又包含三种情况进行处理。我们约定结点 u 的兄弟结点为 s .
(a)u 的兄弟结点 s 是黑色且 s 的孩子结点至少有一个是红色
对于这种情况,需要对 u 的兄弟结点 s 进行旋转操作,我们将 s 的一个红色子结点用 r 表示,u 和 s 的父结点用 p 表示,那么结点 p 、s 和 r 的位置将出现以下四种情况(LL、LR、RR、RL)。
LL(s 是 p 的左孩子,r 是 s 的左孩子,或者 s 的两个孩子都是红色结点):
我们删除下图中的结点 25 为例进行说明。

删除结点 25 ,用结点 25 的NULL结点 a 替换结点 25 ,产生一个双黑结点 u ,双黑结点 u 的兄弟结点 s 为 15 ,结点 s 是其父结点 20(p) 的左孩子,其左孩子 18(r) 正好是红色结点。即为 LL 情况。
s 的左孩子 r 颜色设置为 s 的颜色,s 的颜色设置为父结点 p 的颜色:

右旋结点20(p):
将结点 p的颜色设置为黑色,双黑结点变为单黑结点:

LR(s 是 p 的左孩子,r 是 s 的右孩子,或者 s 的两个孩子都是红色结点):
删除结点 25 ,不过结点 25 的兄弟结点 15 只有一个右孩子 18 ;

将结点 r 的颜色设置为 p 的颜色
左旋结点15(s)

右旋结点20(p),p的颜色设置为黑色,双黑变单黑

RR(s 是 p 的右孩子,r 是 s 的右孩子,或者 s 的两个孩子都是红色结点):
删除结点 2 ,用结点 2 的NULL结点 a 替换结点 2 ,产生一个双黑结点 u ,双黑结点 u 的兄弟结点 s 为 15 ,结点 s 是其父结点 6(p) 的右孩子,其右孩子 18(r) 正好是红色结点。即为 RR 情况(仔细观察其实和 LL 情况是对称的)。

r的颜色变为s的颜色,s的颜色变为p的颜色

左旋p,p的颜色设置为黑色,双黑变单黑

RL情况(s 是 p 的右孩子,r 是 s 的左孩子,或者 s 的两个孩子都是红色结点): 该情况与 LR情况是对称的
结点 r 的颜色变为 p 的颜色

右旋结点15(s)

左旋结点6(p),p的颜色设置为黑色,双黑变单黑

我们接着看3.2的第二类情况。
(b)u 的兄弟结点 s 是黑色且 s 的两个孩子结点都是黑色
对于这种情况需要递归地进行处理,如果删除结点后得到的双黑结点的父结点此时为黑色,则结点 u 变单黑,且结点 u 的父结点 p 变双黑,然后对结点 u 的父结点 p 继续进行处理,直到当前处理的双黑结点的父结点为红色结点,此时将双黑结点的父结点设置为黑色,双黑结点变为单黑结点(红色 + 双黑 = 单黑)。
假设以 10 为根结点的子树为整棵树的左子树,删除结点 9 ,产生双黑结点 c 且其兄弟结点 12(s) 为黑色,兄弟结点的左右孩子均为黑色。

此时双黑结点的兄弟结点 12 变为红色结点,然后将 u 的父结点 10 变为双黑结点,一直向上判断。

至于这一过程何时结束,我们再看下面一个例子。
删除下图中的结点 12 ,得到一个双黑结点 u ,双黑结点的兄弟结点 31 及兄弟结点的孩子结点均为黑色,且双黑结点的父结点 19 为红色结点,刚好是不再继续向上判断的情况。

此时只需要将结点 u 的兄弟结点 31 的颜色变为红色,双黑结点 u 的父结点 19 由红色变为黑色结点,双黑结点 u 变为单黑结点。

我们接着看3.2 (当前结点 u 是双黑结点且不是根结点)的第三类情况。
(c)u 的兄弟结点 s 是红色结点
当前 u 的兄弟结点 s 是红色结点时,通过旋转操作将 u 当前的兄弟结点向上移动,并对 u 的父结点和其旋转前的兄弟结点重新着色,接着继续对结点 u 旋转后的兄弟结点 s 进行判断,确定相应的平衡操作。旋转操作将 u 的兄弟结点情况又会转换为前面刚提到的3.2(a)和(b)的情况。根据兄弟结点 s 是父结点 p 的左右孩子又分为两种情况。
情况一:u 的兄弟结点 s 是父结点 p 的左孩子 ,对结点 p 进行右旋操作。
删除结点 18 ,产生一个双黑结点 u ,且 u 的兄弟结点 s 是红色,兄弟结点 s 是其父结点的左孩子,接着就是对其父结点 15 进行右旋操作。

对结点 15 进行右旋操作,并且对旋转前的 p 和 s 进行重新着色后,继续对双黑结点旋转后的兄弟结点进行判断,发现此时正好和 3.2(b)的情况是一样,进行相应处理,如下图所示。

情况二:u 的兄弟结点 s 是父结点 p 的左孩子 ,对结点 p 进行左旋操作(这种情况与上面的是对称的)。
删除结点 6 ,产生一个双黑结点 u ,且 u 的兄弟结点 10(s) 为红色,s 是父结点 p 的右孩子,左旋P

对双黑结点 u 旋转后的兄弟结点继续判断:

3.3 当前结点 u 是双黑结点且是根结点
当前结点 u 是双黑结点且是根结点时,直接将双黑结点变为单黑结点,整颗红黑树的黑高减 1.
红黑树删除操作案例
下面以最开始讲黑高红黑树为例再演示一遍:
删除结点 2 :

删除结点 2 之后,产生一个双黑结点 u ,u 的兄弟结点 15(s) 是红色结点,且 s 是其父结点 6(p) 的右孩子,则对结点 6 进行左旋操作:
对双黑结点继续进行修正,此时,其兄弟结点 10(s) 为黑色,结点 s 的右孩子 12(r) 为红色结点,满足 RR的情况 。
r的颜色变为 s的颜色,s的颜色变为p的颜色:

左旋结点 6(p):

将结点p设置为黑色,双黑变单黑:
红黑树与AVL树的比较
红黑树
红黑树中的每个结点需要一个存储位表示结点的颜色,可以是红或黑。通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保对于每一个结点其到叶子结点的最长路径不会超过最短路径的两倍,因此,红黑树是一种弱平衡二叉树(由于是弱平衡,可以看到,在相同结点的情况下,AVL树的高度<=红黑树),相对于要求严格的AVL树来说,它的旋转次数少,所以对于插入,删除操作较多的情况下,使用红黑树。
红黑树的性质:
每一个结点非红即黑
树根节点是黑色;
所有的NULL结点都是黑色结点;
树中不存在两个相邻的红色结点(即红色结点的父结点和孩子结点均不能是红色);
从任意一个结点(包括根结点)到其任何后代 NULL 结点(默认是黑色的)的每条路径都具有相同数量的黑色结点。
红黑树的应用:
广泛用于C ++的STL中,map 和 set 是用红黑树实现的;
Linux的的进程调度,用红黑树管理进程控制块,进程的虚拟内存空间都存储在一颗红黑树上,每个虚拟内存空间都对应红黑树的一个结点,左指针指向相邻的虚拟内存空间,右指针指向相邻的高地址虚拟内存空间;
IO多路复用的epoll采用红黑树组织管理sockfd,以支持快速的增删改查;
Nginx中用红黑树管理定时器,因为红黑树是有序的,可以很快的得到距离当前最小的定时器;
Java的 TreeMap 和 TreeSet 的实现;
平衡二叉树(AVL树)
AVL树一般用平衡因子判断是否平衡并通过旋转来实现平衡,左右子树树高不超过1,和红黑树相比,AVL树是高度平衡的二叉树,平衡条件必须满足(所有结点的左右子树高度差不超过1)。不管我们是执行插入还是删除操作,只要不满足上面的条件,就要通过旋转来保持平衡,而的由于旋转比较耗时,由此AVL树适合用于插入与删除次数比较少,但查找多的情况。
AVL树的性质:
对每一个结点,其左右子树的高度之差的绝对值小于 2;
当结点的左右子树的高度之差大于等于2时,需要进行平衡操作;
严格平衡,查找速度更快
AVL树的应用:
由于维护这种高度平衡所付出的代价比从中获得的效率收益还大,故而实际的应用不多,更多的地方是用追求局部而不是非常严格整体平衡的红黑树。当然,如果应用场景中对插入删除不频繁,只是对查找要求较高,那么AVL还是较优于红黑树。
Windows NT内核中广泛存在;
数据库查询查询操作较多的情况下;
最后提示大家,需要红黑树删除操作完整实现代码的后台回复 RBTDelete
即可获得,多点耐心,结合文章,我相信你一定会彻底理解红黑树的删除操作。
推荐阅读
• 完了!CPU一味求快出事儿了!• 当你无聊时,可以玩玩 GitHub 上这个开源项目...• 剑指 offer 面试题精讲图解 | 03 . 数组中重复的数字• 炸裂!万字长文拿下HTTP 我在字节跳动等你!• 我在滴滴和头条的两年后端研发工作经验分享!• 太赞了,VSCode 上也能画流程图了!• 写给小白,从零开始拥有一个酷炫上线的网站!
欢迎关注我的公众号“五分钟学算法”,如果喜欢,麻烦点一下“在看”~
