若被删除的结点有两个非叶子结点,那么可以转换为删除一个“替代点”的问题,该替代点最多只有一个非叶子孩子结点。可以通过前驱或者后继(都最多有一个非叶子孩子结点)来替代最初要被删除的结点,所以下面只关注只有一个非叶子孩子结点的问题,一旦我们解决了这个问题,那么解决方法将同样适用于两种情形:
1、原本想删除的结点最多有一个非叶子孩子结点
2、原本想删除的结点有两个非叶子孩子结点(通过前驱和后继可以转化为最多有一个非叶子孩子结点)
好了,现在用N来表示替代结点(要是被删除的结点没有替代结点,那么N就表示原本要被删除的结点,由红黑树的属性可以推出该结点不可能有孙子结点),替代结点要么是前驱,要么是后继,但是都最多只有一个非叶子孩子结点(程序中使用的是前驱)。结点N为不同颜色时,处理方式不一样,具体如下。
结点N为红色:
若结点N为红色,那么它的孩子结点必须是黑色的,而且两个孩子结点都是叶子结点(因为结点N最多只能有一个非叶子孩子结点,所以只能是一边是一个黑色的非叶子孩子结点,另一边是一个黑色叶子结点,这样通过结点N的路径的黑色结点的数目就不一样了(因为黑色的非叶子孩子结点至少有两个黑色叶子结点),这样就违反了性质5)。此时直接将N删除即可,删除之后N被空叶子结点(NULL)替代,而NULL结点都是黑色的,所以属性5依然保持。好了,结点N为红色的情形比较简单,下面看结点N为黑色的情形。
结点N为黑色:
结点N为黑色时,可以分为两种情况:
第1种情况:若其孩子结点M有一个是非叶子结点,另外一个是黑色空叶子结点,那么非叶子结点M必须是红色。因为如果非叶子结点也是黑色的,这样通过结点N的路径的黑色结点的数目将不一样(通过空叶子结点这边的黑色结点的数目是2,而通过黑色非叶子结点这边的至少是3),由此违反了性质5。所以非叶子结点M只能是红色的,这种情况比较简单,只需用M代替N,并将M变为黑色,则不违反任何属性,此时依然是一棵合格的红黑树。
第2种情况:若两个孩子结点都为空叶子结点(叶子结点都为黑色,并且是NULL),则此时用空叶子结点NULL替代N后,通过原始替代结点N的路径的黑色结点的数目将比原来减少1,此时就要分类讨论了,将替代结点N的父结点表示为P,兄弟结点表示为S。具体分类如下:
case 1:
N是根结点,在这种情况下,我们已经做完了(仅仅通过直接返回就可以达到这个效果!),因为这种情况下整棵树只有一个结点(注意大前提:N的两个孩子结点都是空叶子结点,所以整棵树只有一个结点),要删除的结点正是根结点,直接返回之后,在上一层函数中用N的子结点(NULL)替代N,相当于将N删除掉了。
#294#_
|
~317~
Deleting key 1th: 294 ---------------------------------------------------------------------
case 0 <294> (若当前结点的子结点为红色,则将子结点变成黑色) :
#294#_
|
#317#
case 0 <294> (将当前结点用子结点替代,之后释放当前结点) :
#317#
Deleting key 1th: 317 ---------------------------------------------------------------------
case 1 <317> (若当前结点是根结点,直接返回) :
case 0 <317> (将当前结点用子结点替代,之后释放当前结点) :
NODE is NULL
注意:在case 2,case 5,case 6中,我们假设N是父结点P的左孩子结点,如果是右孩子结点,那么“左”和“右”应该对调。
case 2:
N的兄弟结点S是红色的(从case 3开始S都是黑色的)。此时对调父结点P(肯定是黑色的)和兄弟结点S的颜色,然后将父结点P左旋。注意此时如果直接将N删掉而结束,那么P结点的左边只有一个黑色结点(NULL),而右边有两个黑色结点(S的左孩子和S的孙子结点(黑色的空叶子结点)),这样就违反了属性5,所以左旋之后将接着按case 4、case 5或case 6来处理。
________________#177#__________________________
| |
_#103#______ ________________~265~______
| | | |
#16# _~146~_ _#206#______ _#313#_
| | | | | |
#140# #170# #178# _~236~_ #294# #317#
| |
#208# #253#
Deleting key 1th: 16 ---------------------------------------------------------------------
case 2 <16> (若当前结点的兄弟结点是红色,则将父亲和兄弟的颜色对调,若该结点靠左,则左旋父结点,若靠右则右旋父结点) :
________________#177#__________________________
| |
_~103~______ ________________~265~______
| | | |
#16# _#146#_ _#206#______ _#313#_
| | | | | |
#140# #170# #178# _~236~_ #294# #317#
| |
#208# #253#
左旋: 103
______#177#__________________________
| |
______#146#_ ________________~265~______
| | | |
_~103~_ #170# _#206#______ _#313#_
| | | | | |
#16# #140# #178# _~236~_ #294# #317#
| |
#208# #253#
case 4 <16> (若当前结点的父结点为红色,兄弟结点是黑色,且兄弟结点的左右子结点都为黑色,则将兄弟结点和父结点的颜色对调) :
______#177#__________________________
| |
______#146#_ ________________~265~______
| | | |
_#103#_ #170# _#206#______ _#313#_
| | | | | |
#16# ~140~ #178# _~236~_ #294# #317#
| |
#208# #253#
case 0 <16> (将当前结点用子结点替代,之后释放当前结点) :
______#177#__________________________
| |
______#146#_ ________________~265~______
| | | |
#103#_ #170# _#206#______ _#313#_
| | | | |
~140~ #178# _~236~_ #294# #317#
| |
#208# #253#
case 3:
结点N的父结点P,兄弟结点S以及S的孩子结点都是黑色的。此时简单的将S设为红色,这样做可以让通过S的所有路径都少一个黑色节点,与通过N的路径的黑色结点就相同了。但是,通过P的所有路径现在比不通过P的路径少了一个黑色节点,所以仍然违反性质5。要修正这个问题,我们要从case 1开始,在P上做重新平衡处理,并且一直递归到根结点为止,到根结点时,通过根结点的所有路径都少了一个黑色结点,然后再从case 1返回。
______#236#______
| |
_#178#_ _#294#_
| | | |
#177# #206# #253# #317#
Deleting key 5th: 253 ---------------------------------------------------------------------
case 3 <253> (若当前结点的父结点和兄弟结点是黑色,且兄弟结点的左右子结点都为黑色,则将兄弟结点变红,将父结点转到case 1做重新平衡处理) :
______#236#______
| |
_#178#_ _#294#_
| | | |
#177# #206# #253# ~317~
case 3 <294> (若当前结点的父结点和兄弟结点是黑色,且兄弟结点的左右子结点都为黑色,则将兄弟结点变红,将父结点转到case 1做重新平衡处理) :
______#236#______
| |
_~178~_ _#294#_
| | | |
#177# #206# #253# ~317~
case 1 <236> (若当前结点是根结点,直接返回) :
case 0 <253> (将当前结点用子结点替代,之后释放当前结点) :
______#236#_
| |
_~178~_ #294#_
| | |
#177# #206# ~317~
case 4:
结点N的父结点P是红色的,兄弟结点S以及S的两个孩子结点(都是空叶子结点)都是黑色。此时将P和S的颜色对调,这不影响通过S的黑色结点数量,但是在通过N的路径上增加了一个黑色结点,正好和要删除的结点N相抵消,这样属性5被满足了。
________________#236#______
| |
______#177#______ _#294#_
| | | |
#146#_ _~206~_ #253# #317#
| | |
~170~ #178# #208#
Deleting key 6th: 208 ---------------------------------------------------------------------
case 4 <208> (若当前结点的父结点为红色,兄弟结点是黑色,且兄弟结点的左右子结点都为黑色,则将兄弟结点和父结点的颜色对调) :
________________#236#______
| |
______#177#______ _#294#_
| | | |
#146#_ _#206#_ #253# #317#
| | |
~170~ ~178~ #208#
case 0 <208> (将当前结点用子结点替代,之后释放当前结点) :
___________#236#______
| |
______#177#______ _#294#_
| | | |
#146#_ _#206# #253# #317#
| |
~170~ ~178~
case 5:
结点N的兄弟结点S的左孩子是红色,右孩子是黑色(左红右黑),并且结点N是父结点P的左孩子。此时将S与其左孩子的颜色对调,然后右旋S。注意此时通过父结点P的路径都有相同的黑色结点数目,但是如果直接将N删掉会违反属性5(与case 2中类似),所以我们继续按照case 6来处理。
___________#236#______
| |
_#177#______ _#294#_
| | | |
#170# _#206# #253# #317#
|
~178~
Deleting key 1th: 170 ---------------------------------------------------------------------
case 5 <170> (subcase1: 若当前结点为左子结点,兄弟结点是黑色,且兄弟结点的左子结点为红色,右子结点为黑色,则将兄弟结点和兄弟结点左孩子的颜色对调,再右旋兄弟结点) :
___________#236#______
| |
_#177#______ _#294#_
| | | |
#170# _~206~ #253# #317#
|
#178#
右旋: 206
___________#236#______
| |
_#177#_ _#294#_
| | | |
#170# #178#_ #253# #317#
|
~206~
case 6 <170> (将兄弟结点的颜色和父结点的颜色对调) :
___________#236#______
| |
_#177#_ _#294#_
| | | |
#170# #178#_ #253# #317#
|
~206~
case 6 <170> (subcase1: 若当前结点为左子结点,则兄弟结点的右子结点必为红色,将兄弟结点的右子结点变成黑色,将父结点左旋) :
___________#236#______
| |
_#177#_ _#294#_
| | | |
#170# #178#_ #253# #317#
|
#206#
左旋: 177
______#236#______
| |
_#178#_ _#294#_
| | | |
_#177# #206# #253# #317#
|
#170#
case 0 <170> (将当前结点用子结点替代,之后释放当前结点) :
______#236#______
| |
_#178#_ _#294#_
| | | |
#177# #206# #253# #317#
______#177#__________________________
| |
_#146#_ ________________~265~______
| | | |
#103# #170# _#206#______ _#313#_
| | | |
#178# _~236~_ #294# #317#
| |
#208# #253#
Deleting key 12th: 313 ---------------------------------------------------------------------
若结点有两个非空结点,用前驱代替它
______#177#__________________________
| |
_#146#_ ________________~265~______
| | | |
#103# #170# _#206#______ _#294#_
| | | |
#178# _~236~_ #294# #317#
| |
#208# #253#
case 3 <294> (若当前结点的父结点和兄弟结点是黑色,且兄弟结点的左右子结点都为黑色,则将兄弟结点变红,将父结点转到case 1做重新平衡处理) :
______#177#__________________________
| |
_#146#_ ________________~265~______
| | | |
#103# #170# _#206#______ _#294#_
| | | |
#178# _~236~_ #294# ~317~
| |
#208# #253#
case 5 <294> (subcase2: 若当前结点为右子结点,兄弟结点是黑色,且兄弟结点的左子结点为黑色,右子结点为红色,则将兄弟结点和兄弟结点右孩子的颜色对调,再左旋兄弟结点) :
______#177#__________________________
| |
_#146#_ ________________~265~______
| | | |
#103# #170# _~206~______ _#294#_
| | | |
#178# _#236#_ #294# ~317~
| |
#208# #253#
左旋: 206
______#177#__________________________
| |
_#146#_ ______~265~______
| | | |
#103# #170# ______#236#_ _#294#_
| | | |
_~206~_ #253# #294# ~317~
| |
#178# #208#
case 6 <294> (将兄弟结点的颜色和父结点的颜色对调) :
______#177#__________________________
| |
_#146#_ ______#265#______
| | | |
#103# #170# ______~236~_ _#294#_
| | | |
_~206~_ #253# #294# ~317~
| |
#178# #208#
case 6 <294> (subcase2: 若当前结点为右子结点,则兄弟结点的左子结点必为红色,将兄弟结点的左子结点变成黑色,将父结点右旋) :
______#177#__________________________
| |
_#146#_ ______#265#______
| | | |
#103# #170# ______~236~_ _#294#_
| | | |
_#206#_ #253# #294# ~317~
| |
#178# #208#
右旋: 265
______#177#________________
| |
_#146#_ ______~236~______
| | | |
#103# #170# _#206#_ _#265#______
| | | |
#178# #208# #253# _#294#_
| |
#294# ~317~
case 0 <294> (将当前结点用子结点替代,之后释放当前结点) :
______#177#________________
| |
_#146#_ ______~236~______
| | | |
#103# #170# _#206#_ _#265#_
| | | |
#178# #208# #253# #294#_
|
~317~
case 6:
结点N的兄弟结点S是黑色的,S的右孩子是红色的(右红),并且结点N是父结点P的左孩子。此时将父结点P和兄弟结点S的颜色对调,再将S的右孩子变为黑色,然后左旋父结点P。
________________#177#__________________________
| |
_____#103#______ ________________~265~______
| | | |
#15#_ _~146~_ _#206#______ _#294#_
| | | | | | |
~16~ #140# #170# #178# _~236~_ #293# #313#_
| | |
#208# #253# ~317~
Deleting key 14th: 293 ---------------------------------------------------------------------
case 6 <293> (将兄弟结点的颜色和父结点的颜色对调) :
________________#177#__________________________
| |
_____#103#______ ________________~265~______
| | | |
#15#_ _~146~_ _#206#______ _#294#_
| | | | | | |
~16~ #140# #170# #178# _~236~_ #293# #313#_
| | |
#208# #253# ~317~
case 6 <293> (subcase1: 若当前结点为左子结点,则兄弟结点的右子结点必为红色,将兄弟结点的右子结点变成黑色,将父结点左旋) :
________________#177#__________________________
| |
_____#103#______ ________________~265~______
| | | |
#15#_ _~146~_ _#206#______ _#294#_
| | | | | | |
~16~ #140# #170# #178# _~236~_ #293# #313#_
| | |
#208# #253# #317#
左旋: 294
________________#177#__________________________
| |
_____#103#______ ________________~265~___________
| | | |
#15#_ _~146~_ _#206#______ _#313#_
| | | | | | |
~16~ #140# #170# #178# _~236~_ _#294# #317#
| | |
#208# #253# #293#
case 0 <293> (将当前结点用子结点替代,之后释放当前结点) :
________________#177#__________________________
| |
_____#103#______ ________________~265~______
| | | |
#15#_ _~146~_ _#206#______ _#313#_
| | | | | | |
~16~ #140# #170# #178# _~236~_ #294# #317#
| |
#208# #253#
______#177#__________________________
| |
_#146#_ ________________~265~______
| | | |
#103# #170# _#206#______ _#313#_
| | | |
#178# _~236~_ #294# #317#
| |
#208# #253#
Deleting key 12th: 313 ---------------------------------------------------------------------
若结点有两个非空结点,用前驱代替它
______#177#__________________________
| |
_#146#_ ________________~265~______
| | | |
#103# #170# _#206#______ _#294#_
| | | |
#178# _~236~_ #294# #317#
| |
#208# #253#
case 3 <294> (若当前结点的父结点和兄弟结点是黑色,且兄弟结点的左右子结点都为黑色,则将兄弟结点变红,将父结点转到case 1做重新平衡处理) :
______#177#__________________________
| |
_#146#_ ________________~265~______
| | | |
#103# #170# _#206#______ _#294#_
| | | |
#178# _~236~_ #294# ~317~
| |
#208# #253#
case 5 <294> (subcase2: 若当前结点为右子结点,兄弟结点是黑色,且兄弟结点的左子结点为黑色,右子结点为红色,则将兄弟结点和兄弟结点右孩子的颜色对调,再左旋兄弟结点) :
______#177#__________________________
| |
_#146#_ ________________~265~______
| | | |
#103# #170# _~206~______ _#294#_
| | | |
#178# _#236#_ #294# ~317~
| |
#208# #253#
左旋: 206
______#177#__________________________
| |
_#146#_ ______~265~______
| | | |
#103# #170# ______#236#_ _#294#_
| | | |
_~206~_ #253# #294# ~317~
| |
#178# #208#
case 6 <294> (将兄弟结点的颜色和父结点的颜色对调) :
______#177#__________________________
| |
_#146#_ ______#265#______
| | | |
#103# #170# ______~236~_ _#294#_
| | | |
_~206~_ #253# #294# ~317~
| |
#178# #208#
case 6 <294> (subcase2: 若当前结点为右子结点,则兄弟结点的左子结点必为红色,将兄弟结点的左子结点变成黑色,将父结点右旋) :
______#177#__________________________
| |
_#146#_ ______#265#______
| | | |
#103# #170# ______~236~_ _#294#_
| | | |
_#206#_ #253# #294# ~317~
| |
#178# #208#
右旋: 265
______#177#________________
| |
_#146#_ ______~236~______
| | | |
#103# #170# _#206#_ _#265#______
| | | |
#178# #208# #253# _#294#_
| |
#294# ~317~
case 0 <294> (将当前结点用子结点替代,之后释放当前结点) :
______#177#________________
| |
_#146#_ ______~236~______
| | | |
#103# #170# _#206#_ _#265#_
| | | |
#178# #208# #253# #294#_
|
~317~