TreeMap源碼解析 給jdk寫註釋系列之jdk1.6容器(7)

TreeMap是基於紅黑樹結構實現的一種Map,要分析TreeMap的實現首先就要對紅黑樹有所瞭解。
     要了解什麼是紅黑樹,就要了解它的存在主要是爲了解決什麼問題,對比其他數據結構比如數組,鏈表,Hash表等樹這種結構又有什麼優點。
 
1.二叉查詢樹、紅黑樹介紹
    
  以下爲個人理解,有誤請拍磚。。。
 
     下面我儘可能用通俗易懂的語言,簡單總結一下數組,鏈表,Hash表以及樹的優缺點。
     1.數組,優點:(1)隨機訪問效率高(根據下標查詢),(2)搜索效率較高(可使用折半方法)。缺點:(1)內存連續且固定,存儲效率低。(2)插入和刪除效率低(可能會進行數組拷貝或擴容)。
     2.鏈表,優點:(1)不要求連續內存,內存利用率高,(2)插入和刪除效率高(只需要改變指針指向)。缺點:(1)不支持隨機訪問,(2)搜索效率低(需要遍歷)。
     3.Hash表:優點:(1)搜索效率高,(2)插入和刪除效率較高,缺點:(1)內存利用率低(基於數組),(2)存在散列衝突。
     
     上面說的話比較囉嗦,再精煉一下:數組查詢好、插入和刪除差且浪費內存;鏈表插入和刪除好、查詢差;Hash表查詢好、插入和刪除也不錯但是浪費內存。
     也就是說,查詢好的插入和刪除就差,插入和刪除好的查詢就差,好不容易有一個查詢、插入和刪除都不錯的,但是卻又浪費內存。哎,好苦惱啊,怎麼辦呢,愁死我啦。能不能做到查詢、插入、刪除效率都很高,又不浪費內存呢。答案當然是不能!哎,還是好愁人,煩死啦。但是可以做到查詢、插入、刪除效率比較高,又不浪費內存。哇塞,這是什麼東東,這麼牛掰,這就是二叉查詢樹(又叫二叉排序樹,又叫二叉搜索樹)。你說二叉樹這麼好,那有什麼缺點嗎,有!就是算法複雜。
 
     那麼什麼是二叉查找樹呢,它又有哪些特點呢?(以下見於百度百科)
     (1)若左子樹不空,則左子樹上所有結點的值均小於它的根結點的值;
     (2)若右子樹不空,則右子樹上所有結點的值均大於它的根結點的值;
     (3)左、右子樹也分別爲二叉查找樹;
     (4)沒有鍵值相等的節點。
 
  按照二叉查找樹存儲的數據,對元素的搜索效率是非常高的,比如上圖中如果要查找值爲48的節點,只需要遍歷4個節點就能完成。理論上,一顆平衡的二叉查找樹的任意節點平均查找效率爲樹的高度h,即O(lgn)。但是如果二叉查找樹的失去平衡(元素全在一側),搜索效率就退化爲O(n),因此二叉查找樹的平衡是搜索效率的關鍵所在。而紅黑樹就是靠紅黑規則來維持二叉查找樹的平衡性
 
(一顆失去平衡的二叉樹)
 
  簡單用代碼描述上面的二叉查找樹,試試看:
複製代碼
  1 public class BinaryTree {
  2 
  3         // 二叉樹的根節點
  4         public TreeNode rootNode ;
  5         // 記錄搜索深度
  6         public int count;
  7 
  8         /**
  9         * 利用傳入一個數組來建立二叉樹
 10         */
 11         public BinaryTree(int[] data) {
 12                for (int i = 0; i < data. length; i++) {
 13                      addNodeToTree(data[i]);
 14               }
 15        }
 16 
 17         /**
 18         * 將指定的值加入到二叉樹中適當的節點
 19         */
 20         private void addNodeToTree(int value) {
 21               TreeNode currentNode = rootNode;
 22                // 建立樹根
 23                if (rootNode == null) {
 24                       rootNode = new TreeNode(value);
 25                       return;
 26               }
 27 
 28                // 建立二叉樹
 29                while (true) {
 30                       // 新增的value比節點的value小,則在左子樹
 31                       if (value < currentNode.value ) {
 32                             if (currentNode.leftNode == null) {
 33                                   currentNode. leftNode = new TreeNode(value);
 34                                    return;
 35                            } else {
 36                                   currentNode = currentNode. leftNode;
 37                            }
 38                      } else { // 新增的value比節點的value大,在右子樹
 39                             if (currentNode.rightNode == null) {
 40                                   currentNode. rightNode = new TreeNode(value);
 41                                    return;
 42                            } else {
 43                                   currentNode = currentNode. rightNode;
 44                            }
 45                      }
 46               }
 47        }
 48 
 49         /**
 50         * 中序遍歷(左子樹 -樹根- 右子樹)
 51         */
 52         public void inOrder(TreeNode node) {
 53                if (node != null) {
 54                      inOrder(node. leftNode);
 55                      System. out.print("[" + node.value + "]");
 56                      inOrder(node. rightNode);
 57               }
 58        }
 59 
 60         /**
 61         * 前序遍歷(樹根 -左子樹- 右子樹)
 62         */
 63         public void preOrder(TreeNode node) {
 64                if (node != null) {
 65                      System. out.print("[" + node.value + "]");
 66                      preOrder(node. leftNode);
 67                      preOrder(node. rightNode);
 68               }
 69        }
 70 
 71         /**
 72         * 後序遍歷(左子樹 -右子樹- 樹根)
 73         */
 74         public void postOrder(TreeNode node) {
 75                if (node != null) {
 76                      postOrder(node. leftNode);
 77                      postOrder(node. rightNode);
 78                      System. out.print("[" + node.value + "]");
 79               }
 80        }
 81 
 82         /**
 83         * 從二叉樹中查找指定value
 84         */
 85         public boolean findTree(TreeNode node, int value) {
 86                if (node == null) {
 87                      System. out.println("共搜索" + count + "次");
 88                       return false;
 89               } else if (node.value == value) {
 90                      System. out.println("共搜索" + count + "次");
 91                       return true;
 92               } else if (value < node.value) {
 93                       count++;
 94                       return findTree(node.leftNode , value);
 95               } else {
 96                       count++;
 97                       return findTree(node.rightNode , value);
 98               }
 99        }
100 
101         /**
102         * 利用中序遍歷進行排序
103         */
104         public void sort() {
105                this.inOrder(rootNode );
106        }
107 
108         class TreeNode {
109                int value ;
110               TreeNode leftNode;
111               TreeNode rightNode;
112 
113                public TreeNode(int value) {
114                       this.value = value;
115                       this.leftNode = null;
116                       this.rightNode = null;
117               }
118        }
119 
120         public static void main(String[] args) {
121                int[] content = { 50, 35, 27, 45, 40, 48, 78, 56, 90 };
122 
123               BinaryTree tree = new BinaryTree(content);
124               System. out.println("前序遍歷:" );
125               tree.preOrder(tree. rootNode);
126               System. out.println("\n中序遍歷:" );
127               tree.inOrder(tree. rootNode);
128               System. out.println("\n後序遍歷:" );
129               tree.postOrder(tree. rootNode);
130 
131               System. out.println("\n\n開始搜索:" );
132                boolean isFind = tree.findTree(tree.rootNode, 48);
133               System. out.println("是否搜索到" + 48 + ":" + isFind);
134               
135               System. out.println("\n進行排序:" );
136               tree.sort();
137        }
138 }
複製代碼
  
  看下運行結果:
複製代碼
 1 前序遍歷:
 2 [50][35][27][45][40][48][78][56][90]
 3 中序遍歷:
 4 [27][35][40][45][48][50][56][78][90]
 5 後序遍歷:
 6 [27][40][48][45][35][56][90][78][50]
 7 
 8 開始搜索:
 9 共搜索3次
