红黑树之旅 | 120+个图解过程

  • 本篇红黑树文章,近120多幅图构成,让你了解红黑树不再难。

  • 发表是最好的记忆 --候捷

目录:

  1. 红黑树介绍

  2. 旋转分析

  3. 插入分析

  4. 删除分析

  5. 完整实例宏微观图解过程

        5.1 插入宏微观图解过程

        5.2 删除宏微观图解过程

  1. 代码实现分析

  2. 分析过程附件

先看下《算法导论》对RBTree的介绍:

红黑树,一种二叉查找树,但在每个节点上增加一个存储位表示节点的颜色,可以是Red或Black。


通过对任何一条从根到叶子的路径上各个节点着色方式的限制,红黑树确保没有1条路径会比其它路径长出2倍,因而是接近平衡的。

 

下面,在具体介绍红黑树之前,我们先来简单了解下,二叉查找树的一般性质:

(1)在一棵二叉查找树上,执行查找、插入、删除等操作的时间复杂度为O(logN); 因为,一棵由n个节点,随机构造的二叉查找树的高度为logN, 所以顺理成章,一般操作的执行时间O(logN);

         至于n个节点的二叉树高度为logN的证明,可参考《算法导论》

(2)但若是一棵具有n个节点的线性链,则这些操作最坏情况运行时间为O(n);

 

而红黑树,能保证在最坏情况下,基本的动态操作的时间均O(logN);

我们知道,红黑树上每个节点内含有5个域,color, key, left, right, p。 如果相应的指针域没有,则设为NIL。

一般的,红黑树,满足以下性质,即只有满足以下全部性质的树,我们才称之为红黑树:

  1. 每个节点要么是红,要么是黑色;

  2. 根节点是黑色;

  3. 每个叶节点,即空节点(NIL)是黑色;

  4. 如果一个节点是红色,那么它的2个孩子节点必为黑色;

  5. 对于每个节点,从该节点到其子孙的叶子节点的路径上包含相同数目的黑色节点;

下图所示,即是一颗红黑树:

红黑树之旅 | 120+个图解过程_第1张图片

左旋与右旋

为什么要左旋右旋?

因为红黑树插入或删除节点后,树的结构发生了 变化,从而可能破坏红黑树的性质。为了维持插入或删除节点后的树,仍然是一棵红黑树,所以有必要对树的结构做部分调整,从而恢复红黑树的原本性质。而为了恢复红黑树性质而作的动作包括节点颜色的改变(重新着色),和节点的调整。

这部分节点调整工作,改变指针结构,即是通过左旋或右旋而达到目的。

从而使插入或删除节点的树重新成为一棵新的红黑树。

请看下图:

红黑树之旅 | 120+个图解过程_第2张图片

左旋代码实现,分三步:


红黑树之旅 | 120+个图解过程_第3张图片

红黑树之旅 | 120+个图解过程_第4张图片


右旋代码类似,可自行对比类比分析。


《算法导论》的RB-INSERT(T, z):

红黑树之旅 | 120+个图解过程_第5张图片

我们来具体分析下这段伪代码。
RB-INSERT(T, z), 是将z插入红黑树T内。
为保证红黑树性质在插入操作后依然保持,上述代码调用了一个辅助程序: RB-INSERT-FIXUP 来对节点进行 重新着色,并旋转。
16行,将z着为红色,由于将z着为红色可能会违背某一条红黑树的性质,所以,在第 17行,调用 RB-INSERT-FIXUP(T, z)来保持红黑树的性质。

红黑树之旅 | 120+个图解过程_第6张图片
红黑树之旅 | 120+个图解过程_第7张图片

为什么插入红节点,不是黑色呢?
在对红黑树进行插入操作时,我们一般总是插入 红色的节点,因为这样可以在插入过程中尽量避免对树的调整。那么,我们插入一个节点后,可能会使原树的 哪些性质改变?由于,我们是按照二叉搜索树的方式进行插入,因此元素的搜索性质不会改变。如果插入的节点是根节点, 性质2会被破坏,如果插入节点的父节点是红色的,则会破坏 性质4。
因此,总而言之,插入一个 红色节点只会破坏性质2或性质4。
 
我们的恢复策略很简单。
(1)把出现违背红黑树性质的节点向上移,如果能移到根节点,那么很容易就能通过直接修改根节点来恢复红黑树的性质。直接通过修改根节点来恢复红黑树应满足的性质。
(2)穷举所有的可能性,之后把能归于同一类方法处理的归为同一类,不能直接处理的化归到下面的几种情况。
 
情况1:插入的是根节点:
原树是空树,此情况只会违反 性质2。
对策:直接把此节点涂为黑色。
 
情况2:插入的节点的父节点是黑色。
此不会违反 性质2性质4,红黑树没有被破坏。
对策:什么也不做。
 
