若被删除的结点有两个非叶子结点,那么可以转换为删除一个“替代点”的问题,该替代点最多只有一个非叶子孩子结点。可以通过前驱或者后继(都最多有一个非叶子孩子结点)来替代最初要被删除的结点,所以下面只关注只有一个非叶子孩子结点的问题,一旦我们解决了这个问题,那么解决方法将同样适用于两种情形:
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删除掉了。
注意:在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来处理。
case 3:
结点N的父结点P,兄弟结点S以及S的孩子结点都是黑色的。此时简单的将S设为红色,这样做可以让通过S的所有路径都少一个黑色节点,与通过N的路径的黑色结点就相同了。但是,通过P的所有路径现在比不通过P的路径少了一个黑色节点,所以仍然违反性质5。要修正这个问题,我们要从case 1开始,在P上做重新平衡处理,并且一直递归到根结点为止,到根结点时,通过根结点的所有路径都少了一个黑色结点,然后再从case 1返回。
case 4:
结点N的父结点P是红色的,兄弟结点S以及S的两个孩子结点(都是空叶子结点)都是黑色。此时将P和S的颜色对调,这不影响通过S的黑色结点数量,但是在通过N的路径上增加了一个黑色结点,正好和要删除的结点N相抵消,这样属性5被满足了。
case 5:
结点N的兄弟结点S的左孩子是红色,右孩子是黑色(左红右黑),并且结点N是父结点P的左孩子。此时将S与其左孩子的颜色对调,然后右旋S。注意此时通过父结点P的路径都有相同的黑色结点数目,但是如果直接将N删掉会违反属性5(与case 2中类似),所以我们继续按照case 6来处理。
case 6:
结点N的兄弟结点S是黑色的,S的右孩子是红色的(右红),并且结点N是父结点P的左孩子。此时将父结点P和兄弟结点S的颜色,再将S的右孩子变为黑色,然后左旋父结点P。(未完待续)