红黑树的插入与删除

平衡二叉树

  平衡二叉查找树(Self-balancing binary search tree)又被称为AVL树(有别于AVL算法),且具有以下性质:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

对节点10来说,左边节点10的高度差为1,右边10的高度差为2,所以右边的不是平衡二叉查找树。

平衡因子

      某结点的左子树与右子树的高度(深度)差即为该结点的平衡因子(BF,Balance Factor)。平衡二叉树上所有结点的平衡因子只可能是 -1,0 或 1。如果某一结点的平衡因子绝对值大于1则说明此树不是平衡二叉树。

按照公式   平衡因子 = 左子树的高度 - 右子树的高度

1 : 表示左子树比右子树高

-1  : 表示右子树比左子树高

0 : 表示左子树和右子树等高

上述右图节点10的平衡因子 = 左子树的高度 - 右子树的高度 = 2 - 0 = 2;不满足平衡二叉树的特性。

红黑树的性质

  红黑树是在AVL树的基础上发展而来的。红黑树是一种二叉查找树,但在每个节点增加一个存储位表示节点的颜色,可以是 红或黑(非红即黑)。通过对任何一条从根到叶子的路径上各个节点着色的方式的限制,红黑树确保没有一条路径会比其它 路径长出两倍,因此,红黑树是一种弱平衡二叉树,相对于要求严格的AVL树来说,它的旋转次数少,所以对于搜索,插入, 删除操作较多的情况下,通常使用红黑树。

  红黑树属于平衡二叉树。说它不严格是因为它不是严格控制左、右子树高度或节点数之差小于等于1。但红黑树高度依然是平均log(n),且最坏情况高度不会超过2log(n),这有数学证明。所以它算平衡树,只是不严格。

  红黑树需要满足以下特性:

  1. 节点是红色或黑色。
  2. 根是黑色。
  3. 所有叶子都是黑色(叶子是NIL节点)。
  4. 每个红色节点必须有两个黑色的子节点。(从每个叶子到根的所有路径上不能有两个连续的红色节点。)
  5. 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点(简称黑高)。

正是红黑树的这5条性质,使一棵n个节点的红黑树始终保持了logn的高度,从而也就解释了上面所说的“红黑树的查找、插入、删除的时间复杂度最坏为O(log n)”这一结论成立的原因。

(注:上述第3、5点性质中所说的叶子节点(NIL节点),包括wikipedia.算法导论上所认为的叶子节点即为树尾端的NIL指针,或者说NULL节点。然百度百科以及网上一些其它博文直接说的叶节点,则易引起误会,因此叶节点非子节点)

