摘要
本节在前两节(树的旋转,2-3-4树)的基础上,讨论红黑树的性质及实现。
初识红黑树
简介
通过对2-3-4树的分析,我们认识到其直接实现比较复杂,时间开销可能会比普通BST更大。因而我们通过对普通BST增加一些信息,实现2-3-4树。这里采用的一种高效的方法,就是红黑树。基本思想是在每个结点中添加一个额外的颜色信息用来表示3-和4-结点。我们将链接看成两种不同的类型:红链接用于绑定3-和4-结点组成的小二叉树,黑链接用于绑定2-/3-/4-结点组成二叉树。如图:3-结点表示为由一个红链接连接的两个2-结点,4-结点表示为由两个红链接连接的三个2-结点。
在任何树中,每个节点(除了根节点)都有一个链接指向它,因而对节点着色等价于对链接着色。我们注意到:3-节点有两种表示形式,这个留给红黑树作为宽容度,根据情况选择合适的3-节点表示法。虽然我们可以使得3-结点向同一个方向偏斜,但没有充分理由支持我们这样做。
如果消除红黑树中的红链接,并合并被捆绑的结点,我们就可以得到一棵2-3-4树,如图:
可以看到,红黑树非常出色地表达出2-3-4树的结构。通过合并红节点,将得到相同的2-3-4树。
因而我们得到了红黑树的基本性质:1)BST的标准搜索过程不需修改就可以直接使用;2)红黑树直接与2-3-4树对应,因而只要维护这种对应关系,就可以实现平衡2-3-4树算法。
通过红黑树,我们得到了两种结构的优点:标准BST的简单搜索过程和2-3-4树简单的插入-再平衡过程。应当注意到,搜索过程不会检查结点颜色,因而平衡机制不会增加基本搜索的时间开销。
出于完备性考虑,我们抛开2-3-4树结构,再来分析红黑树,可以得到如下性质(CLRS p163):
1)每个结点非红即黑;
2)根节点为黑;
3)所有NULL结点(叶节点)为黑;
4)红结点的两个子节点均为黑;
5)对于每个结点,从该节点到其子孙结点所有路径上,包含的黑结点数目相同。
引理:一棵具有n个内节点的红黑树的高度至多为2lg(n+1)。
2-3-4树节点分裂与红黑树的表示
见图就行了,没什么好讲的:
红黑树的插入
先来看之前2-3-4树插入序列在红黑树中的表现,可以看到插入完成后我们得到了与2-3-4树一样的结果。
红黑树的插入是复杂的。插入例程包括两个部分:首先将结点按照普通BST插入过程插入到红黑树中,然后调用另外一个维护过程来维护被破坏的红黑树性质。需要注意的是插入时,我们默认将新节点作为红色结点插入。
接下来就要分析哪些红黑树的性质可能被破坏。根据上文列出的五点性质,显而易见性质1)和3)不会被破坏。性质5)也不会破坏,因为我们插入的是红色结点,不会影响黑高度。因而被破坏的性质只有2)根节点为黑色,和4)不能有两个连续的红结点。首先我们给出红黑树插入例程的代码,然后针对代码进行分析:
/*红黑树插入代码*/
代码1~15行的while循环维持下列循环不变式,通过证明不变式,我们便可以证明红黑树性质得以维护:在循环的每一次迭代开始时:
a) 结点z是红色;
b) 如果z->parent是根,则z->parent为黑色;
c) 如果有红黑树性质被破坏,至多只有一个被破坏,并且只有可能是2)或者4)。如果违反性质2),则因为z是根并且z是红色;如果违反性质4),则因为z和z->parent都是红色。
在这段不变式中,我们注意到c)部分是用来处理和维护红黑树性质,是不变式的关键。a)和b)提供了有意义的帮助信息。证明不变式的意义在于它能向我们需要的结果逐步推进。在这里,每次迭代会有两种可能的结果:指针z沿树上移,或者执行旋转后循环结束。
初始化:第一次迭代开始前,我们有一棵正确的红黑树,新增红色结点z。因而调用维护过程时,
a) z就是新增结点,为红色;
b) 如果z的父节点是根,则必定是黑色;
c) 红黑树性质1) 3) 5)必定保持。如果性质2)被破坏,则新增结点z为根,由于只有当树为空时z才会被插入至根,因而性质4)不会被破坏;如果性质4)被破坏,则只有可能是z和其父节点为红色,此时根结点必定为黑色。因而也只破坏性质4。
终止:循环结束是因为z->parent为黑,所以循环结束时性质4)不会被破坏。唯一可能被破坏的性质2),所以当循环结束后会设置根为黑色维护性质。因而所有红黑性质都得了维护。
保持:循环过程中共计有6种情况,除去镜像有3种情况,由第2行确定z的父节点z->parent是其祖父节点z->parent->parent的左孩子还是右孩子而定。可以证明z的祖父节点存在,因为根据不变式,如果z的父节点是根部,则为黑色,而只有父节点为红色才进入循环,因此z的父节点不可能是根,所以z的祖父节点存在。
三种情况的区别在于z的父亲的兄弟(叔叔)结点颜色而有所不同。第3行使y指向z的叔叔结点,即z->parent->parent->right,并在第四行测试叔叔的颜色。y为红色进入情况1,否则执行情况2或3。在所有情况中,z的祖父都一定是黑色的。因为z的父亲是红色,根据性质,祖父节点不能为红。
情况1):z的叔叔y为红色。
只有z->parent和y都是红色结点时,才会执行情况1)。换句话说,当我们将新节点z,连接到一个4-节点时,执行情况1)。因而我们将4-结点分裂处理该问题。z指针上移两层,进入下一轮循环。如图:
我们需要通过证明循环不变式来证明这一步的正确性:
设z为当前迭代中的结点z,z' = z->parent->parent表示下一次迭代时新的z。
a) 因为这次迭代分裂了4-结点,z'一定是一个3-或4-结点,所以z'一定是红色。
b) 此次操作没有修改根节点颜色,因而会继续保持黑色。
c) 显然性质1) 3) 5)得以保持,并且此次操作维护了性质4)。下一轮迭代时,如果z'为根,则有且仅有性质2)被破坏,如果z'不为根,则当z'->parent为红色时,有且仅有性质4)被破坏。
情况2):z的叔叔y是黑色,z是右子节点。
情况3):z的叔叔y是黑色,z是左子结点。
这两种情况下,相当于将新节点插入到一个3-结点上。这个行为本没有问题,不会破坏2-3-4树性质,但根据规定4-结点为一个黑结点左右用红链接捆绑两个结点,因而这是一个修复4-结点表达形式的过程。情况2可以通过一次单旋转,转换到情况3,进而进行维护。如图:
因为这个过程并不存在节点分裂,因而完成此步维护后退出循环,整棵红黑树已经维护完毕。
接下来证明循环不变式,为这一步提供更坚实的理论基础:
a) 情况2让z指向红色的z->parent,在情况2) 3)中,z的颜色不再改变。
b) 情况3)令z->parent变成黑色,如果下一次迭代时z->parent为根,则它是黑色的。
c) 性质1) 3) 5)不受影响。在这两种情况中,z都不是根,根颜色不受影响,性质2)不被破坏。并且性质4)在此过程中得以维护。
于是我们证明了在所有情况下循环不变式都会得以保持,因而也证明了这一过程能够正确维护红黑树性质。
运行时间分析:我们选择红黑树的目的就在于通过简单的方式实现2-3-4树,因而有必要分析红黑树插入时间。跟普通BST一样,含n个结点的红黑树高度为lgn,因而插入过程耗时O(lgn),在维护过程中,如果一路全是4-结点,那么就需要一直分裂到根结点,while执行的总次数就可能是O(lgn)。所以总耗时O(lgn)。值得一提的是,红黑树最多进行两次旋转,因为只有情况2)3)会需要旋转,而旋转完毕后,就退出循环完成维护了。
红黑树中删除结点
和其他操作一样,对一个节点删除需要O(lgn)的时间。与插入操作相比,删除操作极为复杂,一般情况下推荐懒惰删除。
Todo Here.
/*红黑树删除代码*/