死磕 java集合之TreeMap源碼分析(三)- 內含紅黑樹分析全過程

2019獨角獸企業重金招聘Python工程師標準>>> hot3.png

歡迎關注我的公眾號“彤哥讀源碼”,查看更多源碼系列文章, 與彤哥一起暢游源碼的海洋。


刪除元素

刪除元素本身比較簡單,就是采用二叉樹的刪除規則。

(1)如果刪除的位置有兩個葉子節點,則從其右子樹中取最小的元素放到刪除的位置,然后把刪除位置移到替代元素的位置,進入下一步。

(2)如果刪除的位置只有一個葉子節點(有可能是經過第一步轉換后的刪除位置),則把那個葉子節點作為替代元素,放到刪除的位置,然后把這個葉子節點刪除。

(3)如果刪除的位置沒有葉子節點,則直接把這個刪除位置的元素刪除即可。

(4)針對紅黑樹,如果刪除位置是黑色節點,還需要做再平衡。

(5)如果有替代元素,則以替代元素作為當前節點進入再平衡。

(6)如果沒有替代元素,則以刪除的位置的元素作為當前節點進入再平衡,平衡之后再刪除這個節點。

public V remove(Object key) {// 獲取節點Entry<K,V> p = getEntry(key);if (p == null)return null;V oldValue = p.value;// 刪除節點deleteEntry(p);// 返回刪除的valuereturn oldValue;
}private void deleteEntry(Entry<K,V> p) {// 修改次數加1modCount++;// 元素個數減1size--;if (p.left != null && p.right != null) {// 如果當前節點既有左子節點,又有右子節點// 取其右子樹中最小的節點Entry<K,V> s = successor(p);// 用右子樹中最小節點的值替換當前節點的值p.key = s.key;p.value = s.value;// 把右子樹中最小節點設為當前節點p = s;// 這種情況實際上并沒有刪除p節點,而是把p節點的值改了,實際刪除的是p的后繼節點}// 如果原來的當前節點(p)有2個子節點,則當前節點已經變成原來p的右子樹中的最小節點了,也就是說其沒有左子節點了// 到這一步,p肯定只有一個子節點了// 如果當前節點有子節點,則用子節點替換當前節點Entry<K,V> replacement = (p.left != null ? p.left : p.right);if (replacement != null) {// 把替換節點直接放到當前節點的位置上(相當于刪除了p,并把替換節點移動過來了)replacement.parent = p.parent;if (p.parent == null)root = replacement;else if (p == p.parent.left)p.parent.left  = replacement;elsep.parent.right = replacement;// 將p的各項屬性都設為空p.left = p.right = p.parent = null;// 如果p是黑節點,則需要再平衡if (p.color == BLACK)fixAfterDeletion(replacement);} else if (p.parent == null) {// 如果當前節點就是根節點,則直接將根節點設為空即可root = null;} else {// 如果當前節點沒有子節點且其為黑節點,則把自己當作虛擬的替換節點進行再平衡if (p.color == BLACK)fixAfterDeletion(p);// 平衡完成后刪除當前節點(與父節點斷絕關系)if (p.parent != null) {if (p == p.parent.left)p.parent.left = null;else if (p == p.parent.right)p.parent.right = null;p.parent = null;}}
}

刪除再平衡

經過上面的處理,真正刪除的肯定是黑色節點才會進入到再平衡階段。

因為刪除的是黑色節點,導致整顆樹不平衡了,所以這里我們假設把刪除的黑色賦予當前節點,這樣當前節點除了它自已的顏色還多了一個黑色,那么:

(1)如果當前節點是根節點,則直接涂黑即可,不需要再平衡;

(2)如果當前節點是紅+黑節點,則直接涂黑即可,不需要平衡;

(3)如果當前節點是黑+黑節點,則我們只要通過旋轉把這個多出來的黑色不斷的向上傳遞到一個紅色節點即可,這又可能會出現以下四種情況:

(假設當前節點為父節點的左子節點)

