红黑树详解,对插入旋转独到理解

一、红黑树的简介 

R-B Tree,全称是Red-Black Tree,又称为“红黑树”,它一种特殊的二叉查找树。红黑树的每个节点上都有存储位表示节点的颜色,可以是红(Red)或黑(Black)。

红黑树的特性:

(1)每个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
(4)如果一个节点是红色的,则它的子节点必须是黑色的。 [注意:可以出现父节点和子节点都是黑色]
(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

注意
(01) 特性(3)中的叶子节点,是只为空(NIL或null)的节点。
(02) 特性(5),确保没有一条路径会比其他路径长出俩倍。因而,红黑树是相对是接近平衡的二叉树。

红黑树的应用:

红黑树的应用比较广泛,主要是用它来存储有序的数据,它的时间复杂度是O(lgn),效率非常之高。

  • 广泛用于C++的STL中,map和set都是用红黑树实现的.
  • 著名的linux进程调度Completely Fair Scheduler,用红黑树管理进程控制块,进程的虚拟内存区域都存储在一颗红黑树上,每个虚拟地址区域都对应红黑树的一个节点,左指针指向相邻的地址虚拟存储区域,右指针指向相邻的高地址虚拟地址空间.
  • IO多路复用epoll的实现采用红黑树组织管理sockfd,以支持快速的增删改查.
  • ngnix中,用红黑树管理timer,因为红黑树是有序的,可以很快的得到距离当前最小的定时器.
  • Java集合中的TreeSetTreeMap和hashMap树化为红黑树,C++ STL中的set、map,以及Linux虚拟内存的管理,都是通过红黑树去实现的。

二、红黑树插入删除

1.红黑树插入最多旋转两次,删除最多旋转三次,为啥之后说。

2.深入理解五条性质

(1)每个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
(4)如果一个节点是红色的,则它的子节点必须是黑色的。 [注意:可以出现父节点和子节点都是黑色]
(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

1).从第五条我们得知不能直接插入一个黑色节点,会造成黑色节点个数多一,所以插入操作我们都是插入的红色节点,插入后有问题再进行变色或者旋转

2).如果在一个黑色节点上插入一个红色节点,不会破坏红黑树。在任何节点上插入一个红色节点只会违背性质4,不会直接违背性质5。

3).最长路径的节点个数不会超过最短路径的节点个数的二倍?

最短路径是全黑,最长路径是从根节点开始黑、红相间,黑红相间会使红节点和黑节点的数量一样多,就是最短全黑的2倍。

 3.红黑树插入

我们得知不能直接插入一个黑色节点,会造成黑色节点个数多一,所以插入操作我们都是插入的红色节点,插入后有问题再进行变色或者旋转

插入红色节点有这几种情况:

(1).父亲是黑色节点,直接插入,不会破坏红黑树

(2).父亲是红色节点氛围三种情况:

1).父红叔红

插入节点8时,父亲节点15和叔叔节点35都是红色的,处理方法:

(01) 将父节点设为黑色。 红变黑只需要关心黑色节点的数量问题
(02) 将叔叔节点设为黑色。
(03) 将祖父节点设为红色。祖父节点开始肯定是黑色,因为父亲节点是红色
(04) 将祖父节点设为红色,再看祖父节点的父节点是不是红色,如果是需要把祖父节点看作刚插入的节点继续处理。

为什么这样处理:  先声明问题是插入的节点和父节点都是红色,处理方式就是变色,可能有的同学会问那为什么不是旋转呢?其实旋转也是为了变色,如果不能直接通过变色维持红黑树的时候,才需要通过旋转后再变色来维持这棵红黑树

那变色如何变呢?

首先肯定是把父亲节点变黑色,这样就不违背性质4,但是带来一个问题,只要包含父亲节点的路径上黑色节点数量都增加了1(最多两条最多有两个孩子),为了解决这个问题我们把祖父节点变成红色,这样包含祖父节点的路径黑色节点数量都减少了1,包含祖父节点的也是最多两条路径分别是父亲、叔叔这两条,父亲这条路径上黑色节点数量相当于没有变化(抵消了),但是叔叔这条路径减少了1,那只能把叔叔节点变成黑色来抵消黑色节点数量少1。

变色完成后如图:

变化完后,需要再把祖父节点当作新插入的节点再去判断红黑树是否平衡,因为如果祖父的父亲节点也是红色就违背了性质4,上图经过变换后祖父节点25违背了性质4,需要继续:

2). 父红叔不红,插入的子节点和父节点在同一边

把变换后的祖父节点25当作新插入节点,父节点50是红色,叔叔节点150是黑色节点。

