Java數據結構和算法 - TreeMap源碼理解紅黑樹

前言

本篇將結合JDK1.6的TreeMap源碼,來一塊兒探索紅-黑樹的奧祕。紅黑樹是解決二叉搜索樹的非平衡問題。html

當插入(或者刪除)一個新節點時,爲了使樹保持平衡,必須遵循必定的規則,這個規則就是紅-黑規則: 
1) 每一個節點不是紅色的就是黑色的 
2) 根老是黑色的 
3) 若是節點是紅色的,則它的子節點必須是黑色的(反之倒不必定必須爲真) 
4) 從跟到葉節點或者空子節點的每條路徑,必須包含相同數目的黑色節點node

插入一個新節點

紅-黑樹的插入過程和普通的二叉搜索樹基本一致:從跟朝插入點位置走,在每一個節點處經過比較節點的關鍵字相對大小來決定向左走仍是向右走。算法

 1 public V put(K key, V value) {
 2     Entry<K,V> t = root;
 3     int cmp;
 4     Entry<K,V> parent;
 5     Comparable<? super K> k = (Comparable<? super K>) key;
 6     do {
 7         parent = t;
 8         cmp = k.compareTo(t.key);
 9         if (cmp < 0) {
10             t = t.left;
11         } else if (cmp > 0) {
12             t = t.right; 
13         } else {
14             // 注意,return退出方法   
15             return t.setValue(value);  
16         }
17     } while (t != null);
18     Entry<K,V> e = new Entry<K,V>(key, value, parent);
19     if (cmp < 0) {
20         parent.left = e;
21     } else {
22         parent.right = e;
23     }
24     fixAfterInsertion(e);
25     size++;
26     modCount++;
27     return null;
28 }

可是,在紅-黑樹種,找到插入點更復雜,由於有顏色變換和旋轉。fixAfterInsertion()方法就是處理顏色變換和旋轉,需重點掌握它是如何保持樹的平衡(use rotations and the color rules to maintain the tree’s balance)。數據結構

下面的討論中,使用X、P、G表示關聯的節點。X表示一個特殊的節點, P是X的父,G是P的父。less

X is a node that has caused a rule violation. (Sometimes X refers to a newly inserted node, and sometimes to the child node when a parent and child have a redred conflict.)eclipse

On the way down the tree to find the insertion point, you perform a color flip whenever you find a black node with two red children (a violation of Rule 2). Sometimes the flip causes a red-red conflict (a violation of Rule 3). Call the red child X and the red parent P. The conflict can be fixed with a single rotation or a double rotation, depending on whether X is an outside or inside grandchild of G. Following color flips and rotations, you continue down to the insertion point and insert the new node.ide

After you’ve inserted the new node X, if P is black, you simply attach the new red node. If P is red, there are two possibilities: X can be an outside or inside grandchild of G. If X is an outside grandchild, you perform one rotation, and if it’s an inside grandchild, you perform two. This restores the tree to a balanced state.spa

按照上面的解釋,討論可分爲3個部分,按複雜程度排列,分別是: 
1) 在下行路途中的顏色變換(Color flips on the way down) 
2) 插入節點以後的旋轉(Rotations after the node is inserted) 
3) 在向下路途上的旋轉(Rotations on the way down).net

在下行路途中的顏色變換(Color flips on the way down)

Here’s the rule: Every time the insertion routine encounters a black node that has two red children, it must change the children to black and the parent to red (unless the parent is the root, which always remains black)debug

The flip leaves unchanged the number of black nodes on the path from the root on down through P to the leaf or null nodes.

儘管顏色變換不會違背規則4,可是可能會違背規則3。若是P的父是黑色的,則P由黑色變成紅色時不會有任何問題,可是,若是P的父是紅色的,那麼在P的顏色變化以後,就有兩個紅色節點相鏈接了。這個問題須要在繼續向下沿着路徑插入新節點以前解決,能夠經過旋轉修正這個問題,下文將會看到。

插入節點以後的旋轉(Rotations after the node is inserted)

新節點在插入以前,樹是符合紅-黑規則,在插入新節點以後,樹就不平衡了,此時須要經過旋轉來調整樹的平衡,使之從新符合紅-黑規則。

可能性1:P是黑色的,就什麼事情也不用作。插入便可。

可能性2:P是紅色,X是G的一個外側子孫節點,則須要一次旋轉和一些顏色的變化。 
以插入50,25,75,12,6爲例,注意節點6是一個外側子孫節點,它和它的父節點都是紅色。 

在這個例子中,X是一個外側子孫節點並且是左子節點,X是外側子孫節點且爲右子節點,是一種與此對稱的狀況。經過用50,25,75,87,93建立樹,同理再畫一畫圖,這裏就省略了。

可能性3:P是紅色,X是G的一個內側子孫節點,則須要兩次旋轉和一些顏色的改變。 
以插入50,25,75,12,18爲例,注意節點18是一個內側子孫節點,它和它的父節點都是紅色。 

在向下路途上的旋轉(Rotations on the way down)

在插入新節點以前,實際上樹已經違背了紅-黑規則,因此須要插入新節點以前作調整。因此咱們本次討論的主題是「在向下路途準備插入新節點時,上面先進行調整,使上面成爲標準的紅黑樹後,再進行新節點插入」。

外側子孫節點

以插入50,25,75,12,37,6,18,3爲例,例子中違背規則的節點是一個外側子孫節點。 

內側子孫節點

以插入50,25,75,12,37,31,43爲例,例子中違背規則的節點是一個內側子孫節點。

紅-黑樹的效率

和通常的二叉搜索樹相似,紅-黑樹的查找、插入和刪除的時間複雜度爲O(log2N)。

紅-黑樹的查找時間和普通的二叉搜索樹的查找時間應該幾乎徹底同樣。由於在查找過程當中並沒用到紅-黑特徵。額外的開銷只是每一個節點的存儲空間都稍微增長了一點,來存儲紅黑顏色(一個boolean變量)。

final Entry<K, V> getEntry(Object key) {
    Comparable <? super K > k = (Comparable <? super K > ) key;
    Entry<K, V> p = root;
    while (p != null) {
        int cmp = k.compareTo(p.key);
        if (cmp < 0) {
            p = p.left;
        } else if (cmp > 0) {
            p = p.right;
        } else  {
            return p;
        }
    }
    return null;
}

插入和刪除的時間要增長一個常數因子,由於不得不在下行的路徑上和插入點執行顏色變換和旋轉。平均起來一次插入大約須要一次旋轉。

由於在大多數應用中,查找的次數比插入和刪除的次數多,因此應用紅-黑樹取代普通的二叉搜索樹整體上不會增長太多的時間開銷。

參考資料

  1. eclipse如何debug調試jdk源碼
  2. 淺談算法和數據結構: 九 平衡查找樹之紅黑樹
相關文章
相關標籤/搜索