转载

TreeMap之元素删除

微信公众号:I am CR7

如有问题或建议,请在下方留言

最近更新:2018-09-07

TreeMap之插入

通过上一篇文章,介绍了二分查找树的缺陷,引出了红黑树的介绍。进一步分析TreeMap中插入元素的源码,最后借助示例来加深对于红黑树的理解。详细请看TreeMap之元素插入

TreeMap之删除

1、删除指定key对应的元素:

 1public V remove(Object key) {
 2    //根据key获取键值对
 3    Entry<K,V> p = getEntry(key);
 4    if (p == null)
 5        return null;
 6    //删除该键值对,并返回value值
 7    V oldValue = p.value;
 8    deleteEntry(p);
 9    return oldValue;
10}
复制代码

2、删除节点,并对树进行平衡调整:

2.1、源码:

 1private void deleteEntry(Entry<K,V> p) {
 2    modCount++;
 3    size--;
 4
 5    //如果存在左右节点,则获取其后继节点,完成key、value的复制,p指向后继节点
 6    //此处思想就是将有左右节点的节点p的删除转化为其后继节点的删除
 7    if (p.left != null && p.right != null) {
 8        Entry<K,V> s = successor(p);
 9        p.key = s.key;
10        p.value = s.value;
11        p = s;
12    } // p has 2 children
13
14    //获取删除节点的替代节点
15    Entry<K,V> replacement = (p.left != null ? p.left : p.right);
16
17    //如果存在替代节点,则进行替代。接着先删除节点p,再进行删除的平衡调整
18    if (replacement != null) {
19        //用替代节点替代删除节点
20        replacement.parent = p.parent;
21        if (p.parent == null)
22            root = replacement;
23        else if (p == p.parent.left)
24            p.parent.left  = replacement;
25        else
26            p.parent.right = replacement;
27
28        //删除节点p
29        p.left = p.right = p.parent = null;
30
31        //如果删除节点为黑色,必然影响树的平衡,进行平衡调整
32        if (p.color == BLACK)
33            fixAfterDeletion(replacement);
34    } else if (p.parent == null) { // return if we are the only node.
35        root = null;
36    } else {  //如果不存在替代节点,不需要替代。接着先进行删除的平衡调整,再删除节点p
37        //如果删除节点为黑色,必然影响树的平衡,进行平衡调整
38        if (p.color == BLACK)
39            fixAfterDeletion(p);
40
41        //删除节点p
42        if (p.parent != null) {
43            if (p == p.parent.left)
44                p.parent.left = null;
45            else if (p == p.parent.right)
46                p.parent.right = null;
47            p.parent = null;
48        }
49    }
50}
51
52//注意该方法执行的前提是t节点有左右节点,所以返回的是右子树里最左边的非空
53static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
54    if (t == null)
55        return null;
56    else if (t.right != null) {  //存在右子树
57        Entry<K,V> p = t.right;
58        while (p.left != null) //一直往左走
59            p = p.left;
60        return p; //返回右子树里最左的非空节点
61    } else {
62        Entry<K,V> p = t.parent;
63        Entry<K,V> ch = t;
64        while (p != null && ch == p.right) {
65            ch = p;
66            p = p.parent;
67        }
68        return p;
69    }
70}
复制代码

通过上述逻辑我们知道,删除节点分为下面几种情况:

  • 有左右孩子节点
  • 只有一个孩子节点
  • 无孩子节点(叶子节点)【情况最为复杂,后面会详细讲解】

2.2、代码整理成流程图:

TreeMap之元素删除
图注:删除元素流程图

2.3、示例:

  1. 有左右孩子,且无替代节点:【先调整后删除的流程】

    TreeMap之元素删除
    图注:有左右孩子但无替代节点

无替代节点,说明后继节点走到了叶子节点。需要调整说明后继节点为黑色,正如上图所示。此时就转化为对于叶子节点10的删除操作了,处理过程请往后看。

  1. 有左右孩子,且有替代节点:【先删除后调整的流程】

    TreeMap之元素删除
    图注:有左右孩子且有替代节点

