R-B Tree简介

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

红黑树的特性:

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

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

红黑树示意图如下:

红黑树的应用

红黑树的应用比较广泛,主要是用它来存储有序的数据,它的时间复杂度是O(lgn),效率非常之高。
例如,Java集合中的TreeSet和TreeMap,C++ STL中的set、map,以及Linux虚拟内存的管理,都是通过红黑树去实现的。

红黑树的时间复杂度和相关证明

红黑树的时间复杂度为: O(lgn)
下面通过“数学归纳法”对红黑树的时间复杂度进行证明。

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

证明:
  ”一棵含有n个节点的红黑树的高度至多为2log(n+1)” 的逆否命题是 “高度为h的红黑树,它的包含的内节点个数至少为 2h/2-1个”。
  我们只需要证明逆否命题,即可证明原命题为真;即只需证明 “高度为h的红黑树,它的包含的内节点个数至少为 2h/2-1个”。

  从某个节点x出发(不包括该节点)到达一个叶节点的任意一条路径上,黑色节点的个数称为该节点的黑高度(x’s black height),记为bh(x)。关于bh(x)有两点需要说明:
  第1点:根据红黑树的”特性(5) ,即从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点”可知,从节点x出发到达的所有的叶节点具有相同数目的黑节点。这也就意味着,bh(x)的值是唯一的!
  第2点:根据红黑色的”特性(4),即如果一个节点是红色的,则它的子节点必须是黑色的”可知,从节点x出发达到叶节点”所经历的黑节点数目”>= “所经历的红节点的数目”。假设x是根节点,则可以得出结论”bh(x) >= h/2”。进而,我们只需证明 “高度为h的红黑树,它的包含的黑节点个数至少为 2bh(x)-1个”即可。

  到这里,我们将需要证明的定理已经由
“一棵含有n个节点的红黑树的高度至多为2log(n+1)”
转变成只需要证明
“高度为h的红黑树,它的包含的内节点个数至少为 2bh(x)-1个”。

下面通过”数学归纳法”开始论证高度为h的红黑树,它的包含的内节点个数至少为 2bh(x)-1个”。

(01) 当树的高度h=0时,
  内节点个数是0,bh(x) 为0,2bh(x)-1 也为 0。显然,原命题成立。

(02) 当h>0,且树的高度为 h-1 时,它包含的节点个数至少为 2bh(x)-1-1。这个是根据(01)推断出来的!

  下面,由树的高度为 h-1 的已知条件推出“树的高度为 h 时,它所包含的节点树为 2bh(x)-1”。

  当树的高度为 h 时,
  对于节点x(x为根节点),其黑高度为bh(x)。
  对于节点x的左右子树,它们黑高度为 bh(x) 或者 bh(x)-1。
  根据(02)的已知条件,我们已知 “x的左右子树,即高度为 h-1 的节点,它包含的节点至少为 2bh(x)-1-1 个”;

  所以,节点x所包含的节点至少为 ( 2bh(x)-1-1 ) + ( 2bh(x)-1-1 ) + 1 = 2^bh(x)-1。即节点x所包含的节点至少为 2bh(x)-1。
  因此,原命题成立。

  由(01)、(02)得出,”高度为h的红黑树,它的包含的内节点个数至少为 2^bh(x)-1个”。
  因此,“一棵含有n个节点的红黑树的高度至多为2log(n+1)”。

红黑树的基本操作(一) 左旋和右旋

红黑树的基本操作是添加、删除。在对红黑树进行添加或删除之后,都会用到旋转方法。为什么呢?道理很简单,添加或删除红黑树中的节点之后,红黑树就发生了变化,可能不满足红黑树的5条性质,也就不再是一颗红黑树了,而是一颗普通的树。而通过旋转,可以使这颗树重新成为红黑树。简单点说,旋转的目的是让树保持红黑树的特性。
旋转包括两种:左旋 和 右旋。下面分别对它们进行介绍。

1. 左旋

对x进行左旋,意味着”将x变成一个左节点”。

左旋的伪代码《算法导论》:参考上面的示意图和下面的伪代码,理解“红黑树T的节点x进行左旋”是如何进行的。

LEFT-ROTATE(T, x)  y ← right[x]            // 前提:这里假设x的右孩子为y。下面开始正式操作right[x] ← left[y]      // 将 “y的左孩子” 设为 “x的右孩子”,即 将β设为x的右孩子p[left[y]] ← x          // 将 “x” 设为 “y的左孩子的父亲”,即 将β的父亲设为xp[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”

理解左旋之后,看看下面一个更鲜明的例子。你可以先不看右边的结果,自己尝试一下。

2. 右旋

对x进行左旋,意味着”将x变成一个左节点”。

右旋的伪代码《算法导论》:参考上面的示意图和下面的伪代码,理解“红黑树T的节点y进行右旋”是如何进行的。

RIGHT-ROTATE(T, y)  x ← left[y]             // 前提:这里假设y的左孩子为x。下面开始正式操作left[y] ← right[x]      // 将 “x的右孩子” 设为 “y的左孩子”,即 将β设为y的左孩子p[right[x]] ← y         // 将 “y” 设为 “x的右孩子的父亲”,即 将β的父亲设为yp[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”

理解右旋之后,看看下面一个更鲜明的例子。你可以先不看右边的结果,自己尝试一下。

旋转总结:

(01) 左旋 和 右旋 是相对的两个概念,原理类似。理解一个也就理解了另一个。

(02) 下面谈谈如何区分 左旋 和 右旋。
在实际应用中,若没有彻底理解 左旋 和 右旋,可能会将它们混淆。下面谈谈我对如何区分 左旋 和 右旋 的理解。

3. 区分 左旋 和 右旋

仔细观察上面”左旋”和”右旋”的示意图。我们能清晰的发现,它们是对称的。无论是左旋还是右旋,被旋转的树,在旋转前是二叉查找树,并且旋转之后仍然是一颗二叉查找树。

左旋示例图(以x为节点进行左旋):

                               zx                          /                  / \      --(左旋)-->       xy   z                      /y

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

右旋示例图(以x为节点进行右旋):

                               yx                            \                 / \      --(右旋)-->           xy   z                            \
                                   z

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

红黑树的基本操作(二) 添加

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

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

第二步:将插入的节点着色为”红色”。
  为什么着色成红色,而不是黑色呢?为什么呢?在回答之前,我们需要重新温习一下红黑树的特性:
(1) 每个节点或者是黑色,或者是红色。
(2) 根节点是黑色。
(3) 每个叶子节点是黑色。 [注意:这里叶子节点,是指为空的叶子节点!]
(4) 如果一个节点是红色的,则它的子节点必须是黑色的。
(5) 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
  将插入的节点着色为红色,不会违背”特性(5)”!少违背一条特性,就意味着我们需要处理的情况越少。接下来,就要努力的让这棵树满足其它性质即可;满足了的话,它就又是一颗红黑树了。o(∩∩)o…哈哈

