文章目錄
1、樹的介紹
1.1、排序二叉樹
HashMap
和HashSet
的共同實現機制是哈希表,一個共同的限制是沒有順序。
TreeSet
和TreeMap
這兩個類的共同實現基礎是排序二叉樹
排序二叉樹也是二叉樹,但它沒有重複元素,而且是有序的二叉樹
如果左子樹不爲空,則左子樹上的所有節點都小於該節點
如果右子樹不爲空,則右子樹上的所有節點都大於該節點
1.2、常用算法
1.2.1、查找
1.2.2、遍歷
1.2.3、插入
1.2.4、刪除
1.3、平衡二叉樹
1.5哈希與樹的比較
與哈希表一樣,樹也是計算程序中一種最重要的數據結構和思維方式,爲了能夠快速操作數據,
哈希
和樹
是兩種基本的思維方式,不需要順序,,優先考慮哈希,需要順序,考慮樹除了容器類
TreeMap
和TreeSet
,數據庫中的索引結構也是基於樹的(不過基於B樹,而不是二叉樹),而索引是能夠在大量數據中快速訪問數據的關鍵
2、TreeMap的介紹
鍵有順序
2.1、基本用法
TreeMap有兩個基本構造方法
public TreeMap()
public TreeMap(Comparator<>)
- 第一個默認構造方法,如果使用默認構造方法,要求
Map中的鍵實現Comparable接口
,TreeMap內部進行各種比較時會調用鍵的Comparable接口中的CompareTo
方法 - 第二個接受一個比較器對象comparator,如果comparator不爲null,在TreeMap內部進行比較時會調用這個comparator的compare方法,而不再調用鍵的
compareTo
方法
應該使用哪一個?第一個更簡單,但要求鍵實現Comparable
接口,且期望的排序和鍵的比較結果是一致的;第二個更爲靈活,不要求鍵實現Compstsble
接口,比較器可以靈活複雜的方式進行
2.2、實現原理
TreeMap內部是使用紅黑樹實現的,紅黑樹是一種大致平衡的排序二叉樹
TreeMap內部成員
//comparator就是比較器,在構造方法中傳遞,如果沒穿就是null
private final Comparator<? super K> comparator;
//root指向樹的根節點,從根節點可以訪問到每個節點
private transient Entry<K,V> root = null;
//當前節點數
private transient int size = 0;
private transient int modCount = 0;
//紅黑樹中的節點
static final class Entry<K,V> implements Map.Entry<K,V> {
K key;//鍵
V value;//值
Entry<K,V> left = null;//左孩子
Entry<K,V> right = null;//右孩子
Entry<K,V> parent;//父節點
boolean color = BLACK;//顏色
2.2、put方法
添加第一個節點
public V put(K key, V value) {
Entry<K,V> t = root;
/*添加第一個節點 */
if (t == null) {
//爲了檢查key的類型和null,如果類型不匹配或爲null,那麼compare方法會拋出異常
compare(key, key); // type (and possibly null) check
//新建一個節點,指向根節點
root = new Entry<>(key, value, null);
size = 1;
//用於迭代過程中檢測結構性變化
modCount++;
return null;
}
/*添加第一個節點 */
向紅黑樹中添加節點
//如果紅黑樹已經存在,會執行下面的代碼
//添加的關鍵是尋找父節點,尋找父節點根據是否設置了comparator分爲兩種情況
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
//設置了cpmomparator的情況;尋找父節點
if (cpr != null) {
//尋找是一個從根節點開始循環的過程中
//在循環過程中
//cmp:保存了比較結果; t指向當前比較節點; parent爲t的父節點;
//循環結束後 parent就是要找的父節點
/**
* t一開始指向父親節點,從根節點開始比較鍵,如果小於根節點,就將t設爲左孩子。
* 與左孩子比較,大於就與右孩子比較,就這樣一直比較下去,直到t爲null或者比較結果爲0
*
* 如果比較結果爲0,就表示已經有這個鍵,設置值,然後返還。
*
* 如果t爲null,則退出循環時,parebnrt就指向待插入節點的父節點
*/
do {
parent = t;
cmp = cpr.compare(key, t.key);//獲取比較值
if (cmp < 0) //比較則直接放在左子樹
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);//相等則直接設置值
} while (t != null);
}
//沒有設置comparator的尋找父節點
//基本邏輯是一樣的,當退出循環時parent指向父節點,只是如果沒喲設置comparator
//則假設key一定實現了Comparable接口,使用Comparanble接口的compareTo方法進行比較
else {
if (key == null)
throw new NullPointerException();
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);
if (cmp < 0) //根據新的鍵與父節點鍵的比較結果,插入作爲左孩子或又孩子
parent.left = e;
else
parent.right = e;
//調整樹的結構使之符合紅黑樹的約束,保持大致平衡
fixAfterInsertion(e);
size++;
modCount++;
return null;
稍微總結一下,其基本思想就是:循環比較找到父節點,並插入作爲其左孩子或右孩子,然後調整保持樹的大致平衡
2.3、根據鍵獲取值get
//根據鍵獲取值
public V get(Object key) {
Entry<K,V> p = getEntry(key);
return (p==null ? null : p.value);
}
final Entry<K,V> getEntry(Object key) {
// Offload comparator-based version for sake of performance
//如果比較器不爲空,調用單獨的方法getEntryUsingComparator
if (comparator != null)
return getEntryUsingComparator(key);
if (key == null)
throw new NullPointerException();
//否則 假定key實現了Comparable的接口
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;
}
2.4、查看是否包含某個鍵
public boolean containsKey(Object key) {
return getEntry(key) != null;
}
2.5、查看是否包含某個值
TreeMap可以高效的按鍵進行查找,但如果要根據值進行查找,則需要遍歷
//查看是否包含某個值
//TreeMap可以高效的按鍵進行查找,但如果要根據值進行查找,則需要遍歷
public boolean containsValue(Object value) {
//getFirstEntry() :返回左下角的元素
//successor返回給定節點的後繼節點
//從第一個節點開始,逐個比較,直到找到爲止
for (Entry<K,V> e = getFirstEntry(); e != null; e = successor(e))
if (valEquals(value, e.value))//valEquals()就是比較值;
return true;
return false;
}
//找後繼節點
//就是中序遍歷
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;
}
}
//getFirst返回第一個節點,左下角,不斷往左下角找
final Entry<K,V> getFirstEntry() {
Entry<K,V> p = root;
if (p != null)
while (p.left != null)
p = p.left;
return p;
}
static final boolean valEquals(Object o1, Object o2) {
return (o1==null ? o2==null : o1.equals(o2));
}
2.6、根據鍵刪除鍵值對 remove
//根據鍵刪除鍵值對
public V remove(Object key) {
//根據key找到節點
Entry<K,V> p = getEntry(key);
if (p == null)
return null;
//待返回的值
V oldValue = p.value;
/**
* 節點有三種情況:
*
* 葉子節點:直接修改父節點對應引用職位null即可
*
* 只有一個孩子: 在父親節點和孩子節點直接建立連接
*
* 有兩個孩子:先找到後繼節點,找到後,替換當前節點的內容爲後繼節點,然後再刪除後繼節點(因爲這個後繼節點一定沒有左孩子,所以就將兩個孩子的情況轉換爲前面兩種情況)
*
*/
deleteEntry(p);
return oldValue;
}
/**
* Delete node p, and then rebalance the tree.
* 節點有三種情況:
*
* 葉子節點:直接修改父節點對應引用職位null即可
*
* 只有一個孩子: 在父親節點和孩子節點直接建立連接
*
* 有兩個孩子:先找到後繼節點,找到後,替換當前節點的內容爲後繼節點,然後再刪除後繼節點(因爲這個後繼節點一定沒有左孩子,所以就將兩個孩子的情況轉換爲前面兩種情況)
*
* */
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.
//兩個孩子的情況
if (p.left != null && p.right != null) {
//s爲後繼
Entry<K,V> s = successor(p);
//當前節點的key設置爲後繼的key和value
p.key = s.key;
p.value = s.value;
//然後將待刪除的節點p指向了s,這樣就轉換爲了一個孩子或葉子節點的情況
p = s;
} // p has 2 children
// Start fixup at replacement node, if it exists.
//p爲待刪除節點,replaceMent爲要替換p的孩子節點,主體代碼就是在p的父節點
//p.parent和replacement之間建立連接
Entry<K,V> replacement = (p.left != null ? p.left : p.right);
//只有一個孩子的節點
if (replacement != null) {
// Link replacement to 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.
p.left = p.right = p.parent = null;
// Fix replacement
if (p.color == BLACK)
fixAfterDeletion(replacement);
}
//葉子節點
//具體分爲兩種情況:一種是刪除最後一個節點,修改root爲null
else if (p.parent == null) { // return if we are the only node.
root = null;
}
//另一種是根據帶刪除節點是父節點的左孩子還是右孩子,響應的設置孩子節點爲null
else { // No children. Use self as phantom replacement and unlink.
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;
}
}
}