红黑树的删除操作是红黑树最复杂的操作了,只要搞明白这个,基本上就明白红黑树的调整。删除的情况大致可以分为四种。
case 1. .删除节点z,,子节点少于两个时,左儿子为T.NIL,用右儿子代替z
case 2. 删除节点z,子节点少于两个时,右儿子为T.NIL,用左儿子代替z
case 3 删除节点z,有两个子节点时,找出后继节点y,如果后继节点y是右儿子,直接用y子树替换z节点
case 4删除节点z,有两个子节点时,找出后继节点y,如果后继节点不是右儿子,用后继节点的右儿子x子树替换y子树,再用后继节点y子树替换z节点。
下面看看图形展示这四种删除情况,其中z节点表示想要删除的节点,y节点表示真正删除的节点(从删除节点的颜色角度来看),x表示要替换y的节点。由于y的颜色可能改变,用y-original-color表示y颜色改变之前的颜色
伪代码如下:
RB-DELETE(T, z)
y = z;
y-original-color = y.color;
if z.left == T.NIL
x = z.right;
RB-TRANSPLANT(T, z, z.right);
else if z.right == T.NIL
x = z.left;
RB-TRANSPLANT(T, z, z.left);
else y = TREE-MINMUM(z.right)
y-original-color = y.color;
x = y.right;
if y.p = z;
x.p = y;
else RB-TRANSPLANT(T, y, y.right)
y.right = z.right;
y.right.p = y;
RB-TRANSPLANT(T, z, y)
y.left = z.left;
y.left.p = y;
y.color = z.color;
if y-original-color == black
RB-DELETE-FIXUP(T, x)
可以看到,如果y是黑色的,就肯定破坏了红黑树的性质了。如果y是红色的,红黑树性质依然保持,因为树的黑高没有改变,也不存在两个相邻的的红色节点,性质4和5没有任何改变。所以y是黑色的时候需要通过RB-DELETE-FIXUP来调整修正。下面来看看如何调整。
违反性质2的情况:如果y是原来的根节点,而y的一个红色孩子成为新的根几点,就违反了性质2,但是这很容易解决,直接染黑就是了,不再继续讨论这种情况。
违反性质4的情况:x节点为红色,x的父节点也为红色,这种情况也很容易解决,染黑x就是了。
剩下的情况就是只违反了性质5,只要调整好性质5就好了,这里有一个技巧,就是把x节点视为还有一层黑色,问题就变成了解决违反性质1了,也就是把x看成既红又黑,我们只要把这层额外的黑色不断往上推,直到推给了一个红色节点,那么子树的黑高就恢复了。和插入一样,有个关键思想是,转换过程中千万不能破坏其他任何的性质。经过分析,破坏性质1(本质上是破坏性质5)有以下五种情况:
case 1 x是红色的
case 2. x的兄弟节点w是红色的
case 3 x的兄弟节点w是黑色的,而且w的两个子节点都是黑色的
case 4 x的兄弟节点w是黑色的,w的左儿子是红色的,w的右孩子是黑色的
case 5 x的兄弟节点w是黑色的,且w的右孩子是红色的。
如下图所示:
case 1是最容易解决的,直接染黑就是了。
case 2的话,改变w和x.p的颜色,左旋转x.p,这样子不改变任何性质的同时,把case 2转变为case 3,4,5。不做详细讨论
伪代码为:
w.color = black;
x.p.color = red;
LEFT-ROTATE(T, x.p)
w = x.p.right;
case 3的话,可以认为从x和w去掉一层黑色给x.p,如果x.p为原本为红色的话,那么x的子树黑高加一,w子树黑高不变,性质就恢复好了,如果x.p原来为黑色的,那么认为x.p的整个子树黑高都少了1,多了的一层黑色就给了x.p,case3就转为case 2,3,4,5了。
伪代码如下:
w.color = red;
x = x.p
case 4的情况左侄儿为红,右侄儿为黑,这种情况统一转case 5来处理。
这里右旋w并且没有改变红黑树的五大性质,转为了case5。伪代码如下:
w.left.color = black;
w.color = red;
RIGHT-ROTATE(T, w)
w = x.p.right;
case 5的情况是红黑树调整的出口,只要到达了case 5,调整完就能恢复所有性质了。调整如下图所示:
接下来分析case5的转换过程,这里的思路是这样的:首先我们要让x子树黑高加一,那么就左旋转a,左旋转后d的左子树没有任何问题,但是右子树黑高可能减少了1(如果a原来是黑色的情况),为了解决这个问题,可以把a和d颜色交换,然后染黑c,这样左旋转后的d的右子树的黑高也就不会有任何改变了。伪代码如下:
w.color = x.p.color;
x.p.color = black;
w.right.color = black;
LEFT-ROTATE(T, x.p);
x = T.root;
为了能在全局下看到调整情况,下面展示整个删除调整的过程伪代码:
RB-DELETE-FIXUP(T, x)
while x != T.root && x.color = black
if x == x.p.left
w = x.p.right
// case 2
if w.color = red
w.color = black;
x.p.color = red;
LEFT-ROTATE(T, x.p)
w = x.p.right;
// case 3
if w.left.color == black && w.right.color == black
w.color = red;
x = x.p;
// case 4
else if w.right.color == black
w.left.color = black;
w.color = red;
RIGHT-ROTATE(T, w)
w = x.p.right;
// case 5
w.color = x.p.color;
x.p.color = black;
w.right.color = black;
LEFT-ROTATE(T, x.p);
x = T.root;