紅黑樹基礎-第三篇-源碼解析

本文主要解析java8中HashMap的紅黑樹源碼部分,如有不懂可以先看我寫的紅黑樹基礎-第一篇紅黑樹基礎-第二篇

1.左旋

注意:圖中節點標號跟下面代碼中變量名一致,便於理解。

/**
 * 紅黑樹節點左旋操作
 * @param root 根節點
 * @param p 旋轉節點
 * @return 新root節點
 */
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) {
        //1.把r的左子樹變成p的右子樹,並標記爲rl,即p->right=rl
        if ((rl = p.right = r.left) != null)
            //2.如果rl不爲空,鏈起來,rl->parent=p
            rl.parent = p;
        //3.把p的父節點變成r的父節點,並標記爲pp,即r->parent=pp
        if ((pp = r.parent = p.parent) == null)
            //4.如果pp爲空,那麼之前p是根節點,現在左旋後r變成根節點,root=r,並且根節點爲黑色,r.red=false
            (root = r).red = false;
        //5.否則pp不爲空,就判斷p是否是pp的左孩子,如果是,旋轉後r就是左孩子
        else if (pp.left == p)
            pp.left = r;
        else
            //6.否則就是右孩子
            pp.right = r;
        //7.最後完善樹的父子關係,鏈起來
        //r->left=p
        r.left = p;
        //p->parent=r
        p.parent = r;
    }
    //最後返回新的root節點
    return root;
}

2.右旋

/**
 * 紅黑樹節點右旋操作
 * @param root 根節點
 * @param p 旋轉節點
 * @return 新root節點
 */
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) {
        //1.把l的右子樹變成p的左子樹,並標記爲lr,即p->left=lr
        if ((lr = p.left = l.right) != null)
            //2.如果lr不爲空,鏈起來,lr->parent=p
            lr.parent = p;
        //3.把p的父節點變成l的父節點,並標記爲pp,即l->parent=pp
        if ((pp = l.parent = p.parent) == null)
            //4.如果pp爲空,那麼之前p是根節點,現在右旋後l變成根節點,root=l,並且根節點爲黑色,l.red=false
            (root = l).red = false;
        //5.否則pp不爲空,就判斷p是否是pp的右孩子,如果是,旋轉後l就是右孩子
        else if (pp.right == p)
            pp.right = l;
        else
            //6.否則就是左孩子
            pp.left = l;
        //7.最後完善樹的父子關係,鏈起來
        //l->right=p
        l.right = p;
        //p->parent=l
        p.parent = l;
    }
    //最後返回新的root節點
    return root;
}

3.平衡插入

講解平衡插入之前,我們直接從源碼入手,如果感覺理解起來有困難,請先看懂我寫的紅黑樹基礎-第一篇

下面約定下面代碼變量的命名規則,x爲邏輯上待插入的節點(有可能是第一次插入,也有可能是自底向上調整時的插入),xp爲x節點的父節點,xpp爲x節點的祖父節點,也是xp的父節點。xppl是xpp的左孩子,xppr是xpp的右孩子。xp是xppl和xppr中的其中之一。

/**
 * 紅黑樹節點平衡插入操作
 * @param root 根節點
 * @param x 插入節點
 * @return 新root節點
 */
