这一篇解决《算法导论》中红黑树章节的部分习题,在上一篇自己亲自实现红黑树后,解决这些题目就轻松多了。
练习13.1-6 在一棵黑高度为 $k$ 的红黑树中,内节点最多有多少个?最少有多少个?
黑高度为 $k$ 的二叉树,全高度最小为 $k+1$,最大为 $2k+2$ 。内节点最多有 $2^{k+1}-1$ 个,这种情况下红黑树中没有红节点,黑节点全满(满足所有叶子节点黑高度一致的条件);内节点最多有 $2^{2k+2}-1=4^{k+1}-1$ 个。这种情况下红黑树全满。
练习13.1-7 在 $n$ 个关键字上构造出来的二叉树,红节点与黑节点个数的比值最大为多少?最小是多少?
红节点最多时,比值为:
$$\frac{n-2^{h-1}-2^{h-3}-...-(2)-1}{2^{h-1}+2^{h-3}+...+(2)+1},h=\lfloor\lg n\rfloor$$
红节点最少时,比值时为:
$$\frac{n-2^{\lfloor\lg n\rfloor}}{2^{\lfloor\lg n\rfloor}}$$
练习13.2-2 证明,在一棵 $n$ 个节点的二叉查找树中,刚好有 $n-1$ 种可能的旋转。
思路:每一个可能的旋转对应于一条边,每一个条边也只能对应一个可能的旋转,因此可能的旋转数就是边的数目。每一条边都对应一个儿子节点,每一个节点(除了根节点)都对应一个边,所以边的数目为 $n-1$ ,也就是可能的旋转的数目。
练习13.2-4 证明任意一颗含有 $n$ 个节点的二叉查找树都能通过 $O(n)$ 次旋转,转变为另一颗含有同样节点(节点位置可以任意不一样,但仍然保持二叉查找树性质)的二叉查找树。
思路:考虑一颗二叉查找树的“右链”,即从根节点向具有最大节点值的节点的路径。不断地右旋右链上具有左子树的节点,每一次旋转可以使右链上多一个节点,(又,右链上至少有一个节点,根节点),所以至多 $n-1$ 次右旋后,二叉树的右链上连接了所有的节点,这时二叉树实际上称了一个已排序(旋转保持二叉查找树的性质)的单链表。旋转是可逆的,所以任意两棵二叉查找树都可以通过至多 $2n-2=O(n)$ 次旋转来互相变换。
练习13.4-7 假设用 RB-INSERT 方法将一个节点 $x$ 插入到红黑树中,再用 RB-DELETE 方法删除之,请问结果的红黑树与初始的红黑树是否一致?答:不一致。
思考题13-1 持久动态集合。持久动态集合是这样的集合,每当集合被更新(插入元素或删除元素)时,仍然需要维护该集合的一个较旧的版本。比如下图就展示了一个二叉查找树的持久动态集合,对于每个版本,都会有一个对应的根节点,所有根节点处在一个可维护的链表中,可以通过该链表访问所有的根节点,进而访问所有的版本。该二叉树节点没有父节点域。
- 对一个持久二叉查找树,插入或删除一个关键字 $z$ 的,需要改变哪些节点?答:不需要改变任何节点,只需要新增从根节点开始到插入/删除节点路径上的所有节点。
- 写出向持久二叉查找树中插入一个节点的过程
PERSISTENT-TREE-INSERT(T, node) tmpO = T.head; tmpN = copy(tmpO); headN = tmpN; if(tmpO = NULL) headN = node; return headN; while(true) if(node.key <= tmpO.key) if(tmpO->left isNot NULL) tmpN->left = copy(tmpO->left); tmpO = tmpO->left; tmpN = tmpN->left; else // tmpO->left is NULL tmpN->left = node; break; else if(tmpO->right isNot NULL) tmpN->right = copy(tmpO->right); tmpO = tmpO->right; tmpN = tmpN->right; else // tmpO->right is NULL tmpN->right = node; break; return headN;
- 如果持久二叉查找树的高度为 $h$ ,则实现上述过程的时间空间要求是多少?答:$O(h)$ 和 $O(h)$ 。
- 假设每个节点中具有父节点域,又如何?答:每次更新二叉树都要复制整个二叉树了。
思考题13-2 红黑树上的连接操作。连接操作将两个动态集合 $S_{1}$、$S_{2}$ 和元素 $x$ 连接成一个动态集合,且 $S_{1}$ 中的所有元素小于 $x$,$S_{2}$ 中的所有元素大于 $x$ 。
- 给定一棵红黑树,其黑高度被单独维护在域 $bh[T]$ 中,证明在红黑树的插入和删除操作中,每一次更新 $bh[T]$ 只需要$O(1)$的时间。
思路:不论是删除节点导致树的黑高度降低还是增加节点导致树的黑高度升高,这种情况都是不常见的,只有在fix操作递归到根节点时才需要这样做。 - 红黑树上的实现 RB-JOIN(T1, T2, x) 操作。假设 $bh[T_{1}]\geq bh[T_{2}]$,描述一个 $O(\lg n)$ 的算法找出黑高度为 $bh[T_{2}]$ 的节点的具有最大查找字的节点。
思路:实在太简单了,沿着右链走就行了。 - $T_{y}$ 是以 $y$ 为根节点的子树,如何在 $O(1)$ 的时间里完成 $T_{y}\bigcup \{x\}\bigcup T_{2}$ 。
思路:$x$ 为根节点,$T_{y}$ 和 $T_{2}$ 分别为左右子树,该树满足二叉查找树的性质。该树替代原先的 $T_{y}$ 作为 $T_{1}$ 的子树。 - 保持红黑树性质1、3、5,则 $x$ 染成什么颜色?如何恢复?
答:红色,按照红黑树中插入新元素后的fixInsert(x)方法恢复性质2、4。 - 对称的情况,略。
- 运行时间自然是 $O(\lg n)$,更是自然。这本书的题目,只要理解了题目的意图,其实还是比较容易。
思考题13-3 AVL树。AVL树是一种高度平衡的二叉树,任意一个节点的左子树和右子树高度差不大于1。
思考题13-4 Treap(树堆)。一种即具有二叉查找树性质,又具有堆性质的数据结构,用来模拟随机顺序插入的二叉树。每个节点有两个域:关键字域与优先级域。关键字域满足二叉树条件,优先级域满足最小堆条件。
- 证明对于给定的了互异关键字和互异优先级的 $n$ 个元素,存在唯一的treap与之对应(二叉树和最小堆显然不是)。
思路:根据最小堆性质,根节点是确定的,即优先级最小的元素;根节点确定后,实际上根据二叉树性质将剩下的 $n-1$ 个元素分为了两部分,大于根节点的和小于根节点,这两部分的根节点又是确定的。如此递归下去,整个treap是唯一的。 - 解释treap-insert的工作方式。
思路:先根据二叉查找树的插入方法插入元素,通过与父节点之间的旋转(如果元素时左节点,就右旋,反之)将优先级大于该元素的节点转到下面去:首先,旋转操作不会违背二叉查找树的性质;而旋转后,以该元素为根的子树也保留了最小堆性质。 - treap-insert操作后,被插入元素 $x$ 处在了一个新的位置,其左子树的右链长度为 $C$ ,右子树的左链长度为 $D$ ,求证插入 $x$ 的过程中执行了$C+D$ 次旋转。
思路:C为左旋次数,D为右旋次数,注意右链的长度指右链上节点的长度,而不是边的长度。
这一题后面还有几问关于这种数据结构插入时间的期望的,就没再继续做了。