日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
逐行解讀HashMap源碼之紅黑樹(shù)篇

【稿件】

寫在前面

本文是之前發(fā)表的文章《逐行解讀HashMap源碼》的下一篇,也是最后一篇。在《逐行解讀HashMap源碼》文章中,筆者主要分析了 HashMap 類的成員變量與成員方法,未涉及對(duì) HashMap 中紅黑樹(shù)相關(guān)內(nèi)容的分析。于是,筆者經(jīng)過(guò)一段的時(shí)間的編寫與打磨,寫出了本文。本文主要內(nèi)容包括對(duì)上一篇文章的些許補(bǔ)充以及對(duì)紅黑樹(shù)源碼的詳細(xì)解讀。

如果讀者沒(méi)有閱讀過(guò)上一篇內(nèi)容,可以微信搜索公眾號(hào)“代碼藝術(shù)”搜索并閱讀文章。

1. 紅黑樹(shù)概述

從本章節(jié)開(kāi)始,筆者將對(duì) HashMap 內(nèi)部的 TreeNode 內(nèi)部類源碼開(kāi)始分析,這部分的內(nèi)容也就是我們所說(shuō)的紅黑樹(shù)。

紅黑樹(shù)是建立在二叉查找樹(shù)的基礎(chǔ)之上的,二叉查找樹(shù)又是建立在二叉樹(shù)的基礎(chǔ)之上。所以,我們需要從二叉樹(shù)開(kāi)始學(xué)習(xí)紅黑樹(shù)的發(fā)展脈絡(luò)。

2. 二叉樹(shù)

二叉樹(shù)(英語(yǔ):Binary tree)是每個(gè)節(jié)點(diǎn)最多只有兩個(gè)分支(即不存在分支度大于2的節(jié)點(diǎn))的樹(shù)結(jié)構(gòu)。通常分支被稱作“左子樹(shù)”或“右子樹(shù)”。二叉樹(shù)的分支具有左右次序,不能隨意顛倒。

3. 二叉查找樹(shù)

二叉查找樹(shù)(英語(yǔ):Binary Search Tree),也稱為二叉搜索樹(shù)、有序二叉樹(shù)(ordered binary tree)或排序二叉樹(shù)(sorted binary tree),是指一棵空樹(shù)或者具有下列性質(zhì)的二叉樹(shù):

  1. 若任意節(jié)點(diǎn)的左子樹(shù)不空,則左子樹(shù)上所有節(jié)點(diǎn)的值均小于它的根節(jié)點(diǎn)的值;
  2. 若任意節(jié)點(diǎn)的右子樹(shù)不空,則右子樹(shù)上所有節(jié)點(diǎn)的值均大于或等于它的根節(jié)點(diǎn)的值;
  3. 任意節(jié)點(diǎn)的左、右子樹(shù)也分別為二叉查找樹(shù);

一顆二叉查找樹(shù)如下圖所示:

但是,當(dāng)我們順序插入一系列節(jié)點(diǎn)后,二叉查找樹(shù)就退化為線性表,如下圖所示:

雖然二叉查找樹(shù)退化為線性表之后,最壞效率降為 O(n)。但依舊有很多改進(jìn)版的二叉查找樹(shù)可以使樹(shù)高為O(log n),從而將最壞效率降至O(log n),如AVL樹(shù)、紅黑樹(shù)等。

4. AVL樹(shù)

AVL 樹(shù)是最早被發(fā)明的自平衡二叉查找樹(shù)。在AVL樹(shù)中,任一節(jié)點(diǎn)對(duì)應(yīng)的兩棵子樹(shù)的最大高度差為 1 ,因此它也被稱為高度平衡樹(shù)。查找、插入和刪除在平均和最壞情況下的時(shí)間復(fù)雜度都是 O(logn)。增加和刪除元素的操作則可能需要借由一次或多次樹(shù)旋轉(zhuǎn),以實(shí)現(xiàn)樹(shù)的重新平衡。

在 AVL 樹(shù)中,節(jié)點(diǎn)的**平衡因子**是它的左子樹(shù)的高度減去它的右子樹(shù)的高度(有時(shí)相反)。帶有平衡因子 1、0 或 -1 的節(jié)點(diǎn)被認(rèn)為是平衡的。帶有平衡因子 -2 或 2 的節(jié)點(diǎn)被認(rèn)為是不平衡的,并需要重新平衡這個(gè)樹(shù)。平衡因子可以直接存儲(chǔ)在每個(gè)節(jié)點(diǎn)中,或從可能存儲(chǔ)在節(jié)點(diǎn)中的子樹(shù)高度計(jì)算出來(lái)。

AVL 樹(shù)自平衡的方式是做一次或多次所謂的"AVL旋轉(zhuǎn)"。

5. 紅黑樹(shù)

