红黑树基础-第二篇-删除

写在前面

总结了红黑树的几篇文章,总感觉意犹未尽,从当初我惧怕红黑树到现在理解红黑树,真是下了不少功夫,但是也不得不感谢一篇文章,讲红黑树还算透彻,图解比较好,但是仍然有部分我不理解的地方或是我觉得有缺陷的地方,所以我决定自己总结一番彻底搞懂红黑树,并结合java8中HashMap的源码部分进行分析,简直睡觉都迟迟不能忘怀,梦里都是树节点移动的样子。

刚开始,红黑树犹如我面前的一座大山,我真的很畏惧它,它太庞大了,越过他要考虑很多种分支情况,稍不注意就会陷入迷宫陷阱,因此我很敬畏它,换句话说,我很敬畏算法,因为我搞不定他。

每当我每次信誓旦旦打算彻底看看红黑树的原理时,要么看HashMap中红黑树源码就看不懂了,要么翻遍别人的博客看别人的文章时,也觉得别人写的不够清楚,他写的我也知道,要么就是图解不清晰。总而言之,分析了几种情况就进行不下去了,不知道为什么要这样分析推理,所以那个时候自己的心也很简单。

心里想着,反正红黑树这么难,别人知道的也不会有几个人吧,甚至我朋友说,你leader的leader说不定也理解不了,我只能一笑而过,所以好吧,我放弃了。要么就是自己心里太浮躁,不能沉下心来认真分析图解每一种情况,还有可能就是自己真没有一大段时间去积累,去看,去分析源码和图,因为红黑树确实吃透需要的时间很多,我在有一定知识储备的基础上,写了3-4篇博客连续用了一周的时间才把红黑树理解下来弄懂,所以坚定的毅力还是必须要有的。

刚开始看的时候,我连左旋右旋都觉得很别扭,不能快速旋转建立树结构,但是多看看多想想就觉得总算是趟过了山前面的一条小河,这下有信心了一点,然后开始看红黑树的平衡插入,开始分析每一种插入节点的情况,静下心来看,然后看HashMap源码中平衡插入部分,逐渐柳暗花明,慢慢的理解了平衡插入,这个时候我信心倍增。我下定决心要把平衡删除啃下来。

但是看来我想错了,平衡删除远比平衡插入要难,理解起来更复杂,图解也会复杂一些。我又回到了起点。但是我没有放弃,当我打算放弃时,我还是回头了,因为我已经走到了半山腰,我不得不耐心的走下去,开始认真分析每一种情况,为什么要节点互换等,为什么节点的变色要这么变。走到山那边,才发现世外桃源,原来山看上去很高,但实际上很窄。

不得不说,以下红黑树的删除部分是红黑树最难的部分,能有幸啃下来,也算是618期间奖励自己一瓶红牛吧,(tips:今天6月17日晚,大家都值班,算有空)

红黑树的删除

红黑树的删除操作也包括两部分工作:一是查找目标结点;二是删除后自平衡。

查找目标结点显然可以复用查找操作,当不存在目标结点时,忽略本次操作;当存在目标结点时,删除后就得做自平衡处理了。

删除了结点后,我们还需要找结点来替代删除结点的位置,不然子树跟父辈结点断开了,除非删除结点刚好没子结点,那么就不需要替代。

二叉树删除结点找替代结点有 3 种情景:我们假设现在暂不考虑变色等,因为删除节点后还要满足红黑树的性质,我们暂不考虑,只为了先保持树的结构。

  • 若删除结点无子结点,直接删除。
  • 若删除结点只有一个子结点,用子结点替换删除结点。
  • 若删除结点有两个子结点,用后继结点(大于删除结点的最小结点)替换删除结点。

补充说明下,情景 3 的后继结点是大于删除结点的最小结点,也是删除结点的右子树中投影到数轴的最左结点。

那么可以拿前继结点(删除结点的左子树最左结点)替代吗?可以的。但习惯上大多都是拿后继结点来替代,后文的讲解也是用后继结点来替代。

另外告诉大家一种找前继和后继结点的直观的方法:把二叉树所有结点投射在X轴上,所有结点都是从左到右排好序的,所有目标结点的前后结点就是对应前继和后继结点。如下图

