学习红黑树

红黑树归纳总结

在自学java,学习了红黑树,看了几篇文章,感觉分类、操作太过于繁琐,没有一个归纳总结,或者说是核心思想不明确,自己做了一下整理。

本文在总结归纳时,文字描述较多,没有代码,图形等较少,若要参考图形等可以参考参考文档。
在下转行的入门小白,这也是第一篇的文章,请多多指教

参考文档

30张图带你彻底理解红黑树
彻底理解红黑树(三)之 删除

基本概念

红黑树定义和性质

红黑树是一种含有红黑结点并能自平衡的二叉查找树。它必须满足下面性质:
性质1:每个节点要么是黑色,要么是红色。
性质2:根节点是黑色。
性质3:每个叶子节点(NIL)是黑色。
性质4:每个红色结点的两个子结点一定都是黑色。
性质5:任意一结点到每个叶子结点的路径都包含数量相同的黑结点
从性质5又可以推出:
性质5.1:如果一个结点存在黑子结点,那么该结点肯定有两个子结点。

注意,在讨论是一般不考虑叶子节点,因为叶子节点不影响红黑树的两个核心。

三种操作:左旋、右旋和变色

左旋:以某个结点作为支点(旋转结点),其右子结点变为旋转结点的父结点,右子结点的左子结点变为旋转结点的右子结点,左子结点保持不变。如图3。
右旋:以某个结点作为支点(旋转结点),其左子结点变为旋转结点的父结点,左子结点的右子结点变为旋转结点的左子结点,右子结点保持不变。如图4。
变色:结点的颜色由红变黑或由黑变红。

图3Alt
图4
Alt

红黑树核心思想

处理了下面两个核心,那其他任何问题都迎刃而解了。

深度

这里所说的深度,指的是路径上黑节点的个数,也是所谓平衡的依据。注意,红色节点并不影响深度。(至于前人是如何想到,利用红黑节点及相关规则来进行自平衡的,还有待进一步学习)
注意,这里有个非常重要的概念,自平衡。

颜色冲突

关于颜色的规定,其实除了性质5以外,其他都是在讲颜色。并且也非常好理解。至于冲突,无非就是以下几种情况:
1.根节点:只能为黑色。这里需要注意的是,黑色会增加深度。
2.不能有连续两个红色节点。

红黑树查找

因为红黑树是一颗二叉平衡树,并且查找不会破坏树的平衡,所以查找跟二叉平衡树的查找无异:

1.从根结点开始查找,把根结点设置为当前结点;
2.若当前结点为空,返回null;
3.若当前结点不为空,用当前结点的key跟查找key作比较;
4.若当前结点key等于查找key,那么该key就是查找目标,返回当前结点;
5.若当前结点key大于查找key,把当前结点的左子结点设置为当前结点,6.重复步骤2;
7.若当前结点key小于查找key,把当前结点的右子结点设置为当前结点,重复步骤2;

红黑树插入

注意,在对树结构CRUD的处理过程中,一定要结合到红黑树的核心思想。

插入操作包括两部分工作:一查找插入的位置;二插入后自平衡。查找插入的父结点很简单,跟查找操作区别不大:

从根结点开始查找;
1.若根结点为空,那么插入结点作为根结点,结束。
2.若根结点不为空,那么把根结点作为当前结点;
3.若当前结点为null,返回当前结点的父结点,结束。
4.若当前结点key等于查找key,那么该key所在结点就是插入结点,更新结点的值,结束。
5.若当前结点key大于查找key,把当前结点的左子结点设置为当前结点,重复步骤4;
6.若当前结点key小于查找key,把当前结点的右子结点设置为当前结点,重复步骤4;

注意,从上诉过程可以推断出,插入节点一定是原来的叶子节点处,即,插入节点是没有子节点的。
插入节点的位置找到了,接下来就处理两个核心:

深度

可以这么思考,
如果插入的节点颜色是黑色,那么意味着该节点所在的边深度+1,而兄弟节点和父亲的兄弟节点等(如果存在的话)的深度不变,势必都需要做平衡调整;
如果插入节点颜色是红色,该节点所在的边深度不变,自然不会影响到整颗树的平衡。
但是插入红色节点会有如下的问题:颜色冲突

