轉自: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
根據以上描述,紅黑樹定義以下:編程
紅黑樹是一種具備紅色和黑色連接的平衡查找樹,同時知足:數據結構
下圖能夠看到紅黑樹實際上是2-3樹的另一種表現形式:若是咱們將紅色的連線水平繪製,那麼他連接的兩個2-node節點就是2-3樹中的一個3-node節點了。dom
咱們能夠在二叉查找樹的每個節點上增長一個新的表示顏色的標記。該標記指示該節點指向的節點的顏色。編程語言
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; }
紅黑樹是一種特殊的二叉查找樹,他的查找方法也和二叉查找樹同樣,不須要作太多更改。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); }
在介紹插入以前,咱們先介紹如何讓紅黑樹保持平衡,由於通常的,咱們插入完成以後,須要對樹進行平衡化操做以使其知足平衡化。動畫
旋轉又分爲左旋和右旋。一般左旋操做用於將一個向右傾斜的紅色連接旋轉爲向左連接。對比操做先後,能夠看出,該操做其實是將紅線連接的兩個節點中的一個較大的節點移動到根節點上。
左旋操做以下圖:
//左旋轉
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; }
左旋的動畫效果以下:
右旋是左旋的逆操做,過程以下:
代碼以下:
//右旋轉
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; }
右旋的動畫效果以下:
顏色反轉
當出現一個臨時的4-node的時候,即一個節點的兩個子節點均爲紅色,以下圖:
這實際上是個A,E,S 4-node鏈接,操做相似於2-3樹,咱們須要將E提高至父節點,操做方法很簡單,就是把E對子節點的連線設置爲黑色,本身的顏色設置爲紅色。
有了以上基本操做方法以後,咱們如今對比以前對2-3樹的平衡操做和對紅黑樹進行平衡操做,這二者是能夠一一對應的,以下圖:
如今來討論各類狀況:
Case 1 往一個2-node節點底部插入新的節點
先熱身一下,首先咱們看對於只有一個節點的紅黑樹,插入一個新的節點的操做:
這種狀況很簡單,只須要:
Case 2往一個3-node節點底部插入新的節點
先熱身一下,假設咱們往一個只有兩個節點的樹中插入元素,以下圖,根據待插入元素與已有元素的大小,又能夠分爲以下三種狀況:
有了以上基礎,咱們如今來總結一下往一個3-node節點底部插入新的節點的操做步驟,下面是一個典型的操做過程圖:
能夠看出,操做步驟以下:
通過上面的平衡化討論,如今就來實現插入操做,通常地插入操做就是先執行標準的二叉查找樹插入,而後再進行平衡化。對照2-3樹,咱們能夠經過前面討論的,左旋,右旋,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查找樹的分析,紅黑樹可以保證符號表的全部操做即便在最壞的狀況下都能保證對數的時間複雜度,也就是樹的高度。
在分析以前,爲了更加直觀,下面是以升序,降序和隨機構建一顆紅黑樹的動畫:
從上面三張動畫效果中,能夠很直觀的看出,紅黑樹在各類狀況下都能維護良好的平衡性,從而可以保證最差狀況下的查找,插入效率。
下面來詳細分析下紅黑樹的效率:
最壞的狀況就是,紅黑樹中除了最左側路徑所有是由3-node節點組成,即紅黑相間的路徑長度是全黑路徑長度的2倍。
下圖是一個典型的紅黑樹,從中能夠看到最長的路徑(紅黑相間的路徑)是最短路徑的2倍:
下圖是紅黑樹在各類狀況下的時間複雜度,能夠看出紅黑樹是2-3查找樹的一種實現,他能保證最壞狀況下仍然具備對數的時間複雜度。
下圖是紅黑樹各類操做的時間複雜度。
紅黑樹這種數據結構應用十分普遍,在多種編程語言中被用做符號表的實現,如:
下面以.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節點問題。紅黑樹是一種比較高效的平衡查找樹,應用很是普遍,不少編程語言的內部實現都或多或少的採用了紅黑樹。