第三步: 通过一系列的旋转或着色等操作,使之重新成为一颗红黑树。
  第二步中,将插入节点着色为”红色”之后,不会违背”特性(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。理解了RB-INSERT之后,我们接着对 RB-INSERT-FIXUP的伪代码进行说明。

添加修正操作的伪代码《算法导论》

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

根据被插入节点的父节点的情况,可以将”当节点z被着色为红色节点,并插入二叉树”划分为三种情况来处理。
① 情况说明:被插入的节点是根节点。
 处理方法:直接把此节点涂为黑色。
② 情况说明:被插入的节点的父节点是黑色。
 处理方法:什么也不需要做。节点被插入后,仍然是红黑树。
③ 情况说明:被插入的节点的父节点是红色。
 处理方法:那么,该情况与红黑树的“特性(5)”相冲突。这种情况下,被插入节点是一定存在非空祖父节点的;进一步的讲,被插入节点也一定存在叔叔节点(即使叔叔节点为空,我们也视之为存在,空节点本身就是黑色节点)。理解这点之后,我们依据”叔叔节点的情况”,将这种情况进一步划分为3种情况(Case)。

现象说明 处理策略
Case 1 当前节点的父节点是红色,且当前节点的祖父节点的另一个子节点(叔叔节点)也是红色。 (01) 将“父节点”设为黑色。
(02) 将“叔叔节点”设为黑色。
(03) 将“祖父节点”设为“红色”。
(04) 将“祖父节点”设为“当前节点”(红色节点);即,之后继续对“当前节点”进行操作。
Case 2 当前节点的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的右孩子 (01) 将“父节点”作为“新的当前节点”。
(02) 以“新的当前节点”为支点进行左旋。
Case 3 当前节点的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的左孩子 (01) 将“父节点”设为“黑色”。
(02) 将“祖父节点”设为“红色”。
(03) 以“祖父节点”为支点进行右旋。

上面三种情况(Case)处理问题的核心思路都是:将红色的节点移到根节点;然后,将根节点设为黑色。下面对它们详细进行介绍。

1. (Case 1)叔叔是红色

1.1 现象说明
当前节点(即,被插入节点)的父节点是红色,且当前节点的祖父节点的另一个子节点(叔叔节点)也是红色。

1.2 处理策略
(01) 将“父节点”设为黑色。
(02) 将“叔叔节点”设为黑色。
(03) 将“祖父节点”设为“红色”。
(04) 将“祖父节点”设为“当前节点”(红色节点);即,之后继续对“当前节点”进行操作。

  下面谈谈为什么要这样处理。(建议理解的时候,通过下面的图进行对比)
  “当前节点”和“父节点”都是红色,违背“特性(4)”。所以,将“父节点”设置“黑色”以解决这个问题。
  但是,将“父节点”由“红色”变成“黑色”之后,违背了“特性(5)”:因为,包含“父节点”的分支的黑色节点的总数增加了1。 解决这个问题的办法是:将“祖父节点”由“黑色”变成红色,同时,将“叔叔节点”由“红色”变成“黑色”。关于这里,说明几点:第一,为什么“祖父节点”之前是黑色?这个应该很容易想明白,因为在变换操作之前,该树是红黑树,“父节点”是红色,那么“祖父节点”一定是黑色。 第二,为什么将“祖父节点”由“黑色”变成红色,同时,将“叔叔节点”由“红色”变成“黑色”;能解决“包含‘父节点’的分支的黑色节点的总数增加了1”的问题。这个道理也很简单。“包含‘父节点’的分支的黑色节点的总数增加了1” 同时也意味着 “包含‘祖父节点’的分支的黑色节点的总数增加了1”,既然这样,我们通过将“祖父节点”由“黑色”变成“红色”以解决“包含‘祖父节点’的分支的黑色节点的总数增加了1”的问题; 但是,这样处理之后又会引起另一个问题“包含‘叔叔’节点的分支的黑色节点的总数减少了1”,现在我们已知“叔叔节点”是“红色”,将“叔叔节点”设为“黑色”就能解决这个问题。 所以,将“祖父节点”由“黑色”变成红色,同时,将“叔叔节点”由“红色”变成“黑色”;就解决了该问题。
  按照上面的步骤处理之后:当前节点、父节点、叔叔节点之间都不会违背红黑树特性,但祖父节点却不一定。若此时,祖父节点是根节点,直接将祖父节点设为“黑色”,那就完全解决这个问题了;若祖父节点不是根节点,那我们需要将“祖父节点”设为“新的当前节点”,接着对“新的当前节点”进行分析。

1.3 示意图

2. (Case 2)叔叔是黑色,且当前节点是右孩子

2.1 现象说明
当前节点(即,被插入节点)的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的右孩子

2.2 处理策略
(01) 将“父节点”作为“新的当前节点”。
(02) 以“新的当前节点”为支点进行左旋。

  下面谈谈为什么要这样处理。(建议理解的时候,通过下面的图进行对比)
  首先,将“父节点”作为“新的当前节点”;接着,以“新的当前节点”为支点进行左旋。 为了便于理解,我们先说明第(02)步,再说明第(01)步;为了便于说明,我们设置“父节点”的代号为F(Father),“当前节点”的代号为S(Son)。
为什么要“以F为支点进行左旋”呢?根据已知条件可知:S是F的右孩子。而之前我们说过,我们处理红黑树的核心思想:将红色的节点移到根节点;然后,将根节点设为黑色。既然是“将红色的节点移到根节点”,那就是说要不断的将破坏红黑树特性的红色节点上移(即向根方向移动)。 而S又是一个右孩子,因此,我们可以通过“左旋”来将S上移!
  按照上面的步骤(以F为支点进行左旋)处理之后:若S变成了根节点,那么直接将其设为“黑色”,就完全解决问题了;若S不是根节点,那我们需要执行步骤(01),即“将F设为‘新的当前节点’”。那为什么不继续以S为新的当前节点继续处理,而需要以F为新的当前节点来进行处理呢?这是因为“左旋”之后,F变成了S的“子节点”,即S变成了F的父节点;而我们处理问题的时候,需要从下至上(由叶到根)方向进行处理;也就是说,必须先解决“孩子”的问题,再解决“父亲”的问题;所以,我们执行步骤(01):将“父节点”作为“新的当前节点”。

2.2 示意图

3. (Case 3)叔叔是黑色,且当前节点是左孩子

3.1 现象说明
当前节点(即,被插入节点)的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的左孩子

3.2 处理策略
(01) 将“父节点”设为“黑色”。
(02) 将“祖父节点”设为“红色”。
(03) 以“祖父节点”为支点进行右旋。

  下面谈谈为什么要这样处理。(建议理解的时候,通过下面的图进行对比)
  为了便于说明,我们设置“当前节点”为S(Original Son),“兄弟节点”为B(Brother),“叔叔节点”为U(Uncle),“父节点”为F(Father),祖父节点为G(Grand-Father)。
  S和F都是红色,违背了红黑树的“特性(4)”,我们可以将F由“红色”变为“黑色”,就解决了“违背‘特性(4)’”的问题;但却引起了其它问题:违背特性(5),因为将F由红色改为黑色之后,所有经过F的分支的黑色节点的个数增加了1。那我们如何解决“所有经过F的分支的黑色节点的个数增加了1”的问题呢? 我们可以通过“将G由黑色变成红色”,同时“以G为支点进行右旋”来解决。

2.3 示意图

提示:上面的进行Case 3处理之后,再将节点”120”当作当前节点,就变成了Case 2的情况。

红黑树的基本操作(三) 删除

将红黑树内的某一个节点删除。需要执行的操作依次是:首先,将红黑树当作一颗二叉查找树,将该节点从二叉查找树中删除;然后,通过”旋转和重新着色”等一系列来修正该树,使之重新成为一棵红黑树。详细描述如下:

第一步:将红黑树当作一颗二叉查找树,将节点删除。
  这和”删除常规二叉查找树中删除节点的方法是一样的”。分3种情况:
  ① 被删除节点没有儿子,即为叶节点。那么,直接将该节点删除就OK了。
  ② 被删除节点只有一个儿子。那么,直接删除该节点,并用该节点的唯一子节点顶替它的位置。
  ③ 被删除节点有两个儿子。那么,先找出它的后继节点;然后把“它的后继节点的内容”复制给“该节点的内容”;之后,删除“它的后继节点”。在这里,后继节点相当于替身,在将后继节点的内容复制给”被删除节点”之后,再将后继节点删除。这样就巧妙的将问题转换为”删除后继节点”的情况了,下面就考虑后继节点。 在”被删除节点”有两个非空子节点的情况下,它的后继节点不可能是双子非空。既然”的后继节点”不可能双子都非空,就意味着”该节点的后继节点”要么没有儿子,要么只有一个儿子。若没有儿子,则按”情况① “进行处理;若只有一个儿子,则按”情况② “进行处理。

第二步:通过”旋转和重新着色”等一系列来修正该树,使之重新成为一棵红黑树。
  因为”第一步”中删除节点之后,可能会违背红黑树的特性。所以需要通过”旋转和重新着色”来修正该树,使之重新成为一棵红黑树。

删除操作的伪代码《算法导论》

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。理解了RB-DELETE之后,接着对 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) 每个节点或者是黑色,或者是红色。
(2) 根节点是黑色。
(3) 每个叶子节点是黑色。 [注意:这里叶子节点,是指为空的叶子节点!]
(4) 如果一个节点是红色的,则它的子节点必须是黑色的。
(5) 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

  前面我们将”删除红黑树中的节点”大致分为两步,在第一步中”将红黑树当作一颗二叉查找树,将节点删除”后,可能违反”特性(2)、(4)、(5)”三个特性。第二步需要解决上面的三个问题,进而保持红黑树的全部特性。
  为了便于分析,我们假设”x包含一个额外的黑色”(x原本的颜色还存在),这样就不会违反”特性(5)”。为什么呢?
  通过RB-DELETE算法,我们知道:删除节点y之后,x占据了原来节点y的位置。 既然删除y(y是黑色),意味着减少一个黑色节点;那么,再在该位置上增加一个黑色即可。这样,当我们假设”x包含一个额外的黑色”,就正好弥补了”删除y所丢失的黑色节点”,也就不会违反”特性(5)”。 因此,假设”x包含一个额外的黑色”(x原本的颜色还存在),这样就不会违反”特性(5)”。
  现在,x不仅包含它原本的颜色属性,x还包含一个额外的黑色。即x的颜色属性是”红+黑”或”黑+黑”,它违反了”特性(1)”。

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