情況策略
1)x是黑+黑節點,x的兄弟是紅節點(1)將兄弟節點設為黑色;<br>(2)將父節點設為紅色;<br>(3)以父節點為支點進行左旋;<br>(4)重新設置x的兄弟節點,進入下一步;
2)x是黑+黑節點,x的兄弟是黑節點,且兄弟節點的兩個子節點都是黑色(1)將兄弟節點設置為紅色;<br>(2)將x的父節點作為新的當前節點,進入下一次循環;
3)x是黑+黑節點,x的兄弟是黑節點,且兄弟節點的右子節點為黑色,左子節點為紅色(1)將兄弟節點的左子節點設為黑色;<br>(2)將兄弟節點設為紅色;<br>(3)以兄弟節點為支點進行右旋;<br>(4)重新設置x的兄弟節點,進入下一步;
3)x是黑+黑節點,x的兄弟是黑節點,且兄弟節點的右子節點為紅色,左子節點任意顏色(1)將兄弟節點的顏色設為父節點的顏色;<br>(2)將父節點設為黑色;<br>(3)將兄弟節點的右子節點設為黑色;<br>(4)以父節點為支點進行左旋;<br>(5)將root作為新的當前節點(退出循環);

(假設當前節點為父節點的右子節點,正好反過來)

情況策略
1)x是黑+黑節點,x的兄弟是紅節點(1)將兄弟節點設為黑色;<br>(2)將父節點設為紅色;<br>(3)以父節點為支點進行右旋;<br>(4)重新設置x的兄弟節點,進入下一步;
2)x是黑+黑節點,x的兄弟是黑節點,且兄弟節點的兩個子節點都是黑色(1)將兄弟節點設置為紅色;<br>(2)將x的父節點作為新的當前節點,進入下一次循環;
3)x是黑+黑節點,x的兄弟是黑節點,且兄弟節點的左子節點為黑色,右子節點為紅色(1)將兄弟節點的右子節點設為黑色;<br>(2)將兄弟節點設為紅色;<br>(3)以兄弟節點為支點進行左旋;<br>(4)重新設置x的兄弟節點,進入下一步;
3)x是黑+黑節點,x的兄弟是黑節點,且兄弟節點的左子節點為紅色,右子節點任意顏色(1)將兄弟節點的顏色設為父節點的顏色;<br>(2)將父節點設為黑色;<br>(3)將兄弟節點的左子節點設為黑色;<br>(4)以父節點為支點進行右旋;<br>(5)將root作為新的當前節點(退出循環);

讓我們來看看TreeMap中的實現:

