HashMap源码(JKD1.8)深度分析-红黑树(删除)

阅读本专栏需要了解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;
                        }
                    }
                }
            }
        }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值