本文github地址html
上一篇文章史上最清晰的红黑树讲解(上)对Java TreeMap的插入以及插入以后的调整过程给出了详述。本文接着以Java TreeMap为例,从源码层面讲解红黑树的删除,以及删除以后的调整过程。若是尚未看过上一篇文章,请在阅读本文以前大体浏览一下前文,以方便理解。java
对于一棵二叉查找树,给定节点t,其后继(树种比大于t的最小的那个元素)能够经过以下方式找到:git
- t的右子树不空,则t的后继是其右子树中最小的那个元素。
- t的右孩子为空,则t的后继是其第一个向左走的祖先。
后继节点在红黑树的删除操做中将会用到。github
TreeMap中寻找节点后继的代码以下:markdown
// 寻找节点后继函数successor() static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) { if (t == null) return null; else if (t.right != null) {// 1. t的右子树不空,则t的后继是其右子树中最小的那个元素 Entry<K,V> p = t.right; while (p.left != null) p = p.left; return p; } else {// 2. t的右孩子为空,则t的后继是其第一个向左走的祖先 Entry<K,V> p = t.parent; Entry<K,V> ch = t; while (p != null && ch == p.right) { ch = p; p = p.parent; } return p; } }
remove(Object key)
的做用是删除key
值对应的entry
,该方法首先经过上文中提到的getEntry(Object key)
方法找到key
值对应的entry
,而后调用deleteEntry(Entry<K,V> entry)
删除对应的entry
。因为删除操做会改变红黑树的结构,有可能破坏红黑树的约束条件,所以有可能要进行调整。函数
getEntry()
函数前面已经讲解过,这里重点放deleteEntry()
上,该函数删除指定的entry
并在红黑树的约束被破坏时进行调用fixAfterDeletion(Entry<K,V> x)
进行调整。this
因为红黑树是一棵加强版的二叉查找树,红黑树的删除操做跟普通二叉查找树的删除操做也就很是类似,惟一的区别是红黑树在节点删除以后可能须要进行调整。如今考虑一棵普通二叉查找树的删除过程,能够简单分为两种状况:code
- 删除点p的左右子树都为空,或者只有一棵子树非空。
- 删除点p的左右子树都非空。
对于上述状况1,处理起来比较简单,直接将p删除(左右子树都为空时),或者用非空子树替代p(只有一棵子树非空时);对于状况2,能够用p的后继s(树中大于x的最小的那个元素)代替p,而后使用状况1删除s(此时s必定知足状况1,能够画画看)。htm
基于以上逻辑,红黑树的节点删除函数deleteEntry()
代码以下:blog
// 红黑树entry删除函数deleteEntry() private void deleteEntry(Entry<K,V> p) { modCount++; size--; if (p.left != null && p.right != null) {// 2. 删除点p的左右子树都非空。 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) {// 1. 删除点p只有一棵子树非空。 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 { // 1. 删除点p的左右子树都为空 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; } } }
上述代码中占据大量代码行的,是用来修改父子节点间引用关系的代码,其逻辑并不难理解。下面着重讲解删除后调整函数fixAfterDeletion()
。首先请思考一下,删除了哪些点才会致使调整?只有删除点是BLACK的时候,才会触发调整函数,由于删除RED节点不会破坏红黑树的任何约束,而删除BLACK节点会破坏规则4。
跟上文中讲过的fixAfterInsertion()
函数同样,这里也要分红若干种状况。记住,不管有多少状况,具体的调整操做只有两种:1.改变某些节点的颜色,2.对某些节点进行旋转。
上述图解的整体思想是:将状况1首先转换成状况2,或者转换成状况3和状况4。固然,该图解并不意味着调整过程必定是从状况1开始。经过后续代码咱们还会发现几个有趣的规则:a).若是是由状况1以后紧接着进入的状况2,那么状况2以后必定会退出循环(由于x为红色);b).一旦进入状况3和状况4,必定会退出循环(由于x为root)。
删除后调整函数fixAfterDeletion()
的具体代码以下,其中用到了上文中提到的rotateLeft()
和rotateRight()
函数。经过代码咱们可以看到,状况3实际上是落在状况4内的。状况5~状况8跟前四种状况是对称的,所以图解中并无画出后四种状况,读者能够参考代码自行理解。
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); // 状况1 setColor(parentOf(x), RED); // 状况1 rotateLeft(parentOf(x)); // 状况1 sib = rightOf(parentOf(x)); // 状况1 } if (colorOf(leftOf(sib)) == BLACK && colorOf(rightOf(sib)) == BLACK) { setColor(sib, RED); // 状况2 x = parentOf(x); // 状况2 } else { if (colorOf(rightOf(sib)) == BLACK) { setColor(leftOf(sib), BLACK); // 状况3 setColor(sib, RED); // 状况3 rotateRight(sib); // 状况3 sib = rightOf(parentOf(x)); // 状况3 } setColor(sib, colorOf(parentOf(x))); // 状况4 setColor(parentOf(x), BLACK); // 状况4 setColor(rightOf(sib), BLACK); // 状况4 rotateLeft(parentOf(x)); // 状况4 x = root; // 状况4 } } else { // 跟前四种状况对称 Entry<K,V> sib = leftOf(parentOf(x)); if (colorOf(sib) == RED) { setColor(sib, BLACK); // 状况5 setColor(parentOf(x), RED); // 状况5 rotateRight(parentOf(x)); // 状况5 sib = leftOf(parentOf(x)); // 状况5 } if (colorOf(rightOf(sib)) == BLACK && colorOf(leftOf(sib)) == BLACK) { setColor(sib, RED); // 状况6 x = parentOf(x); // 状况6 } else { if (colorOf(leftOf(sib)) == BLACK) { setColor(rightOf(sib), BLACK); // 状况7 setColor(sib, RED); // 状况7 rotateLeft(sib); // 状况7 sib = leftOf(parentOf(x)); // 状况7 } setColor(sib, colorOf(parentOf(x))); // 状况8 setColor(parentOf(x), BLACK); // 状况8 setColor(leftOf(sib), BLACK); // 状况8 rotateRight(parentOf(x)); // 状况8 x = root; // 状况8 } } } setColor(x, BLACK); }
前面已经说过TreeSet
是对TeeMap
的简单包装,对TreeSet
的函数调用都会转换成合适的TeeMap
方法,所以TreeSet
的实现很是简单。这里再也不赘述。
// TreeSet是对TreeMap的简单包装 public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, java.io.Serializable { ...... private transient NavigableMap<E,Object> m; // Dummy value to associate with an Object in the backing Map private static final Object PRESENT = new Object(); public TreeSet() { this.m = new TreeMap<E,Object>();// TreeSet里面有一个TreeMap } ...... public boolean add(E e) { return m.put(e, PRESENT)==null; } ...... }