/*** 刪除再平衡*(1)每個節點或者是黑色,或者是紅色。*(2)根節點是黑色。*(3)每個葉子節點(NIL)是黑色。(注意:這里葉子節點,是指為空(NIL或NULL)的葉子節點!)*(4)如果一個節點是紅色的,則它的子節點必須是黑色的。*(5)從一個節點到該節點的子孫節點的所有路徑上包含相同數目的黑節點。*/
private void fixAfterDeletion(Entry<K,V> x) {// 只有當前節點不是根節點且當前節點是黑色時才進入循環while (x != root && colorOf(x) == BLACK) {if (x == leftOf(parentOf(x))) {// 如果當前節點是其父節點的左子節點// sib是當前節點的兄弟節點Entry<K,V> sib = rightOf(parentOf(x));// 情況1)如果兄弟節點是紅色if (colorOf(sib) == RED) {// (1)將兄弟節點設為黑色setColor(sib, BLACK);// (2)將父節點設為紅色setColor(parentOf(x), RED);// (3)以父節點為支點進行左旋rotateLeft(parentOf(x));// (4)重新設置x的兄弟節點,進入下一步sib = rightOf(parentOf(x));}if (colorOf(leftOf(sib))  == BLACK &&colorOf(rightOf(sib)) == BLACK) {// 情況2)如果兄弟節點的兩個子節點都是黑色// (1)將兄弟節點設置為紅色setColor(sib, RED);// (2)將x的父節點作為新的當前節點,進入下一次循環x = parentOf(x);} else {if (colorOf(rightOf(sib)) == BLACK) {// 情況3)如果兄弟節點的右子節點為黑色// (1)將兄弟節點的左子節點設為黑色setColor(leftOf(sib), BLACK);// (2)將兄弟節點設為紅色setColor(sib, RED);// (3)以兄弟節點為支點進行右旋rotateRight(sib);// (4)重新設置x的兄弟節點sib = rightOf(parentOf(x));}// 情況4)// (1)將兄弟節點的顏色設為父節點的顏色setColor(sib, colorOf(parentOf(x)));// (2)將父節點設為黑色setColor(parentOf(x), BLACK);// (3)將兄弟節點的右子節點設為黑色setColor(rightOf(sib), BLACK);// (4)以父節點為支點進行左旋rotateLeft(parentOf(x));// (5)將root作為新的當前節點(退出循環)x = root;}} else { // symmetric// 如果當前節點是其父節點的右子節點// sib是當前節點的兄弟節點Entry<K,V> sib = leftOf(parentOf(x));// 情況1)如果兄弟節點是紅色if (colorOf(sib) == RED) {// (1)將兄弟節點設為黑色setColor(sib, BLACK);// (2)將父節點設為紅色setColor(parentOf(x), RED);// (3)以父節點為支點進行右旋rotateRight(parentOf(x));// (4)重新設置x的兄弟節點sib = leftOf(parentOf(x));}if (colorOf(rightOf(sib)) == BLACK &&colorOf(leftOf(sib)) == BLACK) {// 情況2)如果兄弟節點的兩個子節點都是黑色// (1)將兄弟節點設置為紅色setColor(sib, RED);// (2)將x的父節點作為新的當前節點,進入下一次循環x = parentOf(x);} else {if (colorOf(leftOf(sib)) == BLACK) {// 情況3)如果兄弟節點的左子節點為黑色// (1)將兄弟節點的右子節點設為黑色setColor(rightOf(sib), BLACK);// (2)將兄弟節點設為紅色setColor(sib, RED);// (3)以兄弟節點為支點進行左旋rotateLeft(sib);// (4)重新設置x的兄弟節點sib = leftOf(parentOf(x));}// 情況4)// (1)將兄弟節點的顏色設為父節點的顏色setColor(sib, colorOf(parentOf(x)));// (2)將父節點設為黑色setColor(parentOf(x), BLACK);// (3)將兄弟節點的左子節點設為黑色setColor(leftOf(sib), BLACK);// (4)以父節點為支點進行右旋rotateRight(parentOf(x));// (5)將root作為新的當前節點(退出循環)x = root;}}}// 退出條件為多出來的黑色向上傳遞到了根節點或者紅節點// 則將x設為黑色即可滿足紅黑樹規則setColor(x, BLACK);
}

刪除元素舉例

假設我們有下面這樣一顆紅黑樹。

treemap-delete1

我們刪除6號元素,則從右子樹中找到了最小元素7,7又沒有子節點了,所以把7作為當前節點進行再平衡。

我們看到7是黑節點,且其兄弟為黑節點,且其兄弟的兩個子節點都是紅色,滿足情況4),平衡之后如下圖所示。

treemap-delete2

我們再刪除7號元素,則從右子樹中找到了最小元素8,8有子節點且為黑色,所以8的子節點9是替代節點,以9為當前節點進行再平衡。

我們發現9是紅節點,則直接把它涂成黑色即滿足了紅黑樹的特性,不需要再過多的平衡了。

treemap-delete3

這次我們來個狠的,把根節點刪除,從右子樹中找到了最小的元素5,5沒有子節點,所以把5作為當前節點進行再平衡。

我們看到5是黑節點,且其兄弟為紅色,符合情況1),平衡之后如下圖所示,然后進入情況2)。

treemap-delete4

對情況2)進行再平衡后如下圖所示。

treemap-delete5

然后進入下一次循環,發現不符合循環條件了,直接把x涂為黑色即可,退出這個方法之后會把舊x刪除掉(見deleteEntry()方法),最后的結果就是下面這樣。

treemap-delete6


未完待續,下一節我們一起探討紅黑樹遍歷元素的操作。

現在公眾號文章沒辦法留言了,如果有什么疑問或者建議請直接在公眾號給我留言。


歡迎關注我的公眾號“彤哥讀源碼”,查看更多源碼系列文章, 與彤哥一起暢游源碼的海洋。