将上面的姿态,可以概括为3种情况。
① 情况说明:x是“红+黑”节点。
  处理方法:直接把x设为黑色,结束。此时红黑树性质全部恢复。
② 情况说明:x是“黑+黑”节点,且x是根。
  处理方法:什么都不做,结束。此时红黑树性质全部恢复。
③ 情况说明:x是“黑+黑”节点,且x不是根。
  处理方法:这种情况又可以划分为4种子情况。这4种子情况如下表所示:

现象说明 处理策略
Case 1 x是”黑+黑”节点,x的兄弟节点是红色。(此时x的父节点和x的兄弟节点的子节点都是黑节点)。 (01) 将x的兄弟节点设为“黑色”。
(02) 将x的父节点设为“红色”。
(03) 对x的父节点进行左旋。
(04) 左旋后,重新设置x的兄弟节点。
Case 2 x是“黑+黑”节点,x的兄弟节点是黑色,x的兄弟节点的两个孩子都是黑色。 (01) 将x的兄弟节点设为“红色”。
(02) 设置“x的父节点”为“新的x节点”。
Case 3 x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的左孩子是红色,右孩子是黑色的。 (01) 将x兄弟节点的左孩子设为“黑色”。
(02) 将x兄弟节点设为“红色”。
(03) 对x的兄弟节点进行右旋。
(04) 右旋后,重新设置x的兄弟节点。
Case 4 x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的右孩子是红色的,x的兄弟节点的左孩子任意颜色。 (01) 将x父节点颜色 赋值给 x的兄弟节点。
(02) 将x父节点设为“黑色”。
(03) 将x兄弟节点的右子节设为“黑色”。
(04) 对x的父节点进行左旋。
(05) 设置“x”为“根节点”。

1. (Case 1)x是”黑+黑”节点,x的兄弟节点是红色

1.1 现象说明
x是”黑+黑”节点,x的兄弟节点是红色。(此时x的父节点和x的兄弟节点的子节点都是黑节点)。

1.2 处理策略
(01) 将x的兄弟节点设为“黑色”。
(02) 将x的父节点设为“红色”。
(03) 对x的父节点进行左旋。
(04) 左旋后,重新设置x的兄弟节点。

  下面谈谈为什么要这样处理。(建议理解的时候,通过下面的图进行对比)
  这样做的目的是将“Case 1”转换为“Case 2”、“Case 3”或“Case 4”,从而进行进一步的处理。对x的父节点进行左旋;左旋后,为了保持红黑树特性,就需要在左旋前“将x的兄弟节点设为黑色”,同时“将x的父节点设为红色”;左旋后,由于x的兄弟节点发生了变化,需要更新x的兄弟节点,从而进行后续处理。

1.3 示意图

2. (Case 2) x是”黑+黑”节点,x的兄弟节点是黑色,x的兄弟节点的两个孩子都是黑色

2.1 现象说明
x是“黑+黑”节点,x的兄弟节点是黑色,x的兄弟节点的两个孩子都是黑色。

2.2 处理策略
(01) 将x的兄弟节点设为“红色”。
(02) 设置“x的父节点”为“新的x节点”。

  下面谈谈为什么要这样处理。(建议理解的时候,通过下面的图进行对比)
  这个情况的处理思想:是将“x中多余的一个黑色属性上移(往根方向移动)”。 x是“黑+黑”节点,我们将x由“黑+黑”节点 变成 “黑”节点,多余的一个“黑”属性移到x的父节点中,即x的父节点多出了一个黑属性(若x的父节点原先是“黑”,则此时变成了“黑+黑”;若x的父节点原先时“红”,则此时变成了“红+黑”)。 此时,需要注意的是:所有经过x的分支中黑节点个数没变化;但是,所有经过x的兄弟节点的分支中黑色节点的个数增加了1(因为x的父节点多了一个黑色属性)!为了解决这个问题,我们需要将“所有经过x的兄弟节点的分支中黑色节点的个数减1”即可,那么就可以通过“将x的兄弟节点由黑色变成红色”来实现。
  经过上面的步骤(将x的兄弟节点设为红色),多余的一个颜色属性(黑色)已经跑到x的父节点中。我们需要将x的父节点设为“新的x节点”进行处理。若“新的x节点”是“黑+红”,直接将“新的x节点”设为黑色,即可完全解决该问题;若“新的x节点”是“黑+黑”,则需要对“新的x节点”进行进一步处理。

2.3 示意图

3. (Case 3)x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的左孩子是红色,右孩子是黑色的

3.1 现象说明
x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的左孩子是红色,右孩子是黑色的。

3.2 处理策略
(01) 将x兄弟节点的左孩子设为“黑色”。
(02) 将x兄弟节点设为“红色”。
(03) 对x的兄弟节点进行右旋。
(04) 右旋后,重新设置x的兄弟节点。

  下面谈谈为什么要这样处理。(建议理解的时候,通过下面的图进行对比)
  我们处理“Case 3”的目的是为了将“Case 3”进行转换,转换成“Case 4”,从而进行进一步的处理。转换的方式是对x的兄弟节点进行右旋;为了保证右旋后,它仍然是红黑树,就需要在右旋前“将x的兄弟节点的左孩子设为黑色”,同时“将x的兄弟节点设为红色”;右旋后,由于x的兄弟节点发生了变化,需要更新x的兄弟节点,从而进行后续处理。

3.3 示意图

4. (Case 4)x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的右孩子是红色的,x的兄弟节点的左孩子任意颜色

4.1 现象说明
x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的右孩子是红色的,x的兄弟节点的左孩子任意颜色。

4.2 处理策略
(01) 将x父节点颜色 赋值给 x的兄弟节点。
(02) 将x父节点设为“黑色”。
(03) 将x兄弟节点的右子节设为“黑色”。
(04) 对x的父节点进行左旋。
(05) 设置“x”为“根节点”。

  下面谈谈为什么要这样处理。(建议理解的时候,通过下面的图进行对比)
  我们处理“Case 4”的目的是:去掉x中额外的黑色,将x变成单独的黑色。处理的方式是“:进行颜色修改,然后对x的父节点进行左旋。下面,我们来分析是如何实现的。
  为了便于说明,我们设置“当前节点”为S(Original Son),“兄弟节点”为B(Brother),“兄弟节点的左孩子”为BLS(Brother’s Left Son),“兄弟节点的右孩子”为BRS(Brother’s Right Son),“父节点”为F(Father)。
  我们要对F进行左旋。但在左旋前,我们需要调换F和B的颜色,并设置BRS为黑色。为什么需要这里处理呢?因为左旋后,F和BLS是父子关系,而我们已知BL是红色,如果F是红色,则违背了“特性(4)”;为了解决这一问题,我们将“F设置为黑色”。 但是,F设置为黑色之后,为了保证满足“特性(5)”,即为了保证左旋之后:
  第一,“同时经过根节点和S的分支的黑色节点个数不变”。
    若满足“第一”,只需要S丢弃它多余的颜色即可。因为S的颜色是“黑+黑”,而左旋后“同时经过根节点和S的分支的黑色节点个数”增加了1;现在,只需将S由“黑+黑”变成单独的“黑”节点,即可满足“第一”。
  第二,“同时经过根节点和BLS的分支的黑色节点数不变”。
    若满足“第二”,只需要将“F的原始颜色”赋值给B即可。之前,我们已经将“F设置为黑色”(即,将B的颜色”黑色”,赋值给了F)。至此,我们算是调换了F和B的颜色。
  第三,“同时经过根节点和BRS的分支的黑色节点数不变”。
    在“第二”已经满足的情况下,若要满足“第三”,只需要将BRS设置为“黑色”即可。
