删除元素
删除元素本身比较简单,就是采用二叉树的删除规则。
- 如果删除的位置有两个叶子节点,则从其右子树中取最小的元素放到删除的位置,然后把删除位置移到替代元素的位置,进入下一步。
- 如果删除的位置只有一个叶子节点(有可能是经过第一步转换后的删除位置),则把那个叶子节点作为替代元素,放到删除的位置,然后把这个叶子节点删除。
- 如果删除的位置没有叶子节点,则直接把这个删除位置的元素删除即可。
- 针对红黑树,如果删除位置是黑色节点,还需要做再平衡。
- 如果有替代元素,则以替代元素作为当前节点进入再平衡。
- 如果没有替代元素,则以删除的位置的元素作为当前节点进入再平衡,平衡之后再删除这个节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| public V remove(Object key) { Entry<K,V> p = getEntry(key); if (p == null) return null;
V oldValue = p.value; deleteEntry(p); return oldValue; }
private void deleteEntry(Entry<K,V> p) { modCount++; size--;
if (p.left != null && p.right != null) { Entry<K,V> s = successor(p); p.key = s.key; p.value = s.value; p = s; }
Entry<K,V> replacement = (p.left != null ? p.left : p.right);
if (replacement != null) { replacement.parent = p.parent; if (p.parent == null) root = replacement; else if (p == p.parent.left) p.parent.left = replacement; else p.parent.right = replacement;
p.left = p.right = p.parent = null;
if (p.color == BLACK) fixAfterDeletion(replacement); } else if (p.parent == null) { root = null; } else { if (p.color == BLACK) fixAfterDeletion(p);
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; } } }
|
删除再平衡
经过上面的处理,真正删除的肯定是黑色节点才会进入到再平衡阶段。
因为删除的是黑色节点,导致整颗树不平衡了,所以这里我们假设把删除的黑色赋予当前节点,这样当前节点除了它自已的颜色还多了一个黑色,那么:
- 如果当前节点是根节点,则直接涂黑即可,不需要再平衡;
- 如果当前节点是红+黑节点,则直接涂黑即可,不需要平衡;如果当前节点是黑+黑节点,则我们只要通过旋转把这个多出来的黑色不断的向上传递到一个红色节点即可,这又可能会出现以下四种情况:
(假设当前节点为父节点的左子节点)
情况 |
策略 |
1)x是黑+黑节点,x的兄弟是红节点 |
(1)将兄弟节点设为黑色; (2)将父节点设为红色; (3)以父节点为支点进行左旋; (4)重新设置x的兄弟节点,进入下一步; |
2)x是黑+黑节点,x的兄弟是黑节点,且兄弟节点的两个子节点都是黑色 |
(1)将兄弟节点设置为红色; (2)将x的父节点作为新的当前节点,进入下一次循环; |
3)x是黑+黑节点,x的兄弟是黑节点,且兄弟节点的右子节点为黑色,左子节点为红色 |
(1)将兄弟节点的左子节点设为黑色; (2)将兄弟节点设为红色; (3)以兄弟节点为支点进行右旋; (4)重新设置x的兄弟节点,进入下一步; |
3)x是黑+黑节点,x的兄弟是黑节点,且兄弟节点的右子节点为红色,左子节点任意颜色 |
(1)将兄弟节点的颜色设为父节点的颜色; (2)将父节点设为黑色; (3)将兄弟节点的右子节点设为黑色; (4)以父节点为支点进行左旋; (5)将root作为新的当前节点(退出循环); |
(假设当前节点为父节点的右子节点,正好反过来)
情况 |
策略 |
1)x是黑+黑节点,x的兄弟是红节点 |
(1)将兄弟节点设为黑色; (2)将父节点设为红色; (3)以父节点为支点进行右旋; (4)重新设置x的兄弟节点,进入下一步; |
2)x是黑+黑节点,x的兄弟是黑节点,且兄弟节点的两个子节点都是黑色 |
(1)将兄弟节点设置为红色; (2)将x的父节点作为新的当前节点,进入下一次循环; |
3)x是黑+黑节点,x的兄弟是黑节点,且兄弟节点的左子节点为黑色,右子节点为红色 |
(1)将兄弟节点的右子节点设为黑色; (2)将兄弟节点设为红色; (3)以兄弟节点为支点进行左旋; (4)重新设置x的兄弟节点,进入下一步; |
3)x是黑+黑节点,x的兄弟是黑节点,且兄弟节点的左子节点为红色,右子节点任意颜色 |
(1)将兄弟节点的颜色设为父节点的颜色; (2)将父节点设为黑色; (3)将兄弟节点的左子节点设为黑色; (4)以父节点为支点进行右旋; (5)将root作为新的当前节点(退出循环); |
让我们来看看TreeMap中的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
|
private void fixAfterDeletion(Entry<K,V> x) { while (x != root && colorOf(x) == BLACK) { if (x == leftOf(parentOf(x))) { Entry<K,V> sib = rightOf(parentOf(x));
if (colorOf(sib) == RED) { setColor(sib, BLACK); setColor(parentOf(x), RED); rotateLeft(parentOf(x)); sib = rightOf(parentOf(x)); }
if (colorOf(leftOf(sib)) == BLACK && colorOf(rightOf(sib)) == BLACK) { setColor(sib, RED); x = parentOf(x); } else { if (colorOf(rightOf(sib)) == BLACK) { setColor(leftOf(sib), BLACK); setColor(sib, RED); rotateRight(sib); sib = rightOf(parentOf(x)); } setColor(sib, colorOf(parentOf(x))); setColor(parentOf(x), BLACK); setColor(rightOf(sib), BLACK); rotateLeft(parentOf(x)); x = root; } } else { Entry<K,V> 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); }
|
删除元素举例
假设我们有下面这样一颗红黑树。
我们删除6号元素,则从右子树中找到了最小元素7,7又没有子节点了,所以把7作为当前节点进行再平衡。
我们看到7是黑节点,且其兄弟为黑节点,且其兄弟的两个子节点都是红色,满足情况4),平衡之后如下图所示。
我们再删除7号元素,则从右子树中找到了最小元素8,8有子节点且为黑色,所以8的子节点9是替代节点,以9为当前节点进行再平衡。
我们发现9是红节点,则直接把它涂成黑色即满足了红黑树的特性,不需要再过多的平衡了。
这次我们来个狠的,把根节点删除,从右子树中找到了最小的元素5,5没有子节点,所以把5作为当前节点进行再平衡。
我们看到5是黑节点,且其兄弟为红色,符合情况1),平衡之后如下图所示,然后进入情况2)。
对情况2)进行再平衡后如下图所示。
然后进入下一次循环,发现不符合循环条件了,直接把x涂为黑色即可,退出这个方法之后会把旧x删除掉(见deleteEntry()方法),最后的结果就是下面这样。