Java集合 --- TreeMap底層實(shí)現(xiàn)和原理(源碼解析)

概述

文章的內(nèi)容基于JDK1.7進(jìn)行分析膳汪,之所以選用這個(gè)版本茄袖,是因?yàn)?.8的有些類做了改動(dòng)渺贤,增加了閱讀的難度雏胃,雖然是1.7,但是對于1.8做了重大改動(dòng)的內(nèi)容志鞍,文章也會(huì)進(jìn)行說明瞭亮。

TreeMap實(shí)現(xiàn)了SotredMap接口,它是有序的集合固棚。而且是一個(gè)紅黑樹結(jié)構(gòu)统翩,每個(gè)key-value都作為一個(gè)紅黑樹的節(jié)點(diǎn)。如果在調(diào)用TreeMap的構(gòu)造函數(shù)時(shí)沒有指定比較器此洲,則根據(jù)key執(zhí)行自然排序厂汗。這點(diǎn)會(huì)在接下來的代碼中做說明,如果指定了比較器則按照比較器來進(jìn)行排序呜师。

數(shù)據(jù)結(jié)構(gòu)

繼承關(guān)系
public class TreeMap<K,V>extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, Serializable
實(shí)現(xiàn)接口
Serializable, Cloneable, Map<K,V>, NavigableMap<K,V>, SortedMap<K,V> 
基本屬性
private final Comparator<? super K> comparator;  //比較器娶桦,是自然排序,還是定制排序 汁汗,使用final修飾衷畦,表明一旦賦值便不允許改變
private transient Entry<K,V> root = null;  //紅黑樹的根節(jié)點(diǎn)
private transient int size = 0;     //TreeMap中存放的鍵值對的數(shù)量
private transient int modCount = 0;   //修改的次數(shù)
源碼解析

由于TreeMap中源碼較長,接下來將分段解析部分源碼知牌。既然是紅黑樹存儲(chǔ)祈争,肯定要有數(shù)據(jù)結(jié)構(gòu)(Node)節(jié)點(diǎn)的〗谴纾看一下TreeMap中關(guān)于節(jié)點(diǎn)的定義部分菩混。

數(shù)據(jù)結(jié)構(gòu)
static final class Entry<K,V> implements Map.Entry<K,V> {
    K key;    //鍵
    V value;    //值
    Entry<K,V> left = null;     //左孩子節(jié)點(diǎn)
    Entry<K,V> right = null;    //右孩子節(jié)點(diǎn)
    Entry<K,V> parent;          //父節(jié)點(diǎn)
    boolean color = BLACK;      //節(jié)點(diǎn)的顏色,在紅黑樹種扁藕,只有兩種顏色墨吓,紅色和黑色

    //構(gòu)造方法,用指定的key,value ,parent初始化纹磺,color默認(rèn)為黑色
    Entry(K key, V value, Entry<K,V> parent) {
        this.key = key;
        this.value = value;
        this.parent = parent;
    }

    //返回key
    public K getKey() {
        return key;
    }

    //返回該節(jié)點(diǎn)對應(yīng)的value
    public V getValue() {
        return value;
    }

    //替換節(jié)點(diǎn)的值帖烘,并返回舊值
    public V setValue(V value) {
        V oldValue = this.value;
        this.value = value;
        return oldValue;
    }
    //重寫equals()方法
    public boolean equals(Object o) {
        if (!(o instanceof Map.Entry))
            return false;
        Map.Entry<?,?> e = (Map.Entry<?,?>)o;
        //兩個(gè)節(jié)點(diǎn)的key相等,value相等橄杨,這兩個(gè)節(jié)點(diǎn)才相等
        return valEquals(key,e.getKey()) && valEquals(value,e.getValue());
    }
    //重寫hashCode()方法
    public int hashCode() {
        int keyHash = (key==null ? 0 : key.hashCode());
        int valueHash = (value==null ? 0 : value.hashCode());
        //key和vale hash值得異或運(yùn)算秘症,相同則為零照卦,不同則為1 
        return keyHash ^ valueHash;
    }
    //重寫toString()方法
    public String toString() {
        return key + "=" + value;
    }
}
構(gòu)造方法
//構(gòu)造方法,comparator用鍵的順序做比較
public TreeMap() {
    comparator = null;
}