qrcode

轉載于:https://my.oschina.net/u/4108008/blog/3032739

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/251991.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/251991.shtml
英文地址,請注明出處:http://en.pswp.cn/news/251991.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Linux:進程實例信息(/proc)

https://blog.csdn.net/test1280/article/details/73632333 Linux:進程實例信息&#xff08;/proc&#xff09; 問幾個問題&#xff1a; 1.怎么知道一個進程對應哪個可執行文件&#xff1f; 2.怎么知道一個進程的資源限制&#xff1f; 3.怎么知道一個進程所處的環境&#xff1f…

四元素理解

旋轉變換_四元數 2017年03月29日 11:59:38 csxiaoshui 閱讀數&#xff1a;5686 1.簡介 四元數是另一種描述三維旋轉的方式&#xff0c;四元數使用4個分量來描述旋轉&#xff0c;四元數的描述方式如下&#xff1a; qsxiyjzk,(s,x,y,z∈?&#xff09;i2j2k2ijk?1 四元數的由…

31、SAM文件中flag含義解釋工具--轉載

轉載&#xff1a;http://www.cnblogs.com/nkwy2012/p/6362996.html SAM是Sequence Alignment/Map 的縮寫。像bwa等軟件序列比對結果都會輸出這樣的文件。samtools網站上有專門的文檔介紹SAM文件。具體地址&#xff1a;http://samtools.sourceforge.net/SAM1.pdf很多人困惑SAM文…

《Head First設計模式》批注系列(一)——觀察者設計模式

最近在讀《Head First設計模式》一書&#xff0c;此系列會引用源書內容&#xff0c;但文章內容會更加直接&#xff0c;以及加入一些自己的理解。 觀察者模式&#xff08;有時又被稱為模型-視圖&#xff08;View&#xff09;模式、源-收聽者(Listener)模式或從屬者模式&#xff…

PYPL 4 月排行:Python 最流行,Java 還行不行?

開發四年只會寫業務代碼&#xff0c;分布式高并發都不會還做程序員&#xff1f; PYPL 發布了 4 月份的編程語言排行榜。 前五的分別是&#xff1a;Python、Java、Javascript、C# 和 PHP。可以看到&#xff0c;榜單沒有什么大變化&#xff0c;但是相比去年 4 月份&#xff0c;…

兩個向量的旋轉矩陣與四元素

兩向量的夾角 2017年06月20日 17:38:11 csxiaoshui 閱讀數&#xff1a;36764 怎么計算兩個向量間的夾角呢&#xff1f; 這里主要分兩種情況&#xff0c;對于二維向量和三維向量來分別討論。 1. 二維向量 二維向量的情況相對簡單&#xff0c;根據向量間的點乘關系 v1?v2|…

順序表

一、數據是如何在內存中存儲的&#xff1f; 32位系統中char&#xff0c;int型數據在內存中的存儲方式&#xff1a; char占1byte&#xff08;8bit&#xff09;int占4byte&#xff08;32bit&#xff09;假設我們有一個int類型的值&#xff0c;它從0x01開始&#xff0c;一個int占據…

Establishing SSL connection without server's identity verification is not recommended.

完全描述:Establishing SSL connection without servers identity verification is not recommended. According to MySQL 5.5.45, 5.6.26 and 5.7.6 requirements SSL connection must be established by default if explicit option isnt set. For compliance with existing …

四元素的真面目..........簡單粗暴

作者&#xff1a;Yang Eninala 鏈接&#xff1a;https://www.zhihu.com/question/23005815/answer/33971127 來源&#xff1a;知乎 著作權歸作者所有。商業轉載請聯系作者獲得授權&#xff0c;非商業轉載請注明出處。 根據我的理解&#xff0c;大多數人用漢密爾頓四元數就只…

2.自定義變量調節器

① 使用registerPlugin()方法來擴充變量調節器 該方法接收3個參數 1. 字符串modifier 2. 插件函數的名字 3. PHP回調函數 示例&#xff1a;自定義一個變量調節器&#xff0c;可以改變文字的顏色和大小 第一步&#xff1a;調用smarty對象的registerPlugin&#xff08;&#x…