情况3:当前节点的父节点是红色且祖父节点的另一个子节点(叔叔节点)是红色。
分析:此时父节点的父节点一定存在,否则插入前就已不是红黑树。
与此同时,又分为父节点是祖父节点的左子还是右子,对于对称性,我们只要解开一个方向就可以了。
在此, 我们只考虑父节点为祖父左子的情况。
同时,还可以分为当前节点是其父节点的左子还是右子,但是处理方式是一样的。我们将此归为同一类。
 
对策:将当前节点的父节点和叔叔节点涂黑,祖父节点涂红,把当前节点指向祖父节点,从新的当前节点重新开始算法。
 
针对情况3,变化前【插入4节点】:
红黑树之旅 | 120+个图解过程_第8张图片
变化后:
红黑树之旅 | 120+个图解过程_第9张图片
 
情况4:当前节点的父节点是红色,叔叔节点是黑色,当前节点是其父节点的右孩子。
对策:当前节点的父节点作为新的当前节点,以新当前节点为支点左旋。
如下图所示,变化前【插入7节点】:
红黑树之旅 | 120+个图解过程_第10张图片
变化后:
红黑树之旅 | 120+个图解过程_第11张图片
 
情况5:当前节点的父节点是红色,叔叔节点是黑色,当前节点是其父节点的左孩子。
对策:父节点变为黑色,祖父节点变为红色,在祖父节点为支点右旋,
如下图所示,变化前【插入2节点】
红黑树之旅 | 120+个图解过程_第12张图片
变化后:
红黑树之旅 | 120+个图解过程_第13张图片

接下来,我们来了解,红黑树的【删除】操作。

《算法导论》一书,给的算法实现:


红黑树之旅 | 120+个图解过程_第14张图片


红黑树之旅 | 120+个图解过程_第15张图片


红黑树删除的几种情况:

以下所有的操作,是针对红黑树已经删除节点之后,为了恢复和保持红黑树原有的5点性质,所做的恢复工作。

 

前面,我已经说了,因为插入,或删除节点后,可能会违背,或破坏红黑树的原有的性质,

所以为了使插入,或删除节点后的树依然维持为一棵性的红黑树,那就要做2方面的工作:

(1)部分节点颜色,重新着色;

(2)调整部分指针的指向,即左旋,右旋;

而下面所有的文字,则是针对红黑树删除节点后,所做的修复红黑树性质的工作:

 

情况1:当前节点是红色。

对策:直接把当前节点染成黑色,结束。

此时红黑树性质全部恢复。

 

情况2:当前节点是黑色且是根节点

对策:什么都不做,结束;

 

情况3:当前节点是黑色,且兄弟节点为红色(此时父节点和兄弟节点的子节点分别为黑)。

对策:把父节点染成红色,把兄弟节点染成黑色,之后重新进入算法(我们只讨论当前节点是其父节点左子的情况)

然后,针对父节点做一次左旋。此变换后原红黑树性质5不变,而把问题转化成兄弟节点为黑的情况。

变化前【删除2节点】:

红黑树之旅 | 120+个图解过程_第16张图片

变化后:

红黑树之旅 | 120+个图解过程_第17张图片

 

情况4:当前节点是黑色,且兄弟节点是黑色,且兄弟节点的两个子节点全为黑色。

对策:把当前节点和兄弟节点中抽取一重黑色追加到父节点上,把父节点当成新的当前节点,重新进入算法

变化前【删除2节点】:

红黑树之旅 | 120+个图解过程_第18张图片

变化后:

红黑树之旅 | 120+个图解过程_第19张图片

 

情况5:当前节点颜色是黑色,兄弟节点是黑色,兄弟的左子是红色,右子是黑色。

对策:把兄弟节点节点涂红,兄弟左子涂黑,之后再在兄弟节点为支点右旋,之后重新进入算法。此时把当前的情况转化为情况6,而性质5得以保持:

变化前【删除2节点】:

红黑树之旅 | 120+个图解过程_第20张图片

变化后:

红黑树之旅 | 120+个图解过程_第21张图片

 

情况6:当前节点颜色是黑色,它的兄弟节点的右子是红色,兄弟节点左子的颜色任意。

对策:把兄弟节点涂成当前节点父节点的颜色,把当前父节点涂成黑色,兄弟节点右子涂成黑色,之后以当前节点的父节点为支点进行左旋,此时算法结束,红黑树所有性质调整正确。

变化前【删除2节点】:

红黑树之旅 | 120+个图解过程_第22张图片

变化后:

红黑树之旅 | 120+个图解过程_第23张图片

红黑树之旅 | 120+个图解过程_第24张图片

首先,各个结点插入与以上的各种插入情况,一一对应起来,如图:

       以下的20个插入元素过程的图例,是依次插入这些结点:12 1 9 2 0 11 7 19 4 15 18 5 14 13 10 16 6 3 8 17的全程演示图,已经把所有的5种插入情况,都全部涉及到了:

 

    1.插12

  • (1) 情形1:

红黑树之旅 | 120+个图解过程_第25张图片

2.插入1
  • (1) 情形2:

红黑树之旅 | 120+个图解过程_第26张图片

 

