阅读本专栏需要了解HashMap底层数组+链表+红黑树的数据结构。红黑树的源码比较复杂,分插入和删除两期来讲,本文只说删除(插入的分析在这)。文章很长,需要花一些精力仔细阅读。文章内容是本人阅读HashMap源码结合查阅资料形成,如有不对的地方,欢迎留言讨论,确实有误的我会及时更正。
红黑树时一种二叉查找树,不是平衡二叉树,这些概念不了解的,建议先去了解一下。在二叉查找树的基础上,红黑树又有以下五个性质:
性质1. 结点是红色或黑色。
性质2. 根结点是黑色。
性质3. 所有叶子都是黑色。(叶子是NIL结点)
性质4. 每个红色结点的两个子结点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色结点)
性质5. 从任一节结点到其每个叶子的所有路径都包含相同数目的黑色结点。
同插入操作类似,红黑树的删除操作也是用到了最基本的左旋、右旋和变色操作,关于左旋和右旋在分析节点插入源码的时候已经讲过,本文不再赘述,不了解的,建议先去看一遍插入源码分析。
本文源码分析从HashMap.removeNode开始,再讲一下removeTreeNode,最后再讲最复杂的删除自平衡方法balanceDeletion。
removeNode方法
removeNode源码较为简单,看它的方法头
final Node<K,V> removeNode(int hash, Object key, Object value,boolean matchValue, boolean movable) {
它有5个参数分别是:
hash:要删除key的hash值;key:要删除的key;value:要删除的key对应的value;
matchValue:是否同时校验删除节点的value值与传入的value匹配;
movable:删除树节点后,是否要将根节点移动到链表的头部。
这个方法的删除思路是这样的:
1、根据传入的hash和key查找是否有当前key的节点,链表就遍历链表去查,红黑树就调用红黑树的方法查找。
2、如果找不到就返回null;如果找到了,是链表的话就调整链表结果,如果是红黑树就调用红黑树的删除方法。
3、最后调整size和modCount的值。
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K,V>[] tab; Node<K,V> p; int n, index;
//table赋值给tab,tab.length赋值给n
//判断table不为空,且根据hash值计算出的数组下标位置的元素不为空
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
Node<K,V> node = null, e; K k; V v;
//判断当前元素的节点是不是要找的节点
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
//如果是,直接赋值给node,下面会调用node的remove逻辑
node = p;
//如果不是,且p.next不为空则说明是链表或树结构
//为空的话,则认为是没有当前key,直接走到方法最后返回null
else if ((e = p.next) != null) {
//检查p节点是否是树节点
if (p instanceof TreeNode)
//如果是树节点,则调用红黑树的查找方法
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else {
//如果不是树节点,说明是单纯的链表结构
//则遍历整个链表查找key的值
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
//找到后给node,跳出循环。
node = e;
break;
}
p = e;
} while ((e = e.next) != null);
}
}
//根据找到的node判断是否要执行删除操作
//首先要保证node不为空,node为空说明没有找到key对应的节点。
//如果传入的matchValue=true,还要判断value值是否匹配。
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
//node节点是树节点,则调用该节点的remove方法
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
//node == p说明是在上面的第一个if判断找到的node节点
//此时node节点为链表的头结点(即数组元素节点)
else if (node == p)
//直接把node.next赋值给当前元素
tab[index] = node.next;
//else的情况说明是通过链表遍历找到的node节点
//此时的p节点为node节点的前置节点
else
//直接把node节点的next节点赋值为p节点的next节点
p.next = node.next;
//modeCount ++ ,用于Fast-Fail机制
++modCount;
//size--
--size;
//对HashMap来说为空,供HashMap的子类实现,例如LinkedHashMap
afterNodeRemoval(node);
//返回找到的node节点
return node;
}
}
//走到这说明没有找到key对应的节点,返回null
return null;
}
这个方法调用了getTreeNode和removeTreeNode方法,先看getTreeNode源码。
//直接调用了root节点的find方法
final TreeNode<K,V> getTreeNode(int h, Object k) {
return ((parent != null) ? root() : this).find(h, k, null);
}
这个方法很简单,就是调用了root的方法的find方法,find的方法的源码已经在插入节点分析的那一篇讲过,不再赘述,不了解的可以去看看(HashMap源码(JDK1.8)分析-红黑树(插入))。
removeTreeNode方法
开始复杂一点了,removeTreeNode方法由TreeNode来调用,意思是删除当前调用该方法的节点。我们先不考虑红黑树的颜色,仅从保证树结构来看,删除情况无非就是一下三种情形:
1、被删除节点是叶子节点。
2、被删除节点只有一个左孩子或右孩子。
3、被删除节点既有左孩子又有右孩子。
仅从保证树结构的角度来看,对于情形1来说,直接删除即可;对于情形2来说,删除节点后,将左孩子或右孩子挪到被删除节点的位置即可;对于情形3是最复杂的,我们不能直接干掉,也不能把左孩子或右孩子简单的拿过来,这样无法保证红黑树的结构。
我们可以这样设想一下,如果把红黑树的所有节点按照从小到大排列的话,我们删除中间的某一个值之后,将被删除节点的前后两个节点替补过来,才会不影响排序。HashMap也是这么做的,她是把被删除节点右面最小的节点拿过来跟被删除节点交换,即把被删除节点右子树最左侧的节点,跟被删除节点交换位置和颜色。这样相当于整颗红黑树的结构没有变化,而且将情形3的这种情况变成了情形1或者情形2。只有被删除节点的位置是不对的,不过这没关系,因为它马上就被删除掉了。
这样我们就把问题简单化了,最终只需要处理情形1和情形2两种情况即可。
看下面的图示:
经过上面的处理可以看到,我们最终只需要处理情形1和情形2两种。对于这两种情况,对于情形2,我们需要将被删除节点的左孩子或右孩子顶替到P节点的位置;对于情形1,不需要替代节点,但是为了旋转和变色方便,我们制定替代节点就是它本身。
看代码
//删除红黑树节点,由被删除节点来调用,意味着要删除当前调用方法的节点
final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
boolean movable) {
int n;
//tab为空则直接返回
if (tab == null || (n = tab.length) == 0)
return;
//计算当前节点的hash对应的数组下标
int index = (n - 1) & hash;
//first 链表头结点、元素节点、一般也是根节点,rl root节点的left节点
TreeNode<K,V> first = (TreeNode<K,V>)tab[index], root = first, rl;
//succ 后置节点,pred 前置节点
TreeNode<K,V> succ = (TreeNode<K,V>)next, pred = prev;
//这一段是调整链表结构
if (pred == null)
tab[index] = first = succ;
else
pred.next = succ;
if (succ != null)
succ.prev = pred;
//first == null说明调整完毕后此处不再有任何节点,直接返回,正常情况下不会出现。
//因为走到这个方法的话,说经当前节点是树结构,而树结构最少要有6个节点否则会链表化。
if (first == null)
return;
//获取root节点
if (root.parent != null)
root = root.root();
//这几种情况来看,树结构太小了,直接将所有节点链表化即可,不用再调整树结构
if (root == null || root.right == null ||
(rl = root.left) == null || rl.left == null) {
tab[index] = first.untreeify(map); // too small
return;
}
//开始对红黑树进行调整
//p 当前节点,pl left节点,pr right节点,replacement 当前节点的替换节点
TreeNode<K,V> p = this, pl = left, pr = right, replacement;
//情形3 当前节点的左孩子和右孩子均不为空,这是最复杂的一种情况
/*
左右孩子均不为空的情况下,需要通过节点交换,转变为下面,情形1、2的情况再进行处理。
具体思路是:
寻找p节点右子树最左面的节点,跟p节点交换位置和颜色。
为什么是右子树最左面的节点,因为这个节点是以p为根节点的子树上比p节点大的最小的节点
这样情形1就变成了下面的情形3或情形4(取决于交换位置后的p节点有没有右孩子节点)
*/
if (pl != null && pr != null) {
//s = pr,从p节点的右子树开始遍历
TreeNode<K,V> s = pr, sl;
//sl = s.left,循环遍历,知道找到最左面的节点(s.left == null),即为没有左孩子的节点
while ((sl = s.left) != null) // find successor
s = sl;
//开始交换位置和颜色
//交换颜色
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;
//如果找到的s节点是p节点的右孩子
if (s == pr) { // p was s's direct parent
//p的parent指向s
p.parent = s;
//s的right指向p
s.right = p;
}
//如果找到的s节点不是p节点的右孩子
else {
//sp为s的父节点
TreeNode<K,V> sp = s.parent;
//p的parent指向s的父节点
//判断如果s的父节点不为空
//(这应该是不可能的,应为s是p这个子树的节点,肯定会有个父节点)
if ((p.parent = sp) != null) {
//如果s是其父节点的左孩子
if (s == sp.left)
//s父节点的left指向p
sp.left = p;
//反之是其父节点的右孩子
else
//s父节点的right指向p
sp.right = p;
}
//s节点的right指向p节点的右孩子
//且判断其是否为空
if ((s.right = pr) != null)
//不为空的话,p节点右孩子的parent指向s节点
pr.parent = s;
}
//p节点的left置为空(因为s节点的左孩子为空)
p.left = null;
//p节点的rigt指向s节点的右孩子,并且判断是否为空
if ((p.right = sr) != null)
//不为空的话s节点的右孩子的parent指向p
sr.parent = p;
//s节点的left指向p节点的左孩子,并判断是否为空
if ((s.left = pl) != null)
//不为空的话,p节点左孩子的parent指向s
pl.parent = s;
//s节点的parent指向p节点的父节点,并判断是否为空
if ((s.parent = pp) == null)
//为空则说明原p节点为根节点,那么此时s节点应该为根节点
root = s;
//否则判断p节点是否是原父节点的左孩子
else if (p == pp.left)
//p节点原父节点的left指向s
pp.left = s;
else
//否则p节点原父节点的right指向s
pp.right = s;
原s节点的右孩子是否为空,即交换位置后的p节点是否有右孩子
if (sr != null)
//有的话,其右孩子节点为p节点的替代节点
replacement = sr;
else
//否则替代节点是自己(没有替代节点,可以直接删除)
//为什么不再判断是否有左孩子节点,因为p现在交换到了s的位置
//s节点本身就是原p节点右面最左位置的节点,所以肯定没有左孩子
//因此此处p也没有左孩子,交换的目的就是让p变成最多只有一个右孩子
replacement = p;
}
//情形2 如果左孩子不为空,则使用左孩子代替
else if (pl != null)
replacement = pl;
//情形2 右孩子不为空,则使用右孩子代替
else if (pr != null)
replacement = pr;
else
//情形1 左右节点均为空则,不用其他节点代替
replacement = p;
//根据计算出的代替节点来执行删除
//走到这里就只有情形1、2了(不考虑颜色的情况)
//如果替代节点不是p节点,说明p是有(且只有一个)孩子节点的
if (replacement != p) {
//先执行删除
//将替代节点的parent指向p的父节点
TreeNode<K,V> pp = replacement.parent = p.parent;
//pp为null,说明p节点为根节点
if (pp == null)
//此时根节点为p的替代节点
root = replacement;
//否则判断p节点是其父节点的左孩子还是有右孩子
else if (p == pp.left)
//是左孩子,p节点父节点的left指向替代节点
pp.left = replacement;
else
//否则是有孩子,p节点的父节点的right指向替代节点
pp.right = replacement;
//p节点的parent、right、left均指向空
p.left = p.right = p.parent = null;
}
//如果删除的节点是红节点,则不用做删除自平衡,因为并不会影响红黑树的任何属性
//如果删除的节点是黑节点,则需要调用删除自平衡方法
//同时将根节点赋值给r。
TreeNode<K,V> r = p.red ? root : balanceDeletion(root, replacement);
//如果替代节点是P节点本身,则先做自平衡,再执行删除
if (replacement == p) { // detach
TreeNode<K,V> pp = p.parent;
//P节点与其父节点断开
p.parent = null;
if (pp != null) {
//P是左孩子
if (p == pp.left)
//P的父节点的left置为空
pp.left = null;
//P是右孩子
else if (p == pp.right)
//P的父节点的right置为空
pp.right = null;
}
}
if (movable)
//将根节点移动到链表的头部
moveRootToFront(tab, r);
}
balanceDeletion删除自平衡方法
大家看上面的方法可能注意到了,对于替代节点replacement和被删除节点P是否是同一个节点,调用balanceDeletion的时机是不一样的。replacement不是P本身的话,先删除在调用自平衡方法;replacement节点是P节点本身的话,先调用自平衡方法在删除P节点。其实调用的先后顺序并不是一定的,也可以先坐自平衡,后删除节点。我猜测这样做的原因是为了删除自平衡方法逻辑简单一些(暂时是这么想,如有不对欢迎指正)。
通过上面的代码还可以看到,被删除节点P如果是红节点的话,是不需要做自平衡处理的,因为删除红节点,并没有破坏红黑树的任何性质。只有删除节点为黑节点,才需要坐自平衡,因此,进入到自平衡方法后,被删除节点肯定是黑节点。
balanceDeletion有两个入参static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root, TreeNode<K,V> x),大家注意第二个参数传入的是替代节点而不是被删除节点。
下面列出所有红黑树删除操作的情形,挨个分析说明该如何处理。
注意这个方法的入参x节点是替代节点,从上面的方法可以看出
替代节点有两种情况:
1、替代节点是被删除节点的左孩子或右孩子
2、替代节点是被删除节点本身
第一种情况是先删除,再调用自平衡方法;第二种情况是先做自平衡再删除节点。
以下几种情形,需要结合替代情况来解释。
情形1:替代节点是根节点,不用做任何平衡操作。
对于替代情况1:此时已经删除完毕,替代节点变成了根节点,
说明被删除节点就是根节点,删除后,整颗树的黑高发生了变化
但未破坏任何红黑树的性质,因此不用做自平衡
对于替代情况2:替代节点是被删除节点本身,说明被删除节点是根节点,
同上,也不需要做任何处理
情形2:替代节点是红节点,将其变黑,不用做任何平衡处理
对于替代情况1:替代节点是红节点,由于被删除节点只可能有一个孩子节点(经过上面的交换后可以确认)
因此替代节点是被删除节点的唯一子树,节点删除后,只要保证这个子树的黑高不变
即可保证红黑树的平衡,所以此时只需要把替代节点变为黑节点。
对于替代情况2:不可能出现这种情况,因为替代节点是被删除节点本身,
而只有被删除节点为黑节点才能走进自平衡方法。
情形3:替代节点是黑节点,替代节点是其父节点的左孩子,又需要细分为两种情形。
对于情形3下面的所有子情形,都可以将替代方案1和2综合起来考虑。
情形3.1:替代节点是黑节点,且是左孩子,替代节点的兄弟节点是红节点。
这种情况的处理思路是,把它转换为情形3.2,变成替代节点的兄弟节点是黑节点的情况来处理。
具体处理方法为:1、将替代节点的父节点和兄弟节点颜色互换。
2、将替代节点的父节点左旋,这样替代节点的兄弟节点的左孩子,成了替代节点的兄弟节点
(由于替代节点的兄弟节点为红节点,由于红黑树的性质4,替代节点的孩子节点必然为黑节点
这样就达到了变成情形3:替代节点的兄弟节点是黑节点的目的。)
对于替代情况1和2:这种情况下替代情况1和2可以综合起来考虑,因为替代节点所在的子树,
其黑高必然减少1(替代情况1:黑高已经减1了;替代情况2:黑高必然将要减1)
无论如何都要做平衡。
情形3.2:替代节点是黑节点,且是左孩子,替代节点的兄弟节点是黑节点。这种情况又要细分为三种情况来处理。
情形3.2.1:替代节点的兄弟节点的右孩子是红节点,左孩子节点任意颜色。
处理思路为:将兄弟节点的右孩子节点变为黑色,并从兄弟节点借一个黑节点过来。
具体方法:1、兄弟节点和其父节点互换颜色,兄弟节点的右孩子变为黑色。
2、将父节点左旋。
情形3.2.2:替代节点的兄弟节点的左孩子是红节点,右孩子节点任意颜色。
处理思路为:讲这种情况通过旋转和变色转换为3.2.1,在按照3.2.1的方法进行处理
具体方法:1、是兄弟节点和其左孩子节点互换颜色。
2、将兄弟节点右旋
这样就变成了3.2.1,替代节点的兄弟节点的右孩子是红节点的情况。
情形3.2.3:替代节点的兄弟节点的左孩子和右孩子都是黑节点,或替代几点的兄弟节点为空(nil节点,为黑节点)。
处理思路:这种情况下,单靠兄弟节点已经解决不了问题,兄弟节点没有红节点可以变色后借过来。
只能讲当前节点指向其父节点,看能够通过父节点的兄弟节点解决问题。
解决不了继续把当前节点指向上层,从更高层尝试解决问题。
直到找到根节点,或成功借到兄弟节点的节点。
处理方法:1、将兄弟节点变为红节点,使父节点的左右子树黑高相同。
2、将当前节点指向父节点,继续向上判断
情形4:替代节点是黑节点,替代节点是其父节点的右孩子,这种情况和情形3类似,支持处理方法要对称过来,仍然要细分为两种情形。
情形4.1:替代节点是黑节点,且是右孩子,替代节点的兄弟节点是红节点。
这种情况需要转变为情形4.2继续处理
处理方法:1、将替代节点的父节点和兄弟节点颜色互换。
2、将替代节点的父节点右旋,这样替代节点兄弟节点的右孩子,变成了替代节点的兄弟节点。
情形4.2:替代节点是黑节点,且是右孩子,其兄弟节点是黑节点,仍然细分为三种情况处理
情形4.2.1:替代节点是黑节点,且是右孩子,替代节点的兄弟节点的右孩子是红节点。
处理办法:1、将兄弟节点和其父节点颜色互换,兄弟节点的左孩子变为黑节点。
2、将父节点右旋。
情形4.2.2:替代节点是黑节点,且是右孩子,替代节点的兄弟节点左孩子节点为红节点。
处理思路:参照情形3,仍然要将其转换为情形4.2.1的情况进行处理
处理办法:1、将兄弟节点与其右孩子颜色互换。
2、将兄弟节点左旋。
这样就变成了情形4.2.1
情形4.2.3:替换节点是黑节点,且是右孩子,其兄弟节点为黑节点,且兄弟节点的左右孩子均为黑节点,或替代几点的兄弟节点为空(nil节点,为黑节点)。
处理思路,同情形3.2.3,兄弟节点搞不定,需要继续向上处理,直到问题解决。
处理方法:1、将兄弟节点变为红节点,使父节点的左右子树黑高相同。
2、将当前节点指向父节点,继续向上判断
下面看图(情形1和情形2比较简单,情形3和情形4是对称的关系,图示以情形3为例),R为替代节点,P是R的父节点(注意P不一定是被删除节点,也可能被删除节点是R本身),图中的节点旁边的n和n+(-)1代表当前节点的黑高,从黑高可以看出,调整完毕后,红黑树时平衡的。
下面看源码
//入参x为替代节点
static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root,
TreeNode<K,V> x) {
//通过死循环来处理
//xp:x节点的父节点,xpl:x节点的左兄弟节点,xpr:x节点的右兄弟节点
for (TreeNode<K,V> xp, xpl, xpr;;) {
//x为替代节点为空,直接返回,不知道什么情况下会出现这种情况
//情形1:替代(或当前)节点为根节点,直接返回
if (x == null || x == root)
return root;
//情形1:替代节点的父节点为空,意味着替代节点是根节点,将替代节点变为黑色后返回。
else if ((xp = x.parent) == null) {
x.red = false;
return x;
}
//情形2:替代节点为红节点,将其变黑后返回。
else if (x.red) {
x.red = false;
return root;
}
//以上条件都不满足,则说明替代节点是黑节点,且不为根节点。
//判断替代节点是其父节点的左孩子,对应情形3
else if ((xpl = xp.left) == x) {
//情形3.1替代节点的兄弟节点(只可能是右兄弟,因为替代节点是左孩子)为红节点
if ((xpr = xp.right) != null && xpr.red) {
//替代节点的兄弟节点变为黑节点
xpr.red = false;
//替代节点的父节点变为红节点
xp.red = true;
//将替代节点的父节点左旋,变成情形3.2
root = rotateLeft(root, xp);
//重新理顺xp,xpr节点的关系
xpr = (xp = x.parent) == null ? null : xp.right;
}
//替代节点的兄弟节点为空(空节点相当于黑节点),属于情形3.2.3的情况
//这种情况经过上面的旋转后,才可能会出现
if (xpr == null)
//将当前节点指向其父节点,继续循环处理
x = xp;
else {
//sl:x节点兄弟节点的左孩子,sr:x节点兄弟节点的右孩子
TreeNode<K,V> sl = xpr.left, sr = xpr.right;
//左孩子和右孩子均为空或黑节点,属于情形3.2.3的情况
if ((sr == null || !sr.red) &&
(sl == null || !sl.red)) {
//将当前节点的兄弟节点变为红色,使父节点的左右子树黑高相同
xpr.red = true;
//将当前节点指向其父节点,继续循环处理
x = xp;
}
else {
//如果右节点为空或为黑节点
if (sr == null || !sr.red) {
/*
经过上面的这个if条件判断后,
if ((sr == null || !sr.red) &&(sl == null || !sl.red))
走到这里说明,左孩子节点不为空且为红节点,符合情形3.2.2,需要将其变为3.2.1再进行处理
*/
//这个判断多此一举
if (sl != null)
//将左孩子节点置为黑节点
sl.red = false;
//将x节点的兄弟节点变为红节点
xpr.red = true;
//将x节点的兄弟节点左旋,变成3.2.1的情形
root = rotateRight(root, xpr);
//重新理顺xp,xpr节点的关系
xpr = (xp = x.parent) == null ?
null : xp.right;
}
//下面是对情形3.2.1的处理
//如果xpr不为Null
if (xpr != null) {
//xp为null,说明xpr为根节点,变为黑节点
//xp节点不为null,则xpr的颜色变为xp的颜色
xpr.red = (xp == null) ? false : xp.red;
//sr:x节点兄弟节点右孩子不为null
//只要满足这个条件sr肯定为红节点(因为经过上面的处理,所有能处理的情形都变为情形3.2.1了
if ((sr = xpr.right) != null)
//将其变为黑节点
sr.red = false;
}
//x父节点不为空
if (xp != null) {
//x的父节点变为黑节点,相当于xp节点和xpr节点颜色互换
xp.red = false;
//将xp节点左旋
root = rotateLeft(root, xp);
}
//到此处理工作结束,将x指向root是保证下一次进入循环后,直接退出。
x = root;
}
}
}
//else是对称的情况,对应情形4
else { // symmetric
//x节点的兄弟节点不为空,且为红节点,对应情形4.1,需要将其变为情形4.2再处理
if (xpl != null && xpl.red) {
//x节点的兄弟节点变为黑色
xpl.red = false;
//x节点的父节点变为红色
xp.red = true;
//将x的父节点右旋,变成4.2的情形
root = rotateRight(root, xp);
//重新理顺xp,xpl的关系
xpl = (xp = x.parent) == null ? null : xp.left;
}
//如果xpl为空(nil节点)符合情形4.2.3
if (xpl == null)
//当前节点执行父节点,继续向上判断。
x = xp;
else {
TreeNode<K,V> sl = xpl.left, sr = xpl.right;
//当前节点的兄弟节点的左右孩子均为空或黑节点,符合情形4.2.3
if ((sl == null || !sl.red) &&
(sr == null || !sr.red)) {
//将当前节点的兄弟节点变为红节点,保证父节点的左右子树黑高相同(均少1)
xpl.red = true;
//当前节点指向其父节点,问题交给父辈们处理
x = xp;
}
//否则说明兄弟节点的左后孩子不都为空或黑节点
else {
//如果兄弟节点的左孩子不为空或黑节点
//则说明其右孩子肯定不为空且为红节点
//符合4.2.2的情形
if (sl == null || !sl.red) {
//多此一举的判断
if (sr != null)
//兄弟节点的右孩子变为黑节点
sr.red = false;
//兄弟节点变为红节点
xpl.red = true;
//将兄弟节点左旋,变为4.2.1的情形
root = rotateLeft(root, xpl);
//从新理顺xp,xpl的关系
xpl = (xp = x.parent) == null ?
null : xp.left;
}
//如果x节点的兄弟节点不为空
if (xpl != null) {
//x的父节点不为空,则x的兄弟节点的颜色变为x父节点的沿着
xpl.red = (xp == null) ? false : xp.red;
//x的兄弟节点的左孩子节点不为空,则将其左孩子变为黑色
if ((sl = xpl.left) != null)
sl.red = false;
}
//x节点的父节点不为空
if (xp != null) {
//将其变为黑色,也就是x节点兄弟节点的颜色,相当于兄弟节点和其父节点颜色互换
xp.red = false;
//将父节点右旋
root = rotateRight(root, xp);
}
//走到这说明问题已解决,将x置为根节点,保证下次循环进来后,直接退出。
x = root;
}
}
}
}
}