如下图所示,即是一颗红黑树(下图引自wikipedia:http://t.cn/hgvH1l):

如上图所示,叶子节点(NIL节点)它不包含数据而只充当树在此结束的指示,这些节点在绘图中经常被省略,望看到此文后的读者朋友注意。

树的旋转

在对红黑树进行插入和删除等操作时,对树做了修改可能会破坏红黑树的性质。为了继续保持红黑树的性质,可以通过对节点进行重新着色,以及对树进行相关的旋转操作,即通过修改树中某些节点的颜色及指针结构,来达到对红黑树进行插入或删除节点等操作后继续保持它的性质或平衡的目的。

LL(右旋)

LL的意思是向左子树(L)的左孩子(L)中插入新节点后导致不平衡,这种情况下需要右旋操作,而不是说LL的意思是右旋,后面的也是一样。如图,节点4插入到节点10的左子树的左子树中,导致节点10的平衡因子 = 2 - 0 = 2,不满足平衡二叉树的特性,需要进行右旋。
在这里插入图片描述
我们将这种情况抽象出来,得到下图:
在这里插入图片描述
我们需要对节点y进行平衡的维护。步骤如下图所示:
在这里插入图片描述

RR(左旋)

RR的意思是向右子树(R)的左孩子(R)中插入新节点后导致不平衡,这种情况下需要左旋操作,而不是说RR的意思是左旋。如图,节点13插入到节点10的右子树的右子树中,导致节点10的平衡因子 = 0 - 2 = -2,不满足平衡二叉树的特性,需要进行左旋。

在这里插入图片描述
我们将这种情况抽象出来,得到下图:
在这里插入图片描述
我们需要对节点y进行平衡的维护。步骤如下图所示:
在这里插入图片描述

LR

在这里插入图片描述
我们将这种情况抽象出来,得到下图:
在这里插入图片描述
我们需要对节点y进行平衡的维护。步骤如下图所示:

 RL

在这里插入图片描述
我们将这种情况抽象出来,得到下图:
在这里插入图片描述
我们需要对节点y进行平衡的维护。步骤如下图所示:

插入

  红黑树的插入过程和二叉查找树插入过程基本类似,不同的地方在于,红黑树插入新节点后,需要进行调整,以满足红黑树的性质。性质1规定红黑树节点的颜色要么是红色要么是黑色,那么在插入新节点时,这个节点应该是红色还是黑色呢?答案是红色,原因也不难理解。如果插入的节点是黑色,那么这个节点所在路径比其他路径多出一个黑色节点,这个调整起来会比较麻烦(参考红黑树的删除操作,就知道为啥多一个或少一个黑色节点时,调整起来这么麻烦了)。如果插入的节点是红色,此时所有路径上的黑色节点数量不变,仅可能会出现两个连续的红色节点的情况。这种情况下,通过变色和旋转进行调整即可,比之前的简单多了。

情况一:

插入的新节点 N 是红黑树的根节点,这种情况下,我们把节点 N 的颜色由红色变为黑色,性质2(根是黑色)被满足。同时 N 被染成黑色后,红黑树所有路径上的黑色节点数量增加一个,性质5(从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点)仍然被满足。

情况二:

N 的父节点是黑色,这种情况下,性质4(每个红色节点必须有两个黑色的子节点)和性质5没有受到影响,不需要调整。

情况三:

N 的父节点是红色(节点 P 为红色,其父节点必然为黑色),叔叔节点 U 也是红色。由于 P 和 N 均为红色,所有性质4被打破,此时需要进行调整。这种情况下,先将 P 和 U 的颜色染成黑色,再将 G 的颜色染成红色。此时经过 G 的路径上的黑色节点数量不变,性质5仍然满足。但需要注意的是 G 被染成红色后,可能会和它的父节点形成连续的红色节点,此时需要递归向上调整。

情况四:

N 的父节点为红色,叔叔节点为黑色。节点 N 是 P 的右孩子,且节点 P 是 G 的左孩子。此时先对节点 P 进行左旋,调整 N 与 P 的位置。接下来按照情况五进行处理,以恢复性质4。

这里需要特别说明一下,上图中的节点 N 并非是新插入的节点。当 P 为红色时,P 有两个孩子节点,且孩子节点均为黑色,这样从 G 出发到各叶子节点路径上的黑色节点数量才能保持一致。既然 P 已经有两个孩子了,所以 N 不是新插入的节点。情况四是由以 N 为根节点的子树中插入了新节点,经过调整后,导致 N 被变为红色,进而导致了情况四的出现。考虑下面这种情况(PR 节点就是上图的 N 节点):

如上图,插入节点 N 并按情况三处理。此时 PR 被染成了红色,与 P 节点形成了连续的红色节点,这个时候就需按情况四再次进行调整。

情况五:

N 的父节点为红色,叔叔节点为黑色。N 是 P 的左孩子,且节点 P 是 G 的左孩子。此时对 G 进行右旋,调整 P 和 G 的位置,并互换颜色。经过这样的调整后,性质4被恢复,同时也未破坏性质5。

插入总结

上面五种情况中,情况一和情况二比较简单,情况三、四、五稍复杂。但如果细心观察,会发现这三种情况的区别在于叔叔节点的颜色,如果叔叔节点为红色,直接变色即可。如果叔叔节点为黑色,则需要选选择,再交换颜色。当把这三种情况的图画在一起就区别就比较容易观察了,如下图:

HashMap中的红黑树

hashMap中链表转为红黑树的两大条件

  table数组的大小>64

  链表中的元素的数量>8

造一些数据来观察链表转成红黑树的过程

public class Main {

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Map<String,String> map = new HashMap<String, String>();
        for (int i = 0; i < 65; i++) {
            map.put("sdf"+ String.valueOf(i),"s" + String.valueOf(i));
            if(getHashCodeIndex("sdf"+ String.valueOf(i)) == 26){
                System.out.println("index=======26时的key:" +  "sdf"+ String.valueOf(i));
            }
        }
        System.out.println(getHashCodeIndex("caocao"));
        System.out.println(getHashCodeIndex("sevennnn"));
        System.out.println(getHashCodeIndex("hate"));
        System.out.println(getHashCodeIndex("happyy"));
        System.out.println(getHashCodeIndex("hapqw"));
        System.out.println(getHashCodeIndex("mg"));
        System.out.println(getHashCodeIndex("vqv"));
        System.out.println(getHashCodeIndex("vaf"));
        System.out.println(getHashCodeIndex("vbde"));
        System.out.println(getHashCodeIndex("cfqb"));
        System.out.println(getHashCodeIndex("u7u42"));
        System.out.println(getHashCodeIndex("pp4b"));
        System.out.println("================");
        
        map.put("caocao","11");
        map.put("sevennnn","22");
        map.put("hate","33");
        map.put("happyy","44");
        map.put("hapqw","55");
        map.put("mg","66");
        map.put("vqv","77");
        map.put("vaf","88");
        map.put("vbde","99");
        map.put("cfqb","111");
        map.put("u7u42","222");
        map.put("pp4b","333");
    }

    /**
     * @Description table大小为128,添加元素时在数组中的下标index
     * @Param [key]
     * @return int
     * @date 2020/8/28 14:32
     * @auther Administrator
     */
    public static int getHashCodeIndex(String key){
        int h;
        h = key.hashCode();
        int hash = h ^ (h >>> 16);
        return 127 & hash;
    }
}

  输出:

index=======26时的key:sdf15
index=======26时的key:sdf59
26
26
26
26
26
26
26
26
26
================

  所以,当map添加("vqv","77")时,链表中的元素将转化成红黑树。

  treeifyBin(Node<K,V>[] tab, int hash)方法是用TreeNode节点替换Node节点,并将原单向链表转化成双向链表

/**
     * Replaces all linked nodes in bin at index for given hash unless
     * table is too small, in which case resizes instead.
     */
    final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        //table为null或是table的长度小于最小的树化值,直接扩容
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
        //数组中元素不为null,进行节点的替换
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            //定义头结点,尾节点
            TreeNode<K,V> hd = null, tl = null;
            //循环
            do {
                //将Node节点替换成TreeNode节点
                TreeNode<K,V> p = replacementTreeNode(e, null);
                //尾节点为空,p是第一个节点
                if (tl == null)
                    //将p赋值给头结点
                    hd = p;
                else {
                    //尾节点不为空,建立p和尾节点的前后关系
                    p.prev = tl;
                    tl.next = p;
                }
                //将尾节点重置为p
                tl = p;
            } while ((e = e.next) != null);//要替换的下一个节点不为空
            //头结点不为空,将双向链表转成红黑树
            if ((tab[index] = hd) != null)
                hd.treeify(tab);
        }
    }

   TreeNode类的继承关系如下,所以treeNode中是含有next属性的。上述treeNode对象的parent,left,right属性都是null

/**
     * Entry for Tree bins. Extends LinkedHashMap.Entry (which in turn
     * extends Node) so can be used as extension of either regular or
     * linked node.
     */
    static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
        TreeNode<K,V> parent;  // red-black tree links
        TreeNode<K,V> left;
        TreeNode<K,V> right;
        TreeNode<K,V> prev;    // needed to unlink next upon deletion
        boolean red;
        TreeNode(int hash, K key, V val, Node<K,V> next) {
            super(hash, key, val, next);
        }
}
/**
     * HashMap.Node subclass for normal LinkedHashMap entries.
     */
    static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }
static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;
}

然后treeify(Node<K,V>[] tab)方法形成具体的红黑树,balanceInsertion方法维护红黑树的平衡,其中涉及到左旋和右旋操作

/**
     * Forms tree of the nodes linked from this node.
     */
    final void treeify(Node<K,V>[] tab) {
        TreeNode<K,V> root = null;
        //hd调用的该方法,所以this是头结点
        for (TreeNode<K,V> x = this, next; x != null; x = next) {
            //定义next节点
            next = (TreeNode<K,V>)x.next;
            //将x节点的左右节点置为null
            x.left = x.right = null;
            if (root == null) {
                //根节点为null,x节点即为根节点
                x.parent = null;
                x.red = false;
                root = x;
            }
            else {//根节点存在
                K k = x.key;
                int h = x.hash;//获取x节点的hash值
                Class<?> kc = null;
                for (TreeNode<K,V> p = root;;) {
                    int dir, ph;
                    K pk = p.key;
                    //比较p节点和x节点的hash值
                    if ((ph = p.hash) > h)
                        dir = -1;
                    else if (ph < h)
                        dir = 1;
                    else if ((kc == null &&
                            (kc = comparableClassFor(k)) == null) ||
                            (dir = compareComparables(kc, k, pk)) == 0)
                        dir = tieBreakOrder(k, pk);
                    //将p节点作为x的parent节点保存起来
                    TreeNode<K,V> xp = p;
                    //根据x、p节点的hash值差,重置p节点为其左右节点的值并判断是否为null
                    if ((p = (dir <= 0) ? p.left : p.right) == null) {
                        //为null,建立p节点和x节点之间的关系
                        x.parent = xp;
                        if (dir <= 0)
                            //x的hash值 <= p的hash值,x位于树节点p的左侧
                            xp.left = x;
                        else
                            //反之,位于右侧
                            xp.right = x;
                        //插入节点之后,进行红黑树平衡的维护
                        root = balanceInsertion(root, x);
                        break;
                    }
                }
            }
        }
        //确保根节点是桶中第一个节点(数组table中)
        moveRootToFront(tab, root);
    }

  balanceInsertion(TreeNode<K,V> root,TreeNode<K,V> x)方法维护插入之后红黑树的平衡

