红黑树是一棵二叉搜索树,它在每个结点上增加了一个存储位来表示结点的颜色。通过对任何一条从根到叶子的简单路径上各个结点的颜色进行约束,红黑树确保没有一条路径会比其他路径长出2倍,因而是近于平衡的。
树中的每个结点包含5个属性:color,key,left,right,p。(p:父结点)
一颗红黑树的示意图如下:(NIL结点可以用一个结点代替)
详细的证明细节见《算法道路》,直观分析如下:
从根节点到叶子结点中的黑结点数目相等(定义4),又因为红结点下面一定是黑结点(定义3)。因此一条根节点到叶子结点的简单路径上的红色结点一定不会超过黑色结点。因此能直观的得到结论:每棵子树的根节点到叶子结点的最长路径不会超过最短路径的2倍。(即一个叶子结点要想足够深,他需要填满他深度一半的满二叉树)因此,对于结点n的内部红黑树的最高高度一定是log(N)级别的。
因此,如果用红黑树来代替AVL树,在查找的复杂度上是合适的,如果增加和删除结点不比AVL树复杂,即达到log(n)级别,那就好了。下面进行分析。
左右旋的示意图如下,在红黑树中也会用到,具体分析和代码在之前一篇博客中有学习笔记_二叉搜索树与平衡二叉树。
需要注意一点,旋转后不改变二叉搜索树的性质(即左子树最大值<=根节点<=右子树最小值)。
为了使插入操作的复杂度为log(n),在二叉搜索树的基础上进行修改。
步骤如下:将Z节点结点插入树T中(二叉搜索树的插入方式),然后将Z染色为红色,再调用一个程序RB-INSERT-FIXUP来对结点重新染色并且旋转。
整个插入函数命名为RB-INSERT(T,x)。
分析《算法导论》中给出的伪代码(迭代版本)
RB-INSERT(T,z) //在树T中插入z
y = T.nil //y为插入点的父结点(对于根节点则为哨兵nil结点)
x = T.root //通过x来找到插入位置
while x != T.nil //最后的插入位置一定是叶子结点
y = x; //继续判断x的孩子结点,因此更新y为新的父结点
if z.key < x.key
x = x.left
else x = x.right
z.p = y; //此时找到了插入位置,因此插入结点Z的父结点为y
if y == T.nil //此时父结点竟然是T.nil!,说明这是一颗空树
T.root = z //因为是空树,那么z就是根节点
elseif z.key
对于RB-INSERT-FIXUP函数,也先给出伪代码分析
RB-INSERT-FIXUP(T,z)
while z.p.color == RED //如果该结点的父结点是红色,则连红了,要调整!
if z.p == z.p.p.left //z的父结点是左孩子 (参考备注1)
y = z.p.p.right //y是z的父亲的右兄弟
if y.color ==RED //参考示意图情况1
z.p.color = BLACK //把z的父亲变黑色
y.color = BLACK //把z的父亲的右兄弟变黑
z.p.p.color = RED //把z的父亲的父亲变红色
z = z.p.p //此时对于z来说他的父亲已经变成了黑色,因为他的爷爷变成了红色,所以判断他的爷爷是否满足性质。
/* 情况1
B z(R)
/ \ / \
R R =====> B B
/ /
z R
*/
else if z == z.p.right //z是右孩子,参考示意图情况2
z = z.p
LEFT-ROTATE(T,z)
z.p.color = BLACK //参考示意图的情况3
z.p.p.color = RED
RIGHT-ROTATE(T,z.p.p)
/* 情况2和情况3:z是右孩子
B B B
/ \ 情况2 / \ 情况3 / \
R B ========> R B =======> z R
\ / \
z z B
*/
else(same as then clause with “right” and left exchanged) //z的父亲是右孩子,则处理过程类似,略去
T.root.color = BLACK //修正因为情况1而导致的根节点变红!
备注1:z的父亲的父亲一定是存在的,因为z的父亲不是根节点(根节点是黑色)
分析:
复杂度分析:因为层数是log(n)级别的,因此修正也是log(n)级别的,综上,插入操作的修正是log(n)级别的。
删除结点比插入结点要复杂。先定义一个删除结点的子过程,RB-TRANSPLANT(T,u,v)(用v子树来替换u子树,结点u的双亲就变成v的双亲,注意v没有继承u的孩子!是一整颗树的替换),在删除结点时使用。伪代码如下:
RB-TRANSPLANT(T,u,v)
if u.p == T.nil //如果u是树根节点
T.root = v
elseif u == u.p.left //如果u是左孩子
u.p.left = v
else u.p.right = v //如果u是右孩子
v.p = u.p //修改v的父亲指针。
注意上面的代码,即使v是T.nil哨兵时,也可以进行。
下面给出删除结点的伪代码。RB-DELETE(T,z)
RB-DELETE(T,z)
y=z;
y-original-color = y.color //被删除的结点的颜色,如果是红色,且不是同时有左右孩子时,那删了就删了!
if z.left == T.nil //z的左子树为空时,直接用右子树代替z即可
x = z.right //用z.right 代替x即可
RB-TRANSPLANT(T,z,z.right)
elif z.right ==T.nil
x = z.left
RB-TRANSPLANT(T,z,z.left) //同理,当右边子树为空时
else //左右孩子都不空时
y = TREE-MINIMUM(z.right) //找到z的后继(即z的右边孩子中最小的结点,该函数很容易实现)
y-original-color = y.color //y需要替换到z上,因此当该颜色是黑色时,xxxxxxxxxxxxxxx
x = y.right
if y.p == z //如果z的后继就是z.right
x.p = y
else
RB-TRANSPLANT(T,y,y.right)
y.right = z.right
y.right.p = y
RB-TRANSLANT(T,z,y) //用z的后继代替z(继承他的父亲)
y.left = z.left //y继承z的左孩子
y.left.p = y
y.color = z.color //y继承z的颜色
if y-original-color == BLACK //该出的解释见备注1
RB-DELETE-FIXUP(T,x) //注意,这里处理的是x而不是y见后面分析(即x的路径需要补一个黑色)
备注1:
对于上述产生的删除问题,需要调用RB-DELETE-FIXUP(T,x)函数进行补救,总结一下上述造成的问题,有3种:
RB-DELETE-FIXUP(T,x)
while x!=T.root and x.color == BLACK //如果x是红色,则直接补为黑色就解决了!
if x == x.p.left //如果x是左孩子
w = x.p.right //w是x的兄弟
if w.color = RED //见下图的情况1
w.color = BLACK
x.p.color = RED //因为w是黑色,所以x.p原本只可能是红色
LEFT-ROTATE(T,x.p)
w = x.p.right //情况1结束 实际上情况1的目的就是把w.color转变成red
/* 情况1 w(B)
/
B R 左旋 R
/ \ =====> / \ =====> / ====> w = x.p.right
x(B) w(R) X(B) w(B) x(B)
*/
if w.left.color == BLACK and w.right.color ==BLACK //如果此时w的两个孩子都是黑 //情况2
w.color = RED //w.color原本一定是黑色 //情况2
x = x.p //此时把问题传递给了x.p,变成了x.p的子树路径少1///情况2结束
/* 情况2 未知 x(未知)
/ \ / \
x(B) w(B) B W(R)
/ \ ====> / \
B B B B
*/
elseif w.right.color ==BLACK //情况3开始
w.left.color == BLACK
w.color = RED
RIGHT-ROTATE(T,w)
w = x.p.right // 情况3结束,情况3操作目的是令w.right变成红色
/* 情况3 未知 未知 未知
/ \ / \ 右旋w / \
x(B) w(B) ====> x(B) W(R) ====> x(B) w(B)
/ \ / \ \
R B B B R
\
B
*/
w.color = x.p.color //情况4
x.p.color = BLACK
w.right.color =BLACK
LEFT-ROTATE(T,x,p)
x = T.root //情况4结束
/* 情况4 未知0 B w(未知0)
/ \ / \ 左旋根 / \
x(R) w(B) ====> x(R) w(未知0) ======> B B ==>X = T.root
\ \ /
R B X(R)
*/
else(same as then clause with "right" and "left" exchanged)
//当x是右孩子时的处理方式和x是左孩子类似
x.color = BLACK
《算法导论》上的图如下需要说明的一点是:第四种情况的根节点也可能是黑色,带进去可以发现这种变换也可以解决这个问题。
简单分析一下RB-DELETE-FIXUP的过程
1 当x是红时,直接转为黑色即可!结束!
2 当x是黑时,如果x的兄弟是红色,通过情况1的变换把兄弟转成黑色进行后续分析。
3 此时x的兄弟已经是黑色了,这时判断x的兄弟的孩子
4 如果两个孩子都是黑色,可以通过情况2的变换把x上推一个(x=x.p)。进行下一轮循环判断。
5 如果右边孩子黑,左孩子红,则可以通过情况3的变换把x的兄弟的右孩子变成红色。
6 如果右边孩子本来就是红色(或者通过步骤5变成了红),直接通过情况4的变换即可调整成功。
红黑树的插入操作的逻辑还是很好理解的,删除操作比较复杂。
B树是为磁盘或其他直接存取的辅助存储设备而设计的一种平衡搜索树。
B树与红黑树的不同之处在于B树的结点可以有很多个孩子。B树的高度也是O(lgN)但是由于表示高度的对数的底数可能非常大,因此高度比红黑树低许多。