TreeMap繼承自AbstractMap,并實現了NavigableMap接口(NavigableMap繼承自SortedMap接口)。底層的數據結構是紅黑樹,按照鍵的自然排序或者自定義實現的規則排序,實現元素的有序性。
特點
- 元素是有序的:按照key的自然排序或者是自定義的比較器進行排序(并不是按照插入順序排序)
- 鍵不能重復,值可以重復:key不能重復,value可以重復
- 鍵不能為null,值可以為null
- 紅黑樹是一種自平衡的二叉樹,通過規則維持樹的高度近似平衡,保證查詢的時間復雜度為O(log n)
紅黑樹底層原理
- TreeMap是基于紅黑樹實現的鍵值對集合
- 樹的根節點為黑色
- 樹里面的節點不是黑色的就是紅色的
- 相鄰的兩個節點不能都是紅色
- 從任意一個節點到其葉子節點的路徑上黑色節點數相同
- 紅黑樹的所有葉子節點都是黑色(葉子節點是Nil節點)
參考博主:
紅黑樹插入: https://www.cnblogs.com/GNLin0820/p/9120337.html最終還是決定把紅黑樹的篇章一分為二,插入操作一篇,刪除操作一篇,因為合在一起寫篇幅實在太長了,寫起來都覺得累,何況是閱讀并理解的讀者。 紅黑樹刪除操作請參考?數據結構 - 紅黑樹(Red Black Tree)刪除詳解與實現(Java) 現在網絡上最不缺的就是對某個知識點的講解博文,各種花
https://www.cnblogs.com/GNLin0820/p/9120337.html
紅黑樹刪除: https://www.cnblogs.com/GNLin0820/p/9668378.html本篇要講的就是紅黑樹的刪除操作 紅黑樹插入操作請參考?數據結構 - 紅黑樹(Red Black Tree)插入詳解與實現(Java) 紅黑樹的刪除是紅黑樹操作中比較麻煩且比較有意思的一部分。 在此之前,重申一遍紅黑樹的五個定義: 1. 紅黑樹的節點不是黑色的就是紅色的 2. 紅黑樹的根節點
https://www.cnblogs.com/GNLin0820/p/9668378.html
添加節點
第一種情況
添加的節點(500)和它的父節點(400)也是紅色,并且它的叔叔節點(Nil1)是黑色(Nil是不存在的葉子節點)
添加的節點(500)是叔叔節點(Nil1)的遠親(是父節點的右節點)
就以父節點(400)為中心點,左旋。旋轉后:
- 父節點變成黑色,祖父節點變成紅色
- 祖父節點(300)的父節點變成400,祖父節點(300)的左子節點是Nil1,右子節點是Nil2
- 父節點(400)的左子節點變成了300,右子節點是500
- 祖父節點(300)有父節點的話,父節點(400)的父節點 變成 祖父節點(300)的父節點
?第二種情況
添加的節點(350)是父節點(400)的左子節點,父節點是紅色;它的叔叔節點(Nil1)是黑色
添加的節點(350)是叔叔節點(Nil1)的近親(父節點的左子節點)
就以父節點(400)為中心,先右旋。旋轉后:
- 父節點(400)變成了插入節點(350)的右節點,400的父節點變成了350,400的左節點變成了Nil4
- 插入節點(350)的右節點變成了400,它的父節點變成了300
再左旋:以350作為中心點,左旋
第三種情況
添加節點(100)是父節點的左子節點,父節點(200)是紅色,它的叔叔節點(Nil4)是黑色
添加的節點(100)是叔叔節點的遠親(挨的遠)
則以父節點(200)為中心點,右旋,旋轉后:
- 父節點(200)變成黑色,祖父節點(300)變成紅色
- 300的父節點變成了200,左子節點變成了Nil3,右子節點變成了Nil4
- 200的右子節點變成了300,左子節點變成了100
- 如果祖父節點(300)不是根節點,父節點(200)的父節點 變成 祖父節點(300)的父節點
第四種情況
插入節點(250)是父節點的右子節點,父節點為祖父節點(300)的左子節點,父節點(200)是紅色
插入節點(250)的叔叔節點(Nil4)是黑色
250與Nil4是近親(挨的近)
以父節點(200)為中心點,先左旋:
- 200的父節點變成250,200的左子節點是Nil1,右子節點是Nil2
- 250的父節點變成300,250的左子節點變成200
再按照第三種情況,以250為中心點右旋
第五種情況
插入節點為紅色,它的父節點也為紅色,它的叔叔節點也是紅色
直接將父節點和叔叔節點變成黑色,祖父節點變成紅色
如果祖父節點是根節點,則祖父節點最后要變成黑色
首先確認插入節點位置,定位好插入節點的父節點,祖父節點,叔叔節點
看自己和父節點顏色,都是紅色就需要修復樹平衡(相鄰節點不能都是紅色)
確認要修復平衡后,看叔叔節點顏色
叔叔是紅色,直接改變叔叔和父親的顏色(變成黑色),再改變祖父節點顏色(變為紅色)
叔叔是黑色,就要左旋或者右旋
自己是左節點,父親也是左節點:自己是紅色,父節點變成黑色,祖父節點變成紅色。以父親為中心右旋
自己是左節點,父親是右節點:以父親為中心,先右旋再左旋,旋轉完之后變換顏色,自己變成黑色,父親節點和祖父節點變成紅色
自己是右節點,父親也是右節點:自己是紅色,父節點變成黑色,祖父節點變成紅色。以父親為中心左旋
自己是右節點,父親是左節點:以父親為中心,先左旋再右旋,旋轉完之后變換顏色,自己變成黑色,父親節點和祖父節點變成紅色
完成當次平衡后,如果叔叔是紅色節點:把祖父節點作為下一次平衡的插入點;如果叔叔是黑色節點:把父親節點作為下一次平衡的插入點
直到遍歷到根節點或者是當前插入點的父節點是黑色,結束平衡操作
刪除節點(部分)
首先要確認刪除節點時不存在的情況
第二,四種:不滿足條件:相鄰的兩個節點不能為紅色
第一,三,五,六種:不滿足條件:從任意節點到葉子節點路徑上的黑色節點數要相同
也就是說:
在第一五種情況下:父節點是紅色,只有一個子節點且是黑色,這時候必然會有另一個子節點也是黑色
在第二六種情況下:父節點是黑色,只有一個子節點且是黑色,這時候必然會有另一個子節點也是黑色
以下是會發生的刪除情況
第一種情況
被刪除的節點是黑色,并且只有一個子節點是紅色
- 100這個節點作為修復樹的起點(代碼中設置為replacement)
- 100的父節點改為400
- 200的所有指針都置為空
- 100作為平衡樹的起點去做平衡修復:100的顏色是紅色,沒有其他操作,直接將100這個節點的顏色變為黑色
第二種情況
要刪除的節點p有左右子節點
- 找到500的后繼節點:就是600,被確認為要刪除的節點
- 將600節點里面的key-value復制到500節點
- p指針變成指向600節點
- 600節點沒有子節點,所以沒有replacement作為作為修復平衡樹的起點
- 600節點有父節點:600節點自己作為修復平衡樹的起點
- 600節點是紅色,不需要修復平衡,直接將600節點中的指針清空
第三種情況
要刪除的節點為450,有左右節點。
- 確認其后繼節點為600,將600節點的key-value復制給450節點,600被確認為要刪除的節點
- 600(p指針指向的節點,以下皆為p指向)沒有子節點,則600作為修復樹的起點
- 600節點的顏色為黑色(被刪除的節點是黑色,破壞了樹的平衡),修復樹
- 600的兄弟節點只有一個子節點,并且和600是近親,420和440節點顏色互換
- 以420為中心左旋
- 以440為中心右旋
- 清空p指針指向的節點
第四種情況
方法
構造方法
public class TreeMap<K,V>extends AbstractMap<K,V>implements NavigableMap<K,V>, Cloneable, java.io.Serializable
{//比較器private final Comparator<? super K> comparator;//根節點private transient Entry<K,V> root;//樹中節點個數private transient int size = 0;//樹的修改次數private transient int modCount = 0;public TreeMap() {comparator = null;}//創建一個TreeMap,自定義比較器public TreeMap(Comparator<? super K> comparator) {this.comparator = comparator;}//創建一個TreeMap,將傳入集合中的元素復制到TreeMap中public TreeMap(Map<? extends K, ? extends V> m) {comparator = null;putAll(m);}//創建一個TreeMap,將傳入的集合復制到TreeMap中,使用傳入集合中的比較器public TreeMap(SortedMap<K, ? extends V> m) {comparator = m.comparator();try {buildFromSorted(m.size(), m.entrySet().iterator(), null, null);} catch (java.io.IOException | ClassNotFoundException cannotHappen) {}}
}
查找當前節點的后繼節點
//查找當前節點的后繼節點(比當前節點大的最小節點)static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {if (t == null)return null;//如果t節點的右節點不為空,則查找該右節點的最小左節點else if (t.right != null) {Entry<K,V> p = t.right;while (p.left != null)p = p.left;return p;} //如果t節點的右節點為空,則查找該節點的父節點,若t是父節點的左節點,則返回該父節點else {Entry<K,V> p = t.parent;Entry<K,V> ch = t;//若t是p(父節點)的右節點,則繼續向上查找,直到查找到p,其中t是p的左節點,返回pwhile (p != null && ch == p.right) {ch = p;p = p.parent;}return p;}}
刪除節點后自平衡
x節點是修復平衡的起點
//x為刪除方法中計算出的后繼者節點的子節點或者是后繼者節點本身//如果x節點不是根節點且它的顏色是黑色,則需要左旋或者右旋//否則直接將x節點的顏色變為黑色private void fixAfterDeletion(Entry<K,V> x) {//如果x節點不是根節點且它的顏色是黑色while (x != root && colorOf(x) == BLACK) {//如果x節點是左節點if (x == leftOf(parentOf(x))) {Entry<K,V> sib = rightOf(parentOf(x));//同一個父節點,x作為左節點是黑色,sib作為右節點是紅色//將sib設置為黑色,父節點設置為紅色//以x的父節點為中心點左旋if (colorOf(sib) == RED) {setColor(sib, BLACK);setColor(parentOf(x), RED);rotateLeft(parentOf(x));sib = rightOf(parentOf(x));}//sib的左節點是黑色,sib的右節點也是黑色,設置sib為紅色//設置完成后將x的父節點賦值給xif (colorOf(leftOf(sib)) == BLACK &&colorOf(rightOf(sib)) == BLACK) {setColor(sib, RED);x = parentOf(x);} //如果sib的左右節點其中一個是紅色else {//如果sib的右節點是黑色,設置sib的左節點為黑色,sib為紅色,以sib為中心右旋//將x節點的父節點的右節點賦值給sibif (colorOf(rightOf(sib)) == BLACK) {setColor(leftOf(sib), BLACK);setColor(sib, RED);rotateRight(sib);sib = rightOf(parentOf(x));}//上面if條件執行與否都會執行下面的代碼//將sib的顏色設置為x父節點的顏色//將x父節點的顏色設置為黑色//將sib的右節點顏色設置為黑色//以x的父節點為中心左旋setColor(sib, colorOf(parentOf(x)));setColor(parentOf(x), BLACK);setColor(rightOf(sib), BLACK);rotateLeft(parentOf(x));//將x設置為根節點,跳出循環x = root;}} //如果x是右節點,與是左節點的情況是對稱的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);}
添加單個元素
public V put(K key, V value) {Entry<K,V> t = root;if (t == null) {compare(key, key); // type (and possibly null) checkroot = new Entry<>(key, value, null);size = 1;modCount++;return null;}int cmp;Entry<K,V> parent;Comparator<? super K> cpr = comparator;//比較器不為空,根據比較器規則查找元素,若key已存在,更新key對應的value值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;elsereturn t.setValue(value);} while (t != null);}//比較器為空,則按照自然順序排序比較,查找key,若key存在,則更新key對應的value值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;elsereturn t.setValue(value);} while (t != null);}//key不存在,新建一個節點存放鍵值對,放入紅黑樹中Entry<K,V> e = new Entry<>(key, value, parent);if (cmp < 0)parent.left = e;elseparent.right = e;//添加完成后,調用函數保證紅黑樹的自平衡fixAfterInsertion(e);size++;modCount++;return null;}
添加多個元素
//按照map的比較器添加元素或者是按照默認自然排序規則添加元素public void putAll(Map<? extends K, ? extends V> map) {int mapSize = map.size();if (size==0 && mapSize!=0 && map instanceof SortedMap) {Comparator<?> c = ((SortedMap<?,?>)map).comparator();if (c == comparator || (c != null && c.equals(comparator))) {++modCount;try {buildFromSorted(mapSize, map.entrySet().iterator(),null, null);} catch (java.io.IOException | ClassNotFoundException cannotHappen) {}return;}}super.putAll(map);}
刪除單個元素
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,后續操作的p其實是此處查找到的后繼者節點p = s;} // p has 2 children//指定從replacement開始修復樹的平衡//從后繼者節點的左節點或者右節點開始Entry<K,V> replacement = (p.left != null ? p.left : p.right);//如果replacement節點不為空,后繼者節點的父節點與成為replacement節點的父節點if (replacement != null) {replacement.parent = p.parent;//如果后繼者節點的父節點為空,則replacement作為根節點if (p.parent == null)root = replacement;//如果后繼者節點是其父節點的左節點,則replacement成為父節點的左節點else if (p == p.parent.left)p.parent.left = replacement;//如果后繼者節點是其父節點的右節點,則replacement成為父節點的右節點elsep.parent.right = replacement;//將后繼者節點的指針都清空p.left = p.right = p.parent = null;// 修復樹的平衡,從replacement節點開始//如果刪除的后繼者節點是黑色,調用修復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;}}}