红黑树,插入和删除,更好的理解

        红黑树是一种b树,二叉树。它在最差的条件下查找效率仍然很高,不会出现普通的二叉查找树在特定条件下退化成单链表的情况。如图1,依次插入5,4,3,2,1。这主要是红黑树能通过旋转和变色等规则来自我调整,保证每次插入或者删除能够维持黑色结点深度相同。

图1. 普通二叉树和红黑树插入对比

        红黑树理解和实现有一定的难度。虽然网上其实有很多相关的资料和讲解,但我也花了很大功夫才弄明白。有不少文章,不可否认,写得很详细,但我就是有些地方看不懂,尤其是删除部分;有些文章则写的太简短了,甚至有些文章还写错了。但好在,我在B站上找到一个很不错的视频:阿里P7大佬手撕红黑树全套教程,视频中的老师讲的很不错,我这篇文章也是基于他的讲解,并且加上些我个人的理解。同时推荐一个在线生成红黑树的网站:Red/Black Tree Visualization

        本文中,我不会介绍太多红黑树的基础理论知识,而是偏向于分享理解插入和删除的操作,并且是基于2-3-4树的。大家需要对二叉查找树有些了解。希望这篇文章能对大家有所帮助,同时要是有疑问和错误,也欢迎评论区互动。

        首先,介绍一些红黑树的性质,我们的插入和删除都是为了满足这些条件。

        1. 红黑树的结点只有两种,红色和黑色。

        2. 根结点规定为黑色。

        3. 叶子结点,其实是普通二叉树叶子结点的null,规定为黑色。(图中经常会省去叶子结点)

        4. 不允许两个红色结点直接相连。

        5. 从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点。

        在我看来,这里比较重要的是性质4和性质5。需要稍微解释一下的就是性质5了,其他性质大家都能看得懂。图2是我随机生成的一个红黑树:

                                                               图2. 红黑树

        要注意的是,图中没画出性质3的叶子结点,但无伤大雅。我们主要看性质5。拿根节点4举例,到叶子结点每条路径都有三个黑色结点(包括自己和没画出的叶子结点),比如4-2-null,4-15-null。或者拿结点10举例,到叶子结点每条路径都有两个黑色结点,比如5-null,15-null。

         第一段说过,红黑树能通过旋转和变色来自我调整。变色不用多说,按例子来分析。旋转分为左旋和右旋。一般以一个子树的父结点为支点,比如左旋,就是让这颗树往左旋转,也就是逆时针,父节点下去,右节点上来,右节点的左子结点成为父节点的右子结点。如图3和图4,分别是左旋和右旋。

                                                            图3. 左旋示意图

                                                            图4. 右旋示意图

        往哪边旋,就把父节点往哪边下去,把另一边的子结点提上来当新的父节点,同时把离父结点近的子结点送给父节点。左旋,左节点仍然是父节点的左节点,右旋,右节点仍然是父节点的右节点。想更详细看,可以看后面代码解析部分。

        这里还不涉及变色,但不管怎样,我们可以发现,旋转完后,起码二叉查找树的特点没变,结点的左子树仍然小于该结点,右子树仍然都大于该结点。并且,左边结点少了,就左旋,从右边借些结点过来。

        接下来开始插入的介绍了:

        首先,我们会将要插入的结点设为红色。为什么不设成黑色?因为,首先,红色结点插入不会破坏原有的黑色平衡,只有在父节点也是红色时才需要调整,但如果插入的是黑色结点,那在原来黑色平衡的基础上,这个黑色结点的加入肯定会破坏平衡,需要调整的情况就很多,很麻烦。

        插入有很多情况,我喜欢从简单的开始分析,留下复杂麻烦的最后啃。

        1. 插入的是空树

        那插入结点就是根节点,根据性质2,我们只需要把插入结点颜色变黑即可。

        2. 插入的结点key值已经存在

        那也很简单,只需要更新一下该key结点的value值即可。

        3. 插入位置的父节点是黑结点

        因为插入的是红结点,不会破坏平衡,直接插入即可。

        4. 插入位置的父节点是红结点

        麻烦的来了,因为插入的是红结点,而性质4,不允许两个红色结点直接相连。所以就需要我们分情况做出调整了。我们可以发现,父结点是红结点,根据性质,那一定会存在黑色祖父结点,同时,叔叔结点要么不存在(也就是叶子null),要么就是红结点。因为如果叔叔是普通黑结点(不是叶子null),那叔叔这边深度就会比父亲这边大,这违背了性质5。

        4.1 叔叔结点不存在(叔叔结点是叶子null)

                4.1.1插入结点和父结点在同一侧,如图5

图5. 插入结点和父结点在同一侧的情况

         这种情况,我们需要1. 变色,将祖父结点变红,父结点变黑;2. 旋转。

                4.1.2 插入结点和父结点不在同一侧,如图6

图6. 插入结点和父结点不在同一侧的情况

        这种情况,我们只需要把它变成在同一侧的情况,然后再按上面同一侧的情况处理即可。需要注意的是,这里的插入结点和父结点和上面同一侧是反过来的,变色的时候要注意。

 4.2 叔叔结点为红色

        这种情况只需要变色即可,如图7,祖父结点变红,父结点和叔叔结点变黑。但是由于祖父结点变红,又得看看祖父结点的父结点是不是红的,如果是红的,那就得把祖父结点当作插入结点再循环一次。

    图7. 叔叔结点为红色的情况   

         插入的情况就这些,大家可以用我推荐的红黑树生成网站检验一下。接下来是重头戏,删除。

        删除的情况就很多了,这里采用了视频中老师的方法来理解,感觉简化了很多。不然可以用穷举的方法,那可就太麻烦了。

        首先,删除一个结点,主要分为三类情况,1. 这个结点有两个子结点(不包括叶子null);2. 这个结点只有一个子结点(不包括叶子null);3. 这个结点没有子结点(不包括叶子null)。看起来,最麻烦的应该是有两个子结点的,但了解二叉查找树的读者肯定知道,这种情况都会转换为情况2和情况3。