接下来,讲一个重要的思路:删除结点被替代后,在不考虑结点的键值的情况下,对于树来说,可以认为删除的是替代结点!话很苍白,我们看下图。

我们再来分析情景1-3删除的场景,(暂不要考虑红与黑的性质)

情景1:删除的就是叶子结点,比如上图中的D节点,M节点等

情景2:删除节点只有唯一的子节点,要么是左孩子要么是右孩子,反正是只能有一个孩子,如下图

上图举的例子是假如删除节点R,那么我们会用Q节点去替代他的位置,Q的后代节点保持不变,当然切记,这只是万里长征的第一步,明显可以看到出现了两个节点都是红色还是父子关系,P和Q。但是我们暂时不管,因为后续我们还是做变色和旋转处理,使这个树满足红黑树的特性。

情景3:这种情况最复杂,假如我们删除P节点,我们让大于P的最小的节点,也就是Q去替换P节点,为什么要用Q,因为Q放在P的位置满足二叉树的排序特性,然后此时P节点不就满足情景1和情景2了吗,看下图

当互换后Q的右孩子,变成了P的右孩子,首先我们知道,原本Q的位置肯定是没有左孩子的,这点不难理解,因为有左孩子就不是Q去替换P,而是Q的左孩子去替换P,因此Q肯定没有左孩子。当然Q的右孩子也可能为空,所以我们不确定的情况下,就用灰色节点吧。

那么现在,情景3变成了这样,现在要删除换位置之后的P节点了,这样又回到了情景1和情景2的情形,如果灰色节点不为空,那么就是情景2,如果灰色节点为空,那么就是情景1。

那么情景3的情况,左边的图删除P就变成右边的图删除P,那么这样分析就简单很多了。

那么总而言之,只有情景1和情景2的场景了,下面来仔细画图分析一下

情景1:假设要删除叶子结点,假设现在要删除Q节点,我们只分析一种情况,就是Q是父节点的左孩子的情况,另一种情况Q是父节点的右孩子的情况类似,就不费篇幅讲解了。看下图

这里p==replacement先放在这,这里主要是为了以后解释HashMap的源码用到,代表变量p和变量replacement都指向节点Q,此时Q是叶子结点。如果不管红黑树的性质,那么直接删除Q即可,下面如果考虑红黑树的性质呢,那么就复杂了

首先,假如要删除的Q他是红色的,那么直接删除。不会影响红黑树的5个性质。但是如果删除的节点Q他是黑色的,那就糟了,因为删除了Q,比如上面那个图,那么父节点R的左子树就少了一个黑色节点,对于以R为根节点的子树就不满足红黑树的第5条性质了。那么就要变色和旋转,让R的左右子树的黑节点相同了。怎么变色和旋转,稍后再讲述

情景2:假设要删除的节点不是叶子结点,他有一个孩子,当然我们不知道是左孩子还是右孩子,我们就拿右孩子举例子吧

这里p != replacement先放在这,这里主要是为了以后解释HashMap的源码用到,代表变量p指向节点Q,此时Q是要删除的结点。replacement是要替代它的节点,如果不管红黑树的性质,那么直接删除Q即可,下面如果考虑红黑树的性质呢,那么就复杂了

首先,假如要删除的Q他是红色的,那么直接删除。不会影响红黑树的5个性质。但是如果删除的节点Q他是黑色的,那就糟了,因为删除了Q,比如上面那个图,那么父节点R的左子树就少了一个黑色节点,对于以R为根节点的子树就不满足红黑树的第5条性质了。那么就要变色和旋转,让R的左右子树的黑节点相同了。怎么变色和旋转,稍后再讲述

好啦,下面开始进入正题,该怎么变色和旋转了,为了方便看代码,分析问题,我们让节点标识和源码中的变量名一致

灰色结点表示它可以是红色也可以是黑色。

值得特别提醒的是,x 是即将被替换到删除结点的位置的替代结点,在删除前,它还在原来所在位置参与树的子平衡,平衡后再替换到删除结点的位置,才算删除完成。

我先说下HashMap源码中的做法,如果是情景2的情况,那么replacement不等于删除节点p,那么我们先删除p节点,即先让p与树断开连接,让replacement顶上去,再通过balanceDeletion方法进行调整。如果是情景1的情况,那么先不断开删除节点p和树的连接,此时p==replacement,然后通过balanceDeletion进行调整,调整之后再删除p节点。(为什么叫p完全是按照HashMap源码变量名称呼的,见removeTreeNode方法,或者见第3篇红黑树源码的讲解)