//構(gòu)造方法乡摹,提供比較器役耕,用指定比較器排序
public TreeMap(Comparator<? super K> comparator) {
    his.comparator = comparator;
}

//將m中的元素轉(zhuǎn)化daoTreeMap中,按照鍵的順序做比較排序
public TreeMap(Map<? extends K, ? extends V> m) {
    comparator = null;
    putAll(m);
}

//構(gòu)造方法聪廉,指定的參數(shù)為SortedMap
//采用m的比較器排序
public TreeMap(SortedMap<K, ? extends V> m) {
    comparator = m.comparator();
    try {
        buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
    } catch (java.io.IOException cannotHappen) {
    } catch (ClassNotFoundException cannotHappen) {
    }
}  

TreeMap提供了四個(gè)構(gòu)造方法瞬痘,實(shí)現(xiàn)了方法的重載。無參構(gòu)造方法中比較器的值為null,采用自然排序的方法板熊,如果指定了比較器則稱之為定制排序.

  • 自然排序:TreeMap的所有key必須實(shí)現(xiàn)Comparable接口框全,所有的key都是同一個(gè)類的對象
  • 定制排序:創(chuàng)建TreeMap對象傳入了一個(gè)Comparator對象,該對象負(fù)責(zé)對TreeMap中所有的key進(jìn)行排序干签,采用定制排序不要求Map的key實(shí)現(xiàn)Comparable接口津辩。等下面分析到比較方法的時(shí)候在分析這兩種比較有何不同。

對于Map來說容劳,使用的最多的就是put()/get()/remove()等方法喘沿,下面依次進(jìn)行分析

put()
public V put(K key, V value) {
    Entry<K,V> t = root;     //紅黑樹的根節(jié)點(diǎn)
    if (t == null) {        //紅黑樹是否為空
        compare(key, key); // type (and possibly null) check
        //構(gòu)造根節(jié)點(diǎn),因?yàn)楦?jié)點(diǎn)沒有父節(jié)點(diǎn)竭贩,傳入null值蚜印。 
        root = new Entry<>(key, value, null);  
        size = 1;     //size值加1
        modCount++;    //改變修改的次數(shù)
        return null;    //返回null 
    }
    int cmp;
    Entry<K,V> parent;    //定義節(jié)點(diǎn)
    // split comparator and comparable paths
    Comparator<? super K> cpr = comparator;     //獲取比較器
    if (cpr != null) {      //如果定義了比較器土辩,采用自定義比較器進(jìn)行比較
        do {
            parent = t;      //將紅黑樹根節(jié)點(diǎn)賦值給parent
            cmp = cpr.compare(key, t.key);     //比較key, 與根節(jié)點(diǎn)的大小
            if (cmp < 0)      //如果key < t.key , 指向左子樹
                t = t.left;   //t = t.left  , t == 它的做孩子節(jié)點(diǎn)
            else if (cmp > 0)
                t = t.right;  //如果key > t.key , 指向它的右孩子節(jié)點(diǎn)
            else
                return t.setValue(value);      //如果它們相等擂涛,替換key的值
        } while (t != null);        //循環(huán)遍歷
    }
    else {
        //自然排序方式,沒有指定比較器
        if (key == null)
            throw new NullPointerException();  //拋出異常
        Comparable<? super K> k = (Comparable<? super K>) key;    //類型轉(zhuǎn)換
        do {
            parent = t;
            cmp = k.compareTo(t.key);
            if (cmp < 0)     // key < t.key 
                t = t.left;   //左孩子
            else if (cmp > 0)   // key > t.key 
                t = t.right;    //右孩子
            else
                return t.setValue(value);   //t == t.key , 替換value值
        } while (t != null);
    }
    Entry<K,V> e = new Entry<>(key, value, parent);   //創(chuàng)建新節(jié)點(diǎn)襟企,并制定父節(jié)點(diǎn)
    //根據(jù)比較結(jié)果肪获,決定新節(jié)點(diǎn)為父節(jié)點(diǎn)的左孩子或者右孩子
    if (cmp < 0)
        parent.left = e;
    else
        parent.right = e;
    fixAfterInsertion(e);   //新插入節(jié)點(diǎn)后重新調(diào)整紅黑樹 
    size++;
    modCount++;
    return null;
}
//比較方法,如果comparator==null ,采用comparable.compartTo進(jìn)行比較柒傻,否則采用指定比較器比較大小
final int compare(Object k1, Object k2) {
    return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
        : comparator.compare((K)k1, (K)k2);
}

