红黑树-插入篇

红黑树插入操作比较复杂,特地从网上整理了一下,下面这两种解释结合起来看,就可以轻松理解红黑树的插入操作了。

原博文的地址为:http://www.cnblogs.com/xuqiang/archive/2011/05/16/2047001.html

http://blog.csdn.net/hackbuteer1/article/details/7740956

由于STL中的关联式容器默认的底层实现都是红黑树,因此红黑树对于后续学习STL源码还是很重要的,有必要掌握红黑树的实现原理和源码实现。
     红黑树是AVL树的变种,红黑树通过一些着色法则确保没有一条路径会比其它路径长出两倍,因而达到接近平衡的目的。所谓红黑树,不仅是一个二叉搜索树,而且必须满足一下规则:
     1、每个节点不是红色就是黑色。
     2、根节点为黑色。
     3、如果节点
为红色,其子节点必须为黑色
     4、任意一个节点到到NULL(树尾端)的任何路径,所含之黑色节点数必须相同。

上面的这些约束保证了这个树大致上是平衡的,这也决定了红黑树的插入、删除、查询等操作是比较快速的。 根据规则4,新增节点必须为红色;根据规则3,新增节点之父节点必须为黑色。当新节点根据二叉搜索树的规则到达其插入点时,却未能符合上述条件时,就必须调整颜色并旋转树形,如下图:

假设我们为上图分别插入节点3、8、35、75,根据二叉搜索树的规则,插入这四个节点后,我们会发现它们都破坏了红黑树的规则,因此我们必须调整树形,也就是旋转树形并改变节点的颜色。

二、红黑树上结点的插入

      在讨论红黑树的插入操作之前必须要明白,任何一个即将插入的新结点的初始颜色都为红色。这一点很容易理解,因为插入黑点会增加某条路径上黑结点的数目,从而导致整棵树黑高度的不平衡。但如果新结点的父结点为红色时(如下图所示),将会违反红黑树的性质:一条路径上不能出现相邻的两个红色结点。这时就需要通过一系列操作来使红黑树保持平衡。

      为了清楚地表示插入操作以下在结点中使用“新”字表示一个新插入的结点;使用“父”字表示新插入点的父结点;使用“叔”字表示“父”结点的兄弟结点;使用“祖”字表示“父”结点的父结点。插入操作分为以下几种情况:
1、黑父
     如下图所示,如果新节点的父结点为黑色结点,那么插入一个红点将不会影响红黑树的平衡,此时插入操作完成。红黑树比AVL树优秀的地方之一在于黑父的情况比较常见,从而使红黑树需要旋转的几率相对AVL树来说会少一些。

2、红父
     如果新节点的父结点为红色,这时就需要进行一系列操作以保证整棵树红黑性质。如下图所示,由于父结点为红色,此时可以判定,祖父结点必定为黑色。这时需要根据叔父结点的颜色来决定做什么样的操作。青色结点表示颜色未知。由于有可能需要根结点到新点的路径上进行多次旋转操作,而每次进行不平衡判断的起始点(我们可将其视为新点)都不一样。所以我们在此使用一个蓝色箭头指向这个起始点,并称之为判定点。

2.1 红叔
当叔父结点为红色时,如下图所示,无需进行旋转操作,只要将父和叔结点变为黑色,将祖父结点变为红色即可。但由于祖父结点的父结点有可能为红色,从而违反红黑树性质。此时必须将祖父结点作为新的判定点继续向上(迭代)进行平衡操作。

需要注意的是,无论“父节点”在“叔节点”的左边还是右边,无论“新节点”是“父节点”的左孩子还是右孩子,它们的操作都是完全一样的(其实这种情况包括4种,只需调整颜色,不需要旋转树形)。
2.2 黑叔
当叔父结点为黑色时,需要进行旋转,以下图示了所有的旋转可能:
Case 1:

Case 2:

Case 3:

Case 4:

      可以观察到,当旋转完成后,新的旋转根全部为黑色,此时不需要再向上回溯进行平衡操作,插入操作完成。需要注意,上面四张图的“叔”、“1”、“2”、“3”结点有可能为黑哨兵结点。