static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
                                            TreeNode<K,V> x) {
    //首先插入節點x的顏色初始化爲紅色
    x.red = true;
    //for循環沒有結束條件,自底向上插入循環處理,直至到root或者紅黑自平衡退出循環
    for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
        //如果xp爲空,說明x已經爲root了
        if ((xp = x.parent) == null) {
            x.red = false;
            return x;
        }
        //否則判斷父節點xp是否是黑色,或者xp是否爲root
        else if (!xp.red || (xpp = xp.parent) == null)
            return root;
        //判斷父節點xp是否是祖父節點的左孩子
        if (xp == (xppl = xpp.left)) {
            //叔叔節點存在,並且爲紅節點
            if ((xppr = xpp.right) != null && xppr.red) {
                //紅黑樹基礎-第一篇,根據情景4.1進行變色
                xppr.red = false;
                xp.red = false;
                xpp.red = true;
                //以xpp爲插入節點,進行下一次循環
                x = xpp;
            }
            //否則叔叔節點要麼爲空,要麼爲黑色
            else {
                //如果插入節點x是父節點xp的右孩子,那麼滿足紅黑樹基礎-第一篇,情景4.2.2
                if (x == xp.right) {
                    //把xp進行左旋,同時把插入節點x賦值爲xp
                    root = rotateLeft(root, x = xp);
                    //重新設置xp和xpp
                    xpp = (xp = x.parent) == null ? null : xp.parent;
                }
                //進行紅黑樹基礎-第一篇,情景4.2.1的處理
                if (xp != null) {
                    xp.red = false;
                    if (xpp != null) {
                        xpp.red = true;
                        //xpp右旋
                        root = rotateRight(root, xpp);
                    }
                }
            }
        }
        //否則父節點xp是祖父節點的右孩子
        else {
            //叔叔節點存在,並且爲紅節點
            if (xppl != null && xppl.red) {
                //紅黑樹基礎-第一篇,根據情景4.1進行變色
                xppl.red = false;
                xp.red = false;
                xpp.red = true;
                //以xpp爲插入節點,進行下一次循環
                x = xpp;
            }
            //否則叔叔節點要麼爲空,要麼爲黑色
            else {
                //如果插入節點x是父節點xp的左孩子,那麼滿足紅黑樹基礎-第一篇,情景4.3.2
                if (x == xp.left) {
                    //把xp進行右旋,同時把插入節點x賦值爲xp
                    root = rotateRight(root, x = xp);
                    //重新設置xp和xpp
                    xpp = (xp = x.parent) == null ? null : xp.parent;
                }
                //進行紅黑樹基礎-第一篇,情景4.3.1的處理
                if (xp != null) {
                    xp.red = false;
                    if (xpp != null) {
                        xpp.red = true;
                        //xpp左旋
                        root = rotateLeft(root, xpp);
                    }
                }
            }
        }
    }
}

4.平衡刪除

4.1. 先找到替代節點

先看圖解,如果不懂原理請關注我的紅黑樹基礎-第二篇-刪除,或者HashMap源碼解析

這個函數主要是this是要刪除的節點,找到節點s與之互換,然後用balanceDeletion調整,看下圖中樹結構的變化再看代碼註釋

/**
 * Removes the given node, that must be present before this call.
 * This is messier than typical red-black deletion code because we
 * cannot swap the contents of an interior node with a leaf
 * successor that is pinned by "next" pointers that are accessible
 * independently during traversal. So instead we swap the tree
 * linkages. If the current tree appears to have too few nodes,
 * the bin is converted back to a plain bin. (The test triggers
 * somewhere between 2 and 6 nodes, depending on tree structure).
 * @param map 該hashmap
 * @param tab hashmap內部Node數組
 * @param movable 是否需要移動root到鏈表頭
 * @param this 待刪除節點p
 */
