Java集合-TreeMap和紅黑樹(shù)

TreeMap是一種通過(guò)實(shí)現(xiàn)了紅黑樹(shù)數(shù)據(jù)結(jié)構(gòu)的Map集合。

【圖片有英文注釋的均摘抄于國(guó)外文章】

首先,先來(lái)看一些基礎(chǔ)概念。

1. 二叉排序樹(shù)

二叉排序樹(shù)的定義和性質(zhì):
(1)若左子樹(shù)不空,則左子樹(shù)上所有結(jié)點(diǎn)的值均小于或等于它的根結(jié)點(diǎn)的值;
(2)若右子樹(shù)不空,則右子樹(shù)上所有結(jié)點(diǎn)的值均大于或等于它的根結(jié)點(diǎn)的值;
(3)左、右子樹(shù)也分別為二叉排序樹(shù);
【摘自百度百科】
.
如下圖是一個(gè)普通的二叉樹(shù)結(jié)構(gòu):

二叉樹(shù)【摘抄于國(guó)外文章】

上圖是相應(yīng)的根據(jù)值比較生成的一個(gè)簡(jiǎn)單的二叉樹(shù),那么對(duì)應(yīng)查找就非常簡(jiǎn)單,不斷通過(guò)比較節(jié)點(diǎn)的值,大于就向右子樹(shù)走,小于就往左子樹(shù)走,相同則返回當(dāng)前值.

二叉樹(shù)查找【摘抄于國(guó)外文章】

對(duì)應(yīng)插入就可以類(lèi)似查找,先查到當(dāng)前值于節(jié)點(diǎn)比較,如果找到,直接更新,沒(méi)有找到就不斷往子節(jié)點(diǎn)尋找,直到到達(dá)為空的子節(jié)點(diǎn),將值填入到該空節(jié)點(diǎn)上。


二叉樹(shù)插入【摘抄于國(guó)外文章】

二叉樹(shù)查找的時(shí)候,查找的效率和樹(shù)的形狀有關(guān),當(dāng)節(jié)點(diǎn)完全平衡時(shí)(最底層子節(jié)點(diǎn)到根節(jié)點(diǎn)的“路程”相同),效率最高;當(dāng)所有節(jié)點(diǎn)都只有一個(gè)子節(jié)點(diǎn)時(shí),效率最差

二叉樹(shù)效率【摘抄于國(guó)外文章】

2. B樹(shù)

B-樹(shù)是一種多路搜索樹(shù)(并不一定是二叉的)
如下圖,節(jié)點(diǎn)內(nèi)有多個(gè)值(多路):


B樹(shù)【摘抄于國(guó)外文章】

上圖展示的最多只有兩個(gè)三個(gè)子節(jié)點(diǎn)的B樹(shù),這種簡(jiǎn)單結(jié)構(gòu)的樹(shù)可以稱(chēng)為2-3樹(shù),這里點(diǎn)到為止,后續(xù)將通過(guò)這種樹(shù)去分析后續(xù)的紅黑樹(shù)

3. 2-3樹(shù)

2-3樹(shù)允許每個(gè)節(jié)點(diǎn)保存1個(gè)或2個(gè)值,含有1個(gè)值節(jié)點(diǎn)稱(chēng)為2-node(有兩個(gè)子節(jié)點(diǎn)),含有2個(gè)值的節(jié)點(diǎn)稱(chēng)為3-node(有三個(gè)子節(jié)點(diǎn))。
如圖:


2-3樹(shù)【摘抄于國(guó)外文章】

在2-3樹(shù)中查找,與二叉樹(shù)類(lèi)似,通過(guò)不斷和節(jié)點(diǎn)比較進(jìn)行向下查找,需要注意的是,在3-node節(jié)點(diǎn)中,需要同時(shí)比較兩個(gè)值,如果介于兩個(gè)值之間,需要找的子節(jié)點(diǎn)就是中間的子節(jié)點(diǎn)。

2-3樹(shù)查找過(guò)程【摘抄于國(guó)外文章】

