TreeMap源碼

1、樹的介紹

1.1、排序二叉樹

HashMapHashSet的共同實現機制是哈希表,一個共同的限制是沒有順序。

TreeSetTreeMap這兩個類的共同實現基礎是排序二叉樹

排序二叉樹也是二叉樹,但它沒有重複元素,而且是有序的二叉樹

  • 如果左子樹不爲空,則左子樹上的所有節點都小於該節點

  • 如果右子樹不爲空,則右子樹上的所有節點都大於該節點

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-JqrTyfFQ-1582551196483)(images/07.png)]

1.2、常用算法

1.2.1、查找

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-dHzNWwz2-1582551196484)(images/08.png)]

1.2.2、遍歷

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-uichGFNu-1582551196485)(images/09.png)]

1.2.3、插入

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-LNLWxJt4-1582551196486)(images/10.png)]

1.2.4、刪除

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-KmQ3zGLU-1582551196487)(images/11.png)]

1.3、平衡二叉樹

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-BaP9KHlr-1582551196488)(images/12.png)]

1.5哈希與樹的比較

與哈希表一樣,樹也是計算程序中一種最重要的數據結構和思維方式,爲了能夠快速操作數據,哈希是兩種基本的思維方式,不需要順序,,優先考慮哈希需要順序,考慮樹

除了容器類TreeMapTreeSet,數據庫中的索引結構也是基於樹的(不過基於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;
            }
        }
    }


2.7、小結

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-orMen3iq-1582551196489)(images/13.png)]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章