1. 程式人生 > >Java 集合:TreeMap工作原理及實現

Java 集合:TreeMap工作原理及實現

前言

本文轉載自:點這裡,該部落格非常不錯,建議前去看看。

正文

1. 概述

A Red-Black tree based NavigableMap implementation. The map is sorted according to 
the natural ordering of its keys, or by a Comparator provided at map creation time, 
depending on which constructor is used.
This implementation provides guaranteed log(n) time cost for the containsKey, get, 
put and remove operations. Algorithms are adaptations of those in Cormen, Leiserson, 
and Rivest’s Introduction to Algorithms.

之前已經學習過HashMap和LinkedHashMap了,HashMap不保證資料有序,LinkedHashMap保證資料可以保持插入順序,而如果我們希望Map可以保持key的大小順序的時候,我們就需要利用TreeMap了。

TreeMap<Integer, String> tmap = new TreeMap<Integer, String>();
tmap.put(1, "語文");
tmap.put(3, "英語");
tmap.put(2, "數學");
tmap.put(4, "政治");
tmap.put(5, "歷史");
tmap.put(6, "地理");
tmap.put(7, "生物");
tmap.put(8, "化學");
for(Entry<Integer, String> entry : tmap.entrySet()) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}

其大致的結構如下所示:

TreeMap

使用紅黑樹的好處是能夠使得樹具有不錯的平衡性,這樣操作的速度就可以達到log(n)的水平了。具體紅黑樹的實現不在這裡贅述,可以參考資料結構之紅黑樹wikipedia-紅黑樹等的實現。

2. put函式

Associates the specified value with the specified key in this map.
If the map previously contained a mapping for the key, the old value is replaced.

如果存在的話,old value被替換;如果不存在的話,則新添一個節點,然後對做紅黑樹的平衡操作。

public V put(K key, V value) {
    Entry<K,V> t = root;
    if (t == null) {
        compare(key, key); // type (and possibly null) check

        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
                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);
    if (cmp < 0)
        parent.left = e;
    else
        parent.right = e;
        // 紅黑樹平衡調整
    fixAfterInsertion(e);
    size++;
    modCount++;
    return null;
}

3. get函式

get函式則相對來說比較簡單,以log(n)的複雜度進行get

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;
}

public V get(Object key) {
    Entry<K,V> p = getEntry(key);
    return (p==null ? null : p.value);
}

4. successor後繼

TreeMap是如何保證其迭代輸出是有序的呢?其實從巨集觀上來講,就相當於樹的中序遍歷(LDR)。我們先看一下迭代輸出的步驟

for(Entry<Integer, String> entry : tmap.entrySet()) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}
for(Iterator<Map.Entry<String, String>> it = tmap.entrySet().iterator() ; tmap.hasNext(); ) {
    Entry<Integer, String> entry = it.next();
    System.out.println(entry.getKey() + ": " + entry.getValue());
}

在it.next()的呼叫中會使用nextEntry呼叫successor這個是過的後繼的重點,具體實現如下:

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 {
        // 如果右子樹為空,則尋找當前節點所在左子樹的第一個祖先節點
        // 因為左子樹找完了,根據LDR該D了
        Entry<K,V> p = t.parent;
        Entry<K,V> ch = t;
        // 保證左子樹
        while (p != null && ch == p.right) {
            ch = p;
            p = p.parent;
        }
        return p;
    }
}

怎麼理解這個successor呢?只要記住,這個是中序遍歷就好了,L-D-R。具體細節如下:

a. 空節點,沒有後繼
b. 有右子樹的節點,後繼就是右子樹的“最左節點”
c. 無右子樹的節點,後繼就是該節點所在左子樹的第一個祖先節點

a.好理解,不過b, c,有點像繞口令啊,沒關係,上圖舉個例子就懂了!

有右子樹的節點,節點的下一個節點,肯定在右子樹中,而右子樹中“最左”的那個節點則是右子樹中最小的一個,那麼當然是右子樹的“最左節點”,就好像下圖所示:

TreeMap1

無右子樹的節點,先找到這個節點所在的左子樹(右圖),那麼這個節點所在的左子樹的父節點(綠色節點),就是下一個節點。

TreeMap2