死磕 java集合之TreeMap源碼分析(三)——紅黑樹全解析

歡迎關注個人公衆號「彤哥讀源碼」,查看更多源碼系列文章, 與彤哥一塊兒暢遊源碼的海洋。java


刪除元素

刪除元素自己比較簡單,就是採用二叉樹的刪除規則。git

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

(2)若是刪除的位置只有一個葉子節點(有多是通過第一步轉換後的刪除位置),則把那個葉子節點做爲替代元素,放到刪除的位置,而後把這個葉子節點刪除。code

(3)若是刪除的位置沒有葉子節點,則直接把這個刪除位置的元素刪除便可。rem

(4)針對紅黑樹,若是刪除位置是黑色節點,還須要作再平衡。get

(5)若是有替代元素,則以替代元素做爲當前節點進入再平衡。源碼

(6)若是沒有替代元素,則以刪除的位置的元素做爲當前節點進入再平衡,平衡以後再刪除這個節點。it

public V remove(Object key) {
    // 獲取節點
    Entry<K,V> p = getEntry(key);
    if (p == null)
        return null;

    V oldValue = p.value;
    // 刪除節點
    deleteEntry(p);
    // 返回刪除的value
    return oldValue;
}

private void deleteEntry(Entry<K,V> p) {
    // 修改次數加1
    modCount++;
    // 元素個數減1
    size--;

    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;
        else
            p.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;
        }
    }
}
複製代碼

刪除再平衡

通過上面的處理,真正刪除的確定是黑色節點纔會進入到再平衡階段。io

由於刪除的是黑色節點,致使整顆樹不平衡了,因此這裏咱們假設把刪除的黑色賦予當前節點,這樣當前節點除了它自已的顏色還多了一個黑色,那麼:table

(1)若是當前節點是根節點,則直接塗黑便可,不須要再平衡;

(2)若是當前節點是紅+黑節點,則直接塗黑便可,不須要平衡;

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

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

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

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

狀況 策略
1)x是黑+黑節點,x的兄弟是紅節點 (1)將兄弟節點設爲黑色;
(2)將父節點設爲紅色;
(3)以父節點爲支點進行右旋;
(4)從新設置x的兄弟節點,進入下一步;
2)x是黑+黑節點,x的兄弟是黑節點,且兄弟節點的兩個子節點都是黑色 (1)將兄弟節點設置爲紅色;
(2)將x的父節點做爲新的當前節點,進入下一次循環;
3)x是黑+黑節點,x的兄弟是黑節點,且兄弟節點的左子節點爲黑色,右子節點爲紅色 (1)將兄弟節點的右子節點設爲黑色;
(2)將兄弟節點設爲紅色;
(3)以兄弟節點爲支點進行左旋;
(4)從新設置x的兄弟節點,進入下一步;
3)x是黑+黑節點,x的兄弟是黑節點,且兄弟節點的左子節點爲紅色,右子節點任意顏色 (1)將兄弟節點的顏色設爲父節點的顏色;
(2)將父節點設爲黑色;
(3)將兄弟節點的左子節點設爲黑色;
(4)以父節點爲支點進行右旋;
(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
相關文章
相關標籤/搜索