3. 插9

  • (1)情形4(Case2):

  • (2)情形5(Case3):

红黑树之旅 | 120+个图解过程_第27张图片

红黑树之旅 | 120+个图解过程_第28张图片

 

4.插2

  • (1)情况3(Case1):

红黑树之旅 | 120+个图解过程_第29张图片

红黑树之旅 | 120+个图解过程_第30张图片

 

红黑树之旅 | 120+个图解过程_第31张图片

 

5.插0

  • (1)情形2:

红黑树之旅 | 120+个图解过程_第32张图片

 

6.插入11

  • (1) 情形2:

红黑树之旅 | 120+个图解过程_第33张图片

 

7.插入7

  • (1)情形3(Case1):

红黑树之旅 | 120+个图解过程_第34张图片

红黑树之旅 | 120+个图解过程_第35张图片

 

8.插入19

  • (1)情形2:

红黑树之旅 | 120+个图解过程_第36张图片

 

9.插入4

  • (1)情形4(Case2):

红黑树之旅 | 120+个图解过程_第37张图片

  • (2)  情形5 (Case3):

红黑树之旅 | 120+个图解过程_第38张图片

红黑树之旅 | 120+个图解过程_第39张图片

 

10.插入15

  • (1)情形3(Case1):

红黑树之旅 | 120+个图解过程_第40张图片

红黑树之旅 | 120+个图解过程_第41张图片

11.插入18
  • (1) 情形4(Case2):
  • (2) 情形5 (Case3):
红黑树之旅 | 120+个图解过程_第42张图片
红黑树之旅 | 120+个图解过程_第43张图片
12.插入5
  • (1)情形3(Case1):
红黑树之旅 | 120+个图解过程_第44张图片
  • (2) 情形3 (Case2) : (这里就显示了为什么在插入时使用的while循环条件判断,为了就是能回溯继续执行)
红黑树之旅 | 120+个图解过程_第45张图片
红黑树之旅 | 120+个图解过程_第46张图片
 
13.插入14
  • (1)情形3(Case1):
红黑树之旅 | 120+个图解过程_第47张图片
红黑树之旅 | 120+个图解过程_第48张图片
 
 
14.插入13
  • (1) 情形5(Case3):
红黑树之旅 | 120+个图解过程_第49张图片
红黑树之旅 | 120+个图解过程_第50张图片
 
15.插入10
  • 情形2:
红黑树之旅 | 120+个图解过程_第51张图片
 
 
16.插入16
  • (1)情形3:
红黑树之旅 | 120+个图解过程_第52张图片
  • (2) 情形4(Case2):
红黑树之旅 | 120+个图解过程_第53张图片
  • (3) 情形5(Case3):
红黑树之旅 | 120+个图解过程_第54张图片
红黑树之旅 | 120+个图解过程_第55张图片
 
17.插入6
  • (1)情形4(Case2):
红黑树之旅 | 120+个图解过程_第56张图片
  • (2) 情形5(Case3):
红黑树之旅 | 120+个图解过程_第57张图片
红黑树之旅 | 120+个图解过程_第58张图片
 
18.插入3
  • (1)情形2:
 
19.插入8
  • (1)情形3(Case1):
红黑树之旅 | 120+个图解过程_第59张图片
  • (2) 情形5(Case3):
红黑树之旅 | 120+个图解过程_第60张图片
红黑树之旅 | 120+个图解过程_第61张图片
 
20.插入17
  • (1)情形4(Case2):
 
 

红黑树之旅 | 120+个图解过程_第62张图片

红黑树之旅 | 120+个图解过程_第63张图片

 

红黑树的一一插入各结点:12 1 9 2 0 11 7 19 4 15 18 5 14 13 10 16 6 3 8 17的全程演示图完。

-----插入完毕-----


红黑树之旅 | 120+个图解过程_第64张图片
红黑树之旅 | 120+个图解过程_第65张图片
接下来,便是一一删除这些点12 1 9 2 0 11 7 19 4 15 18 5 14 13 10 16 6 3 8 17为例。
首先,插入12 1 9 2 0 11 7 19 4 15 18 5 14 13 10 16 6 3 8 17结点后,形成的红黑树为:
红黑树之旅 | 120+个图解过程_第66张图片
然后,下面图例是一一删除这些结点12 1 9 2 0 11 7 19 4 15 18 5 14 13 10 16 6 3 8 17所得到的删除情况的全程演示图:
通过删除 12   1   9   2   0   11   7   19   4   15   18   5   14   13   10   16   6   3   8   17 完成上述所有情形的展示。
 
 
1.删除12   
红黑树之旅 | 120+个图解过程_第67张图片
12有2个儿子-->情况  ③,找到后继13,将12和13数值对换,此时12位黑色且没有孩子-->情况 ①,以12为当前节点进行旋转调色:
红黑树之旅 | 120+个图解过程_第68张图片
此时12为黑色,其兄弟11为黑色,且兄弟的右孩子为红色,满足情况E(Case4):将11设为13的颜色,将13和10设为黑色,并对13进行右旋,最后删除12:
红黑树之旅 | 120+个图解过程_第69张图片 
2.删除1
红黑树之旅 | 120+个图解过程_第70张图片
1有两个儿子,根据情况 ③找到1的后继是2,将1和2数值对换,删除当前的1,而1又是黑色,对3进行删除调整:
红黑树之旅 | 120+个图解过程_第71张图片
由于3是红色,直接进行变黑,调整完成:
 
