红黑树是一种很有意思的平衡检索树。它的统计性能要好于平衡二叉树(有些书籍根据作者姓名,Adelson-Velskii和Landis,将其称为AVL-树),因此,红黑树在很多地方都有应用。在C++ STL中,很多部分(目前包括set, multiset, map, multimap)应用了红黑树的变体(SGI STL中的红黑树有一些变化,这些修改提供了更好的性能,以及对set操作的支持)。
红黑树的定义如下:
|
之所以称为红黑树的原因就在于它的每个结点都被“着色”为红色或黑色。这些结点颜色被用来检测树的平衡性。但需要注意的是,红黑树并不是平衡二叉树,恰恰相反,红黑树放松了平衡二叉树的某些要求,由于一定限度的“不平衡”,红黑树的性能得到了提升。
从根结点到叶结点的黑色结点数被称为树的“黑色高度”(black-height)。前面关于红黑树的性质保证了从根结点到叶结点的路径长度不会超过任何其他路径的两倍。
我们来解释一下这个结论。考虑一棵黑色高度为3的红黑树:从根结点到叶结点的最短路径长度显然是2(黑-黑-黑),最长路径为4(黑-红-黑-红-黑)。由于性质4,不可能在最长路经中加入更多的黑色结点,此外根据性质3,红色结点的子结点必须是黑色的,因此在同一简单路径中不允许有两个连续的红色结点。综上,我们能够建立的最长路经将是一个红黑交替的路径。
由此我们可以得出结论:对于给定的黑色高度为n的红黑树,从根到叶结点的简单路径的最短长度为n-1,最大长度为2(n-1)。
插入和删除操作中,结点可能被旋转以保持树的平衡。红黑树的平均和最差搜索时间都是O(log2 n)。Cormen [2001]给出了对于这一结论的证明。在实际应用中,红黑树的统计性能要好于平衡二叉树,但极端性能略差。
红黑树中结点的插入过程
插入结点的过程是:
|
注意 空结点和NULL指针是不同的。在简单的实现中,可以使用作为“监视哨”,标记为黑色的公共结点作为前面提到的空结点。
给一个红色结点加入两个空的子结点符合性质4,同时,也必须确保红色结点的两个子结点都是黑色的(根据性质3)。尽管如此,当新结点的父结点时红色时,插入红色的子结点将是违反定义的。这时存在两种情况。
红色父结点的兄弟结点也是红色的
例如下面的情况(X是希望插入的结点,本例来参考了这篇文章)
简单地对上级结点重新着色将解决冲突。当结点B被重新着色之后,应该重新检验更大范围内树结点的颜色,以确保整棵树符合定义的要求。结束时根结点应当是黑色的,如果它原先是红色的,则红黑树树的黑色高度将递增1。
红色父结点的兄弟结点是黑色的
这种情形比较复杂,如下图:
重新对结点着色将把结点A变成黑色,于是,树的平衡将被破坏,因为左子树的黑色高度将增加,而右子树的黑色高度没有相应地改变。如果我们把结点B着上红色,那么左右子树的高度都将减少,树依然不平衡。此时,继续对结点C进行着色将导致更糟糕的情况,左子树黑色高度增加,右子树黑色高度减少。为了解决问题,需要旋转并对树结点进行重新着色。这时算法将正常结束,因为子树的根结点(A)被着色为黑色,同时,不会引入新的红-红冲突。
结束插入过程
插入结点时,可能会需要重新着色,或者旋转,来保持红黑树的性质。如果旋转完成,那么算法就结束了。对于重新着色来说,我们会在子树的根留下一个红色结点,于是需要继续向上对树进行修整,以保持红黑树的性质。最坏情况下,我们将不得不对到树根的所有路径进行处理。插入的时间复杂度为O(log2 n)。删除结点的时间复杂度与此类似。
结点的删除
红黑树的结点删除情况要比插入复杂一些。我们可以把实际的删除操作分成3种情况(先不讨论颜色),其中被删除的结点用紫色标记,蓝色表示任意颜色的结点,可能是红色,也可能是黑色:
情况a: 被删除的结点没有子结点(两个子结点都是空结点)
原先属于X的空结点被A“继承”。如果被删除结点是黑色结点,则可能造成树的黑色高度变化。
情况b: 有一个子结点
B结点取代原X结点的位置。如果被删除的结点是黑色结点,则可能造成树的黑色高度发生变化;如果B是红色结点,还可能需要重新着色。
情况c: 有两个子结点
这种情形比较复杂。需要将X和它的左子树中的键值最大的结点进行交换。这通常会导致重新着色,对树的黑色高度的改变,以及随之而来的旋转。
需要说明的是,仅仅删除结点是不够的,因为此后很可能还需要对树进行重新着色。如果删除的是红色结点,那么没有关系,因为这不会影响树的黑色高度;而如果删除的是黑色结点,事情就没那么简单了。需要把受到影响(移动或交换)的结点标记为黑色,如果它原来已经是黑色的,那么需要标记为“双黑”(双黑,或double-black是许多英文资料中提及的一个概念。简单地说,标记为“双黑”意味着需要对周围的红色结点进行“抹黑”处理)。
包含“双黑”结点显然不符合红黑树的要求,因此必须消除这种情况。出现“双黑”的情况可以分为4种:
1、双黑结点的兄弟结点是红色的
2、双黑结点的兄弟是黑色的,并且它的兄弟有两个黑色的子结点
3、双黑结点的兄弟是黑色的,并且,它的兄弟的左、右子结点分别是红色和黑色
4、双黑结点的兄弟是黑色的,并且,它的兄弟的右子结点是红色的
很显然,上述四种情况包括了可能的所有状况。
处理双黑结点的基本思想是进行“色彩补偿”。换言之,将邻近的红色结点变为黑色,同时,此双黑结点也“还原”为黑色。
总结
红黑树引入了“颜色”的概念。引入“颜色”的目的在于使得红黑树的平衡条件得以简化。正如著名的密码学专家Bruce Schneier所说的那样,“Being Partly balanced can be good enough”,红黑树并不追求“完全平衡”——它只要求部分地达到平衡要求,降低了对旋转的要求,从而提高了性能。
红黑树能够以O(log2 n)的时间复杂度进行搜索、插入、删除操作。此外,由于它的设计,任何不平衡都会在三次旋转之内解决。当然,还有一些更好的,但实现起来更复杂的数据结构能够做到一步旋转之内达到平衡,但红黑树能够给我们一个比较“便宜”的解决方案。红黑树的算法时间复杂度和AVL相同,但统计性能比AVL树更高。
当然,红黑树并不适应所有应用树的领域。如果数据基本上是静态的,那么让他们待在他们能够插入,并且不影响平衡的地方会具有更好的性能。如果数据完全是静态的,例如,做一个哈希表,性能可能会更好一些。
在实际的系统中,例如,需要使用动态规则的防火墙系统,使用红黑树而不是散列表被实践证明具有更好的伸缩性。
参考文献
AVL Search Tree - Introduction and ANSI C Reference Implementation, http://www.purists.org/georg/avltree/
AVL Tree applet[source code], http://www.seanet.com/users/arsen/avltree.html
Introduction to Algorithms (MIT Electrical Engineering and Computer Science), by Thomas H. Cormen (Author), et al, 2001. http://www.amazon.com/exec/obidos/tg/detail/-/0262031418/ref=ase_none01A/103-4588028-6419015?v=glance&s=books
NIST Dictionary of Algorithms and Data Structures, http://www.nist.gov/dads/
Red-Black Trees, http://epaperpress.com/sortsearch/rbt.html
司徒彦南 2002年8月16日 第一版
CST 2002年8月17日 10:00:00 第一次发布
司徒彦南 2002年8月19日修订
y x / / / / x C <==> A y / / / / A B B C圖中 x 與 y 為 nodes; A, B, C 為一整串的 subtrees 注意: 這個動作很小心地保持 binary search tree 的特性 -- 旋轉前後都滿足: (A 內的所有元素) <= x <= (B 內的所有元素) <= y <= (C 內的所有元素). 姑且將這個動作稱為 "以 x-y 為軸, 向左/右旋轉".