参考博客:http://www.cnblogs.com/skywang12345/p/3245399.html
红黑树:
一种二叉查找树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。通过对任何一条从根到叶子的简单路径上各个结点的颜色进行约束,红黑树确保没有一条路径会比其他路径长出2倍,因而是近似于平衡的(有性质5保证)。树中每个结点包含5个属性:color、key、left、right和p。如果一个结点没有子节点或父节点,则该结点相应的指针属性值为NIL,我们可以把这些NIL视为指向二叉搜索树的叶节点(外部结点)的指针,而把带关键字的结点视为树的内部结点。
1、一棵红黑树是满足下面性质的二叉搜索树:
1)每个结点或是红色的,或是黑色的;
2)根结点是黑色的;
3)每个叶结点(叶结点即指树尾端NIL指针或NULL结点)是黑的;
4)如果一个结点是红色的,则它的两个子结点都是黑色的;
5)对每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点。
2、红黑树虽然本质上是一棵二叉查找树,但它在二叉查找树的基础上增加了着色和相关的性质使得红黑树相对平衡,从而保证了红黑树的查找、插入、删除的时间复杂度最坏为O(log n)
3、红黑树的高度最多是2log(N+1)
4、红黑树上主要的操作:查找、左旋、右旋、插入、删除
1)查找
查找直接按照二叉查找树节点值的比较即可
2)左旋
对x进行左旋,意味着"将x变成一个左节点"。
左旋的伪代码《算法导论》:参考上面的示意图和下面的伪代码,理解“红黑树T的节点x进行左旋”是如何进行的。
LEFT-ROTATE(T, x)
01
y ← right[x]
//
前提:这里假设
x
的右孩子为
y
。下面开始正式操作
02
right[x] ← left[y]
//
将
“y
的左孩子
”
设为
“x
的右孩子
”
,即
将
β
设为
x
的右孩子
03
p[left[y]] ← x
//
将
“x”
设为
“y
的左孩子的父亲
”
,即
将
β
的父亲设为
x
04
p[y] ← p[x]
//
将
“x
的父亲
”
设为
“y
的父亲
”
05
if
p[x] = nil[T]
06
then root[T] ← y
//
情况
1
:如果
“x
的父亲
”
是空节点,则将
y
设为根节点
07
else
if
x = left[p[x]]
08
then left[p[x]] ← y
//
情况
2
:如果
x
是它父节点的左孩子,则将
y
设为
“x
的父节点的左孩子
”
09
else
right[p[x]] ← y
//
情况
3
:
(x
是它父节点的右孩子
)
将
y
设为
“x
的父节点的右孩子
”
10
left[y] ← x
//
将
“x”
设为
“y
的左孩子
”
11
p[x] ← y
//
将
“x
的父节点
”
设为
3)右旋
对
Y进行右旋,意味着"将Y变成一个右节点"。
右旋的伪代码《算法导论》:参考上面的示意图和下面的伪代码,理解“红黑树T的节点y进行右旋”是如何进行的。
RIGHT-ROTATE(T, y)
01
x ← left[y]
//
前提:这里假设
y
的左孩子为
x
。下面开始正式操作
02
left[y] ← right[x]
//
将
“x
的右孩子
”
设为
“y
的左孩子
”
,即
将
β
设为
y
的左孩子
03
p[right[x]] ← y
//
将
“y”
设为
“x
的右孩子的父亲
”
,即
将
β
的父亲设为
y
04
p[x] ← p[y]
//
将
“y
的父亲
”
设为
“x
的父亲
”
05
if
p[y] = nil[T]
06
then root[T] ← x
//
情况
1
:如果
“y
的父亲
”
是空节点,则将
x
设为根节点
07
else
if
y = right[p[y]]
08
then right[p[y]] ← x
//
情况
2
:如果
y
是它父节点的右孩子,则将
x
设为
“y
的父节点的右孩子
”
09
else
left[p[y]] ← x
//
情况
3
:
(y
是它父节点的左孩子
)
将
x
设为
“y
的父节点的左孩子
”
10
right[x] ← y
//
将
“y”
设为
“x
的右孩子
”
11
p[y] ← x
//
将
“y
的父节点
”
设为
“x”
区分左旋和右旋:
仔细观察上面"左旋"和"右旋"的示意图。我们能清晰的发现,它们是对称的。无论是左旋还是右旋,被旋转的树,在旋转前是二叉查找树,并且旋转之后仍然是一颗二叉查找树。
4)添加节点
第一步: 将红黑树当作一颗二叉查找树,将节点插入
红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然是一颗二叉查找树。也就意味着,树的键值仍然是有序的。此外,无论是左旋还是右旋,若旋转之前这棵树是二叉查找树,旋转之后它一定还是二叉查找树。这也就意味着,任何的旋转和重新着色操作,都不会改变它仍然是一颗二叉查找树的事实。
第二步:将插入的节点着色为"红色"
为什么着色成红色,而不是黑色呢?为什么呢?在回答之前,我们需要重新温习一下红黑树的特性:
(1) 每个节点或者是黑色,或者是红色。
(2) 根节点是黑色。
(3) 每个叶子节点是黑色。 [注意:这里叶子节点,是指为空的叶子节点!]
(4) 如果一个节点是红色的,则它的子节点必须是黑色的。
(5) 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
将插入的节点着色为红色,不会违背"特性(5)"!少违背一条特性,就意味着我们需要处理的情况越少。接下来,就要努力的让这棵树满足其它性质即可;满足了的话,它就又是一颗红黑树了
第三步: 通过一系列的旋转或着色等操作,使之重新成为一颗红黑树。
第二步中,将插入节点着色为"红色"之后,不会违背"特性(5)"。那它到底会违背哪些特性呢?
对于"特性(1)",显然不会违背了。因为我们已经将它涂成红色了。
对于"特性(2)",显然也不会违背。在第一步中,我们是将红黑树当作二叉查找树,然后执行的插入操作。而根据二叉查找数的特点,插入操作不会改变根节点。所以,根节点仍然是黑色。
对于"特性(3)",显然不会违背了。这里的叶子节点是指的空叶子节点,插入非空节点并不会对它们造成影响。
对于"特性(4)",是有可能违背的!
那接下来,想办法使之"满足特性(4)",就可以将树重新构造成红黑树了。
根据被插入节点的父节点的情况,可以将"当节点z被着色为红色节点,并插入二叉树"划分为三种情况来处理。
① 情况说明:被插入的节点是根节点。
处理方法:直接把此节点涂为黑色。
② 情况说明:被插入的节点的父节点是黑色。
处理方法:什么也不需要做。节点被插入后,仍然是红黑树。
③ 情况说明:被插入的节点的父节点是红色。
处理方法:那么,该情况与红黑树的“特性(5)”相冲突。这种情况下,被插入节点是一定存在非空祖父节点的;进一步的讲,被插入节点也一定存在叔叔节点(即使叔叔节点为空,我们也视之为存在,空节点本身就是黑色节点)。理解这点之后,我们依据"叔叔节点的情况",将这种情况进一步划分为3种情况(Case)。
|
现象说明
|
处理策略
|
Case 1
|
当前节点的父节点是红色,且当前节点的祖父节点的另一个子节点(叔叔节点)也是红色。
|
(01) 将“父节点”设为黑色。
(02) 将“叔叔节点”设为黑色。
(03) 将“祖父节点”设为“红色”。
(04) 将“祖父节点”设为“当前节点”(红色节点);即,之后继续对“当前节点”进行操作。
|
Case 2
|
当前节点的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的右孩子
|
(01) 将“父节点”作为“新的当前节点”。
(02) 以“新的当前节点”为支点进行左旋。
|
Case 3
|
当前节点的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的左孩子
|
(01) 将“父节点”设为“黑色”。
(02) 将“祖父节点”设为“红色”。
(03) 以“祖父节点”为支点进行右旋。
|
上面三种情况(Case)处理问题的核心思路都是:将红色的节点移到根节点;然后,将根节点设为黑色。
5)删除节点
第一步:将红黑树当作一颗二叉查找树,将节点删除。
这和"删除常规二叉查找树中删除节点的方法是一样的"。分3种情况:
① 被删除节点没有儿子,即为叶节点
② 被删除节点只有一个儿子。
③ 被删除节点有两个儿子
第二步:通过"旋转和重新着色"等一系列来修正该树,使之重新成为一棵红黑树。
因为"第一步"中删除节点之后,可能会违背红黑树的特性。所以需要通过"旋转和重新着色"来修正该树,使之重新成为一棵红黑树。
下面对删除函数进行分析。在分析之前,我们再次温习一下红黑树的几个特性:
(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不是根。