有替代节点,说明后继节点未走到叶子节点,那么后继节点肯定只有一个孩子节点,否则必可以走到叶子节点(此处可以画图体会体会)。一个节点只有一个孩子节点,那么只有一种可能,就是节点为黑,孩子为红,否则树不平衡,正如上图所示。此时,替代节点为红色,调整逻辑就很简单了,就是将该红色节点改成黑色节点即可。

  1. 只有一个孩子节点,必有替代节点:【先删除后调整的流程】

    TreeMap之元素删除
    图注:只有一个孩子->必有替代节点

只有一个孩子节点,替代节点获取的规则就是要么左节点,要么右节点,所以必有替代节点。一个节点只有一个孩子节点,那么只有一种可能,就是节点为黑,孩子为红,否则树不平衡,正如上图所示。此时,替代节点为红色,调整逻辑就很简单了,就是将该红色节点改成黑色节点即可。

2.4、总结:

通过上述的分析,凡是先进行删除,后进行调整的情况,其调整逻辑都很简单,就是只需要将节点颜色从红色变成黑色即可。先进行调整,后进行删除的情况,才是逻辑最为复杂的,请继续往下看。

3、对树进行删除后的调整:

  1private void fixAfterDeletion(Entry<K,V> x) {
  2    //只要x不为根节点且为黑色,就需要调整
  3    while (x != root && colorOf(x) == BLACK) {
  4        //X是否为父亲节点的左节点
  5        if (x == leftOf(parentOf(x))) {
  6            //获取X右兄弟节点sib
  7            Entry<K,V> sib = rightOf(parentOf(x));
  8            //X兄弟节点为红色转化为X兄弟节点为黑色的情况
  9            if (colorOf(sib) == RED) {
 10                //设置X兄弟节点为黑色
 11                setColor(sib, BLACK);
 12                //设置X父亲节点为红色
 13                setColor(parentOf(x), RED);
 14                //以X父亲节点为中心进行左旋
 15                rotateLeft(parentOf(x));
 16                //sib指向X的兄弟节点
 17                sib = rightOf(parentOf(x));
 18            }
 19
 20            //兄弟节点左右节点都为黑色
 21            if (colorOf(leftOf(sib))  == BLACK &&
 22                colorOf(rightOf(sib)) == BLACK) {
 23                //兄弟节点改成红色
 24                setColor(sib, RED);
 25                //X指向父节点
 26                x = parentOf(x);
 27            } else {
 28                //X远侄节点为黑色转化为X远侄节点为红色的情况
 29                if (colorOf(rightOf(sib)) == BLACK) {
 30                    //近侄节点改成黑色
 31                    setColor(leftOf(sib), BLACK);
 32                    //兄弟节点改成红色
 33                    setColor(sib, RED);
 34                    //以兄弟节点为中心进行右旋
 35                    rotateRight(sib);
 36                    //sib指向X右兄弟节点
 37                    sib = rightOf(parentOf(x));
 38                }
 39                //设置sib为X父节点颜色
 40                setColor(sib, colorOf(parentOf(x)));
 41                //设置X父节点为黑色
 42                setColor(parentOf(x), BLACK);
 43                //设置远侄节点为黑色
 44                setColor(rightOf(sib), BLACK);
 45                //以X父亲节点为中心进行左旋
 46                rotateLeft(parentOf(x));
 47                //X指向根,结束循环
 48                x = root;
 49            }
 50        } else { // symmetric
 51            //获取X左兄弟节点sib
 52            Entry<K,V> sib = leftOf(parentOf(x));
 53            //X兄弟节点为红色转化为X兄弟节点为黑色的情况
 54            if (colorOf(sib) == RED) {
 55                //设置X兄弟节点为黑色
 56                setColor(sib, BLACK);
 57                //设置X父亲节点为红色
 58                setColor(parentOf(x), RED);
 59                //以X父亲节点为中心进行右旋
 60                rotateRight(parentOf(x));
 61                //sib指向X的兄弟节点
 62                sib = leftOf(parentOf(x));
 63            }
 64
 65            //兄弟节点左右节点都为黑色
 66            if (colorOf(rightOf(sib)) == BLACK &&
 67                colorOf(leftOf(sib)) == BLACK) {
 68                //兄弟节点改成红色
 69                setColor(sib, RED);
 70                //X指向父节点
 71                x = parentOf(x);
 72            } else {
 73                //X远侄节点为黑色转化为X远侄节点为红色的情况
 74                if (colorOf(leftOf(sib)) == BLACK) {
 75                    //近侄节点改成黑色
 76                    setColor(rightOf(sib), BLACK);
 77                    //兄弟节点改成红色
 78                    setColor(sib, RED);
 79                    //以兄弟节点为中心进行左旋
 80                    rotateLeft(sib);
 81                    //sib指向X左兄弟节点
 82                    sib = leftOf(parentOf(x));
 83                }
 84                //设置sib为X父节点颜色
 85                setColor(sib, colorOf(parentOf(x)));
 86                //设置X父节点为黑色
 87                setColor(parentOf(x), BLACK);
 88                //设置远侄节点为黑色
 89                setColor(leftOf(sib), BLACK);
 90                //以X父亲节点为中心进行右旋
 91                rotateRight(parentOf(x));
 92                //X指向根,结束循环
 93                x = root;
 94            }
 95        }
 96    }
 97
 98    //设置X为黑色
 99    setColor(x, BLACK);
100}
复制代码