private void fixAfterInsertion(Entry<K,V> x) {
    //插入的節(jié)點(diǎn)默認(rèn)的顏色為紅色
    x.color = RED;    //
    //情形1: 新節(jié)點(diǎn)x 是樹的根節(jié)點(diǎn)孝赫,沒有父節(jié)點(diǎn)不需要任何操作
    //情形2: 新節(jié)點(diǎn)x 的父節(jié)點(diǎn)顏色是黑色的,也不需要任何操作

    while (x != null && x != root && x.parent.color == RED) {
    //情形3:新節(jié)點(diǎn)x的父節(jié)點(diǎn)顏色是紅色的
    //判斷x的節(jié)點(diǎn)的父節(jié)點(diǎn)位置红符,是否屬于左孩子
    if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
          //獲取x節(jié)點(diǎn)的父節(jié)點(diǎn)的兄弟節(jié)點(diǎn)青柄,上面語句已經(jīng)判斷出x節(jié)點(diǎn)的父節(jié)點(diǎn)為左孩子,所以直接取右孩子
         Entry<K,V> y = rightOf(parentOf(parentOf(x)));
         //判斷是否x節(jié)點(diǎn)的父節(jié)點(diǎn)的兄弟節(jié)點(diǎn)為紅色预侯。
         if (colorOf(y) == RED) {
              setColor(parentOf(x), BLACK); // x節(jié)點(diǎn)的父節(jié)點(diǎn)設(shè)置為黑色
              setColor(y, BLACK);           // y節(jié)點(diǎn)的顏色設(shè)置為黑色
              setColor(parentOf(parentOf(x)), RED); // x.parent.parent設(shè)置為紅色
              x = parentOf(parentOf(x)); // x == x.parent.parent ,進(jìn)行遍歷致开。
         } else {
               //x的父節(jié)點(diǎn)的兄弟節(jié)點(diǎn)是黑色或者缺少的
               if (x == rightOf(parentOf(x))) {   //判斷x節(jié)點(diǎn)是否為父節(jié)點(diǎn)的右孩子
                    x = parentOf(x);     //x == 父節(jié)點(diǎn)
                    rotateLeft(x);    //左旋轉(zhuǎn)操作
               }
               //x節(jié)點(diǎn)是其父的左孩子
               setColor(parentOf(x), BLACK);
               setColor(parentOf(parentOf(x)), RED);  //上面兩句將x.parent 和x.parent.parent的顏色做調(diào)換
               rotateRight(parentOf(parentOf(x)));   //進(jìn)行右旋轉(zhuǎn)
            }
        } else {
            Entry<K,V> y = leftOf(parentOf(parentOf(x)));  //y 是x 節(jié)點(diǎn)的祖父節(jié)點(diǎn)的左孩子
            if (colorOf(y) == RED) {    //判斷顏色
                setColor(parentOf(x), BLACK);    //父節(jié)點(diǎn)設(shè)置為黑色
                setColor(y, BLACK);         //父節(jié)點(diǎn)的兄弟節(jié)點(diǎn)設(shè)置為黑色
                setColor(parentOf(parentOf(x)), RED);   //祖父節(jié)點(diǎn)設(shè)置為紅色
                x = parentOf(parentOf(x));   //將祖父節(jié)點(diǎn)作為新插入的節(jié)點(diǎn),遍歷調(diào)整
            } else {
                if (x == leftOf(parentOf(x))) {     //x 是其父親的左孩子
                    x = parentOf(x);
                    rotateRight(x);    //以父節(jié)點(diǎn)為旋轉(zhuǎn)點(diǎn)萎馅,進(jìn)行右旋操作
                }
                setColor(parentOf(x), BLACK);    //父節(jié)點(diǎn)為設(shè)置為黑色
                setColor(parentOf(parentOf(x)), RED);  //祖父節(jié)點(diǎn)設(shè)置為紅色
                rotateLeft(parentOf(parentOf(x)));  //以父節(jié)點(diǎn)為旋轉(zhuǎn)點(diǎn)双戳,進(jìn)行左旋操作
            }
        }
    }
    root.color = BLACK; //通過節(jié)點(diǎn)位置的調(diào)整,最終將紅色的節(jié)點(diǎn)條調(diào)換到了根節(jié)點(diǎn)的位置糜芳,根節(jié)點(diǎn)重新設(shè)置為黑色
}

紅黑樹是一個(gè)更高效的檢索二叉樹飒货,有如下特點(diǎn):

  1. 每個(gè)節(jié)點(diǎn)只能是紅色或者黑色
  2. 根節(jié)點(diǎn)永遠(yuǎn)是黑色的
  3. 所有的葉子的子節(jié)點(diǎn)都是空節(jié)點(diǎn)魄衅,并且都是黑色的
  4. 每個(gè)紅色節(jié)點(diǎn)的兩個(gè)子節(jié)點(diǎn)都是黑色的(不會(huì)有兩個(gè)連續(xù)的紅色節(jié)點(diǎn))
  5. 從任一個(gè)節(jié)點(diǎn)到其子樹中每個(gè)葉子節(jié)點(diǎn)的路徑都包含相同數(shù)量的黑色節(jié)點(diǎn)(葉子節(jié)點(diǎn)到根節(jié)點(diǎn)的黑色節(jié)點(diǎn)數(shù)量每條路徑都相同)

上面的代碼,詳細(xì)的標(biāo)注了每條語句的作用塘辅,但是我相信晃虫,如果你沒有一定的功力,即使注釋已經(jīng)很詳細(xì)了扣墩,你也會(huì)是一臉懵逼 哲银,二臉懵逼,全腦懵逼中呻惕,下面配合圖片來梳理一下代碼所表示的含義:
當(dāng)一個(gè)默認(rèn)為紅色的節(jié)點(diǎn)插入樹中荆责,其實(shí)對應(yīng)的是7中可能發(fā)生的情況,分別進(jìn)行敘述:

  • 情形1:新插入的節(jié)點(diǎn)時(shí)紅黑樹的根節(jié)點(diǎn)蟆融,沒有父節(jié)點(diǎn)草巡,無需任何的操作,直接將顏色設(shè)置為黑色就可以了

  • 情形2:新節(jié)點(diǎn)的父節(jié)點(diǎn)顏色是黑色的型酥,新插入的節(jié)點(diǎn)是紅色的山憨。也無需任何的操作。因?yàn)樾鹿?jié)點(diǎn)的插入并沒有影響到紅黑書的特點(diǎn)

  • 情形3:新節(jié)點(diǎn)的父節(jié)點(diǎn)(左孩子節(jié)點(diǎn))顏色是紅色的弥喉,而父節(jié)點(diǎn)的兄弟節(jié)點(diǎn)顏色也是紅色的郁竟。那么情況就出現(xiàn)了,此時(shí)插入的節(jié)點(diǎn)就違反了紅黑樹的特點(diǎn)4 由境,需要對紅黑樹進(jìn)行調(diào)整棚亩。 操作看下圖:


    父節(jié)點(diǎn)和父親的兄弟節(jié)點(diǎn)都為紅色.png

    調(diào)整操作如上圖,將父節(jié)點(diǎn)和父節(jié)點(diǎn)的兄弟節(jié)點(diǎn)虏杰,都修改為紅色讥蟆,然后將祖父節(jié)點(diǎn)修改為紅色,因?yàn)樾薷牧俗娓腹?jié)點(diǎn)的顏色纺阔,祖父節(jié)點(diǎn)可能會(huì)發(fā)生顏色的沖突瘸彤,所以將新插入的節(jié)點(diǎn)修改為祖父節(jié)點(diǎn),在進(jìn)行調(diào)整笛钝。

  • 情形4:父節(jié)點(diǎn)(左孩子節(jié)點(diǎn))的顏色為紅色质况,父節(jié)點(diǎn)的兄弟節(jié)點(diǎn)的顏色為黑色或者為null,新插入的節(jié)點(diǎn)為父節(jié)點(diǎn)的右孩子節(jié)點(diǎn)玻靡。如下圖:


    父節(jié)點(diǎn)為紅色结榄,父節(jié)點(diǎn)的兄弟節(jié)點(diǎn)為黑色或者null.png

    此時(shí)以父節(jié)點(diǎn)為旋轉(zhuǎn)點(diǎn),就新插入的節(jié)點(diǎn)進(jìn)行左旋操作囤捻。便變成了情形5對應(yīng)的情況臼朗,將執(zhí)行情形5的操作

  • 情形5:父節(jié)點(diǎn)(左孩子節(jié)點(diǎn))的顏色為紅色,父節(jié)點(diǎn)的兄弟節(jié)點(diǎn)顏色為黑色或者null,新插入節(jié)點(diǎn)為父親的左孩子節(jié)點(diǎn)。如下圖:


    父節(jié)點(diǎn)為紅色依溯,新節(jié)點(diǎn)為左孩子節(jié)點(diǎn).png
  • 情形6 和情形7的操作與情形4和情形5的操作相同老厌,它們之前的區(qū)別是父節(jié)點(diǎn)為有孩子節(jié)點(diǎn),再次不再贅述黎炉。

總結(jié)
關(guān)于紅黑樹的節(jié)點(diǎn)插入操作枝秤,首先是改變新節(jié)點(diǎn),新節(jié)點(diǎn)的父節(jié)點(diǎn)慷嗜,祖父節(jié)點(diǎn)淀弹,和新節(jié)點(diǎn)的顏色,能在當(dāng)前分支通過節(jié)點(diǎn)的旋轉(zhuǎn)改變的庆械,則通過此種操作薇溃,來滿足紅黑書的特點(diǎn)。如果當(dāng)前相關(guān)節(jié)點(diǎn)的旋轉(zhuǎn)解決不了紅黑樹的沖突缭乘,則通過將紅色的節(jié)點(diǎn)移動(dòng)到根節(jié)點(diǎn)解決沐序,最后在將根節(jié)點(diǎn)設(shè)置為黑色

remove()
public V remove(Object key) {
    Entry<K,V> p = getEntry(key);  //根據(jù)key查找節(jié)點(diǎn),并返回該節(jié)點(diǎn)
    if (p == null)
        return null;

    V oldValue = p.value;    //獲取key對應(yīng)的值
    deleteEntry(p);     //刪除節(jié)點(diǎn)
    return oldValue;   //返回key對應(yīng)的值
}

final Entry<K,V> getEntry(Object key) {
    //根據(jù)鍵尋找節(jié)點(diǎn)堕绩,有非為兩種方式策幼,如果定制了比較器,采用定制排序方式奴紧,否則使用自然排序
    if (comparator != null) 
        return getEntryUsingComparator(key); //循環(huán)遍歷樹特姐,尋找和key相等的節(jié)點(diǎn)
    if (key == null)
        throw new NullPointerException();
    Comparable<? super K> k = (Comparable<? super K>) key;
    Entry<K,V> p = root;
    while (p != null) {  //循環(huán)遍歷樹,尋找和key相等的節(jié)點(diǎn)
        int cmp = k.compareTo(p.key);
        if (cmp < 0)
            p = p.left;
        else if (cmp > 0)
            p = p.right;
        else
            return p;
    }
    return null;
}
//刪除節(jié)點(diǎn)
private void deleteEntry(Entry<K,V> p) {
    modCount++;  //記錄修改的次數(shù)
    size--;   //數(shù)量減1

    //當(dāng)前節(jié)點(diǎn)的兩個(gè)孩子都不為空
    if (p.left != null && p.right != null) {
        //尋找繼承者黍氮,繼承者為當(dāng)前節(jié)點(diǎn)的右孩子節(jié)點(diǎn)或者右孩子節(jié)點(diǎn)的最小左孩子
        Entry<K,V> s = successor(p);
        p.key = s.key;     //key - value  的替換 唐含,并沒有替換顏色
        p.value = s.value;
        p = s;  //指向繼承者
    } // p has 2 children

    // Start fixup at replacement node, if it exists.
    //開始修復(fù)樹結(jié)構(gòu),繼承者的左孩子不為空沫浆,返回左孩子捷枯,否則返回右孩子
    //不可能存在左右兩個(gè)孩子都存在的情況,successor尋找的就是最小節(jié)點(diǎn)专执,它的左孩子節(jié)點(diǎn)為null
    Entry<K,V> replacement = (p.left != null ? p.left : p.right);

    if (replacement != null) {
        // Link replacement to parent
        //已經(jīng)被選為繼承者淮捆,當(dāng)前擁有的一切放棄,所以將孩子交給爺爺撫養(yǎng)
        replacement.parent = p.parent;
        //p節(jié)點(diǎn)沒有父節(jié)點(diǎn)他炊,則指向根節(jié)點(diǎn)
        if (p.parent == null)
           root = replacement;
        //如果p為左孩子,如果p為左孩子已艰,則將p.parent.left = p.left
        else if (p == p.parent.left)
            p.parent.left  = replacement;
        else
            p.parent.right = replacement;

        //刪除p節(jié)點(diǎn)到左右分支痊末,和父節(jié)點(diǎn)的引用
        p.left = p.right = p.parent = null;

        // Fix replacement
        if (p.color == BLACK)
            //恢復(fù)顏色分配
            fixAfterDeletion(replacement);
    } else if (p.parent == null) { // return if we are the only node.
        //紅黑書中父節(jié)點(diǎn)為空的只能是根節(jié)點(diǎn)。
        root = 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;
        }
    }
}
private void fixAfterDeletion(Entry<K,V> x) {
    //不是根節(jié)點(diǎn)哩掺,顏色為黑色凿叠,調(diào)整結(jié)構(gòu)
    while (x != root && colorOf(x) == BLACK) {

        //判斷x是否為左孩子
        if (x == leftOf(parentOf(x))) {
            //x的兄弟節(jié)點(diǎn)
            Entry<K,V> sib = rightOf(parentOf(x));
            //若兄弟節(jié)點(diǎn)是紅色
            if (colorOf(sib) == RED) {
                setColor(sib, BLACK);   //設(shè)置兄弟節(jié)點(diǎn)變?yōu)楹谏?                setColor(parentOf(x), RED);  //父節(jié)點(diǎn)設(shè)置為紅色
                rotateLeft(parentOf(x));   //左旋父節(jié)點(diǎn)
                sib = rightOf(parentOf(x)); //重新設(shè)置x的兄弟節(jié)點(diǎn)
            }

            if (colorOf(leftOf(sib))  == BLACK &&
                colorOf(rightOf(sib)) == BLACK) {
                setColor(sib, RED); //兄弟節(jié)點(diǎn)的兩個(gè)孩子都是黑色的重新設(shè)置兄弟節(jié)點(diǎn)的顏色,修改為紅色
                x = parentOf(x);   //將x定位到父節(jié)點(diǎn)
            } else {
                if (colorOf(rightOf(sib)) == BLACK) {   //兄弟節(jié)點(diǎn)的右孩子是黑色的,左孩子是紅色的
                    setColor(leftOf(sib), BLACK);  //設(shè)置左孩子節(jié)點(diǎn)為黑色
                    setColor(sib, RED); //兄弟節(jié)點(diǎn)為紅色
                    rotateRight(sib);   //右旋
                    sib = rightOf(parentOf(x));  //右旋后重新設(shè)置兄弟節(jié)點(diǎn)
                }
                setColor(sib, colorOf(parentOf(x)));  //兄弟節(jié)點(diǎn)顏色設(shè)置和父節(jié)點(diǎn)的顏色相同
                setColor(parentOf(x), BLACK);   //父節(jié)點(diǎn)設(shè)置為黑色
                setColor(rightOf(sib), BLACK);  //將兄弟節(jié)點(diǎn)的有孩子設(shè)置為黑色
                rotateLeft(parentOf(x));   //左旋
                x = root;  //設(shè)置x為根節(jié)點(diǎn)
            }
        } else { // symmetric
            //x為父節(jié)點(diǎn)的右節(jié)點(diǎn)盒件,參考上面的操作
            Entry<K,V> sib = leftOf(parentOf(x));

            if (colorOf(sib) == RED) {
                setColor(sib, BLACK);
                setColor(parentOf(x), RED);
                rotateRight(parentOf(x));
                sib = leftOf(parentOf(x));
            }

            if (colorOf(rightOf(sib)) == BLACK &&colorOf(leftOf(sib)) == BLACK) {
                    setColor(sib, RED);
                    x = parentOf(x);
            } else {
                if (colorOf(leftOf(sib)) == BLACK) {
                    setColor(rightOf(sib), BLACK);
                    setColor(sib, RED);
                    rotateLeft(sib);
                    sib = leftOf(parentOf(x));
                }
                setColor(sib, colorOf(parentOf(x)));
                setColor(parentOf(x), BLACK);
                setColor(leftOf(sib), BLACK);
                rotateRight(parentOf(x));
                x = root;
            }
        }
    }

    setColor(x, BLACK);
}

刪除紅黑樹的操作比插入操作要稍微麻煩一點(diǎn)蹬碧,分為兩步:

  • 以排序二叉樹的方法刪除指定節(jié)點(diǎn)。刪除的節(jié)點(diǎn)存在三種情況:
    • 被刪除節(jié)點(diǎn)炒刁,沒有左右孩子節(jié)點(diǎn)恩沽,直接刪除即可
    • 被刪除節(jié)點(diǎn),有一個(gè)孩子節(jié)點(diǎn)翔始,那么讓它的孩子節(jié)點(diǎn)指向它的父節(jié)點(diǎn)即可
    • 本刪除的節(jié)點(diǎn)罗心,有兩個(gè)非空的孩子節(jié)點(diǎn),那么需要找到該節(jié)點(diǎn)的前驅(qū)或者后繼節(jié)點(diǎn)城瞎,更換元素值渤闷,在將前驅(qū)或者后繼節(jié)點(diǎn)刪除(任意一個(gè)節(jié)點(diǎn)的前驅(qū)或者后繼都必定至多有一個(gè)非空的子節(jié)點(diǎn),可以按照前面的兩種情形進(jìn)行操作)
  • 進(jìn)行顏色的調(diào)換和樹的旋轉(zhuǎn)脖镀,滿足紅黑樹的特征

