黑树属于特殊的二叉搜索树,通过对任一条从根结点到叶子结点简单路径上的各个结点进行颜色约束,确保了没有一条路径会比其他路径长出两倍,是*乎*衡的。

  • 有以下性质:
  1. 每个结点或是红色,或是黑色;
  2. 根结点是黑色的;
  3. 每个叶结点是黑色的;
  4. 如果一个结点是红色的,则其两个子结点都是黑色的;
  5. 对于每个结点x,从该结点到其所有后代叶结点的简单路径上,均包含了相同数量的黑色结点(即 黑高,记为 bh(x) 相同 )

红黑树大量应用在底层数据结构中,主要用于存储和查找。*日开发虽然很少自己去写红黑树,但是深入了解数据结构和算法是非常必要的。

那红黑树都有哪些操作都是如何实现的……请看下文。

 

树中每个结点均包含5个属性:color、key、left、right、p

红黑树的插入、删除需要不断的维护上述5个性质……如何实现?两种操作:旋转(左旋、右旋)变色(在插入、删除中实现)

旋转操作

红黑树_红黑树

1、左旋

 1 LEFT-ROTATE(T, x)
 2     y = x.right
 3     x.right = y.left
 4     if y.left != T.nil then
 5         y.left.p = x
 6     y.p = x.p
 7     if x.p == T.nil then
 8         T.root = y
 9     else if x == x.p.left then
10         x.p.left = y
11     else
12         x.p.right = y
13     y.left = x
14     x.p = y

 2、右旋:

 1 RIGHT-ROTATE(T, y)
 2     x = y.left
 3     y.left = x.right
 4     if x.right != T.nil then
 5         x.right.p = y
 6     x.p = y.p
 7     if y.p == T.nil then
 8         T.root = x
 9     else if y == y.p.left then
10         y.p.left = x
11     else 
      y.p.right = x 12 x.right = y 13 y.p = x

 

