红黑树

红黑树是一种很有意思的平衡检索树。它的统计性能要好于平衡二叉树(有些书籍根据作者姓名,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日修订


--------------------------------------------
More from http://www.cyut.edu.tw/~ckhung/b/al/bst.shtml

  1. 紅黑樹是另一種 balanced tree. 定義如下:
  2. 它基本上是一棵 binary search tree, 但每個 node 還漆上了顏色 -- 可以是紅色或黑色.
  3. 所有的 leaves 都是空的 (不存資料), 且都是黑色的.
  4. 紅色的 node 必定有兩個 children, 且兩個 children 都是黑色的.
  5. 從一個 node X 往下走, 每一條通往 leaf 的路徑上遇到的黑色 nodes 都一樣多. 這個固定的數目叫做 X 的 black-height.
  1. 直覺解釋: 希望定義出一棵 (黑色的) full binary tree (每個 leaf 所在的深度都一樣), 但這個條件顯然太嚴格, 無法達成 (nodes 數目只能是 2^k - 1). 所以允許有紅色的 nodes 穿插其間. 又, 紅色的 nodes 不可以太多, 以免讓樹太不平衡.
  2. n 個 internal nodes 的紅黑樹 (可以存放 n 筆資料), 它的高度為 Theta(lg n), 所以它是一棵 balanced tree.
  3. 查詢資料很簡單; 刪除資料比較麻煩, 但原則與新增相同.
  4. 新增或刪除資料時需要用到的一個基本動作: rotation.
                    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 為軸, 向左/右旋轉".
  5. 新增資料時如何保持 red-black tree 的特性? 先當做一般的 BST, 一路往下找到新資料 x 的落點, 並把 x 塗上紅色, 然後一路往上整修樹:
  6. 如果 x 的父親是黑色的, 就太完美了, 完全不需要整修. 結束.
  7. 不然的話, x 的父親既是紅色, x 的祖父一定是黑色的. 令 y 表 x 的叔叔. 有以下三種狀況要考慮:
    1. y 也是紅色的: 讓祖/父兩輩「紅黑變色」. 現在 x 是沒有問題了, 但祖父可能變成紅色, 因而產生問題. 所以讓祖父扮演 x 的角色, 繼續往上整修.
    2. y 是黑色的, x 到祖父的路上沒有轉彎: 以「祖父-父親」為軸旋轉, 並調整顏色.
    3. y 是黑色的, x 到祖父的路上有轉彎: 先以「父親-x」為軸旋轉, 變成上個狀況, 再依上個狀況處理.
  8. 注意: 第二/三種狀況表示祖父以下還可以容納新的 red node, 所以不需要再往上整修.
  9. 結論: 不論是增刪查改, 所需時間皆為 O(lg (n)).

你可能感兴趣的:(技术,tree,search,dictionary,reference,算法,applet)