事先聲明如下代碼基於JDK1.8版本
html
大部分圖片引自https://www.jianshu.com/p/e136ec79235c侵刪java
http://www.javashuo.com/article/p-gciojcqo-mx.htmlnode
http://www.javashuo.com/article/p-pwmenxbf-gg.html數據結構
http://www.javashuo.com/article/p-orraekjs-n.html函數
TreeMap這樣一種數據結構給了咱們什麼:性能
Collections.synchronizedSortedMap()
若是你有上述的需求,其實也就是快速搜索,能夠考慮使用它this
TreeMap是經過什麼樣的數據結構來存儲數據的呢?紅黑樹3d
那爲何二叉搜索樹又不行呢?code
在必定的狀況下,二叉搜索樹會退化成鏈表,失去了log(n)的搜索時間複雜度orm
以下圖所示,若是按照1,2,3,4這樣的順序去構成二叉搜索樹的話,它就會退化成一個鏈表,這樣的性能是無法接收的
那麼AVL樹又如何呢?爲何就選擇紅黑樹呢
紅黑樹因爲其特性,它並不追求徹底的平衡,更低的平衡要求對應着插入、刪除效率的提升(可能提升並不明顯甚至不如AVL,由於插入、刪除的第一步就是定位),搜索的效率下降,AVL相對紅黑樹而言,要求徹底的平衡,天然插入、刪除須要進行的操做更多,效率更低,而因爲徹底的平衡,搜索的效率就更高,綜合而言,選擇了實現起來更爲簡單,各方面更爲均衡的紅黑樹,就是如此
推論
紅黑樹爲了維持上述5個性質,須要作出一些努力,這些努力就是旋轉與着色
左旋示意圖:
語言總結一下就是:
左旋,右孩子成爲旋轉中心的父親節點,旋轉中心成爲右孩子的左孩子,此時旋轉中心的右孩子空缺,右孩子的左子樹失去了父親,把右孩子的左子樹置爲旋轉中心的右孩子
對應的TreeMap的rotateLeft方法以下
private void rotateLeft(Entry<K,V> p) { if (p != null) { Entry<K,V> r = p.right; //p的右孩子->r的左子樹 p.right = r.left; if (r.left != null) //右孩子指向p.parent r.left.parent = p; r.parent = p.parent; //判斷p是否爲root以及p在parent中的位置 if (p.parent == null) root = r; else if (p.parent.left == p) p.parent.left = r; else p.parent.right = r; //p成爲r的左孩子,r成爲p的父親 r.left = p; p.parent = r; } }
右旋示意圖
TreeMap中的右旋代碼
private void rotateRight(Entry<K,V> p) { if (p != null) { Entry<K,V> l = p.left; p.left = l.right; if (l.right != null) l.right.parent = p; l.parent = p.parent; if (p.parent == null) root = l; else if (p.parent.right == p) p.parent.right = l; else p.parent.left = l; l.right = p; p.parent = l; } }
着色
Entry的color成員變量以下
boolean color = BLACK; //上色方法以下 private static <K,V> void setColor(Entry<K,V> p, boolean c) { if (p != null) p.color = c; }
紅黑樹本質仍是一顆二叉搜索樹,天然與二叉搜索樹的查找方式相差無幾
TreeMap中的實現以下:
final Entry<K,V> getEntry(Object key) { // Offload comparator-based version for sake of performance if (comparator != null) return getEntryUsingComparator(key); if (key == null) throw new NullPointerException(); @SuppressWarnings("unchecked") //若是有,使用自定義的排序規則 Comparable<? super K> k = (Comparable<? super K>) key; Entry<K,V> p = root; while (p != null) { int cmp = k.compareTo(p.key); if (cmp < 0) p = p.left; else if (cmp > 0) p = p.right; else return p; } return null; }
因爲性質5的存在,插入節點黑色勢必致使紅黑樹的性質5的不知足,而插入紅色並不必定須要調整樹的結構
4.2與4.3徹底對稱式的操做,以4.2爲例
操做以下
爲何如此操做?
當前節點爲紅,父親爲紅,違背性質4,修改父親爲黑,此時通過父親節點的路徑的黑色節點數量大於通過叔叔節點的路徑黑色節點數目,那就再把叔叔染黑,那麼此時父輩平衡,可是通過祖父的這條分支黑色節點數目多了一個,那麼再把祖父染紅便可,而後以祖父爲新插入的節點再插入便可
效果如圖:
操做以下
效果如圖:
爲何這麼操做?
不知足性質4,修改父親爲黑色,此時父親這邊的路徑黑色數量多1,那麼再修改祖父爲紅色,但此時通過叔叔的路徑黑色數量少1,此時以祖父爲中心,右旋,讓剛剛染色爲黑色的節點成爲父親,父親成爲叔叔的父親,便可知足平衡
操做以下:
效果如圖:
爲何這麼操做?
就是爲了變成4.2.1,而後按照4.2.1的操做進行便可
4.3是4.2的鏡像版本,對稱過去就行了
public V put(K key, V value) { Entry<K,V> t = root; if (t == null) { compare(key, key); // type (and possibly null) check //1 若是root爲null,插入並返回便可 root = new Entry<>(key, value, null); size = 1; modCount++; return null; } int cmp; Entry<K,V> parent; // split comparator and comparable paths Comparator<? super K> cpr = comparator; if (cpr != null) { do { parent = t; cmp = cpr.compare(key, t.key); if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else //2 若是找到了,直接修改value return t.setValue(value); } while (t != null); } else { if (key == null) throw new NullPointerException(); @SuppressWarnings("unchecked") Comparable<? super K> k = (Comparable<? super K>) key; do { parent = t; cmp = k.compareTo(t.key); if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else return t.setValue(value); } while (t != null); } Entry<K,V> e = new Entry<>(key, value, parent); //3 直接插入節點,默認color爲黑色 if (cmp < 0) parent.left = e; else parent.right = e; fixAfterInsertion(e); size++; modCount++; return null; }
private void fixAfterInsertion(Entry<K,V> x) { //把color着色爲紅 x.color = RED; while (x != null && x != root && x.parent.color == RED) { //父親爲祖父的左孩子 if (parentOf(x) == leftOf(parentOf(parentOf(x)))) { //y-叔叔節點 Entry<K,V> y = rightOf(parentOf(parentOf(x))); if (colorOf(y) == RED) { //叔叔爲紅--4.1,修改父親和叔叔的節點爲黑,祖父爲紅 setColor(parentOf(x), BLACK); setColor(y, BLACK); setColor(parentOf(parentOf(x)), RED); //將祖父設置爲當前節點 x = parentOf(parentOf(x)); } else { //插入節點爲右孩子,則先左旋一下,從4.2.2 --> 4.2.1 if (x == rightOf(parentOf(x))) { x = parentOf(x); rotateLeft(x); } //4.2.1,修改父親爲黑色,祖父爲紅色,以祖父爲中心右旋 setColor(parentOf(x), BLACK); setColor(parentOf(parentOf(x)), RED); rotateRight(parentOf(parentOf(x))); } } else { //對稱的,父親爲祖父的右孩子 Entry<K,V> y = leftOf(parentOf(parentOf(x))); if (colorOf(y) == RED) { setColor(parentOf(x), BLACK); setColor(y, BLACK); setColor(parentOf(parentOf(x)), RED); x = parentOf(parentOf(x)); } else { if (x == leftOf(parentOf(x))) { x = parentOf(x); rotateRight(x); } setColor(parentOf(x), BLACK); setColor(parentOf(parentOf(x)), RED); rotateLeft(parentOf(parentOf(x))); } } } //若是x爲root,直接着色便可 root.color = BLACK; }
後繼節點
後繼節點的選取,選投影到x軸上離它最近的兩個節點(確定有兩個)中的任意一個均可以,這裏選擇第一個比刪除節點大的節點,對應到樹中的位置以下
若是刪除節點有右子樹,則對應着右子樹的最左節點
若是沒有則對應着第一個向右走的節點,如圖所示
後繼節點必定是樹末尾的節點,由於任何一個左右孩子非空的節點都不可能成爲最終的後繼節點,它遞歸下去最終到樹梢的位置
刪除過程對應以下:
如今完成了第一步,定位真正要刪除的節點,下面就是刪除這個節點以後如何平衡二叉樹?
只討論1,2的狀況,3與2對稱操做
紅色節點死有餘辜!!無需處理,刪除便可
操做以下
效果如圖:
why
假設R刪除了,那麼爲了達到平衡,P應該幫忙補一個黑色回來,那麼出問題了,任何包含P和S的路徑黑色就多出來1了,把S置爲紅色便可達到局部的平衡,把須要補黑色的需求向上傳遞
操做以下:
效果如圖:
why
向2.2/2.3/2.4靠攏
操做以下:
效果如圖:
why
咱們的最終目的就是爲包含R的路徑補一個黑色節點
修改P爲黑色(不必定能保證補回一個黑色,有可能P就是黑色的)
以P爲中心左旋,讓S成爲包含R路徑的新root,那麼包含S的路徑黑色節點沒變,包含R的路徑的黑色節點因爲S的存在+1
左旋以後,P與SL爲父子關係,而P與SL又都爲任意顏色,不必定能保證性質4,因此在左旋以前將P與S顏色互換,可是此時包含SR(紅色)的右子樹黑色節點少1,將SR置爲黑色便可
操做以下:
效果如圖:
刪除節點爲黑色,且爲父親節點的右孩子節點的操做與上述操做對稱
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 strictly internal, copy successor's element to p and then make p // point to successor. //left與right都非空,即尋找後繼節點 if (p.left != null && p.right != null) { Entry<K,V> s = successor(p); //將s的值賦值給p,而後讓p指向s p.key = s.key; p.value = s.value; p = s; } // p has 2 children // Start fixup at replacement node, if it exists. //left!=null則爲left,right!=null則爲right Entry<K,V> replacement = (p.left != null ? p.left : p.right); if (replacement != null) { // Link replacement to parent //把replace連接到parent上 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; // Null out links so they are OK to use by fixAfterDeletion. //for GC p.left = p.right = p.parent = null; // Fix replacement //若是刪除節點爲黑色,則調整 if (p.color == BLACK) fixAfterDeletion(replacement); } else if (p.parent == null) { // return if we are the only node. //惟一節點,root節點 root = null; } else { // No children. Use self as phantom replacement and unlink. //左右孩子均爲null if (p.color == BLACK) fixAfterDeletion(p); //斷開p與parent的鏈接 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; } } }
static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) { if (t == null) return null; else if (t.right != null) { //右子樹的最左下節點 Entry<K,V> p = t.right; while (p.left != null) p = p.left; return p; } else { //第一個向右走的節點 Entry<K,V> p = t.parent; Entry<K,V> ch = t; while (p != null && ch == p.right) { ch = p; p = p.parent; } return p; } }
private void fixAfterDeletion(Entry<K,V> x) { while (x != root && colorOf(x) == BLACK) { //待刪除節點爲父親節點的左孩子且爲黑色 if (x == leftOf(parentOf(x))) { //sib--兄弟節點 Entry<K,V> sib = rightOf(parentOf(x)); //2.1-->2.2/2.3/2.4 if (colorOf(sib) == RED) { setColor(sib, BLACK); setColor(parentOf(x), RED); rotateLeft(parentOf(x)); sib = rightOf(parentOf(x)); } //2.2 設置兄弟節點爲紅色,x修改成parent if (colorOf(leftOf(sib)) == BLACK && colorOf(rightOf(sib)) == BLACK) { setColor(sib, RED); x = parentOf(x); } else { //2.3-->2.4 if (colorOf(rightOf(sib)) == BLACK) { setColor(leftOf(sib), BLACK); setColor(sib, RED); rotateRight(sib); sib = rightOf(parentOf(x)); } //2.4 setColor(sib, colorOf(parentOf(x))); setColor(parentOf(x), BLACK); setColor(rightOf(sib), BLACK); rotateLeft(parentOf(x)); x = root; } } else { // symmetric 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; } } } //若是x爲red,直接設置爲x,便可彌補回丟失的黑色節點,若是是黑色,無法經過setColor來補回黑色,經過平衡的方式來補回黑色 setColor(x, BLACK); }
總結來講,不管是插入也好,刪除也罷,紅黑樹保持平衡的策略是自底向下追求紅黑樹的平衡,把矛盾交給上層的節點解決。
TreeMap紅黑樹的代碼已經混雜在上面,再也不進行梳理
public NavigableSet<K> navigableKeySet() { KeySet<K> nks = navigableKeySet; //若是已經建立了nks,直接返回,不然new一個內部類KeySet return (nks != null) ? nks : (navigableKeySet = new KeySet<>(this)); } //成員變量navigableKeySet private transient KeySet<K> navigableKeySet;
//成員變量m private final NavigableMap<E, ?> m; //構造方法,其實就把TreeMap傳入了 KeySet(NavigableMap<E,?> map) { m = map; } //簡單看幾個方法,發現都是調用m的方法 public int size() { return m.size(); } public boolean isEmpty() { return m.isEmpty(); } public boolean contains(Object o) { return m.containsKey(o); } public void clear() { m.clear(); } //包括iterator也是經過m訪問另外一個內部類 public Iterator<E> iterator() { if (m instanceof TreeMap) return ((TreeMap<E,?>)m).keyIterator(); else return ((TreeMap.NavigableSubMap<E,?>)m).keyIterator(); }
EntrySet、Values的實現也都相差無幾,有點相似於外觀類,提供受限的方法調用
看一下TreeSet的構造函數便可
public TreeSet() { this(new TreeMap<E,Object>()); } TreeSet(NavigableMap<E,Object> m) { this.m = m; } private transient NavigableMap<E,Object> m;
add()
private static final Object PRESENT = new Object(); public boolean add(E e) { return m.put(e, PRESENT)==null; }
顯然TreeSet基於TreeMap實現它的基本功能,能夠說它就是一個TreeSet,只不過每次add的value是一個final的obj