3.删除9
红黑树之旅 | 120+个图解过程_第72张图片
9有2个儿子,根据情况 ③找到其后继10,将9和10数字对换,此时9为黑色且没有孩子,根据情况 ①,对9进行旋转调色:
红黑树之旅 | 120+个图解过程_第73张图片
此时9没有孩子,且9为黑色,其兄弟13位黑色,13的左右孩子都为黑色,满足情况C: 将13设为红色,将11设为当前节点进行旋转调色,此时情况A, 直接将11变为黑色,最后删掉9:
红黑树之旅 | 120+个图解过程_第74张图片
 
4.删除2
2有2个儿子,根据情况 ③找到其后继为3,将2和3数组对换,此时2为黑色且没有孩子,以2为当前节点进行旋转调色:
红黑树之旅 | 120+个图解过程_第75张图片
此时2位黑色,其兄弟0为黑色,0的左右孩子都为黑色,满足情况C, 将0设为红色,将3设为当前节点进行旋转调色,此时3为红色,满足情况A, 直接将3设为黑色,最后删除2:
红黑树之旅 | 120+个图解过程_第76张图片
 
5.删除0
红黑树之旅 | 120+个图解过程_第77张图片
0没有孩子,根据情况 ①,并且是红色,可直接删除:
红黑树之旅 | 120+个图解过程_第78张图片
 
6.删除11
红黑树之旅 | 120+个图解过程_第79张图片
11只有1个右子13, 根据情况 ②交换11和13的数值,此时11为红色,且没有孩子,直接将其删除即可:
 
7.删除7
红黑树之旅 | 120+个图解过程_第80张图片
7只有1个右子8,根据情况 ②,删除7,并以8进行旋转调色:
此时8为红色,且没有孩子,直接涂黑即可:
红黑树之旅 | 120+个图解过程_第81张图片
 
8.删除19
红黑树之旅 | 120+个图解过程_第82张图片
19没有孩子,并且为黑色-->情况 ①,则以19当前节点进行旋转调色,由于19位黑色,其兄弟16位黑色,且兄弟16的左孩子为红色,按情况E:将16设为18的颜色,18和15设为黑色,对18进行右旋,最后删除19:
红黑树之旅 | 120+个图解过程_第83张图片
 
9.删除4
红黑树之旅 | 120+个图解过程_第84张图片
4有2个儿子,根据情况 ③找到其后继5,交换4和5的数值,根据情况 ①,以4为当前对象进行旋转调色:
红黑树之旅 | 120+个图解过程_第85张图片
此时4没有孩子,且为黑色-->情况 ①其兄弟8为黑色,且8的2个儿子都是黑色,满足情况C, 将8设为红色,并以6为当前节点进行旋转调色:
红黑树之旅 | 120+个图解过程_第86张图片
此时6位红色,根据情况A, 直接将6变为黑色,最后删除4:
红黑树之旅 | 120+个图解过程_第87张图片
 
10.删除15
红黑树之旅 | 120+个图解过程_第88张图片
15没有孩子,且为黑色-->情况 ①,则以15为当前节点进行旋转调色,15的兄弟18位黑色,且15的右子为黑色,满足情况D,交换18和17的颜色,并对18进行右旋:
红黑树之旅 | 120+个图解过程_第89张图片
此时15的新兄弟节点17为黑色,且17的右孩子为红色,满足情况E,将17设为16的颜色,将16和18都设为黑色,并对16进行左旋,最后删掉15:
红黑树之旅 | 120+个图解过程_第90张图片 
11.删除18
红黑树之旅 | 120+个图解过程_第91张图片
18没有孩子,且为黑色,根据情况 ①,以18位当前节点进行旋转调色。由于18的兄弟16是黑色,16的孩子都是黑色,满足情况C, 将16设为红色,将17设为当前节点进行旋转调色,17为红色,满足情况A:直接将17设为黑色,并删除18:
红黑树之旅 | 120+个图解过程_第92张图片
 
12.删除5
红黑树之旅 | 120+个图解过程_第93张图片
5有两个孩子,根据情况3找到其后继为6,交换5和6的数值。此时,删除5,并对8进行旋转调色,此时,8为红色,且没有孩子,则直接将8涂黑即可:
红黑树之旅 | 120+个图解过程_第94张图片
 
13.删除14
红黑树之旅 | 120+个图解过程_第95张图片
14有2个儿子,根据情况 ③找到其后继16,交换14和16的数值,此时14是红色,且没有孩子,根据情况 ①,直接删除即可:
红黑树之旅 | 120+个图解过程_第96张图片
 
14.删除13
红黑树之旅 | 120+个图解过程_第97张图片
13没有孩子,且为黑色,根据情况 ①,以13做为当前节点进行旋转调色。13的兄弟17为黑色,且17的2个儿子都是黑色,满足情况C, 将17设为红色,将16设为当前节点进行旋转调色:
红黑树之旅 | 120+个图解过程_第98张图片
16为黑色,其兄弟6为黑色,且6的两个孩子都是黑色,满足情况C, 将16设为红色,并以10为当前节点进行旋转调色,10为根,且为黑色,按情况A结束,最后删除13即可:
红黑树之旅 | 120+个图解过程_第99张图片
 
15.删除10
红黑树之旅 | 120+个图解过程_第100张图片
10有2个儿子,根据情况 ③找到其后继为16,交换10和16的值,
删除16,此时对17进行旋转调色,由于17位红色,则根据情况A将其直接删除即可:
红黑树之旅 | 120+个图解过程_第101张图片
 
16.删除16
红黑树之旅 | 120+个图解过程_第102张图片
16有2个儿子,根据情况 ③找到其后继17,交换16和17的值,此时16位黑色,其兄弟6为红色,根据情况B, 将6设置为黑色,17设为红色,对17进行右旋:
红黑树之旅 | 120+个图解过程_第103张图片
此时16位黑色,其兄弟8为黑色,且8的两个孩子都为黑色,满足情况C, 将8设为红色,将17作为当前节点:
红黑树之旅 | 120+个图解过程_第104张图片
17为红色,根据情况A, 直接将其设为黑色,最后删除16:
红黑树之旅 | 120+个图解过程_第105张图片
 
17.删除6
红黑树之旅 | 120+个图解过程_第106张图片
6有2个儿子,根据情况 ③找到其后继8,交换6和8的值,此时6位红色,根据情况1将其直接删除:
红黑树之旅 | 120+个图解过程_第107张图片
 
18.删除3
红黑树之旅 | 120+个图解过程_第108张图片
3为黑色,无儿子,根据情况 ①,对3进行旋转调色,其兄弟17为黑色,且17的两个孩子都为黑色,满足情况C,将17设为红色,将8作为当前节点进行分析。由于8是树根则结束,最后删除3:
红黑树之旅 | 120+个图解过程_第109张图片
 
19.删除8
红黑树之旅 | 120+个图解过程_第110张图片
8只有1个右子17, 根据情况 ②, 删除8,此时对17进行旋转调色:
红黑树之旅 | 120+个图解过程_第111张图片
 
20.删除17
红黑树之旅 | 120+个图解过程_第112张图片
17是根,可直接删除
 
-----删除完毕-----

(参考 JDK TreeMap 的实现,注释清晰明朗)
package zhuguozhu.algods;
import java.util.*;
/**
* 红黑树
*
* @Author guozhu_zhu
* @Date 2020/3/30 14:47
*/
public class TreeMap001, V> {

    private transient Entry root;

    private transient int size = 0;

    public TreeMap001() {
    }

