红黑树原理及其操作详解


参考《算法导论(第三版)》13 章。

参考https://www.cnblogs.com/skywang12345/p/3245399.html

红黑树(red-black tree)是许多“平衡”搜索树中的一种,可以保证在最坏情况下基本动态集合操作的时间复杂度为 O ( l o g n ) O(logn) O(logn)
红黑树的应用比较广泛,主要是用它来存储有序的数据,例如,Java集合中的 TreeSet 和 TreeMap,C++ STL中的 set、map,以及Linux虚拟内存的管理,都是通过红黑树去实现的。

红黑树简介

红黑树是一棵二叉搜索树,它在每个节点上增加了一个存储位来表示结点的颜色,可以是 REDBLACK
通过对任何一条从根到叶子的简单路径上各个节点的颜色进行约束,红黑树确保没有一条路径会比其他路径长出 2 倍,因而是近似于平衡的

按《算法导论中》的红黑树节点的定义,树中每个节点包含 5 个属性:color, key, left, rightp

红黑树的性质

红黑树是满足下面红黑性质二叉搜索树

(1)每个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
(4)如果一个节点是红色的,则它的子节点必须是黑色的。
(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

注意:

  • 特性(3)中的叶子节点,是只为空(NIL 或 null)的节点。
  • 特性(5)确保没有一条路径会比其他路径长出俩倍。因而,红黑树是相对是接近平衡的二叉树。

一棵红黑树如下图所示(转自https://www.cnblogs.com/skywang12345/p/3245399.html):

定理:一棵含有n个节点的红黑树的高度至多为2log(n+1).

证明略。
由该定理可知,二叉搜索树的各种查询操作的时间复杂度为 O ( l o g n ) O(logn) O(logn),插入和删除操作的运行时间也为 O ( l o g n ) O(logn) O(logn),但是这两个算法并不直接支持动态集合操作 INSERTDELETE,因为它们不能保证被这些操作修改过的二叉搜索树仍是红黑树

红黑树的基本操作——左右旋转

搜索树操作 TREE-INSERTTREE-DELETE 对树做了修改,结果可能违反红黑树的性质,也就不再是一棵红黑树了。为了维护这些性质,必须要改变树中某些节点的颜色以及指针结构。

指针结构的修改是通过旋转(rotation) 来完成的,旋转包括两种:左旋转 和 右旋转。(与 AVL 树的左右旋转相同)

左旋转

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oPIUGYDf-1606278764784)(./figures/RR.png)]

在下面伪代码中,假设 x.right != T.nil,也就是说 x.right 不是叶节点(这里的叶节点指的是为空(NULL或NIL)的叶子节点,叶节点统一用 T.nil 表示),而且根节点的父节点为 T.nil

LEFT-ROTATE(T, x)  
    y = x.right
    x.right = y.left
    if y.left != T.nil
        y.left.p = x
    y.p = x.p
    if x.p == T.nil         // 对应 x 是根节点的情况
        T.root = y
    elseif x == x.p.left
        x.p.left = y
    else 
        x.p.right == y
    y.left = x
    x.p = y

右旋转

在这里插入图片描述

RIGHT-ROTATE(T, y) 
    x = y.left;
    y.left = x.right
    if x.right != T.nil
        y.left = x.right
    x.p = y.p
    if y.p == T.nil
        T.root = x
    elseif y == y.p.left
        y.p.left = x
    else 
        y.p.right = x
    x.right = y
    y.p = x

LEFT-ROTATERIGHT-ROTATE 都在 O ( 1 ) O(1) O(1) 时间内运行完成。在旋转操作中只有指针改变,其他所有属性都保持不变。

红黑树——插入节点

将一个节点 z z z 插入到红黑树中,需要执行哪些步骤呢?
首先,将红黑树当作一颗二叉查找树,将节点插入;然后,将节点着色为红色;最后,通过旋转和重新着色等方法来修正该树,使之重新成为一颗红黑树。详细描述如下:

  • 第一步: 将红黑树当作一颗二叉查找树,将节点插入

    红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然是一颗二叉查找树。也就意味着,树的键值仍然是有序的。
    此外,无论是左旋还是右旋,若旋转之前这棵树是二叉查找树,旋转之后它一定还是二叉查找树。这也就意味着,任何的旋转和重新着色操作,都不会改变它仍然是一颗二叉查找树的事实。
    那接下来,我们就来想方设法的旋转以及重新着色,使这颗树重新成为红黑树!

  • 第二步:将插入的节点着色为"红色"

    为什么着色成红色,而不是黑色呢?
    将插入的节点着色为红色,不会违背特性(5):从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点”。少违背一条特性,就意味着我们需要处理的情况越少。接下来,就要努力的让这棵树满足其它性质即可;满足了的话,它就又是一颗红黑树了。

  • 第三步: 通过一系列的旋转或着色等操作,使之重新成为一颗红黑树

    第二步中,将插入节点着色为红色之后,不会违背特性(5)。那它到底会违背哪些特性呢?

    • 特性(1)显然不会违背,因为已经将它涂成红色了。
    • 特性(3)显然不会违背,因为这里的叶子节点是指的空叶子节点(NIL 或 NULL),插入非空节点并不会对它们造成影响。
    • 对于特性(2),是有可能违背的,因为根节点需要为黑色,如果插入节点 z z z 是根节点,那么就会违背特性(2)。
    • 对于特性(4),是有可能违背的,因为如果插入节点 z z z 的父节点 z . p z.p z.p 是红节点,则会违背特性(4).

    如果有任何红黑特性被破坏,则至多只有一条被破坏,或是特性(2),或是特性(4)。如果特性(2)被破坏,原因为插入的节点是根节点且是红色节点;如果特性(4)被破坏,原因为插入节点和其父节点都是红色节点。

根据被插入节点 z z z 的父节点 z . p z.p z.p 的情况,可以将“当节点z被着色为红色节点,并插入二叉搜索树”划分为三种情况来处理:

  • 被插入的节点是根节点。

    处理方法:此时违背了特性(2),则红色根节点一定是新增节点 z z z,且它是树中唯一的内部节点,直接把此节点涂为黑色。

  • 被插入的节点的父节点是黑色。

    处理方法:什么也不需要做。节点被插入后,仍然是红黑树。

  • 被插入的节点的父节点是红色

    处理方法:那么,该情况与红黑树的特性(4)相冲突。这种情况下,被插入节点是一定存在非空祖父节点的(因为如果 z . p z.p z.p 如果是根节点那么 z . p z.p z.p 是黑色的,可知 z . p . p z.p.p z.p.p 存在);进一步的讲,被插入节点也一定存在叔节点(即使叔节点为空,我们也视之为存在,空节点本身就是黑色节点)。理解这点之后,我们依据"叔节点的情况",将这种情况进一步划分为3种情况:

    • 当前节点的父节点是红色,且当前节点的祖父节点的另一个子节点(叔节点)也是红色。
    • 当前节点的父节点是红色,叔节点是黑色,且当前节点是其父节点的右孩子
    • 当前节点的父节点是红色,叔节点是黑色,且当前节点是其父节点的左孩子

    其实总共有六种情况,而其中三种与另外三种是对称的,这取决于 z z z 的父节点 z . p z.p z.p z z z 的祖父节点 z . p . p z.p.p z.p.p 的左孩子还是右孩子。
    在所有的三种情况中,插入节点 z z z 的祖父节点 z . p . p z.p.p z.p.p 是黑色的,因为它的父节点 z . p z.p z.p 是红色的,故特性(4)只在 z z z z . p z.p z.p 之间被破坏了。

情形一:插入节点 z z z 的叔节点是红色的

当前节点 z z z 的父节点是红色的,且当前节点的叔节点也是红色。

处理策略:
(1)将父节点 z . p z.p z.p 设为黑色。
(2)将叔节点 z . p . p . l e f t z.p.p.left z.p.p.left z . p . p . r i g h t z.p.p.right z.p.p.right 设为黑色。
(3)将祖父节点 z . p . p z.p.p z.p.p 设为红色。
(4)将祖父节点设为“当前节点”(红色节点),之后继续对“当前节点”进行操作。

当前节点 z z z 和父节点 z . p z.p z.p 都是红色,违背特性(4)。所以,将父节点 z . p z.p z.p 置黑色以解决这个问题。但是,将父节点由红色变成黑色之后,违背了特性(5),因为包含父节点的分支的黑色节点的总数增加了 1 1 1
解决这个问题的办法是:将祖父节点 z . p . p z.p.p z.p.p 由黑色变成红色,同时,将叔叔节点由红色变成黑色

关于这里,说明一下为什么将祖父节点 z . p . p z.p.p z.p.p 由黑色变成红色,同时将叔叔节点由红色变成黑色,能解决“包含父节点的分支的黑色节点的总数增加了 1 1 1 的问题”?
这个道理也很简单。包含父节点的分支的黑色节点的总数增加了 1 1 1, 同时也意味着包含祖父节点的分支的黑色节点的总数增加了 1 1 1,既然这样,我们通过将祖父节点 z . p . p z.p.p z.p.p 由黑色变成红色以解决“包含祖父节点的分支的黑色节点的总数增加了 1 1 1”的问题; 但是,这样处理之后又会引起另一个问题,“包含叔节点的分支的黑色节点的总数减少了 1 1 1”,现在我们已知叔节点是红色,将叔节点设为黑色就能解决这个问题。
所以,将祖父节点由黑色变成红色,同时将叔节点由红色变成黑色,就解决了该问题。

在这里插入图片描述

按照上面的步骤处理之后:当前节点、父节点、叔节点之间都不会违背红黑树特性,祖父节点是红色的,可能会违背红黑树特性。若此时,祖父节点是根节点,直接将祖父节点设为黑色,那就完全解决这个问题了;若祖父节点不是根节点,那我们需要将祖父节点设为“新的当前节点”,接着对“新的当前节点”进行分析。

情形二:插入节点 z z z 的叔节点是黑色的且 z z z 是一个右孩子

当前节点 z z z 及其父节点 z . p z.p z.p 是红色,叔节点是黑色,且当前节点是其父节点的右孩子。

处理策略:
(1) 将父节点 z . p z.p z.p 作为“新的当前节点”。
(2) 以“新的当前节点”为支点进行左旋,转化为下一种情形。

使用一个左旋来将此情形变为下一个情形。因为 z z z z . p z.p z.p 都是红色的,所以该旋转对节点的特性(5)是没有影响的。

情形三:插入节点 z z z 的叔节点是黑色的且 z z z 是一个左孩子

当前节点 z z z 及其父节点 z . p z.p z.p 都是红色的,叔节点为黑色,且当前节点是其父节点的左孩子。

处理策略:
(1)将父节点 z . p z.p z.p 设为黑色。
(2)将祖父节点 z . p . p z.p.p z.p.p 设为红色。
(3)将祖父节点 z . p . p z.p.p z.p.p 作为支点进行右旋转。
(4)此时 z . p z.p z.p 是黑色的,无需再次进行分析。

当前节点 z z z 和父节点 z . p z.p z.p 都是红色,违背了特性(4),因此我们将父节点置为黑色可以解决这个问题;但是,将父节点由红色变成黑色之后,违背了特性(5),因为包含父节点的分支的黑色节点的总数增加了 1 1 1。我们可以通过将祖父节点的颜色变成红色,然后以祖父节点为支点进行右旋转来解决这个问题。
而且此时 z . p z.p z.p 是黑色的,所以无需再执行一次 while 循环去处理这三种情况中的任何一种。

在这里插入图片描述

红黑树插入操作的时间复杂度为 O ( l o g n ) O(logn) O(logn),其中 n n n 为红黑树的节点个数:
由于一棵有 n n n 个节点的红黑树的高度为 O ( l o g n ) O(logn) O(logn),因此向该二叉搜索树中插入的时间复杂度为为 O ( l o g n ) O(logn) O(logn) 的时间。当对红黑树的红黑特性进行修复时(下面的RB-INSERT-FIXUP操作),如果仅当情况一发生,然后指针 z z z 沿着树上升 2 层,while 循环才会重复执行,所以 while 循环可能被执行的总次数为 O ( l o g n ) O(logn) O(logn)。因此红黑树插入操作的时间复杂度为 O ( l o g n ) O(logn) O(logn)。此外,该程序所做的旋转从不超过 2 次,因为只要执行了情况 2 或情况 3,while 循环就结束了,因为此时当前节点 z z z 的父节点时黑色的。

RB-INSERT(T, z)  
01  y ← nil[T]                        // 新建节点“y”,将y设为空节点。
02  x ← root[T]                       // 设“红黑树T”的根节点为“x”
03  while x ≠ nil[T]                  // 找出要插入的节点“z”在二叉树T中的位置“y”
04      do y ← x                      
05         if key[z] < key[x]  
06            then x ← left[x]  
07            else x ← right[x]  
08  p[z] ← y                          // 设置 “z的父亲” 为 “y”
09  if y = nil[T]                     
10     then root[T] ← z               // 情况1:若y是空节点,则将z设为根
11     else if key[z] < key[y]        
12             then left[y] ← z       // 情况2:若“z所包含的值” < “y所包含的值”,则将z设为“y的左孩子”
13             else right[y] ← z      // 情况3:(“z所包含的值” >= “y所包含的值”)将z设为“y的右孩子” 
14  left[z] ← nil[T]                  // z的左孩子设为空
15  right[z] ← nil[T]                 // z的右孩子设为空。至此,已经完成将“节点z插入到二叉树”中了。
16  color[z] ← RED                    // 将z着色为“红色”
17  RB-INSERT-FIXUP(T, z)             // 通过RB-INSERT-FIXUP对红黑树的节点进行颜色修改以及旋转,让树T仍然是一颗红黑树
RB-INSERT-FIXUP(T, z)
01 while color[p[z]] = RED                                                  // 若“当前节点(z)的父节点是红色”,则进行以下处理。
02     do if p[z] = left[p[p[z]]]                                           // 若“z的父节点”是“z的祖父节点的左孩子”,则进行以下处理。
03           then y ← right[p[p[z]]]                                        // 将y设置为“z的叔叔节点(z的祖父节点的右孩子)”
04                if color[y] = RED                                         // Case 1条件:叔叔是红色
05                   then color[p[z]] ← BLACK                    ▹ Case 1   //  (01) 将“父节点”设为黑色。
06                        color[y] ← BLACK                       ▹ Case 1   //  (02) 将“叔叔节点”设为黑色。
07                        color[p[p[z]]] ← RED                   ▹ Case 1   //  (03) 将“祖父节点”设为“红色”。
08                        z ← p[p[z]]                            ▹ Case 1   //  (04) 将“祖父节点”设为“当前节点”(红色节点)
09                   else if z = right[p[z]]                                // Case 2条件:叔叔是黑色,且当前节点是右孩子
10                           then z ← p[z]                       ▹ Case 2   //  (01) 将“父节点”作为“新的当前节点”。
11                                LEFT-ROTATE(T, z)              ▹ Case 2   //  (02) 以“新的当前节点”为支点进行左旋。
12                           color[p[z]] ← BLACK                 ▹ Case 3   // Case 3条件:叔叔是黑色,且当前节点是左孩子。(01) 将“父节点”设为“黑色”。
13                           color[p[p[z]]] ← RED                ▹ Case 3   //  (02) 将“祖父节点”设为“红色”。
14                           RIGHT-ROTATE(T, p[p[z]])            ▹ Case 3   //  (03) 以“祖父节点”为支点进行右旋。
15        else (same as then clause with "right" and "left" exchanged)      // 若“z的父节点”是“z的祖父节点的右孩子”,将上面的操作中“right”和“left”交换位置,然后依次执行。
16 color[root[T]] ← BLACK

红黑树——删除节点

将红黑树内的某一个节点 z z z 删除。需要执行的操作依次是:

  • 第一步:将红黑树当作一颗二叉查找树,将节点删除。

    这和“在二叉搜索树中删除节点”的方法是一样的。分三种情况:

    ① 如果删除的是叶节点,则直接删除。
    ② 如果删除的节点 z z z 只有一株左子树或右子树,则直接继承:将该子树移到被删节点的位置。
    ③ 如果删除的节点 z z z 有两株子树,则用继承节点(中序后继) y y y 代替被删节点,这就相当于删除继承节点 y y y ——按 ① 或 ② 处理继承节点,继承节点要么没有子树(按 ① 处理),要么仅有一棵右子树(按 ② 处理)。

  • 第二步:通过"旋转和重新着色"等一系列来修正该树,使之重新成为一棵红黑树。

    因为第一步删除及诶单后,可能会违背红黑树的特性,所以需要通过“旋转和重新着色”来修正该树,使之重新成为一棵红黑树。

红黑树是满足下面红黑性质二叉搜索树

(1)每个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
(4)如果一个节点是红色的,则它的子节点必须是黑色的。
(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

y-origin-color 保存实际删除节点的颜色REDBLACK);x 保存实际删除的节点的子树,可以是空的叶节点(NIL)。

  • 如果被删除节点 z z z 仅有一棵子树或者没有子树,那么实际删除的节点便是节点 z z z x x x z z z 的孩子节点:

    y = z;
    y-origin-color = y.color;
    if z.left == T.nil:
        x = z.right;
    elseif z.right == T.nil:
        x = z.left
    
  • 如果被删除节点 z z z 有两个子树,那么找到它的继承节点 y y y,实际删除的节点是 y y y x x x y y y 的右孩子:

    y = TREE-MINIMUM(z.right);      // 找删除节点 z 的后继结点 y
    y-origin-color = y;
    x = y.right;            
    

如果实际删除节点(无论删除的是 z z z 还是继承节点 y y y,之后就记为 y y y的颜色 y-origin-color 是红色,当 y y y 被删除或移动时,红黑性质仍然保持不变,原因如下:
① 从根到每个叶节点的简单路径上黑色节点的个数保持不变;
② 不存在两个相邻的红色节点;

  • 如果实际删除的是节点 z z z,那么y-origin-color = z.color = RED,此时 z z z 的父节点一定不是红色,只能是黑色,否则违背特性(4); z z z 的子节点一定是黑色(包含空的叶子节点NIL),否则也违背特性(4)。所以删除 z z z 会将 x x x 接到 z . p z.p z.p 的相应位置上,不可能会出现两个红色节点相邻的情况。
  • 如果实际删除的是 z z z 的继承节点 y y y,因为 y y y 会在树中占据 z z z 的位置,再考虑到 z z z 的颜色,删除后的树中 y y y 的新位置不可能有两个相邻的红节点。另外,如果 y y y 不是 z z z 的右孩子,则 y y y 的原右孩子 x x x 代替 y y y如果 y y y 是红色,则 x x x 一定是黑色,因此用 x x x 替代 y y y 不可能使两个红节点相邻。

③ 如果 y y y 是红色节点,就不可能是根节点,所以根节点仍旧是黑色。

但是如果实际删除节点的颜色 y-origin-color 是黑色的,那么
① 可能违背特性(2):如果删除的是原来的根节点,而 y y y 的一个红色的孩子成为了新的根节点,这就违反了特性(2)。
② 可能违背特性(4):如果 x x x x . p x.p x.p 都是红色的,则违反了特性(4)。
③ 可能违背特性(5):在树中移动 y y y 将导致先前包含 y y y 的任何简单路径上黑节点个数少 1,因此 y y y 的任何祖先都不满足特性(5)。

第二步:通过"旋转和重新着色"等一系列来修正该树,使之重新成为一棵红黑树,需要解决上面的三个问题,进而保持红黑树的全部特性。
改正这一问题的办法是将现在占有 y y y 原来位置的节点 x x x 视为还有一重额外的黑色,也就是将任意包含节点 x x x 的简单路径上黑节点个数加 1 1 1,则在这种假设下,特性(5)成立。
现在问题变为 x x x 可能既不是红色,又不是黑色, x x x 的颜色属性是 “红+黑” 或 “黑+黑”,从而违反了特性(1)。

现在,我们面临的问题,由解决“违反了特性(2)、(4)、(5)三个特性”转换成了“解决违反特性(1)、(2)、(4)三个特性”。RB-DELETE-FIXUP 需要做的就是通过算法恢复红黑树的特性(1)、(2)、(4)。
RB-DELETE-FIXUP 的思想是:将 x x x 所包含的额外的黑色不断沿树上移(向根方向移动),直到出现下面的姿态
a) x x x 指向一个“红+黑”节点(记为红黑节点)。此时,将x设为一个"黑"节点即可。
b) x x x 指向根节点,此时可以简单地“移除”额外的黑色。
c) 非前面两种姿态,执行适当的旋转和重新着色,退出循环。

将上面的姿态,可以概括为三种情况:

  • x x x 是红黑节点。

    处理方法:直接把 x x x 设为黑色,结束。此时红黑树性质全部恢复。

  • x x x 是双重黑色的根节点。

    处理方法:简单地“移除”额外的黑色,结束。此时红黑树性质全部恢复。

  • x x x 是双重黑色的非根节点。

    处理方法:这种情况又可以划分为4种子情况。这 4 种子情况如下表所示:

    • x x x 是双重黑色非根节点, x x x 的兄弟节点是红色。(此时 x x x 的父节点和 x x x 的兄弟节点的子节点都是黑节点)。
    • x x x 是双重黑色非根节点, x x x 的兄弟节点是黑色, x x x 的兄弟节点的两个孩子都是黑色。
    • x x x 是双重黑色非根节点, x x x 的兄弟节点是黑色; x x x 的兄弟节点的左孩子是红色,右孩子是黑色的。
    • x x x 是双重黑色非根节点, x x x 的兄弟节点是黑色; x x x 的兄弟节点的右孩子是红色的, x x x 的兄弟节点的左孩子任意颜色。

情况一: x x x 是双重黑色节点, x x x 的兄弟节点是红色

x x x 是双重黑色非根节点, x x x 的兄弟节点是红色。(此时 x x x 的父节点和 x x x 的兄弟节点的子节点都是黑节点)。

处理策略:
(1)将 x x x 的兄弟节点设为黑色。
(2)将 x x x 的父节点设为红色。
(3)对 x x x 的父节点进行左旋。
(4)左旋后,重新设置 x x x 的兄弟节点。

这样做的目的是将情况一转换为情况二、情况三或情况四,从而进行进一步的处理。
x x x 的父节点进行左旋;左旋后,为了保持红黑树特性,就需要在左旋前“将 x x x 的兄弟节点设为黑色”,同时“将 x x x 的父节点设为红色”;左旋后,由于 x x x 的兄弟节点发生了变化,需要更新 x x x 的兄弟节点,从而进行后续处理。
此时 x x x 还是双重黑色节点,按情况二三四进行下一步的处理。

在这里插入图片描述

情况二: x x x 是双重黑色节点, x x x 的兄弟节点是黑色, x x x 的兄弟节点的两个孩子都是黑色

x x x 是双重黑色非根节点, x x x 的兄弟节点是黑色, x x x 的兄弟节点的两个孩子都是黑色。

处理策略:
(1)将 x x x 由双重黑色节点变为黑色节点。
(2)将 x x x 的父节点 z . p z.p z.p 增加一个黑色(变成双重黑色节点或红黑节点)。
(3)将 x x x 的兄弟节点设为红色。
(4)设置 x x x 的父节点为新的 x x x 节点。

这个情况的处理思想:是将“ x x x 中多余的一个黑色属性上移(往根方向移动)”。
x x x 是双重黑色节点,我们 x x x 由双重黑色节点变成黑节点,多余的一个黑属性移到 x x x 的父节点中,即 x x x 的父节点多出了一个黑属性(若 x x x 的父节点原先是黑,则此时变成了双重黑色节点;若 x x x 的父节点原先是红,则此时变成了红黑节点)。
此时,需要注意的是:所有经过 x x x 的分支中黑节点个数没变化;但是,所有经过 x x x 的兄弟节点的分支中黑色节点的个数增加了 1 1 1(因为 x x x 的父节点多了一个黑色属性)!为了解决这个问题,我们需要将“所有经过 x x x 的兄弟节点的分支中黑色节点的个数减 1 1 1”即可,那么就可以通过“ x x x 的兄弟节点由黑色变成红色”来实现。 、

经过上面的步骤(将x的兄弟节点设为红色),多余的黑色已经跑到 x x x 的父节点中。我们需要 x x x 的父节点设为“新的 x x x 节点”进行处理。若“新的 x x x 节点”是红黑节点,直接将“新的 x x x 节点”设为黑色,即可完全解决该问题;若“新的x节点”是双重黑色节点,则需要对“新的x节点”进行进一步处理。

在这里插入图片描述

情况三: x x x 是双重黑色节点, x x x 的兄弟节点是黑色, x x x 的兄弟节点的左孩子是红色,右孩子是黑色的

x x x 是双重黑色非根节点, x x x 的兄弟节点是黑色; x x x 的兄弟节点的左孩子是红色,右孩子是黑色的。

处理策略:
(1)将 x x x 兄弟节点的左孩子设为黑色。
(2)将 x x x 兄弟节点设为红色。
(3)对 x x x 的兄弟节点进行右旋。
(4)右旋后,重新设置 x x x 的兄弟节点。

我们处理情况三的目的是为了将其转换成情况四,从而进行进一步的处理。
转换的方式是对 x x x 的兄弟节点进行右旋;为了保证右旋后,它仍然是红黑树,就需要在右旋前“将 x x x 的兄弟节点的左孩子设为黑色”,同时“将 x x x 的兄弟节点设为红色”;右旋后,由于 x x x 的兄弟节点发生了变化,需要更新 x x x 的兄弟节点,从而进行后续处理。

在这里插入图片描述

情况四: x x x 是双重黑色节点, x x x 的兄弟节点是黑色, x x x 的兄弟节点的右孩子是红色的, x x x 的兄弟节点的左孩子任意颜色

x x x 是双重黑色非根节点, x x x 的兄弟节点是黑色; x x x 的兄弟节点的右孩子是红色的, x x x 的兄弟节点的左孩子任意颜色。

处理策略:
(1)将 x x x 由双重黑色节点变为黑色节点。
(2)将 x x x 父节点颜色赋值给 x x x 的兄弟节点。
(3)将 x x x 父节点设为黑色。
(4)将 x x x 兄弟节点的右子节设为黑色。
(5)对 x x x 的父节点进行左旋。
(6)设置 x x x 为此时的根节点,也就是之前 x x x 的兄弟节点。

通过进行某些颜色修改并对 x . p x.p x.p 做一次左旋,可以去掉 x x x 的额外黑色,从而使它变成单重黑色,而且不破坏红黑树的任何性质。将 x x x 设置为根后,当 while 循环测试其循环条件时,循环终止。

为了便于说明,我们设置“当前节点”为 S(Original Son),“兄弟节点”为 B(Brother),“兄弟节点的左孩子”为 BLS(Brother’s Left Son),“兄弟节点的右孩子”为 BRS(Brother’s Right Son),“父节点”为 F(Father)。
我们要对 F 进行左旋。但在左旋前,我们需要调换 F 和 B 的颜色,并设置 BRS 为黑色。为什么需要这里处理呢?因为左旋后,F 和 BLS 是父子关系,而我们已知 BLS 是红色,如果 F 是红色,则违背了特性(4);为了解决这一问题,我们将“F设置为黑色”。 但是,F 设置为黑色之后,为了保证满足特性(5),即为了保证左旋之后:

第一,“同时经过根节点和 S 的分支的黑色节点个数不变”。
若满足“第一”,只需要 S 丢弃它多余的颜色即可。因为 S 的颜色是“黑+黑”,而左旋后“同时经过根节点和S的分支的黑色节点个数”增加了1;现在,只需将 S 由“黑+黑”变成单独的黑节点,即可满足“第一”。

第二,“同时经过根节点和 BLS 的分支的黑色节点数不变”。
若满足“第二”,只需要将“F 的原始颜色”赋值给 B 即可。之前,我们已经将“F设置为黑色”(即,将 B 的颜色"黑色",赋值给了 F)。至此,我们算是调换了F和B的颜色。

第三,“同时经过根节点和 BRS 的分支的黑色节点数不变”。
在“第二”已经满足的情况下,若要满足“第三”,只需要将 BRS 设置为“黑色”即可。

经过,上面的处理之后。红黑树的特性全部得到的满足!接着,我们将 x x x 设为根节点进行下一步的处理。

直到跳出 while 循环(参考伪代码);即完成了全部处理。

在这里插入图片描述

RB-DELETE(T, z)
01 if left[z] = nil[T] or right[z] = nil[T]         
02    then y ← z                                  // 若“z的左孩子” 或 “z的右孩子”为空,则将“z”赋值给 “y”;
03    else y ← TREE-SUCCESSOR(z)                  // 否则,将“z的后继节点”赋值给 “y”。
04 if left[y] ≠ nil[T]
05    then x ← left[y]                            // 若“y的左孩子” 不为空,则将“y的左孩子” 赋值给 “x”;
06    else x ← right[y]                           // 否则,“y的右孩子” 赋值给 “x”。
07 p[x] ← p[y]                                    // 将“y的父节点” 设置为 “x的父节点”
08 if p[y] = nil[T]                               
09    then root[T] ← x                            // 情况1:若“y的父节点” 为空,则设置“x” 为 “根节点”。
10    else if y = left[p[y]]                    
11            then left[p[y]] ← x                 // 情况2:若“y是它父节点的左孩子”,则设置“x” 为 “y的父节点的左孩子”
12            else right[p[y]] ← x                // 情况3:若“y是它父节点的右孩子”,则设置“x” 为 “y的父节点的右孩子”
13 if y ≠ z                                    
14    then key[z] ← key[y]                        // 若“y的值” 赋值给 “z”。注意:这里只拷贝z的值给y,而没有拷贝z的颜色!!!
15         copy y's satellite data into z         
16 if color[y] = BLACK                            
17    then RB-DELETE-FIXUP(T, x)                  // 若“y为黑节点”,则调用
18 return y
RB-DELETE-FIXUP(T, x)
01 while x ≠ root[T] and color[x] = BLACK  
02     do if x = left[p[x]]      
03           then w ← right[p[x]]                                             // 若 “x”是“它父节点的左孩子”,则设置 “w”为“x的叔叔”(即x为它父节点的右孩子)                                          
04                if color[w] = RED                                           // Case 1: x是“黑+黑”节点,x的兄弟节点是红色。(此时x的父节点和x的兄弟节点的子节点都是黑节点)。
05                   then color[w] ← BLACK                        ▹  Case 1   //   (01) 将x的兄弟节点设为“黑色”。
06                        color[p[x]] ← RED                       ▹  Case 1   //   (02) 将x的父节点设为“红色”。
07                        LEFT-ROTATE(T, p[x])                    ▹  Case 1   //   (03) 对x的父节点进行左旋。
08                        w ← right[p[x]]                         ▹  Case 1   //   (04) 左旋后,重新设置x的兄弟节点。
09                if color[left[w]] = BLACK and color[right[w]] = BLACK       // Case 2: x是“黑+黑”节点,x的兄弟节点是黑色,x的兄弟节点的两个孩子都是黑色。
10                   then color[w] ← RED                          ▹  Case 2   //   (01) 将x的兄弟节点设为“红色”。
11                        x ←  p[x]                               ▹  Case 2   //   (02) 设置“x的父节点”为“新的x节点”。
12                   else if color[right[w]] = BLACK                          // Case 3: x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的左孩子是红色,右孩子是黑色的。
13                           then color[left[w]] ← BLACK          ▹  Case 3   //   (01) 将x兄弟节点的左孩子设为“黑色”。
14                                color[w] ← RED                  ▹  Case 3   //   (02) 将x兄弟节点设为“红色”。
15                                RIGHT-ROTATE(T, w)              ▹  Case 3   //   (03) 对x的兄弟节点进行右旋。
16                                w ← right[p[x]]                 ▹  Case 3   //   (04) 右旋后,重新设置x的兄弟节点。
17                         color[w] ← color[p[x]]                 ▹  Case 4   // Case 4: x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的右孩子是红色的。(01) 将x父节点颜色 赋值给 x的兄弟节点。
18                         color[p[x]] ← BLACK                    ▹  Case 4   //   (02) 将x父节点设为“黑色”。
19                         color[right[w]] ← BLACK                ▹  Case 4   //   (03) 将x兄弟节点的右子节设为“黑色”。
20                         LEFT-ROTATE(T, p[x])                   ▹  Case 4   //   (04) 对x的父节点进行左旋。
21                         x ← root[T]                            ▹  Case 4   //   (05) 设置“x”为“根节点”。
22        else (same as then clause with "right" and "left" exchanged)        // 若 “x”是“它父节点的右孩子”,将上面的操作中“right”和“left”交换位置,然后依次执行。
23 color[x] ← BLACK
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值