TreeMap

TreeMap

TreeMap 的實現是紅黑樹算法的實現,因此要了解 TreeMap 就必須對紅黑樹有必定的瞭解。html

其實這篇博文的名字叫作:根據紅黑樹的算法來分析 TreeMap 的實現,可是爲了與 Java 提升篇系列博文保持一致仍是叫作 TreeMap 比較好。經過這篇博文你能夠得到以下知識點:java

  • 一、紅黑樹的基本概念。算法

  • 二、紅黑樹增長節點、刪除節點的實現過程。bash

  • 三、紅黑樹左旋轉、右旋轉的複雜過程。數據結構

  • 四、Java 中 TreeMap 是如何經過 put、deleteEntry 兩個來實現紅黑樹增長、刪除節點的。spa

我想經過這篇博文你對 TreeMap 必定有了更深的認識。好了,下面先簡單普及紅黑樹知識。.net

紅黑樹簡介

紅黑樹又稱紅-黑二叉樹,它首先是一顆二叉樹,它具體二叉樹全部的特性。同時紅黑樹更是一顆自平衡的排序二叉樹。code

咱們知道一顆基本的二叉樹他們都須要知足一個基本性質–即樹中的任何節點的值大於它的左子節點,且小於它的右子節點。cdn

按照這個基本性質使得樹的檢索效率大大提升。咱們知道在生成二叉樹的過程是很是容易失衡的,最壞的狀況就是一邊倒(只有右/左子樹),這樣勢必會致使二叉樹的檢索效率大大下降(O(n)),因此爲了維持二叉樹的平衡,大牛們提出了各類實現的算法,如:AVL,SBT,伸展樹,TREAP ,紅黑樹等等。htm

平衡二叉樹必須具有以下特性:它是一棵空樹或它的左右兩個子樹的高度差的絕對值不超過 1,而且左右兩個子樹都是一棵平衡二叉樹。也就是說該二叉樹的任何一個等等子節點,其左右子樹的高度都相近。

紅黑樹顧名思義就是節點是紅色或者黑色的平衡二叉樹,它經過顏色的約束來維持着二叉樹的平衡。對於一棵有效的紅黑樹二叉樹而言咱們必須增長以下規則:

  • 一、每一個節點都只能是紅色或者黑色

  • 二、根節點是黑色

  • 三、每一個葉節點(NIL 節點,空節點)是黑色的。

  • 四、若是一個結點是紅的,則它兩個子節點都是黑的。也就是說在一條路徑上不能出現相鄰的兩個紅色結點。

  • 五、從任一節點到其每一個葉子的全部路徑都包含相同數目的黑色節點。

這些約束強制了紅黑樹的關鍵性質: 從根到葉子的最長的可能路徑很少於最短的可能路徑的兩倍長。結果是這棵樹大體上是平衡的。由於操做好比插入、刪除和查找某個值的最壞狀況時間都要求與樹的高度成比例,這個在高度上的理論上限容許紅黑樹在最壞狀況下都是高效的,而不一樣於普通的二叉查找樹。因此紅黑樹它是複雜而高效的,其檢索效率 O(log n)。下圖爲一顆典型的紅黑二叉樹。

對於紅黑二叉樹而言它主要包括三大基本操做:左旋、右旋、着色。

注:因爲本文主要是講解 Java 中 TreeMap,因此並無對紅黑樹進行很是深刻的瞭解和研究,若是諸位想對其進行更加深刻的研究Lz提供幾篇較好的博文:

一、紅黑樹系列集錦

二、紅黑樹數據結構剖析

三、紅黑樹

2、TreeMap 數據結構

public class TreeMap<K,V>
        extends AbstractMap<K,V>
        implements NavigableMap<K,V>, Cloneable, java.io.Serializable
複製代碼

TreeMap 繼承 AbstractMap,實現 NavigableMap、Cloneable、Serializable 三個接口。其中 AbstractMap 代表 TreeMap 爲一個 Map 即支持 key-value 的集合,NavigableMap(更多)則意味着它支持一系列的導航方法,具有針對給定搜索目標返回最接近匹配項的導航方法 。

TreeMap 中同時也包含了以下幾個重要的屬性:

//比較器,由於TreeMap是有序的,經過comparator接口咱們能夠對TreeMap的內部排序進行精密的控制
        private final Comparator<? super K> comparator;
            //TreeMap紅-黑節點,爲TreeMap的內部類
        private transient Entry<K,V> root = null;
            //容器大小
        private transient int size = 0;
            //TreeMap修改次數
        private transient int modCount = 0;
            //紅黑樹的節點顏色--紅色
        private static final boolean RED = false;
            //紅黑樹的節點顏色--黑色
        private static final boolean BLACK = true;
複製代碼

對於葉子節點 Entry 是 TreeMap 的內部類,它有幾個重要的屬性:

//鍵
            K key;
            //值
            V value;
            //左孩子
            Entry<K,V> left = null;
            //右孩子
            Entry<K,V> right = null;
            //父親
            Entry<K,V> parent;
            //顏色
            boolean color = BLACK;
複製代碼

注:前面只是開胃菜,下面是本篇博文的重中之重,在下面兩節我將重點講解 treeMap 的 put()、delete() 方法。經過這兩個方法咱們會了解紅黑樹增長、刪除節點的核心算法。

TreeMap put() 方法

在瞭解 TreeMap 的 put() 方法以前,咱們先了解紅黑樹增長節點的算法。

紅黑樹增長節點

紅黑樹在新增節點過程當中比較複雜,複雜歸複雜它一樣必需要依據上面提到的五點規範,同時因爲規則 一、二、3 基本都會知足,下面咱們主要討論規則 四、5。假設咱們這裏有一棵最簡單的樹,咱們規定新增的節點爲 N、它的父節點爲 P、P 的兄弟節點爲 U、P 的父節點爲 G。

新增N 父節點P P的兄弟U P的父節點G

  • 插入新節點總爲紅色
  • 若是插入節點的父節點是黑色,能維持性質
  • 若是插入節點的父節點是紅色,破壞了性質。插入算法着色或旋轉
public V put(K key, V value) {
                //用t表示二叉樹的當前節點
                Entry<K,V> t = root;
                //t爲null表示一個空樹,即TreeMap中沒有任何元素,直接插入
                if (t == null) {
                    compare(key, key); // type (and possibly null) check //將新的key-value鍵值對建立爲一個Entry節點,並將該節點賦予給root
                    root = new Entry<>(key, value, null);
                    //容器的size = 1,表示TreeMap集合中存在一 個元素
                    size = 1;
                    //修改次數 + 1
                    modCount++;
                    return null;
                }
                //cmp表示key排序的返回結果
                int cmp;    
                //父節點
                Entry<K,V> parent; 
                // split comparator and comparable paths
                Comparator<? super K> cpr = comparator;    //指定的排序算法
                //若是cpr不爲空,則採用既定的排序算法進行建立TreeMap集合
                if (cpr != null) {
                    //排序二叉樹
                    do {
                        parent = t;      //parent指向上次循環後的t
                        //比較新增節點的key和當前節點key的大小
                        cmp = cpr.compare(key, t.key);
                        //cmp返回值小於0,表示新增節點的key小於當前節點的key,則以當前節點的左子節點做爲新的當前節點
                        if (cmp < 0)
                            t = t.left;
                        //cmp返回值大於0,表示新增節點的key大於當前節點的key,則以當前節點的右子節點做爲新的當前節點
                        else if (cmp > 0)
                            t = t.right;
                        //cmp返回值等於0,表示兩個key值相等,則新值覆蓋舊值,並返回新值
                        else
                            return t.setValue(value);
                    } while (t != null);
                }
                //若是cpr爲空,則採用默認的排序算法進行建立 TreeMap集合
                else {
                    if (key == null)     //key值爲空拋出異常
                        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);
                }
                //將新增節點當作parent的子節點
                Entry<K,V> e = new Entry<>(key, value,   parent);
                //若是新增節點的key小於parent的key,則當作左子節點
                if (cmp < 0)
                    parent.left = e;
                //若是新增節點的key大於parent的key,則當作右子節點
                else
                    parent.right = e;
                /* * 上面已經完成了排序二叉樹的的構建,將新增節點 插入該樹中的合適位置 * 下面fixAfterInsertion()方法就是對這棵樹進行調整、平衡,具體過程參考上面的五種狀況 */
                fixAfterInsertion(e);
                //TreeMap元素數量 + 1
                size++;
                //TreeMap容器修改次數 + 1
                modCount++;
                return null;
            }