    public int size() {
        return size;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    public boolean containsKey(K key) {
        return getEntry(key) != null;
    }

    public boolean containsValue(Object value) {
        for (Entry e = getFirstEntry(); e != null; e = successor(e)) {
            if (valEquals(value, e.value)) {
                return true;
            }
        }
        return false;
    }

    public V get(K key) {
        Entry p = getEntry(key);
        return (p == null ? null : p.value);
    }

    public K firstKey() {
        return key(getFirstEntry());
    }

    public K lastKey() {
        return key(getLastEntry());
    }

    final Entry getEntry(K key) {
        if (key == null) {
            throw new NullPointerException();
        }
        Entry p = root;
        while (p != null) {
            int cmp = key.compareTo(p.key);
            if (cmp > 0) {
                p = p.right;
            } else if (cmp < 0) {
                p = p.left;
            } else {
                return p;
            }
        }
        return null;
    }

    public V put(K key, V value) {
        Entry t = root;
        if (t == null) {
            root = new Entry<>(key, value, null);
            size++;
            return null;
        }
        int cmp;
        Entry parent;
        if (key == null) {
            throw new NullPointerException();
        }
        do {
            parent = t;
            cmp = key.compareTo(t.key);
            if (cmp < 0) {
                t = t.left;
            } else if (cmp > 0) {
                t = t.right;
            } else {
                return t.setValue(value);
            }
        } while (t != null);
        Entry e = new Entry(key, value, parent);
        if (cmp < 0) {
            parent.left = e;
        } else {
            parent.right = e;
        }
        fixAfterInsertion(e);
        size++;
        return null;
    }

    public V remove(K key) {
        Entry p = getEntry(key);
        if (p == null) {
            return null;
        }
        V oldValue = p.value;
        delteEntry(p);
        return oldValue;
    }

    public V replace(K key, V value) {
        Entry p = getEntry(key);
        if (p != null) {
            V oldValue = p.value;
            p.value = value;
            return oldValue;
        }
        return null;
    }

    static final boolean valEquals(Object o1, Object o2) {
        return (o1 == null ? o2 == null : o1.equals(o2));
    }

    static  K keyOrNull(Entry e) {
        return (e == null) ? null : e.key;
    }

    static  K key(Entry e) {
        if (e == null) {
            throw new NoSuchElementException();
        }
        return e.key;
    }

    // RB-TREE
    private static final boolean RED = false;
    private static final boolean BLACK = true;
    static final class Entry {
        K key;
        V value;
        Entry left;
        Entry right;
        Entry parent;
        boolean color = BLACK;
        Entry(K key, V value, Entry parent) {
            this.key = key;
            this.value = value;
            this.parent = parent;
        }

        public K getKey() {
            return key;
        }

        public V getValue() {
            return value;
        }

        public V setValue(V value) {
            V oldValue = this.value;
            this.value = value;
            return oldValue;
        }

        @Override
        public boolean equals(Object o) {
            if (!(o instanceof Entry)) {
                return false;
            }
            Entry e = (Entry)o;
            return valEquals(key, e.getKey()) && valEquals(value, e.getValue());
        }

        @Override
        public int hashCode() {
            int keyHash = (key == null) ? 0 : key.hashCode();
            int valueHash = (value == null)? 0 : value.hashCode();
            return keyHash ^ valueHash;
        }

        @Override
        public String toString() {
            return key + "=" + value;
        }
    }

    final Entry getFirstEntry() {
        Entry p = root;
        if (p != null) {
            while (p.left != null) {
                p = p.left;
            }
        }
        return p;
    }

    final Entry getLastEntry() {
        Entry p = root;
        if (p != null) {
            while (p.right != null) {
                p = p.right;
            }
        }
        return p;
    }

    // 返回被删除节点继承者节点
    static  Entry successor(Entry t) {
        if (t == null) {
            // 如果被删除节点为空,则直接返回null
            return null;
        } else if (t.right != null) {
            // 如果被删除节点的右子节点不为空
            // 将被删除节点的右子节点记录下来
            // 从该节点开始循环向下查找最左子节点,直到查找到叶子节点后返回叶子节点
            Entry p = t.right;
            while (p.left != null) {
                p = p.left;
            }
            return p;
        } else {
            // 如果被删除节点的右子节点为空
            // 向上回溯搜索
            // 将被删除节点用p变量记录
            Entry p = t.parent;
            Entry ch = t;
            while (p != null && ch == p.right) {
                ch = p;
                p = p.parent;
            }
            return p;
        }
    }

    static  Entry predecessor(Entry t) {
        if (t == null) {
            return null;
        } else if (t.left != null) {
            Entry p = t.left;
            while (p.right != null) {
                p = p.right;
            }
            return p;
        } else {
            Entry p = t.parent;
            Entry ch = t;
            while (p != null && ch == p.left) {
                ch = p;
                p = p.parent;
            }
            return p;
        }
    }

    private static  boolean colorOf(Entry p) {
        return p == null ? BLACK : p.color;
    }

    private static  Entry parentOf(Entry p) {
        return p == null ? null : p.parent;
    }

    private static  Entry leftOf(Entry p) {
        return p == null ? null : p.left;
    }

    private static  Entry rightOf(Entry p) {
        return p == null ? null : p.right;
    }

    private static  void setColor(Entry p, boolean c) {
        if (p != null) {
            p.color = c;
        }
    }

    // 左旋
    private void rotateLeft(Entry p) {
        if (p != null) {
            Entry r = p.right;
            p.right = r.left;
            if (r.left != null) {
                r.left.parent = p;
            }
            r.parent = p.parent;


            if (p.parent == null) {
                root = r;
            } else if (p.parent.left == p) {
                p.parent.left = r;
            } else if (p.parent.right == p) {
                p.parent.right = r;
            }
            r.left = p;
            p.parent = r;
        }
    }

    // 右旋
    private void rotateRight(Entry p) {
        if (p != null) {
            Entry l = p.left;


            p.left = l.right;
            if (l.right != null) {
                l.right.parent = p;
            }


            l.parent = p.parent;
            if (p.parent == null) {
                p = l;
            } else if (p.parent.left == p) {
                p.parent.left = l;
            } else if (p.parent.right == p) {
                p.parent.right = l;
            }
            l.right = p;
            p.parent = l;
        }
    }

    // 树插入一个新节点后,将其根据红黑树的规则进行修正
    private void fixAfterInsertion(Entry x) {
        // 默认将当前插入树的节点颜色设置为红色, 为什么???
        // 因为红黑树有一个特点:"从根节点到所有叶子节点上的黑色节点个数是相同的
        // 如果当前插入的节点是黑色,那么必然会违反这个特性,所以必须将插入节点的颜色先设置为红色
        x.color = RED;
        // 第一次变量时,X变量保存的是当前新插入的节点
        // 为什么要用while循环
        // 因为在旋转过程中有可能还会出现父子节点均为红色的情况,所以要不断上变量直到整个树都符合红黑树的规则
        while (x != null && x != root && x.parent.color == RED) {
            // 如果当前节点不为空切不是根节点,并且当前节点的父节点颜色为红色
            if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
                // 如果当前节点的父节点等于当前节点父节点的父节点的左子节点(即当前节点为左子树插入)
                Entry y = rightOf(parentOf(parentOf(x)));
                // 获取当前节点的叔父节点
                if (colorOf(y) == RED) {
                    // 如果叔父节点的颜色为红色
                    // 以4步用来保证不会连续出现两个红色节点
                    // 将当前节点的父节点设置为黑色
                    setColor(parentOf(x), BLACK);
                    // 将当前节点的叔父节点设置为黑色
                    setColor(y, BLACK);
                    // 将当前节点的祖父节点设置为红色
                    setColor(parentOf(parentOf(x)), RED);
                    // 当前遍历节点变更为当前节点的祖父节点
                    x = parentOf(parentOf(x));
                } else {
                    // 如果叔父节点的颜色为黑色,或没有叔父节点
                    // 如果当前节点为左子树内侧插入
                    if (x == rightOf(parentOf(x))) {
                        // 将x变更为当前节点的父节点
                        x = parentOf(x);
                        // 对当前节点的父节点进行一次左旋操作(旋转完毕后x对应的就是最左边的叶子节点)
                        rotateLeft(x);
                    }
                    // 如果当前节点为左子树外侧插入
                    // 将当前节点的父节点设置为黑色
                    setColor(parentOf(x), BLACK);
                    // 将当前节点的祖父节点设置为红色
                    setColor(parentOf(parentOf(x)), RED);
                    // 对当前节点的祖父节点进行一次右旋
                    rotateRight(parentOf(parentOf(x)));
                }
            } else {
                Entry y = leftOf(parentOf(parentOf(x)));
                if (colorOf(y) == RED) {
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                    if (x == leftOf(parentOf(x))) {
                        x = parentOf(x);
                        rotateRight(x);
                    }
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    rotateLeft(parentOf(parentOf(x)));
                }
            }
        }
        // 注意在选择的过程中可能会将根节点变更为红色的,但是红黑树特性要求根节点必须为黑色,所以无论如何最好总要执行这行代码,将根节点设置为黑色
        root.color = BLACK;
    }