SpringBoot2構建基于RBAC權限模型的駕校代理小程序后端

本項目是使用SpringBoot2構建的一套基于RBAC權限模型的后臺管理系統&#xff0c;前端是微信小程序。 項目地址: github.com/fuyunwang/D… 項目的緣由 最近接了個外包,主要是針對于駕校開發一個代理小程序。目的是為了方便駕校的管理來招攬學員,同時方便維護學員和代理信息。 項…

while read line的問題

循環中的重定向或許你應該在其他腳本中見過下面的這種寫法&#xff1a;while read linedo…done < file剛開始看到這種結構時&#xff0c;很難理解< file是如何與循環配合在一起工作的。因為循環內有很多條命令&#xff0c;而我們之前接觸的重定向都是為一條命令工作的。…

Linemod;理解

Linemod 代碼筆記 2019年03月11日 16:18:30 haithink 閱讀數&#xff1a;197 最近了解到 Linemod 這個模板匹配算法&#xff0c;印象不錯 準備仔細學習一下&#xff0c;先做點代碼筆記&#xff0c;免得后面不好回顧 目前的筆記基本上把 核心流程都分析得比較清楚了&#xff0…

Swift3中數組創建方法

轉載自&#xff1a;http://blog.csdn.net/bwf_erg/article/details/70858865 數組是由一組類型相同的元素構成的有序數據集合。數組中的集合元素是有 序的&#xff0c;而且可以重復出現。 1 數組創建 在Swift語言中&#xff0c;數組的類型格式為&#xff1a; Array<ElementT…

BZOJ 5249: [2018多省省隊聯測]IIIDX(貪心 + 線段樹)

題意 這一天&#xff0c;\(\mathrm{Konano}\) 接到了一個任務&#xff0c;他需要給正在制作中的游戲 \(\mathrm{《IIIDX》}\) 安排曲目 的解鎖順序。游戲內共有\(n\) 首曲目&#xff0c;每首曲目都會有一個難度 \(d\) &#xff0c;游戲內第 \(i\) 首曲目會在玩家 Pass 第 \(\lf…

手眼標定

Eye-in-hand和Eye-to-hand問題求解和實驗 2018年12月07日 00:00:40 百川木易 閱讀數 3018 2018/12/5 By Yang Yang&#xff08;yangyangipp.ac.cn&#xff09; 本文所有源碼和仿真場景文件全部公開&#xff0c;點擊Gitee倉庫鏈接。 文章目錄 問題描述Eye-in-hand問題求解公式…

RNN總結

RNN既可以表述為循環神 經網絡&#xff08;recurrent neural network&#xff09;&#xff0c;也可以表述為遞歸神經網絡&#xff08;recursive neural network&#xff09;&#xff0c;前者一般用于處理以時間序列為輸入的問題&#xff08;比如把一個句子看成詞組成的序列&…

Problem 2. number題解

number&#xff1a;數學二分圖匹配 首先&#xff0c;如果S<N,那么S1&#xff0c;S2...N這些數直接放在S1,S2...N的位置上(如果其他數x放在這些位置上面&#xff0c;這些數不放在對應位置&#xff0c;那么x一定能放在這些數放的位置&#xff0c;所以直接交換即可)所以可以直接…

SSD列子

一、介紹 本博文主要介紹實現通過SSD物體檢測方式實現工件裂紋檢測。裂紋圖像如下所示&#xff1a; 二、關于SSD算法 具體算法不再闡述&#xff0c;詳細請參考&#xff1a; https://blog.csdn.net/u013989576/article/details/73439202 https://blog.csdn.net/xiaohu2022/arti…

linux硬鏈接與軟鏈接

Linux 系統中有軟鏈接和硬鏈接兩種特殊的“文件”。 軟鏈接可以看作是Windows中的快捷方式&#xff0c;可以讓你快速鏈接到目標檔案或目錄。 硬鏈接則透過文件系統的inode來產生新檔名&#xff0c;而不是產生新檔案。 創建方法都很簡單&#xff1a; 軟鏈接&#xff08;符號鏈接…