final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
                          boolean movable) {
    int n;
    if (tab == null || (n = tab.length) == 0)
        return;
    int index = (n - 1) & hash;
    //root是根節點
    TreeNode<K,V> first = (TreeNode<K,V>)tab[index], root = first, rl;
    //分別是this=p的鏈表指針的前驅和後繼
    TreeNode<K,V> succ = (TreeNode<K,V>)next, pred = prev;
    if (pred == null)
        //如果要刪除的p是鏈表的頭,那麼first = succ;並且tab[index] = succ;
        tab[index] = first = succ;
    else
        //否則鏈表斷開this的鏈接
        pred.next = succ;
    if (succ != null)
        //鏈表斷開this的鏈接,把鏈表關係完善
        succ.prev = pred;
    if (first == null)
        //空樹
        return;
    if (root.parent != null)
        //獲取到真正的根root
        root = root.root();
    if (root == null || root.right == null ||
        (rl = root.left) == null || rl.left == null) {
        //根的左孩子的左孩子爲空,基本上可以判斷只剩2-6個node了,紅黑樹可以變成鏈表了
        tab[index] = first.untreeify(map);  // too small
        return;
    }
    TreeNode<K,V> p = this, pl = left, pr = right, replacement;
    //主要關注這種左右孩子都非空的場景,爲什麼這種這麼複雜,請看我寫的關於紅黑樹基礎的其他博客
    if (pl != null && pr != null) {
        //請看圖解,我隨便畫了個圖,節點名和變量名一致
        TreeNode<K,V> s = pr, sl;
        //先找到大於刪除節點p的最小節點,爲什麼這麼做,請看紅黑樹基礎-第3篇
        while ((sl = s.left) != null) // find successor
            s = sl;
        //首先互換p節點和s節點的顏色,因爲最終s要被換到p的位置,p要被換到s的位置
        //互換後,在p位置的s因爲是p的顏色,所以不影響紅黑樹的性質
        //而換到s位置的p,顏色是原s節點的顏色
        boolean c = s.red; s.red = p.red; p.red = c; // swap colors
        TreeNode<K,V> sr = s.right;
        TreeNode<K,V> pp = p.parent;
        if (s == pr) { // p was s's direct parent
            //此時s==sp==pr,其實是建立p和s的關係,將else的內容簡化了
            p.parent = s;
            s.right = p;
        }
        else {
            TreeNode<K,V> sp = s.parent;
            //建立p和sp的新父子關係,大家可以畫圖分析
            if ((p.parent = sp) != null) {
                if (s == sp.left)
                    sp.left = p;
                else
                    sp.right = p;
            }
            //建立s和pr的新父子關係
            if ((s.right = pr) != null)
                pr.parent = s;
        }
        p.left = null;
        //建立p和sr的新父子關係
        if ((p.right = sr) != null)
            sr.parent = p;
        //建立s和pl的新父子關係
        if ((s.left = pl) != null)
            pl.parent = s;
        //建立s和pp的新父子關係
        //如果pp爲空,之前p就是根節點,那麼現在s就是根節點了
        if ((s.parent = pp) == null)
            root = s;
        else if (p == pp.left)
            //如果原來p是pp的左孩子,互換後s就還是pp左孩子
            pp.left = s;
        else
            pp.right = s;
        //如果sr不爲空,則互換後p有右孩子,沒有左孩子,
        if (sr != null)
            //單鏈接情況直接用孩子替換
            replacement = sr;
        else
            //此時p沒有孩子
            replacement = p;
    }
    //單鏈接情況,單單隻有左孩子
    else if (pl != null)
        replacement = pl;
    //單鏈接情況,單單隻有右孩子
    else if (pr != null)
        replacement = pr;
    else
        //p是葉子結點
        replacement = p;
    //互換後,如果p不是葉子結點,在樹結構中直接把p節點幹掉
    if (replacement != p) {
        TreeNode<K,V> pp = replacement.parent = p.parent;
        if (pp == null)
            root = replacement;
        else if (p == pp.left)
            pp.left = replacement;
        else
            pp.right = replacement;
        p.left = p.right = p.parent = null;
    }

    //回到紅黑樹的平衡刪除了,如果要刪除的節點是紅色,那麼直接刪除即可
    //如果p是黑色的,那麼此時就不滿足紅黑樹的性質了,因爲少了一個黑色節點,那麼要進行balanceDeletion調整
    //注意:p的顏色是原來s的顏色
    TreeNode<K,V> r = p.red ? root : balanceDeletion(root, replacement);

    //那麼如果p是葉子結點,在樹結構中直接把p節點幹掉,detach斷開連接
    if (replacement == p) {  // detach
        TreeNode<K,V> pp = p.parent;
        p.parent = null;
        if (pp != null) {
            if (p == pp.left)
                pp.left = null;
            else if (p == pp.right)
                pp.right = null;
        }
    }
    //是否需要把root放到鏈表head
    if (movable)
        moveRootToFront(tab, r);
}

4.2 balanceDeletion方法進行平衡調整,原理不懂得請一定先看紅黑樹基礎第二篇,對照圖解

/** 
 * 平衡刪除調整部分代碼
 * 情景?.?請見紅黑樹基礎-第二篇-刪除 這篇文章對應的解析
 * @param x 替代節點
 * @param root 樹的根節點
 * @return 調整後,新的root節點
 * 變量規則,x是替代節點,xp是x的父節點,xpl是xp的左孩子,xpr是xp的右孩子
 */