1. 这个结点有两个子结点

        首先我们需要找到这个结点的后继结点(或者前驱结点),然后把要删除的结点的key和value都换成后继结点的。接着,我们已经将问题转换为,删除后继结点了。如图8

                                                       图8. 删除有两个子结点的结点

         我们想删除10这个结点,它有两个子结点,我们就需要先找到它的后继结点,也就是它的右子树中最左边的结点,结点11,然后用结点11替换掉结点10的key和value,颜色不用变。接下来就只用删除掉下面的结点11了。

因为后继结点或者前驱结点只可能是叶节点或者只有一个子结点,也就是情况2和情况3。所以,有两个子结点的情况1总能转换为情况2或者情况3。也就是删除结点只会删掉最下面两层的结点。

2. 这个结点只有一个子结点

        这种情况,只有一种可能:此结点为黑结点,有一个红色的子结点。如果这是个后继结点,那子结点还只能是右子结点。因为,假如此结点为黑色,只有一个黑色子结点,或者此结点为红结点,只有一个黑色子结点,那就不平衡了,或者此结点为红色,子结点也为红色,这违背了性质4。如图9。

图9. 只有一个结点的情况

         这种情况也简单,只需要删掉这个结点,然后用子结点换成它的颜色(黑)然后接替它即可

 3. 这个结点没有子结点

        这种情况看起来是最简单的,其实是最麻烦的。老规矩,从简单的情况开始分析。

        3.1. 这个结点是红结点

        那直接删除即可,不会影响黑色平衡。

        3.2. 这个结点是黑结点

        麻烦的来了!由于是黑结点,删掉后,肯定会导致黑色不平衡。需要再分情况讨论。这里参考了视频中老师的方法。老师的思路是,删掉这个黑色结点后,删除结点这边的子树黑色结点减一,就需要通过旋转从另一边的子树借结点过来平衡。所以依据此分为,能不能从兄弟结点那边借。

        3.2.1 能从兄弟结点借

        首先需要注意的是,这个兄弟结点一定是黑色的结点。如果兄弟结点是红色结点,需要通过旋转,让它的兄弟结点为黑色。这是因为,这个借兄弟结点,其实依据的是2-3-4树。这种兄弟结点为红色对应的是2-3-4树中,父结点为一种3结点(7|9)的情况,如图10。

 

图10. 兄弟结点为红色结点情况

         图10中,我们想删掉黑色叶结点6,但红黑树中,它的兄弟结点是红色结点,所以我们需要变色,将父结点7和兄弟结点9交换颜色,接着以结点7为支点左旋,现在6的兄弟结点就是黑色结点8了。

        现在兄弟结点是黑色的了,并且兄弟结点只会有红色子结点或者没有子结点。不会存在没有兄弟结点的情况,因为要删除的结点就是黑色的,所以肯定有一个黑色的兄弟结点来平衡黑色。一直说的能不能借,指的就是这个兄弟结点有没有红色的子结点,因为红色子结点不算在黑色平衡里,所以,在删掉黑色结点后,我们可以让这个红色结点变黑,并旋转,来平衡黑色。

        这里就有三种情况:

        3.2.1.1 兄弟结点有在同一侧的红色子结点

图11. 兄弟结点有在同一侧的红色子结点

         如图11,这里不需要考虑父结点的颜色,只需要通过左旋,将父结点改为黑色并拉下来,代替要删除的结点,兄弟结点改为父结点的颜色,上去代替父结点,而兄弟结点的红色右子结点变为黑色,代替兄弟结点。相当于,通过旋转,大家按顺序接替前面那个人的位置。之后再把要删除结点删去即可。而删除结点为右子结点则只是反过来即可。

        3.2.1.2 兄弟节点有不在同一侧的红色子结点

           

图12. 兄弟节点有不在同一侧的红色子结点

         如图12,兄弟结点的子结点为左子结点,它本身为右子结点,按照前面的思路,我们只需要通过变色,再旋转,让它变为同一侧的即可。在这里,将兄弟结点的颜色变为红,它的子结点颜色变为黑,再右旋,现在两者在同一侧了,接下来按上面同一侧的流程即可,要注意的是,现在的兄弟结点是原来兄弟结点的子结点。

        3.2.1.3 兄弟节点有两个红色子结点

        这种情况其实就是第一种情况,按同一侧的处理即可。

        3.2.2 兄弟结点没得借

        这就是意味着,兄弟结点没有红色子结点。这种情况就只能将深度减少往上传了。我们需要,将兄弟结点变为红色,父结点变为黑色。如图13

图13. 兄弟结点没有子结点的情况

         如图13,这里分为两种情况,父结点是红色的话,那就简单了,变色删掉即可,但父结点是黑色的话,由于深度不可避免的减1,就需要往上循环,以父结点为删除结点再进行一轮变色,直到删除结点循环到根结点为止,最后再删掉结点。

        到此删除的情况就讲完了。大家可以在那个红黑树生成网站上尝试一下。删除主要在于理解这个能不能借是什么意思。其实还可以列出所有可能的情况 ,不停的if if if,但这样不好记忆,而且写代码也很复杂。欢迎大家在评论区提出疑问和我的错误。

        下一篇我将附上相应的C++代码。

        红黑树,插入和删除,基于C++的实现

        

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值