複製代碼

do{} 代碼塊是實現排序二叉樹的核心算法,經過該算法咱們能夠確認新增節點在該樹的正確位置。

紅黑樹是一棵平衡排序二叉樹,普通的排序二叉樹可能會出現失衡的狀況,因此下一步就是要進行調整。fixAfterInsertion(e); 調整的過程務必會涉及到紅黑樹的左旋、右旋、着色三個基本操做。代碼以下:

/** * 新增節點後的修復操做 * x 表示新增節點 */
         private void fixAfterInsertion(Entry<K,V> x) {
                x.color = RED;   //新增節點的顏色爲紅色
                //循環 直到 x不是根節點,且x的父節點不爲紅色
                while (x != null && x != root && x.parent.color == RED) {
      //若是X的父節點(P)是其父節點的父節點(G)的左節點
                    if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
                        //獲取X的叔節點(U)
                        Entry<K,V> y = rightOf(parentOf (parentOf(x)));
                        //若是X的叔節點(U) 爲紅色(狀況三)
                        if (colorOf(y) == RED) {     
                            //將X的父節點(P)設置爲黑色
                            setColor(parentOf(x), BLACK);
                            //將X的叔節點(U)設置爲黑色
                            setColor(y, BLACK);
                            //將X的父節點的父節點(G)設置紅色
                            setColor(parentOf(parentOf(x)), RED);
                            x = parentOf(parentOf(x));
                        }
                        //若是X的叔節點(U爲黑色);這裏會存在 兩種狀況(狀況4、狀況五)
                        else {   
                            //若是X節點爲其父節點(P)的右子 樹,則進行左旋轉(狀況四)
                            if (x == rightOf(parentOf(x))) {
                                //將X的父節點做爲X
                                x = parentOf(x);
                                //右旋轉
                                rotateLeft(x);
                            }
                            //(狀況五)
                            //將X的父節點(P)設置爲黑色
                            setColor(parentOf(x), BLACK);
                            //將X的父節點的父節點(G)設置紅色
                            setColor(parentOf(parentOf(x)), RED);                            //以X的父節點的父節點(G)爲中心右旋轉
                           rotateRight(parentOf(parentOf(x)));
                        }
                    }                
                    //若是X的父節點(P)是其父節點的父節點(G的右節點)
                    else {
                        //獲取X的叔節點(U)
                        Entry<K,V> y = leftOf(parentOf(parentOf(x)));
                        //若是X的叔節點(U) 爲紅色(狀況三)
                        if (colorOf(y) == RED) {
                            //將P、U設爲黑色,G設爲黑色
                            //將X的父節點(P)設置爲黑色
                            setColor(parentOf(x), BLACK);
                            //將X的叔節點(U)設置爲黑色
                            setColor(y, BLACK);
                            //將X的父節點的父節點(G)設置紅色
                            setColor(parentOf(parentOf(x)), RED);
                            //
                            x = parentOf(parentOf(x));
                        }
                      //若是X的叔節點(U爲黑色);這裏會存在兩種狀況(狀況4、狀況五)
                        else {
                            //若是X節點爲其父節點(P)的右子樹,則進行左旋轉(狀況四)
                            if (x == leftOf(parentOf(x)))   {
                                //將X的父節點做爲X
                                x = parentOf(x);
                                //右旋轉
                                rotateRight(x);
                            }
                            //(狀況五)
                            //將X的父節點(P)設置爲黑色
                            setColor(parentOf(x), BLACK);
                            //將X的父節點的父節點(G)設置紅色
                            setColor(parentOf(parentOf(x)), RED);
                            //以X的父節點的父節點(G)爲中心右旋轉
                            rotateLeft(parentOf(parentOf(x)));
                        }
                    }
                }
                //將根節點G強制設置爲黑色
                root.color = BLACK;
            }
複製代碼
相關文章
相關標籤/搜索