13.1 红黑树的性质
13.1-1
按照图13-1(a)的方式,画出在关键字集合{1,2,...,15}上高度为3的完全二叉搜索树。以三种不同方式向图中加入NIL叶结点并对各结点着色,使所得的红黑树的黑高分别为2、3和4。
13.1-2
如果插入的结点被标记为红色,所得的树不是一颗红黑树,因为违反了红黑性质4。如果该结点被标记为黑色,所得的树也不是一颗红黑树,因为违反了红黑性质5。
13.1-3
考虑一颗根结点为红色的松弛红黑树T。如果将T的根结点标为黑色而其他都不变,所得到的还是一颗红黑树。
13.1-4
当一个黑结点的所有红色子结点都被吸收后,它可能的度有0、2、3和4。所得的树的叶结点深度为黑高度。
13.1-5
证明:根据红黑性质5,在一棵红黑树中,从某结点x到其后代叶结点的所有简单路径中,均包含相同数目的黑色结点。路径最短的情况是全是黑色结点,路径最长的情况是包含了尽可能多的红色结点。根据红黑性质4,路径中的红色结点之间要间隔至少一个黑色结点,所以最长路径中的红色结点数目至多与黑色结点相同。因此,在一棵红黑树中,从某结点x到其后代叶结点的所有简单路径中,最长的一条至多是最短一条的2倍。
13.1-6
在一棵黑高为k的红黑树中,树的高度最多为2k,除去叶结点后由内部结点组成的树的高度最多为2k-1,所以内部结点最多可能有个,根据引理13.1,最少可能有
个。
13.1-7
一棵含有n个关键字的红黑树,当其总层数为奇数且偶数层全为红色结点时,其红色内部结点个数与黑色内部结点个数的比值最大,比值是2。当其所有结点都为黑色时,该比值最小,比值是0。
13.2 旋转
13.2-1
写出RIGHT-ROTATE的伪代码。
RIGHT-ROTATE(T, y)
x = y.left // set x
y.left = x.right // turn x's right subtree into x's left subtree
if x.right ≠ T.nil
x.right.p = y
x.p = y.p // link y's parent to x
if y.p == T.nil
T.root = x
elseif y == y.p.left
y.p.left = x
else y.p.right = x
x.right = y // put y on x's right
y.p = x
13.2-2
证明:因为在一棵有n个结点的二叉搜索树中,共有n-1条边,每条边都可以旋转(左旋或者右旋),所以恰有n-1种可能的旋转。
13.2-3
a的深度会加1,b的会减1,c的不变。
13.2-4
证明:根据图13-2,在某个结点y上做右旋后,以y为根结点的子树的最右侧链上的结点数会加1。所以,至多n-1次右旋足以将树转变为一条右侧伸展的链。因此,任何一棵含n个结点的二叉搜索树可以通过次旋转,转变为其他任何一棵含n个结点的二叉搜索树。
13.3 插入
13.3-1
因为如果将z着为黑色,则红黑树的性质5就会被破坏,调整起来更加困难,所以不选择将z着为黑色。
13.3-2
将关键字41、38、31、12、19、8连续地插入一棵初始为空的红黑树之后,画出该结果树。
13.3-3
给每张图中的每个结点标上黑高,以验证图中所示的转换能保持性质5。
13.3-4
在RB-INSERT-FIXUP中将结点设为RED的只有第7行和第13行,执行它们的共同条件是z.p==z.p.p.left,此时z.p.p≠T.nil。所以第7行不会将T.nil.color设为RED,虽然在执行第13行之前,在第10行将z提升了一层,但在执行完第11行的左旋后z又下降了一层,所以z.p.p不变。因此,RB-INSERT-FIXUP永远不会将T.nil.color设置为RED。
13.3-5
证明:在RB-INSERT-FIXUP(T, z) 的伪代码中,①若出现 case 2 或 case 3,则第 16 行的 T.root.color = BLACK 并不会起作用;所以会运行第 13 行的 z.p.p.color = RED,即在循环不变式中会出现红结点,且退出循环后红结点颜色不会变更。②若循环中只出现 case 1,则当n>1 时,插入的结点必不为根结点,且插入的结点为红色,所以第 16 行不会改变插入的红结点的颜色。综上,至少存在一个红结点。
13.3-6
通过用栈保存父节点,在适当的时候压入和弹出节点,从而有效地实现RB-INSERT。
13.4 删除
13.4-1
证明:
case1:要进入循环,则x≠T.nil且x.color==BLACK,而case1中没有将x上移或者改变x的颜色,所以不可能从case1退出循环,必定会进入case2、3或4。
case2:因为w=x.p.right,所以w不可能是T.root,则在第10行中不是将T.root着为红色。如果从case2退出循环,则x==T.nil或x.color==RED,无论哪种情况,第23行都能保证树根一定是黑色的。
case3:会进入case4。
case4:因为在第21行中令x=T.root,所以肯定会退出循环,在第23行中令x.color=BLACK即令T.root.color=BLACK。
综上,在执行RB-DELETE-FIXUP之后,树根一定是黑色的。
13.4-2
证明:在RB-DELETE中,如果x和x.p都是红色的,在调用RB-DELETE-FIXUP(T,x)时不会进入循环,会在第23行时令x.color=BLACK,从而恢复性质4。
13.4-3
在练习13.3-2中,将关键字41、38、31、12、19、8连续插入一棵初始的空树中,从而得到一棵红黑树。给出从该树中连续删除关键字8、12、19、31、38、41后的红黑树。
13.4-4
在RB-DELETE-FIXUP代码的第1、2、4、9、12和22行中,可能会检查或修改哨兵T.nil。
13.4-5
在图13-7的每种情况中,给出所示子树的根结点至每棵子树α,β,...,ζ之间的黑结点个数,并验证它们在转换之后保持不变。
子树 | α | β | γ | δ | ε | ζ |
所示子树的根结点至每棵子树之间的黑结点个数 | 3 | 3 | 2 | 2 | 2 | 2 |
子树 | α | β | γ | δ | ε | ζ |
所示子树的根结点至每棵子树之间的黑结点个数 | 2+count(c) | 2+count(c) | 2+count(c) | 2+count(c) | 2+count(c) | 2+count(c) |
子树 | α | β | γ | δ | ε | ζ |
所示子树的根结点至每棵子树之间的黑结点个数 | 2+count(c) | 2+count(c) | 1+count(c) | 1+count(c) | 2+count(c) | 2+count(c) |
子树 | α | β | γ | δ | ε | ζ |
所示子树的根结点至每棵子树之间的黑结点个数 | 2+count(c) | 2+count(c) | 1+count(c)+count(c') | 1+count(c)+count(c') | 1+count(c) | 1+count(c) |
13.4-6
证明:在RB-DELETE中y-original-color存储的是x.p的颜色,在第21行中,只有当y-original-color是黑色时才会调用RB-DELETE-FIXUP,所以x.p在情况1开始时必是黑色的,因此这两位教授没有担心的必要。
13.4-7
不一样。
思考题
13-1 持久动态集合
a.对于一棵一般的持久二叉搜索树,为插入一个关键字k或删除一个结点y,需要改变该结点的所有祖先结点。
b.写出一个过程PERSISTENT-TREE-INSERT,使得在给定一棵持久树T和一个要插入的关键字k时,它返回将k插入T后得到的新的持久树T'。
PERSISTENT-TREE-INSERT(T, k)
x = T.root
x' = new Node
T' = new Tree
T'.root = x'
while x ≠ NIL
x'.key = x.key
if k < x.key
x'.right = x.right
x'.left = new Node
x = x.left
x' = x'.left
else x'.left = x.left
x'.right = new Node
x = x.right
x' = x'.right
x'.key = k
x'.left = NIL
x'.right = NIL
return T'
c.如果持久二叉搜索树T的高度为h,实现PERSISTENT-TREE-INSERT过程的时间和空间需求都是。
d.证明:如果在每个结点中增加一个父结点属性,这种情况下,PERSISTENT-TREE-INSERT需要复制整棵树,否则那些共有结点会有两个父结点。所以PERSISTENT-TREE-INSERT的时间需求和空间需求为,其中n为树中的结点个数。
e.要想利用红黑树来保证每次插入或删除的最坏情况运行时间和空间为,根据上一小问,则结点中不能有父结点属性。因此,可以根据练习13.3-6在红黑树的表示中不提供父指针,从而保证每次插入或删除的最坏情况运行时间和空间为
。
13-2 红黑树上的连接操作
a.证明:
- 对于插入过程,注意到只有case1中的结点C的黑高增加了1。图13-5和图13-6中的每个结点的黑高参见练习13.3-3。
- 对于删除过程,可以使用练习13.3-3类似的方法计算其黑高变化,得到的结果是case2中的结点B的黑高减少1,case4中的结点B的黑高减少1,结点D的黑高增加1。
所以我们只需要在插入和删除函数中的某个if-else判断中增加几行对黑高的计算代码。因此,在不需要树中结点的额外存储空间和不增加渐进运行时间的前提下,RB-INSERT和RB-DELETE可以维护这个属性。
当沿T下降时,遇到黑色结点时将黑高度减1即可得到各个结点的黑高,因此,可以对每个被访问的结点在时间内确定其黑高。
b.假设,描述一个
时间的算法,使之能从黑高为
的结点中选出具有最大关键字的
中的黑结点y。
CHOOSE-NODE(T1, T2)
bh = T1.bh
y = T1.root
while bh > T2.bh
y = y.right
if y.color == BLACK
bh = bh - 1
return y
c.根据上一小问的结果,y的黑高为,所以
。因为对任何
和
,有
,因此,只需将
作为x的左子树,
作为x的右子树,即可在不破坏二叉搜索树性质的前提下,在
时间内用
来取代
。
d.要保持红黑性质1、3和5,应将x着成红色。调用RB-INSERT-FIXUP即可在时间内维护性质2和性质4。
e.当时,先从黑高为
的结点中选出具有最小关键字的
中的黑结点y,然后用
来取代
,最后调用RB-INSERT-FIXUP维护性质2和性质4。
f.证明:RB-JOIN由三个过程组成,先在时间内选出黑结点y,然后在
时间内用新的子树取代
,最后在
时间内维护性质2和性质4,因此,RB-JOIN的运行时间是
。
13-3 AVL树
a.证明:假设高度为h的AVL树的结点数为,因为对AVL树中的每一个结点x,x的左子树和右子树的高度差至多为1,所以它的左右子树的结点数之和至少为
,即
。可见高度为h的AVL树至少有
个结点,其中
是斐波那契数列的第h个数。根据式3.25,
,其中
,所以
。因此,一棵有n个结点的AVL树高度为
。
b.描述一个过程BALANCE(x),输入一棵以x为根的子树,其左子树与右子树都是高度平衡的,而且它们的高度差至多是2,即,并将这棵以x为根的子树转变为高度平衡的。(提示:使用旋转。)
BALANCE(x)
if x.right.h - x.left.h == 2
if x.right.left.h <= x.right.right.h
LEFT-ROTATE(T, x)
else RIGHT-ROTATE(T, x.right)
LEFT-ROTATE(T, x)
elseif x.left.h - x.right.h == 2
if x.left.right.h <= x.left.left.h
RIGHT-ROTATE(T, x)
else LEFT-ROTATE(T, x.left)
RIGHT-ROTATE(T, x)
c.利用(b)来描述一个递归过程AVL-INSERT(x,z),该操作输入一个AVL树中的结点x以及一个新创建的结点z(其关键字已经填入),然后将z添加到以x为根的子树中,并保持x是一棵AVL树的根结点。
AVL-INSERT(x, z)
temp = x
y = NIL
while x != NIL
y = x
if z.key < x.key
if x.left.h > x.right.h
temp = x
x = x.left
else if x.left.h < x.right.h
temp = x
x = x.right
z.p = y
if z.key < y.key
y.left = z
else y.right = z
while y != NIL
if y.left == NIL and y.right != NIL
h = y.right.h + 1
elseif y.left != NIL and y.right == NIL
h = y.left.h + 1
else if y.left.h > y.right.h
h = y.left.h + 1
else h = y.right.h + 1
if y.h == h
break
else y.h = h
y = y.p
BALANCE(temp)
d.证明:根据(c)的结果,AVL-INSERT操作先从树根开始,指针x记录了一条向下的简单路径,直至找到输入项z要放置的地方,该过程在一棵高度为h的树上的运行时间为。然后调用一次过程BALANCE维护AVL树的性质,根据(b)的结果,该过程的运行时间为
,执行了
次旋转。根据(a)的结论,一棵有n个结点的AVL树高度为
。因此,在一棵n个结点的AVL树上AVL-INSERT操作需花费
时间,且执行
次旋转。
13-4 treap树
a.证明:根据二叉搜索树的性质,插入一个新结点后得到的新树唯一。给定一个已有相应关键字和优先级(互异)的结点,
,...,
组成的集合,因为
表示
在
之前被插入,所以这些结点插入二叉搜索树的顺序已定,按此顺序插入一棵正常的二叉搜索树后得到的treap树唯一。因此,存在唯一的一棵treap树与这些结点相关联。
b.证明:
- 因为每个结点的
是一个独立选取的随机数,也即结点插入到二叉搜索树中的顺序是随机的,从12.4节可知,随机构造二叉搜索树是趋向平衡的。所以,treap树的期望高度是
。
- 根据定理12.2,在一棵高度为h的二叉搜索树上,操作SEARCH可以在
时间内完成。因为treap树具有二叉搜索树的特征,且treap树的期望高度是
,因此在treap内查找一个值所花的时间为
。
c.TREAP-INSERT先执行通常的二叉搜索树插入过程,然后做旋转来恢复最小堆顺序的性质。下面给出伪代码:
TREAP-INSERT(T, z)
TREE-INSERT(T, z)
y = z.p
while y != NIL
if y.priority > z.priority
if z == y.left
RIGHT-ROTATE(T, y)
else LEFT-ROTATE(T, y)
y = z.p
else y = NIL
d.证明:TREAP-INSERT先从树根开始,沿着一条简单路径向下直到要替换的输入项z的NIL,此过程的运行时间为。然后再沿着这条简单路径返回做旋转来恢复最小堆顺序的性质,最多返回到树根,也即要做
次旋转,一次旋转的运行时间为
,所以TREAP-INSERT的期望运行时间是
。根据(b)的结论,treap树的期望高度是
,因此,TREAP-INSERT的期望运行时间是
。
e.证明:使用数学归纳法进行证明,设在插入x期间所执行的旋转的总次数为k。
- 当结点x插入treap T后的位置在叶结点时,则不用进行调整,k=0,C+D=0成立。
- 假设k=i时,C+D=i成立。当k=i+1时,即在下一次旋转时,通过观察可知道,当为左旋时,x左子树的右脊柱的长度增加了1,当为右旋时,x右子树的左脊柱的长度增加了1,所以C+D=i+1成立。
因此,在插入x期间所执行的旋转的总次数等于C+D。
f.证明:
- 当
时,即y在x的左子树的右脊柱中。因为y在x的左子树中,根据treap树的性质,可得
,
。假设对于某个满足
的z,
,则y在z的左子树内,与y在x的左子树的右脊柱中相矛盾,所以
。
- 当
,
时,根据treap树的性质,可得y在x的左子树中。因为对于每个满足
的z,有
,可得在x的左子树中,所有key比y.key大的结点都在y的右子树中,所以y在x的左子树的右脊柱中。
因此,当且仅当
,
成立,且对于每个满足
的z,有
。
g.证明:因为关键字为1,2,...,n,所以key在k~i之间包括k和i的结点总共有k-i+1个,随机排列有(k-i+1)!种情况。根据(f)的结论,当时,对于每个满足key在k~i之间不包括k和i的结点z,存在
,这样的结点z总共有k-i-1个,随机排列有(k-i-1)!种情况。因此,
。
h.证明:因为,所以
。
i.证明:定义指示器随机变量{y在x的右子树的左脊柱中},利用对称性可得,
。因此,
。
j.证明:根据(h)和(i)的结论,可得。因此,当在一棵treap树中插入一个结点时,执行旋转的期望次数小于2。