紅黑樹(shù)(英語(yǔ):Red–black tree)也是一種自平衡二叉查找樹(shù)。紅黑樹(shù)在二叉查找樹(shù)的基礎(chǔ)之上,對(duì)每個(gè)節(jié)點(diǎn)都增加了顏色屬性,分為紅色或黑色。在二叉查找樹(shù)強(qiáng)制的一般要求以外,對(duì)于任何有效的紅黑樹(shù)增加了如下 5 條額外要求:

  1. 節(jié)點(diǎn)是紅色或黑色。
  2. 根節(jié)點(diǎn)是黑色。
  3. 所有葉子都是黑色(葉子是NIL節(jié)點(diǎn))。
  4. 每個(gè)紅色節(jié)點(diǎn)必須有兩個(gè)黑色的子節(jié)點(diǎn)。(從每個(gè)葉子到根的所有路徑上不能有兩個(gè)連續(xù)的紅色節(jié)點(diǎn)。)
  5. 從任一節(jié)點(diǎn)到其每個(gè)葉子的所有簡(jiǎn)單路徑都包含相同數(shù)目的黑色節(jié)點(diǎn)。

下面是一個(gè)具體的紅黑樹(shù)的圖例:

紅黑樹(shù)的這些特性,保證了紅黑樹(shù)**從根到葉子的最長(zhǎng)的可能路徑不多于最短的可能路徑的兩倍長(zhǎng)**,造成的結(jié)果是紅黑樹(shù)大致上是平衡的。如上圖所示,"nil葉子"或"空(null)葉子"不包含數(shù)據(jù)而只充當(dāng)樹(shù)在此結(jié)束的指示。

對(duì)于紅黑樹(shù)的讀與寫,因?yàn)槊恳粋€(gè)紅黑樹(shù)都是一個(gè)(特殊的)二叉查找樹(shù),因此紅黑樹(shù)上的只讀操作與普通二叉查找樹(shù)上的只讀操作相同。然而,在紅黑樹(shù)上進(jìn)行插入操作和刪除操作會(huì)導(dǎo)致不再符合紅黑樹(shù)的性質(zhì)?;謴?fù)紅黑樹(shù)的性質(zhì)需要少量 O(log n) 的顏色變更(實(shí)際是非常快速的)和不超過(guò)三次的樹(shù)旋轉(zhuǎn)(對(duì)于插入操作是兩次)。雖然插入和刪除很復(fù)雜,但操作時(shí)間仍可以保持為 O(log n) 次。

本章節(jié)開(kāi)始結(jié)合 HashMap 源碼講解紅黑樹(shù)的插入、紅黑樹(shù)的刪除、紅黑樹(shù)的自平衡、左旋、右旋等內(nèi)容。

6. 鏈表轉(zhuǎn)為紅黑樹(shù)的過(guò)程

HashMap 的桶類型除了 Node (鏈表)外,還有 TreeNode (樹(shù))。

TreeNode 類包含成員方法 `treeify()` ,該方法的作用是形成以當(dāng)前 TreeNode 對(duì)象為根節(jié)點(diǎn)的紅黑樹(shù)。

樹(shù)化過(guò)程不外乎是循環(huán)遍歷鏈表,構(gòu)造一顆二叉查找樹(shù),最后使用紅黑樹(shù)的平衡方法進(jìn)行自平衡。