static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root,
                                           TreeNode<K,V> x) {
    for (TreeNode<K,V> xp, xpl, xpr;;)  {
        //替代節點已經到達根節點,直接退出函數返回
        if (x == null || x == root)
            return root;
        //如果x的父節點是空,說明x==root了
        else if ((xp = x.parent) == null) {
            x.red = false;
            return x;
        }
        //如果替代節點是紅色,直接變爲黑色,返回
        else if (x.red) {
            x.red = false;
            return root;
        }
        //情景2.1
        else if ((xpl = xp.left) == x) {
            //情景2.1.1
            if ((xpr = xp.right) != null && xpr.red) {
                xpr.red = false;
                xp.red = true;
                root = rotateLeft(root, xp);
                //此時,x不變,xp和xpr更新
                xpr = (xp = x.parent) == null ? null : xp.right;
            }
            //如果xpr爲空,那麼右子樹沒有紅節點借,找父母的兄弟去借吧,自底向上處理
            if (xpr == null)
                x = xp;
            //否則,情景2.1.2,xpr是黑色
            else {
                TreeNode<K,V> sl = xpr.left, sr = xpr.right;
                //如果xpr的左右孩子都是黑色,情景2.1.2.3
                if ((sr == null || !sr.red) &&
                    (sl == null || !sl.red)) {
                    //找父母的兄弟去處理,自底向上處理
                    xpr.red = true;
                    x = xp;
                }
                else {
                    //否則,左子樹紅色,右子樹黑色
                    if (sr == null || !sr.red) {
                        //情景2.1.2.2
                        if (sl != null)
                            sl.red = false;
                        xpr.red = true;
                        root = rotateRight(root, xpr);
                        //更新xpr和xp
                        xpr = (xp = x.parent) == null ?
                            null : xp.right;
                    }
                    //情景2.1.2.1
                    if (xpr != null) {
                        xpr.red = (xp == null) ? false : xp.red;
                        if ((sr = xpr.right) != null)
                            sr.red = false;
                    }
                    //情景2.1.2.1
                    if (xp != null) {
                        xp.red = false;
                        root = rotateLeft(root, xp);
                    }
                    x = root;
                }
            }
        }
        //情景2.2
        else { // symmetric
            //情景2.2.1
            if (xpl != null && xpl.red) {
                xpl.red = false;
                xp.red = true;
                root = rotateRight(root, xp);
                //此時,x不變,xp和xpl更新
                xpl = (xp = x.parent) == null ? null : xp.left;
            }
            //如果xpl爲空,那麼左子樹沒有紅節點借,找父母的兄弟去借吧,自底向上處理
            if (xpl == null)
                x = xp;
            //否則,情景2.2.2,xpl是黑色
            else {
                //如果xpl的左右孩子都是黑色,情景2.2.2.3
                TreeNode<K,V> sl = xpl.left, sr = xpl.right;
                if ((sl == null || !sl.red) &&
                    (sr == null || !sr.red)) {
                    //找父母的兄弟去處理,自底向上處理
                    xpl.red = true;
                    x = xp;
                }
                else {
                    //否則,左子樹黑色,右子樹紅色
                    if (sl == null || !sl.red) {
                        //情景2.2.2.2
                        if (sr != null)
                            sr.red = false;
                        xpl.red = true;
                        root = rotateLeft(root, xpl);
                        //更新xpr和xp
                        xpl = (xp = x.parent) == null ?
                            null : xp.left;
                    }
                    //情景2.2.2.1
                    if (xpl != null) {
                        xpl.red = (xp == null) ? false : xp.red;
                        if ((sl = xpl.left) != null)
                            sl.red = false;
                    }
                    //情景2.2.2.1
                    if (xp != null) {
                        xp.red = false;
                        root = rotateRight(root, xp);
                    }
                    x = root;
                }
            }
        }
    }
}

紅黑樹的源碼比較複雜,如有不懂請通過紅黑樹基礎第1篇,第2篇深入理解後再看。

 

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