ava 集合 | 紅黑樹 | 前置知識
一、前言
為啥要學紅黑樹吖?
因為筆者最近在趕專案的時候,不忘抽出時間來複習 Java
基礎知識,現在準備看集合的原始碼啦啦。聽聞,HashMap
在 jdk 1.8
的時候,底層的資料結構發生了變化,變成了陣列+連結串列+紅黑樹。很好,沒了解過紅黑樹,所以就趁今天閒暇學習一下啦
二、什麼是紅黑樹?
2.1 有啥用處?
紅黑樹從本質上來說就是一顆二叉查詢樹,但是在二叉樹的基礎上增加了著色相關的性質,使得紅黑樹可以保證相對平衡,從而保證紅黑樹的增刪改查的時間複雜度最壞也能達到 O(log N)
。
2.2 紅黑樹的六條性質你知道嗎?
-
每個節點要麼是黑的,要麼是紅的
-
根節點是黑的
-
葉節點是黑的
-
如果一個節點是紅的,他的兩個兒子節點都是黑的
-
對於任一節點而言,其到葉節點樹尾端NIL指標的每一條路徑都包含相同數目的黑節點。這其實就是黑高啦!
-
新插入的節點必須是紅色噢!
2.3 插入操作
首先,先看這個圖吧,這就是全部的插入操作後,平衡的方法啦!其實我還是喜歡用筆畫 hhh
紅黑樹的概念理解起來較為複雜,我們以一個簡單的示例,看看如何構造一棵紅黑樹。
現有陣列int[] a = {1, 10, 9, 2, 3, 8, 7, 4, 5, 6};
我們要將其變為一棵紅黑樹。
首先插入1,此時樹是空的,1就是根結點,根結點是黑色的:
首先插入 1,此時樹是空的,1 就是根結點,根結點是黑色的:
插入 1
然後插入元素 10,此時依然符合規則,結果如下:
插入 10
當插入元素 9 時,這時是需要調整的第一種情況,結果如下:
插入 9
紅黑樹規則 4 中強調不能有兩個相鄰的紅色結點,所以此時我們需要對其進行調整。調整的原則有多個相關因素,這裡的情況是,父結點 10 是其祖父結點 1(父結點的父結點)的右孩子,當前結點 9 是其父結點 10 的左孩子,且沒有叔叔結點(父結點的兄弟結點),此時需要進行兩次旋轉,第一次,以父結點 10 右旋:
右旋
然後將父結點**(此時是 9)**染為黑色,祖父結點 1 染為紅色,如下所示:
染色
然後以祖父結點 1 左旋:
左旋
下一步,插入元素 2,結果如下:
插入 2
此時情況與上一步類似,區別在於父結點 1 是祖父結點 9 的左孩子,當前結點 2 是父結點的右孩子,且叔叔結點 10 是紅色的。這時需要先將叔叔結點 10 染為黑色,再進行下一步操作,具體做法是將父結點 1 和叔叔結點 10 染為黑色,祖父結點 9 染為紅色,如下所示:
染色
由於結點 9 是根節點,必須為黑色,將它染為黑色即可:
染色
下一步,插入元素 3,如下所示:
插入 3
這和我們之前插入元素 10 的情況一模一樣,需要將父結點 2 染為黑色,祖父結點 1 染為紅色,如下所示:
染色
然後左旋:
左旋
下一步,插入元素 8,結果如下:
插入 8
此時和插入元素 2 有些類似,區別在於父結點 3 是右孩子,當前結點 8 也是右孩子,這時也需要先將叔叔結點 1 染為黑色,具體操作是先將 1 和 3 染為黑色,再將祖父結點 2 染為紅色,如下所示:
染色
此時樹已經平衡了,不需要再進行其他操作了,現在插入元素 7,如下所示:
插入 7
這時和之前插入元素 9 時一模一樣了,先將 7 和 8 右旋,如下所示:
右旋
然後將 7 染為黑色,3 染為紅色,再進行左旋,結果如下:
左旋
下一步要插入的元素是 4,結果如下:
插入 4
這裡和插入元素 2 是類似的,先將 3 和 8 染為黑色,7 染為紅色,如下所示:
染色
但此時 2 和 7 相鄰且顏色均為紅色,我們需要對它們繼續進行調整。這時情況變為了父結點 2 為紅色,叔叔結點 10 為黑色,且 2為左孩子,7 為右孩子,這時需要以 2 左旋。這時左旋與之前不同的地方在於結點 7 旋轉完成後將有三個孩子,結果類似於下圖:
錯誤示意圖
這種情況處理起來也很簡單,只需要把 7 原來的左孩子 3,變成 2 的右孩子即可,結果如下:
調整
然後再把 2 的父結點 7 染為黑色,祖父結點 9 染為紅色。結果如下所示:
染色
此時又需要右旋了,我們要以 9 右旋,右旋完成後 7 又有三個孩子,這種情況和上述是對稱的,我們把 7 原有的右孩子 8,變成 9的左孩子即可,如下所示:
右旋
下一個要插入的元素是 5,插入後如下所示:
插入 5
有了上述一些操作,處理 5 變得十分簡單,將 3 染為紅色,4 染為黑色,然後左旋,結果如下所示:
左旋
最後插入元素 6,如下所示:
插入 6
又是叔叔結點 3 為紅色的情況,這種情況我們處理過多次了,首先將 3 和 5 染為黑色,4 染為紅色,結果如下:
染色
此時問題向上傳遞到了元素 4,我們看 2、4、7、9 的顏色和位置關係,這種情況我們也處理過,先將 2 和 9 染為黑色,7 染為紅色,結果如下:
染色
最後 7 是根結點,染為黑色即可,最終結果如下所示:
2.4 刪除操作
刪除的規則如下:
要從一棵紅黑樹中刪除一個元素,主要分為三種情況。
情況 1:待刪除元素沒有孩子
沒有孩子指的是沒有值不為 NIL 的孩子。這種情況下,如果刪除的元素是紅色的,可以直接刪除,如果刪除的元素是黑色的,就需要進行調整了。
例如我們從下圖中刪除元素 1:
紅黑樹
刪除元素 1 後,2 的左孩子為 NIL,這條支路上的黑色結點數就比其他支路少了,所以需要進行調整。
這時,我們的關注點從叔叔結點轉到兄弟結點,也就是結點 4,此時 4 是紅色的,就把它染為黑色,把父結點 2 染為紅色,如下所示:
染色
然後以 2 左旋,結果如下:
左旋
此時兄弟結點為 3,且它沒有紅色的孩子,這時只需要把它染為紅色,父結點 2 染為黑色即可。結果如下所示:
調整完畢
情況 2:待刪除元素有一個孩子
這應該是刪除操作中最簡單的一種情況了,根據紅黑樹的定義,我們可以推測,如果一個元素僅有一個孩子,那麼這個元素一定是黑色的,而且其孩子是紅色的。
假設我們有一個紅色節點,它是樹中的某一個節點,且僅有一個孩子,那麼根據紅色節點不能相鄰的條件,它的孩子一定是黑色的,如下所示:
紅色節點僅一個孩子
但這個子樹的黑高卻不再平衡了(注意每個節點的葉節點都是一個 NIL 節點),因此紅色節點不可能只有一個孩子。
而若是一個黑色節點僅有一個孩子,如果其孩子是黑色的,同樣會打破黑高的平衡,所以其孩子只能是紅色的,如下所示:
黑色節點僅一個孩子
只有這一種情況符合紅黑樹的定義,這時要刪除這個元素,只需要使用其孩子代替它,僅代替值而不代替顏色即可,上圖的情況刪除完後變為:
刪除完畢
可以看到,樹的黑高並沒有發生變化,因此也不需要進行調整。
情況 3:待刪除元素有兩個孩子#
我們在討論二叉排序樹時說過,如果刪除一個有兩個孩子的元素,可以使用它的前驅或者後繼結點代替它。因為它的前驅或者後繼結點最多隻會有一個孩子,所以這種情況可以轉為情況 1 或情況 2 處理。
刪除元素最複雜的是情況 1,這主要由其兄弟結點以及兄弟結點的孩子顏色共同決定。這裡簡要做下總結。
我們以 N 代表當前待刪除節點,以 P 代表父結點,以 S 代表兄弟結點,以 SL 代表兄弟結點的左孩子,SR 代表兄弟結點的右孩子,如下所示:
圖樣
根據紅黑樹定義,這種情況下 S 要麼有紅色的子結點,要麼只有 NIL 結點,以下對 S 有黑色結點的情況均表示 NIL
主要有以下幾種:
-
S 是紅色,P 一定是黑色,S 也不會有紅色的孩子,如下:
紅色兄弟結點
此時把 P 和 S 顏色變換,再左旋,如下:
左旋
這樣變換後,N 支路上的黑色結點並沒有增加,所以依然少一個,
-
P,S 以及 S 的全部孩子都是黑色
無論 S 有幾個孩子,或者沒有孩子,只要不是紅色都是這種情況,此時情況如下:
全黑色
我們把 S 染為紅色,這樣一來,N 和 S 兩個支路都少了一個黑色結點,所以可以把問題向父結點轉移,通過遞迴解決。染色後如下:
染色
-
P 為紅(S 一定為黑),S 的孩子都為黑
這種情況最為簡單,只需要把 P 和 S 顏色交換即可。這樣 N 支路多了一個黑色元素,而 S 支路沒有減少,所以達到了平衡。
交換前
交換後
-
P 任意色,S 為黑,N 是 P 的左孩子,S 的右孩子 SR 為紅,S 的左孩子任意
如下所示
任意色
此時將 S 改為 P 的顏色,SR 和 P 改為黑色,然後左旋,結果如下:
左旋
可以發現,此時 N 支路多了一個黑色結點,而其餘支路均沒有收到影響,所以調整完畢。
此時變換 S 和 SL 的顏色,然後右旋,結果如下:
右旋
這時,所有分支的黑色結點數均沒有改變,但情況 5 轉為了情況 4,再進行一次操作即可。
還有一些情況與上述是對稱的,我們進行相應的轉換即可。
紅黑樹的操作比較複雜,插入元素可能需要多次變色與旋轉,刪除也是。這些操作的目的都是為了保證紅黑樹的結構不被破壞。這些複雜的插入與刪除操作希望大家可以親手嘗試一下,以加深理解。
三、結語
紅黑樹其實一開始看起來有點懵逼懵逼的,但是,其實你看完了全文,然後手動模擬一下插入刪除操作,發現也不是想象中的很難啦!
如果文章對您有一點幫助的話,希望您能點一下贊,您的點贊,是我前進的動力
相關文章
- JAVA集合:TreeMap紅黑樹深度解析Java
- 二、JAVA知識點之HashMap、TreeMap、紅黑樹——精髓JavaHashMap
- 紅黑樹
- 圖解集合7:紅黑樹概念、紅黑樹的插入及旋轉操作詳細解讀圖解
- Java集合(3)一 紅黑樹、TreeMap與TreeSet(上)Java
- Java集合(4)一 紅黑樹、TreeMap與TreeSet(下)Java
- 思考紅黑樹
- 圖解集合8:紅黑樹的移除節點操作圖解
- 瞭解紅黑樹的起源,理解紅黑樹的本質
- 紅黑樹詳解
- 淺談紅黑樹
- 【轉】理解紅黑樹
- 紅黑樹左右旋
- pwn前置知識
- JUC前置知識
- 從紅黑樹的本質出發,徹底理解紅黑樹!
- Java集合原始碼分析之基礎(六):紅黑樹(RB Tree)Java原始碼
- js實現紅黑樹JS
- 紅黑樹新增刪除
- Web前置知識(1)Web
- 死磕 java集合之TreeMap原始碼分析(二)——紅黑樹全解析Java原始碼
- 死磕 java集合之TreeMap原始碼分析(三)——紅黑樹全解析Java原始碼
- 死磕 java集合之TreeMap原始碼分析(一)——紅黑樹全解析Java原始碼
- 死磕 java集合之TreeMap原始碼分析(四)——紅黑樹全解析Java原始碼
- [原始碼-webpack01-前置知識] AST抽象語法樹原始碼WebAST抽象語法樹
- 2-3-4樹對應紅黑樹的實現,紅黑樹的融會貫通!!!
- 資料結構--紅黑樹資料結構
- 用Js實現紅黑樹JS
- 紅黑樹原始碼實現原始碼
- Java 反射【前置知識點】Java反射
- 域滲透前置知識
- vue面試題(前置知識)Vue面試題
- 平衡二叉查詢樹:紅黑樹
- 資料結構之「紅黑樹」資料結構
- Java基礎-理解紅黑樹(插入)Java
- 紅黑樹的原理以及實現
- 紅黑樹核心程式碼分析(JAVA)Java
- Java實現紅黑樹(平衡二叉樹)Java二叉樹