经过,上面的处理之后。红黑树的特性全部得到的满足!接着,我们将x设为根节点,就可以跳出while循环(参考伪代码);即完成了全部处理。

至此,我们就完成了Case 4的处理。理解Case 4的核心,是了解如何“去掉当前节点额外的黑色”。

4.3 示意图

OK!至此,红黑树的理论知识差不多讲完了。后续再更新红黑树的实现代码!

参考文献

1, 《算法导论》

2, 教你透彻了解红黑树

红黑树的介绍

红黑树(Red-Black Tree,简称R-B Tree),它一种特殊的二叉查找树。
红黑树是特殊的二叉查找树,意味着它满足二叉查找树的特征:任意一个节点所包含的键值,大于等于左孩子的键值,小于等于右孩子的键值。
除了具备该特性之外,红黑树还包括许多额外的信息。

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

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

红黑树示意图如下:

红黑树的Java实现(代码说明)

红黑树的基本操作是添加、删除和旋转。在对红黑树进行添加或删除后,会用到旋转方法。为什么呢?道理很简单,添加或删除红黑树中的节点之后,红黑树就发生了变化,可能不满足红黑树的5条性质,也就不再是一颗红黑树了,而是一颗普通的树。而通过旋转,可以使这颗树重新成为红黑树。简单点说,旋转的目的是让树保持红黑树的特性。
旋转包括两种:左旋 和 右旋。下面分别对红黑树的基本操作进行介绍。

1. 基本定义

public class RBTree<T extends Comparable<T>> {private RBTNode<T> mRoot;    // 根结点private static final boolean RED   = false;private static final boolean BLACK = true;public class RBTNode<T extends Comparable<T>> {boolean color;        // 颜色T key;                // 关键字(键值)RBTNode<T> left;    // 左孩子RBTNode<T> right;    // 右孩子RBTNode<T> parent;    // 父结点public RBTNode(T key, boolean color, RBTNode<T> parent, RBTNode<T> left, RBTNode<T> right) {this.key = key;this.color = color;this.parent = parent;this.left = left;this.right = right;}}...
}

RBTree是红黑树对应的类,RBTNode是红黑树的节点类。在RBTree中包含了根节点mRoot和红黑树的相关API。
注意:在实现红黑树API的过程中,我重载了许多函数。重载的原因,一是因为有的API是内部接口,有的是外部接口;二是为了让结构更加清晰。

2. 左旋

对x进行左旋,意味着”将x变成一个左节点”。

左旋的实现代码(Java语言)

/* * 对红黑树的节点(x)进行左旋转** 左旋示意图(对节点x进行左旋):*      px                              px*     /                               /*    x                               y                *   /  \      --(左旋)-.            / \                #*  lx   y                          x  ry     *     /   \                       /  \*    ly   ry                     lx  ly  ***/
private void leftRotate(RBTNode<T> x) {// 设置x的右孩子为yRBTNode<T> y = x.right;// 将 “y的左孩子” 设为 “x的右孩子”;// 如果y的左孩子非空,将 “x” 设为 “y的左孩子的父亲”x.right = y.left;if (y.left != null)y.left.parent = x;// 将 “x的父亲” 设为 “y的父亲”y.parent = x.parent;if (x.parent == null) {this.mRoot = y;            // 如果 “x的父亲” 是空节点,则将y设为根节点} else {if (x.parent.left == x)x.parent.left = y;    // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”elsex.parent.right = y;    // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”}// 将 “x” 设为 “y的左孩子”y.left = x;// 将 “x的父节点” 设为 “y”x.parent = y;
}

3. 右旋

对y进行左旋,意味着”将y变成一个右节点”。

右旋的实现代码(Java语言)

/* * 对红黑树的节点(y)进行右旋转** 右旋示意图(对节点y进行左旋):*            py                               py*           /                                /*          y                                x                  *         /  \      --(右旋)-.             /  \                     #*        x   ry                           lx   y  *       / \                                   / \                   #*      lx  rx                                rx  ry* */
private void rightRotate(RBTNode<T> y) {// 设置x是当前节点的左孩子。RBTNode<T> x = y.left;// 将 “x的右孩子” 设为 “y的左孩子”;// 如果"x的右孩子"不为空的话,将 “y” 设为 “x的右孩子的父亲”y.left = x.right;if (x.right != null)x.right.parent = y;// 将 “y的父亲” 设为 “x的父亲”x.parent = y.parent;if (y.parent == null) {this.mRoot = x;            // 如果 “y的父亲” 是空节点,则将x设为根节点} else {if (y == y.parent.right)y.parent.right = x;    // 如果 y是它父节点的右孩子,则将x设为“y的父节点的右孩子”elsey.parent.left = x;    // (y是它父节点的左孩子) 将x设为“x的父节点的左孩子”}// 将 “y” 设为 “x的右孩子”x.right = y;// 将 “y的父节点” 设为 “x”y.parent = x;
}

4. 添加

将一个节点插入到红黑树中,需要执行哪些步骤呢?首先,将红黑树当作一颗二叉查找树,将节点插入;然后,将节点着色为红色;最后,通过”旋转和重新着色”等一系列操作来修正该树,使之重新成为一颗红黑树。详细描述如下:
第一步: 将红黑树当作一颗二叉查找树,将节点插入。
  红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然是一颗二叉查找树。也就意味着,树的键值仍然是有序的。此外,无论是左旋还是右旋,若旋转之前这棵树是二叉查找树,旋转之后它一定还是二叉查找树。这也就意味着,任何的旋转和重新着色操作,都不会改变它仍然是一颗二叉查找树的事实。
好吧?那接下来,我们就来想方设法的旋转以及重新着色,使这颗树重新成为红黑树!

第二步:将插入的节点着色为”红色”。
  为什么着色成红色,而不是黑色呢?为什么呢?在回答之前,我们需要重新温习一下红黑树的特性:
(1) 每个节点或者是黑色,或者是红色。
(2) 根节点是黑色。
(3) 每个叶子节点是黑色。 [注意:这里叶子节点,是指为空的叶子节点!]
(4) 如果一个节点是红色的,则它的子节点必须是黑色的。
(5) 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
  将插入的节点着色为红色,不会违背”特性(5)”!少违背一条特性,就意味着我们需要处理的情况越少。接下来,就要努力的让这棵树满足其它性质即可;满足了的话,它就又是一颗红黑树了。o(∩∩)o…哈哈

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

添加操作的实现代码(Java语言)

/* * 将结点插入到红黑树中** 参数说明:*     node 插入的结点        // 对应《算法导论》中的node*/
private void insert(RBTNode<T> node) {int cmp;RBTNode<T> y = null;RBTNode<T> x = this.mRoot;// 1. 将红黑树当作一颗二叉查找树,将节点添加到二叉查找树中。while (x != null) {y = x;cmp = node.key.compareTo(x.key);if (cmp < 0)x = x.left;elsex = x.right;}node.parent = y;if (y!=null) {cmp = node.key.compareTo(y.key);if (cmp < 0)y.left = node;elsey.right = node;} else {this.mRoot = node;}// 2. 设置节点的颜色为红色node.color = RED;// 3. 将它重新修正为一颗二叉查找树insertFixUp(node);
}/* * 新建结点(key),并将其插入到红黑树中** 参数说明:*     key 插入结点的键值*/
public void insert(T key) {RBTNode<T> node=new RBTNode<T>(key,BLACK,null,null,null);// 如果新建结点失败,则返回。if (node != null)insert(node);
}

内部接口 – insert(node)的作用是将”node”节点插入到红黑树中。
外部接口 – insert(key)的作用是将”key”添加到红黑树中。

添加修正操作的实现代码(Java语言)

