本篇是 TreeMap 和紅黑樹源碼分析的最後一篇了,此次會結合 TreeMap 的源碼教你們紅黑樹刪除節點的算法。紅黑樹的刪除算法要比插入更爲複雜些,可是也沒必要擔憂,本文會用簡單明瞭的解釋,並結合 JDK 的源碼讓你瞭解紅黑樹的刪除算法。java
在正文開始以前,還請你們確保本身理解以前兩篇文章中講述的知識點,若是有些遺忘也不妨再次快速的複習一下。node
Java Collections Framework 源碼分析(5.1 - Map, TreeMap, 紅黑樹)算法
Java Collections Framework 源碼分析(5.2 - TreeMap, 紅黑樹的插入)segmentfault
Map
上的 remove
方法的做用是從容器內移除鍵值對,咱們先看一下 TreeMap
上的實現:微信
public V remove(Object key) { Entry<K,V> p = getEntry(key); if (p == null) return null; V oldValue = p.value; deleteEntry(p); return oldValue; }
經過 getEntry
方法從紅黑樹中獲取對應的 Entry
對象,若是對應 key 的 Entry
不存在則直接返回 null。不然會執行 deleteEntry
方法,並將返回舊的值。讓咱們繼續往下看 deleteEntry
方法的實現。數據結構
從 deleteEntry
的代碼來看主要分爲兩個部分:源碼分析
讓咱們先了解一下平衡二叉樹刪除節點的算法,具體算法其實很簡單,須要區分兩種不一樣的狀態。咱們先說簡單的,若是當前刪除的節點只有一個非空子節點,那麼只須要直接刪除就好了,即把本身子節點和父節點創建鏈接關係便可。而當本身有兩個非空子節點時,則須要按照下面的順序進行刪除操做:學習
在詳細解釋以前,咱們先說前驅和後繼的概念。由於平衡二叉樹實質上是一種排序的數據結構,若是把它拉成一條直線,其實就是一個鏈表。而前驅的意思就是小於且最接近當前節點的節點,相應的後繼就是大於且最接近的節點,具體能夠看下面的圖:spa
假設圖中40(紅色)的節點爲當前節點,那麼35(藍色)節點爲前驅節點,45(綠色)節點爲後繼節點。操作系統
瞭解了這些背景知識以後,能夠看一下 deleteEntry
的代碼了。
// If strictly internal, copy successor's element to p and then make p // point to successor. if (p.left != null && p.right != null) { Entry<K,V> s = successor(p); p.key = s.key; p.value = s.value; p = s; } // p has 2 children
這部分代碼按照咱們以前所說的算法,先執行了有兩個非空子節點狀況下的邏輯,同時這裏選擇的是使用 successor 後繼節點進行替換。很容易看出在使用 successor
得到當前節點的後繼節點後,將後繼節點的值複製給了當前節點,而後將須要刪除節點的引用指向了後繼節點。
successor
方法我就不在解釋了,我建議你能夠去看一下,看看是否符合咱們以前的定義。相應的,在 TreeMap
的源碼中還有一個 predecessor
方法是獲取當前節點前驅節點,也值得看一下。
而後咱們接着往下看:
// Start fixup at replacement node, if it exists. Entry<K,V> replacement = (p.left != null ? p.left : p.right); if (replacement != null) { // Link replacement to parent replacement.parent = p.parent; if (p.parent == null) root = replacement; else if (p == p.parent.left) p.parent.left = replacement; else p.parent.right = replacement; // Null out links so they are OK to use by fixAfterDeletion. p.left = p.right = p.parent = null; // Fix replacement if (p.color == BLACK) fixAfterDeletion(replacement); }
這裏是進行節點刪除的具體代碼,此時須要刪除的 P 節點有可能已是指向後繼節點了,可是不管如何,刪除的邏輯都是同樣的,都是從新創建父節點與子節點之間的關聯,並移除與要刪除節點間的關聯。至此第一步刪除節點的操做已經完成了,接下來就是要對樹進行從新平衡,以符合紅黑樹的要求。
從上面代碼片斷中能夠看出,在進行節點刪除以後,調用了 fixAfterDeletion
方法,還記得上一篇中有個相似的 fixAfterInsertion
嗎?不難猜出,這個 fixAfterDeletion
就是在刪除節點後對二叉樹從新平衡的方法,讓咱們先參考 wiki 上的算法定義。
相對插入而言刪除的算法稍微更復雜些,須要執行 6 步操做。但也不用慌,耐心往下看(算法描述依然採用以前的 N,P 等縮寫)。
S 節點,若是 S 節點顏色爲紅色:
N 是否爲 P 的左節點
執行第 3 步操做
P,S,S.left,S.right 這些節點的顏色都爲黑色:
不然執行第 4 步
P 爲紅色,S,S.left,S.right 都爲黑色:
不然執行第 5 步
S 的顏色爲黑色
N 爲 P 的左節點,S.right 爲黑色,S.left 爲紅色
N 爲 P 的右節點,S.right 爲紅色,S.left 爲黑色
執行第 6 步操做
P 改成黑色
1. N 爲 P 的左節點 1. S.right 改成黑色 2. 將 P 左轉 2. N 爲 P 的右節點 1. S.left 改成黑色 2. 將 P 右轉
上手一看算法邏輯可能很繁瑣,其實仔細看一下,不少都是對稱的,你只須要記住一半就好了。如今結合 TreeMap
的代碼看一下:
private void fixAfterDeletion(Entry<K,V> x) { while (x != root && colorOf(x) == BLACK) { if (x == leftOf(parentOf(x))) { Entry<K,V> sib = rightOf(parentOf(x)); if (colorOf(sib) == RED) { setColor(sib, BLACK); setColor(parentOf(x), RED); rotateLeft(parentOf(x)); sib = rightOf(parentOf(x)); } if (colorOf(leftOf(sib)) == BLACK && colorOf(rightOf(sib)) == BLACK) { setColor(sib, RED); x = parentOf(x); } else { if (colorOf(rightOf(sib)) == BLACK) { setColor(leftOf(sib), BLACK); setColor(sib, RED); rotateRight(sib); sib = rightOf(parentOf(x)); } setColor(sib, colorOf(parentOf(x))); setColor(parentOf(x), BLACK); setColor(rightOf(sib), BLACK); rotateLeft(parentOf(x)); x = root; } } ......
能夠看到 fixAfterDeletetion
方法的邏輯與咱們描述的算法在順序上稍有不一樣,它在一開始的分支條件是區分當前節點是左節點仍是右節點。緊接着的:
if (colorOf(sib) == RED) { setColor(sib, BLACK); setColor(parentOf(x), RED); rotateLeft(parentOf(x)); sib = rightOf(parentOf(x)); }
這部分能夠看到對應咱們算法的第 2 步。須要注意的是 sib = rightOf(parentOf(x));
,那是由於發生旋轉後,sib 也發生了變化,須要從新獲取。接下來的:
if (colorOf(leftOf(sib)) == BLACK && colorOf(rightOf(sib)) == BLACK) { setColor(sib, RED); x = parentOf(x); }
也能看到是對應算法的第 3 步,將 x 設爲了 parent,而後從新開始 while 循環。以後的分支也均可以對應到算法的剩餘步驟,我把這部分代碼解讀的工做流給你了,不妨本身拿筆和紙出來,將代碼和我所描述的算法一一對應,看看能不能對上。
至此,TreeMap
和紅黑樹的全部代碼都分析完了。最後這篇節點刪除算法拖了好久,期間有人也私信問我,爲何要學習紅黑樹?爲何要學習數據結構?咱們工做中就是 CRUD ,什麼排序,什麼查找,都是用現成的呀,有問題嗎?這是個頗有趣的問題,在我工做過程當中有不少人問過我相似的問題,能夠把數據結構和紅黑樹替換成其餘的許多名詞,例如操做系統,編譯原理,JVM 等等,等等。我想後面會花時間用一篇單獨的文章來回答這個,而在這裏我只想說對於這些底層知識的掌握,決定了你能力的上限,換而言之也決定了你能作什麼和不能作什麼 。
接下來應該是 Java Collections Framework 最後一部分了,也就是 HashMap
的源碼解析,但願你不要錯過。
歡迎關注個人微信號「且把金針度與人」,獲取更多高質量文章