這里重點(diǎn)討論一下插入操作,首先看一種簡(jiǎn)單的,往2-node節(jié)點(diǎn)插入;如果找到的末節(jié)點(diǎn)是一個(gè)2-node的節(jié)點(diǎn),直接在此節(jié)點(diǎn)上新增當(dāng)前值,將其節(jié)點(diǎn)變成3-node節(jié)點(diǎn),如圖:


2-3樹(shù)2-node新增【摘抄于國(guó)外文章】

當(dāng)插入的節(jié)點(diǎn)是3-node節(jié)點(diǎn)時(shí),應(yīng)該怎么操作?首先看一下一個(gè)最簡(jiǎn)單的,只有一個(gè)3-node節(jié)點(diǎn)的樹(shù):


2-3樹(shù)3-node根新增【摘抄于國(guó)外文章】

首先,將值插入到當(dāng)前的3-node節(jié)點(diǎn),將其變成4-node節(jié)點(diǎn),然后提取中間的值,讓其分解為一個(gè)普通的二叉樹(shù)即可。

那么如果當(dāng)前插入的3-node節(jié)點(diǎn)有父節(jié)點(diǎn)時(shí)應(yīng)該怎么處理呢,首先還是將3-node變成4-node節(jié)點(diǎn),然后拆解出一個(gè)父節(jié)點(diǎn)插入到原來(lái)的父節(jié)點(diǎn)中,如果當(dāng)前父節(jié)點(diǎn)是2-node節(jié)點(diǎn),那么將其變成3-node節(jié)點(diǎn),如果原來(lái)父節(jié)點(diǎn)已經(jīng)是3-node節(jié)點(diǎn),那么循環(huán)上面的處理步驟。

2-3樹(shù)3-node根新增【摘抄于國(guó)外文章】
2-3樹(shù)3-node根新增【摘抄于國(guó)外文章】

總結(jié)一下步驟:

  1. 查找需要插入值的位置
  2. 判斷當(dāng)前插入節(jié)點(diǎn)是否是2-node節(jié)點(diǎn),如果是,則將其變成3-node節(jié)點(diǎn),如不是,繼續(xù)執(zhí)行一下步驟
  3. 將當(dāng)前節(jié)點(diǎn)變成4-node節(jié)點(diǎn)
    4.分解當(dāng)前4-node節(jié)點(diǎn),判斷當(dāng)前節(jié)點(diǎn)是否為根節(jié)點(diǎn),如果是,將整個(gè)2-3樹(shù),提高一層,如不是,繼續(xù)執(zhí)行一下步驟
  4. 新增的上層節(jié)點(diǎn)插入到父節(jié)點(diǎn)中,重復(fù)執(zhí)行2-4步,直到結(jié)束

4. 紅黑樹(shù)

紅黑樹(shù)是一種解決平衡二叉樹(shù)的方法。紅黑樹(shù)具有以下性質(zhì):

  1. 節(jié)點(diǎn)是紅色或黑色。
  2. 根節(jié)點(diǎn)是黑色。
  3. 每個(gè)葉節(jié)點(diǎn)(NIL節(jié)點(diǎn),空節(jié)點(diǎn))是黑色的。
  4. 每個(gè)紅色節(jié)點(diǎn)的兩個(gè)子節(jié)點(diǎn)都是黑色。(從每個(gè)葉子到根的所有路徑上不能有兩個(gè)連續(xù)的紅色節(jié)點(diǎn))
    5.從任一節(jié)點(diǎn)到其每個(gè)葉子的所有路徑都包含相同數(shù)目的黑色節(jié)點(diǎn)。
    【摘自百度百科】
紅黑樹(shù)【摘抄于國(guó)外文章】

紅黑樹(shù)其實(shí)是2-3樹(shù)的一種二叉樹(shù)表現(xiàn)方法,如果將紅線連接線水平連接,那么連接的兩個(gè)節(jié)點(diǎn)就是2-3樹(shù)中的3-node節(jié)點(diǎn),其他沒(méi)有紅線連接的就是2-3樹(shù)中的2-node節(jié)點(diǎn),如圖:

紅黑樹(shù)與2-3樹(shù)【摘抄于國(guó)外文章】

在講解插入操作前,先了解集中紅黑樹(shù)的平衡操作。第一種是左旋轉(zhuǎn)和右旋轉(zhuǎn):

左旋轉(zhuǎn)【摘抄于國(guó)外文章】
右旋轉(zhuǎn)【摘抄于國(guó)外文章】

第二種是顏色反轉(zhuǎn),當(dāng)出現(xiàn)一個(gè)節(jié)點(diǎn)下的兩個(gè)節(jié)點(diǎn)均是紅色時(shí),可以轉(zhuǎn)換到2-3樹(shù)中思考,這是一個(gè)4-node節(jié)點(diǎn),那么需要將其分解為一個(gè)二叉樹(shù),并向上合并:

顏色轉(zhuǎn)換【摘抄于國(guó)外文章】

有了2-3樹(shù)的插入講解,這邊,直接分析最復(fù)雜的紅黑樹(shù)插入:

紅黑樹(shù)插入【摘抄于國(guó)外文章】
  1. 查找需要插入值的位置
  2. 新插入的節(jié)點(diǎn)元素用紅色標(biāo)識(shí)(2-3樹(shù)中插入到2-node節(jié)點(diǎn),將其變成3-node節(jié)點(diǎn))
  3. 如果出現(xiàn)節(jié)點(diǎn)下的兩個(gè)子節(jié)點(diǎn)都是紅色時(shí)(4-node節(jié)點(diǎn)),需要進(jìn)行顏色轉(zhuǎn)換(或旋轉(zhuǎn))

選取紅黑樹(shù)比完全平衡二叉樹(shù)的好處就是,紅黑樹(shù)是一種代價(jià)較小就可以實(shí)現(xiàn)較平衡的二叉樹(shù)的結(jié)構(gòu),最差的情況也就是比完全二叉樹(shù)的深度多一倍,但是在刪除操作的平衡里面可以節(jié)省很多的效率。

5. TreeMap

通過(guò)分析二叉樹(shù)和紅黑樹(shù)的概念,再來(lái)看源碼,首先看TreeMap的容器:

private transient Entry<K,V> root = null;

其中,TreeMap重寫(xiě)了Entry類(lèi):

K key;
V value;
Entry<K,V> left = null;
Entry<K,V> right = null;
Entry<K,V> parent;

其中key value是為了實(shí)現(xiàn)Map的鍵值結(jié)構(gòu),left、right、parent表示的是二叉樹(shù)中一個(gè)節(jié)點(diǎn)與其他節(jié)點(diǎn)的關(guān)系指針。

TreeMap提供了節(jié)點(diǎn)值比較的接口:

private final Comparator<? super K> comparator;

接下來(lái),看一下put方法:

public V put(K key, V value) {
    Entry<K,V> t = root;
    ... ...
    int cmp;
    Entry<K,V> parent;
    // split comparator and comparable paths
    Comparator<? super K> cpr = comparator;
    if (cpr != null) {
        do {
            parent = t;
            cmp = cpr.compare(key, t.key);
            if (cmp < 0)
                t = t.left;
            else if (cmp > 0)
                t = t.right;
            else
                return t.setValue(value);
        } while (t != null);
    }
    ... ...
    fixAfterInsertion(e);
    ... ...
}

首先獲取當(dāng)前的比較器,運(yùn)用當(dāng)前比較器作為后續(xù)比較的工具(比較器為空則用默認(rèn)的,默認(rèn)的邏輯和有比較器的類(lèi)似,不重復(fù)分析).然后小于,則跳到左邊的節(jié)點(diǎn)繼續(xù)比較,如果大于則在右邊子節(jié)點(diǎn)比較,如果相同,則設(shè)置當(dāng)前值。然后調(diào)用fixAfterInsertion平衡紅黑樹(shù)操作。

先偷一下懶,里面的平衡源碼就不分析了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容