10 是否搜索到48:true
11 
12 進行排序:
13 [27][35][40][45][48][50][56][78][90]
複製代碼

 

  通過上面代碼是不是對二叉查詢樹有所認識呢,那麼,紅黑樹的紅黑規則到底是什麼呢?
     (1)節點是紅色或黑色。
     (2)根節點是黑色。
     (3)每個葉節點(NIL節點,空節點)是黑色的。
     (4)每個紅色節點的兩個子節點都是黑色。(從每個葉子到根的所有路徑上不能有兩個連續的紅色節點)
     (5)從任一節點到其每個葉子的所有路徑都包含相同數目的黑色節點。
 
 
  上面的規則前4條都好理解,第5條規則到底是什麼情況,下面簡單解釋下,比如圖中紅8到1左邊的葉子節點的路徑包含兩個黑色節點,到6下面的葉子節點的路徑也包含兩個黑色節點。
 
     但是在在添加或刪除節點後,紅黑樹就發生了變化,可能不再滿足上面的5個特性,爲了保持紅黑樹的以上特性,就有了三個動作:左旋、右旋、着色。
 
     下面來看下什麼是紅黑樹的左旋和右旋:
  對x進行左旋,意味着"將x變成一個左節點"
 
  對y進行右旋,意味着"將y變成一個右節點"。
 
     如果還是沒看明白,下面找了兩張左旋和右旋的動態圖(http://www.cnblogs.com/yangecnu/p/Introduce-Red-Black-Tree.html):
 
  
 
  
  ok,對二叉樹、紅黑樹的概念有所瞭解後,我們來看下紅黑樹的兩個主要邏輯添加和刪除,看看TreeMap是怎麼實現的。
     
2.TreeMap的底層實現
 
     首先來看下TreeMap的定義:  
1 public class TreeMap<K,V>
2     extends AbstractMap<K,V>
3     implements NavigableMap<K,V>, Cloneable, java.io.Serializable
  
  可以看到TreeMap繼承了AbstractMap抽象類,並實現NavigableMap、Cloneable、Serializable接口。NavigableMap接口擴展了SortedMap,主要是提供了給定搜索目標返回最接近匹配項的導航方法。這個不是我們今天的重點,這裏不做分析了。
     
     下面再看下TreeMap的底層存儲相關定義:
  
複製代碼
 1     // 比較器
 2     private final Comparator<? super K> comparator;
 3 
 4     // 紅黑樹根節點
 5     private transient Entry<K,V> root = null;
 6 
 7     // 集合元素數量
 8     private transient int size = 0;
 9 
10     // "fail-fast"集合修改記錄
11     private transient int modCount = 0;
複製代碼

  這裏的Comparator是一個比較器,這裏不詳細講解,後面會單獨進行分析,這裏只要明白,一個類實現了Comparator接口並重寫其compare方法,就能進行比較大小。Entry是樹的節點類,我們來看一下Entry的定義:

 

複製代碼
 1 static final class Entry<K,V> implements Map.Entry<K,V> {
 2        K key;
 3         V value;
 4         // 左孩子節點
 5         Entry<K,V> left = null;
 6         // 右孩子節點
 7         Entry<K,V> right = null;
 8         // 父節點
 9         Entry<K,V> parent;
10         // 紅黑樹用來表示節點顏色的屬性,默認爲黑色
11         boolean color = BLACK;
12 
13         /**
14          * 用key,value和父節點構造一個Entry,默認爲黑色
15          */
16         Entry(K key, V value, Entry<K,V> parent) {
17             this.key = key;
18             this.value = value;
19             this.parent = parent;
20         }
21 
22         public K getKey() {
23             return key ;
24         }
25 
26         public V getValue() {
27             return value ;
28         }
29 
30         public V setValue(V value) {
31             V oldValue = this.value ;
32             this.value = value;
33             return oldValue;
34         }
35 
36         public boolean equals(Object o) {
37             if (!(o instanceof Map.Entry))
38                 return false;
39             Map.Entry<?,?> e = (Map.Entry<?,?>)o;
40 
41             return valEquals( key,e.getKey()) && valEquals( value,e.getValue());
42         }
43 
44         public int hashCode() {
45             int keyHash = (key ==null ? 0 : key.hashCode());
46             int valueHash = (value ==null ? 0 : value.hashCode());
47             return keyHash ^ valueHash;
48         }
49 
50         public String toString() {
51             return key + "=" + value;
52         }
53     }
複製代碼

 

  Entry類理解起來比較簡單(因爲我們前面看過很多的Entry類了),主要是定義了樹的孩子和父親節點引用,和紅黑顏色屬性,並對equals和hashCode進行重寫,以利於比較是否相等。
 
3.TreeMap的構造方法
 
     接下來看下TreeMap的構造方法:
複製代碼
 1 /**
 2      * 默認構造方法,comparator爲空,代表使用key的自然順序來維持TreeMap的順序,這裏要求key必須實現Comparable接口
 3      */
 4     public TreeMap() {
 5         comparator = null;
 6     }
 7 
 8     /**
 9      * 用指定的比較器構造一個TreeMap
10      */
11     public TreeMap(Comparator<? super K> comparator) {
12         this.comparator = comparator;
13     }
14 
15     /**
16      * 構造一個指定map的TreeMap,同樣比較器comparator爲空,使用key的自然順序排序
17      */
18     public TreeMap(Map<? extends K, ? extends V> m) {
19         comparator = null;
20         putAll(m);
21     }
22 
23     /**
24      * 構造一個指定SortedMap的TreeMap,根據SortedMap的比較器來來維持TreeMap的順序
25      */
26     public TreeMap(SortedMap<K, ? extends V> m) {
27         comparator = m.comparator();
28         try {
29             buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
30         } catch (java.io.IOException cannotHappen) {
31         } catch (ClassNotFoundException cannotHappen) {
32         }
33     }
複製代碼
  從構造方法中可以看出,要創建一個紅黑樹實現的TreeMap必須要有一個用於比較大小的比較器,因爲只有能夠比較大小才能實現紅黑樹的左孩子<樹根<右孩子的特點。
 
4.紅黑樹的添加原理及TreeMap的put實現
 
     將一個節點添加到紅黑樹中,通常需要下面幾個步驟:
     (1)將紅黑樹當成一顆二叉查找樹,將節點插入。
     這一步比較簡單,就上開始我們自己寫的二叉查找樹的操作一樣,至於爲什麼可以這樣插入,是因爲紅黑樹本身就是一個二叉查找樹。
     (2)將新插入的節點設置爲紅色
     有沒有疑問,爲什麼新插入的節點一定要是紅色的,因爲新插入節點爲紅色,不會違背紅黑規則第(5)條,少違背一條就少處理一種情況。
     (3)通過旋轉和着色,使它恢復平衡,重新變成一顆符合規則的紅黑樹。
     要想知道怎麼樣進行左旋和右旋,首先就要知道爲什麼要進行左旋和右旋。
     我們來對比下紅黑樹的規則和新插入節點後的情況,看下新插入節點會違背哪些規則。
     (1)節點是紅色或黑色。
     這一點肯定是不會違背的了。
     (2)根節點是黑色。
     這一點也不會違背了,如果是根節點,只需將根節點插入就好了,因爲默認是黑色。
     (3)每個葉節點(NIL節點,空節點)是黑色的。
     這一點也不會違背的,我們插入的是非空的節點,不會影響空節點。
     (4)每個紅色節點的兩個子節點都是黑色。(從每個葉子到根的所有路徑上不能有兩個連續的紅色節點)
     這一點是有可能違背的,我們將新插入的節點都設置成紅色,如果其父節點也是紅色的話,那就產生衝突了
     (5)從任一節點到其每個葉子的所有路徑都包含相同數目的黑色節點。
     這一點也不會違背,因爲我們將新插入的節點都設置成紅色。
 
     瞭解了紅黑樹左旋和右旋操作,以及新插入節點主要是可能會違背紅黑樹的規則(4)後,我們來分析下,添加新節點的過程有哪幾種情況:
     (1)新插入節點爲跟節點。這種情況直接將新插入節點設置爲跟節點即可,無需進行後續的旋轉和着色處理。
     (2)新插入節點的父節點是黑色。這種情況直接將新節點插入即可,不會違背規則(4).
     (3)新插入節點的父節點是紅色。這種情況會違背規則(4),而這種情況又分爲了以下幾種,下面進行圖解:
          ①新插入節點N的父節點P和叔叔節點U都是紅色。方法是:將祖父節點G設置爲紅色,父節點P和叔叔節點U設置爲黑色,這時候就看似平衡了。但是,如果祖父節點G的父節點也是紅色,這時候又違背規則(4)了,怎麼辦,方法是:將GPUN這一組看成一個新的節點,按照前面的方案遞歸;又但是根節點爲紅就違反規則(2)了,怎麼辦,方法是直接將根節點設置爲黑色(兩個連續黑色是沒問題的)。
 
  ②新插入節點N的父節點P是紅色,叔叔節點U是黑色或者缺少,且新節點N是P的右孩子。方法是:左旋父節點P。左旋後N和P角色互換,但是P和N還是連續的兩個紅色節點,還沒有平衡,怎麼辦,看第三種情況。
 
  ③新插入節點N的父節點P是紅色,叔叔節點U是黑色或者缺少,且新節點N是P的左孩子。方法是:右旋祖父節點G,然後將P設置爲黑色,G設置爲紅色,達到平衡。此時父節點P是黑色,所有不用擔心P的父節點是紅色。
 
 
  當然上面說的三種情況都是基於一個前提:新插入節點N的父節點P是祖父節點G的左孩子,如果P是G的右孩子又是什麼情況呢?其實情況和上面是相似的,只需要調整左旋還是右旋,這裏就不細講了。
 
     上面分析了這麼多,到底TreeMap是怎麼實現的呢,我們來看下:
 
複製代碼
  1 public V put(K key, V value) {
  2         // 根節點
  3         Entry<K,V> t = root;
  4         // 如果根節點爲空,則直接創建一個根節點,返回
  5         if (t == null) {
  6            // TBD:
  7            // 5045147: (coll) Adding null to an empty TreeSet should
  8            // throw NullPointerException
  9            //
 10            // compare(key, key); // type check
 11             root = new Entry<K,V>(key, value, null);
 12             size = 1;
 13             modCount++;
 14             return null;
 15         }
 16         // 記錄比較結果
 17         int cmp;
 18         Entry<K,V> parent;
 19         // split comparator and comparable paths
 20         // 當前使用的比較器
 21         Comparator<? super K> cpr = comparator ;
 22         // 如果比較器不爲空,就是用指定的比較器來維護TreeMap的元素順序
 23         if (cpr != null) {
 24              // do while循環,查找key要插入的位置(也就是新節點的父節點是誰)
 25             do {
 26                 // 記錄上次循環的節點t
 27                 parent = t;
 28                 // 比較當前節點的key和新插入的key的大小
 29                 cmp = cpr.compare(key, t. key);
 30                  // 新插入的key小的話,則以當前節點的左孩子節點爲新的比較節點
 31                 if (cmp < 0)
 32                     t = t. left;
 33                 // 新插入的key大的話,則以當前節點的右孩子節點爲新的比較節點
 34                 else if (cmp > 0)
 35                     t = t. right;
 36                 else
 37               // 如果當前節點的key和新插入的key想的的話,則覆蓋map的value,返回
 38                     return t.setValue(value);
 39             // 只有當t爲null,也就是沒有要比較節點的時候,代表已經找到新節點要插入的位置
 40             } while (t != null);
 41         }
 42         else {
 43             // 如果比較器爲空,則使用key作爲比較器進行比較
 44             // 這裏要求key不能爲空,並且必須實現Comparable接口
 45             if (key == null)
 46                 throw new NullPointerException();
 47             Comparable<? super K> k = (Comparable<? super K>) key;
 48             // 和上面一樣,喜歡查找新節點要插入的位置
 49             do {
 50                 parent = t;
 51                 cmp = k.compareTo(t. key);
 52                 if (cmp < 0)
 53                     t = t. left;
 54                 else if (cmp > 0)
 55                     t = t. right;
 56                 else
 57                     return t.setValue(value);
 58             } while (t != null);
 59         }
 60         // 找到新節點的父節點後,創建節點對象
 61         Entry<K,V> e = new Entry<K,V>(key, value, parent);
 62         // 如果新節點key的值小於父節點key的值,則插在父節點的左側
 63         if (cmp < 0)
 64             parent. left = e;
 65         // 如果新節點key的值大於父節點key的值,則插在父節點的右側
 66         else
 67             parent. right = e;
 68         // 插入新的節點後,爲了保持紅黑樹平衡,對紅黑樹進行調整
 69         fixAfterInsertion(e);
 70         // map元素個數+1
 71         size++;
 72         modCount++;
 73         return null;
 74     }
 75      
 76 
 77     /** 新增節點後對紅黑樹的調整方法 */
 78     private void fixAfterInsertion(Entry<K,V> x) {
 79         // 將新插入節點的顏色設置爲紅色
 80         x. color = RED;
 81 
 82         // while循環,保證新插入節點x不是根節點或者新插入節點x的父節點不是紅色(這兩種情況不需要調整)
 83         while (x != null && x != root && x. parent.color == RED) {
 84             // 如果新插入節點x的父節點是祖父節點的左孩子
 85             if (parentOf(x) == leftOf(parentOf (parentOf(x)))) {
 86                 // 取得新插入節點x的叔叔節點
 87                 Entry<K,V> y = rightOf(parentOf (parentOf(x)));
 88                 // 如果新插入x的父節點是紅色-------------------①
 89                 if (colorOf(y) == RED) {
 90                     // 將x的父節點設置爲黑色
 91                     setColor(parentOf (x), BLACK);
 92                     // 將x的叔叔節點設置爲黑色
 93                     setColor(y, BLACK);
 94                     // 將x的祖父節點設置爲紅色
 95                     setColor(parentOf (parentOf(x)), RED);
 96                     // 將x指向祖父節點,如果x的祖父節點的父節點是紅色,按照上面的步奏繼續循環
 97                     x = parentOf(parentOf (x));
 98                 } else {
 99                     // 如果新插入x的叔叔節點是黑色或缺少,且x的父節點是祖父節點的右孩子-------------------②
100                     if (x == rightOf( parentOf(x))) {
101                         // 左旋父節點
102                         x = parentOf(x);
103                         rotateLeft(x);
104                     }
105                     // 如果新插入x的叔叔節點是黑色或缺少,且x的父節點是祖父節點的左孩子-------------------③
106                     // 將x的父節點設置爲黑色
107                     setColor(parentOf (x), BLACK);
108                     // 將x的祖父節點設置爲紅色
109                     setColor(parentOf (parentOf(x)), RED);
110                     // 右旋x的祖父節點
111                     rotateRight( parentOf(parentOf (x)));
112                 }
113             } else { // 如果新插入節點x的父節點是祖父節點的右孩子,下面的步奏和上面的相似,只不過左旋右旋的區分,不在細講
114                 Entry<K,V> y = leftOf(parentOf (parentOf(x)));
115                 if (colorOf(y) == RED) {
116                     setColor(parentOf (x), BLACK);
117                     setColor(y, BLACK);
118                     setColor(parentOf (parentOf(x)), RED);
119                     x = parentOf(parentOf (x));
120                 } else {
121                     if (x == leftOf( parentOf(x))) {
122                         x = parentOf(x);
123                         rotateRight(x);
124                     }
125                     setColor(parentOf (x), BLACK);
126                     setColor(parentOf (parentOf(x)), RED);
127                     rotateLeft( parentOf(parentOf (x)));
128                 }
129             }
130         }
131         // 最後將根節點設置爲黑色,不管當前是不是紅色,反正根節點必須是黑色
132         root.color = BLACK;
133     }
134 
135     /**
136      * 對紅黑樹的節點(x)進行左旋轉
137      *
138      * 左旋示意圖(對節點x進行左旋):
139      *      px                              px
140      *     /                               /
141      *    x                               y               
142      *   /  \      --(左旋)--           / \                
143      *  lx   y                          x  ry    
144      *     /   \                       /  \
145      *    ly   ry                     lx  ly 
146      *
147      */
148     private void rotateLeft(Entry<K,V> p) {
149         if (p != null) {
150             // 取得要選擇節點p的右孩子
151             Entry<K,V> r = p. right;
152             // "p"和"r的左孩子"的相互指向...
153             // 將"r的左孩子"設爲"p的右孩子"
154             p. right = r.left ;
155             // 如果r的左孩子非空,將"p"設爲"r的左孩子的父親"
156             if (r.left != null)
157                 r. left.parent = p;
158             
159             // "p的父親"和"r"的相互指向...
160             // 將"p的父親"設爲"y的父親"
161             r. parent = p.parent ;
162             // 如果"p的父親"是空節點,則將r設爲根節點
163             if (p.parent == null)
164                 root = r;
165             // 如果p是它父節點的左孩子,則將r設爲"p的父節點的左孩子"
166             else if (p.parent. left == p)
167                 p. parent.left = r;
168             else             
169                 // 如果p是它父節點的左孩子,則將r設爲"p的父節點的左孩子"
170                 p. parent.right = r;
171             // "p"和"r"的相互指向...
172             // 將"p"設爲"r的左孩子"
173             r. left = p;
174             // 將"p的父節點"設爲"r"
175             p. parent = r;
176         }
177     }
178 
179    
180     /**
181      * 對紅黑樹的節點進行右旋轉
182      *
183      * 右旋示意圖(對節點y進行右旋):
184      *            py                               py
185      *           /                                /
186      *          y                                x                 
187      *         /  \      --(右旋)--            /  \                     
188      *        x   ry                           lx   y 
189      *       / \                                   / \                   
190      *      lx  rx                                rx  ry
191      *
192      */
193     private void rotateRight(Entry<K,V> p) {
194         if (p != null) {
195             // 取得要選擇節點p的左孩子
196             Entry<K,V> l = p. left;           
197             // 將"l的右孩子"設爲"p的左孩子"
198             p. left = l.right ;
199             // 如果"l的右孩子"不爲空的話,將"p"設爲"l的右孩子的父親"
200             if (l.right != null) l. right.parent = p;
201             // 將"p的父親"設爲"l的父親"
202             l. parent = p.parent ;
203             // 如果"p的父親"是空節點,則將l設爲根節點
204             if (p.parent == null)
205                 root = l;      
206             // 如果p是它父節點的右孩子,則將l設爲"p的父節點的右孩子"
207             else if (p.parent. right == p)
208                 p. parent.right = l;
209             //如果p是它父節點的左孩子,將l設爲"p的父節點的左孩子"
210             else p.parent .left = l;
211             // 將"p"設爲"l的右孩子"
212             l. right = p;
213             // 將"l"設爲"p父節點"
214             p. parent = l;
215         }
216     }
複製代碼
  單純的看代碼和註釋,絕對會發出,cha這是什麼亂七八糟的,任誰也看不懂,所以一定要結合上面的圖解,不懂了就看看圖,然後動手畫一下。如果你告訴我,還是沒有懂,沒問題可以理解,這裏有一位大神錄製的紅黑樹增加元素視頻動畫,來吧,http://v.youku.com/v_show/id_XNjI4NzgxMjA4.html(視頻不是我錄得,尊重版權,向大神致敬)。
 
5.紅黑樹的刪除原理及TreeMap的remove實現
     
     相比添加,紅黑樹的刪除顯得更加複雜了。看下紅黑樹的刪除需要哪幾個步奏:
     (1)將紅黑樹當成一顆二叉查找樹,將節點刪除。
     (2)通過旋轉和着色,使它恢復平衡,重新變成一顆符合規則的紅黑樹。
 
     刪除節點的關鍵是:
     (1)如果刪除的是紅色節點,不會違背紅黑樹的規則。
     (2)如果刪除的是黑色節點,那麼這個路徑上就少了一個黑色節點,則違背了紅黑樹的規則
    
     來看下紅黑樹刪除節點會有哪幾種情況:
     (1)被刪除的節點沒有孩子節點,即葉子節點。可直接刪除。
     (2)被刪除的節點只有一個孩子節點,那麼直接刪除該節點,然後用它的孩子節點頂替它的位置。
     (3)被刪除的節點有兩個孩子節點。這種情況二叉樹的刪除有一個技巧,就是查找到要刪除的節點X,接着我們找到它左子樹的最大元素M,或者它右子樹的最小元素M,交換X和M的值,然後刪除節點M。此時M就最多隻有一個子節點N(若是左子樹則沒有右子節點,若是右子樹則沒有左子節點 ),若M沒有孩子則進入(1)的情況,否則進入(2)的情況。
 
 
  如上圖,我們假定節點X是要刪除的節點,而節點M是找到X右子樹的最小元素,所以節點M是X的替代節點,也就是說M是真正要刪除的節點。上面我們分析了此時的M只會有一個子節點N,當刪除節點M後,N將替代M作爲M節點的父節點的子節點。刪除的節點M是黑色(刪除紅色不影響上面分析了),此時如果N是紅色,只需將N設置爲黑色,就會重新達到平衡,不會出現該路徑上少了一個黑色節點的情況;但是如果N是紅色,情況則比較複雜,需要對紅黑樹進行調整,而這種情況又分爲了以下幾種,下面進行圖解:
    N的兄弟節點B是紅色。方法是:交換P和B的顏色,左旋父節點P。此時並未完成平衡,左子樹仍然少了一個黑色節點,進入情況③。(B爲紅色,P必然爲黑色)
 
 
  ②N的父節點P是黑色,且兄弟節點B和它的兩個孩子節點也都是黑色。方法是:將N的兄弟節點B改爲紅色,這樣從P出發到葉子節點的路徑都包含了相同的黑色節點,但是,對於節點P這個子樹,P的父節點G到P的葉子節點路徑上的黑色節點就少了一個,此時需要將P整體看做一個節點,繼續調整
 
  ③N的父節點P爲紅色,兄弟節點B和它的兩個孩子節點也都是黑色。此時只需要交換P和B的顏色,將P改爲黑色,B改爲紅色,則可到達平衡。這相當於既然節點N路徑少了一個黑色節點,那麼B路徑也少一個黑色節點,這兩個路徑達到平衡,爲了防止P路徑少一個黑色節點,將P節點置黑,則達到最終平衡。
 
  ④N的兄弟節點B是黑色,B的左孩子節點BL是紅色,B的右孩子節點BR是黑色,P爲任意顏色。方法是:交換B和BL的顏色,右旋節點B。此時N子樹路徑並沒有增加黑色節點,也就是沒有達到平衡,此時進入下一種情況⑤。
 
  ⑤N的兄弟節點B是黑色,B的右孩子節點BR是紅色,B的左孩子節點BL任意顏色,P任意顏色。方法是:BR變爲黑色,P變爲黑色,B變爲P的顏色;左旋節點B。首先給N路徑增加一個黑色節點P,P原位置上的顏色不變;S路徑少了一個黑色節點,於是將BR改爲黑色,最終達到了平衡。
 
  上面對紅黑樹刪除的原理和刪除過程中遇到的情況進行了分析說明,我們得到的結論是紅黑樹的刪除遇到的主要問題就是被刪除路徑上的黑色節點減少,於是需要進行一系列旋轉和着色,當然上面的情況是基於M是X右子樹的最小元素,而M如果是X左子樹的最大元素和上面的情況是相似的,我們具體看下TreeMap的代碼是怎麼實現的:
 
複製代碼
  1 public V remove(Object key) {
  2         // 根據key查找到對應的節點對象
  3         Entry<K,V> p = getEntry(key);
  4         if (p == null)
  5             return null;
  6 
  7         // 記錄key對應的value,供返回使用
  8         V oldValue = p. value;
  9         // 刪除節點
 10         deleteEntry(p);
 11         return oldValue;
 12     }
 13 
 14      
 15 private void deleteEntry(Entry<K,V> p) {
 16         modCount++;
 17         // map容器的元素個數減一
 18         size--;
 19 
 20         // If strictly internal, copy successor's element to p and then make p
 21         // point to successor.
 22         // 如果被刪除的節點p的左孩子和右孩子都不爲空,則查找其替代節點-----------這裏表示要刪除的節點有兩個孩子(3)
 23         if (p.left != null && p. right != null) {
 24             // 查找p的替代節點
 25             Entry<K,V> s = successor (p);
 26             p. key = s.key ;
 27             p. value = s.value ;
 28             // 將p指向替代節點,※※※※※※從此之後的p不再是原先要刪除的節點p,而是替代者p(就是圖解裏面講到的M) ※※※※※※
 29             p = s;
 30         } // p has 2 children
 31 
 32         // Start fixup at replacement node, if it exists.
 33         // replacement爲替代節點p的繼承者(就是圖解裏面講到的N),p的左孩子存在則用p的左孩子替代,否則用p的右孩子
 34         Entry<K,V> replacement = (p. left != null ? p.left : p. right);
 35  
 36         if (replacement != null) { // 如果上面的if有兩個孩子不通過--------------這裏表示要刪除的節點只有一個孩子(2)
 37             // Link replacement to parent
 38             // 將p的父節點拷貝給替代節點
 39             replacement. parent = p.parent ;
 40             // 如果替代節點p的父節點爲空,也就是p爲跟節點,則將replacement設置爲根節點
 41             if (p.parent == null)
 42                 root = replacement;
 43             // 如果替代節點p是其父節點的左孩子,則將replacement設置爲其父節點的左孩子
 44             else if (p == p.parent. left)
 45                 p. parent.left   = replacement;
 46             // 如果替代節點p是其父節點的左孩子,則將replacement設置爲其父節點的右孩子
 47             else
 48                 p. parent.right = replacement;
 49 
 50             // Null out links so they are OK to use by fixAfterDeletion.
 51             // 將替代節點p的left、right、parent的指針都指向空,即解除前後引用關係(相當於將p從樹種摘除),使得gc可以回收
 52             p. left = p.right = p.parent = null;
 53 
 54             // Fix replacement
 55             // 如果替代節點p的顏色是黑色,則需要調整紅黑樹以保持其平衡
 56             if (p.color == BLACK)
 57                 fixAfterDeletion(replacement);
 58         } else if (p.parent == null) { // return if we are the only node.
 59             // 如果要替代節點p沒有父節點,代表p爲根節點,直接刪除即可
 60             root = null;
 61         } else { //  No children. Use self as phantom replacement and unlink.
 62             // 判斷進入這裏說明替代節點p沒有孩子--------------這裏表示沒有孩子則直接刪除(1)
 63             // 如果p的顏色是黑色,則調整紅黑樹
 64             if (p.color == BLACK)
 65                 fixAfterDeletion(p);
 66             // 下面刪除替代節點p
 67             if (p.parent != null) {
 68                 // 解除p的父節點對p的引用
 69                 if (p == p.parent .left)
 70                     p. parent.left = null;
 71                 else if (p == p.parent. right)
 72                     p. parent.right = null;
 73                 // 解除p對p父節點的引用
 74                 p. parent = null;
 75             }
 76         }
 77     }
 78 
 79     /**
 80      * 查找要刪除節點的替代節點
 81      */
 82     static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
 83         if (t == null)
 84             return null;
 85         // 查找右子樹的最左孩子
 86         else if (t.right != null) {
 87             Entry<K,V> p = t. right;
 88             while (p.left != null)
 89                 p = p. left;
 90             return p;
 91         } else { // 查找左子樹的最右孩子
 92             Entry<K,V> p = t. parent;
 93             Entry<K,V> ch = t;
 94             while (p != null && ch == p. right) {
 95                 ch = p;
 96                 p = p. parent;
 97             }
 98             return p;
 99         }
100     }
101 
102     /** From CLR */
103     private void fixAfterDeletion(Entry<K,V> x) {
104         // while循環,保證要刪除節點x不是跟節點,並且是黑色(根節點和紅色不需要調整)
105         while (x != root && colorOf (x) == BLACK) {
106             // 如果要刪除節點x是其父親的左孩子
107             if (x == leftOf( parentOf(x))) {
108                 // 取出要刪除節點x的兄弟節點
109                 Entry<K,V> sib = rightOf(parentOf (x));
110 
111                 // 如果刪除節點x的兄弟節點是紅色---------------------------①
112                 if (colorOf(sib) == RED) {
113                     // 將x的兄弟節點顏色設置爲黑色
114                     setColor(sib, BLACK);
115                     // 將x的父節點顏色設置爲紅色
116                     setColor(parentOf (x), RED);
117                     // 左旋x的父節點
118                     rotateLeft( parentOf(x));
119                     // 將sib重新指向旋轉後x的兄弟節點 ,進入else的步奏③
120                     sib = rightOf(parentOf (x));
121                 }
122 
123                 // 如果x的兄弟節點的兩個孩子都是黑色-------------------------③
124                 if (colorOf(leftOf(sib))  == BLACK &&
125                     colorOf(rightOf (sib)) == BLACK) {
126                     // 將兄弟節點的顏色設置爲紅色
127                     setColor(sib, RED);
128                     // 將x的父節點指向x,如果x的父節點是黑色,需要將x的父節點整天看做一個節點繼續調整-------------------------②
129                     x = parentOf(x);
130                 } else {
131                     // 如果x的兄弟節點右孩子是黑色,左孩子是紅色-------------------------④
132                     if (colorOf(rightOf(sib)) == BLACK) {
133                         // 將x的兄弟節點的左孩子設置爲黑色
134                         setColor(leftOf (sib), BLACK);
135                         // 將x的兄弟節點設置爲紅色
136                         setColor(sib, RED);
137                         // 右旋x的兄弟節點
138                         rotateRight(sib);
139                         // 將sib重新指向旋轉後x的兄弟節點,進入步奏⑤
140                         sib = rightOf(parentOf (x));
141                     }
142                     // 如果x的兄弟節點右孩子是紅色-------------------------⑤
143                     setColor(sib, colorOf (parentOf(x)));
144                     // 將x的父節點設置爲黑色
145                     setColor(parentOf (x), BLACK);
146                     // 將x的兄弟節點的右孩子設置爲黑色
147                     setColor(rightOf (sib), BLACK);
148                     // 左旋x的父節點
149                     rotateLeft( parentOf(x));
150                     // 達到平衡,將x指向root,退出循環
151                     x = root;
152                 }
153             } else { // symmetric // 如果要刪除節點x是其父親的右孩子,和上面情況一樣,這裏不再細講
154                 Entry<K,V> sib = leftOf(parentOf (x));
155 
156                 if (colorOf(sib) == RED) {
157                     setColor(sib, BLACK);
158                     setColor(parentOf (x), RED);
159                     rotateRight( parentOf(x));
160                     sib = leftOf(parentOf (x));
161                 }
162 
163                 if (colorOf(rightOf(sib)) == BLACK &&
164                     colorOf(leftOf (sib)) == BLACK) {
165                     setColor(sib, RED);
166                     x = parentOf(x);
167                 } else {
168                     if (colorOf(leftOf(sib)) == BLACK) {
169                         setColor(rightOf (sib), BLACK);
170                         setColor(sib, RED);
171                         rotateLeft(sib);
172                         sib = leftOf(parentOf (x));
173                     }
174                     setColor(sib, colorOf (parentOf(x)));
175                     setColor(parentOf (x), BLACK);
176                     setColor(leftOf (sib), BLACK);
177                     rotateRight( parentOf(x));
178                     x = root;
179                 }
180             }
181         }
182 
183         setColor(x, BLACK);
184     }
複製代碼
  刪除相對來說更加複雜,還是那句話一定要對照着圖解看代碼,否則是讀不懂的,別問我是怎麼看懂得,我n天不看再看代碼也不知道123了。
     
  終於看完了紅黑樹的增加和刪除,下面來看個稍微簡單的查詢:
 
