天天看點

java1.8 hashMap紅黑樹源碼解析左旋右旋,balanceInsertion,rotateLeft,rotateRight方法的實作原理

學完了紅黑樹的性質,再來看hashMap源碼就舒服多了,紅黑樹插入後自平衡的情景有很多種,在上一篇紅黑色的部落格中都有提到,還有就是左旋右旋的實作,這裡就不再多說了,直接來撸源碼。
/**
 * 插入平衡(分多鐘情況,左旋,右旋)
 * @param root 目前根節點
 * @param x 目前要插入的節點
 * @return  傳回根節點(平衡涉及左旋右旋會将根節點改變,是以需要傳回最新的根節點) 
 */
static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,TreeNode<K,V> x) {
            x.red = true; //首先将要插入的節點染色成紅色,不要問為什麼不是黑色,因為黑色會破壞黑高(黑色完美平衡),還是不知道到先去學一下紅黑樹的性						  //質再來看
    
    		//xp:x的父節點,xpp:x的爺爺節點,xppl:爺爺節點的左子節點,xppr:爺爺節點的右子節點
            for (TreeNode<K,V> xp, xpp, xppl, xppr;;) { //死循環,直到找到根節點才結束。
                if ((xp = x.parent) == null) { //如果目前插入節點的父節點為空,那麼說明目前節點就是根節點,
                    x.red = false;	//染色為黑色(根節點規定為黑色)
                    return x;
                }
                else if (!xp.red || (xpp = xp.parent) == null) //如果爸爸節點為黑色或者爺爺節點為空(插入後不影響黑色完美平衡,直接傳回)
                    return root;
                if (xp == (xppl = xpp.left)) { //目前插入節點的父節點為紅色,并且是爺爺節點的左子節點(有兩種情況:LL或者LR)
                    if ((xppr = xpp.right) != null && xppr.red) { //叔叔節點存在并且為紅色 
                        xppr.red = false; //将爸爸節點和叔叔節點染色成黑
                        xp.red = false;
                        xpp.red = true; //将爺爺節點染色成紅
                        x = xpp; //最後将爺爺節點設定為目前節點進行下一輪操作
                    }
                    else { //叔叔節點不存在或者為黑色
                        if (x == xp.right) { // 目前插入節點是父節點的右子節點(LR的情景)
                            root = rotateLeft(root, x = xp); //以爸爸節點為旋轉節點進行左旋
                            xpp = (xp = x.parent) == null ? null : xp.parent; //設定爺爺節點
                        }
                        if (xp != null) { //左旋完了之後,就回到了LL的情景(爸爸節點是爺爺節點的左子節點,目前節點是爸爸節點的左子節點),然後爸爸節點又是紅色,目前插入節點也是紅色,違反了紅黑色的性質,紅色不能兩兩相連,是以接下來需要進行染色;
                            xp.red = false;  //将爸爸節點染色為黑
                            if (xpp != null) {
                                xpp.red = true; //将爺爺節點染色為紅,然後在對爺爺節點右旋。
                                root = rotateRight(root, xpp);
                            }
                        }
                    }
                }
                else { //爸爸節點是爺爺節點的右子節點,爸爸節點為紅色,也有兩種情況(RR 或者 RL)
                    if (xppl != null && xppl.red) { //叔叔節點不為空并且為紅色
                        xppl.red = false; //需要将爸爸節點和叔叔節點染色成黑
                        xp.red = false;
                        xpp.red = true; //将爺爺節點染色成紅
                        x = xpp; //并且爺爺節點設定為目前節點進行下一輪操作
                    }
                    else { //叔叔節點不存在或者為黑色
                        if (x == xp.left) { //目前插入節點是爸爸節點的左子節點(RL的情景)
                            root = rotateRight(root, x = xp); //先将爸爸節點右旋變成的RR的情景
                            xpp = (xp = x.parent) == null ? null : xp.parent;
                        }
                        if (xp != null) { //這個時候已經變成了RR的情況,需要染色在意爺爺節點左旋來維持平衡
                            xp.red = false; //将爸爸節點染色為黑
                            if (xpp != null) {
                                xpp.red = true; //将爺爺節點染色成紅
                                root = rotateLeft(root, xpp); //在對爺爺節點左旋
                            }
                        }
                    }
                }
            }
        }
           
