在本系列的前面两篇文章中,已经介绍了红黑树以及其插入操作。具体可参考下面两个链接:
红黑树(1) – 介绍
红黑树(2) – 插入操作
1.删除操作介绍
类似于插入操作,红黑树进行删除节点时,也使用重新着色以及旋转这两种方式,来维护它的属性。在插入操作中,我们主要是依靠检测叔节点的颜色来决定哪种场景。在删除操作中,我们使用检测兄弟的颜色,来决定是哪种场景。
在插入操作中,最常见的违反红黑树属性的一种情况是存在两个连续的红色节点。而在删除操作中,常见的情况是,当删除节点是黑色时,会影响从根节点到叶子的黑色节点高度。违反红黑树的性质5。
删除的过程相对比较复杂。为了便于理解删除过程,我们将使用到”double black”的概念。当一个黑色节点被删除,并且被它的黑色孩子取代时,这个孩子就标记为double black。因此,主要的工作就变为了将这个double black转换为single black。
2.删除操作步骤
下面是详细的删除步骤。在以下的内容里,d表示被删节点,c表示将用于替换d的孩子节点。
执行标准的二叉搜索树的删除操作。在删除过程中,如果d是叶子或者只有一个孩子,则操作比较简单,基本上直接删除就可以了。而对于存在两个孩子的节点,可以先查找到d的中序遍历时的后继节点,用它的值替换掉d的值,然后再删除这个后继节点(中序遍历时的后续节点总是一个叶子或只有一个孩子)。如下图所示。这样的话,我们只需要处理被删节点是叶子或只有一个孩子的这种情况。
50 60 60
/ \ delete(50) / \ delete(60') / \
40 70 -----------------> 40 70 ------------------> 40 70
/ \ 后继节点赋值 / \ 删除后继节点 \
60 80 给被删节点 60' 80 80
上图中,被删节点d是50,则它的中序遍历后继节点是60。用后继节点的值替换d的值,然后删除60这个后继节点。
当然,在本步骤中,也可以选取前驱节点(即被删节点左子树最大值)进行替换。原理与后继节点相似。这里不再描述。
3. 简单场景-d或者c是红色
使用孩子节点c替换d,然后将其置为黑色。这样黑色高度维持不变。这是因为d和c不可能同时是红色,其中必定有一个为黑色。
本步骤会覆盖到下面的这4种场景。
30 30
/ \ delete(20) / \
20 40 -------------> 10 40
/
10
30 30
/ \ delete(10) / \
10 40 -------------> 20 40
\
20
30 30
/ \ delete(20) / \
20 40 -------------> 10 40
/
10
30 30
/ \ delete(10) / \
10 40 -------------> 20 40
\
20
对于d拥有两个孩子(两颗子树)的场景,如下面的两个图所示,可以采用第2节提供的方法,转换为处理叶子节点或单个孩子的场景。
40 40 40 / \ delete(20) / \ delete(30') / \ 20 50 ----------------------> 30 50 ---------------------> 30 50 / \ 执行步骤2.1,将d的 / \ 此时转换为了删除叶子30'. / 10 30 后继节点的值赋给d. 10 30' 10
40 40 40 / \ delete(20) / \ delete(30') / \ 20 50 ----------------------> 30 50 ---------------------> 30 50 / \ 执行步骤2.1,将d的 / \ 此时转换为了删除叶子30'. / 10 30 后继节点的值赋给d. 10 30' 10
4. 复杂场景-d和c都是黑色(包括d是叶子)
4.1. 孩子节点c是double black
此时,主要工作就是将这个double black转换为single black。注意:当d是叶子时,则默认c为null节点并且为黑色。所以,如果删除的是黑色叶子,则也会引发double black操作。
上图中,在转换为了double black后,实际上已经变成了4.2.1.c的场景。可以使用Right Right Case旋转方式。
最终,删除节点20后,这棵树调整为:
30 40
/ \ delete(20) / \
20 40 -------------> 30 50
\
50
4.2. d是double black,或者d不是根节点
在这种情况下,假设s表示d的兄弟节点,则存在下面这些场景。
4.2.1. s是黑色,并且s的孩子中至少有一个是红色,则进行旋转
假设s的这个红色的孩子为r,则根据s和r的位置,可以分为4种情况。
a. Left Left Case (s是左孩子,且r是s的左孩子或者s的两个孩子都是红色)。这种情形与下面的Right Right Case正好相反。
b. Left Right Case (s是左孩子,且r是s的右孩子)。这种情形与下面的Right Left Case正好相反。
c. Right Right Case (s是右孩子,且r是s的右孩子或者s的两个孩子都是红色)。
d. Right Left Case (s是右孩子,且r是s的左孩子)。
4.2.2. s是黑色,并且s的两个孩子都是黑色(包括s是叶子)
这种情况下需要重新着色,并且:
a. 如果s的父节点是黑色,则做完删除操作后,还需要检测父节点。
b. 如果s的父节点是红色,则不需要再检测父节点,而是可以简单地将其设置为黑色(红色+double black = single black)。
4.2.3. s是红色,执行旋转操作,提升s,并且重新着色s以及它的父节点
此时新的兄弟节点总是黑色的(下图的节点25)。至此,已经将这棵树通过旋转,转换为了兄弟为黑色的这种场景,使用4.2.1或者4.2.2继续处理。这种情形可以分为两种情况。
a. Left Case (s是左孩子)。右旋转父节点p。
b. Right Case (s是右孩子). 左旋转父节点p。