开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第8天,点击查看活动详情
红黑树删除的讨论分析
写在前面
文章摘要
- 红黑树删除的简单分析
- 分类讨论
- 删除节点的颜色是红色
- 删除节点的颜色是黑色
- 删除的是
BST中度为2的节点 - 删除的是
BST中度为1的节点 - 删除的是
BST中度为0的节点- 兄弟节点是黑色且有红色的子节点
- 兄弟节点是黑色且没有红色的子节点
- 兄弟节点是红色
- 删除的是
阅读准备
- 建议阅读时间:10 ~ 15 分钟
- 阅读前提:
- 红黑树的性质与B树的等价转换 ->《初识红黑树》♨️♨️♨️
- 红黑树的提前量是:二叉搜索树、树的旋转、B树的性质【文末有推荐的阅读文章】
- 本篇文章没有代码实现,仅从用图文分析了红黑树删除的几种情况~
一、如何分析讨论红黑树的删除
(1)分析
- 分析红黑树时,一定要想象出它等价的4阶B树
- 这是一棵红黑树,将它的等价B树给画出来
- 看右边的B树,在如果要在上面删除元素,我们知道:真正被删除的元素都是在叶子节点中的
- 也就是从图中的这些节点:
(4、8、10)、(16、18)、(22、25)、(36)中去删除元素 - 加上左边红黑树节点的颜色,对应B树的叶子节点同添加一样,也只有这四种情况:
红黑红、黑红、红黑、黑
- 之前在《红黑树添加的分析与实现》中,我们想要尽快的满足红黑树的五条性质,来使其自平衡
- 我们做了一个操作:使新添加的节点默认为红色,经过这个操作后,会发现有几种情况就不需要额外的处理了。
- 在删除节点的时候,有没有不用处理的情况呢?跟着我的思路,我们来分类讨论一下吧~
(2)分类讨论1
- 在上面的图片中,可以发现,真正会被删除的节点,有黑有红。
- 那下面我们先按删除节点的颜色分类,可以分为两类:
① 删除的节点是红色
- 如图上箭头所指:删除的全部都是红色节点
- 我先不解释,我直接将其删掉,你看看图,你就明白了,不信你看看:
- 看完上图可以发现:如果删除的节点是红色的,并不会对红黑树的5条性质产生任何影响
- 回想之前在添加时,我们也将节点调成了红色再添加,红色真的很令人泪目啊!
- 这就是不需要做处理的情况吗?还挺舒服的,还有没有不需要处理的情况呢?
- 别着急,我们慢慢讨论
② 删除的节点是黑色
- 看完了上面的第一种讨论,再来看看这些情况:删除的节点是黑色的
- 看起来好像就没刚刚那么舒服了,而且感觉也很笼统
- 那咱们再来细化讨论一下:按删除节点的
(红色子节点)的数目
(3) 分类讨论2
① 有两个红色的子节点
- 在说这种情况之前,我们来简单复习一下
二叉搜索树中,删除度为2的节点时的思路:- 找到前驱或后继节点、转换成
度为 0 或 1的节点进行删除- 看上图,我们想要删除节点
8 - 在二叉搜索树的眼中,也就是
删除度为2的节点 - 需要找到它的前驱或者后继节点
4或10。先赋值,再删除前驱或后继节点
- 看上图,我们想要删除节点
- 找到前驱或后继节点、转换成
- 这样一看,发现真正被删除的节点,是
4 或者 10。看看它们的颜色,这不就是上面讨论的,不需要处理的情况了吗(真正被删除的是红色节点) - 比如我拿前驱节点为例,画一幅图给你看看:
- 可以发现,删除后并没有影响到红黑树的性质,它还是一棵红黑树
- 因为真正被删除的节点是红色,这就等价于上面讨论的第一种情况了~
② 只有一个红色的子节点
- 如图箭头所指:想要删除的节点只有一个红色的子节点
- 如果站在B树的角度:
- 待删除的元素在节点:
(16、18)和 (22、25)中 - 在删除后,变成了
(18)和(22),还是满足4阶B树的性质,不会产生副作用
- 待删除的元素在节点:
- 但是如果站在等价转换的角度来看:
- 删除后的节点是:
(18)和(22),都是红色,不能作为一个B树的节点。 - 那么需要将它们染成黑色,才能独立作为一个B树节点,也就是:
- 删除后的节点是:
- 如果站在二叉搜索树的角度:
- 这是
删除度为1的节点 - 需要找到一个替代它的子节点,让它的父节点指向替代它的子节点
- 再维护好红黑树的性质,所以最终思路是:
- 这是
③ 没有红色的子节点
- 看到这里,先恭喜你,删除的情况已经讨论了一半。什么?才一半......
- 没关系,耐心一点,毕竟红黑树对大多数人来说,可不简单呢,我们来看看这种情况
-
图中想要删除的元素是
36,可以发现,它没有红色的子节点 -
探讨这种情况前,我们也来简单复习一下B树是如何删除元素的:
- 首先明确:真正删除的元素必然在叶子节点中
- 在叶子节点中的元素,直接删除即可
- 但是在删除完成后,需要检验是否满足m阶B树的性质:
┍ m/2 ┑ - 1 ≤ 非根节点元素个数 ≤ m - 1(m为阶数) - 若满足该性质,则删除成功,不需要做额外的处理
- 若不满足,则会产生下溢现象,需要处理下溢节点
- 处理完成后可能还会继续下溢,重复解决下溢,直至完全平衡。删除才完成
-
既然提到了下溢,那顺便谈谈如何解决下溢:
- 先看看,可否与临近的兄弟节点借一个元素
- 若可以借,则通过旋转的方式,借用元素、下溢解决
- 若没得借,则需要从父节点中取出一个元素,与兄弟节点(如果有)中的元素合并成一个节点
- 从父节点中取出一个元素,可能会导致父节点也下溢。那么需要重复上面的过程解决下溢。直至下溢完全解决
-
相信到现在,不用我再解释一遍,我们学红黑树,为什么要来看B树的删除吧!
-
我们想要删除节点:
(36)节点中的元素 36
- 因为其等价的是4阶B树,有这样的
1 ≤ 节点元素数量 ≤ 3关系。 - 从图中可以发现,节点的元素数量为0了,已经超出了这个范围,也就是出现了下溢现象,所以我们下面要解决下溢
- 并且由于这是一棵红黑树,解决下溢时,需要满足红黑树的5条性质,也需要满足它们间等价转换的关系
- 既然要解决下溢,是不是又可以分类讨论了:按解决下溢的办法分类
1、临近兄弟节点有得借 —— 兄弟节点是黑色
- 我们来看图中的这几种情况:
- 在B树的眼中:待删除元素所在节点的兄弟节点是:
红黑、红黑红、黑红,它们至少可以向借一个元素(它们借出去了,也不会产生影响) - 在红黑树的眼中:待删除节点的兄弟节点是黑色的,并且至少有一个红色的子节点
- 在B树的眼中:待删除元素所在节点的兄弟节点是:
- 如果是上面的这些情况,那么向兄弟节点借一个元素过来即可。怎么借呢?
- 刚复习的,直接旋转过来即可,我们先看兄弟节点是
红黑、黑红、这两种情况:
-
如图所示,当删除元素后,在B树的角度来看,得先通过旋转来解决下溢:
- 具体如何旋转,主要是看删除节点的父节点是:
LL、LR、RR、RL中的哪一种情况,决定如何使用左旋和右旋,这里就不多赘述了
- 具体如何旋转,主要是看删除节点的父节点是:
-
然后在红黑树与其等价转换的角度来看,需要维护红黑树的性质,也就是给节点的染色:
- 将旋转到上方新的父节点颜色继承原先父节点的颜色(要满足等价转换的条件:红色节点向上合并)
- 将新父节点的左右子节点变成黑色(要满足等价转换的条件:黑色节点才能独立作为一个B树节点)
-
说完了刚刚:
红黑、黑红的两种情况,我们再来看看剩下一种可以借元素给兄弟的情况:红黑红
- 为什么要把这种情况单独拿出来说呢?从图中可以看出,不论是在B树还是红黑树的角度。两棵一样的红黑树删除同样的节点,最终结果却不同,为什么呢?
- 可以发现,在选择旋转策略的时候,用了两种方式:单旋和双旋
- 而这两种策略,最终的结果虽然不一样,可是都满足了B树的性质
- 再通过染色。来维护节点的颜色后,结果肯定也不一样,可是也都满足了红黑树的性质
- 所以,得出的结论是:导致结果不同的是旋转的策略不同。
- 而结果都皆大欢喜,也就是说,两种其实都没错。选择任意一种都可以
- 看完了上面的三种情况,是不是只有会出现:
LL、LR的情况啊?RR、RL去哪里了啊?
- 如上图所示,其实也会出现:
RR、RL,只不过待删除的节点在父节点的左边来了而已,【下面的讨论情况也是一样的】 - 那这些情况该怎么分析呢?咱们也就不必duck担心,其实不用分析了,只需要掌握好待删除节点在右边的情况即可
- 因为待删除节点在左边的情况,与在右边的情况,操作是对称的,染色的逻辑是一致的。
- 这种可以跟兄弟节点借元素的情况,关于染色,我再补充一点,小伙伴可能会有的疑惑:
- 为什么我要说:旋转后新的父节点(中心节点)必须要继承旧父节点的颜色呢?
- 看上面画的图,染色操作不都是将旋转后新父节点,染成红色吗?
- 别着急,咱们来看看下面这幅图:
- 看到上图所示的情形,如果只是简单的将新父节点染成红色的话,就不满足性质了。
(将红黑树的5条性质罚抄一百遍,相信你不敢乱染色了吧!!!) - 正确的操作是继承旧父节点的颜色
2、临近的兄弟节点没得借 —— 兄弟节点是黑色
- 说完了别人家的兄弟
(可以借),来看看自己家的兄弟(没得借)的情况,比如说下面的两种情况:
- 不是兄弟节点不想借给你,而是人家里面都只有一个元素了,根本借不出来啊
- 根据B树的性质:我们知道在兄弟没得借的时候,要在父节点中取出一个元素,与兄弟节点合并
- 说到合并,之前添加后若出现上溢现象,也要通过合并来解决,我们来看看,它们有什么相似的地方吗?
-
上图中上部是方便我们理解,等价转换出来的B树,下部分是这棵红黑树,这种情况下真正执行的操作
-
与添加一样:真正的操作其实是去染色,并没有真的去合并
- 父节点染成
【黑色】兄弟节点染成【红色】
- 父节点染成
-
染完色,我们按照红黑树等价转换成B树的性质,再次转换成帮助我们理解的B树时
-
会发现,B树的性质也得到了满足
-
之前分析添加时解决上溢,向上合并后,可能会导致上面的父节点也上溢
-
解决下溢也是同理,向下合并后,可能会导致父节点也下溢
- 的确如此,向下合并后,可能导致父节点也下溢。而且最坏的情况,会一直下溢到根节点(如上图)
- 回想之前添加时,放上去与父节点合并的元素,我们会把它当做一个新添加的元素,重复进行行添加时的分析逻辑,也就是会去递归调用添加后的处理函数
- 这里我们也利用同样的思想,从父节点中取出来的与兄弟节点合并的元素,我们可以将它当做一个被删除的节点,重复进行删除时的分析逻辑,也就是会去递归调用删除后的处理函数
3、关系很模糊的临近兄弟节点 —— 兄弟节点是红色
- 再坚持一下,我们来看看删除的最后一种情况。马上就分析完了
-
我们站在B树的角度来看:
- 第一棵红黑树:
(21)节点临近的兄弟节点是(19),可是它在红黑树中却是兄弟节点的子节点 - 第二棵也是一样的道理:
(21)节点临近的兄弟节点是(18、19),可是在红黑树中却是兄弟节点的子节点,甚至是孙子节点
- 第一棵红黑树:
-
你回看前两种讨论的情况:
- 在红黑树中,待删除节点的兄弟节点都是黑色的
- 那在B树中,与待删除的节点肯定生活在同一层级,关系很单纯
- 如果它邻近兄弟的有多的元素,向它借一个,没有就请求它们的父节点帮助
-
可是现在的这种情况:
- 在红黑树中,待删除节点的兄弟节点是红色的
- 那在B树中,与待删除的节点肯定不在同一层级,关系较为复杂
- 就算B树的临近兄弟有多的元素,也不方便直接借用。没有的话也找不到它们共同的父节点
-
好了,说了老半天,这一段没看懂没关系。你知道一个点就行了:它们的关系很复杂
-
既然它们的关系很复杂,能否转换一下,变成我们熟悉的关系呢?
- 先看最终的结果,是不是感觉很熟悉,不熟悉的话,往上翻一下,看看是不是前面兄弟节点是黑色的两种情况~~~
- 看完了结果,我来说说其中的思路:
- 1、将兄弟节点染成黑色、父节点染成红色【保证旋转后红黑树的性质不变】
- 2、将父节点左旋或右旋,将兄弟节点的子节点变自己的子节点【将侄子变成兄弟。转换成熟悉的关系】
- 这种情况矛盾的地方,其实是:在B树的临近兄弟节点中,没有红黑树的兄弟,导致了较复杂的关系
- 这句话有些绕,用上面的例子来看看:
- 再给你看看转换后:
- 这下你算是知道,为什么会矛盾了吧!!!
(4)删除的总结
- 再次强调:一定要想象出一棵红黑树,它等价的B树
- 因为删除的很多操作都具有对称性,(如:删除二叉搜索树中度为0的节点,它位于左边和右边是完全对称的操作)
- 那么,下面的总结以上面的例子为例:(删除的节点都在右边为例)
① 删除红色节点
- 删除红色节点,并不会对红黑树的性质产生影响,也不会对其等价B树产生影响,所以直接返回即可
② 删除黑色节点
- 下面提到的BST是二叉搜索树
- ❗❗❗一定要想象等价的B树。并且要知道,在B树中,真正被删除的节点,一定位于最后一层
1、删除的是BST中“度为2”的节点
- 这种情况比较特殊。在B树中看起来是删除黑色的节点,但是在红黑树中,真正被删除的是红色节点
- 其实就是上面删除红色节点的情况,那么这一种也不需要特殊处理
2、删除的是BST中度为1的节点
- 这种情况,该黑色节点有且仅有一个红色子节点
- 我们需要找到取代它的子节点,将它的子节点染成黑色即可
3、删除的是BST中度为0的节点
-
若BST中度为0,删除之后。在等价B树中会产生下溢现象
-
这里又可以分成三种情况:
ⅰ、兄弟是黑色且至少有一个红色的节点
- 如图所示:直接向兄弟借一个节点就行了。不用还的那种哟~
- 最终解决方案:旋转 + 染色
ⅱ、兄弟节点是黑色且没有一个红色的节点
-
如图所示:兄弟只有一个黑色的节点,想帮兄弟也借不了啊
-
最终解决方案:染色(合并)
-
但是有一种特殊情况,如上图右边所示:
-
父节点也是黑色的时候。将父节点向下合并时,父节点也会下溢
-
那么需要将向下合并的父节点,当做是被删除的节点,递归使其恢复性质
-
ⅲ、兄弟节点是红色
- 如图所示:就是我们当时描述的,它们的关系很复杂,我们不熟悉
- 最终解决方案:将其转换成上面的两种情况:兄弟节点是黑色且....
- 经过这样的转换,我们就能够统一使用上面的逻辑了
写在后面
- 完整代码在下一篇《红黑树删除的实现》后会一起给出,可根据文章分析尝试实现~
- 推荐阅读:
- 了解二叉搜索树 ->《二叉搜索树的实现与分析》
- 了解树的旋转 ->《透过AVL树的实现,学习树的旋转》
- 了解上溢现象 ->《你心里有B树吗?》
- 红黑树的添加 ->《红黑树添加的分析与实现》
本篇收获
- 红黑树删除的分析
- 该如何分类讨论红黑树的删除
- 复习树的旋转、B树的性质
读后思考
- 能否根据本文分析红黑树删除操作的方法,尝试用代码实现红黑树的删除呢?
- 下一篇文章,我们将一起用代码来实现红黑树的删除~