颜色冲突

颜色冲突的两点:
如果是根节点,直接将插入节点的颜色变成黑色即可;
如果插入节点的父节点是红色,则需要进行相应的旋转和颜色处理

Alt

为简化描述的冗余度,规定:
父节点为根的子树:简称为父子树

场景说明:

情景4.1:

如果父节点是红色,则一定是没有兄弟节点的,此时,不能在父子树中解决问题了,需要上升到更高层。(这里的局部处理、递归的思想是很重要的。)
由于不能连续两个红色节点,直接将父节点修改成黑色,意味着父子树的深度+1,如果叔叔节点(即父节点的兄弟节点)存在(一定是红色),也需要变成黑色来将深度+1。
而为了将影响最小化,需要将祖父节点(一定是黑色,否则跟父节点的红色冲突)变成红色,以使祖父子树的深度保持不变。但此时需要校验并处理这一改变带来的颜色冲突,按照递归的思想的话,就是将祖父子树当做新插入的红色节点进行代码处理即可。
但是有个特例,是当自底向上递归到根节点时,需要将根节点改成黑色。根据上诉递归处理的过程可以知道,只有这个特例中,整颗树的深度+1了,其他情况下,深度均保持不变。

总结来说,上诉过程其实就是:
深度处理->颜色处理->深度处理->颜色处理…

情景4.2、4.3:

场景4.2.2可以转换成4.2.1(场景4.3也是类似的处理,只是方向不一样而已)
简单的说,就是下图这样处理,在局部的范围内,深度不变,并且向上连接的是黑色节点,不会出现颜色冲突。
在这里插入图片描述

小结

虽然图片上整了一大片,但其实在归纳总结后,很好理解,而且有了核心思想的指导,处理和思考起来思路非常清晰:
1.父节点是黑色,直接完成
2.父节点红色,关注点就在父节点的兄弟节点上了(即叔叔节点),
如果叔叔节点不存在或为黑色,那么:改变颜色->旋转
如果叔叔节点是红色,那么:改变3个颜色->向上递归

推论:只有在父节点是红色,叔叔节点存在,并且一直向上递归到根节点的情况,才可能导致树的深度+1;
否则树的深度不变

红黑树删除

红黑树和二叉搜索树的删除类似,只不过加上颜色属性(这里的子节点均属非NULL节点):
1.无子节点时,删除节点可能为红色或者黑色;
1.1 如果为红色,直接删除即可,不会影响黑色节点的数量;
1.2 如果为黑色,则需要进行删除平衡的操作了;
2.只有一个子节点时,删除节点只能是黑色,其子节点为红色,否则无法满足红黑树的性质了。 此时用删除节点的子节点接到父节点,且将子节点颜色涂黑,保证黑色数量。
3.有两个子节点时,与二叉搜索树一样,使用后继节点作为替换的删除节点,情形转至为1或2处理。

对于上述的第3点,两个参考文档在文字描述上都有些让我不好理解,其实这个地方对于二叉树搜索的删除理解也不是很到位,需要注意的是:
将树的键值和树结构本身分开看待:
删除节点,并不是说就要删除某个节点,而只是要删除这个键值而已,至于是在哪个具体的节点位置进行删除,都可以的,只要最后能保持二叉树搜索的性质即可,这里是红黑树,这样理解更深层的意思是,原本要删除的节点颜色是不用变更的!这一点对红黑树的删除可以说是关键

本文余下内容均指的是删除黑色的叶子节点后引发的一系列平衡操作。比如P->D->N,删除D(黑色)后,N接至父节点:P->N。
因为删除了一个黑色节点(N的父节点D),经过N的路径的黑色数量减1,即h(P->N->叶子) 比 h(P->S->叶子) 少1。平衡的方式有:
(1)h(P->N->叶子)不变,h(P->S->叶子)减1,此时已经子平衡;然而h(GP->P->叶子)还是会比h(GP -> U ->叶子)少1。此时需要将P当作新的N,向上递归处理;
(2)h(P->N->叶子)加1,h(P->S->叶子)不变,也就是恢复了原来的状态,此时已经平衡,因为h(GP->P->叶子)=h(GP -> U ->叶子)。