6.紅黑樹的查詢
 
複製代碼
 1 public V get(Object key) {
 2         Entry<K,V> p = getEntry(key);
 3         return (p==null ? null : p. value);
 4     }
 5 
 6 final Entry<K,V> getEntry(Object key) {
 7         // Offload comparator-based version for sake of performance
 8         if (comparator != null)
 9             // 如果比較器爲空,只是用key作爲比較器查詢
10             return getEntryUsingComparator(key);
11         if (key == null)
12             throw new NullPointerException();
13        Comparable<? super K> k = (Comparable<? super K>) key;
14         // 取得root節點
15         Entry<K,V> p = root;
16         // 從root節點開始查找,根據比較器判斷是在左子樹還是右子樹
17         while (p != null) {
18             int cmp = k.compareTo(p.key );
19             if (cmp < 0)
20                 p = p. left;
21             else if (cmp > 0)
22                 p = p. right;
23             else
24                 return p;
25         }
26         return null;
27     }
28 
29     final Entry<K,V> getEntryUsingComparator(Object key) {
30        K k = (K) key;
31         Comparator<? super K> cpr = comparator ;
32         if (cpr != null) {
33             Entry<K,V> p = root;
34             while (p != null) {
35                 int cmp = cpr.compare(k, p.key );
36                 if (cmp < 0)
37                     p = p. left;
38                 else if (cmp > 0)
39                     p = p. right;
40                 else
41                     return p;
42             }
43         }
44         return null;
45     }
複製代碼
  查詢看起來真的是so easy。。。
 
6.TreeMap對NavigableMap接口的實現
 
     TreeMap對NavigableMap接口的實現的內容,不作爲這裏的重點,這些內容會和TreeSet一起分析,TreeSet見。
 
 
     到此TreeMap就分析完了,其實大部分時間都在整理紅黑樹,在數據結構中樹是比較難懂的一個,其算法也比較複雜,對於樹的理解一定要多看圖畫圖,要明白這麼做是爲了解決什麼問題,這麼做又有什麼好處,當然看一遍看不懂就要多看幾遍了。什麼你問我平時工作中會用到樹嗎?那真的要看你做的什麼性質的工作,如果是web、客戶端開發,調用api就可以了對吧,如果是從事底層開發,比如文件系統,存儲系統,緩存等工作必須是需要的。當然就算用不到,理解了也是有益無害的。
 
     紅黑樹&TreeMap 完!
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章