該方法源碼如下:

 
 
 
 
  1. final void treeify(Node[] tab) { 
  2. TreeNode root = null; 
  3. // 步驟①:循環(huán)遍歷當(dāng)前TreeNode鏈表 
  4. for (TreeNode x = this, next; x != null; x = next) { 
  5. next = (TreeNode)x.next; 
  6. x.left = x.right = null; 
  7. // 步驟②:如果還未設(shè)置根節(jié)點(diǎn).. 
  8. if (root == null) { 
  9. x.parent = null; 
  10. x.red = false; 
  11. root = x; 
  12. // 步驟③:如果已設(shè)置根節(jié)點(diǎn).. 
  13. else { 
  14. K k = x.key; 
  15. int h = x.hash; 
  16. Class kc = null; 
  17. // 步驟④:從根節(jié)點(diǎn)開(kāi)始遍歷,插入新節(jié)點(diǎn) 
  18. for (TreeNode p = root;;) { 
  19. int dir, ph; 
  20. K pk = p.key; 
  21. // 步驟⑤:比較當(dāng)前節(jié)點(diǎn)的hash值與新節(jié)點(diǎn)的hash值 
  22. // 若是新節(jié)點(diǎn)hash值較小 
  23. if ((ph = p.hash) > h) 
  24. dir = -1; 
  25. // 若是新節(jié)點(diǎn)的hash值較大 
  26. else if (ph < h) 
  27. dir = 1; 
  28. // 若是新節(jié)點(diǎn)與當(dāng)前節(jié)點(diǎn)的hash值相等 
  29. else if ( 
  30. // 如果新節(jié)點(diǎn)的key沒(méi)有實(shí)現(xiàn)Comparable接口.. 
  31. (kc == null && (kc = comparableClassFor(k)) == null) 
  32. // 或者實(shí)現(xiàn)了Comparable接口但是k.compareTo(pk)結(jié)果為0 
  33. ||(dir = compareComparables(kc, k, pk)) == 0) 
  34. // 則調(diào)用tieBreakOrder繼續(xù)比較大小 
  35. dir = tieBreakOrder(k, pk); 
  36. TreeNode xp = p; 
  37. // 步驟⑥:如果新節(jié)點(diǎn)經(jīng)比較后小于等于當(dāng)前節(jié)點(diǎn)且當(dāng)前節(jié)點(diǎn)的左子節(jié)點(diǎn)為null,則插入新節(jié)點(diǎn),反之亦然 
  38. if ((p = (dir <= 0) ? p.left : p.right) == null) { 
  39. x.parent = xp; 
  40. if (dir <= 0) 
  41. xp.left = x; 
  42. else 
  43. xp.right = x; 
  44. // 步驟⑦:平衡紅黑樹(shù) 
  45. root = balanceInsertion(root, x); 
  46. break; 
  47. // 步驟⑧:確保給定的根節(jié)點(diǎn)是所在桶的第一個(gè)節(jié)點(diǎn) 
  48. moveRootToFront(tab, root); 
  49. }

7. 紅黑樹(shù)的左旋和右旋

紅黑樹(shù)的左旋和右旋其實(shí)很簡(jiǎn)單,所謂的左旋是把要平衡的節(jié)點(diǎn)向左下旋轉(zhuǎn)成一個(gè)葉子節(jié)點(diǎn)。

如下圖所示:

所謂的右旋是把要平衡的節(jié)點(diǎn)向右下旋轉(zhuǎn)成一個(gè)葉子節(jié)點(diǎn)。

如下圖所示:

在 HashMap 的源碼中,左旋的實(shí)現(xiàn)代碼如下:

 
 
 
 
  1. static TreeNode rotateLeft(TreeNode root, 
  2. TreeNode p) { 
  3. // p: 當(dāng)前節(jié)點(diǎn) 
  4. // r: 當(dāng)前節(jié)點(diǎn)的右兒子 
  5. // rl: 當(dāng)前節(jié)點(diǎn)的右兒子的左兒子 
  6. TreeNode r, pp, rl; 
  7. // 如果當(dāng)前節(jié)點(diǎn)和當(dāng)前節(jié)點(diǎn)的右兒子不為空,就左旋 
  8. if (p != null && (r = p.right) != null) { 
  9. // 步驟①:當(dāng)前節(jié)點(diǎn)的右兒子的左兒子成為當(dāng)前節(jié)點(diǎn)的右兒子 
  10. if ((rl = p.right = r.left) != null) 
  11. rl.parent = p; 
  12. // 步驟②:當(dāng)前節(jié)點(diǎn)的右兒子成為當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn) 
  13. if ((pp = r.parent = p.parent) == null) 
  14. // 如果當(dāng)前節(jié)點(diǎn)是根節(jié)點(diǎn),那么r的顏色必須是黑色 
  15. (root = r).red = false; 
  16. else if (pp.left == p) 
  17. pp.left = r; 
  18. else 
  19. pp.right = r; 
  20. r.left = p; 
  21. p.parent = r; 
  22. return root; 
  23. }

右旋的實(shí)現(xiàn)代碼如下:

 
 
 
 
  1. static TreeNode rotateRight(TreeNode root, 
  2. TreeNode p) { 
  3. // p: 當(dāng)前節(jié)點(diǎn) 
  4. // l: 當(dāng)前節(jié)點(diǎn)的左兒子 
  5. // lr: 當(dāng)前節(jié)點(diǎn)的左兒子的右兒子 
  6. TreeNode l, pp, lr; 
  7. // 如果當(dāng)前節(jié)點(diǎn)和當(dāng)前節(jié)點(diǎn)的左兒子不為空,就右旋 
  8. if (p != null && (l = p.left) != null) { 
  9. // 步驟①:當(dāng)前節(jié)點(diǎn)的左兒子的右兒子成為當(dāng)前節(jié)點(diǎn)的左兒子 
  10. if ((lr = p.left = l.right) != null) 
  11. lr.parent = p; 
  12. // 步驟②:當(dāng)前節(jié)點(diǎn)的左兒子成為當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn) 
  13. if ((pp = l.parent = p.parent) == null) 
  14. // 如果當(dāng)前節(jié)點(diǎn)是根節(jié)點(diǎn),那么r的顏色必須是黑色 
  15. (root = l).red = false; 
  16. else if (pp.right == p) 
  17. pp.right = l; 
  18. else 
  19. pp.left = l; 
  20. l.right = p; 
  21. p.parent = l; 
  22. return root; 
  23. }

 8. 紅黑樹(shù)的插入

紅黑樹(shù)是一棵特殊的二叉查找樹(shù),如同二叉查找樹(shù)的插入一樣,紅黑樹(shù)的插入,也需要判斷插入節(jié)點(diǎn)與當(dāng)前節(jié)點(diǎn)的大小。如果插入節(jié)點(diǎn)大于或等于當(dāng)前節(jié)點(diǎn),則插入當(dāng)前節(jié)點(diǎn)的右子樹(shù);否則,插入到左子樹(shù)。循環(huán)此過(guò)程,直到找到為空的葉子節(jié)點(diǎn)放入即可。

 
 
 
 
  1. /** 
  2. * Tree version of putVal. 
  3. */ 
  4. final TreeNode putTreeVal(HashMap map, Node[] tab, 
  5. int h, K k, V v) { 
  6. Class kc = null; 
  7. boolean searched = false; 
  8. // root: 樹(shù)根節(jié)點(diǎn) 
  9. TreeNode root = (parent != null) ? root() : this; 
  10. for (TreeNode p = root;;) { 
  11. // p: 當(dāng)前節(jié)點(diǎn) 
  12. // dir: 標(biāo)識(shí)新節(jié)點(diǎn)應(yīng)該插入到當(dāng)前節(jié)點(diǎn)的左子樹(shù)還是右子樹(shù) 
  13. // ph: 當(dāng)前節(jié)點(diǎn)的hash值 
  14. // pk: 當(dāng)前節(jié)點(diǎn)的key 
  15. int dir, ph; K pk; 
  16. // 步驟①:判斷新節(jié)點(diǎn)應(yīng)該插入到當(dāng)前節(jié)點(diǎn)的左子樹(shù)還是右子樹(shù) 
  17. if ((ph = p.hash) > h) 
  18. dir = -1; 
  19. else if (ph < h) 
  20. dir = 1; 
  21. else if ((pk = p.key) == k || (k != null && k.equals(pk))) 
  22. return p; 
  23. else if ((kc == null && 
  24. (kc = comparableClassFor(k)) == null) || 
  25. (dir = compareComparables(kc, k, pk)) == 0) { 
  26. if (!searched) { 
  27. TreeNode q, ch; 
  28. searched = true; 
  29. if (((ch = p.left) != null && 
  30. (q = ch.find(h, k, kc)) != null) || 
  31. ((ch = p.right) != null && 
  32. (q = ch.find(h, k, kc)) != null)) 
  33. return q; 
  34. dir = tieBreakOrder(k, pk); 
  35. TreeNode xp = p; 
  36. // 步驟②:終于從上向下遍歷到了空的葉子節(jié)點(diǎn),插入新節(jié)點(diǎn) 
  37. if ((p = (dir <= 0) ? p.left : p.right) == null) { 
  38. // 1.父節(jié)點(diǎn)指向新節(jié)點(diǎn) 
  39. Node xpn = xp.next; 
  40. TreeNode x = map.newTreeNode(h, k, v, xpn); 
  41. // 新節(jié)點(diǎn)位置在左子樹(shù)還是右子樹(shù) 
  42. if (dir <= 0) 
  43. xp.left = x; 
  44. else 
  45. xp.right = x; 
  46. xp.next = x; 
  47. // 2.新節(jié)點(diǎn)指向父節(jié)點(diǎn) 
  48. x.parent = x.prev = xp; 
  49. if (xpn != null) 
  50. // 如果有兄弟節(jié)點(diǎn),兄弟節(jié)點(diǎn)的上一個(gè)節(jié)點(diǎn)指向新節(jié)點(diǎn) 
  51. ((TreeNode)xpn).prev = x; 
  52. // 最后平衡紅黑樹(shù),并保證根節(jié)點(diǎn)在哈希桶的頭部 
  53. moveRootToFront(tab, balanceInsertion(root, x)); 
  54. return null; 
  55. }

紅黑樹(shù)的插入與二叉查找樹(shù)的不同之處,在于最后的平衡部分。

插入節(jié)點(diǎn)之后的紅黑樹(shù)平衡方法如下:

 
 
 
 
  1. /** 
  2. * 平衡紅黑樹(shù)-當(dāng)新增后 
  3. * x: 影響平衡的點(diǎn)(俗稱:當(dāng)前節(jié)點(diǎn)) 
  4. */ 
  5. static TreeNode balanceInsertion(TreeNode root, 
  6. TreeNode x) { 
  7. // 新增節(jié)點(diǎn)x默認(rèn)是紅色 
  8. x.red = true; 
  9. // xp父節(jié)點(diǎn) xpp祖父節(jié)點(diǎn) xppl祖父左節(jié)點(diǎn) xppr 祖父右節(jié)點(diǎn)(p: parent, l: left, r: right) 
  10. for (TreeNode xp, xpp, xppl, xppr;;) { 
  11. if ((xp = x.parent) == null) { 
  12. // 步驟①:如果插入的是根節(jié)點(diǎn),根據(jù)紅黑樹(shù)性質(zhì)之根節(jié)點(diǎn)是黑色,直接涂黑即可 
  13. x.red = false; 
  14. return x; 
  15. // 步驟②:插入節(jié)點(diǎn)的父節(jié)點(diǎn)是黑色或者父節(jié)點(diǎn)是根節(jié)點(diǎn),紅黑樹(shù)沒(méi)有被破壞,不需要調(diào)整 
  16. else if (!xp.red || (xpp = xp.parent) == null) 
  17. return root; 
  18. // 父節(jié)點(diǎn)是祖父節(jié)點(diǎn)的左子節(jié)點(diǎn) 
  19. if (xp == (xppl = xpp.left)) { 
  20. // 步驟③:當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn)是紅色,且叔叔節(jié)點(diǎn)也是紅色 
  21. if ((xppr = xpp.right) != null && xppr.red) { 
  22. // 1.叔叔節(jié)點(diǎn)設(shè)置為黑色 
  23. xppr.red = false; 
  24. // 2.父節(jié)點(diǎn)設(shè)置為黑色 
  25. xp.red = false; 
  26. // 3.祖父節(jié)點(diǎn)設(shè)置為紅色 
  27. xpp.red = true; 
  28. // 4.將當(dāng)前節(jié)點(diǎn)指向祖父節(jié)點(diǎn),從新的當(dāng)前節(jié)點(diǎn)重新開(kāi)始算法 
  29. x = xpp; 
  30. else { 
  31. // 步驟④:當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn)是紅色,且叔叔節(jié)點(diǎn)是黑色,當(dāng)前節(jié)點(diǎn)是其父右子節(jié)點(diǎn) 
  32. if (x == xp.right) { 
  33. // 當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn)作為新的當(dāng)前節(jié)點(diǎn),以新當(dāng)節(jié)點(diǎn)為支點(diǎn)左旋 
  34. root = rotateLeft(root, x = xp); 
  35. // 重新賦值 
  36. xpp = (xp = x.parent) == null ? null : xp.parent; 
  37. if (xp != null) { 
  38. // 父節(jié)點(diǎn)設(shè)置為黑色 
  39. xp.red = false; 
  40. if (xpp != null) { 
  41. // 祖父節(jié)點(diǎn)設(shè)置為紅色 
  42. xpp.red = true; 
  43. // 以祖父節(jié)點(diǎn)為支點(diǎn)右旋 
  44. root = rotateRight(root, xpp); 
  45. // 父節(jié)點(diǎn)是祖父節(jié)點(diǎn)的右子節(jié)點(diǎn) 
  46. else { 
  47. // 步驟③:當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn)是紅色,且叔叔節(jié)點(diǎn)也是紅色 
  48. if (xppl != null && xppl.red) { 
  49. // 1.叔叔節(jié)點(diǎn)設(shè)置為黑色 
  50. xppl.red = false; 
  51. // 2.父節(jié)點(diǎn)設(shè)置為黑色 
  52. xp.red = false; 
  53. // 3.祖父節(jié)點(diǎn)設(shè)置為紅色 
  54. xpp.red = true; 
  55. // 4.將當(dāng)前節(jié)點(diǎn)指向祖父節(jié)點(diǎn),從新的當(dāng)前節(jié)點(diǎn)重新開(kāi)始算法 
  56. x = xpp; 
  57. else { 
  58. // 步驟④:當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn)是紅色,且叔叔節(jié)點(diǎn)是黑色,當(dāng)前節(jié)點(diǎn)是其父左子節(jié)點(diǎn) 
  59. if (x == xp.left) { 
  60. // 當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn)作為新的當(dāng)前節(jié)點(diǎn),以新當(dāng)節(jié)點(diǎn)為支點(diǎn)右旋 
  61. root = rotateRight(root, x = xp); 
  62. // 重新賦值 
  63. xpp = (xp = x.parent) == null ? null : xp.parent; 
  64. if (xp != null) { 
  65. // 父節(jié)點(diǎn)設(shè)置為黑色 
  66. xp.red = false; 
  67. if (xpp != null) { 
  68. // 祖父節(jié)點(diǎn)設(shè)置為紅色 
  69. xpp.red = true; 
  70. // 以祖父節(jié)點(diǎn)為支點(diǎn)左旋 
  71. root = rotateLeft(root, xpp); 
  72. }

10. 紅黑樹(shù)的刪除

紅黑樹(shù)的刪除相比插入更加復(fù)雜,需要先從鏈表結(jié)構(gòu)上進(jìn)行刪除,也就是處理當(dāng)前節(jié)點(diǎn)的 next 指針與 prev 指針。如果當(dāng)前樹(shù)的節(jié)點(diǎn)太少,那么就將樹(shù)退化為鏈表。然后,查找被刪除節(jié)點(diǎn)的后繼節(jié)點(diǎn)。所謂后繼節(jié)點(diǎn),就是當(dāng)刪除節(jié)點(diǎn)p后,如果p有子節(jié)點(diǎn),p的子節(jié)點(diǎn)上移繼承其位置。當(dāng)找到后繼節(jié)點(diǎn),立即刪除節(jié)點(diǎn),如果被刪除節(jié)點(diǎn)是紅色,那么不需要平衡,否則平衡紅黑樹(shù)。

紅黑樹(shù)的刪除方法代碼如下:

 
 
 
 
  1. final void removeTreeNode(HashMap map, Node[] tab, 
  2. boolean movable) { 
  3. int n; 
  4. if (tab == null || (n = tab.length) == 0)
  5. // 不處理空的哈希表
  6. return;
  7. // 第 1 部分: 處理鏈表結(jié)構(gòu) 
  8. // 通過(guò)被刪除的key的哈希值查找桶(紅黑樹(shù))的位置
  9. int index = (n - 1) & hash; 
  10. // first、root: 紅黑樹(shù)的根節(jié)點(diǎn) 
  11. TreeNode first = (TreeNode)tab[index], root = first, rl;
  12. // succ: 當(dāng)前節(jié)點(diǎn)(被刪除節(jié)點(diǎn))的下一個(gè)節(jié)點(diǎn)
  13. // pred: 當(dāng)前節(jié)點(diǎn)(被刪除節(jié)點(diǎn))的上一個(gè)節(jié)點(diǎn) 
  14. TreeNode succ = (TreeNode)next, pred = prev;
  15. // 步驟①:從鏈表結(jié)構(gòu)上刪除當(dāng)前節(jié)點(diǎn)(處理上一個(gè)節(jié)點(diǎn)的 next 指針與下一個(gè)節(jié)點(diǎn)的 prev 指針)
  16. if (pred == null) 
  17. // 上一個(gè)節(jié)點(diǎn)為空,說(shuō)明是要?jiǎng)h除的是根節(jié)點(diǎn),將紅黑樹(shù)的根節(jié)點(diǎn)索引改為當(dāng)前節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn) 
  18. tab[index] = first = succ; 
  19. else 
  20. // 刪除的不是根節(jié)點(diǎn),就把當(dāng)前節(jié)點(diǎn)的上一個(gè)節(jié)點(diǎn)的next指向當(dāng)前節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)
  21. pred.next = succ;
  22. if (succ != null) 
  23. // 當(dāng)前節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)不為空,就把下一個(gè)節(jié)點(diǎn)的prev指向當(dāng)前節(jié)點(diǎn)的上一個(gè)節(jié)點(diǎn) 
  24. succ.prev = pred;
  25. // 步驟②:刪除節(jié)點(diǎn)完畢后,紅黑樹(shù)為空,直接返回
  26. if (first == null) 
  27. return;
  28. // 步驟③:更新root指針,并判斷是否需要將樹(shù)轉(zhuǎn)為鏈表結(jié)構(gòu)
  29. if (root.parent != null) 
  30. root = root.root(); 
  31. if (root == null || root.right == null || 
  32. (rl = root.left) == null || rl.left == null) { 
  33. tab[index] = first.untreeify(map); // too small 
  34. return;
  35. }
  36. // 第 2 部分: 處理樹(shù)結(jié)構(gòu) 
  37. // p: 被刪除的節(jié)點(diǎn); replacement: 后繼節(jié)點(diǎn)(刪除節(jié)點(diǎn)p后,如果p有子節(jié)點(diǎn),p的子節(jié)點(diǎn)上移繼承其位置) 
  38. // pl: 被刪除節(jié)點(diǎn)的左兒子; pr: 被刪除節(jié)點(diǎn)的右兒子
  39. TreeNode p = this, pl = left, pr = right, replacement;
  40. // 步驟①:查找被刪除節(jié)點(diǎn)的后繼節(jié)點(diǎn),分以下幾種情況 
  41. // ⑴ 被刪除節(jié)點(diǎn)有左子樹(shù)和右子樹(shù)
  42. if (pl != null && pr != null) { 
  43. TreeNode s = pr, sl;
  44. // 1.查找右子樹(shù)最左葉子節(jié)點(diǎn)s與待刪除節(jié)點(diǎn)p進(jìn)行位置互換 
  45. while ((sl = s.left) != null) // find successor 
  46. s = sl; 
  47. // 2.交換最左葉子節(jié)點(diǎn)和待刪除節(jié)點(diǎn)的顏色 
  48. boolean c = s.red; s.red = p.red; p.red = c; // swap colors 
  49. // sr:最左葉子節(jié)點(diǎn)的右兒子 
  50. TreeNode sr = s.right; 
  51. // pp:被刪除節(jié)點(diǎn)的父節(jié)點(diǎn) 
  52. TreeNode pp = p.parent;
  53. // 3.交換被刪除節(jié)點(diǎn)p和最左葉子節(jié)點(diǎn)s的位置 
  54. // 判斷最左葉子節(jié)點(diǎn)是否是被刪除節(jié)點(diǎn)的右兒子(即右子樹(shù)是否只有一個(gè)節(jié)點(diǎn))分別處理節(jié)點(diǎn)s.right和p.parent的引用 
  55. if (s == pr) { // p was s's direct parent 
  56. p.parent = s; 
  57. s.right = p; 
  58. else { 
  59. TreeNode sp = s.parent; 
  60. if ((p.parent = sp) != null) { 
  61. if (s == sp.left) 
  62. sp.left = p; 
  63. else 
  64. sp.right = p; 
  65. if ((s.right = pr) != null) 
  66. pr.parent = s; 
  67. p.left = null;
  68. // 處理p.right和sr.parent的引用 
  69. if ((p.right = sr) != null) 
  70. sr.parent = p; 
  71. // 處理s.left和pl.parent的引用 
  72. if ((s.left = pl) != null) 
  73. pl.parent = s; 
  74. // 處理s.parent的引用和pp.left或pp.right 
  75. if ((s.parent = pp) == null) 
  76. root = s; 
  77. else if (p == pp.left) 
  78. pp.left = s; 
  79. else 
  80. pp.right = s;
  81. // 4.交換最左葉子節(jié)點(diǎn)和被刪除節(jié)點(diǎn)的位置完成 
  82. // 此時(shí)被刪除節(jié)點(diǎn)p在原最左葉子節(jié)點(diǎn)的位置,現(xiàn)在被刪除節(jié)點(diǎn)p沒(méi)有左子樹(shù),如果有右子樹(shù),那么右兒子sr就是后繼節(jié)點(diǎn),否則后繼節(jié)點(diǎn)指向自身
  83. if (sr != null) 
  84. replacement = sr; 
  85. else 
  86. replacement = p; 
  87. // ⑵ 被刪除節(jié)點(diǎn)只有左子樹(shù),后繼節(jié)點(diǎn)就是左兒子
  88. else if (pl != null) 
  89. replacement = pl; 
  90. // ⑶ 被刪除節(jié)點(diǎn)只有右子樹(shù),后繼節(jié)點(diǎn)就是右兒子 
  91. else if (pr != null) 
  92. replacement = pr; 
  93. // ⑷ 被刪除節(jié)點(diǎn)沒(méi)有子樹(shù),那么后繼節(jié)點(diǎn)就指向自身 
  94. else 
  95. replacement = p; 
  96. // 步驟②:已經(jīng)找到刪除節(jié)點(diǎn)后的后繼節(jié)點(diǎn),這一步將從樹(shù)中徹底刪除節(jié)點(diǎn)p。
  97. if (replacement != p) { 
  98. // 1.修改替代節(jié)點(diǎn)的父節(jié)點(diǎn)引用 
  99. TreeNode pp = replacement.parent = p.parent;
  100. // 2.將被刪除節(jié)點(diǎn)的父節(jié)點(diǎn)對(duì)其的引用進(jìn)行修改 
  101. if (pp == null) 
  102. root = replacement; 
  103. else if (p == pp.left) 
  104. pp.left = replacement; 
  105. else 
  106. pp.right = replacement; 
  107. // 3.徹底刪除節(jié)點(diǎn)p 
  108. p.left = p.right = p.parent = null; 
  109. // 步驟③:刪除節(jié)點(diǎn)完成,刪除的是紅色節(jié)點(diǎn),不需要平衡;否則,平衡 
  110. TreeNode r = p.red ? root : balanceDeletion(root, replacement); 
  111. // 步驟④:若沒(méi)有后繼節(jié)點(diǎn),直接刪除節(jié)點(diǎn)p 
  112. if (replacement == p) { // detach 
  113. TreeNode pp = p.parent; 
  114. p.parent = null; 
  115. if (pp != null) {
  116. if (p == pp.left) 
  117. pp.left = null; 
  118. else if (p == pp.right) 
  119. pp.right = null; 
  120. if (movable) 
  121. // 確保節(jié)點(diǎn)r是樹(shù)根 
  122. moveRootToFront(tab, r); 

刪除紅黑樹(shù)節(jié)點(diǎn)之后的平衡代碼如下,筆者認(rèn)為,應(yīng)當(dāng)重點(diǎn)關(guān)注紅黑樹(shù)的變色、旋轉(zhuǎn)邏輯。

 
 
 
 
  1. /** 
  2. * 平衡紅黑樹(shù)-當(dāng)刪除后 
  3. * x: 影響平衡的點(diǎn)(俗稱:當(dāng)前節(jié)點(diǎn)) 
  4. */
  5. static TreeNode balanceDeletion(TreeNode root, 
  6. TreeNode x) {
  7. // xp: 當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn)
  8. // xpl: 當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn)的左兒子
  9. // xpr: 當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn)的右兒子
  10. for (TreeNode xp, xpl, xpr;;) {
  11. // 步驟①:當(dāng)前節(jié)點(diǎn)是null或者是根節(jié)點(diǎn),不改變紅黑樹(shù)的結(jié)構(gòu),不需要改變
  12. if (x == null || x == root)
  13. return root;
  14. // 步驟②:當(dāng)前節(jié)點(diǎn)成為根節(jié)點(diǎn),置為黑色
  15. else if ((xp = x.parent) == null) {
  16. x.red = false;
  17. return x;
  18. }
  19. // 步驟③:當(dāng)前節(jié)點(diǎn)為紅色,改為黑色后,不影響路徑上黑色的數(shù)量,不需要改變
  20. else if (x.red) {
  21. x.red = false;
  22. return root;
  23. }
  24. // 步驟④:當(dāng)前節(jié)點(diǎn)為父節(jié)點(diǎn)的左兒子
  25. else if ((xpl = xp.left) == x) {
  26. // 如果兄弟節(jié)點(diǎn)是紅色,那么父節(jié)點(diǎn)一定是黑色
  27. if ((xpr = xp.right) != null && xpr.red) {
  28. // 1.兄弟節(jié)點(diǎn)置為黑色
  29. xpr.red = false;
  30. // 2.父節(jié)點(diǎn)置為紅色
  31. xp.red = true;
  32. // 3.以父節(jié)點(diǎn)為支點(diǎn)左旋 
  33. root = rotateLeft(root, xp);
  34. // 4.刷新兄弟節(jié)點(diǎn) 
  35. xpr = (xp = x.parent) == null ? null : xp.right;
  36. }
  37. // 如果兄弟節(jié)點(diǎn)為空,將當(dāng)前節(jié)點(diǎn)向上調(diào)整為父節(jié)點(diǎn),繼續(xù)循環(huán)
  38. if (xpr == null)
  39. x = xp;
  40. else {
  41. // sl: 兄弟節(jié)點(diǎn)的左兒子 
  42. // sr: 兄弟節(jié)點(diǎn)的右兒子 
  43. TreeNode sl = xpr.left, sr = xpr.right; 
  44. if ((sr == null || !sr.red) && 
  45. (sl == null || !sl.red)) { 
  46. // 如果兄弟節(jié)點(diǎn)沒(méi)有紅色孩子,則兄弟節(jié)點(diǎn)置為紅色 
  47. xpr.red = true; 
  48. // 本輪結(jié)束,將當(dāng)前節(jié)點(diǎn)向上調(diào)整為父節(jié)點(diǎn),繼續(xù)循環(huán) 
  49. x = xp;
  50. }
  51. else {
  52. if (sr == null || !sr.red) { 
  53. // 如果兄弟節(jié)點(diǎn)的左兒子是紅色就改為黑色 
  54. if (sl != null) 
  55. sl.red = false; 
  56. // 并將兄弟節(jié)點(diǎn)置為紅色 
  57. xpr.red = true; 
  58. // 以兄弟節(jié)點(diǎn)為支點(diǎn)右旋 
  59. root = rotateRight(root, xpr); 
  60. // 刷新兄弟節(jié)點(diǎn) 
  61. xpr = (xp = x.parent) == null ? 
  62. null : xp.right; 
  63. }
  64. if (xpr != null) { 
  65. // 將兄弟節(jié)點(diǎn)的顏色染成和父節(jié)點(diǎn)一樣 
  66. xpr.red = (xp == null) ? false : xp.red;
  67. // 將兄弟節(jié)點(diǎn)的右兒子染成黑色,防止出現(xiàn)兩個(gè)紅色節(jié)點(diǎn)相連 
  68. if ((sr = xpr.right) != null) 
  69. sr.red = false; 
  70. }
  71. if (xp != null) { 
  72. // 父節(jié)點(diǎn)置為黑色,并對(duì)其左旋,這樣就能保證被刪除的節(jié)點(diǎn)所在的路徑又多了一個(gè)黑色節(jié)點(diǎn),從而達(dá)到恢復(fù)平衡的目的 
  73. xp.red = false; 
  74. root = rotateLeft(root, xp); 
  75. }
  76. // 調(diào)整完畢,下一次循環(huán)直接退出 
  77. x = root; 
  78. }
  79. // 步驟⑤:當(dāng)前節(jié)點(diǎn)為父節(jié)點(diǎn)的右兒子,同上
  80. else { // symmetric 
  81. if (xpl != null && xpl.red) { 
  82. xpl.red = false; 
  83. xp.red = true; 
  84. root = rotateRight(root, xp); 
  85. xpl = (xp = x.parent) == null ? null : xp.left; 
  86. }
  87. if (xpl == null) 
  88. x = xp;
  89. else { 
  90. TreeNode sl = xpl.left, sr = xpl.right; 
  91. if ((sl == null || !sl.red) &&
  92. (sr == null || !sr.red)) { 
  93. xpl.red = true; 
  94. x = xp;
  95. }
  96. else { 
  97. if (sl == null || !sl.red) { 
  98. if (sr != null) 
  99. sr.red = false; 
  100. xpl.red = true; 
  101. root = rotateLeft(root, xpl); 
  102. xpl = (xp = x.parent) == null ? 
  103. null : xp.left;
  104. }

  105. 分享題目:逐行解讀HashMap源碼之紅黑樹(shù)篇
    標(biāo)題URL:http://m.5511xx.com/article/cojpdgp.html