红黑树祥解

- 简介

    红黑树(Red Black Tree) 是一种近似平衡二叉查找树,具有基本二叉树的所有特性的同时,还具有如下附加特性。

    1. 每个节点都是红色或黑色

    2. 根节点是黑色

    3. 每个叶节点都是黑色的空节点(NIL节点)

    4. 每个红色节点的两个子节点都是黑色(从每个叶子到根的所有路径上不能有两个连续的红色节点)

    5. 从任一节点到每个叶子的所有路径都包含相同数目的黑色节点

    所以,红黑树从根到叶子的最长路径不会超过最短路径的2倍,当插入或删除节点的时候,红黑树的规则可能被打破,这时候需要对树进行调整(左旋,右旋,变色),来保证树的规则不被破坏。


红黑树祥解_第1张图片
一颗典型红黑树

- 新增节点

    在红黑树里,如果加入一个黑色节点,会导致所有经过这个节点的路径黑色节点增加,破坏了红黑树中从任一节点到每个叶子的所有路径都包含相同数目的黑色节点的规则,所以,红黑树中,新加入的节点都认为是红色节点。 如果新增的节点父节点是红色,则需要对树进行调整(旋转、着色),因为父节点和子节点可以同时为黑色,但是不能同时为红色。

- 旋转首色操作

    旋转是指对所当插入的节点出现破坏红黑树规则时,对插入节点的父节点进行左/右旋转来调整树的结构,当父节点旋转完成之后,如果还未满路红黑树规则,则继续对插入节上的祖父节点再次进行左/右旋转。

    1. 旋转逻辑,以Java TreeMap为例 (参考来自:https://blog.csdn.net/abcdef314159/article/details/77193888)

/** From CLR */

    private void fixAfterInsertion(TreeMapEntry x) {

            //在红黑树里面,如果加入一个黑色节点,则导致所有经过这个节点的路径黑色节点数量增加1,

            //这样就肯定破坏了红黑树中到所有叶节点经过的黑色节点数量一样的约定。所以,

        //我们最简单的办法是先设置加入的节点是红色的。

        x.color = RED;

        //当前节点变为红色,如果他的父节点是红色则需要调整,因为父节点和子节点不能同时为红色,但可以同时为黑色,

        //所以这里的循环条件是父节点必须为红色才需要调整。

        while (x != null && x != root && x.parent.color == RED) {

        //这里会分多钟情况讨论,

            if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {//如果父节点是爷爷节点的左节点

                TreeMapEntry y = rightOf(parentOf(parentOf(x)));//当前节点的叔叔节点,也就是他父节点的兄弟节点,这个也可能为null

                if (colorOf(y) == RED) {

                //因为添加的是红色,而父与子不能同时为红色,所以打破了平衡,需要先让父为黑色,然后再让爷爷为红色,因为爷爷节点为红色,所以

                //子节点必须为黑色,所以把叔叔节点也调为黑色,继续往上调整,

                //(1)如果当前节点的叔叔节点是红色,也就是说他的叔叔节点一定是存在的,因为如果为null,则colorOf会返回黑色。既然叔叔节点

                //是红色,那么他的爷爷节点一定是黑色,否则就打破了红黑平衡,那么他的父节点也一定是红色,因为只有父节点为红色才执行while

                //循环,这种情况下,无论x是父节点的左节点还是右节点都不需要在旋转,

                    setColor(parentOf(x), BLACK);//让x的父节点为黑色

                    setColor(y, BLACK);//叔叔节点也设为黑色

                    setColor(parentOf(parentOf(x)), RED);//当前节点的爷爷节点为红色

                    //把爷爷节点设置为红色之后,继续往上循环,即使执行到最上端也不用担心,因为在最后会把根节点设置为黑色的。

                    x = parentOf(parentOf(x));

                } else {

                    //如果他的叔叔节点是黑色的,并且他的父节点是红色的,那么说明他的叔叔节点是null,因为如果叔叔节点是黑色的且不为空,

                    //那么违反了他的第5条性质所以这里叔叔节点是空。因为叔叔节点

                    //为空,出现了不平衡,所以这里当前节点无论是父节点的左节点还是右节点,都需要旋转

                    if (x == rightOf(parentOf(x))) {

                        //(2)当前节点是父节点的右节点,

                        x = parentOf(x);//让当前节点的父节点为当前节点

                        rotateLeft(x);//对父节点进行左旋

                    }

                    //(3)当前节点是父节点的左节点,这个左节点有可能是添加的时候添加到左节点的,也有可能是上面旋转的时候旋转到左节点的

                    setColor(parentOf(x), BLACK);//让父节点为黑色

                    setColor(parentOf(parentOf(x)), RED);//爷爷节点变为红色

                    rotateRight(parentOf(parentOf(x)));//对爷爷节点右旋

                }

            } else {//父节点为爷爷节点的右节点

                TreeMapEntry y = leftOf(parentOf(parentOf(x)));//找出叔叔节点

                //如果叔叔节点是红色,那么说明他一定是存在的,所以不需要旋转,这里要铭记,无论是左旋还是右旋的前提条件是他的叔叔节点不存在,

                //如果存在就不需要旋转,只需要遍历改变颜色值即可

                if (colorOf(y) == RED) {

                //(4)修改颜色

                    setColor(parentOf(x), BLACK);

                    setColor(y, BLACK);

                    setColor(parentOf(parentOf(x)), RED);

                    x = parentOf(parentOf(x));//改变颜色之后遍历

                } else {//没有叔叔节点

                    if (x == leftOf(parentOf(x))) {

                        x = parentOf(x);

                        rotateRight(x);//(5)右旋操作

                    }

                    setColor(parentOf(x), BLACK);

                    setColor(parentOf(parentOf(x)), RED);

                    rotateLeft(parentOf(parentOf(x)));//(6)左旋操作

                }

            }

        }

        root.color = BLACK;//根节点必须是黑色

    }

                代码中出现了6种的可能逻辑判断,下面,以图片来说明各种情况

红黑树祥解_第2张图片
图1,不需要旋转,只需要调整颜色

        


红黑树祥解_第3张图片
图2,图3,树不平衡,需要旋转



红黑树祥解_第4张图片
图4
红黑树祥解_第5张图片
图5,图6,树不平衡,需要旋转

无论怎么旋转,他的左节点永远小于他,右节点永远大于他。通过不断的while循环,最终保证红黑树的平衡。

下面来看一下旋转的方法,先看一下图

    

红黑树祥解_第6张图片
旋转示例图

    2. 左旋

/** From CLR */

    private void rotateLeft(TreeMapEntry p) {

//参照上面旋转的图来分析,p就是图中的x

        if (p != null) {

            TreeMapEntry r = p.right;//r相当于图中的40节点

            p.right = r.left;//让35节点(r.left也就是图中40节点的左节点)等于p的右节点,看上图

            //如果r.left!=null,让p等于他的父节点,因为在上一步他已经等于p的右节点,自然就是他的子节点

            //所以他的父节点自然就变成p了

            if (r.left != null)

                r.left.parent = p;

            //让原来p节点的父节点等于r的父节点,可以根据图看的很明白,通过旋转40节点,挪到上面了,

            r.parent = p.parent;

            //这里也很好理解,如果原来p的父节点为null,说明原来父节点就是根节点,这里让调整过来的r节点

            //(即40节点)成为根节点

            if (p.parent == null)

                root = r;

            //这里很好理解,如果原来p节点是左节点就让调整过来的r节点变成左节点,是右节点就让r变成右节点

            else if (p.parent.left == p)

                p.parent.left = r;

            else

                p.parent.right = r;

            //让p(也就是图中的30节点)成为r(也就是图中的40节点)的左节点,

            r.left = p;

            //然后让r(图中的40节点)成为p(图中的30节点)的父节点。

            p.parent = r;

        }

    }

以上,是红黑树的新增节点的处理逻辑,下面讲解一下删除节点的处理逻辑

     3. 删除节点

public V remove(Object key) {

        //getEntry(Object key)方法是获取TreeMapEntry,如果比当前节点大则找右节点,如果比当前节点小则找左节点

        //通过不断的循环,知道找到为止,如果没找着则返回为null。

        TreeMapEntry p = getEntry(key);

        if (p == null)

            return null;

        V oldValue = p.value;

        //找到之后删除

        deleteEntry(p);

        return oldValue;

    }

   deleteEntry方法

/**

    * Delete node p, and then rebalance the tree.

    */

    private void deleteEntry(TreeMapEntry p) {

        modCount++;

        size--;//删除,size减1

        // If strictly internal, copy successor's element to p and then make p

        // point to successor.

//当有两个节点的时候不能直接删除,要删除他的后继节点,后继节点最多只有一个子节点。因为如果p有两个子节点,你删除之后

//他的两个子节点怎么挂载,挂载到p的父节点下?这显然不合理,因为这样一来p的父节点很有可能会有3个子节点,那么最好的办法

//就是找一个替罪羊,删除p的后继节点s,当然删除前需要把后继节点s的值赋给p

        if (p.left != null && p.right != null) {

//successor(p)返回p节点的后继节点,其实这个后继节点就是比p大的最小值,这个待会再分析

            TreeMapEntry s = successor(p);

//把后继节点s的值赋值给p,待会删除的是后继节点s,注意这里赋值并没有把颜色赋给原来的p。当然这里删除并不会打乱树的

//大小顺序,因为后继节点是比p大的最小值,赋值之后在删除,树的大小顺序依然是正确的,这里只是把s的值赋给了p,如果

//再把p原来的值赋给s,在删除s可能就会更好理解了,但这其实并不影响。

            p.key = s.key;

            p.value = s.value;

            p = s;

        } // p has 2 children

        // Start fixup at replacement node, if it exists.

        TreeMapEntry replacement = (p.left != null ? p.left : p.right);

        if (replacement != null) {

//p有子节点,并且有且只有一个节点,因为如果p有两个节点,那么上面的successor方法会一直查找,要么返回p的右节点

//(前提是p的右节点没有左节点),要么会一直循环找到p的右节点的最左孙子节点。待会看successor代码会发现,如果p

//有2个子节点,那么successor返回的节点最多也只有1个节点。

            // Link replacement to parent

            replacement.parent = p.parent;

//如果p的父节点为null,说明p是root节点,因为执行到这一步,所以replacement是p唯一的节点,把p节点删除后,让

//replacement成为root节点

            if (p.parent == null)

                root = replacement;

//这个不会变,原来p是左节点就让replacement成为左节点,原来p为右节点就让replacement成为右节点。相当于替换p节点的位置

            else if (p == p.parent.left)

                p.parent.left  = replacement;

            else

                p.parent.right = replacement;

            // Null out links so they are OK to use by fixAfterDeletion.

//把p的子节点及父节点全部断开

            p.left = p.right = p.parent = null;

            // Fix replacement

//如果删除的是黑色要进行调整,因为黑色删除会打破红黑平衡,

//所以这里只是做颜色调整,调整的时候并没有删除。

            if (p.color == BLACK)

//上面的p确定只有一个节点replacement,但这里replacement子节点是不确定的,有可能0个,1个或2个。

                fixAfterDeletion(replacement);

        } else if (p.parent == null) { // return if we are the only node.

            root = null;//p是根节点,直接删除,不用调整

        } else { //  No children. Use self as phantom replacement and unlink.

//p没有子节点,说明p是个叶子节点,不需要找继承者,调整完之后直接删除就可以了。

//如果删除的是黑色,需要调整,上面的调整是删除之后再调整,是因为删除的不是叶子节点,如果调整之后再删除还有可能出现错误,

//而这里是调整之后再删除,是因为这里删除的是叶子节点,调整完之后直接把叶子节点删除就是了,删除之后调整的是颜色,并不是树的

//大小顺序

            if (p.color == BLACK)

                fixAfterDeletion(p);

//调整完之后再删除p节点,此时p是叶子节点,因为调整完之后通过左旋或右旋p.rarent可能为null,所以这里需要判断

            if (p.parent != null) {

                if (p == p.parent.left)

                    p.parent.left = null;

                else if (p == p.parent.right)

                    p.parent.right = null;

                p.parent = null;

            }

        }

    }

    
下面先来看一下successor方法,这个方法很简单,其实就是返回大于节点p的最小值,看一下代码

/**

    * Returns the successor of the specified Entry, or null if no such.

    */

    static TreeMapEntry successor(TreeMapEntry t) {

        if (t == null)

            return null;

        else if (t.right != null) {//t的右节点不为空

            TreeMapEntry p = t.right;

//循环左节点,如果左节点一开始就为null,那么直接就返回p,此时p是t的右节点,如果p的左节点

//存在,那么会一直循环,一直在找左节点,直到为null为止,

            while (p.left != null)

                p = p.left;

            return p;//所以查找到最后,返回的p最多只有一个节点,并且查找的p是大于t的最小值

        } else {

            TreeMapEntry p = t.parent;

            TreeMapEntry ch = t;

//不停的往上找父节点,直到p为null,或者父节点(这个父节点也可能是父节点的父节点的父节点,反正

//只要满足条件就会一直往上循环)是左节点,最终查找的结果是p是大于t的最小值,要明白这一点,首先要

//明白,一个节点大于他的左节点小于他的右节点

            while (p != null && ch == p.right) {

                ch = p;

                p = p.parent;

            }

            return p;//这里返回的p有可能有2个子节点,并且只有在t没有右节点的时候才有可能。

        }

    }

fixAfterDeletion方法,因为x所在分支少了一个黑色的节点,所以他的主要目的就是让x分支增加一个黑色节点。这个比fixAfterInsertion方法还难理解,看代码

/** From CLR */

    private void fixAfterDeletion(TreeMapEntry x) {

//再看这个方法之前先看一下最后一行代码,他会把x节点设置为黑色

//很明显,在x只有黑色的时候才会调整,因为删除黑色打破了红黑平衡,但deleteEntry方法中的删除有两种,

//一种是替换之后的replacement,这个replacement不是删除的节点,需要删除的节点在这之前就已经被删除,

//他是来找平衡的,因为删除之后在这一分支上少了一个黑色节点,如果replacement节点为红色,那么不用执行

// while循环,直接在最后把它置为黑色就正好弥补了删除的黑色节点,如果replacement是黑色,那么需要执行

//下面的while循环(前提是replacement不等于root)。还一种就是没有子节点的,先调整完在删除,如果他是

//红色,就不用执行while循环,直接删除就是了,下面置不置为黑色都一样,如果是黑色,就必须执行下面的方法,

//因为删除黑色会打破红黑平衡。

        while (x != root && colorOf(x) == BLACK) {

//x是父节点的左节点

            if (x == leftOf(parentOf(x))) {

                TreeMapEntry sib = rightOf(parentOf(x));//x的兄弟节点

//(1)兄弟节点是红色,这种情况下兄弟节点的父节点和子节点都一定是黑色的,

//然后让兄弟节点变为黑色,父节点变为红色,这种情况下从root节点到兄弟节点的各叶子节点黑色个数没有变化,

//但从root节点到x节点的黑色个数少了1(如果删除的是黑色节点,那么传进来的replacement分支上其实就已经少

//了一个黑色,但这里减少只是相对于传进来的x来说的,是相对的。),然后通过左旋,达到各分支上的黑色

//节点一致。

                if (colorOf(sib) == RED) {

                    setColor(sib, BLACK);

                    setColor(parentOf(x), RED);

                    rotateLeft(parentOf(x));//左旋

//通过左旋,x的位置已经改变,但这里sib还是等于x的兄弟节点。

                    sib = rightOf(parentOf(x));

                }

//其实执行到这一步往后可以认为x所在分支少了一个黑色节点。并且兄弟节点sib是黑色的

//(2)首先可以肯定一点的是sib节点肯定是黑色的,通过(1)及上面代码可以明白,如果sib是红色,那么他的子节

//点是黑色的,经过上面的左旋调整,sib的子节点会变为sib,也就是黑色。这里如果sib的两个子节点都是黑色的,那么

//让sib为红色,这样一来x分支和他兄弟分支sib都相当于少了一个黑色节点,所以从root节点到x分支和到sib分支的黑色

//节点都是一样的。那么问题来了,从root节点到x和sib分支的黑色节点比到其他分支的黑色节点明显是少了一个黑色节点,

//但是后面又让x等于x的父节点,所以如果父节点为红色,则跳出循环,在最后再变为黑色,此时所有的节点都又达到平衡,

//如果为黑色,则继续循环。

                if (colorOf(leftOf(sib))  == BLACK &&

                    colorOf(rightOf(sib)) == BLACK) {

                    setColor(sib, RED);

                    x = parentOf(x);

                } else {

//(3)如果sib的右子节点是黑色,左子节点是红色(如果两个都是黑色则执行上面),这样不能直接让sib节点变为红色,因为

//这样会打破平衡.这个时候需要让左子节点变黑色,sib节点再变为红色。如果这样,那么问题就来了,因为这样从root到

//sib左分支的黑色节点是没有变化,但从root到sib右分支的黑色节点明显是少了一个黑色节点,然后再对sib进行右旋,

//让sib的左右子节点又各自达到平衡。然后在重新定位sib节点。但即使这样,从root到x节点的分支依然少了一个黑色节点。

                    if (colorOf(rightOf(sib)) == BLACK) {

                        setColor(leftOf(sib), BLACK);

                        setColor(sib, RED);

                        rotateRight(sib);

                        sib = rightOf(parentOf(x));

                    }

//(4)由上上面可知sib是黑色的,即使sib的右节点为黑色,通过上面的改变颜色及旋转到最后sib还是黑色。sib的右节点是红色,

//如果是黑色,那么执行上面也会变为黑色,可以看一下下面的图(3).执行到这一步,从root

//到sib分支的黑色节点是没有变化,但从root到x分支的黑色节点是少了一个,然后执行下面的代码会使x的兄弟分支黑色节点不变

//x分支黑色节点加1,最终达到平衡。然后让x等于root,退出循环。最后是对root置为黑色,基本没有影响(因为root节点

//本来就是黑色),这里的代码让sib的颜色等于x父节点的颜色,基本没影响,其实他最终目的是让x所在分支增加一个黑色节点,

//来达到红黑平衡。

                    setColor(sib, colorOf(parentOf(x)));

                    setColor(parentOf(x), BLACK);

                    setColor(rightOf(sib), BLACK);

                    rotateLeft(parentOf(x));

                    x = root;

                }

            } else { // symmetric

//对称的,x是父节点的右节点的情况。

                TreeMapEntry sib = leftOf(parentOf(x));

                if (colorOf(sib) == RED) {

                    setColor(sib, BLACK);

                    setColor(parentOf(x), RED);

                    rotateRight(parentOf(x));

                    sib = leftOf(parentOf(x));

                }

                if (colorOf(rightOf(sib)) == BLACK &&

                    colorOf(leftOf(sib)) == BLACK) {

                    setColor(sib, RED);

                    x = parentOf(x);

                } else {

                    if (colorOf(leftOf(sib)) == BLACK) {

                        setColor(rightOf(sib), BLACK);

                        setColor(sib, RED);

                        rotateLeft(sib);

                        sib = leftOf(parentOf(x));

                    }

                    setColor(sib, colorOf(parentOf(x)));

                    setColor(parentOf(x), BLACK);

                    setColor(leftOf(sib), BLACK);

                    rotateRight(parentOf(x));

                    x = root;

                }

            }

        }

        setColor(x, BLACK);//最后把x节点置为黑色

    }

结合上面代码看一下下面的四张图


红黑树祥解_第7张图片


红黑树祥解_第8张图片


红黑树祥解_第9张图片


红黑树祥解_第10张图片

你可能感兴趣的:(红黑树祥解)