TreeMap之元素插入

微信公衆號:I am CR7
若有問題或建議,請在下方留言;
最近更新:2018-09-05java

二分查找樹(BST)

一、定義:

    二分查找樹又稱二叉查找樹、二叉排序樹,英文縮寫爲BST,即Binary Search Tree。該數據結構的出現,是爲了提升查找的效率。之因此成爲二分查找樹,是由於其採用二分查找的算法。算法

二、特性:
  • 若左子樹不爲空,左子樹上全部節點的值均小於根節點的值
  • 若右子樹不爲空,右子樹上全部節點的值均大於根節點的值
  • 左右子樹又分別是一棵二分查找樹
三、示例:

    下圖是一個典型的二分查找樹:
微信

圖注:二分查找樹
圖注:二分查找樹

查找節點10:
  1. 查看根節點5,由於10>5,往右子樹走
圖注:查看根節點5
圖注:查看根節點5

  1. 查看節點8,由於10>8,往右子樹走
圖注:查看節點8
圖注:查看節點8

  1. 查看節點15,由於10<15,往左子樹走
圖注:查看節點15
圖注:查看節點15
  1. 查看節點12,由於10<12,往左子樹走
圖注:查看節點12
圖注:查看節點12
  1. 查看節點10,正是咱們要找的節點
圖注:查看節點10
圖注:查看節點10
四、思考:

    雖然二叉查找樹提升了查詢的效率,可是依舊存在着缺陷,請看下面例子:數據結構

圖注:依次插入六、五、四、三、2
圖注:依次插入六、五、四、三、2

    當依次插入六、五、四、三、2時,發現樹成了線性形式,在此種狀況下,查找效率大打折扣,所以就有了自平衡的二叉查找樹,名爲紅黑樹。

紅黑樹(RBT)

一、定義:

    紅黑樹,是一個自平衡的二叉排序樹,英文簡稱爲RBT,即Red Black Tree。每一個節點上增長了顏色屬性,要麼爲紅,要麼爲黑。app

二、特性:
  • 每一個節點要麼爲紅色,要麼爲黑色
  • 根節點爲黑色
  • 每一個葉子節點(NIL)是黑色 [注意:這裏葉子節點,是指爲空(NIL或NULL)的葉子節點!]
  • 不容許連續兩個紅色節點
  • 從一個節點到該節點的子孫節點的全部路徑上包含相同數目的黑色節點
三、示例:

    下圖是一個典型的紅黑樹:學習

圖注:紅黑樹
圖注:紅黑樹
四、思考:

    針對於BST中提到的缺陷,依次插入六、五、四、三、2,紅黑樹是如何處理的呢?此處不作解答,請繼續往下看。優化

紅黑樹實踐-TreeMap

一、TreeMap.Entry:

    首先咱們來看下TreeMap中存儲鍵值對的類-TreeMap.Entry,該類定義了紅黑樹的數據結構。查看其成員變量,除了存儲key、value外,還存儲了左節點、右節點、父節點、顏色(默認爲黑色)。this

 1 static final class Entry<K,Vimplements 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}
複製代碼
二、添加元素代碼實現:
 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}
複製代碼

    由於插入後可能帶來紅黑樹的不平衡,因此須要進行平衡調整,方法無非兩種:spa

  • 變色
  • 旋轉(左旋或者右旋)
 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}
複製代碼
三、添加元素流程圖:
圖注:插入元素調整流程
圖注:插入元素調整流程
四、添加元素精簡流程圖:
圖注:插入元素調整流程
圖注:插入元素調整流程

    經過上述流程圖能夠看出,紅黑樹的調整是採用局部平衡的方式,從下往上依次處理,最終達到整棵樹的平衡。

五、實踐:

    依次插入二、三、四、五、七、八、1六、1五、1四、十、12,畫出紅黑樹插入過程。
3d

圖注:插入元素2
圖注:插入元素2

圖注:插入元素3
圖注:插入元素3

圖注:插入元素4
圖注:插入元素4

圖注:插入元素5
圖注:插入元素5

圖注:插入元素7
圖注:插入元素7

圖注:插入元素8
圖注:插入元素8

圖注:插入元素16
圖注:插入元素16

圖注:插入元素15
圖注:插入元素15

圖注:插入元素14
圖注:插入元素14

圖注:插入元素10
圖注:插入元素10

圖注:插入元素12
圖注:插入元素12

    上述例子,涵蓋了元素插入中遇到的全部狀況。相信經過學習優化後的流程圖,對於紅黑樹的元素插入問題,你們的思路是足夠清晰的。

總結

    本文主要是經過二分查找樹的缺陷,引出了紅黑樹的說明。經過分析TreeMap插入元素的源碼,整理出紅黑樹調整的流程圖,最後經過示例來加深對於紅黑樹的理解。
    此時,再回到BST中的缺陷問題,紅黑樹是如何進行解決的,想必你們都已經有了答案。
    文章的最後,感謝你們的支持,歡迎掃描下方二維碼,進行關注。若有任何疑問,歡迎你們留言。

相關文章
相關標籤/搜索