深入淺出紅黑樹(Java TreeMap)


  實現原理:紅黑樹
什么是紅黑樹(數據來自百度百科):
    紅黑樹(自平衡二叉樹)是每個節點都帶有顏色屬性的二叉查找樹,顏色或紅色或黑色。在二叉查找樹強制一般要求以外,對於任何有效的紅黑樹我們增加了如下的額外要求:
性質1. 節點是紅色或黑色。
性質2. 根節點是黑色。
性質3. 每個葉節點(NIL節點,空節點)是黑色的。
性質4. 每個紅色節點的兩個子節點都是黑色。(從每個葉子到根的所有路徑上不能有兩個連續的紅色節點)
性質5. 從任一節點到其每個葉子的所有路徑都包含相同數目的黑色節點。
    這些約束強制了紅黑樹的關鍵性質:  從根到葉子的最長的可能路徑不多於最短的可能路徑的兩倍長。結果是這個樹大致上是平衡的。因為操作比如插入、刪除和查找某個值的最壞情況時間都要求與樹的高度成比例,這個在高度上的理論上限允許紅黑樹在最壞情況下都是高效的,而不同於普通的二叉查找樹。
 
思路:
1.創建空比較器,空Entry引用,entry個數默認為0
 //比較器,因為TreeMap是有序的,通過comparator接口我們可以對TreeMap的內部排序進行精密的控制
 private final Comparator<? super K> comparator;
 //TreeMap紅-黑節點,為TreeMap的內部類
 private transient Entry<K,V> root = null;
    
 private transient int size = 0;
 
 private static final boolean RED   = false;
 
 private static final boolean BLACK = true;

2.四個構造器,全部使用比較器,比較器基本容器/單位?

 public TreeMap() {
    comparator = null;
 }
 public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
 }
 public TreeMap(Map<? extends K, ? extends V> m) {
        comparator = null;
        putAll(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) {
        }
}
 3.Entry類

 

static final class Entry<K,V> implements Map.Entry<K,V> {
        K key;
        V value;
        Entry<K,V> left;
        Entry<K,V> right;
        Entry<K,V> parent;
        boolean color = BLACK;
    
        /**
         * Make a new cell with given key, value, and parent, and with
         * {@code null} child links, and BLACK color.
         */
        Entry(K key, V value, Entry<K,V> parent) {
            this.key = key;
            this.value = value;
            this.parent = parent;
        }

    }
4.put方法
    兩個點:
        構建一個有序二叉樹
        平衡二叉樹
 
1、以根節點為初始節點進行檢索。
2、與當前節點進行比對,若新增節點值較大,則以當前節點的右子節點作為新的當前節點。
   否則以當前節點的左子節點作為新的當前節點。
3、循環遞歸2步驟知道檢索出合適的葉子節點為止。
4、將新增節點與3步驟中找到的節點進行比對,如果新增節點較大,則添加為右子節點;否則添加為左子節點。
 
    1.root為空 
            新增根節點  
            return
    2.root不為空,比較器不為空,
            在root左右插入節點后 繼續
            或者替換root值后     return
    3.root不為空,比較器為空
            key為空,拋出空指針
            在root左右插入節點后 繼續
            或者替換root值后     return

 

public V put(K key, V value) {
        //用t表示二叉樹的當前節點 
        Entry<K,V> t = root;
        //t為null表示一個空樹,即TreeMap中沒有任何元素,直接插入
        if (t == null) {
            //比較key值,個人覺得這句代碼沒有任何意義,空樹還需要比較、排序?
            compare(key, key); // type (and possibly null) check
            //將新的key-value鍵值對創建為一個Entry節點,並將該節點賦予給root
            root = new Entry<>(key, value, null);
            //容器的size = 1,表示TreeMap集合中存在一個元素 
            size = 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;
                else //cmp返回值等於0,表示兩個key值相等,則新值覆蓋舊值,並返回新值
                    return t.setValue(value);
            } while (t != null);
        }
        else { //如果cpr為空,則采用默認的排序算法進行創建TreeMap集合
            //key不能為空
            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);
        }
        //將新增節點當做parent的子節點
        Entry<K,V> e = new Entry<>(key, value, parent);
       //如果新增節點的key小於parent的key,則當做左子節點  
        if (cmp < 0)
            parent.left = e;
        else    //如果新增節點的key大於parent的key,則當做右子節點
            parent.right = e;
    
         /**
         *  上面已經完成了排序二叉樹的的構建,將新增節點插入該樹中的合適位置             
         *  下面fixAfterInsertion()方法就是對這棵樹進行調整、平衡,具體過程參考上面的五種情況                  */ 
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }
 上面的do{}代碼僅僅是排序二叉樹的核心算法,真正的實現紅黑樹依靠的是fixAfterInsertion(e)方法 
    紅黑樹是一棵平衡排序二叉樹,普通的排序二叉樹可能會出現失衡的情況,而fixAfterInsertion就是調       整結構實現紅黑樹 : 左旋右旋着色三個基本操作 
/** From CLR */
    private void fixAfterInsertion(Entry<K,V> x) {
        //新節點着色
        x.color = RED;
        //循環到新節點 不為空且不是根且根的節點不是紅色
        while (x != null && x != root && x.parent.color == RED) {
         
            //如果X的父節點(假設為:P)是其父節點的父節點(假設為:G)的左節點
            if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
                //獲取父節點的父節點的右節點y
                Entry<K,V> y = rightOf(parentOf(parentOf(x)));
       //         G
       //       /   \
       //     p       y
       //    / 
       //   x       
                if (colorOf(y) == RED) {
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    // ?????
                    x = parentOf(parentOf(x));
                } else {
                    //右旋
                    if (x == rightOf(parentOf(x))) {
                        x = parentOf(x);
                        //以X的父節點的父節點(G)為中心左旋轉
                        rotateLeft(x);
                    }
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    rotateRight(parentOf(parentOf(x)));
                }
            } else {
                //如果X的父節點(假設為:P)是其父節點的父節點(假設為:G)的右節點
                //                G
                //              /   \
                //             y     P
                //                    \
                //                     x
                //
                
                Entry<K,V> y = leftOf(parentOf(parentOf(x)));
                if (colorOf(y) == RED) {
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                    //左旋
                    if (x == leftOf(parentOf(x))) {
                        x = parentOf(x);
                        //以X的父節點的父節點(G)為中心右旋轉
                        rotateRight(x);
                    }
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    rotateLeft(parentOf(parentOf(x)));
                }
            }
        }
        root.color = BLACK;
    }

右旋方法(左旋類似)

/** From CLR */
    private void rotateRight(Entry<K,V> p) {
        if (p != null) {
            //    p
            //   /
            //   L
            //將L設置為P的左子樹
            Entry<K,V> l = p.left;
            //將L的右子樹設置為P的左子樹
            p.left = l.right;
             //若L的右子樹不為空,則將P設置L的右子樹的父節點
            if (l.right != null) l.right.parent = p;
            //將P的父節點設置為L的父節點
            l.parent = p.parent;
             //如果P的父節點為空,則將L設置根節點 
            if (p.parent == null)
                root = l;
            //若P為其父節點的右子樹,則將L設置為P的父節點的右子樹
            else if (p.parent.right == p)
                p.parent.right = l;
            //否則將L設置為P的父節點的左子樹
            else p.parent.left = l;
            //將P設置為L的右子樹
            l.right = p;
            //將L設置為P的父節點
            p.parent = l;
        }
    }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM