数据结构--红黑树

本文详细介绍了红黑树的基本特性,包括每个节点的颜色规则,以及左旋、右旋操作的实现。接着,重点讲解了红黑树的节点插入过程,包括插入后如何保持红黑树的性质。删除操作同样涉及了如何调整树结构以保持红黑树的平衡。通过对红黑树的这些操作的理解,读者能够深入掌握这种高效的数据结构。
摘要由CSDN通过智能技术生成

目录

一、红黑树

1、红黑树的特性

2、左旋

3、右旋

4、添加

5、删除


一、红黑树

R-B Tree,全称是Red-Black Tree,又称为“红黑树”,它一种特殊的二叉查找树。红黑树的每个节点上都有存储位表示节点的颜色,可以是红(Red)或黑(Black)。

1、红黑树的特性

(1)每个节点或者是黑色,或者是红色。

(2)根节点是黑色。

(3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL 或NULL)的叶子节点!]

(4)如果一个节点是红色的,则它的子节点必须是黑色的。

(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

2、左旋

对 x 进行左旋,意味着,将“x 的右孩子”设为“x 的父亲节点”;即:将 x 变成了一个左节点(x 成了为 y 的左孩子)!。 因此,左旋中的“左”,意味着“被旋转的节点将变成一个左节点”。

 左旋的伪代码

LEFT-ROTATE(T, x)  
 y ← right[x]            // 前提:这里假设x的右孩子为y.下面开始正式操作
 right[x] ← left[y]      // 将 “y的左孩子” 设为 “x的右孩子”,即 将B设为x的右孩子
 p[left[y]] ← x          // 将 “x” 设为 “y的左孩子的父亲”,即 将B的父亲设为x
 p[y] ← p[x]             // 将 “x的父亲” 设为 “y的父亲”
 if p[x] = nil[T]       
 then root[T] ← y        // 情况1:如果 “x的父亲” 是空节点,则将y设为根节点
 else if x = left[p[x]]  
    then left[p[x]] ← y  // 情况2:如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
    else right[p[x]] ← y // 情况3:(x是它父节点的右孩子) 将y设为“x的父节点的右孩子”
 left[y] ← x             // 将 “x” 设为 “y的左孩子”
 p[x] ← y                // 将 “x的父节点” 设为 “y”

左旋中的“左”,意味着“被旋转的节点将变成一个左节点”

3、右旋

对 x 进行右旋,意味着,将“x 的左孩子”设为“x 的父亲节点”;即:将 x 变成了一个右节点(x 成了为 y 的右孩子)! 因此,右旋中的“右”,意味着“被旋转的节点将变成一个右节点”。

  右旋的伪代码

RIGHT-ROTATE(T, y)  
 x ← left[y]              // 前提:这里假设y的左孩子为x。下面开始正式操作
 left[y] ← right[x]       // 将 “x的右孩子” 设为 “y的左孩子”,即 将B设为y的左孩子
 p[right[x]] ← y          // 将 “y” 设为 “x的右孩子的父亲”,即 将B的父亲设为y
 p[x] ← p[y]              // 将 “y的父亲” 设为 “x的父亲”
 if p[y] = nil[T]       
 then root[T] ← x         // 情况1:如果“y的父亲” 是空节点,则将x设为根节点
 else if y = right[p[y]]  
     then right[p[y]] ← x // 情况2:如果y是它父节点的右孩子,则将x设为“y的父节点的左孩子”
     else left[p[y]] ← x  // 情况3:(y是它父节点的左孩子) 将x设为“y的父节点的左孩子”
 right[x] ← y             // 将 “y” 设为 “x的右孩子”
 p[y] ← x                 // 将 “y的父节点” 设为 “x”

右旋中的“右”,意味着“被旋转的节点将变成一个右节点”

4、添加

首先,将红黑树当作一颗二叉查找树,将节点插入;然后,将节点着色为红色;最后,通过旋转和重新着色等方法来修正该树,使之重新成为一颗红黑树。

1、将红黑树当作一颗二叉查找树,将节点插入

红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然是一颗二叉查找树。

2、将插入的节点着色为"红色"。

为什么着色成红色,而不是黑色呢?涉及到红黑树的五条特性。将插入的节点着色为红色,不会违背"特性(5)"!少违背一条特性,就意味着我们需要处理的情况越少。

3、通过一系列的旋转或着色等操作,使之重新成为一颗红黑树。

第2步中,将插入节点着色为"红色"之后,不会违背"特性(5)"。那它到底会违背哪些特性呢?
对于"特性(1)",显然不会违背了。因为我们已经将它涂成红色了。
对于"特性(2)",显然也不会违背。在第一步中,我们是将红黑树当作二叉查找树,然后执行的插入操作。而根据二叉查找数的特点,插入操作不会改变根节点。所以,根节点仍然是黑色。
对于"特性(3)",显然不会违背了。这里的叶子节点是指的空叶子节点,插入非空节点并不会对它们造成影响。
对于"特性(4)",是有可能违背的!
那接下来,想办法使之"满足特性(4)",就可以将树重新构造成红黑树了。

添加操作的伪代码

RB-INSERT(T, z)  
 y ← nil[T]                  // 新建节点“y”,将y设为空节点。
 x ← root[T]                 // 设“红黑树T”的根节点为“x”
 while x ≠ nil[T]            // 找出要插入的节点“z”在二叉树T中的位置“y”
     do y ← x                      
        if key[z] < key[x]  
           then x ← left[x]  
           else x ← right[x]  
 p[z] ← y                    // 设置 “z的父亲” 为 “y”
 if y = nil[T]                     
    then root[T] ← z         // 情况1:若y是空节点,则将z设为根
    else if key[z] < key[y]        
       then left[y] ← z      // 情况2:若“z所包含的值” < “y所包含的值”,则将z设为“y的左孩子”
       else right[y] ← z     // 情况3:(“z所包含的值” >= “y所包含的值”)将z设为“y的右孩子” 
 left[z] ← nil[T]            // z的左孩子设为空
 right[z] ← nil[T]           // z的右孩子设为空。至此,已经完成将“节点z插入到二叉树”中了。
 color[z] ← RED              // 将z着色为“红色”
 RB-INSERT-FIXUP(T, z)       // 通过RB-INSERT-FIXUP对红黑树的节点进行颜色修改以及旋转,让树T仍然是一颗红黑树

添加修正操作的伪代码

RB-INSERT-FIXUP(T, z)
while color[p[z]] = RED               // 若“当前节点(z)的父节点是红色”,则进行以下处理。
    do if p[z] = left[p[p[z]]]        // 若“z的父节点”是“z的祖父节点的左孩子”,则进行以下处理。
          then y ← right[p[p[z]]]     // 将y设置为“z的叔叔节点(z的祖父节点的右孩子)”
               if color[y] = RED                 // Case 1条件:叔叔是红色
                  then color[p[z]] ← BLACK        ▹ Case 1   //(01) 将“父节点”设为黑色。
                       color[y] ← BLACK           ▹ Case 1   //(02) 将“叔叔节点”设为黑色。
                       color[p[p[z]]] ← RED       ▹ Case 1   //(03) 将“祖父节点”设为“红色”。
                       z ← p[p[z]]                ▹ Case 1   //(04) 将“祖父节点”设为“当前节点”(红色节点)
                  else if z = right[p[z]]                                // Case 2条件:叔叔是黑色,且当前节点是右孩子
                          then z ← p[z]           ▹ Case 2   //(01) 将“父节点”作为“新的当前节点”。
                               LEFT-ROTATE(T, z)  ▹ Case 2   //(02) 以“新的当前节点”为支点进行左旋。
                          color[p[z]] ← BLACK     ▹ Case 3   //Case 3条件:叔叔是黑色,且当前节点是左孩子。(01) 将“父节点”设为“黑色”。
                          color[p[p[z]]] ← RED    ▹ Case 3   //(02) 将“祖父节点”设为“红色”。
                          RIGHT-ROTATE(T, p[p[z]]) ▹ Case 3   //(03) 以“祖父节点”为支点进行右旋。
       else (same as then clause with "right" and "left" exchanged)  // 若“z的父节点”是“z的祖父节点的右孩子”,将上面的操作中“right”和“left”交换位置,然后依次执行。
color[root[T]] ← BLACK

插入二叉树处理情况:

1、被插入的节点是根节点。

处理方法:直接把此节点涂为黑色。

2、被插入的节点的父节点是黑色。

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

3、被插入的节点的父节点是红色。

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

现象说明处理策略
Case1当前节点的父节点是红色,且当前节点的祖父节点的另一个子节点(叔叔节点)也是红色

(01) 将“父节点”设为黑色。

(02) 将“叔叔节点”设为黑色。

(03) 将“祖父节点”设为“红色”。

(04) 将“祖父节点”设为“当前节点”(红色节点)

Case2当前节点的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的右孩子

(01) 将“父节点”作为“新的当前节点”。

(02) 以“新的当前节点”为支点进行左旋。

Case3当前节点的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的左孩子

(01) 将“父节点”设为“黑色”。
(02) 将“祖父节点”设为“红色”。

(03) 以“祖父节点”为支点进行右旋。

5、删除

首先,将红黑树当作一颗二叉查找树,将该节点从二叉查找树中删除;然后,通过"旋转和重新着色"等一系列来修正该树,使之重新成为一棵红黑树。

1、将红黑树当作一颗二叉查找树,将节点删除。

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

删除操作的伪代码

RB-DELETE(T, z)
if left[z] = nil[T] or right[z] = nil[T]         
   then y ← z                     // 若“z的左孩子” 或 “z的右孩子”为空,则将“z”赋值给 “y”;
   else y ← TREE-SUCCESSOR(z)     // 否则,将“z的后继节点”赋值给 “y”。
if left[y] ≠ nil[T]
   then x ← left[y]               // 若“y的左孩子” 不为空,则将“y的左孩子” 赋值给 “x”;
   else x ← right[y]              // 否则,“y的右孩子” 赋值给 “x”。
p[x] ← p[y]                       // 将“y的父节点” 设置为 “x的父节点”
if p[y] = nil[T]                               
   then root[T] ← x               // 情况1:若“y的父节点” 为空,则设置“x” 为 “根节点”。
   else if y = left[p[y]]                    
           then left[p[y]] ← x    // 情况2:若“y是它父节点的左孩子”,则设置“x” 为 “y的父节点的左孩子”
           else right[p[y]] ← x   // 情况3:若“y是它父节点的右孩子”,则设置“x” 为 “y的父节点的右孩子”
if y ≠ z                                    
   then key[z] ← key[y]           // 若“y的值” 赋值给 “z”。注意:这里只拷贝z的值给y,而没有拷贝z的颜色!!!
        copy y's satellite data into z         
if color[y] = BLACK                            
   then RB-DELETE-FIXUP(T, x)     // 若“y为黑节点”,则调用
return y

RB-DELETE-FIXUP的伪代码

RB-DELETE-FIXUP(T, x)
while x ≠ root[T] and color[x] = BLACK  
    do if x = left[p[x]]      
          then w ← right[p[x]]     // 若 “x”是“它父节点的左孩子”,则设置 “w”为“x的叔叔”(即x为它父节点的右孩子)                                          
               if color[w] = RED   // Case 1: x是“黑+黑”节点,x的兄弟节点是红色。(此时x的父节点和x的兄弟节点的子节点都是黑节点)。
                  then color[w] ← BLACK       ▹  Case 1   //(01) 将x的兄弟节点设为“黑色”。
                       color[p[x]] ← RED      ▹  Case 1   //(02) 将x的父节点设为“红色”。
                       LEFT-ROTATE(T, p[x])   ▹  Case 1   //(03) 对x的父节点进行左旋。
                       w ← right[p[x]]        ▹  Case 1   //(04) 左旋后,重新设置x的兄弟节点。
               if color[left[w]] = BLACK and color[right[w]] = BLACK  // Case 2: x是“黑+黑”节点,x的兄弟节点是黑色,x的兄弟节点的两个孩子都是黑色。
                  then color[w] ← RED         ▹  Case 2   //(01) 将x的兄弟节点设为“红色”。
                       x ←  p[x]              ▹  Case 2   //(02) 设置“x的父节点”为“新的x节点”。
                  else if color[right[w]] = BLACK         // Case 3: x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的左孩子是红色,右孩子是黑色的。
                          then color[left[w]] ← BLACK    ▹  Case 3   //(01) 将x兄弟节点的左孩子设为“黑色”。
                               color[w] ← RED            ▹  Case 3   //(02) 将x兄弟节点设为“红色”。
                               RIGHT-ROTATE(T, w)        ▹  Case 3   //(03) 对x的兄弟节点进行右旋。
                               w ← right[p[x]]           ▹  Case 3   //(04) 右旋后,重新设置x的兄弟节点。
                        color[w] ← color[p[x]]           ▹  Case 4   // Case 4: x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的右孩子是红色的。(01) 将x父节点颜色 赋值给 x的兄弟节点。
                        color[p[x]] ← BLACK              ▹  Case 4   //(02) 将x父节点设为“黑色”。
                        color[right[w]] ← BLACK          ▹  Case 4   //(03) 将x兄弟节点的右子节设为“黑色”。
                        LEFT-ROTATE(T, p[x])             ▹  Case 4   //(04) 对x的父节点进行左旋。
                        x ← root[T]                      ▹  Case 4   //(05) 设置“x”为“根节点”。
       else (same as then clause with "right" and "left" exchanged)  // 若 “x”是“它父节点的右孩子”,将上面的操作中“right”和“left”交换位置,然后依次执行。
color[x] ← BLACK

删除处理情况:

1、x是“红+黑”节点。
处理方法:直接把x设为黑色,结束。此时红黑树性质全部恢复。
2、x是“黑+黑”节点,且x是根。
处理方法:什么都不做,结束。此时红黑树性质全部恢复。
3、x是“黑+黑”节点,且x不是根。
处理方法:这种情况又可以划分为4种子情况。这4种子情况如下表所示:

现象说明处理策略
Case1x是“黑+黑”节点,x的兄弟节点是红色(此时x的父节点和x的兄弟节点的子节点都是黑节点)

(01) 将x的兄弟节点设为“黑色”。

(02) 将x的父节点设为“红色”。

(03) 对x的父节点进行左旋。

(04) 左旋后,重新设置x的兄弟节点。

Case2x是“黑+黑”节点,x的兄弟节点是黑色,x兄弟节点的两个孩子节点都是黑色。

(01) 将x的兄弟节点设为“红色”。

(02) 设置“x的父节点”为“新的x节点”。

Case3x是“黑+黑”节点,x的兄弟节点是黑色,x兄弟节点的左孩子是红色,右孩子是黑色

(01) 将x兄弟节点的左孩子设为“黑色”。

(02) 将x兄弟节点设为“红色”。

(03) 对x的兄弟节点进行右旋。

(04) 右旋后,重新设置x的兄弟节点。

Case4x是“黑+黑”节点,x的兄弟节点是黑色,x兄弟节点的右孩子是红色,x兄弟节点的左孩子任意颜色。

(01) 将x父节点颜色 赋值给 x的兄弟节点。

(02) 将x父节点设为“黑色”。

(03) 将x兄弟节点的右子节设为“黑色”。

(04) 对x的父节点进行左旋。

(05) 设置“x”为“根节点”。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杀神lwz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值