/** 红黑树插入修正函数** 在向红黑树中插入节点之后(失去平衡),再调用该函数;* 目的是将它重新塑造成一颗红黑树。** 参数说明:*     node 插入的结点        // 对应《算法导论》中的z*/
private void insertFixUp(RBTNode<T> node) {RBTNode<T> parent, gparent;// 若“父节点存在,并且父节点的颜色是红色”while (((parent = parentOf(node))!=null) && isRed(parent)) {gparent = parentOf(parent);//若“父节点”是“祖父节点的左孩子”if (parent == gparent.left) {// Case 1条件:叔叔节点是红色RBTNode<T> uncle = gparent.right;if ((uncle!=null) && isRed(uncle)) {setBlack(uncle);setBlack(parent);setRed(gparent);node = gparent;continue;}// Case 2条件:叔叔是黑色,且当前节点是右孩子if (parent.right == node) {RBTNode<T> tmp;leftRotate(parent);tmp = parent;parent = node;node = tmp;}// Case 3条件:叔叔是黑色,且当前节点是左孩子。setBlack(parent);setRed(gparent);rightRotate(gparent);} else {    //若“z的父节点”是“z的祖父节点的右孩子”// Case 1条件:叔叔节点是红色RBTNode<T> uncle = gparent.left;if ((uncle!=null) && isRed(uncle)) {setBlack(uncle);setBlack(parent);setRed(gparent);node = gparent;continue;}// Case 2条件:叔叔是黑色,且当前节点是左孩子if (parent.left == node) {RBTNode<T> tmp;rightRotate(parent);tmp = parent;parent = node;node = tmp;}// Case 3条件:叔叔是黑色,且当前节点是右孩子。setBlack(parent);setRed(gparent);leftRotate(gparent);}}// 将根节点设为黑色setBlack(this.mRoot);
}

insertFixUp(node)的作用是对应”上面所讲的第三步”。它是一个内部接口。

5. 删除操作

将红黑树内的某一个节点删除。需要执行的操作依次是:首先,将红黑树当作一颗二叉查找树,将该节点从二叉查找树中删除;然后,通过”旋转和重新着色”等一系列来修正该树,使之重新成为一棵红黑树。详细描述如下:
第一步:将红黑树当作一颗二叉查找树,将节点删除。
  这和”删除常规二叉查找树中删除节点的方法是一样的”。分3种情况:
① 被删除节点没有儿子,即为叶节点。那么,直接将该节点删除就OK了。
② 被删除节点只有一个儿子。那么,直接删除该节点,并用该节点的唯一子节点顶替它的位置。
③ 被删除节点有两个儿子。那么,先找出它的后继节点;然后把“它的后继节点的内容”复制给“该节点的内容”;之后,删除“它的后继节点”。在这里,后继节点相当于替身,在将后继节点的内容复制给”被删除节点”之后,再将后继节点删除。这样就巧妙的将问题转换为”删除后继节点”的情况了,下面就考虑后继节点。 在”被删除节点”有两个非空子节点的情况下,它的后继节点不可能是双子非空。既然”的后继节点”不可能双子都非空,就意味着”该节点的后继节点”要么没有儿子,要么只有一个儿子。若没有儿子,则按”情况① “进行处理;若只有一个儿子,则按”情况② “进行处理。

第二步:通过”旋转和重新着色”等一系列来修正该树,使之重新成为一棵红黑树。
  因为”第一步”中删除节点之后,可能会违背红黑树的特性。所以需要通过”旋转和重新着色”来修正该树,使之重新成为一棵红黑树。

删除操作的实现代码(Java语言)

/* * 删除结点(node),并返回被删除的结点** 参数说明:*     node 删除的结点*/
private void remove(RBTNode<T> node) {RBTNode<T> child, parent;boolean color;// 被删除节点的"左右孩子都不为空"的情况。if ( (node.left!=null) && (node.right!=null) ) {// 被删节点的后继节点。(称为"取代节点")// 用它来取代"被删节点"的位置,然后再将"被删节点"去掉。RBTNode<T> replace = node;// 获取后继节点replace = replace.right;while (replace.left != null)replace = replace.left;// "node节点"不是根节点(只有根节点不存在父节点)if (parentOf(node)!=null) {if (parentOf(node).left == node)parentOf(node).left = replace;elseparentOf(node).right = replace;} else {// "node节点"是根节点,更新根节点。this.mRoot = replace;}// child是"取代节点"的右孩子,也是需要"调整的节点"。// "取代节点"肯定不存在左孩子!因为它是一个后继节点。child = replace.right;parent = parentOf(replace);// 保存"取代节点"的颜色color = colorOf(replace);// "被删除节点"是"它的后继节点的父节点"if (parent == node) {parent = replace;} else {// child不为空if (child!=null)setParent(child, parent);parent.left = child;replace.right = node.right;setParent(node.right, replace);}replace.parent = node.parent;replace.color = node.color;replace.left = node.left;node.left.parent = replace;if (color == BLACK)removeFixUp(child, parent);node = null;return ;}if (node.left !=null) {child = node.left;} else {child = node.right;}parent = node.parent;// 保存"取代节点"的颜色color = node.color;if (child!=null)child.parent = parent;// "node节点"不是根节点if (parent!=null) {if (parent.left == node)parent.left = child;elseparent.right = child;} else {this.mRoot = child;}if (color == BLACK)removeFixUp(child, parent);node = null;
}/* * 删除结点(z),并返回被删除的结点** 参数说明:*     tree 红黑树的根结点*     z 删除的结点*/
public void remove(T key) {RBTNode<T> node; if ((node = search(mRoot, key)) != null)remove(node);
}

内部接口 – remove(node)的作用是将”node”节点插入到红黑树中。
外部接口 – remove(key)删除红黑树中键值为key的节点。

删除修正操作的实现代码(Java语言)

