java集合框架(四):TreeMap

        TreeMap的數據結構與HashMap、LinkedHashMap不一樣,其整個結構就是一顆紅黑樹,因此咱們要研究TreeMap就得先從紅黑樹開始。對於紅黑樹的算法,我在本文章不詳細展開,有興趣的同窗能夠點擊這裏學習。本文主要是剖析紅黑樹的原理,以及解讀TreeMap是如何運用紅黑樹實現的。html

        紅黑樹是什麼?咱們能夠從《數據結構與算法分析》這本書找到解析:紅黑樹是具備着色性質的二叉查找樹,是AVL樹(自平衡二叉查找樹)的一個變種。接下來我從它的基本定義中來說解紅黑樹。java

1、二叉查找樹

        二叉查找樹,也能夠叫作二叉排序樹,他具備排序的功能。二叉樹要成爲二叉查找樹需知足如下條件:算法

  1. 若左子樹不空,則左子樹上全部節點的值均小於父節點的值。
  2. 若右子樹不空,則右子樹上全部節點的值均大於父節點的值。
  3. 左、右子樹也分別爲二叉查找樹。

        由以上條件可知,下圖左邊爲二叉查找樹,右邊爲普通二叉樹。二叉查找樹的平均深度爲O(log2 n)。深度是指對於任何一個節點,根節點到其自己的惟一路徑長。根節點的深度爲0,以下圖所示,節點值爲2的深度是1,節點值爲4的深度是2。api

2、AVL樹(自平衡二叉查找樹

        二叉查找樹的平均深度是爲O(log2 n), 可是也會出現一些極端的狀況,若是插入的節點集自己就是有序的,要麼從大到小排序,要麼從小到大排序,就會出現以下圖的結果。在這種狀況下,排序二叉樹就變成了普通鏈表,其查找效率就會不好。數據結構

        爲了解決這種極端狀況,兩位科學家 G.M. Adelson-Velsky 和 E.M. Landis就提出了自平衡的二叉查找樹,AVL樹也就來自於他們兩個的名字組合。AVL樹能保持自平衡的條件是二叉查找樹每一個節點的左子樹和右子樹的高度最多差1。其中高度是指一個節點到葉子節點的最長路徑,所以任何一個葉子節點的高度爲0.以下圖所示,左圖根節點的左子樹的高度爲2,右子樹的高度爲1,相差爲1,因此是AVL樹。右圖根節點左子樹的高度爲2,右子樹高度爲0,相差爲2,因此不是AVL樹。app

                        左圖.AVL樹                                        右圖.非AVL樹ide

        AVL樹可以保證樹的深度爲O(log2 n),所以它可以提供較好的查找性能。它的缺點是爲了保持平衡,添加或者刪除節點時須要進行復雜的平衡操做,須要很大的性能開銷。函數

3、紅黑樹

        紅黑樹是二叉查找樹,它不追求「徹底平衡」,只要求部分達到平衡,其在添加或者刪除節點時不須要太多複雜的旋轉操做就能夠保持平衡,任何不平衡在三次旋轉內解決。紅黑樹可以以O(log2 n)的時間複雜度進行搜索、插入、刪除操做。性能

        紅黑樹就是犧牲了高度平衡的性質換取了較高性能的插入和刪除操做,爲了保持其自身的部分平衡,着色須要知足下面條件:學習

  1. 每個節點要麼着成紅色,要麼着成黑色。
  2. 根是黑色的。
  3. 若是一個節點時紅色的,那麼它的子節點必須是黑色的。
  4. 從一個節點到一個null引用的每一條路徑必須包含相同數目的黑色節點。

        上圖爲紅黑樹的例圖。當插入或者刪除節點時,紅黑樹是經過變色和旋轉來同時保證4個條件知足的,讀者若是有興趣研究他們的變換原理,能夠點擊此處在線構建紅黑樹。

4、類的定義

public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable{}
public interface NavigableMap<K,V> extends SortedMap<K,V> {}

        TreeMap實現了NavigableMap(導航map,裏面定義了一些接口,不多用到),而NavigableMap則繼承了SortMap接口(要求實現類須要對key排序),故TreeMap間接實現了SortMap,也就會實如今SortMap中定義的對key排序的接口。類關係圖以下所示:

5、構造器

public TreeMap() {
    // 比較器爲空
    comparator = null;
}
public TreeMap(Comparator<? super K> comparator) {
     // 對比較器賦值,插入節點到紅黑樹中會用到這個比較器來比較key的大小,提早是key要實現這個比較器
     // 對key的排序規則能夠經過這個比較器來指定(大到小或者小到大)
     this.comparator = comparator;
}
public TreeMap(Map<? extends K, ? extends V> m) {
    comparator = null;
    // 把外面傳進來的key-value加入到紅黑樹
    putAll(m);
}
// 該構造器是把已經排序好的SortMap節點從新構形成紅黑樹,並把SortMap的比較器賦值給TreeMap
public TreeMap(SortedMap<K, ? extends V> m) {
        // 使用SortMap的比較器
        comparator = m.comparator();
        try {
            // 構造紅黑樹
            buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
        } catch (java.io.IOException cannotHappen) {
        } catch (ClassNotFoundException cannotHappen) {
        }
}

6、存儲的實現

1.put方法

        put方法就是把節點加到紅黑樹中,從源碼中能夠看到,key不能爲空。爲了插入紅黑樹時對key做比較,key要麼實現Comparator接口,要麼實現Comparable接口

public V put(K key, V value) {
        Entry<K,V> t = root;
        if (t == null) {
            // 對key判空和類型檢查
            compare(key, key);

            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent;
        Comparator<? super K> cpr = comparator;
        // 比較器不爲空就使用Comparator來對key作比較
        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);
        }
        // 使用Comparable接口做比較
        else {
            // key不能爲空,不然拋出異常
            if (key == null)
                throw new NullPointerException();
            // 這裏作了強轉型,若是key沒有實現Comparable接口會拋異常
            @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++;
        // fast-fail機制
        modCount++;
        return null;
}

2.get操做

        get操做是比較容易理解的,它從根節點查找key,找到就返回value。它分開了兩種比較器查找過程。

public V get(Object key) {
        Entry<K,V> p = getEntry(key);
        return (p==null ? null : p.value);
}
final Entry<K,V> getEntry(Object key) {
        // 用Comparator比較器獲取
        if (comparator != null)
            return getEntryUsingComparator(key);
        // 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;
}
// 用Comparator比較器查找
final Entry<K,V> getEntryUsingComparator(Object key) {
        @SuppressWarnings("unchecked")
            K k = (K) key;
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {
            Entry<K,V> p = root;
            while (p != null) {
                int cmp = cpr.compare(k, p.key);
                if (cmp < 0)
                    p = p.left;
                else if (cmp > 0)
                    p = p.right;
                else
                    return p;
            }
        }
        return null;
}

7、遍歷的實現

        TreeMap的遍歷操做是在內部抽象類PrivateEntryIterator中完成的,其調用外部類的successor方法對紅黑樹進行遍歷,根據key值大小的順序(大到小或者小到大,由比較器決定)遍歷,其實就是從左往右遍歷紅黑樹。在使用中,若是咱們有根據key值大小的順序遍歷map的需求,可使用TreeMap,可是key必須實現Comparator或者Comparable接口

abstract class PrivateEntryIterator<T> implements Iterator<T> {
        Entry<K,V> next;
        Entry<K,V> lastReturned;
        int expectedModCount;
        // 構造器參數first是紅黑樹最左邊的節點,也就是key最小的節點
        PrivateEntryIterator(Entry<K,V> first) {
            expectedModCount = modCount;
            lastReturned = null;
            next = first;
        }

        public final boolean hasNext() {
            return next != null;
        }

        final Entry<K,V> nextEntry() {
            Entry<K,V> e = next;
            if (e == null)
                throw new NoSuchElementException();
            // fast-fail機制
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            // 這個方法就是遍歷紅黑樹的
            next = successor(e);
            lastReturned = e;
            return e;
        }
}
// 返回紅黑樹的下一個節點,t爲當前遍歷的節點。遍歷的key是從小到大
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;
        }
}

