学完了红黑树的性质,再来看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;
}