微信公眾號:I am CR7
如有問題或建議,請在下方留言;
最近更新:2018-09-05
二分查詢樹(BST)
1、定義:
二分查詢樹又稱二叉查詢樹、二叉排序樹,英文縮寫為BST,即Binary Search Tree。該資料結構的出現,是為了提高查詢的效率。之所以成為二分查詢樹,是因為其採用二分查詢的演算法。
2、特性:
- 若左子樹不為空,左子樹上所有節點的值均小於根節點的值
- 若右子樹不為空,右子樹上所有節點的值均大於根節點的值
- 左右子樹又分別是一棵二分查詢樹
3、示例:
下圖是一個典型的二分查詢樹:
查詢節點10:
- 檢視根節點5,因為10>5,往右子樹走
- 檢視節點8,因為10>8,往右子樹走
- 檢視節點15,因為10<15,往左子樹走
- 檢視節點12,因為10<12,往左子樹走
- 檢視節點10,正是我們要找的節點
4、思考:
雖然二叉查詢樹提高了查詢的效率,但是依舊存在著缺陷,請看下面例子:
當依次插入6、5、4、3、2時,發現樹成了線性形式,在此種情況下,查詢效率大打折扣,因此就有了自平衡的二叉查詢樹,名為紅黑樹。
紅黑樹(RBT)
1、定義:
紅黑樹,是一個自平衡的二叉排序樹,英文簡稱為RBT,即Red Black Tree。每個節點上增加了顏色屬性,要麼為紅,要麼為黑。
2、特性:
- 每個節點要麼為紅色,要麼為黑色
- 根節點為黑色
- 每個葉子節點(NIL)是黑色 [注意:這裡葉子節點,是指為空(NIL或NULL)的葉子節點!]
- 不允許連續兩個紅色節點
- 從一個節點到該節點的子孫節點的所有路徑上包含相同數目的黑色節點
3、示例:
下圖是一個典型的紅黑樹:
4、思考:
針對於BST中提到的缺陷,依次插入6、5、4、3、2,紅黑樹是如何處理的呢?此處不做解答,請繼續往下看。
紅黑樹實踐-TreeMap
1、TreeMap.Entry:
首先我們來看下TreeMap中儲存鍵值對的類-TreeMap.Entry,該類定義了紅黑樹的資料結構。檢視其成員變數,除了儲存key、value外,還儲存了左節點、右節點、父節點、顏色(預設為黑色)。
1 static final class Entry<K,V> implements Map.Entry<K,V> {
2 K key;
3 V value;
4 Entry<K,V> left;
5 Entry<K,V> right;
6 Entry<K,V> parent;
7 boolean color = BLACK;
8
9 /**
10 * Make a new cell with given key, value, and parent, and with
11 * {@code null} child links, and BLACK color.
12 */
13 Entry(K key, V value, Entry<K,V> parent) {
14 this.key = key;
15 this.value = value;
16 this.parent = parent;
17 }
18
19 /**
20 * Returns the key.
21 *
22 * @return the key
23 */
24 public K getKey() {
25 return key;
26 }
27
28 /**
29 * Returns the value associated with the key.
30 *
31 * @return the value associated with the key
32 */
33 public V getValue() {
34 return value;
35 }
36
37 /**
38 * Replaces the value currently associated with the key with the given
39 * value.
40 *
41 * @return the value associated with the key before this method was
42 * called
43 */
44 public V setValue(V value) {
45 V oldValue = this.value;
46 this.value = value;
47 return oldValue;
48 }
49
50 public boolean equals(Object o) {
51 if (!(o instanceof Map.Entry))
52 return false;
53 Map.Entry<?,?> e = (Map.Entry<?,?>)o;
54
55 return valEquals(key,e.getKey()) && valEquals(value,e.getValue());
56 }
57
58 public int hashCode() {
59 int keyHash = (key==null ? 0 : key.hashCode());
60 int valueHash = (value==null ? 0 : value.hashCode());
61 return keyHash ^ valueHash;
62 }
63
64 public String toString() {
65 return key + "=" + value;
66 }
67}
複製程式碼
2、新增元素程式碼實現:
1public V put(K key, V value) {
2 //找到合適的位置插入Entry元素
3 Entry<K,V> t = root;
4 if (t == null) {
5 compare(key, key); // type (and possibly null) check
6
7 root = new Entry<>(key, value, null);
8 size = 1;
9 modCount++;
10 return null;
11 }
12 int cmp;
13 Entry<K,V> parent;
14 // split comparator and comparable paths
15 Comparator<? super K> cpr = comparator;
16 if (cpr != null) {
17 do {
18 parent = t;
19 cmp = cpr.compare(key, t.key);
20 if (cmp < 0)
21 t = t.left;
22 else if (cmp > 0)
23 t = t.right;
24 else
25 return t.setValue(value);
26 } while (t != null);
27 }
28 else {
29 if (key == null)
30 throw new NullPointerException();
31 @SuppressWarnings("unchecked")
32 Comparable<? super K> k = (Comparable<? super K>) key;
33 do {
34 parent = t;
35 cmp = k.compareTo(t.key);
36 if (cmp < 0)
37 t = t.left;
38 else if (cmp > 0)
39 t = t.right;
40 else
41 return t.setValue(value);
42 } while (t != null);
43 }
44 Entry<K,V> e = new Entry<>(key, value, parent);
45 if (cmp < 0)
46 parent.left = e;
47 else
48 parent.right = e;
49 //進行插入後的平衡調整
50 fixAfterInsertion(e);
51 size++;
52 modCount++;
53 return null;
54}
複製程式碼
因為插入後可能帶來紅黑樹的不平衡,所以需要進行平衡調整,方法無非兩種:
- 變色
- 旋轉(左旋或者右旋)
1private void fixAfterInsertion(Entry<K,V> x) {
2 //預設插入元素顏色為紅色
3 x.color = RED;
4
5 //只要x不為根且父親節點為紅色就繼續調整
6 while (x != null && x != root && x.parent.color == RED) {
7 //父節點為爺爺節點的左節點
8 if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
9 //獲取右叔節點
10 Entry<K,V> y = rightOf(parentOf(parentOf(x)));
11 //右叔節點為紅色(此時父節點也為紅色)
12 if (colorOf(y) == RED) {
13 //變色
14 //父節點改為黑色
15 setColor(parentOf(x), BLACK);
16 //右叔節點改為黑色
17 setColor(y, BLACK);
18 //爺爺節點改成紅色
19 setColor(parentOf(parentOf(x)), RED);
20 //爺爺節點所在的子樹已平衡,所以讓x指向爺爺節點,繼續往上調整
21 x = parentOf(parentOf(x));
22 } else {
23 //父親節點在爺爺節點的左邊,而x在父親節點的右邊,則需要調整到同一側
24 if (x == rightOf(parentOf(x))) {
25 //x指向父節點
26 x = parentOf(x);
27 //以x進行左旋
28 rotateLeft(x);
29 }
30 //變色
31 //父節點改為黑色
32 setColor(parentOf(x), BLACK);
33 //右叔節點改為黑色-本身已經為黑色
34 //爺爺節點改為紅色
35 setColor(parentOf(parentOf(x)), RED);
36 //右叔為黑,變色後左邊黑色多與右邊,故以爺爺節點為中心右旋
37 rotateRight(parentOf(parentOf(x)));
38 }
39 } else {//父節點為爺爺節點的右節點
40 //獲取左叔節點
41 Entry<K,V> y = leftOf(parentOf(parentOf(x)));
42 //左叔節點為紅色(此時父節點也為紅色)
43 if (colorOf(y) == RED) {
44 //變色
45 //父節點改為黑色
46 setColor(parentOf(x), BLACK);
47 //左叔節點改為黑色
48 setColor(y, BLACK);
49 //爺爺節點改為紅色
50 setColor(parentOf(parentOf(x)), RED);
51 //爺爺節點所在的子樹已平衡,所以讓x指向爺爺節點,繼續往上調整
52 x = parentOf(parentOf(x));
53 } else {
54 //父親節點在爺爺節點的右邊,而x在父親節點的左邊,則需要調整到同一側
55 if (x == leftOf(parentOf(x))) {
56 //x指向父節點
57 x = parentOf(x);
58 //以x進行右旋
59 rotateRight(x);
60 }
61 //變色
62 //父節點改為黑色
63 setColor(parentOf(x), BLACK);
64 //左叔節點改為黑色-本身已經為黑色
65 //爺爺節點改為紅色
66 setColor(parentOf(parentOf(x)), RED);
67 //左叔為黑,變色後右邊黑色多與左邊,故以爺爺節點為中心左旋
68 rotateLeft(parentOf(parentOf(x)));
69 }
70 }
71 }
72 //根節點設定為黑色
73 root.color = BLACK;
74}
複製程式碼
3、新增元素流程圖:
4、新增元素精簡流程圖:
通過上述流程圖可以看出,紅黑樹的調整是採用區域性平衡的方式,從下往上依次處理,最終達到整棵樹的平衡。
5、實踐:
依次插入2、3、4、5、7、8、16、15、14、10、12,畫出紅黑樹插入過程。
上述例子,涵蓋了元素插入中遇到的所有情況。相信通過學習優化後的流程圖,對於紅黑樹的元素插入問題,大家的思路是足夠清晰的。
總結
本文主要是通過二分查詢樹的缺陷,引出了紅黑樹的說明。通過分析TreeMap插入元素的原始碼,整理出紅黑樹調整的流程圖,最後通過示例來加深對於紅黑樹的理解。
此時,再回到BST中的缺陷問題,紅黑樹是如何進行解決的,想必大家都已經有了答案。
文章的最後,感謝大家的支援,歡迎掃描下方二維碼,進行關注。如有任何疑問,歡迎大家留言。