    private void delteEntry(Entry p) {
        // 节点总数-1
        size--;
        // 1. 这里的情况是两个节点不为空,转化为存在1个子节点为空的情况,便于处理
        if (p.left != null && p.right != null) {
            // 当前要删除节点的左右节点都不为空,找后继节点
            // 采用前驱替代也是可以,predecessor(p);
            // https://www.cs.usfca.edu/~galles/visualization/RedBlack.html
            // 这个网站的删除操作就是通过前驱替代的,而TreeMap中采用的是后继替代
            Entry s = successor(p);
            // 找到一个待删除节点的继承者节点s
            p.key = s.key;
            p.value = s.value;
            p = s;
        }

        // 2. 替代节点选择为当前删除节点的左子节点或右子节点
        Entry replacement = (p.left != null ? p.left : p.right);
        // 2.1 替代节点(被删除节点的子节点)不为空
        if (replacement != null) {
            replacement.parent = p.parent;
            // 如果被删除节点的父节点为null
            if (p.parent == null) {
                // 将根节点设置为替换节点
                root = replacement;
            } else if (p == p.parent.left) {
                // 若原先被删除节点是父的左子
                p.parent.left = replacement;
            } else {
                p.parent.right = replacement;
            }
            // 将被删除节点的左子节点、右子节点,父节点引用都设置为null
            p.left = p.right = p.parent = null;
            // 删除后要执行后续的保证红黑树规则的平衡调整
            // 如果被删除节点是黑色
            if (p.color == BLACK) {
                // 调用删除后修正红黑树规则的方法
                fixAfterDeletion(replacement);
            }
        } else if (p.parent == null) {
            // 2.2 被删除节点就是树根节点,直接删除即可
            root = null;
        } else {
            // 2.3 被删除节点没有节点可替代的情况(即被删除节点是叶子节点)
            if (p.color == BLACK) {
                fixAfterDeletion(p);
            }
            // 如果被删除节点的父节点不为null
            if (p.parent != null) {
                // 如果原先被删除节点是左子树插入
                if (p == p.parent.left) {
                    // 删除节点后将删除节点父节点的左子节点设置为null;
                    p.parent.left = null;
                } else if (p == p.parent.right) {
                    p.parent.right = null;
                }
                // 将被删除节点的父节点引用设置为null;
                p.parent = null;
            }
        }
    }