8、排序的例子

1.升序排序:

public static void main(String[] args) {

        InnerClass inner1 = new InnerClass(1);
        // 把比較器傳到構造函數
        Map<InnerClass,String> map = new TreeMap<InnerClass, String>(inner1);

        InnerClass inner2 = new InnerClass(2);
        InnerClass inner3 = new InnerClass(3);
        InnerClass inner4 = new InnerClass(4);
        InnerClass inner5 = new InnerClass(5);

        map.put(inner1,"1");
        map.put(inner2,"2");
        map.put(inner3,"3");
        map.put(inner4,"4");
        map.put(inner5,"5");

        for(Map.Entry<InnerClass,String> entry:map.entrySet()){
            System.out.print(entry.getValue() + ",");
        }

    }

    // 內部類實現比較器
    static class InnerClass implements Comparator{
        int num = 0;
        public InnerClass(int num) {
            this.num = num;
        }

        public int getNum() {
            return num;
        }

        public void setNum(int num) {
            this.num = num;
        }

        @Override
        public int compare(Object o1, Object o2) {
            InnerClass inner1 = (InnerClass)o1;
            InnerClass inner2 = (InnerClass)o2;
            // 對key升序排
            if(inner1.getNum() > inner2.getNum()){
                return 1;
            }else if (inner1.getNum() < inner2.getNum()){
                return -1;
            }else {
                return 0;
            }
        }
    }

升序排序結果:1,2,3,4,5,

2.降序排序

// 內部類實現比較器
    static class InnerClass implements Comparator{
        int num = 0;
        public InnerClass(int num) {
            this.num = num;
        }

        public int getNum() {
            return num;
        }

        public void setNum(int num) {
            this.num = num;
        }

        @Override
        public int compare(Object o1, Object o2) {
            InnerClass inner1 = (InnerClass)o1;
            InnerClass inner2 = (InnerClass)o2;
            // 對key降序排
            if(inner1.getNum() < inner2.getNum()){
                return 1;
            }else if (inner1.getNum() > inner2.getNum()){
                return -1;
            }else {
                return 0;
            }
        }
    }

降序排序結果:5,4,3,2,1,

9、總結

  1. TreeMap裏面還有不少方法在本文中沒有說起,我以爲只要把TreeMap的結構和原理理解清楚了,之後使用中若是有操做TreeMap方面的須要能夠到jdk的api或者到源碼中找。
  2. TreeMap的數據結構就是紅黑樹,紅黑樹的高度最可能是2log2(n+1),它犧牲了avl樹的高度平衡特性,換取了高效的插入、刪除、搜索性能(時間複雜度都是O(log2n)),構建紅黑樹的時候任何不知足紅黑樹條件最多3次旋轉變色會解決。
  3. TreeMap相對於HashMap而言,插入節點的時候不用考慮擴容,消除了擴容的性能開銷。
  4. TreeMap能夠根據key值的大小順序遍歷節點,在對key-value遍歷有作順序要求的場合能夠考慮使用TreeMap。
  5. TreeMap的key值不能爲空,且key必須實現Comparator接口或者Comparable接口,不然使用的時候會拋出異常。

以上就是我對紅黑樹的理解,若有不足之處,請批評和指正!

參考資料:

        博客園:http://www.cnblogs.com/wzyxidian/p/5204879.html

相關文章
相關標籤/搜索