下面來分情形討論一下可能發(fā)生的情況:

  • 情形1:被刪除的節(jié)點(diǎn)為根節(jié)點(diǎn)或者顏色為空色飒箭,此時(shí)刪除該節(jié)點(diǎn)不影響紅黑樹的特點(diǎn)。無需操作
  • 情形2:被刪除節(jié)點(diǎn)為黑色蜒灰,兄弟節(jié)點(diǎn)為紅色弦蹂,如下圖:


    兄弟節(jié)點(diǎn)為紅色.png

    若刪除上圖中的x節(jié)點(diǎn),將缺少一個(gè)黑節(jié)點(diǎn)卷员,與紅黑樹的性質(zhì)沖突盈匾,所以修改sib顏色為黑色,設(shè)置p節(jié)點(diǎn)為紅色毕骡,并進(jìn)行左旋操作削饵。在進(jìn)行后續(xù)的處理。

  • 情形3:被刪除節(jié)點(diǎn)為黑色未巫,x節(jié)點(diǎn)的兄弟節(jié)點(diǎn)的子節(jié)點(diǎn)都是黑色窿撬,如下圖:


    x節(jié)點(diǎn)的兄弟節(jié)點(diǎn)的子節(jié)點(diǎn)都是黑色.png

    x節(jié)點(diǎn)是黑色的,兄弟節(jié)點(diǎn)(黑色的)的子節(jié)點(diǎn)也是黑色的叙凡,p節(jié)點(diǎn)的顏色無法確定劈伴,有可能是紅色的,也有可能是黑色的握爷。如果是紅色的直接設(shè)置為黑色即可跛璧,如果為黑色的,則需要將x定位的p節(jié)點(diǎn)新啼,在進(jìn)行處理追城。

  • 情形4:被刪除節(jié)點(diǎn)為黑色,x的兄弟節(jié)點(diǎn)的右自子節(jié)點(diǎn)為黑色燥撞。如下圖:


    x兄弟節(jié)點(diǎn)的右子節(jié)點(diǎn)為黑色.png

    情形4的調(diào)整為了轉(zhuǎn)變成情形5的情況座柱,來進(jìn)行處理

  • 情形5:被刪除節(jié)點(diǎn)為黑色迷帜,x的兄弟節(jié)點(diǎn)右子節(jié)點(diǎn)為紅色。如下圖:


    x兄弟節(jié)點(diǎn)的右子節(jié)點(diǎn)為紅色色洞,左子節(jié)點(diǎn)的顏色未知.png

    sib的左子節(jié)點(diǎn)的顏色不確定戏锹,可能是紅色也可能是黑色,但是對它并沒有什么影響火诸,因?yàn)樽儞Q前后它的上層分支的黑色節(jié)點(diǎn)數(shù)并沒有改變锦针。