    private void fixAfterDeletion(Entry x) {
        // 循环遍历, X刚开始是为被删除节点
        while (x != root && colorOf(x) == BLACK) {
            // 如果当前遍历到的节点不是根节点且为黑色
            // 如果当前遍历的节点是父节点的左子节点
            if (x == leftOf(parentOf(x))) {
                // 将当前遍历到的节点的父节点的右节点用sib(兄弟节点)保存
                Entry sib = rightOf(parentOf(x));
                if (colorOf(sib) == RED) {
                    // 如果sib引用的节点是红色
                    // 将sib引用的节点设置为黑色
                    setColor(sib, BLACK);
                    // 将当前遍历到的节点的父节点设置为红色
                    setColor(parentOf(x), RED);
                    // 对当前遍历到的父节点进行一次左旋
                    rotateLeft(parentOf(x));
                    // sib节点变更为旋转后节点的父节点的右子节点
                    sib = rightOf(parentOf(x));
                }


                if (colorOf(leftOf(sib)) == BLACK && colorOf(rightOf(sib)) == BLACK) {
                    // 如果sib引用的左右节点都是黑色
                    // 将sib引用的节点设置为红色
                    setColor(sib, RED);
                    // 下次遍历的节点变更为当前遍历到节点的父节点
                    x = parentOf(x);
                } else {
                    // 如果sib引用节点的左右节点不全是黑色
                    if (colorOf(rightOf(sib)) == BLACK) {
                        // 如果sib引用节点的右节点为黑色
                        // 将sib引用的左子节点设置为黑色
                        setColor(leftOf(sib), BLACK);
                        // sib引用节点设置为红色
                        setColor(sib, RED);
                        // 对sib节点进行一次右旋操作
                        rotateRight(sib);
                        // sib引用的节点变更为当前遍历到的节点的父节点的右子节点
                        sib = rightOf(parentOf(x));
                    }
                    // 将sib引用节点的颜色设置为当前遍历到节点父节点的颜色
                    setColor(sib, colorOf(parentOf(x)));
                    // 将当前遍历到节点的父节点设置为黑色
                    setColor(parentOf(x), BLACK);
                    // 将sib引用节点的右子节点设置为黑色
                    setColor(rightOf(sib), BLACK);
                    // 对当前遍历到的节点的父节点进行一次左旋
                    rotateLeft(parentOf(x));
                    // 下一次遍历的节点变更为根节点
                    x = root;
                }
            } else {
                // 当前遍历到的节点是其父节点的右子节点
                // 与上述代码类似,可对比分析
                Entry sib = leftOf(parentOf(x));
                if (colorOf(sib) == RED) {
                    setColor(sib, BLACK);
                    setColor(parentOf(x), RED);
                    rotateRight(parentOf(x));
                    sib = leftOf(parentOf(x));
                }

                if (colorOf(rightOf(sib)) == BLACK && colorOf(leftOf(sib)) == BLACK) {
                    setColor(sib, RED);
                    x = parentOf(x);
                } else {
                    if (colorOf(leftOf(sib)) == BLACK) {
                        setColor(rightOf(sib), BLACK);
                        setColor(sib, RED);
                        rotateLeft(sib);
                        sib = leftOf(parentOf(x));
                    }
                    setColor(sib, colorOf(parentOf(x)));
                    setColor(parentOf(x), BLACK);
                    setColor(leftOf(sib), BLACK);
                    rotateRight(parentOf(x));
                    x = root;
                }
            }
        }
        setColor(x, BLACK);
    }

    // 层次遍历
    public ArrayList> levelOrder() {
        ArrayList> resList = new ArrayList>();
        ArrayList list = new ArrayList();
        Queue queue = new LinkedList();
        queue.offer(root);
        int cur = 1;
        int next = 0;
        while (!queue.isEmpty()) {
            Entry curNode = queue.poll();
            list.add(curNode.value);
            cur--;
            if (curNode.left != null) {
                queue.offer(curNode.left);
                next++;
            }
            if (curNode.right != null) {
                queue.offer(curNode.right);
                next++;
            }
            if (cur == 0) {
                cur = next;
                next = 0;
                resList.add(list);
                list = new ArrayList();
            }
        }
        return resList;
    }

    public static void main(String[] args) {
        TreeMap001 tree = new TreeMap001();
        for (int i = 0; i < 100; i++) {
            tree.put(i, i);
        }
        tree.remove(16);
        System.out.println(tree.containsValue(15));
        System.out.println(tree.containsValue(16));
        ArrayList> resList = tree.levelOrder();
        for (ArrayList i : resList) {
            System.out.println(i);
        }
    }

}

 

1.插入修正:


2.删除修正:

 

       我们只要牢牢抓住红黑树的5个性质不放,而不论是树的左旋还是右旋,不论是红黑树的插入,还是删除,都只为了保持和修复红黑树的5个性质而已。


参考文献

[1] 算法导论(原书第3版)/(美)科尔曼(Cormen, T.H.)等著;殷建平等译.北京:机械工业出版社,2013.1.

 

 

 

 

你可能感兴趣的:(数据结构,算法,算法,数据结构)