帮助理解插入操作:
通过上面的操作生成的只有一个节点的树,满足1-5条性质,显然该树是红黑树。

 

 

 特殊情况考虑完成之后,下面假设又开始添加节点,我们面对的第一个问题是新增加的节点是标记成红色还是黑色?显然无论是新插入的节点是黑色或者是红色,红黑树限制1,2,3一定是满足的,那么如果将新插入的节点标识成黑色的话,可能违反5,但是如果将新插入的节点标识成红色,肯能违反4,看似好像是两个是类似的。

 

但是考虑这样的一种情况:如果新插入的节点标识成红色,并且新插入的节点的父节点是黑色,那么是违反性质4的,也就是说是不需要重新调整红黑树的。

 

 

 

但是如果标识成黑色的话,那么是一定会违反性质5,好的,我们还是选择将插入的节点标识成红色吧,至少运气好的话,就不需要重新调整红黑树了。 

 

 确定了新插入的节点的颜色之后,现在开始具体的实现插入操作,由于红黑树实际上也是一种二叉查找树,那么新插入的顶点一定是在红黑树的最低端,我们忽略掉这个查找节点的过程,这里仅仅关心插入节点之后如何调整红黑树。数学上的常用方法:分类讨论

 

case 1. 如果插入的节点是根节点,也就是说初始的红黑树为空,这是最简单的情况,直接将该节点标识成黑色即可。

 

 

case 2. 如果新插入的节点不是红黑树的根节点,如果新插入的节点的父节点是黑色的话,那么红黑树是不需要调整的

 

 

 

 

 

case 3. 如果新插入的节点的父节点是红色的话,显然这里违反了红黑树中不能存在父节点和子节点同时为红色的性质,。

 

 

 

于是需要调整,我们的调整的目标是在不违反红黑树性质(或者是可调整)如何将两个相邻的红色节点分隔开来,显然最终的结果是我们需要将新插入的节点的父节点更改成黑色(新插入节点是如法作出调整的,实现将两个红色节点分割开的,除非将该节点标识为黑色,但是这会增加黑高度),但是如果是单纯的修改父节点为黑色的话,那么将会违背黑色顶点数目的性质,直接修改是行不通,那么能否通过交换两个两个节点达到目的呢?

 

显然新插入的节点的祖父节点一定是黑色的,那么是否能够通过交换父节点和祖父节点的红黑性质来达到目的呢?显然这可能违反将新插入节点的叔叔子树的红黑树性质破坏掉。但是如果叔叔节点是红色的话,问题似乎变得很简单了。 

 

 

 

我们仅仅需要将主父节点改成红色,同时将父节点和叔叔节点改成黑色即可。

 

 

 

但是这将引入新的问题, 显然我们将G节点表示成红色之后,那么G节点和G的父节点是可能违反红黑性质的,为了解决这个问题,这里使用尾递归的形式来对节点G进行调整。

 

 

一种情况已经解决。下面将是更加艰巨的任务,如果叔叔节点是黑色的,该如何是好?

 

 case 4:我们来看看还剩下的就是这种情况了,P为红色,U为黑色,G为黑色,怎么解决?对了,到这里是否忘了我们的最终目的是什么?

 

 在不违反红黑树性质(或者是可调整)如何将两个相邻的红色节点分隔开来. 

 

 

 

 

 

直接将P节点修改成黑色是不行的,这将增加G-P-U路径上的黑高度, 没有思路,好的,来回头看看case 3的情况,毕竟已经解决了一种情况,case 3中我们修改了节点G的颜色,为什么能够修改,显然是说G是最顶点(辈份最高)的节点,最顶点的节点的颜色可以是红色(重新调整)或者是黑色。

 

那如果我们呢将P节点提升至现在G的位置呢(通过树的旋转)?

 

 

 

现在可以将P的颜色标识成黑色,但是会破坏P-G-U的黑路径,运用一下交换的思想吧,交换P和G的颜色即可。

 

现在基本上已经解决了红黑树中如果叔叔是黑色的情况,剩下的工作就是分析旋转方向,根据节点P和N是左孩子还是右孩子进行相应旋转。

 

这是case 4情况:

 

 

 

或者是:

 

 

 

 

这样将P和U不再同一条直线的情景转换成在统一条直线上的情况,case 5我们处理在同一条直线上的情况:

 

case 5:

 

 

 

或者是:

 

 

 

 

红黑树的插入的所有情况分析完了,

 

 

posted @ 2013-08-20 20:42  sandyhit  阅读(6696)  评论(0编辑  收藏  举报