一,首先需要了解以下幾個問題:
1.為什么要引入紅黑數(特殊的平衡二叉樹)數據結構
2.引入紅黑樹HashMap做了哪些改造
3. 紅黑樹的特性
4.紅黑樹的具體實現方式
二,逐一解釋以上三個問題
1.1 為什么要引入紅黑數(特殊的平衡二叉樹)數據結構
由於在JDK1.7之前,HashMap的數據結構為:數組 + 鏈表。數組相當於日常中永到的數據結構Array. 用來確定key-value對所存儲的位置。那么為什么又有鏈表結構?這個要從HashMap散列值生成來講起。這個具體細節可參考相關文檔即可。如果按照Hash值,通過Hash函數來確認桶位,會存在一個問題,就是hash沖突的問題,也就是不同的key可能會產生不一樣的hash值。
static final int hash(Object key) { int h;
// 兩個值做異或,最終相同的可能性很大 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
所以引入了鏈表來存儲hash值一樣的key-value. 如果按照鏈表的方式存儲,隨着節點的增加數據會越來越多,這會導致查詢節點的時間復雜度會逐漸增加,平均時間復雜度O(n)。 為了提高查詢效率,故在JDK1.8中引入了改進方法紅黑樹。此數據結構的平均查詢效率為O(long n) 。
1.2 引入紅黑樹HashMap做了哪些改造
當鏈表節點長度超過8時,將鏈表轉換為二叉樹。
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { // 先將新節點插入到 p.next p.next = newNode(hash, key, value, null); // 如果長度鏈表長度超過8,則轉換為二叉樹 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st /** * hash 需要轉化二叉樹的hash值 */ // 轉化為二叉樹 treeifyBin(tab, hash); break; } // 存在hash和key都一樣的情況,則說明已經存在。直接跳出循環 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; // 繼續下一次循環 p = e; } } // if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
1.3 紅黑樹的特性
1.3.1 什么時紅黑樹
紅黑樹(Red Black Tree) 是一種自平衡二叉查找樹,是在計算機科學中用到的一種數據結構,典型的用途是實現關聯數組。 《引自百度百科》
1.3.2 紅黑樹的特點
紅黑樹和AVL樹類似,都是在進行插入和刪除操作時通過特定操作保持二叉查找樹的平衡,從而獲得較高的查找性能。它雖然是復雜的,但它的最壞情況運行時間也是非常良好的,並且在實踐中是高效的: 它可以在O(log n)時間內做查找,插入和刪除,這里的n 是樹中元素的數目。
1.3.3 紅黑樹特點
性質1: 節點是紅色或黑色。
性質2:根節點是黑色。
性質3:每個葉節點(NIL節點,空節點)是黑色的。
性質4:每個紅色節點的兩個子節點都是黑色。(從每個葉子到根的所有路徑上不能有兩個連續的紅色節點)
//數據結構
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> { TreeNode<K,V> parent; // red-black tree links TreeNode<K,V> left; TreeNode<K,V> right; TreeNode<K,V> prev; // needed to unlink next upon deletion boolean red; //紅黑節點標識 TreeNode(int hash, K key, V val, Node<K,V> next) { super(hash, key, val, next); }
1.4 紅黑樹的具體實現方式(重點)
final void treeifyBin(Node<K,V>[] tab, int hash) { int n, index; Node<K,V> e; if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize(); // 重新計算 hash段位,及table的索引位,第一個節點 else if ((e = tab[index = (n - 1) & hash]) != null) { /************ 雙向鏈表 start***************/ // hd頭節點, tl尾節點 TreeNode<K,V> hd = null, tl = null; do { // 循環所有節點 TreeNode<K,V> p = replacementTreeNode(e, null); if (tl == null) hd = p; else { p.prev = tl; tl.next = p; } tl = p; } while ((e = e.next) != null);// 循環下一個節點 /************ 雙向鏈表 end***************/ // 前面僅僅轉換為雙向鏈表,treeify才是轉換紅黑樹的處理方法入口 // 第一個節點賦值為頭節點,也就是根節點 if ((tab[index] = hd) != null) // 將二叉樹轉換為紅黑樹 hd.treeify(tab); } }
1.4.2 驗證是否滿足紅黑樹的五大特征
/** * 調用這個方法之前 也就是一個雙向鏈表 * 初始進入值為 this頭節點 * 將雙向鏈表轉換為紅黑樹 * 目標:查詢 root 節點 * @param tab */ final void treeify(Node<K,V>[] tab) { TreeNode<K,V> root = null;//root節點 for (TreeNode<K,V> x = this, next; x != null; x = next) { next = (TreeNode<K,V>)x.next; //next 下一個節點 x.left = x.right = null;//設置左右節點為空 if (root == null) {//首次循環 root == null x.parent = null; // 將根節點的父節點設置位空 x.red = false; // 將根節點設置為 black root = x; //將x 設置為根節點 } else {// 非根節點 K k = x.key;// 獲取當前循環節點key int h = x.hash;// 獲取當前節點hash Class<?> kc = null; // 從根節點開始驗證 for (TreeNode<K,V> p = root;;) { int dir, ph; K pk = p.key;// 每個節點的key if ((ph = p.hash) > h) //每個節點的hash 與 外層循環的x.hash做比較 dir = -1;// <0 ,沿左路徑查找 -1 else if (ph < h)// >0, 沿右路徑查找 1 dir = 1; // 如果存在比較對象,則根據比較對象定義的comparable進行比較 // 比較之后返回查詢節點路徑(左或右) else if ((kc == null && (kc = comparableClassFor(k)) == null) || (dir = compareComparables(kc, k, pk)) == 0) dir = tieBreakOrder(k, pk); // p設置位x的父節點 xp TreeNode<K,V> xp = p; // 如果父節點的左節點或右節點為空時,才進行插入操作 if ((p = (dir <= 0) ? p.left : p.right) == null) { // 將px設置為x的父節點 x.parent = xp; if (dir <= 0) xp.left = x; else xp.right = x; // 將二叉樹轉換位紅黑樹-正式轉換紅黑樹 root = balanceInsertion(root, x); break; } } } } moveRootToFront(tab, root); }
1.4.3 對二叉樹進行左右旋轉操作
/** * 轉換二叉樹為紅黑樹 * @param root 根節點 * @param x 執行的節點 * @param <K> * @param <V> * @return */ static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root, TreeNode<K,V> x) { // 默認x節點為紅色節點 x.red = true; /** * xp: x的父節點 * xpp: x父節點的父節點 * xppl: x父節點的父節點左子節點 * xppr: x父節點的父節點右子節點 */ for (TreeNode<K,V> xp, xpp, xppl, xppr;;) { // xp = x.parent // 如果x存在父節點,則說明目前只有一個節點,即root.根據 // 紅黑樹的五大特征,根節點只能為黑色節點 if ((xp = x.parent) == null) { x.red = false; return x; } //xpp = xp.parent //直接查詢的是根節點 else if (!xp.red || (xpp = xp.parent) == null) return root; // xppl = xpp.left // x的父節點時左節點時 if (xp == (xppl = xpp.left)) { // 驗證是否需要旋轉 // xppr = xpp.right 存在右節點 且 右節點為紅色 if ((xppr = xpp.right) != null && xppr.red) { xppr.red = false; // xppr 設置位black xp.red = false; // xp 設置位black xpp.red = true; // xpp 設置位red x = xpp;// 將x賦值為父節點的父節點 } else { if (x == xp.right) { // 左旋轉 root = rotateLeft(root, x = xp); xpp = (xp = x.parent) == null ? null : xp.parent; } if (xp != null) { xp.red = false; if (xpp != null) { xpp.red = true; // 右旋轉 root = rotateRight(root, xpp); } } } } // x的父節點右節點時 else { // 驗證是否需要旋轉 if (xppl != null && xppl.red) { xppl.red = false; xp.red = false; xpp.red = true; x = xpp; } else { if (x == xp.left) { // 右旋轉 root = rotateRight(root, x = xp); xpp = (xp = x.parent) == null ? null : xp.parent; } if (xp != null) { xp.red = false; if (xpp != null) { xpp.red = true; // 左旋轉 root = rotateLeft(root, xpp); } } } } } }
1.4.3.1 左旋轉
/** * 左旋轉 * @param root * @param p * @param <K> * @param <V> * @return */ static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root, TreeNode<K,V> p) { TreeNode<K,V> r, pp, rl; if (p != null && (r = p.right) != null) { if ((rl = p.right = r.left) != null) rl.parent = p; if ((pp = r.parent = p.parent) == null) (root = r).red = false; else if (pp.left == p) pp.left = r; else pp.right = r; r.left = p; p.parent = r; } return root; }
1.4.3.2 右旋轉
/** * 右旋轉 * @param root * @param p * @param <K> * @param <V> * @return */ static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root, TreeNode<K,V> p) { // l: p的左節點 pp:p的父節點 lr:左右節點 TreeNode<K,V> l, pp, lr; // 傳入參數 // root: 默認調用此方法前指定的root節點 // p: root的父節點 if (p != null && (l = p.left) != null) { if ((lr = p.left = l.right) != null) lr.parent = p; // 判斷p的父節點是否為空 if ((pp = l.parent = p.parent) == null) // 調整root的值 (root = l).red = false; else if (pp.right == p) pp.right = l; else pp.left = l; // 將p調整為 root 節點的右節點 l.right = p; //將l調整為p的parent p.parent = l; } return root; }