java1.8 hashMap红黑树源码解析左旋右旋,balanceInsertion,rotateLeft,rotateRight方法的实现原理

3 篇文章 0 订阅
3 篇文章 0 订阅

学完了红黑树的性质,再来看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;
        }
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值