那么情景1和情景2的区别就是,前者先进行调整再删除p,后者是先删除p再进行balanceDeletion调整,p是要删除的节点,replacement是替代节点,他们可能不是同一个。

注意:只有删除节点是黑色,才会进行balanceDeletion调整,为什么?稍后会讲到

万事俱备,我们进入最后的也是最难的讲解。

删除情况1:删除结点是红色结点。

由于删除节点是红色,如果删除节点p是叶子结点,那么直接删除即可,这样也不违反红黑树的5点性质;如果删除节点p是情景2的情况,那么直接用replacement顶替他即可,因为p是红色,直接删除了也不违背红黑树的5点性质。

删除情况2:删除节点是黑色

我们先换位思考一下,首先原本树是红黑树,整棵树都满足红黑树的5点性质,现在我们要删除这个黑节点,不管先调整再删除p还是先删除p在调整(调整指的是进入balanceDeletion方法),p的父节点xp,他的某个子树都缺少了一个黑节点,或者最后也要少一个黑节点。因此我们要通过balanceDeletion来调整使其满足红黑树的性质,

以上图为例讲解,我们就讲述一种通用情况,其他情况类似。此时x是xp的左子节点,x就是替代节点,也就是replacement节点。

重点:现在可以知道的是,x已经顶替了删除的节点(情景2),或者最后会被删除(情景1),反正xp的左子树是少一个黑节点,他肯定不满足红黑树的第5点性质

我们现在要做的,balanceDeletion要做的就是调整使xp的左右子树满足红黑树的性质,并且xp的颜色相对不变,相对不变的意思是(xp可能位置变了,可能不是他的父节点的子节点了,但是他原来父节点的子节点的颜色依然是xp原来的颜色,我不知道我说清楚没有,有点绕)

好的,我先提前告诉你们一点提示,假如xp的右子树,也就是x替代节点的叔叔及其后代有红节点,那么我们把这个红节点拿到左子树这边,然后把它变成黑色不就行了吗,这样左边补充了一个黑色节点,右边黑色节点也不会变少,这样刚好满足红黑树的性质,哈哈,思路确实是这样,但是实现起来可不简单哦,且看我分析每一种情况,以及怎么变色旋转。

思路:首先我们可以这样去理解,假如替换节点是R,那么以R为根的子树是少一个黑色节点的,是违背红黑树的性质的,那么调整的目的就是从R的叔叔节点借一个红色节点过来,然后把这个红节点变成黑色,这样左右子树的黑节点就平衡了。还有一种情况,就是叔叔子树没有红节点,那没法借一个红节点过来,那我们可以把他的叔叔节点变成红色,这样左右子树就平衡了,但是这样带来一个新问题,就是R的父节点为根的子树比他的叔叔子树又少一个黑色节点,因此类比,我们进行自底向上的处理,把R的父节点当成替代节点,让该父节点的叔叔子树去接一个红节点,如果还借不到,继续向上处理(直至root)找兄弟借。

删除情景 2.1:替换结点是其父结点的左子结点。

删除情景 2.1.1:替换结点的兄弟结点是红结点。

                                                                        图 2.1.1:删除情景 2.1.1

首先R节点是黑色节点,根据红黑树的扩展性质2,必然存在叔叔节点,否则违背定义5

假设若兄弟结点是红结点,那么根据红黑树定义4,兄弟结点的父结点和子结点肯定为黑色,不会有其他子情景。我们按照图2.1.1的方式处理,得到最右侧图(图中标号错误),此时R依然为替代节点,可以看到旋转后,SL和SR的黑色节点都满足平衡,依然是以R为根的子树不平衡,少一个黑节点。此时这样处理的变化就是R作为替代节点的角色不变,他的兄弟节点变成了黑色,我们记为2.1.2情景。

删除情景2.1.2:替换结点的兄弟结点是黑结点。

当兄弟结点为黑色时,其父结点和子结点的具体颜色也无法确定,此时又得考虑多种子情景。

删除情景 2.1.2.1:替换结点的兄弟结点的右子结点是红结点,左子结点任意颜色。

                                                                        图 2.1.2.1:删除情景 2.1.2.1

