《数据结构、算法与应用 —— C++语言描述》学习笔记 — 平衡搜索树 — 红黑树
一、基本概念
红黑树是这样的一棵二叉搜索树:树中每一个节点的颜色或者是红色或者是黑色。红-黑树的其他特征可以用相应的扩充二叉树来说明:
① 根节点和所有外部节点都是黑色。
② 在根至外部节点路径上,没有连续两个节点是红色。
③ 在所有根节点至外部节点的路径上,黑色节点的数目相同。
红黑树还有另一种等价,它取决于父子节点间的指针颜色。从父节点指向黑色孩子的指针是黑色,从父节点指向红色孩子的指针是红色。另外还有:
① 从内部节点指向外部节点的指针是黑色的。
② 在根至外部节点路径上,没有两个连续的红色指针。
③ 在所有根至外部节点路径上,黑色指针的数目都相同。
因此,如果我们知道指针的颜色,就能推断节点的颜色,反之亦然。
一棵红黑树的构造如图(把外部节点当做黑色节点):

令红黑树的一个节点的阶,是从该节点到一外部节点的路径上黑色指针的数量。因此,一个外部节点的阶是零,上图中根节点的阶是2。
根据上面的性质,我们可以得到以下两条定理:
① 设从根到外部节点的路径长度(length)是该路径上的指针数量。如果P和Q是红黑树中的两条从根至外部节点的路径,那么 l e n g t h ( P ) ≤ 2 l e n g t h ( Q ) length(P)\le2length(Q) length(P)≤2length(Q)
任何一条路径上的红色指针个数都不可能超过黑色指针个数,因此任何一条路径的长度都不可能是另一条路径长度的2倍以上。
② 令h是一棵红黑树的高度,n是树的内部节点数量,而r是根节点的阶,则:
(1) h ≤ 2 r h\le2r h≤2r
(2) h ≤ 2 r − 1 h\le2^r-1 h≤2r−1
(3) h ≤ 2 log 2 ( n + 1 ) h\le2\log_2(n+1) h≤2log2(n+1)
由于红黑树的高度最多为 2 log 2 ( n + 1 ) 2\log_2(n+1) 2log2(n+1),所以,在O(h)时间内可以完成的搜索、插入和删除操作,其复杂度为 O(logn)。
二、红黑树操作
1、红黑树的搜索
我们使用普通二叉搜索树的搜索来实现红黑树的搜索。其时间复杂度为 O(logn)。
2、红黑树的插入
红黑树的插入使用普通二叉树的插入算法。对于插入的新元素,我们需要上色。如果插入前的树是空的,那么新节点是根节点,颜色应该是黑色。假设插入前的树是非空的。如果新节点的颜色是黑色,那么在它所属的从根到外部节点的路径上,黑色节点的数量将会发生变化。这导致特征③一定被破坏。如果新节点的颜色是红色,我们有可能出现两个相连的红色节点,也有可能没有。因此特征②有可能被保留。因此,我们将新节点赋为红色。
如果将新节点赋为红色引起了特征②被破坏,我们就说树的平衡被破坏了。不平衡的类型可以通过检查新节点 n、n 的父亲 p、p 的父亲 g 来确定。我们知道特征②被破坏意味着 n 和 p 一定都为红色节点。因为 p 是红色节点,所以它一定不是根节点。所以 g 节点必然存在,且为黑色。当 p 是 g 的左孩子,n 是 p 的左孩子且 g 的另一个孩子是黑色时,我们称该不平衡是LLb型。其它的不平衡类型还包括LLr、LRb、LRr、RLb、RLr、RRb、RRr。
(1)XYr类型不平衡
XYr类型的不平衡可以通过改变节点颜色来处理。以LLr为例:我们需要将节点 p 的颜色变为黑色,将其兄弟节点的颜色变为红色;如果 g 不是根节点,我们还需要将其染为红色。这样以后,在以 g 节点为根的子树中,g 节点到外部节点的黑色节点数量没有发生变化。因此我们在保证了特征③的条件下,复原了 n、p 导致的不平衡。染色方式如下图:

不难看出,当 g 不是根节点时,由于其颜色会变为红色,这将可能引起新的双红缺陷。因此,以上的过程需要递归进行,直到到达根节点或者祖父节点的颜色变化不再引起失衡。
(2)XYb类型不平衡
XYb类型的不平衡可以通过旋转来处理。这种旋转的方式和AVL树类似,只是需要增加染色的处理。在旋转之后,需要将该子树新的根节点染为黑色,n、p 、g 中的另外两个节点染为红色。以LLb和LRb为例:


我们可以看出,整个旋转和染色过程,子树的根节点颜色没有变化,子树的根节点到该子树的所有外部节点的黑色节点数量没有发生变化。因此,这种变化不会引起祖先节点的失衡。
3、红黑树的删除
类似地,我们先使用普通二叉树的删除操作删除一个节点 n,然后修复其带来的不平衡。如果我们删除的是一个红色节点,显而易见,没有任何特征会被破坏,因此我们不需要做任何操作。如果我们删除的是一个黑色节点,且其后继节点 s 也为黑色,则会导致不平衡。当 n 是其父节点 p 的右孩子时,我们称不平衡是R型的,否则是L型的。不难分析出,这种情况下 n 的兄弟节点 v 一定不是外部节点(否则 n 本身也应该是外部节点或者红节点才能保证在 p 处左右子树黑节点长度相同)。如果 v 是黑色节点,则不平衡是Rb或Lb型的;如果是红色节点,则是Rr或Lr型的。下面我们以R型为例讨论各种情况。
(1)Rb型
根据 v 的子节点和 p 的颜色,我们可以将Rb型不平衡分为三种情况:
① v 至少有一个红孩子。这种情况我们可以通过3+4重构来解决。重构所用的节点为 p、v、v 的任一红孩子节点(白色节点既可能为黑色,也可能为红色):

② v 没有红孩子节点,p 为红色。此时我们不需要旋转,只需进行染色:

③ v 没有红孩子节点,p 为黑色。同样需要染色,不同的是,此时 p 子树的黑色节点深度会发生变化,进而可能会导致其祖先节点失衡。因此,此过程需要递归处理:

(2)Rr型
Rr型的不平衡可以通过一次旋转转化为Rb型不平衡,进而借助Rb型不平衡的方法解决问题。v 及其左孩子是满足黑色节点深度要求的,现在的失衡转化为 v 的右子树 p 的失衡,而其为Rb型失衡:

三、实现
1、AVL问题修改
(1)BST 修改
插入节点后应该将之返回用于重平衡处理:
template<typename Key, typename Value>
inline auto BinarySearchTree<Key, Value>::insertElementWithParent(const ValueType& element, NodeType* parentNode) -> NodeType*
{
auto newNode = new NodeType(element, nullptr, nullptr, parentNode);
...
return newNode;
}
(2)AVL 修改
template<typename Key, typename Value>
inline void AVL<Key, Value>::insert(const ValueType& element)
{
auto node = this->findNode(element.first);
...
node = this-&g
最低0.47元/天 解锁文章
423

被折叠的 条评论
为什么被折叠?



