在树中被删除的结点并不一定是那个最初包含要删除的数据项的那个结点。
从这个角度来讲,所谓删除,是指颜色被重置,而非关键字被删除。
就像下图中要删除z,实际被删除的是y,因为 y 顶替了 z 的位置,并被重置成 z 的颜色,就像z并没有消失,只是换了个关键字变成 y 而存在。而 y 却彻底消失了,本来有y在能保持的红黑树的5个性质却可能被破坏,虽然 y 原来的位置被 y.right 也就是 x 顶替()了——原因就在于 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); //1.
else if z.right==T.nil
x=z.left
RB-TRANSPLANT(T,z,z.left) //x.p是原来的y的父节点,这里的z.left就是x
else
y=TREE-MINIMUM(z.right) //2.
y-original-color=y.color
x=y.right //x等于y唯一的子节点(y不可能有左子节点),在 y 移动后代替 y 原来的位置
if y.p=z
x.p=y //x.p是原来的y的父节点,但这里例外,因为当y.p=z时,y要覆盖z,所以应该直接令x.p=y
else
RB-TRANSPLANT(T,y,y.right) //1.2.3.通过
y.left=z.left
y.left.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) //从底向上修起(x位于红黑树的最后一层)
删除红色节点不影响红黑树的黑高(Bh),因此如果y(本来的)是红色的话,可以直接删除而不破坏结构(此时 x 必为黑色),但是如果是黑色的话,就破坏了性质5。
所以需要修正的前提就是 y 此前的颜色是黑色,此时把因为删除y 而缺少的黑色附加在 x 上,使 x具有双重黑色(就当y没删除一样——把y与x两个结点合并为x一个结点但是是双重黑色,x顶替y(用RB—TRANSPLANT()实现的)的目的,就是为了合二为一),并且在变换过程中一直把其当作双重黑色来用——因此变换原则就是★★★
注意:双重的概念是虚拟的,仅仅是为了方便调整红黑树的结构而采取的一种策略。
那么我们就从x 开始(被破坏子树的最后一层,此时 x 具有两个黑色——附加了 y 的黑色),依次向上调整被破坏的子树红黑树的结构,直到 x 变成整棵红黑树的根(在向上调整过程中,x 的位置是变化的,但总是指向一个具有双重黑色的非根结点)——此时把 x 的多余的那重黑色去掉成为单一黑色即可(根结点对于子树来说只有一个嘛!)。
记住一句话:循环在当 x 指向根结点时,可以简单地 “移除” 额外的黑色!
★★★:从子树的根(包括根) 到每棵子树a,b,…,n之间的黑节点个数(包括 x 的额外黑色)不被变换改变。每一步变换都是如此,直到双重黑色结点上移到了根结点或变成情况4( x 的双重黑色被拆成两个结点的单一黑色)——这才是我们的居心所在。
旋转过程不改变黑高,要么旋转到最后x变成根结点,把双重黑色蒸发掉一个(此时把多余的那一重简单去除并不影响性质5)——黑高要少大家都少;要么就是旋转成情况4,把x一拆为二——就像当初合二为一一样,功德圆满,再把x指向根结点,退出循环。
y 此前的颜色是黑色:
1)如果x 的颜色是红色,直接设置成黑色( y (的颜色)“附身”)——就像如果y是红色(此时 x 必为黑色,故连设置都不需要了) 可以直接删除一样;
2)如果x 的颜色是黑色,设置成双重黑色( y 同样 “附身”);
4种情况的变换见 算导 P186 – P187.
参考资料:http://blog.csdn.net/sprintfwater/article/details/8308827