前言
红黑树是一种较为“平衡的”二叉查找树。它能保证最坏的情况下其基本操作时间为O(lgn).
红黑树的性质
红黑树 是一种二叉查找树,但在每个结点上增加了一个存储位表示结点的颜色,可以是 RED 或 BLACK 。通过对任何一条从根到叶子的路径上的各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,因而是接近平衡的。
树中每个结点包含五个域: color , key , left , right 和 p 。如果某结点没有一个子结点或父结点,则该结点相应的指针为 NIL 。我们将这些 NIL 视为指向二叉查找树外结点(叶子)的指针,而把带关键字的结点视为树的内结点。
一棵红黑树需要满足下面的 红黑性质 :
- 每个结点或是红的,或是黑的。
- 根结点是黑的。
- 每个叶结点( NIL )是黑的。
- 如果一个结点是红的,则它的两个孩子都是黑的。
- 对每个结点,从该结点到其子孙结点的所有路径上包含相同数目的黑结点。
通常我们将注意力放在红黑树的内部结点上,因为它们存储了关键字的值。因此本文其余部分都将忽略红黑树的叶子
引理13.1 一棵有n个内结点的红黑树的高度至多为2lg(n+1).
练习
1-1 使用图13-1a的格式,画出在关键字集合{1,2,…,15}上高度为3的完全二叉查找树。以三种不同方式,向图中加入NIL叶结点并对各结点着色,使所得的红黑树的黑高度分别为2,3,4.
1-2对图13-1中的红黑树,画出调用TREE-INSERT插入关键字36后的结果。结果插入的结点被标为红色,所得的树是否还是一棵红黑树?如果该结点被标为黑色呢?
- 标为红色后,不是红黑树,不符合性质4.
- 标为黑色后,不是红黑树,不符合性质5.
1-3 定义松弛红黑树为满足红黑性质1,3,4,5的二叉查找树。换言之,根部可以是红色或是黑色。考虑一棵根是红色的松弛红黑树T。如果将T的根部标为黑色而其他都不变,则所得到的是否还是一棵红黑树?
是。节点变了色后1,3,4,5性质也满足。
1-4
(略)
1-5 证明:在一棵红黑树中,从某结点x到其后代叶结点的所有简单路径中,最长的一条是最短一条的至多两倍。
性质4,5决定。最短:此路径上全是黑色结点。最长:此路径上红黑结点交替出现(红色结点数目=黑色结点数目)
1-6 在一棵黑高度为k的红黑树中,内结点最多可能有多少个?最少可能有多少个?
根据引理13.1 至少包含 2 k − 1 2^k-1 2k−1个内部结点。至多包含 2 2 k − 1 个 2^{2k}-1个 22k−1个内部结点。(最多就是红黑结点交替出现的情况)
1-7 请描述出一棵在n个关键字上构造出来的红黑树,使其中红的内部结点数与黑的内部结点数比值最大。这个比值是多少?具有最小可能比例的树又是怎么样?此比值是多少?
最小可能比值就是所有结点都是黑色,比值为0。 最大可能是红黑结点交替出现,并且最底层是全部是红色结点,比值是2:1.
旋转
当在含 n 个关键字的红黑树上运行时,查找树操作 TREE-INSERT 和 TREE-DELETE 的时间为 O ( lg n )。由于这两个操作对树做了修改,结果可能违反了红黑树的性质,为保持红黑树的性质,就要改变树中某些结点的颜色和指针结构。指针结构的修改是通过 旋转 来完成的,这是一种能保持二叉查找树性质的查找树局部操作。
LEFT-ROTATE(T, x)
y = x.right // set y
x.right = y.left // turn y's left subtree into s's right subtree
if y.left != T.nil
y.left.p = x
y.p = x.p
if x.p == T.nil
T.root = y
elseif x == x.p.left
x.p.left = y
else
x.p.right = y
y.left = x // put x on y's left
x.p = y
练习
2-1 写出RIGHT-ROTATE的伪代码。
RIGHT-ROTATE(T, y)
x = y.left
y.left = x.right
if x.right != T.nil
x.right.p = y
x.p = y.p
if y.p == T.nil
T.root = x
else if y == y.p.right
y.p.right = x
else y.p.left = x
x.right = y
y.p = x
2-2 证明:在一棵有n个结点的二叉查找树中,刚好有n-1种可能的旋转。
因为n个结点有n-1条边,每条边都可以(左或右)旋转,所以有n-1种可能的旋转。
2-3
(略)
2-4 证明:任何一棵含n个结点的二叉查找树,可以通过O(n)次旋转,转变为另一棵含n个结点的二叉查找树。
每次右(左)旋转,都会使最右(左)链上多一个结点,经过任意n-1次右(左)旋转,任意右(左)总会变成一个单链表。
插入
向一棵含 n 个结点的红黑树 T 中插入一个新结点 z。首先将结点 z 插入树 T 中,就好像 T 是一棵普通的二叉查找树一样,然后将 z 着为红色。
RB-INSERT(T, z)
1 y = T.nil
2 x = T.root
3 while x != T.nil
4 y = x
5 if z.key < x.key
6 x = x.left
7 else
8 x = x.right
9 z.p = y
10 if y == T.nil
11 T.root = z
12 elseif z.key < y.key
13 y.left = z
14 else
15 y.right = z
16 z.left = T.nil
17 z.right = T.nil
18 z.color = RED
19 RB-INSERT-FIXUP(T, z)
为保证红黑性质,这里要调用一个辅助程序 RB-INSERT-FIXUP 来对结点重新着色并旋转.
RB-INSERT-FIXUP(T, z)
1 while z.p.color == RED
2 if z.p == z.p.p.left // z的父结点是其父结点的左孩子
3 y = z.p.p.right // 令y为z的叔父结点
4 if y.color == RED
5 z.p.color = BLACK // case 1
6 y.color = BLACK // case 1
7 z.p.p.color = RED // case 1
8 z = z.p.p // case 1
9 else
10 if z == z.p.right
11 z = z.p // case 2
12 LEFT-ROTATE(T, z) // case 2
13 z.p.color = BLACK // case 3
14 z.p.p.color = RED // case 3
15 RIGHT-ROTATE(T, z.p.p) // case 3
16 else // z的父结点是其父结点的右孩子
17 y = z.p.p.left // 令y为z的叔父结点
18 if y.color = RED
19 z.p.color = BLACK
20 y.color = BLACK
21 z.p.p.color = RED
22 z = z.p.p
23 else
24 if z = z.p.left
25 z = z.p
26 RIGHT-ROTATE(T, z)
27 z.p.color = BLACK
28 z.p.p.color = RED
29 LEFT-ROTATE(T, z.p.p)
30 T.root.color = BLACK
具体情况参考红黑树-插入
练习
3-1在RB_INSERT的第16行中,假设新插入的结点z是红的。注意如果将z着为黑色,则红黑树性质4就不会被破坏。那么我们为什么没有选择将z着为黑色呢?
如果z着为黑色,那么性质5会被破坏,红黑树调整起来会更复杂。
3-2在将关键字48,38,31,12,19,8插入一棵初始为空的红黑树中之后,结果树是什么样子?
3-3
(略)
3-4
(略)
3-5 考虑用RB_INSERT插入n个结点而成的一棵红黑树。证明:如果n>1,则该树至少有一个红结点。
假设前n-1个插入结点后都为黑色 满足红黑树性质,那么第n个结点插入后,必须是红色。否则违反性质5.
3-6说明如果红黑树的表示中不提供父指针的话,应当如何有效地实现RB_INSERT.
使用栈。
删除
删除操作运行时间也是O(lgn).并且比插入操作复杂。
RB-DELETE(T, z)
1 y = z
2 y-original-color = y.color
3 if z.left == T.nil
4 x = z.right
5 RB-TRANSPLANT(T, z, z.right)
6 elseif z.right == T.nil
7 x = z.left
8 RB-TRANSPLANT(T, z, z.left)
9 else
10 y = TREE-MINIMUM(z.right)
11 y-original-color = y.color
12 x = y.right
13 if y.p == z
14 x.p = y
15 else
16 RB-TRANSPLANT(T, y, y.right)
17 y.right = z.right
18 y.right.p = y
19 RB-TRANSPLANT(T, z, y)
20 y.left = z.left
21 y.left.p = y
22 y.color = z.color
23 if y-original-color == BLACK
24 RB-DELETE-FIXUP(T, x)
还要调用 RB-DELETE-FIXUP 以保持红黑性质
RB-DELETE-FIXUP(T, x)
1 while x != T.root and x.color == BLACK
2 if x == x.p.left
3 w = x.p.right
4 if w.color == RED
5 w.color = BLACK // case 1
6 x.p.color = RED // case 1
7 LEFT-ROTATE(T, x.p) // case 1
8 w = x.p.right // case 1
9 if w.left.color == BLACK and w.right.color == BLACK
10 w.color = RED // case 2
11 x = x.p // case 2
12 else
13 if w.right.color == BLACK
14 w.left.color = BLACK // case 3
15 w.color = RED // case 3
16 RIGHT-ROTATE(T, w) // case 3
17 w = x.p.right // case 3
18 w.color = x.p.color // case 4
19 x.p.color = BLACK // case 4
20 w.right.color = BLACK // case 4
21 LEFT-ROTATE(T, x.p) // case 4
22 x = T.root // case 4
23 else (same as then clause with "right" and "left" exchanged)
24 x.color = BLACK
具体情况参考红黑树-删除
练习
4-1 在执行RB_DELETE-FIXUP之后,证明:树根一定是黑色的。
- Case 1: transform to 2, 3, 4.
- Case 2: if terminates, the root of the subtree (the new xx) is set to black.
- Case 3: transform to 4.
- Case 4: the root (the new xx) is set to black.
4-2在RB_DELETE中,如果x和x.p都是红色的。证明:可以通过调用RB_DELETE_FIXUP(T,x)来恢复性质4.
x和x.p都为红色,那么不进入循环,直接最后把x着为黑色以恢复性质4.。
4-3在练习13.3-3中,将关键字41,38,31,12,19,8连续插入一棵初始的空树中,从而得到一棵红黑树。请给出从该树中连续删除关键字8,12,19,31,38,41后的红黑树。
- 删除8
- 删除12
- 删除19
4-[4,5,6]
(略)
4-7 假设用RB_INSERT将一个结点x插入一棵红黑树,紧接着又用RB_DELETE将它从树中删除。结果的红黑树与初始红黑树是否一样?证明你的答案。
思考题
- 13-1 持久动态集合
- 13-2 红黑树的连接操作
- 13-3 AVL树
- 13-4 Treap树