红黑树 学习笔记4 - 删除
接上,算法导论13.4 删除
与 个节点的红黑树上的其它基本操作一样,删除一个结点要花费 时间。与插入操作相比,删除操作要稍微复杂一些。
首先来完成一些辅助程序。
RB-TRANSPLANT( ) 把以 为根的子树 替换为 以 为根的子树。
一些说明:
这段程序其实只是设置了 和 的关系。
第 1 行首先判断 是否为根结点。
接下来看 RB-DELETE( )
先解释一下原理:
考察 的孩子数量,若少于2个,则用孩子替换 ,然后直接移除 。
若有2个孩子,则取到 的后继结点。并且这里有个小细节:
区分 的后继结点是否为右孩子,这导致一些微小的改动。
然后,将 移除,替换为 的后继,并将其颜色改为 的颜色。
最后,若 的后继原来颜色为黑色,这有可能导致令人不快的情况,将其黑色下推个右孩子,然后调用一个调整函数 RB-DELETE-FIXUP( )。
现在专注于上方的代码细节:
先做一些注记:对于 的左右均非空的情况, 为要删除的结点,
为其后继, 为 的右孩子。
1. 第 3 行判断 左为空,第 6 行判断 左非空,右为空,
如果进入了这两个状态,只需要将 和其有可能非空的孩子交换即可,
然后直接到第 21 行,注意到我们可以推出:
此时 结点一定为红色,并且它是原来 下的唯一一个内部结点。
那么进入 FIXUP 程序直接将其颜色设置为黑即可。
2. 复杂的情况从第 9 行开始,此时 的左右均非空,取 的后继为 。
第 10 行记录 的颜色。
第 11 行取 的右子为 。
然后分2中情况:
1. 的后继是右子。
第 13 行有一个迷之操作。
2. 的后继不是右子。
第 14 行用 替换 所在位置。
第 15-16 行重新设置 的新右子关系(取代 )。
接着到达第 17 行,此时,不论哪种情况,
原来以 为根的子树已经被 和 分为左右两边,
那么用 替换 ,并且重新设置 的左孩子。
(注意应做好 的销毁工作,第一种情况 的右子是 ,第二种情况 的右子是 的右子)
至此,我们已经完成了树形结构上的替换操作,下来研究如何维持红黑树的性质。
在第 20 行将 的颜色替换为原来 的颜色。
然后检查 的原始颜色,若为黑色,则下推给 ,此举将导致 的颜色成为 红黑 / 黑黑 ,并调用 RB-DELETE-FIXUP( ) 程序来恢复红黑树的性质。
一点说明:如果 是红色,则红黑树的性质不受破坏。
1. 根结点颜色保持为黑。
2. 黑高没有变化。
3. 不存在两个相邻红结点。
另一方面,如果 为黑色,则会产生3个问题:
1. 根结点变为红色,这只可能在 的孩子少于2个是发生。
2. 若 和 是红色的,则违反了性质4
3. 的移动导致任何 的祖先的包含 的路径上都少了一个黑结点,这违反了性质5。
改进的办法是将 的黑色下推给 结点,使之成为 红黑 / 黑黑 结点。
易知在 FIXUP 调用之前,性质5 成立。
RB-DELETE-FIXUP( )
下面我们来看看过程 RB-DELETE-FIXUP 是如何恢复红黑树性质的。
(注:图片中黑结点表示黑色,深色结点表示红色,浅色结点表示颜色未知。)
第 1-22 行的 循环的目标是将额外的黑色沿树上移,直到:
1. 指向红(黑)结点,此时在第 23 行中,将 着为黑色。
2. 指向根结点,此时可以简单地“移除”额外的黑色。
3. 执行适当的旋转和重新着色,退出循环。
在 循环中, 总是指向一个黑(黑)的非根节点。
退出循环时, 为非根节点或为红(黑)。
在第 2-21 行中是 为左孩子的情况。
保持指针 指向 的兄弟,注意到 不可能是 。
下面我们将分 4 种情况证明:
如果 性质5 在变换之前成立,那么在变换之后也成立。
结合之前的结论 FIXUP 调用之前 性质5 成立,就可以知道 FIXUP调用完成后,红黑树所有性质都得到满足。
情况1: 的兄弟结点 为红色。
由于 为红色,故 和 的2个孩子都为黑色。
操作:交换 和 的颜色,并且对 做一次左旋。
总结:改变2个结点颜色,并做一次左旋。额外黑色不变。
① –> ② –> End
① –> ④ –> End
① –> ③ –> ④ –> End
情况2: 的兄弟结点 为黑色,且 的2个孩子都是黑色。
操作:从 和 上各去掉一重黑色,于是 变为一重黑色,而 变为红色。为了补偿去掉的黑色(即恢复性质5),在 上新增一重黑色,将 作为新结点 来重复 循环。
总结:各去掉一重黑色,并补偿父节点一重黑色。无旋转操作。额外黑色上移。
② –> ( ① | ② | ③ | ④ | End)
情况3: 的兄弟结点 为黑色,且 的左孩子为红色, 的右孩子为黑色。
操作:交换 和 的颜色,然后对 进行右旋,进入情况4。
总结:改变2个结点颜色,并做一次右旋。额外黑色不变。
③ –> ④ –> End
情况4: 的兄弟结点 为黑色,且 的右孩子为红色。
操作:交换 和 的颜色,并对 做一次左旋,
然后将 由红色变为黑色,并去掉 上的一重黑色。
将 设置为 根结点,方便最后调整为黑色。
总结:改变3个结点颜色,并做一次左旋。额外黑色消失。
(这个比较特殊,其他旋转操作只用改变旋转的2个结点颜色)
④ –> End
最后,从循环中退出时有两种可能:
1. 从情况 ② 退出,只可能是从 ① 或 ② 进入 ② 最后退出,于是退出时 为红(黑)结点(从 ① 进入) 或 根结点(从 ② 进入)。
2. 从情况 ④ 退出,根结点的颜色可能被破坏,所以在 ④ 中最后将 设为根结点。
FIXUP 程序最后统一将 着为黑色。
总结:
RB-DELETE 在调用 RB-DELETE-FIXUP 之前时间为 。
在 RB-DELETE-FIXUP 中,
情况 ①、③、④ 在各执行 次数的颜色改变和至多 3 次旋转后便终止。
情况 ② 是 循环可以重复执行的唯一情况,
然后指针 沿树上升至多 次,且不执行任何旋转。
所以,过程 RB-DELETE-FIXUP 要花费 时间,做至多 3 次旋转。
因此, RB-DELETE 运行的总时间为 。
课后习题:
13. 4-1
13. 4-2
13. 4-3
13. 4-4
13. 4-5
13. 4-6