/** 红黑树删除修正函数** 在从红黑树中删除插入节点之后(红黑树失去平衡),再调用该函数;* 目的是将它重新塑造成一颗红黑树。** 参数说明:*     node 待修正的节点*/
private void removeFixUp(RBTNode<T> node, RBTNode<T> parent) {RBTNode<T> other;while ((node==null || isBlack(node)) && (node != this.mRoot)) {if (parent.left == node) {other = parent.right;if (isRed(other)) {// Case 1: x的兄弟w是红色的  setBlack(other);setRed(parent);leftRotate(parent);other = parent.right;}if ((other.left==null || isBlack(other.left)) &&(other.right==null || isBlack(other.right))) {// Case 2: x的兄弟w是黑色,且w的俩个孩子也都是黑色的  setRed(other);node = parent;parent = parentOf(node);} else {if (other.right==null || isBlack(other.right)) {// Case 3: x的兄弟w是黑色的,并且w的左孩子是红色,右孩子为黑色。  setBlack(other.left);setRed(other);rightRotate(other);other = parent.right;}// Case 4: x的兄弟w是黑色的;并且w的右孩子是红色的,左孩子任意颜色。setColor(other, colorOf(parent));setBlack(parent);setBlack(other.right);leftRotate(parent);node = this.mRoot;break;}} else {other = parent.left;if (isRed(other)) {// Case 1: x的兄弟w是红色的  setBlack(other);setRed(parent);rightRotate(parent);other = parent.left;}if ((other.left==null || isBlack(other.left)) &&(other.right==null || isBlack(other.right))) {// Case 2: x的兄弟w是黑色,且w的俩个孩子也都是黑色的  setRed(other);node = parent;parent = parentOf(node);} else {if (other.left==null || isBlack(other.left)) {// Case 3: x的兄弟w是黑色的,并且w的左孩子是红色,右孩子为黑色。  setBlack(other.right);setRed(other);leftRotate(other);other = parent.left;}// Case 4: x的兄弟w是黑色的;并且w的右孩子是红色的,左孩子任意颜色。setColor(other, colorOf(parent));setBlack(parent);setBlack(other.left);rightRotate(parent);node = this.mRoot;break;}}}if (node!=null)setBlack(node);
}

removeFixup(node, parent)是对应”上面所讲的第三步”。它是一个内部接口。

红黑树的Java实现(完整源码)

下面是红黑树实现的完整代码和相应的测试程序。
(1) 除了上面所说的”左旋”、”右旋”、”添加”、”删除”等基本操作之后,还实现了”遍历”、”查找”、”打印”、”最小值”、”最大值”、”创建”、”销毁”等接口。
(2) 函数接口大多分为内部接口和外部接口。内部接口是private函数,外部接口则是public函数。
(3) 测试代码中提供了”插入”和”删除”动作的检测开关。默认是关闭的,打开方法可以参考”代码中的说明”。建议在打开开关后,在草稿上自己动手绘制一下红黑树。

红黑树的实现文件(RBTree.java)

/*** Java 语言: 红黑树** @author skywang* @date 2013/11/07*/public class RBTree<T extends Comparable<T>> {private RBTNode<T> mRoot;    // 根结点private static final boolean RED   = false;private static final boolean BLACK = true;public class RBTNode<T extends Comparable<T>> {boolean color;        // 颜色T key;                // 关键字(键值)RBTNode<T> left;    // 左孩子RBTNode<T> right;    // 右孩子RBTNode<T> parent;    // 父结点public RBTNode(T key, boolean color, RBTNode<T> parent, RBTNode<T> left, RBTNode<T> right) {this.key = key;this.color = color;this.parent = parent;this.left = left;this.right = right;}public T getKey() {return key;}public String toString() {return ""+key+(this.color==RED?"(R)":"B");}}public RBTree() {mRoot=null;}private RBTNode<T> parentOf(RBTNode<T> node) {return node!=null ? node.parent : null;}private boolean colorOf(RBTNode<T> node) {return node!=null ? node.color : BLACK;}private boolean isRed(RBTNode<T> node) {return ((node!=null)&&(node.color==RED)) ? true : false;}private boolean isBlack(RBTNode<T> node) {return !isRed(node);}private void setBlack(RBTNode<T> node) {if (node!=null)node.color = BLACK;}private void setRed(RBTNode<T> node) {if (node!=null)node.color = RED;}private void setParent(RBTNode<T> node, RBTNode<T> parent) {if (node!=null)node.parent = parent;}private void setColor(RBTNode<T> node, boolean color) {if (node!=null)node.color = color;}/** 前序遍历"红黑树"*/private void preOrder(RBTNode<T> tree) {if(tree != null) {System.out.print(tree.key+" ");preOrder(tree.left);preOrder(tree.right);}}public void preOrder() {preOrder(mRoot);}/** 中序遍历"红黑树"*/private void inOrder(RBTNode<T> tree) {if(tree != null) {inOrder(tree.left);System.out.print(tree.key+" ");inOrder(tree.right);}}public void inOrder() {inOrder(mRoot);}/** 后序遍历"红黑树"*/private void postOrder(RBTNode<T> tree) {if(tree != null){postOrder(tree.left);postOrder(tree.right);System.out.print(tree.key+" ");}}public void postOrder() {postOrder(mRoot);}/** (递归实现)查找"红黑树x"中键值为key的节点*/private RBTNode<T> search(RBTNode<T> x, T key) {if (x==null)return x;int cmp = key.compareTo(x.key);if (cmp < 0)return search(x.left, key);else if (cmp > 0)return search(x.right, key);elsereturn x;}public RBTNode<T> search(T key) {return search(mRoot, key);}/** (非递归实现)查找"红黑树x"中键值为key的节点*/private RBTNode<T> iterativeSearch(RBTNode<T> x, T key) {while (x!=null) {int cmp = key.compareTo(x.key);if (cmp < 0) x = x.left;else if (cmp > 0) x = x.right;elsereturn x;}return x;}public RBTNode<T> iterativeSearch(T key) {return iterativeSearch(mRoot, key);}/* * 查找最小结点:返回tree为根结点的红黑树的最小结点。*/private RBTNode<T> minimum(RBTNode<T> tree) {if (tree == null)return null;while(tree.left != null)tree = tree.left;return tree;}public T minimum() {RBTNode<T> p = minimum(mRoot);if (p != null)return p.key;return null;}/* * 查找最大结点:返回tree为根结点的红黑树的最大结点。*/private RBTNode<T> maximum(RBTNode<T> tree) {if (tree == null)return null;while(tree.right != null)tree = tree.right;return tree;}public T maximum() {RBTNode<T> p = maximum(mRoot);if (p != null)return p.key;return null;}/* * 找结点(x)的后继结点。即,查找"红黑树中数据值大于该结点"的"最小结点"。*/public RBTNode<T> successor(RBTNode<T> x) {// 如果x存在右孩子,则"x的后继结点"为 "以其右孩子为根的子树的最小结点"。if (x.right != null)return minimum(x.right);// 如果x没有右孩子。则x有以下两种可能:// (01) x是"一个左孩子",则"x的后继结点"为 "它的父结点"。// (02) x是"一个右孩子",则查找"x的最低的父结点,并且该父结点要具有左孩子",找到的这个"最低的父结点"就是"x的后继结点"。RBTNode<T> y = x.parent;while ((y!=null) && (x==y.right)) {x = y;y = y.parent;}return y;}/* * 找结点(x)的前驱结点。即,查找"红黑树中数据值小于该结点"的"最大结点"。*/public RBTNode<T> predecessor(RBTNode<T> x) {// 如果x存在左孩子,则"x的前驱结点"为 "以其左孩子为根的子树的最大结点"。if (x.left != null)return maximum(x.left);// 如果x没有左孩子。则x有以下两种可能:// (01) x是"一个右孩子",则"x的前驱结点"为 "它的父结点"。// (01) x是"一个左孩子",则查找"x的最低的父结点,并且该父结点要具有右孩子",找到的这个"最低的父结点"就是"x的前驱结点"。RBTNode<T> y = x.parent;while ((y!=null) && (x==y.left)) {x = y;y = y.parent;}return y;}/* * 对红黑树的节点(x)进行左旋转** 左旋示意图(对节点x进行左旋):*      px                              px*     /                               /*    x                               y                *   /  \      --(左旋)-.           / \                #*  lx   y                          x  ry     *     /   \                       /  \*    ly   ry                     lx  ly  ***/private void leftRotate(RBTNode<T> x) {// 设置x的右孩子为yRBTNode<T> y = x.right;// 将 “y的左孩子” 设为 “x的右孩子”;// 如果y的左孩子非空,将 “x” 设为 “y的左孩子的父亲”x.right = y.left;if (y.left != null)y.left.parent = x;// 将 “x的父亲” 设为 “y的父亲”y.parent = x.parent;if (x.parent == null) {this.mRoot = y;            // 如果 “x的父亲” 是空节点,则将y设为根节点} else {if (x.parent.left == x)x.parent.left = y;    // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”elsex.parent.right = y;    // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”}// 将 “x” 设为 “y的左孩子”y.left = x;// 将 “x的父节点” 设为 “y”x.parent = y;}/* * 对红黑树的节点(y)进行右旋转** 右旋示意图(对节点y进行左旋):*            py                               py*           /                                /*          y                                x                  *         /  \      --(右旋)-.            /  \                     #*        x   ry                           lx   y  *       / \                                   / \                   #*      lx  rx                                rx  ry* */private void rightRotate(RBTNode<T> y) {// 设置x是当前节点的左孩子。RBTNode<T> x = y.left;// 将 “x的右孩子” 设为 “y的左孩子”;// 如果"x的右孩子"不为空的话,将 “y” 设为 “x的右孩子的父亲”y.left = x.right;if (x.right != null)x.right.parent = y;// 将 “y的父亲” 设为 “x的父亲”x.parent = y.parent;if (y.parent == null) {this.mRoot = x;            // 如果 “y的父亲” 是空节点,则将x设为根节点} else {if (y == y.parent.right)y.parent.right = x;    // 如果 y是它父节点的右孩子,则将x设为“y的父节点的右孩子”elsey.parent.left = x;    // (y是它父节点的左孩子) 将x设为“x的父节点的左孩子”}// 将 “y” 设为 “x的右孩子”x.right = y;// 将 “y的父节点” 设为 “x”y.parent = x;}/** 红黑树插入修正函数** 在向红黑树中插入节点之后(失去平衡),再调用该函数;* 目的是将它重新塑造成一颗红黑树。** 参数说明:*     node 插入的结点        // 对应《算法导论》中的z*/private void insertFixUp(RBTNode<T> node) {RBTNode<T> parent, gparent;// 若“父节点存在,并且父节点的颜色是红色”while (((parent = parentOf(node))!=null) && isRed(parent)) {gparent = parentOf(parent);//若“父节点”是“祖父节点的左孩子”if (parent == gparent.left) {// Case 1条件:叔叔节点是红色RBTNode<T> uncle = gparent.right;if ((uncle!=null) && isRed(uncle)) {setBlack(uncle);setBlack(parent);setRed(gparent);node = gparent;continue;}// Case 2条件:叔叔是黑色,且当前节点是右孩子if (parent.right == node) {RBTNode<T> tmp;leftRotate(parent);tmp = parent;parent = node;node = tmp;}// Case 3条件:叔叔是黑色,且当前节点是左孩子。setBlack(parent);setRed(gparent);rightRotate(gparent);} else {    //若“z的父节点”是“z的祖父节点的右孩子”// Case 1条件:叔叔节点是红色RBTNode<T> uncle = gparent.left;if ((uncle!=null) && isRed(uncle)) {setBlack(uncle);setBlack(parent);setRed(gparent);node = gparent;continue;}// Case 2条件:叔叔是黑色,且当前节点是左孩子if (parent.left == node) {RBTNode<T> tmp;rightRotate(parent);tmp = parent;parent = node;node = tmp;}// Case 3条件:叔叔是黑色,且当前节点是右孩子。setBlack(parent);setRed(gparent);leftRotate(gparent);}}// 将根节点设为黑色setBlack(this.mRoot);}/* * 将结点插入到红黑树中** 参数说明:*     node 插入的结点        // 对应《算法导论》中的node*/private void insert(RBTNode<T> node) {int cmp;RBTNode<T> y = null;RBTNode<T> x = this.mRoot;// 1. 将红黑树当作一颗二叉查找树,将节点添加到二叉查找树中。while (x != null) {y = x;cmp = node.key.compareTo(x.key);if (cmp < 0)x = x.left;elsex = x.right;}node.parent = y;if (y!=null) {cmp = node.key.compareTo(y.key);if (cmp < 0)y.left = node;elsey.right = node;} else {this.mRoot = node;}// 2. 设置节点的颜色为红色node.color = RED;// 3. 将它重新修正为一颗二叉查找树insertFixUp(node);}/* * 新建结点(key),并将其插入到红黑树中** 参数说明:*     key 插入结点的键值*/public void insert(T key) {RBTNode<T> node=new RBTNode<T>(key,BLACK,null,null,null);// 如果新建结点失败,则返回。if (node != null)insert(node);}/** 红黑树删除修正函数** 在从红黑树中删除插入节点之后(红黑树失去平衡),再调用该函数;* 目的是将它重新塑造成一颗红黑树。** 参数说明:*     node 待修正的节点*/private void removeFixUp(RBTNode<T> node, RBTNode<T> parent) {RBTNode<T> other;while ((node==null || isBlack(node)) && (node != this.mRoot)) {if (parent.left == node) {other = parent.right;if (isRed(other)) {// Case 1: x的兄弟w是红色的  setBlack(other);setRed(parent);leftRotate(parent);other = parent.right;}if ((other.left==null || isBlack(other.left)) &&(other.right==null || isBlack(other.right))) {// Case 2: x的兄弟w是黑色,且w的俩个孩子也都是黑色的  setRed(other);node = parent;parent = parentOf(node);} else {if (other.right==null || isBlack(other.right)) {// Case 3: x的兄弟w是黑色的,并且w的左孩子是红色,右孩子为黑色。  setBlack(other.left);setRed(other);rightRotate(other);other = parent.right;}// Case 4: x的兄弟w是黑色的;并且w的右孩子是红色的,左孩子任意颜色。setColor(other, colorOf(parent));setBlack(parent);setBlack(other.right);leftRotate(parent);node = this.mRoot;break;}} else {other = parent.left;if (isRed(other)) {// Case 1: x的兄弟w是红色的  setBlack(other);setRed(parent);rightRotate(parent);other = parent.left;}if ((other.left==null || isBlack(other.left)) &&(other.right==null || isBlack(other.right))) {// Case 2: x的兄弟w是黑色,且w的俩个孩子也都是黑色的  setRed(other);node = parent;parent = parentOf(node);} else {if (other.left==null || isBlack(other.left)) {// Case 3: x的兄弟w是黑色的,并且w的左孩子是红色,右孩子为黑色。  setBlack(other.right);setRed(other);leftRotate(other);other = parent.left;}// Case 4: x的兄弟w是黑色的;并且w的右孩子是红色的,左孩子任意颜色。setColor(other, colorOf(parent));setBlack(parent);setBlack(other.left);rightRotate(parent);node = this.mRoot;break;}}}if (node!=null)setBlack(node);}/* * 删除结点(node),并返回被删除的结点** 参数说明:*     node 删除的结点*/private void remove(RBTNode<T> node) {RBTNode<T> child, parent;boolean color;// 被删除节点的"左右孩子都不为空"的情况。if ( (node.left!=null) && (node.right!=null) ) {// 被删节点的后继节点。(称为"取代节点")// 用它来取代"被删节点"的位置,然后再将"被删节点"去掉。RBTNode<T> replace = node;// 获取后继节点replace = replace.right;while (replace.left != null)replace = replace.left;// "node节点"不是根节点(只有根节点不存在父节点)if (parentOf(node)!=null) {if (parentOf(node).left == node)parentOf(node).left = replace;elseparentOf(node).right = replace;} else {// "node节点"是根节点,更新根节点。this.mRoot = replace;}// child是"取代节点"的右孩子,也是需要"调整的节点"。// "取代节点"肯定不存在左孩子!因为它是一个后继节点。child = replace.right;parent = parentOf(replace);// 保存"取代节点"的颜色color = colorOf(replace);// "被删除节点"是"它的后继节点的父节点"if (parent == node) {parent = replace;} else {// child不为空if (child!=null)setParent(child, parent);parent.left = child;replace.right = node.right;setParent(node.right, replace);}replace.parent = node.parent;replace.color = node.color;replace.left = node.left;node.left.parent = replace;if (color == BLACK)removeFixUp(child, parent);node = null;return ;}if (node.left !=null) {child = node.left;} else {child = node.right;}parent = node.parent;// 保存"取代节点"的颜色color = node.color;if (child!=null)child.parent = parent;// "node节点"不是根节点if (parent!=null) {if (parent.left == node)parent.left = child;elseparent.right = child;} else {this.mRoot = child;}if (color == BLACK)removeFixUp(child, parent);node = null;}/* * 删除结点(z),并返回被删除的结点** 参数说明:*     tree 红黑树的根结点*     z 删除的结点*/public void remove(T key) {RBTNode<T> node; if ((node = search(mRoot, key)) != null)remove(node);}/** 销毁红黑树*/private void destroy(RBTNode<T> tree) {if (tree==null)return ;if (tree.left != null)destroy(tree.left);if (tree.right != null)destroy(tree.right);tree=null;}public void clear() {destroy(mRoot);mRoot = null;}/** 打印"红黑树"** key        -- 节点的键值 * direction  --  0,表示该节点是根节点;*               -1,表示该节点是它的父结点的左孩子;*                1,表示该节点是它的父结点的右孩子。*/private void print(RBTNode<T> tree, T key, int direction) {if(tree != null) {if(direction==0)    // tree是根节点System.out.printf("%2d(B) is root\n", tree.key);else                // tree是分支节点System.out.printf("%2d(%s) is %2d's %6s child\n", tree.key, isRed(tree)?"R":"B", key, direction==1?"right" : "left");print(tree.left, tree.key, -1);print(tree.right,tree.key,  1);}}public void print() {if (mRoot != null)print(mRoot, mRoot.key, 0);}
}

红黑树的测试文件(RBTreeTest.java)

/*** Java 语言: 二叉查找树** @author skywang* @date 2013/11/07*/
public class RBTreeTest {private static final int a[] = {10, 40, 30, 60, 90, 70, 20, 50, 80};private static final boolean mDebugInsert = false;    // "插入"动作的检测开关(false,关闭;true,打开)private static final boolean mDebugDelete = false;    // "删除"动作的检测开关(false,关闭;true,打开)public static void main(String[] args) {int i, ilen = a.length;RBTree<Integer> tree=new RBTree<Integer>();System.out.printf("== 原始数据: ");for(i=0; i<ilen; i++)System.out.printf("%d ", a[i]);System.out.printf("\n");for(i=0; i<ilen; i++) {tree.insert(a[i]);// 设置mDebugInsert=true,测试"添加函数"if (mDebugInsert) {System.out.printf("== 添加节点: %d\n", a[i]);System.out.printf("== 树的详细信息: \n");tree.print();System.out.printf("\n");}}System.out.printf("== 前序遍历: ");tree.preOrder();System.out.printf("\n== 中序遍历: ");tree.inOrder();System.out.printf("\n== 后序遍历: ");tree.postOrder();System.out.printf("\n");System.out.printf("== 最小值: %s\n", tree.minimum());System.out.printf("== 最大值: %s\n", tree.maximum());System.out.printf("== 树的详细信息: \n");tree.print();System.out.printf("\n");// 设置mDebugDelete=true,测试"删除函数"if (mDebugDelete) {for(i=0; i<ilen; i++){tree.remove(a[i]);System.out.printf("== 删除节点: %d\n", a[i]);System.out.printf("== 树的详细信息: \n");tree.print();System.out.printf("\n");}}// 销毁二叉树tree.clear();}
}

红黑树的Java测试程序

前面已经给出了红黑树的测试代码(RBTreeTest.java),这里就不再重复说明。下面是测试程序的运行结果:

== 原始数据: 10 40 30 60 90 70 20 50 80
== 前序遍历: 30 10 20 60 40 50 80 70 90
== 中序遍历: 10 20 30 40 50 60 70 80 90
== 后序遍历: 20 10 50 40 70 90 80 60 30
== 最小值: 10
== 最大值: 90
== 树的详细信息:
30(B) is root
10(B) is 30's   left child
20(R) is 10's  right child
60(R) is 30's  right child
40(B) is 60's   left child
50(R) is 40's  right child
80(B) is 60's  right child
70(R) is 80's   left child
90(R) is 80's  right child

参考说明

在写红黑树这几篇文章的过程中,参考了许多资料:主要有《算法导论》、《数据结构与算法分析-C语言描述》以及网上的一些文章;此外,还参考了”Linux内核中的红黑树源码”、”JDK中的红黑树源码”、”STL中的红黑树源码”。

1. Linux内核中的红黑树源码

  在”红黑树(三)之 Linux内核中红黑树的经典实现“这篇文章中,我已经将Linux内核中的红黑树源码移植出来了,供大家参考。若你想自己亲自研究,可以获取Linux内核源码后再进行查阅。
(01) Linux内核源码下载地址:https://www.kernel.org/
(02) 头文件路径:include/linux/rbtree.h
(03) 实现文件路径:lib/rbtree.c

2. JDK中的红黑树源码

  JDK中的红黑树在实现TreeMap和TreeSet时有用到。在我以前写的”Java 集合系列“的”Java 集合系列12之 TreeMap详细介绍(源码解析)和使用示例“中有介绍过。如果你想一探究竟,参考查看JDK源码。
(01) JDK的源码可以通过解压”JDK包中的src.zip”获取。当然,你也可以通过openjdk来查看JDK源码。
(02) TreeMap的路径:java/util/TreeMap.java

3. STL中的红黑树源码

  这部分我没有仔细研究。若你感兴趣,可以研究之后,再来分享给大家。
(01) ubuntu下STL中红黑树的源码路径:/usr/include/c++/4.6/ext/pb_ds/detail/rb_tree_map_
  说明:上面是我自己使用的ubuntu(12.04版本)下STL源码路径,不同的系统可能略有不同。”/usr/include/c++/4.6/”是g++4.6的路径,而”ext/pb_ds/detail/rb_tree_map_”则是红黑树源码路径。
(02) 我将rb_tree_map_中的代码打包出来,点击下载。

转载自:
红黑树(一)之 原理和算法详细介绍
红黑树(五)之 Java的实现
红黑树(六)之 参考资料

红黑树 原理和算法详细介绍(Java)相关推荐

  1. 红黑树 —— 原理和算法详细介绍

    红黑树 -- 原理和算法详细介绍 R-B Tree简介 R-B Tree,全称是Red-Black Tree,又称为"红黑树",它一种特殊的二叉查找树.红黑树的每个节点上都有存储位 ...

  2. 红黑树原理和算法详细介绍

    目录 1 R-B Tree简介 2 红黑树的时间复杂度和相关证明 3 红黑树的基本操作 1. 左旋 2. 右旋 3. 添加 4.1 删除 1 R-B Tree简介 R-B Tree,全称是Red-Bl ...

  3. 红黑树(一)之 原理和算法详细介绍---转帖

    目录 1 红黑树的介绍 2 红黑树的应用 3 红黑树的时间复杂度和相关证明 4 红黑树的基本操作(一) 左旋和右旋 5 红黑树的基本操作(二) 添加 6 红黑树的基本操作(三) 删除 作者:Sky W ...

  4. 红黑树(一)之 原理和算法详细介绍

    出处:http://www.cnblogs.com/skywang12345/p/3245399.html 概要 目录 1 红黑树的介绍 2 红黑树的应用 3 红黑树的时间复杂度和相关证明 4 红黑树 ...

  5. 红黑树 之 原理和算法详细介绍

    概要 目录 1 红黑树的介绍 2 红黑树的应用 3 红黑树的时间复杂度和相关证明 4 红黑树的基本操作(一) 左旋和右旋 5 红黑树的基本操作(二) 添加 6 红黑树的基本操作(三) 删除 作者:Sk ...

  6. HashMap底层红黑树原理(超详细图解)+手写红黑树代码

    在看完了小刘老师和黑马的源码视频之后,我整理了一篇HashMap的底层源码文章,学海无涯,这几天看了对红黑树的讲解,故将其整理出来 HashMap底层源码解析上 HashMap底层源码解析下 视频链接 ...

  7. 红黑树(一)的原理和算法详细介绍

    红黑树的原理和算法的详细介绍 目录 概要 一.R-B Tree简介 二.R-B Tree应用场景及时间复杂度 三.R-B Tree的基本操作(一) 左旋和右旋 1.左旋 2.右旋 四.R-B Tree ...

  8. 二叉查找树与红黑树原理和程序全面介绍

    转载请注明出处 http://blog.csdn.net/yankai0219/article/details/8273542 学习方法:我主要是参考算法导论以及Nginx中rbtree.h和rbtr ...

  9. 纯干货-17 分布式深度学习原理、算法详细介绍

    介绍 无监督的特征学习和深度学习已经证明,通过海量的数据来训练大型的模型可以大大提高模型的性能.但是,考虑需要训练的深度网络模型有数百万甚至数十亿个参数需要训练,这其实是一个非常复杂的问题.我们可以很 ...

最新文章

  1. ckplayer php,ckplayer 网页视频播放插件
  2. c改java_Android NDK开发:C修改Java的属性
  3. array_unique() 去重复
  4. MongoDB Wiredtiger存储引擎实现原理
  5. Android studio使用git切换分支开发的方法(图文教程)
  6. 多态(Polymoph)
  7. 机器学习实战(十四)Pegasos(原始估计子梯度求解器)
  8. 【六】Jmeter:断言
  9. css3中skew与rotateX(),rotateY()的用法
  10. PyTips 0x14 - Python 描述符
  11. 如何采用类比法和类推法估算软件项目工作量
  12. CodeSmith(1):使用和语法简介
  13. MySQL 5.7详细下载安装配置教程
  14. 5000的台式计算机,预算5000的台式组装机配置清单_预算5000的台式组装机配置
  15. bzoj4200: [Noi2015]小园丁与老司机(可行流+dp)
  16. Gym - 101572K Kayaking Trip 二分
  17. 智能化汽车3D ToF摄像头
  18. 对话行癫:CTO 最重要的是判断未来!| 人物志
  19. 全球值得关注的11家人脸识别公司与机构
  20. 中国能出现一家 Zoom 吗?

热门文章

  1. Stripe国际支付平台接入
  2. 用SAXReader解析xml文档
  3. AI美图相机原型(智能P图、AI换脸)
  4. 怎样记账简单明白,实用记账技巧
  5. overleaf 插入图片_Overleaf手册(三)--图片
  6. OSPF多区域与配置
  7. 图卷积(1)——从欧式空间到非欧式空间
  8. sed 第n行后加入_sed详解
  9. 统计学习方法——知识点(1)经验分布
  10. 关于ResNet50的解读