JDK8:HashMap源碼解析:TreeNode類的balanceInsertion方法

一、概述

balanceInsertion指的是紅黑樹的插入平衡算法,當樹結構中新插入了一個節點後,要對樹進行重新的結構化,以保證該樹始終維持紅黑樹的特性。

關於紅黑樹的特性:

 

性質1. 節點是紅色或黑色。

性質2. 根節點是黑色。

性質3 每個葉節點(NIL節點,空節點)是黑色的。

性質4 每個紅色節點的兩個子節點都是黑色。(從每個葉子到根的所有路徑上不能有兩個連續的紅色節點)

性質5. 從任一節點到其每個葉子的路徑上包含的黑色節點數量都相同。


二、方法解析

/**
 * 紅黑樹插入節點後,需要重新平衡
 * root 當前根節點
 * x 新插入的節點
 * 返回重新平衡後的根節點
 */
static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
                                                    TreeNode<K,V> x) {
    x.red = true; // 新插入的節點標爲紅色

    /*
     * 這一步即定義了變量,又開起了循環,循環沒有控制條件,只能從內部跳出
     * xp:當前節點的父節點、xpp:爺爺節點、xppl:左叔叔節點、xppr:右叔叔節點
     */
    for (TreeNode<K,V> xp, xpp, xppl, xppr;;) { 

        // 如果父節點爲空、說明當前節點就是根節點,那麼把當前節點標爲黑色,返回當前節點
        if ((xp = x.parent) == null) { // L1
            x.red = false;
            return x;
        }

        // 父節點不爲空
        // 如果父節點爲黑色 或者 【(父節點爲紅色 但是 爺爺節點爲空) -> 這種情況何時出現?】
        else if (!xp.red || (xpp = xp.parent) == null) // L2
            return root;
        if (xp == (xppl = xpp.left)) { // 如果父節點是爺爺節點的左孩子  // L3
            if ((xppr = xpp.right) != null && xppr.red) { // 如果右叔叔不爲空 並且 爲紅色  // L3_1
                xppr.red = false; // 右叔叔置爲黑色
                xp.red = false; // 父節點置爲黑色
                xpp.red = true; // 爺爺節點置爲紅色
                x = xpp; // 運行到這裏之後,就又會進行下一輪的循環了,將爺爺節點當做處理的起始節點 
            }
            else { // 如果右叔叔爲空 或者 爲黑色 // L3_2
                if (x == xp.right) { // 如果當前節點是父節點的右孩子 // L3_2_1
                    root = rotateLeft(root, x = xp); // 父節點左旋,見下文左旋方法解析
                    xpp = (xp = x.parent) == null ? null : xp.parent; // 獲取爺爺節點
                }
                if (xp != null) { // 如果父節點不爲空 // L3_2_2
                    xp.red = false; // 父節點 置爲黑色
                    if (xpp != null) { // 爺爺節點不爲空
                        xpp.red = true; // 爺爺節點置爲 紅色
                        root = rotateRight(root, xpp);  //爺爺節點右旋,見下文右旋方法解析
                    }
                }
            }
        }
        else { // 如果父節點是爺爺節點的右孩子 // L4
            if (xppl != null && xppl.red) { // 如果左叔叔是紅色 // L4_1
                xppl.red = false; // 左叔叔置爲 黑色
                xp.red = false; // 父節點置爲黑色
                xpp.red = true; // 爺爺置爲紅色
                x = xpp; // 運行到這裏之後,就又會進行下一輪的循環了,將爺爺節點當做處理的起始節點 
            }
            else { // 如果左叔叔爲空或者是黑色 // L4_2
                if (x == xp.left) { // 如果當前節點是個左孩子 // L4_2_1
                    root = rotateRight(root, x = xp); // 針對父節點做右旋,見下文右旋方法解析
                    xpp = (xp = x.parent) == null ? null : xp.parent; // 獲取爺爺節點
                }
                if (xp != null) { // 如果父節點不爲空 // L4_2_4
                    xp.red = false; // 父節點置爲黑色
                    if (xpp != null) { //如果爺爺節點不爲空
                        xpp.red = true; // 爺爺節點置爲紅色
                        root = rotateLeft(root, xpp); // 針對爺爺節點做左旋
                    }
                }
            }
        }
    }
}


/**
 * 節點左旋
 * root 根節點
 * p 要左旋的節點
 */
static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
                                              TreeNode<K,V> p) {
    TreeNode<K,V> r, pp, rl;
    if (p != null && (r = p.right) != null) { // 要左旋的節點以及要左旋的節點的右孩子不爲空
        if ((rl = p.right = r.left) != null) // 要左旋的節點的右孩子的左節點 賦給 要左旋的節點的右孩子 節點爲:rl
            rl.parent = p; // 設置rl和要左旋的節點的父子關係【之前只是爹認了孩子,孩子還沒有答應,這一步孩子也認了爹】

        // 將要左旋的節點的右孩子的父節點  指向 要左旋的節點的父節點,相當於右孩子提升了一層,
        // 此時如果父節點爲空, 說明r 已經是頂層節點了,應該作爲root 並且標爲黑色
        if ((pp = r.parent = p.parent) == null) 
            (root = r).red = false;
        else if (pp.left == p) // 如果父節點不爲空 並且 要左旋的節點是個左孩子
            pp.left = r; // 設置r和父節點的父子關係【之前只是孩子認了爹,爹還沒有答應,這一步爹也認了孩子】
        else // 要左旋的節點是個右孩子
            pp.right = r; 
        r.left = p; // 要左旋的節點  作爲 他的右孩子的左節點
        p.parent = r; // 要左旋的節點的右孩子  作爲  他的父節點
    }
    return root; // 返回根節點
}

/**
 * 節點右旋
 * root 根節點
 * p 要右旋的節點
 */
static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,
                                               TreeNode<K,V> p) {
    TreeNode<K,V> l, pp, lr;
    if (p != null && (l = p.left) != null) { // 要右旋的節點不爲空以及要右旋的節點的左孩子不爲空
        if ((lr = p.left = l.right) != null) // 要右旋的節點的左孩子的右節點 賦給 要右旋節點的左孩子 節點爲:lr
            lr.parent = p; // 設置lr和要右旋的節點的父子關係【之前只是爹認了孩子,孩子還沒有答應,這一步孩子也認了爹】

        // 將要右旋的節點的左孩子的父節點  指向 要右旋的節點的父節點,相當於左孩子提升了一層,
        // 此時如果父節點爲空, 說明l 已經是頂層節點了,應該作爲root 並且標爲黑色
        if ((pp = l.parent = p.parent) == null) 
            (root = l).red = false;
        else if (pp.right == p) // 如果父節點不爲空 並且 要右旋的節點是個右孩子
            pp.right = l; // 設置l和父節點的父子關係【之前只是孩子認了爹,爹還沒有答應,這一步爹也認了孩子】
        else // 要右旋的節點是個左孩子
            pp.left = l; // 同上
        l.right = p; // 要右旋的節點 作爲 他左孩子的右節點
        p.parent = l; // 要右旋的節點的父節點 指向 他的左孩子
    }
    return root;
}

三、圖例

1、無旋轉

2、有旋轉

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章