本文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; } ...... }