static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
                                                TreeNode<K,V> x) {
        //将新插入节点的red属性置为true,新插入的节点均为红色节点
        x.red = true;
        //定义x节点的父节点xp,祖父节点xpp,祖父节点的左节点xppl,祖父节点的右节点xppr
        for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
            //x节点的父节点为null,说明x节点为根节点
            if ((xp = x.parent) == null) {
                x.red = false;
                return x;
            }
            //x的父节点为黑色节点或是x节点的祖父节点为null(xp为root节点),不影响红黑树特性
            else if (!xp.red || (xpp = xp.parent) == null)
                //返回根节点
                return root;
            //x节点的父节点是否是其祖父节点的左节点
            if (xp == (xppl = xpp.left)) {
                //是,x节点的叔叔节点是否为红色节点
                if ((xppr = xpp.right) != null && xppr.red) {
                    //x节点的叔叔节点是红色节点,将其父节点、叔叔节点置为黑色节点,祖父节点置为红色节点
                    //并将x节点重置为祖父节点
                    xppr.red = false;
                    xp.red = false;
                    xpp.red = true;
                    x = xpp;
                }
                else {//x节点的叔叔节点为null或是叔叔节点为黑色节点
                    if (x == xp.right) {//x节点是其父节点的右节点
                        //先对父节点进行左旋
                        root = rotateLeft(root, x = xp);
                        xpp = (xp = x.parent) == null ? null : xp.parent;
                    }
                    if (xp != null) {//x节点是其父节点的左节点
                        //或左旋之后新的xp节点及xpp节点不为null
                        xp.red = false;
                        if (xpp != null) {
                            xpp.red = true;
                            //对xpp节点进行右旋
                            root = rotateRight(root, xpp);
                        }
                    }
                }
            }
            else {//x节点的父节点不是其祖父节点的左节点
                //x节点的叔叔节点是否为红色节点
                if (xppl != null && xppl.red) {
                    //为红色节点,将其父节点、叔叔节点置为黑色节点,祖父节点置为红色节点
                    xppl.red = false;
                    xp.red = false;
                    xpp.red = true;
                    x = xpp;
                }
                else {//x节点的叔叔节点为null或是黑色节点
                    if (x == xp.left) {//x节点是其父节点的左节点
                        //对父节点进行右旋
                        root = rotateRight(root, x = xp);
                        xpp = (xp = x.parent) == null ? null : xp.parent;
                    }
                    if (xp != null) {//x节点是其父节点的右节点
                        //或右旋之后新的xp节点及xpp节点不为null
                        xp.red = false;
                        if (xpp != null) {
                            xpp.red = true;
                            //对xpp节点进行左旋
                            root = rotateLeft(root, xpp);
                        }
                    }
                }
            }
        }
    }

 

  每插入一个节点图示说明红黑树的变化。

  插入第一个节点("sdf15","s15"),该节点是红黑树的根节点

  插入第二个节点("sdf59","s59")

 

   插入第三个节点("caocao","11")

 

    因为新插入的节点是红色,这样节点2和节点3都是红色,不符合红黑树特性,需要进行调整。对节点1进行右旋,所以右旋之前先改变节点1和节点2的颜色

     右旋过后的红黑树

 

  插入第四个节点("sevennnn","22"),注意此时节点4有一个叔叔节点,需要考虑叔叔节点的颜色了

   连续两个红色节点不满足红黑树特性,进行维护,参见插入情况的第三种

  插入第五个节点("hate","33")

 

 

  插入第六个节点("happyy","44"),连续两个红色节点,先对节点5进行右旋,再对节点3进行左旋

 

   对节点5进行右旋

  对节点3进行左旋,左旋之前改变节点3和节点6的颜色

   进行左旋

 

  插入第七个节点("hapqw","55") 

 插入节点之后红黑树的维护,父节点和叔叔节点均为红色,改变节点3、5和节点6的颜色

  插入第八个节点("mg","66") 

 

  插入第九个节点("vqv","77") 

 

  插入节点后进行红黑树的维护,父节点和叔叔节点均为红色,先改变节点5和节点7、8的颜色(循环体第一次)

 

  将节点5看成新插入的节点,其父节点为红色,叔叔节点为黑色,对节点6进行左旋(循环体第二次) 

  

  接着对节点2进行右旋,最终

 

 至此,链表转为红黑树完成。

注意此时根节点由节点1变成节点5,所以,各节点间的双向链表如下:

此红黑树中添加第十个节点("vaf","88");

 进行维护,对节点9进行右旋,如下

对节点8进行左旋,左旋之前,先更改节点10和节点8的颜色

 对节点8进行左旋,如下

各节点之间的双向链表如下:

 

 此红黑树添加第十一个节点("vbde","99")

 

 各节点之间的双向链表如下:

添加第十二个节点("cfqb","111")

 

 红黑树的维护,节点12的父节点和叔叔节点均为红色,先改变节点8、9和节点10的颜色

 可将节点10看成新插入的节点,其父节点6和叔叔节点2均为红色节点。继续,改变节点2、6和节点5的颜色

将根节点置为黑色,最终

添加第十三个节点("u7u42","222")

 对节点4进行右旋:

 对节点1进行左旋

 

添加第十四个节点("pp4b","333")

 对节点7进行右旋

 

 红黑树的删除

关于红黑树的删除参见知乎上的这篇文章

数据结构:红黑树的删除

=================复制====================

