紅黑樹

轉自:http://www.cnblogs.com/yangecnu/p/Introduce-2-3-Search-Tree.htmlhtml

 

前面一篇文章介紹了2-3查找樹,能夠看到,2-3查找樹能保證在插入元素以後能保持樹的平衡狀態,最壞狀況下即全部的子節點都是2-node,樹的高度爲lgN,從而保證了最壞狀況下的時間複雜度。可是2-3樹實現起來比較複雜,本文介紹一種簡單實現2-3樹的數據結構,即紅黑樹(Red-Black Tree)java

定義

紅黑樹的主要是想對2-3查找樹進行編碼,尤爲是對2-3查找樹中的3-nodes節點添加額外的信息。紅黑樹中將節點之間的連接分爲兩種不一樣類型,紅色連接,他用來連接兩個2-nodes節點來表示一個3-nodes節點。黑色連接用來連接普通的2-3節點。特別的,使用紅色連接的兩個2-nodes來表示一個3-nodes節點,而且向左傾斜,即一個2-node是另外一個2-node的左子節點。這種作法的好處是查找的時候不用作任何修改,和普通的二叉查找樹相同。node

Red black tree

根據以上描述,紅黑樹定義以下:編程

紅黑樹是一種具備紅色和黑色連接的平衡查找樹,同時知足:數據結構

  • 紅色節點向左傾斜
  • 一個節點不可能有兩個紅色連接
  • 整個樹徹底黑色平衡,即從根節點到因此葉子結點的路徑上,黑色連接的個數都相同。

下圖能夠看到紅黑樹實際上是2-3樹的另一種表現形式:若是咱們將紅色的連線水平繪製,那麼他連接的兩個2-node節點就是2-3樹中的一個3-node節點了。dom

1-1 correspondence between 2-3 and LLRB

表示

咱們能夠在二叉查找樹的每個節點上增長一個新的表示顏色的標記。該標記指示該節點指向的節點的顏色。編程語言

private const bool RED = true; private const bool BLACK = false; private Node root; class Node { public Node Left { get; set; } public Node Right { get; set; } public TKey Key { get; set; } public TValue Value { get; set; } public int Number { get; set; } public bool Color { get; set; } public Node(TKey key, TValue value,int number, bool color) { this.Key = key; this.Value = value; this.Number = number; this.Color = color; } } private bool IsRed(Node node) { if (node == null) return false; return node.Color == RED; }

 Red black tree representation

實現

查找

紅黑樹是一種特殊的二叉查找樹,他的查找方法也和二叉查找樹同樣,不須要作太多更改。ide

可是因爲紅黑樹比通常的二叉查找樹具備更好的平衡,因此查找起來更快。工具

//查找獲取指定的值
public override TValue Get(TKey key) { return GetValue(root, key); } private TValue GetValue(Node node, TKey key) { if (node == null) return default(TValue); int cmp = key.CompareTo(node.Key); if (cmp == 0) return node.Value; else if (cmp > 0) return GetValue(node.Right, key); else return GetValue(node.Left, key); }

平衡化

在介紹插入以前,咱們先介紹如何讓紅黑樹保持平衡,由於通常的,咱們插入完成以後,須要對樹進行平衡化操做以使其知足平衡化。動畫

旋轉

旋轉又分爲左旋和右旋。一般左旋操做用於將一個向右傾斜的紅色連接旋轉爲向左連接。對比操做先後,能夠看出,該操做其實是將紅線連接的兩個節點中的一個較大的節點移動到根節點上。

左旋操做以下圖:

before left rotation after left rotation

//左旋轉
private Node RotateLeft(Node h) { Node x = h.Right; //將x的左節點複製給h右節點 h.Right = x.Left; //將h複製給x右節點 x.Left = h; x.Color = h.Color; h.Color = RED; return x; }

左旋的動畫效果以下:

rotate left in red black tree

右旋是左旋的逆操做,過程以下:

before right rotation after right rotation

代碼以下:

//右旋轉
private Node RotateRight(Node h) { Node x = h.Left; h.Left = x.Right; x.Right = h; x.Color = h.Color; h.Color = RED; return x; }

右旋的動畫效果以下:

rotate right the red black tree

顏色反轉

當出現一個臨時的4-node的時候,即一個節點的兩個子節點均爲紅色,以下圖:

before flip colors after flip colors

這實際上是個A,E,S 4-node鏈接,操做相似於2-3樹,咱們須要將E提高至父節點,操做方法很簡單,就是把E對子節點的連線設置爲黑色,本身的顏色設置爲紅色。

有了以上基本操做方法以後,咱們如今對比以前對2-3樹的平衡操做和對紅黑樹進行平衡操做,這二者是能夠一一對應的,以下圖:

RB tree 1-1 correspondence with 2-3 tree

如今來討論各類狀況:

Case 1 往一個2-node節點底部插入新的節點

先熱身一下,首先咱們看對於只有一個節點的紅黑樹,插入一個新的節點的操做:

Insert into a tree with only 1 node

這種狀況很簡單,只須要:

  • 標準的二叉查找樹遍歷便可。新插入的節點標記爲紅色
  • 若是新插入的節點在父節點的右子節點,則須要進行左旋操做

Case 2往一個3-node節點底部插入新的節點

先熱身一下,假設咱們往一個只有兩個節點的樹中插入元素,以下圖,根據待插入元素與已有元素的大小,又能夠分爲以下三種狀況:

Insert into a tree with only 2 nodes

  • 若是待插入的節點比現有的兩個節點都大,這種狀況最簡單。咱們只須要將新插入的節點鏈接到右邊子樹上便可,而後將中間的元素提高至根節點。這樣根節點的左右子樹都是紅色的節點了,咱們只須要調研FlipColor方法便可。其餘狀況通過反轉操做後都會和這同樣。
  • 若是插入的節點比最小的元素要小,那麼將新節點添加到最左側,這樣就有兩個鏈接紅色的節點了,這是對中間節點進行右旋操做,使中間結點成爲根節點。這是就轉換到了第一種狀況,這時候只須要再進行一次FlipColor操做便可。
  • 若是插入的節點的值位於兩個節點之間,那麼將新節點插入到左側節點的右子節點。由於該節點的右子節點是紅色的,因此須要進行左旋操做。操做完以後就變成第二種狀況了,再進行一次右旋,而後再調用FlipColor操做便可完成平衡操做。

有了以上基礎,咱們如今來總結一下往一個3-node節點底部插入新的節點的操做步驟,下面是一個典型的操做過程圖:

Insert into 3-node at the bottom

能夠看出,操做步驟以下:

  1. 執行標準的二叉查找樹插入操做,新插入的節點元素用紅色標識。
  2. 若是須要對4-node節點進行旋轉操做
  3. 若是須要,調用FlipColor方法將紅色節點提高
  4. 若是須要,左旋操做使紅色節點左傾
  5. 在有些狀況下,須要遞歸調用Case1 Case2,來進行遞歸操做。以下:

Insert into 3-node at the bottom case 2

代碼實現

通過上面的平衡化討論,如今就來實現插入操做,通常地插入操做就是先執行標準的二叉查找樹插入,而後再進行平衡化。對照2-3樹,咱們能夠經過前面討論的,左旋,右旋,FlipColor這三種操做來完成平衡化。

Passing a red link up a red-black BST

具體操做方式以下:

  • 若是節點的右子節點爲紅色,且左子節點位黑色,則進行左旋操做
  • 若是節點的左子節點爲紅色,而且左子節點的左子節點也爲紅色,則進行右旋操做
  • 若是節點的左右子節點均爲紅色,則執行FlipColor操做,提高中間結點。

根據這一邏輯,咱們就能夠實現插入的Put方法了。

public override void Put(TKey key, TValue value)
{
    root = Put(root, key, value);
    root.Color = BLACK;
}

private Node Put(Node h, TKey key, TValue value) { if (h == null) return new Node(key, value, 1, RED); int cmp = key.CompareTo(h.Key); if (cmp < 0) h.Left = Put(h.Left, key, value); else if (cmp > 0) h.Right = Put(h.Right, key, value); else h.Value = value; //平衡化操做 if (IsRed(h.Right) && !IsRed(h.Left)) h = RotateLeft(h); if (IsRed(h.Right) && IsRed(h.Left.Left)) h = RotateRight(h); if (IsRed(h.Left) && IsRed(h.Right)) h = FlipColor(h); h.Number = Size(h.Left) + Size(h.Right) + 1; return h; } private int Size(Node node) { if (node == null) return 0; return node.Number; }

分析

對紅黑樹的分析其實就是對2-3查找樹的分析,紅黑樹可以保證符號表的全部操做即便在最壞的狀況下都能保證對數的時間複雜度,也就是樹的高度。

在分析以前,爲了更加直觀,下面是以升序,降序和隨機構建一顆紅黑樹的動畫:

  • 以升序插入構建紅黑樹:

insert in ascending order rb tree

  • 以降序插入構建紅黑樹:

insert in descend order rb tree

  • 隨機插入構建紅黑樹

insert in random order rb tree

從上面三張動畫效果中,能夠很直觀的看出,紅黑樹在各類狀況下都能維護良好的平衡性,從而可以保證最差狀況下的查找,插入效率。

下面來詳細分析下紅黑樹的效率:

1. 在最壞的狀況下,紅黑樹的高度不超過2lgN

最壞的狀況就是,紅黑樹中除了最左側路徑所有是由3-node節點組成,即紅黑相間的路徑長度是全黑路徑長度的2

下圖是一個典型的紅黑樹,從中能夠看到最長的路徑(紅黑相間的路徑)是最短路徑的2倍:

a typic red black tree

2. 紅黑樹的平均高度大約爲lgN

下圖是紅黑樹在各類狀況下的時間複雜度,能夠看出紅黑樹是2-3查找樹的一種實現,他能保證最壞狀況下仍然具備對數的時間複雜度。

下圖是紅黑樹各類操做的時間複雜度。

analysis of red black tree

應用

紅黑樹這種數據結構應用十分普遍,在多種編程語言中被用做符號表的實現,如:

  • Java中的java.util.TreeMap,java.util.TreeSet
  • C++ STL中的:map,set,multimap,multiset
  • .NET中的:SortedDictionary,SortedSet

下面以.NET中爲例,經過Reflector工具,咱們能夠看到SortedDictionary的Add方法以下:

public void Add(T item)
{
    if (this.root == null) { this.root = new Node<T>(item, false); this.count = 1; } else { Node<T> root = this.root; Node<T> node = null; Node<T> grandParent = null; Node<T> greatGrandParent = null; int num = 0; while (root != null) { num = this.comparer.Compare(item, root.Item); if (num == 0) { this.root.IsRed = false; ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_AddingDuplicate); } if (TreeSet<T>.Is4Node(root)) { TreeSet<T>.Split4Node(root); if (TreeSet<T>.IsRed(node)) { this.InsertionBalance(root, ref node, grandParent, greatGrandParent); } } greatGrandParent = grandParent; grandParent = node; node = root; root = (num < 0) ? root.Left : root.Right; } Node<T> current = new Node<T>(item); if (num > 0) { node.Right = current; } else { node.Left = current; } if (node.IsRed) { this.InsertionBalance(current, ref node, grandParent, greatGrandParent); } this.root.IsRed = false; this.count++; this.version++; } }

能夠看到,內部實現也是一個紅黑樹,其操做方法和本文將的大同小異,感興趣的話,您可使用Reflector工具跟進去查看源代碼。

總結

前文講解了自平衡查找樹中的2-3查找樹,這種數據結構在插入以後可以進行自平衡操做,從而保證了樹的高度在必定的範圍內進而可以保證最壞狀況下的時間複雜度。可是2-3查找樹實現起來比較困難,紅黑樹是2-3樹的一種簡單高效的實現,他巧妙地使用顏色標記來替代2-3樹中比較難處理的3-node節點問題。紅黑樹是一種比較高效的平衡查找樹,應用很是普遍,不少編程語言的內部實現都或多或少的採用了紅黑樹。

相關文章
相關標籤/搜索