B 型树虽然具有奇妙的平衡性,但实施起来却非常困难。我们需要跟踪不同的节点,而且拆分过程也相当复杂。作为懂得欣赏好代码和迎接好挑战的计算机科学家,让我们找到另一种创建平衡树的方法。
对于任何二项搜索树 BST 来说,都有多种结构化方法来保持 BST 的不变性。在第 11.1 章中,我们谈到了以不同顺序插入元素会产生不同的 BST。
然而,插入并不是同一 BST 产生不同结构的唯一方法。我们可以做的一件事是通过一个叫做旋转的过程来改变已经存在节点的树。
旋转树
- 任意给定二叉搜索树,我们都可以对其进行旋转,任意改变树的形状,同时保留树的属性。
rotateLeft(G):假设x是G的右子节点,让G成为X的新左子节点。
rotateRight(G):假设x是G的左子节点,让G成为x的新右子节点。
private Node rotateRight(Node h) {
// assert (h != null) && isRed(h.left);
Node x = h.left;
h.left = x.right;
x.right = h;
return x;
}
// make a right-leaning link lean to the left
private Node rotateLeft(Node h) {
// assert (h != null) && isRed(h.right);
Node x = h.right;
h.right = x.left;
x.left = h;
return x;
}
下面是节点 G 向左旋转时的描述。
G 的右边子节点 P 与 G 合并,并将它的子节点一起合并。然后,P 将其左侧子节点传递给 G,G 向左旋转成为 P 的左侧子节点。你可以看到,树的结构和层数都发生了变化。我们还可以在非根节点上进行旋转。我们只需暂时断开节点与父节点的连接,旋转节点上的子树,然后重新连接新的根节点。
图例如下:
rotateLeft(G):
理解窍门:左旋可以看成将要旋转的节点和其右子节点合并成一个节点,再将这个合并后新节点的左元素下移,下移的时候带着左子节点、中间子节点,并留下右子节点。
同理,右旋——合并目标节点和其左字节点——右元素下移。
旋转可以改变树的长短,保留搜索树的特性。
如果左短右长,可以让根左转。
红黑树
我们在上一节说过,我们非常喜欢 2-3 树(一个节点含有2个元素,并有3个子节点),因为它们始终保持着平衡,但我们也不喜欢它们,因为它们很难实现。但为什么不能两者兼得呢?为什么不创建一棵使用二项搜索树 BST 实现的树,但在结构上与 2-3 树完全相同,从而保持平衡呢?(注意,在本章中,我们将专门讨论 2-3 树,而不是 2-3-4 树
介绍
我们可以做的一件事就是创建一个 "胶水 "节点,它不包含任何信息,只是用来显示它的两个子节点实际上是一个节点的一部分。
然而,这是一个非常不优雅的解决方案,因为我们会占用更多的空间,代码也会很难看,而且这样会使得树多出一个空链接(如图d节点右下方)。
因此,我们不使用胶水节点,而是使用胶水链接!
我们任意选择让左边的元素成为右边元素的子元素。这样就产生了一棵左倾树。我们用红色来表示一个链接是胶水链接。普通链接为黑色。因此,我们称这些结构为左倾红黑树(LLRB)。我们将在 61B 中使用左倾树。
注意:左倾红黑树本质上就是二项搜索树 BST ,叫这个名字只是为了记住哪些链接对应于 2元素3子节点 的子树。
左倾红黑树与 2-3 树有 1-1 的对应关系。每一棵 2-3 树都有一棵与之相关的唯一的左倾红黑树。至于 2-3-4 树,它们与标准红黑树保持对应关系。
左红黑树特性
- 1-1 对应 2-3 棵树。
- 没有节点有 2 个红色链接。
- 没有红色右链接。
- 从树根到树叶的每条路径都有相同数量的黑色链接(因为 2-3 树到每片树叶的链接数量相同)。
- 高度不超过相应 2-3 树高度的 2 倍+1。
红黑树的构建(插入)
1. 插入新节点的时候总是使用红色链接。且只能添加在左下侧。具体应该插入到哪个节点下面遵循二叉搜索树BST的规则。
2. 如果有右倾红色链接,就要左旋某个节点。(各链接颜色不变)
3. 如果有两个连续的(红色)左链接,就要右旋某个节点。(各链接颜色不变)
4. 当一个节点左右子节点都是红色链接,那么将所有与它相连接的链接的颜色都调换(红、黑互换)。
5. 旋转或反色操作可能会导致另一个上述情况产生,需要接着转化,直到符合上述的所有条件。
红黑树的时间复杂度
左倾红黑树的高度:O(logN).
插入节点用时:O(log N)
- 添加新节点用时O(logN).
- 每次插入节点后的旋转、反色操作用时O(logN).