/**
 * 左旋
 * @param root 目前根節點
 * @param p 指定的旋轉節點
 * @return  傳回根節點(平衡涉及左旋右旋會将根節點改變,是以需要傳回最新的根節點) 
 * 左旋示意圖:左旋p節點
         pp                  pp
         |                   |
         p                   r
        / \         ---->   / \
       l   r               p   rr
          / \             / \
         rl  rr          l   rl
  左旋做了幾件事?
     * 1、将rl設定為p的右子節點,将rl的父節點設定為p
     * 2、将r的父節點設定pp,将pp的左子節點或者右子節點設定為r
     * 3、将r的左子節點設定為p,将p的父節點設定為r
 */
static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,TreeNode<K,V> p) {
    	// r:旋轉節點的右子節點;	pp:旋轉節點的父節點, rl:旋轉節點的右子節點的左子節點
            TreeNode<K,V> r, pp, rl;
            if (p != null && (r = p.right) != null) { //旋轉節點非空并且旋轉節點的右子節點非空
                if ((rl = p.right = r.left) != null)  //将p節點的右子節點設定為右子節點的左子節點
                    rl.parent = p; //将rl的父節點設定為p
                if ((pp = r.parent = p.parent) == null)//将r的爸爸節點設定為p的爸爸節點,如果是空的話
                    (root = r).red = false;//染色成黑
                else if (pp.left == p) //判斷父節點是爺爺節點的左子節點還是右子節點
                    pp.left = r; //如果是左子節點,那麼就把爺爺節點的左子節點設定為r
                else  
                    pp.right = r; //如果是右子節點,就把爺爺節點的右子節點設定為r
                r.left = p; //最後将r的左子節點設定為p
                p.parent = r; //将p的爸爸節點設定為r
            }
            return root;
        }
           
/**
 * 右旋
 * @param root 目前根節點
 * @param p 指定的旋轉節點
 * @return  傳回根節點(平衡涉及左旋右旋會将根節點改變,是以需要傳回最新的根節點) 
 * 右旋示意圖:右旋p節點
     
         pp                      pp
         |                       |
         p                       l
        / \          ---->      / \
       l   r                   ll  p
      / \                         / \
     ll  lr                      lr  r
     
     * 右旋都做了幾件事?
     * 1.将lr設定為p節點的左子節點,将lr的父節點設定為p
     * 2.将l的父節點設定為pp,将pp的左子節點或者右子節點設定為l
     * 3.将l的右子節點設定為p,将p的父節點設定為l
 */
static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,TreeNode<K,V> p) {
    //l:p節點的左子節點 pp:p節點的爸爸節點 lr:p節點的左子節點的右子節點
            TreeNode<K,V> l, pp, lr;
            if (p != null && (l = p.left) != null) { //旋轉節點p非空并且p節點的左子節點非空
                if ((lr = p.left = l.right) != null) //将p節點的左子節點設定為左子節點的右子節點
                    lr.parent = p; //然後将p節點的左子節點的右子節點的父節點設定為p
                if ((pp = l.parent = p.parent) == null) //将p節點的左子節點的父節點設定為p的父節點,如果為空的話,說明l就是根節點了
                    (root = l).red = false; //染色成黑
                else if (pp.right == p) //判斷p節點是pp節點的左子節點還是右子節點,
                    pp.right = l; //如果p節點是pp節點的右子節點的話,将爸爸節點pp的右子節點設定為l
                else //如果p節點是pp節點的左子節點的話,将爸爸節點pp的左子節點設定為l
                    pp.left = l;
                l.right = p; //最後将l節點的右子節點設定為p
                p.parent = l; //将p節點的父節點設定為l
            }
            return root;
        }