红黑树也是一颗二叉排序树,节点的删除也是分为3种情况即,将要删除的节点没有子节点,将要删除的节点下有一个子节点,将要删除的节点下有两个子节点。有不了解的可以参考我们前面介绍二叉搜索树的文章,这里不再赘述。我们主要介绍各种不同情况下节点的修复是怎样操作的。在介绍之前我们再次回顾一下红黑树的5个特性,这非常重要,因为删除节点后,很可能会破坏这5个特性,我们的修复操作就是使它重新满足这5个特性。

  1. 所有节点都是红色或者黑色
  2. 根节点为黑色
  3. 所有的 NULL 叶子节点都是黑色
  4. 如果该节点是红色的,那么该节点的子节点一定都是黑色
  5. 所有的 NULL 节点到根节点的路径上的黑色节点数量一定是相同的

下面我们开始讨论修复操作(下面的叶子节点都是指非NULL的叶子节点):

A. 删除的是叶子节点且该叶子节点是红色的 ---> 无需修复,因为它不会破坏红黑树的5个特性

B. 删除的是叶子节点且该叶子节点是黑色的 ---> 很明显会破坏特性5,需要修复。

C. 删除的节点(为了便于叙述我们将其称为P)下面有一个子节点 S,对于这种情况我们通过 将P和S的值交换的方式,巧妙的将删除P变为删除S,S是叶子节点,这样C这种情况就会转 换为A, B这两种情况:

C1: P为黑色,S为红色 ---> 对应 A 这种情况

C2: P为黑色或红色,S为黑色 --- > 对应 B 这种情况

D. 删除的节点有两个子节点,对于这种情况,我们通过将P和它的后继节点N的值交换的方 式,将删除节点P转换为删除后继节点N,而后继节点只可能是以下两种情况:

D1: N是叶子节点 --- > 对应情况 A 或 B

D2: N有一个子节点 ---- > 对应情况 C

所以通过上面的分析我们发现,红黑树节点删除后的修复操作都可以转换为 A 或 B这两种情况,而A不需要修复,所以我们只需要研究B这种情况如何修复就行了。

下面我们讨论如何修复B中情况:

 

=================复制====================

 removeTreeNode方法,注意替代节点以及删除节点有两个子节点的情况下其后继节点这两个节点的取值

/**
     * Removes the given node, that must be present before this call.
     * This is messier than typical red-black deletion code because we
     * cannot swap the contents of an interior node with a leaf
     * successor that is pinned by "next" pointers that are accessible
     * independently during traversal. So instead we swap the tree
     * linkages. If the current tree appears to have too few nodes,
     * the bin is converted back to a plain bin. (The test triggers
     * somewhere between 2 and 6 nodes, depending on tree structure).
     */
    final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
                              boolean movable) {
        int n;
        if (tab == null || (n = tab.length) == 0)
            return;
        int index = (n - 1) & hash;
        TreeNode<K,V> first = (TreeNode<K,V>)tab[index], root = first, rl;
        TreeNode<K,V> succ = (TreeNode<K,V>)next, pred = prev;
        //维护删除节点的前后节点的next,prev关系
        if (pred == null)
            tab[index] = first = succ;
        else
            pred.next = succ;
        if (succ != null)
            succ.prev = pred;
        if (first == null)
            return;
        if (root.parent != null)
            root = root.root();
        if (root == null
                || (movable
                && (root.right == null
                || (rl = root.left) == null
                || rl.left == null))) {
            tab[index] = first.untreeify(map);  // too small
            return;
        }
        TreeNode<K,V> p = this, pl = left, pr = right, replacement;
        //删除节点有两个子节点,交换删除节点和后继节点的值(包括节点的颜色)=======交换两个节点的位置
        if (pl != null && pr != null) {
            TreeNode<K,V> s = pr, sl;
            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;
            if (s == pr) { // p was s's direct parent
                p.parent = s;
                s.right = p;
            }
            else {
                TreeNode<K,V> sp = s.parent;
                if ((p.parent = sp) != null) {
                    if (s == sp.left)
                        sp.left = p;
                    else
                        sp.right = p;
                }
                if ((s.right = pr) != null)
                    pr.parent = s;
            }
            p.left = null;
            if ((p.right = sr) != null)
                sr.parent = p;
            if ((s.left = pl) != null)
                pl.parent = s;
            if ((s.parent = pp) == null)
                root = s;
            else if (p == pp.left)
                pp.left = s;
            else
                pp.right = s;
            //根据后继节点只有一个子节点来获取其替代节点
            if (sr != null)
                replacement = sr;
            else
                replacement = p;
        }
        //删除节点只有一个左节点,为其替代值
        else if (pl != null)
            replacement = pl;
        else if (pr != null)//删除节点只有一个右节点,为其替代值
            replacement = pr;
        else//删除节点没有子节点,替代值为其本身
            replacement = p;
        if (replacement != p) {//当替代值不是删除节点时,维护替代值和删除节点父节点间树关系
            TreeNode<K,V> pp = replacement.parent = p.parent;
            if (pp == null)
                (root = replacement).red = false;
            else if (p == pp.left)
                pp.left = replacement;
            else
                pp.right = replacement;
            //将要删除节点的属性置为null,删除此节点
            p.left = p.right = p.parent = null;
        }
        
        //根据删除节点的颜色决定是否需要红黑树的平衡维护
        TreeNode<K,V> r = p.red ? root : balanceDeletion(root, replacement);

        if (replacement == p) {  // detach      替代节点是删除节点自己,维护删除节点及其父节点间的树关系
            TreeNode<K,V> pp = p.parent;
            p.parent = null;
            if (pp != null) {
                if (p == pp.left)
                    pp.left = null;
                else if (p == pp.right)
                    pp.right = null;
            }
        }
        if (movable)
            //将根节点移到数组中
            moveRootToFront(tab, r);
    }

 