上诉文字中,最核心的是这两种方式,但真正的分类却没有明确的说出来。其实有个非常重要的点是:兄弟节点的子节点(SL SR)!
注意,不能单纯的只考虑兄弟节点的子节点是叶子节点的父节点的情况(即倒数第二层节点),因为会有自底向上递归处理的情况出现,所以SL SR的所有组合情况都要考虑。

1.SL SR都不存在

此时,没有办法在父子树的范围内通过旋转和改变颜色来在局部达到平衡,只能将兄弟节点S变成红色,父子树的深度已经-1,需要处理颜色冲突:
如果P是红色,则将P改成黑色,这样父子树的深度恢复,并且内部和向上都不存在颜色冲突,删除完成;
如果P是黑色,则需要递归的将P为根的子树看作是删除的节点,进而区分P的兄弟节点的子节点情况,若最终处理到了根节点,则整颗树的深度-1,否则整颗树的深度是在一个局部范围内恢复到原值,意味着不会影响到整颗树的深度,即不变
在这里插入图片描述

2.SL SR至少一个为红色(五种组合情况)

此时,可以通过旋转和改变颜色,来将删除一边的深度补充回来
注意,这里有个简单规律,如果要在一个子树上保持深度不变,可以结合旋转改变颜色:
左旋:左边分子树的深度+1,右边分子树的深度-1(不一定完全正确,但是一种处理深度的思路)
右旋:右边分子树的深度+1,左边分子树的深度-1(不一定完全正确,但是一种处理深度的思路)
变色:红色变黑色,深度+1(注意,一般情况下不要将黑色变红色,以免需要后续的颜色冲突处理)
上述三种情况需要组合使用。
例如:
先右旋,再左旋
在这里插入图片描述

3.SL SR全都为黑

3.1S是黑色

这种情况比较特殊,不能用左旋、右旋的组合,只能将兄弟节点S变成红色,并且将P节点作为替换节点,向上处理深度和颜色冲突(迭代思想)。

3.2S是红色

这时可以直接对P进行旋转,并将P和S的颜色互换,再重新考虑删除/替换节点D,如下图
在这里插入图片描述

参考文档的全部分类如下:

Alt

小结

1.按照兄弟节点的子节点情况进行分类
2.左旋、右旋和颜色改变来处理深度(注意颜色改变,只有在S为黑色,且两孩子都为黑色的情况下,才需要将S变成红色;其他情况都是将红色变成黑色,这样处理深度不用考虑颜色冲突的问题)
3.在深度处理之后进行颜色处理

处理的具体思路:

(注意,这里的删除节点,不一定是指定的键值所在的节点,而是实际存在过程中找到的替换节点原本所在的位置)
1.删除节点是红色,直接完成
2.删除节点是黑色,兄弟节点没有子节点时:改变颜色->向上递归
3.删除节点是黑色,兄弟节点的子节点至少有一个红色:旋转+改变颜色(有可能会有一次或两次相反旋转,其实也不用多想,一次不行就两次呗)
4.删除节点是黑色,兄弟节点的子节点为全为黑色,兄弟节点是红色:旋转+改变颜色->新场景下重新考虑删除/替换
5.删除节点是黑色,兄弟节点的子节点为全为黑色,兄弟节点是黑色:改变兄弟节点颜色为红色->向上递归

上述的向上递归中,如果遇到一个父节点是红色,变成黑色即完成;
否则需要将父节点作为替换节点,继续向上递归

推论:
只有当“.删除节点是黑色,且兄弟节点是黑色,且子节点不存在或全都是黑色”这种情况才会向上递归,并且只有当递归到根节点时,整颗树的深度-1;
否则其他任何情况,树的深度不变。

后结

由于红黑树的运算带来的算法优势是非常大的,相应的对逻辑结构的维护就会变得复杂,但这种复杂程度是可以接受的,并且有了深度、颜色冲突和删除过程中的替换这三个核心思想,在解决实际场景时,理解起来也比较容易。
后续可以适当进行一些练习:
红黑树的删除全过程图化
算法导论习题练习——红黑树的插入和删除

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值