插入操作

 与普通二叉搜索树差异不大,唯二的区别是:需要维护红黑树的5个性质,插入的结点的初始颜色是红色的(如果插入结点是黑色的,则将违反性质5

1、插入

 1 RB-INSERT(T, x)
 2     y = T.nil
 3     z = T.root
 4     while z != T.nil do
 5         y = z
 6         if x.key > y.key then
 7             z = z.right
 8         else
 9             z = z.left
10     x.p = y
11     if y == T.nil then
12         T.root = x
13     else if y.key > x.key then
14         y.left = x
15     else
16          y.right = x
17     x.left = T.nil
18     x.right = T.nil
19     x.color = RED    //插入点是红色的
20     RB-INSERT-FIXUP(T, x)  //维护红黑树性质

2、维护

插入结点为x,若x.p.color == BLACK,则完事大吉,不需要做任何处理;否则将违反性质4,故需要旋转跳跃我闭着眼~

如何旋转、如何变色,将需要视情况而定:

情形一:结点x的父节点为左孩子结点x为左孩子结点x的叔结点为黑色(或不存在,指向黑色哨兵结点T.nil)。此时仅需①x.p.color = BLACK; x.p.p.color = RED + ②对x.p.p进行右旋操作

  红黑树_结点_02

情形二:结点x的父节点为左孩子结点x为右孩子结点x的叔结点为黑色(或不存在,指向黑色哨兵T.nil)。此类情况经过一定的操作是可以转换为情形一的。此时仅需①令x=x.p(为的是后续能复用情形一的代码) + ②对x进行左旋 + ③对x.p与x.p.p变色 + ④对x.p.p进行右旋操作

  红黑树_结点_03

情形三:结点x的父结点为右孩子结点x为右孩子结点x的叔结点为黑色(或不存在,指向哨兵结点T.nil)。此时仅需令①x.p.color = BLACK; x.p.p.color = RED + ②对x.p.p进行左旋

   红黑树_子树_04

情形四:结点x的父结点为左孩子结点x为左孩子结点x的叔结点为黑色(或不存在,指向哨兵结点T.nil)。此时仅需①令x=x.p(为的是后续能复用情形三的代码)+ ②对x进行右旋 + ③对x.p与x.p.p换色 + ④对x.p.p进行左旋

  红黑树_结点_05

情形五:结点x的叔结点红色。此类情况仅需给结点变色即可,①x.p.color = x.p.p.right.color = BLACK; ②x.p.p.color = RED

  红黑树_删除结点_06

具体操作如下:

 1 RB-INSERT-FIXUP(T, x)
 2     while x.p.color == RED do  //违反了性质4(x.color亦为RED)
 3         if x.p == x.p.p.left then
 4             y = x.p.p.right     //结点x的叔结点
 5             if y.color == BLACK then
 6                 if x == x.p.right then  //情形二
 7                     x = x.p
 8                     LEFT-ROTATE(T, x)
 9                 x.p.color = BLACK   //情形一
10                 x.p.p.color = RED
11                 RIGHT-ROTATE(T, x.p.p)
12             else  //y.color == RED  //情形五
13                 x.p.color = BLACK
14                 y.color = BLACK
15                 x.p.p.color = RED
16                 x = x.p.p
17 
18         else //x.p == x.p.p.right
19             y = x.p.p.left     //结点x的叔结点
20             if y.color == BLACK then
21                 if x == x.p.left then  //情形四
22                     x = x.p
23                     RIGHT-ROTATE(T, x)
24                 x.p.color = BLACK  //情形三
25                 x.p.p.color = RED
26                 LEFT-ROTATE(T, x.p.p)
27             else  //y.color == RED  //情形五
28                 x.p.color = BLACK
29                 y.color = BLACK
30                 x.p.p.color = RED
31                 x = x.p.p
32     //while循环外部
33     T.root.color = BLACK

 

最值操作

获取以结点x为根的子树值最大/最小的结点

 

 1 //最小值结点
 2 TREE-MINIMUM(x)
 3     while x.left != T.nil do
 4         x = x.left
 5     return x
 6 
 7 
 8 //最大值结点
 9 TREE-MAXIMUM(x)
10     while x.right != T.nil do
11         x = x.right
12     return x

 

前驱&后继操作

获取以结点x为根结点的子树前驱结点与后继结点

 1 //后继结点
 2 TREE-SUCCESSOR(x)
 3     if x.right != T.nil then
 4         return TREE-MINIMUM(x)
 5     y = x.p
 6     while y != T.nil and x == y.right do
 7         x = y
 8         y = y.p
 9     return y
10 
11 
12 //前驱结点
13 TREE-PRECESSOR(x)
14     if x.left != T.nil then
15         return TREE-MAXIMUM(x)
16     y = x.p
17     while y != T.nil and x == y.left do
18         x = y
19         y = y.p
20     return y

 

删除操作

同样与普通二叉搜索树删除结点类似 

1、删除

删除结点x也需要技巧……如何维持搜索二叉树的正确性,也需要视情况而定:

情形一:即将删除的结点x无左子树,此时仅需用结点x的右子树补上即。注意:删除结点x前需要:

  1.  缓存x的颜色,记为ori-color;//原因该结点即将被移除,如果是黑色的话,将导致经过该结点的黑高减1,但另外一边不变导致,即破坏了红黑树的性质5
  2. 记录替补结点y,y = x.right;//原因是如果真的破坏了红黑树的性质,将从替补结点y开始不断修复颜色。(为什么不将y.color变为x的原始颜色后,再修复?原因其一该情形仅判断没有左子树,那可能也不存在右子树,右孩子x.right直接指向哨兵结点T.nil,哨兵结点T.nil无法改变属性~ )故y永远指向即将在树内移动且不能变色的结点。

  红黑树_子树_07

情形二:即将删除的结点x无右子树,此时仅需用结点x的左子树补上即可。注意:删除结点x前需要 :

  1. 缓存x的颜色,记为ori-color;//原因如情形一所述一致
  2. 记录替补结点y; //原因如情形一所述类似

  红黑树_结点_08

情形三:即将删除的结点x拥有左右子树,此时操作相对复杂,在删除结点x前需要:

  1. 找到x的后继结点y;//拥有左右子树的结点要删除的话,那肯定是找前驱或者后继结点补上的……
  2. 记录y的替补结点w = y.right;//记录y.right,因为y的位置将有y.right顶替。为什么是y.right ? ? y是结点x的后继结点,那y肯定没有左子树…所以y的位置将有y.right(不管是否为T.nil)替补…
  3. 缓存y的颜色,记为ori-color;//y的位置将由w顶替,w不能变色(像情形一二所述,w可能为T.nil),故需要标记y的原始颜色,后续需要修复时也有依据
  4. 移植w到y的位置;
  5. 令y成为结点x右子树根结点,移植y到结点x的位置;//结点y上任
  6. y.color = x.color;//y.color的颜色变为x.color。为甚?首先y肯定存在且不为T.nil(可改变颜色);可减少变化量,即使移动破坏了红黑树的性质,那也只需要从结点w开始修复即可…
  7. y = w;//纯粹是想与情形一二保持一致,y永远指向在树内移动又不能变色的结点~~

  红黑树_子树_09

最后 if ori-color == BLACK then 肯定破坏了红黑树的性质,需要维护;为何ori-color为红色的不需要?原因:(先假设ori-color的原始结点为A)1、A的黑高没有改变;2、A肯定不是根结点,根节点还是黑色。没有任何影响~

具体操作

 1 TREE-TRANSPLANT(T, D, N)
 2     //将结点N移植到结点D位置
 3     if D.p == T.nil then
 4         T.root = N
 5     else if D == D.p.left then
 6         D.p.left = N
 7     else
 8         D.p.right = N
 9     N.p = D.p
10 
11 
12 //删除结点x
13 RB-DELETE(T, x)
14     ori-color = x.color //标记即将被移除的结点x的颜色,如果该结点为黑色,那么移除后可能会引起黑高不一致
15     if x.left == T.nil then
16         //情形一
17         y = x.right
18         TREE-TRANSPLANT(T, x, y)
19     else if x.right == T.nil then
20         //情形二
21         y = x.left
22         TREE-TRANSPLANT(T, x, y)
23     else 
24         //情形三
25         y = TREE-MINIMUM(x.right)
26         ori-color = y.color
27         w = y.right //w即将被移动到结点y的位置,需要标记,目的是解决ori-color为黑色时,移动w到y的位置导致的黑高不一致问题
28         if y.p != x then
29             //将结点y升级为结点x右子树的根结点。
30             TREE-TRANSPLANT(T, y, y.right)
31             y.right = x.right
32             y.right.p = y
33         TREE-TRANSPLANT(T, x, y)
34         y.left = x.left
35         y.left.p = y
36         y.color = x.color
37         y = w
38     if ori-color == BLACK then
39         //ori-color 为黑色,可能破坏了红黑树性质,需要调整维护;
40         RB-DELETE-FIXUP(T, y)

2、维护

仅发生在ori-color为黑色的条件下。

既然移动结点y的操作破坏了红黑树的性质,那肯定需要从结点y开始不断修复;如何操作,也同样需要不同情况不同操作…

y:被移动的结点;w:结点y的兄弟结点(y.p肯定存在左右子树,否则原树将违反性质5)

情形一:y.color == RED。此时需:

  1. 令y.color = BLACK;//因为已知y.p的左子树或者右子树,比另一边少了一个黑色结点,所以直接变色即可

  红黑树_红黑树_10

情形二:y为左孩子,y的颜色为黑色,w的颜色为红色此时需:

  1. 令w.color = BLACK,y.p.color = RED;
  2. 对x.p进行左旋操作;
  3. 令w重新指向y的兄弟;
  4. 继续判断w的情况;

  红黑树_红黑树_11

情形三:y为左孩子,y与w同为黑色,且w的左右孩子颜色亦为黑色。此时需:

  1. 改变w的颜色为红色;
  2. 将y重定向为y的父结点;
  3. 开启新一轮修复;

  红黑树_删除结点_12

情形四:y为左孩子,y与w同为黑色,且w的右孩子为红色此时要:

  1. 改变w的颜色为y父结点颜色、w右孩子变成黑色、再将y父结点颜色变为黑色;
  2. 对y父结点进行左旋操作;
  3. 令y指向T.root(退出循环)

  红黑树_结点_13

情形五:y为左孩子,y与w同为黑色,w的右孩子为黑色,w的左孩子为红色此时要:

  1. 改变w左孩子的颜色为黑色;
  2. 改变w的颜色为红色;
  3. 对w进行右旋;
  4. 令w重新指向y的兄弟结点;此时就可以用情形四的方法处理了

  红黑树_缓存_14

情形六:y为右孩子,y的颜色为黑色,w的颜色为红色此时要:

  1. 改变w的颜色为黑色,改变y.p的颜色为红色;
  2. 对y.p进行右旋……

  红黑树_红黑树_15

情形七:y为右孩子,y与w同为黑色,w的左右孩子均为黑色。此时要:

  1. 改变w的颜色为红色;
  2. 令y重定向为y.p;
  3. 重新检测…

  红黑树_红黑树_16

情形八:y为右孩子,y与w同为黑色,w的左孩子为红色此时要:

  1. 令w的颜色为y的父结点颜色,令w的左孩子为黑色,令y父结点为黑色;
  2. 对y父结点进行右旋操作;
  3. 令y指向T.root(退出循环)

  红黑树_子树_17

情形九:y为右孩子,y与w同为黑色,w的左孩子为黑色,w的右孩子为红色此时要:

  1. 改变w的颜色为红色,改变w右孩子为黑色;
  2. 对w进行左旋操作;
  3. 令w重新指向y的兄弟结点;
  4. 变为了情况八…

  红黑树_子树_18

具体操作如下

 1 RB-DELETE-FIXUP(T, y)
 2     while y != T.root and y.color == BLACK do
 3         if y = y.p.left then
 4             w = y.p.right
 5             if w.color == RED then    //情形二
 6                 y.p.color = RED
 7                 w.color = BLACK
 8                 LEFT-ROTATE(T, y.p)
 9                 w = y.p.right
10             if w.left.color == BLACK and w.right.color == BLACK then
11                 //情形三
12                 w.color = RED
13                 y = y.p
14             else
15                 if w.right.color == BLACK then
16                     //情形四
17                     //此时w.left.color必定为红色,因为条件…
18                     w.color = RED
19                     w.left.color = BLACK
20                     RIGHT-ROTATE(T, w)
21                     w = y.p.right
22                 //情形五
23                 w.color = y.p.color
24                 y.p.color = BLACK
25                 w.right.color = BLACK
26                 LEFT-ROTATE(T, y.p)
27                 y = T.root
28         else // y == y.p.right
29             w = y.p.left
30             if w.color == RED then //情形六
31                 w.color = BLACK
32                 y.p.color = RED
33                 RIGHT-ROTATE(T, y.p)
34                 w = y.p.left
35             if w.right.color == BLACK and w.left.color == BLACK then
36                 //情形七
37                 w.color == RED
38                 y = y.p
39             else
40                 if w.left.color == BLACK then    //情形八
41                     //此时w.right.color肯定为红色,条件限制…
42                     w.color = RED
43                     w.right.color = BLACK
44                     LEFT-ROTATE(T, w)
45                     w = y.p.left
46                 //情形九
47                 w.color = y.p.color 
48                 y.p.color = BLACK
49                 w.left.color = BLACK
50                 RIGHT-ROTATE(T, y.p)
51                 y = T.root
52     //退出while循环…
53     y.color = BLACK    //情形一…