即将删除的左子树的一个黑色结点,显然左子树的黑色结点少 1 了,然而右子树又有红色结点,那么我们直接向右子树“借”个红结点来补充黑结点就好啦,此时肯定需要用旋转处理了,如图 2.1.2.1 所示。

处理:将 S 的颜色设为 P 的颜色,将 P 设为黑色,将 SR 设为黑色,对 P 进行左旋。

平衡后的图怎么不满足红黑树的性质?前文提醒过,R 是即将替换的,它还参与树的自平衡,平衡后再替换到删除结点的位置,所以 R 最终可以看作是删除的。

删除情景 2.1.2.2:替换结点的兄弟结点的右子结点为黑结点,左子结点为红结点。

兄弟结点所在的子树有红结点,我们总是可以向兄弟子树借个红结点过来,显然该情景可以转换为情景 2.1.2.1。如图 2.1.2.2所示:

                                                                        图 2.1.2.2:删除情景 2.1.2.2

处理:将 S 设为红色,将 SL 设为黑色,对 S 进行右旋,得到情景 2.1.2.1,进行情景 2.1.2.1 的处理。

删除情景 2.1.2.3:替换结点的兄弟结点的子结点都为黑结点。

好了,此次兄弟子树都没红结点“借”了,兄弟帮忙不了,找父母呗,这种情景我们把兄弟结点设为红色,再把父结点当作替代结点,自底向上处理,去找父结点的兄弟结点去“借”。

但为什么需要把兄弟结点设为红色呢?显然是为了在 P 所在的子树中保证平衡(R 即将删除,少了一个黑色结点,子树也需要少一个),后续的平衡工作交给父辈们考虑了,还是那句,当每棵子树都保持平衡时,最终整棵总是平衡的。

                                                                        图 2.1.2.3:删除情景 2.1.2.3

处理:将 S 设为红色,把 P 作为新的替换结点,重新进行删除结点情景处理。

删除情景 2.2:替换结点是其父结点的右子结点。

好啦,右边的操作也是方向相反,不做过多说明了,相信理解了删除情景 2.1 后,肯定可以理解 2.2。

删除情景 2.2.1:替换结点的兄弟结点是红结点。

                                                                        图 2.2.1:删除情景 2.2.1

处理:将 S 设为黑色,将 P 设为红色,对 P 进行右旋,得到情景 2.2.2,进行情景 2.2.2 的处理。(图中写错了)

删除情景 2.2.2:替换结点的兄弟结点是黑结点。

删除情景 2.2.2.1:替换结点的兄弟结点的左子结点是红结点,右子结点任意颜色。

                                                                        图 2.2.2.1:删除情景 2.2.2.1

处理:将 S 的颜色设为 P 的颜色,将 P 设为黑色,将 SL 设为黑色,对 P 进行右旋。

删除情景 2.2.2.2:替换结点的兄弟结点的左子结点为黑结点,右子结点为红结点。

                                                                        图 2.2.2.2:删除情景 2.2.2.2

处理:将 S 设为红色,将 SR 设为黑色,对 S 进行左旋,得到情景 2.2.2.1,进行情景 2.2.2.1 的处理。

删除情景 2.2.2.3:替换结点的兄弟结点的子结点都为黑结点。

处理:将 S 设为红色,把 P 作为新的替换结点,重新进行删除结点情景处理。

综上,红黑树删除后自平衡的处理可以总结为:

自己能搞定的自消化(情景 1)

自己不能搞定的叫兄弟帮忙(除了情景 1、情景 2.1.2.3 和情景 2.2.2.3)

兄弟都帮忙不了的,通过父母,找远方亲戚(情景2.1.2.3和情景2.2.2.3)

哈哈,是不是跟现实中很像,当我们有困难时,首先先自己解决,自己无力了找兄弟姐妹帮忙,如果连兄弟姐妹都帮不上,再去找远方的亲戚了。这样记忆应该会好记点~

好了,到这里红黑树的删除部分就要结束了,等把这篇文章看懂就可以去看我的红黑树基础-第三篇-源码解析,看看HashMap源码中关于红黑树删除部分是怎么做的。

最后也非常感谢这篇关于红黑树的文章给我的灵感,写的也非常透彻,如果看不懂我写的就看别人的吧!

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值