按照上述分类图示说明删除节点后的红黑树的变化(以前面插入数据后的红黑树为基础):

A. 删除的是叶子节点且该叶子节点是红色的 ---> 无需修复,因为它不会破坏红黑树的5个特性  删除节点12为例

 

 

找到节点12的替代节点是其本身,删除的是红色节点,无需进行红黑树删除的维护,直接删除节点12即可,最终红黑树如下

 

 

B. 删除的是叶子节点且该叶子节点是黑色的 ---> 很明显会破坏特性5,需要修复。  删除节点8为例

 

找到节点8的替代节点是其本身,删除的是黑色节点,需进行红黑树删除的维护,维护类型属于上述修复中:删除的是左节点中的第一种类型。最终红黑树如下

 

 

C. 删除的节点(为了便于叙述我们将其称为P)下面有一个子节点 S,对于这种情况我们通过将节点S替换到节点P的位置,然后将节点P中属性置为null,根据节点P的颜色来判断是否需要执行红黑树的平衡维护(P为黑色,维护以替代节点为中心进行)。这种情况下,S只能是红色的,如果S是黑色的话,无论节点P是红是黑,对节点P来说,都不满足黑高的特性。

所以只有一种情况,P为黑色,S为红色。  删除节点9为例

找到要删除节点9的替代节点12,维护替代节点12和要删除节点9的父节点10之间的树关系,并将要删除的节点9中的属性置为null。如下

 

 要删除的节点是黑色,需要进行红黑树平衡的维护,以替代节点12为中心进行维护,12为红色节点,变成黑色即可,最终如下

 

D. 删除的节点有两个子节点,对于这种情况,我们通过将P和它的后继节点N的值交换的方式(包括颜色======也就是互换位置),那么此时要删除的节点P就变成了至多只有一个子节点的情况,可以分别按照情况A、情况B以及情况C来处理。  删除节点2为例

 

 维护节点1、节点2及其与周边节点间的关系

 

 节点1和节点2互换位置

对于要删除的节点2来说,可以按照A中的情况进行处理,最终如下

 

 

 

 

 

 

 

 参考:

 详细图文——AVL树

红黑树详解

 红黑树详细分析,看了都说好

 AVL树平衡因子详解(左子树-右子树)

 请你说一说红黑树的性质还有左右旋转

HashMap源码(七) —— 红黑树删除原理分析 动态图解析!!!

聊聊java中的哪些Map:(二)HashMap中的TreeNode

posted @ 2020-08-29 17:45  吹灭读书灯  阅读(542)  评论(0编辑  收藏  举报