这时仅仅变换颜色是不能达到平衡的,这种情形需要经过一次旋转,然后变色可以达到把多的红色分到另一边的目的,因为另一边是黑色,加一个红色节点不会破坏性质

为什么会想到这么做?上面我们提到解决这个问题的本质就是变色,旋转也是为了更好的变色,变色肯定变的是插入节点的父节点,按照我们的方式1上图中的父亲节点50变为黑色,祖父节点100变成红色,这样叔叔节点那一路径会少一个黑色节点,方式1中叔叔节点是红色的我们直接变黑就维护了红黑树,但是这次叔叔是黑的咋办?变换颜色后的场景不平衡点在于叔叔这一方的路径会比父亲这一方的路径黑色节点少一,根本原因父节点变黑了,叔叔节点没变,我们以父节点50(现在是黑色)为轴旋转一下,这样使得叔叔这条路径的黑色节点加1,原父亲路径上的黑色节点没变,正好解决了这个问题。

以上是我自己的理解,这样可以帮助理解为啥会旋转然后变色的。

变化完后:

 还有一个疑问75节点这颗子树,100节点变成红色后,直接把75节点变成100的孩子不会造成红黑树的不平衡吗,主要是性质5黑色节点的个数。

不会首先我们要明白,不平衡的关键在于50节点变成了黑色,父亲这一路径多了这个50的黑节点,75这颗子树在小范围内,是和其它不包含50这个节点的路径的黑色节点的数量一致例如以25为节点的子树、150为节点的子树,所以在100节点变成红色后,直接把75节点变成100的孩子不会造成红黑树的不平衡。

3). 父红叔不红,插入的子节点和父节点不在同一边

75节点当作新插入的节点,和父亲节点50都是红色违背了性质4。

这种情况思路是把这边多余红色分到另一边去,再同一边时可旋转改变,所以需要把插入子节点旋转到和父亲节点在同一边

我还用自己理解的那一套,先变色,把父亲50节点变成黑色,祖父100节点变成红色,叔叔节点是黑色,那就以50节点为轴旋转,发现转不了,学过AVL树我们就知道类似LR旋转,需要把插入子节点旋转到和父亲节点在同一边然后再LL旋转,这个是一样的道理。

如图:

先把插入子节点75旋转到和父亲节50点在同一边 

 然后在按照方式2的方式最终变成:

 以上就是红黑树插入的所有情况,可以看到最多就旋转两次。

 4.红黑树删除

(1).无子节点时,删除节点可能为红色或黑色

1).如果为红色,直接删除即可,不会破坏红黑树的性质

2).如果为黑色,则需要进行删除平衡的操作(后面详细分析)

(2).如果只有一个子节点时,我们要删除的这个节点一定是黑色节点,它的那个子节点一定是红色的。 为什么这么说?

假设我们要删除的这个节点是红色节点,那他的那个子节点一定是黑色的(性质4)如图:

 如上图,A的左孩子只有一个null黑节点,而右子树至少有两个黑节点一个是B另一个是null,也就是说A的右子树永远至少比左子树多一个黑节点,违背了性质5,其实我发现主要是B这个节点必须是红色的,因为只要B是黑色的就一定会造成A的左边的黑色节点永远至少比右边少一,所以B必须是红色的,如果B是红色的那A必须是黑色的(性质4),所以红黑树中只有一个子节点的一定是黑色,而那个子节点一定是红色。

我们接着说只有一个子节点时怎么处理:只有一个子节点时,要删除的节点一定是黑色,它的子节点一定是红色,此时我们用这个子节点直接替换父节点,并且将子节点颜色涂黑,保证黑色数量。

父节点是黑色的,删除后,子节点直接提上去,染成黑色,这样既不会使黑色节点的个树发生变化,更不可能违背性质4。

(3).如果有两个子节点,用要删除节点的前驱或者后继节点代替它,这样就转变成删除它的前驱或者后继节点,而因为前驱或者后继节点的特性,前驱和后继节点一定不会有两个子节点,问题被转换为了有一个子节点或没有子节点也就是上面的两种情况。

(4).无子节点时,删除节点可能为红色或黑色,如果为黑色,则需要进行删除平衡的操作详细分析:

注意我们后面讨论的删除黑色叶子结点均为非null叶子结点

删除黑色的叶子结点,会直接破坏性质5,我们要根据不同的情况,通过旋转和染色来维持红黑树

先看流程图:

1).如果是根结点,删除了就变为了一颗空树,啥也不用干。

2).如果不是根结点,那要删除的这个黑色叶子结点一定有兄弟节点(如果没有兄弟节点那它总比兄弟那条路径多一个黑色节点违背了性质5)。

下面就要围绕兄弟节点的情况来处理:

2.1).兄弟节点为黑色

2.1.1).兄弟节点为黑色,兄弟节点的子节点都是黑色

2.1.1.1).兄弟节点为黑色,兄弟节点的子节点都是黑色,父亲节点是红色

把兄弟节点染成红色,因为兄弟节点的子节点都是黑色,兄弟节点染成红色后不会违背性质4,但是违背了性质5,和被删除黑色叶子结点一样,兄弟节点这边少了一个黑色节点,,把父亲节点染成黑色,这样两兄弟路径上的黑色节点都加1如图:

2.1.1.2).兄弟节点为黑色,兄弟节点的子节点都是黑色,父亲节点是黑色

把兄弟节点染红,和上面2.1.1.1不同的是父节点本来就是黑色的,这样兄弟和被删除节点这两条路径都会少一个黑色节点,需要自平衡,进行递归处理。

2.1.2) .兄弟节点为黑色,兄弟节点的子节点不全为黑色

2.1.2.1) .兄弟节点为黑色,兄弟节点在左边,兄弟的左孩子是红的。

父亲节点的颜色不用关心,兄弟的右孩子颜色也不用关心,不关心的用白色表示。

先明确目的删除C后,少一个黑色节点,想办法让兄弟节点给匀一个过来,还得保证排序树的特性,交换父节点和兄弟节点的颜色,然后把父节点旋转到删除节点这一边,这么做的效果就是删除节点这一边补齐了一个黑色节点,兄弟节点那一边少了一个黑色节点,兄弟节点的子节点正好是红色的,直接把这个兄弟节点的红色子节点染成黑色这样就维持了红黑树的平衡

2.1.2.2).兄弟节点为黑色,兄弟节点在右边,兄弟的右孩子是红的

和2.1.2.1是对称的:

 2.1.2.3)兄弟节点为黑色,兄弟节点在左边,兄弟的左子节点是黑的

兄弟的右子节点一定是红的,因为我们的前提是兄弟节点的子节点不是全黑。不能像前面2.1.2.1那样做,因为兄弟节点在左边,兄弟的右子节点E是红色,如果向右旋转的话,红色的节点就会跑到右边。我们考虑变成2.1.2.1那种情况即红色节点在兄弟节点的左边,因为要保证二叉排序树的特性,我们只能通过旋转来完成,单纯的以B为支点向左旋转,红色节点E变成了C的兄弟节点,这不是我们想要的,我们要红色节点是C的兄弟的左孩子,所以先交换兄弟节点B和兄弟节点的右孩子的颜色,再旋转正好能达到我们的目的,同时也没有破坏红黑树的性质。之后就可以按照2.1.2.1那种情况去处理了

2.1.2.4)兄弟节点为黑色,兄弟节点在右边,兄弟的右子节点是黑的

和2.1.2.3是对称的:

2.2)兄弟节点为红色

2.2.1)兄弟节点为红色,兄弟节点是左子节点时

如果被删除节点的兄弟节点是红色,没法弄,就得想办法通过旋转变色把被删除节点的兄弟变成黑色

父亲节点一定是黑色,兄弟节点一定有两个孩子而且都是黑色的(性质5),父亲节点和兄弟节点颜色互换,以父亲节点为支点右旋,之后就变成了父亲节点是红色,兄弟为黑色E,但是注意一点E节点只可以有红色的孩子,也可以没有孩子,当没有孩子的时候就是2.1.1.1那种情况,有孩子的时候分为有没有左孩子,有左孩子就是2.1.2.1,没有左孩子只有右孩子就是2.1.2.3

2.2.1.1).旋转完后类似2.1.1.1:

2.2.1.2).旋转完后类似2.1.2.1:

2.2.1.3).旋转完后类似2.1.2.3:

 2.2.2)兄弟节点为红色,兄弟节点是右子节点时

和上面2.2.1对称: 

2.2.2.1).旋转完后类似2.1.1.1:

2.2.2.2).旋转完后类似2.1.2.2: 

注意下图中第一段话是错的改为:父亲节点一定是黑色,父亲节点和兄弟节点颜色互换,以父亲节点A为支点左旋,之后变成了兄弟节点E为黑色,E的右子节点为红色

 2.2.2.3).旋转完后类似2.1.2.4: 

注意下图中第一段话是错的改为:父亲节点一定是黑色,父亲节点和兄弟节点颜色互换,以父亲节点A为支点左旋,之后变成了兄弟节点E为黑色,E的左子节点为红色,右子节点为null是黑色

 到这里删除就讲完了,可以看到,删除最多旋转三次,2.2.1.2、2.2.1.3、2.2.2.2、2.2.2.3这四种情况会旋转三次。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值