上面的情形只是針對刪除的節(jié)點(diǎn)是左孩子的情況,進(jìn)行的分析惭蹂,被刪除的節(jié)點(diǎn)也可能是右分支伞插。情況完全相同只不過左右順序發(fā)生了顛倒,不再進(jìn)行復(fù)述盾碗。

至此TreeMap中實(shí)現(xiàn)的最重要已經(jīng)說完了媚污。下面簡單說一下一些方法的作用

  • firstEntry() 返回Map中最小的key
  • higherEntry(Object key ) 返回該Map中位于key后一位的key-value
  • lowerEntry(Object key ) 返回該Map中唯一key前一位的key-value
  • tailMap(Object key , boolean inclusive) 返回該Map的子Map


少年聽雨歌樓上,紅燭昏羅帳廷雅。  
壯年聽雨客舟中耗美,江闊云低,斷雁叫西風(fēng)航缀。
感謝支持商架!
                                        ---起個(gè)名忒難

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市芥玉,隨后出現(xiàn)的幾起案子蛇摸,更是在濱河造成了極大的恐慌,老刑警劉巖灿巧,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赶袄,死亡現(xiàn)場離奇詭異,居然都是意外死亡抠藕,警方通過查閱死者的電腦和手機(jī)饿肺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來盾似,“玉大人敬辣,你說我怎么就攤上這事×阍海” “怎么了溉跃?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長告抄。 經(jīng)常有香客問我撰茎,道長,這世上最難降的妖魔是什么玄妈? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任乾吻,我火速辦了婚禮,結(jié)果婚禮上拟蜻,老公的妹妹穿的比我還像新娘绎签。我一直安慰自己,他們只是感情好酝锅,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布诡必。 她就那樣靜靜地躺著,像睡著了一般搔扁。 火紅的嫁衣襯著肌膚如雪爸舒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天稿蹲,我揣著相機(jī)與錄音扭勉,去河邊找鬼。 笑死苛聘,一個(gè)胖子當(dāng)著我的面吹牛涂炎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播设哗,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼唱捣,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了网梢?” 一聲冷哼從身側(cè)響起震缭,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎战虏,沒想到半個(gè)月后拣宰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡活烙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年徐裸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片啸盏。...
    茶點(diǎn)故事閱讀 40,498評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡重贺,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出回懦,到底是詐尸還是另有隱情气笙,我是刑警寧澤,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布怯晕,位于F島的核電站潜圃,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏舟茶。R本人自食惡果不足惜谭期,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一堵第、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧隧出,春花似錦踏志、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至凄诞,卻和暖如春圆雁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背帆谍。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工伪朽, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人汛蝙。 一個(gè)月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓驱负,卻偏偏與公主長得像,于是被迫代替她去往敵國和親患雇。 傳聞我的和親對象是個(gè)殘疾皇子跃脊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評論 2 359

推薦閱讀更多精彩內(nèi)容