3.1、代码整理为流程图:

TreeMap之元素删除
图注:删除操作的平衡流程图
高清图请看:https://user-gold-cdn.xitu.io/2018/9/7/165b3848ec1c7eee?w=1914&h=2314&f=jpeg&s=382012

3.2、优化的流程图:

TreeMap之元素删除
图注:删除操作的平衡流程图-优化版
高清图请看:https://user-gold-cdn.xitu.io/2018/9/7/165b3848ec062ee1?w=1246&h=1341&f=jpeg&s=191723

3.3、流程图解析说明:

  1. 兄弟节点为黑色,远侄节点为红色(删除节点X为叶子节点,且为黑色[如果为红直接删除即可])

    TreeMap之元素删除
    图注:兄弟为黑,远侄为红
  2. 兄弟节点为黑色,远侄节点为黑色(删除节点X为叶子节点,且为黑色[如果为红直接删除即可])

    TreeMap之元素删除
    图注:兄弟为黑,远侄为黑

    兄弟节点为黑色,并且删除节点X为黑色,且为叶子节点->远侄节点如果为黑色,必然是null节点,否则树不平衡。

  3. 兄弟节点为黑色,远侄近侄节点都为黑色(删除节点X为叶子节点,且为黑色[如果为红直接删除即可])

    TreeMap之元素删除
    图注:兄弟为黑,远近侄为黑
  4. 兄弟节点为红色(删除节点X为叶子节点,且为黑色[如果为红直接删除即可])

    TreeMap之元素删除
    图注:兄弟为红

    兄弟节点为红色,X为黑色,且是叶子->远侄节点和近侄节点必为null节点,否则树不平衡。

3.4、示例:

  1. 兄弟节点为红色

    TreeMap之元素删除
    图注:兄弟为红
    高清图请看:https://user-gold-cdn.xitu.io/2018/9/7/165b33e01bf074ab?w=7315&h=640&f=jpeg&s=558111
  2. 兄弟节点为黑色,远侄节点为红色

    TreeMap之元素删除
    图注:兄弟为黑,远侄为红
    高清图请看:https://user-gold-cdn.xitu.io/2018/9/7/165b33e02c61c338?w=4471&h=640&f=jpeg&s=347442
  3. 兄弟节点为黑色,远侄节点为黑色

    TreeMap之元素删除
    图注:兄弟为黑,远侄为黑
    高清图请看:https://user-gold-cdn.xitu.io/2018/9/7/165b3848ed9e566b?w=6613&h=640&f=jpeg&s=480598
  4. 兄弟节点为黑色,远侄近侄节点都为黑色

    TreeMap之元素删除
    图注:兄弟为黑,远近侄为黑
    高清图请看:https://user-gold-cdn.xitu.io/2018/9/7/165b33e03146c697?w=3902&h=640&f=jpeg&s=273947

总结

红黑树的删除相对于插入而言,复杂了不少。但是只要时刻记住五条性质,对于包含的每种场景动手去练习,我想理解它应该也不是难事。

最后,如果任何意见或建议,欢迎大家留言,如果觉得可以,记得点赞转发哦。

原文  https://juejin.im/post/5b923cefe51d450e4d2f99d1
正文到此结束
Loading...