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 完!
參見:
參考資料: