Java HashMap類源碼解析(續)-TreeNode

  因爲TreeNode自己是紅黑樹的實現,因此在分析TreeNode的以前我仍是摸了一篇算法導論裏紅黑樹的讀書筆記:算法導論——紅黑樹,從僞代碼行數也能夠看出完整的紅黑樹的插入和刪除操做代碼是很長的,下面源碼分析部分的行數就更多了,因此所謂手寫紅黑樹畫個圖分析下邏輯還行,手寫代碼估計要寫死(滑稽)html

  TreeNode從JDK8開始引入,做用是當HashMap解決衝突的鏈表長度超過了8時,生成一個紅黑樹來加速查找和插入,這裏樹結構存在並不影響自己依然存在線性鏈表結構,意思是Node.next這個屬性依然有效,因此說樹替換了線性鏈表依然仍是鏈表法解決衝突,只不過鏈表的實現策略換了。當結點由於移除或分裂操做少於6個時,消除樹結構。雖然生產樹以後能加快查找插入和刪除,可是創建和消除樹自己是存在消耗的,因此在兩個臨界值之間來回插入和刪除會致使開銷快速增長。HashMap的源碼分析見:Java HashMap類源碼解析node

  紅黑樹是基於二叉搜索樹擴展而來,對於TreeNode來講排序的依據是結點的hash值,若相等而後比較key值,若key不能比較或是相等則根據hash值,左兒子的hash值小於等於父親,右兒子的hash值大於父親。TreeNode 保有紅黑樹的性質:算法

  1. 每一個結點都是紅色的或者是黑色的
  2. 根結點是黑色的
  3. 每一個葉結點NIL是黑色的,可是一般咱們不考慮NIL葉結點。
  4. 若是一個結點是紅色的,它的兩個子結點都是黑色的
  5. 每一個結點到其餘全部後代葉結點的簡單路徑上,均包含相同數目的黑色結點,這個屬性被稱爲黑高,記做bh(x)

  先來看一下TreeNode擴展的內部屬性數組

        TreeNode<K,V> parent;  //父親結點

        TreeNode<K,V> left;    //左兒子

        TreeNode<K,V> right;   //右兒子

        TreeNode<K,V> prev;    //前方結點

        boolean red;//是不是紅色

根據他的構造函數向上追溯TreeNode<K,V>繼承了LinkedHashMap.Entry<K,V>然後者又繼承了HashMap.Node<K,V>。因此TreeNode依然保有Node的屬性,同時因爲添加了prev這個前驅指針使得鏈表變爲了雙向的。app

    TreeNode(int hash, K key, V val, Node<K,V> next) {
       super(hash, key, val, next);
    }

    static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }

下面這個方法能夠返回根結點,實現很簡單就是不斷從一個結點檢查parent是否爲nullide

        /**
         * Returns root of tree containing this node.返回根結點
         */
        final TreeNode<K,V> root() {
            for (TreeNode<K,V> r = this, p;;) {
                if ((p = r.parent) == null)
                    return r;
                r = p;//不斷檢查parent是否爲null,爲null的是根結點
            }
        }

moveRootToFront這個方法的做用是確保根結點被保存在了table數組上面,若是不是的話,就將root從鏈表中取出,將他放到數組對應的位置上,本來在數組上的結點連接到root的後面。這裏最後調用了斷言方法checkInvariants,做用是遞歸檢查整棵樹是否符合紅黑樹的性質,若檢查不符會返回false致使moveRootToFront拋出錯誤。函數

        /**
         * Ensures that the given root is the first node of its bin.
         * 確保給出的根結點是箱中的第一個結點也就是直接位於table上,本來的第一個結點若不是root則將root從鏈表中剪下放到第一個結點的前方
         */
        static <K,V> void moveRootToFront(Node<K,V>[] tab, TreeNode<K,V> root) {
            int n;
            if (root != null && tab != null && (n = tab.length) > 0) {
                int index = (n - 1) & root.hash;//根據root的hash值快速定位下標
                TreeNode<K,V> first = (TreeNode<K,V>)tab[index];//取出table[index]中的第一個結點
                if (root != first) {//root不是第一個結點
                    Node<K,V> rn;
                    tab[index] = root;//root放到table[index]位置
                    TreeNode<K,V> rp = root.prev;//rp=root的前一個結點
                    if ((rn = root.next) != null)//rn=root的後一個結點
                        ((TreeNode<K,V>)rn).prev = rp;//rn的前指針指向root的前一個結點
                    if (rp != null)
                        rp.next = rn;//rp的後指針指向root的後一個結點
                    if (first != null)
                        first.prev = root;//將本來的first放到root的後面
                    root.next = first;
                    root.prev = null;
                }
                assert checkInvariants(root);//assert後面的表達式爲false時會拋出錯誤
            }
        }

        /**
         * Recursive invariant check
         * 從root開始遞歸檢查紅黑樹的性質,僅在檢查root是否落在table上時調用
         */
        static <K,V> boolean checkInvariants(TreeNode<K,V> t) {
            TreeNode<K,V> tp = t.parent, tl = t.left, tr = t.right,
                tb = t.prev, tn = (TreeNode<K,V>)t.next;
            if (tb != null && tb.next != t)
                return false;//t的前一個結點的後續應爲t
            if (tn != null && tn.prev != t)
                return false;//t的後一個結點的前驅應爲t
            if (tp != null && t != tp.left && t != tp.right)
                return false;//t由於t父親的左兒子或右兒子
            if (tl != null && (tl.parent != t || tl.hash > t.hash))
                return false;//t的左兒子的hash值應小於t,父親應爲t
            if (tr != null && (tr.parent != t || tr.hash < t.hash))
                return false;//t的右兒子的hash值應大於t,父親應爲t
            if (t.red && tl != null && tl.red && tr != null && tr.red)
                return false;//t和t的兒子不能同時是紅色
            if (tl != null && !checkInvariants(tl))
                return false;//遞歸檢查t的左兒子
            if (tr != null && !checkInvariants(tr))
                return false;//遞歸檢查t的右兒子
            return true;
        }
View Code

getTreeNode這個方法在HashMap中被屢次使用,左右是尋找某個結點所在的樹中是否有hash和key值符合的結點。咱們能夠看到這個方法必定會確保最後調用的是root.find(),也就是說find方法調用時this必定是根結點。因此不管最初調用getTreeNode的結點在樹中處於什麼位置,最後都會從根結點開始尋找,因爲紅黑樹是相對平衡的二叉搜索樹,因此能夠認爲搜索時間相比於鏈表從O(n)降低到了O(lgn)源碼分析

        /**
         * Calls find for root node.從根結點尋找h和k符合的結點
         */
        final TreeNode<K,V> getTreeNode(int h, Object k) {
            return ((parent != null) ? root() : this).find(h, k, null);
        }

        /**
         * Finds the node starting at root p with the given hash and key.
         * The kc argument caches comparableClassFor(key) upon first use
         * comparing keys.
         * 從根結點p開始根據hash和key值尋找指定的結點。kc是key的class
         */
        final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
            TreeNode<K,V> p = this;//該方法調用時this是根結點
            do {
                int ph, dir; K pk;
                TreeNode<K,V> pl = p.left, pr = p.right, q;
                if ((ph = p.hash) > h)
                    p = pl;//p.hash>參數hash時,移向左子樹
                else if (ph < h)
                    p = pr;//p.hash<參數hash時,移向右子樹
                else if ((pk = p.key) == k || (k != null && k.equals(pk)))
                    return p;//p.hash=參數hash,且p.key與參數key相等找到指定結點並返回
                else if (pl == null)//若hash相等但key不等,向左右子樹非空的一側移動
                    p = pr;
                else if (pr == null)
                    p = pl;
                else if ((kc != null ||
                          (kc = comparableClassFor(k)) != null) &&//kc是不是一個可比較的類
                         (dir = compareComparables(kc, k, pk)) != 0)//比較k和p.key
                    p = (dir < 0) ? pl : pr;//k<p.key向左子樹移動不然向右子樹移動
                else if ((q = pr.find(h, k, kc)) != null)//這裏開始的條件僅當輸入k=null的時候纔會進入,先檢查右子樹再檢查左子樹
                    return q;
                else
                    p = pl;
            } while (p != null);
            return null;
        }
View Code

下面這個treeify就是根據鏈表生成樹了,遍歷鏈表獲取結點,一個個插入到紅黑樹中,每次插入從根開始根據hash值尋找到葉結點位置進行插入,插入一個結點後調用一次balanceInsertion(root, x)檢查x位置的紅黑樹性質是否須要修復。tieBreakOrder(k, pk)是在插入結點的key值k和父結點的key值pk沒法比較出大小時,用於比較k和pk的hash值大小。關於紅黑樹性質的修復和保持稍後一塊兒討論。this

        final void treeify(Node<K,V>[] tab) {
            TreeNode<K,V> root = null;
            for (TreeNode<K,V> x = this, next; x != null; x = next) {
                next = (TreeNode<K,V>)x.next;//根據鏈表進行遍歷
                x.left = x.right = null;
                if (root == null) {
                    x.parent = null;
                    x.red = false;//根結點必定是黑色的
                    root = x;
                }
                else {
                    K k = x.key;
                    int h = x.hash;
                    Class<?> kc = null;
                    for (TreeNode<K,V> p = root;;) {
                        int dir, ph;
                        K pk = p.key;
                        if ((ph = p.hash) > h)//p.hash>h則dir=-1,p.hash<h則dir=1
                            dir = -1;
                        else if (ph < h)
                            dir = 1;
                        else if ((kc == null &&
                                  (kc = comparableClassFor(k)) == null) ||
                                 (dir = compareComparables(kc, k, pk)) == 0)//k是不可比較的類或者k和p.key相等,kc==null這個條件只是爲了給kc初始化
                            dir = tieBreakOrder(k, pk);//比較k和p.k的hash值大小,k大dir=-1,p.key大則dir=1,pk是父親的hash,k是要插入結點的hash

                        TreeNode<K,V> xp = p;
                        if ((p = (dir <= 0) ? p.left : p.right) == null) {//要插入的位置已沒有子結點,則進行插入,不然沿着要插入的子樹位置繼續向下遍歷
                            x.parent = xp;
                            if (dir <= 0)
                                xp.left = x;//x的hash值小於等於p的hash值時嘗試插入到左子樹
                            else
                                xp.right = x;//x的hash值大於p的hash值時嘗試插入到右子樹
                            root = balanceInsertion(root, x);//插入後修復紅黑樹性質
                            break;
                        }
                    }
                }
            }
            moveRootToFront(tab, root);//確保當前的root是直接落在table數組上
        }

        static int tieBreakOrder(Object a, Object b) {
            int d;
            if (a == null || b == null ||
                (d = a.getClass().getName().
                 compareTo(b.getClass().getName())) == 0)//a和b的class相同或者一方是null
                d = (System.identityHashCode(a) <= System.identityHashCode(b) ?
                     -1 : 1);
            return d;//a的hashcode<=b的hashcode則返回-1,不然返回1,不能比較返回0
        }
View Code

untreeify的做用就是把樹轉爲鏈表,因爲replacementNode這個方法會生成新的Node,因此產生的新鏈表再也不具備樹的信息了,本來的TreeNode被gc了。spa

        final Node<K,V> untreeify(HashMap<K,V> map) {
            Node<K,V> hd = null, tl = null;//hd是頭部,tl是尾部
            for (Node<K,V> q = this; q != null; q = q.next) {
                Node<K,V> p = map.replacementNode(q, null);//根據q產生一個新的結點,next=null,hash key value和q相等
                if (tl == null)
                    hd = p;//第一個結點產生時頭部指向它
                else
                    tl.next = p;
                tl = p;
            }
            return hd;
        }
View Code

拆分這個方法只有在resize的時候調用,能夠對照線性鏈表擴展的狀況,做用是把樹拆成兩棵,一棵放到新擴展出來的數組高位去,一棵留在原來的位置,劃分的依據是擴展後新增的hash有效位是0仍是1,拆分的時候會破壞樹結構,因此先拆成兩個鏈表再調用treeify來組裝樹。

        /**
         * Splits nodes in a tree bin into lower and upper tree bins,
         * or untreeifies if now too small. Called only from resize;
         * see above discussion about split bits and indices.
         * 將樹從給定的結點分裂成低位和高位的兩棵樹,若新樹結點太少則轉爲線性鏈表。只有resize時會調用
         *
         * @param map the map
         * @param tab the table for recording bin heads存儲鏈表頭的hash表
         * @param index the index of the table being split須要分裂的表下標位置
         * @param bit the bit of hash to split on分裂時分到高位和低位的依據參數,實際使用時輸入的是擴展以前舊數組的大小
         */
        final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
            TreeNode<K,V> b = this;
            // Relink into lo and hi lists, preserving order
            TreeNode<K,V> loHead = null, loTail = null;//低位頭尾指針
            TreeNode<K,V> hiHead = null, hiTail = null;//高位頭尾指針
            int lc = 0, hc = 0;//低位和高位的結點個數統計
            for (TreeNode<K,V> e = b, next; e != null; e = next) {//e從this開始遍歷直到next爲null
                next = (TreeNode<K,V>)e.next;
                e.next = null;
                //這段決定了該結點被分到低位仍是高位,依據算式是e.hash mod bit,因爲bit是擴展前數組的大小,因此必定是2的指數次冪,因此bit必定只有一個高位是1其他全是0
                //這個算式實際是判斷e.hash新多出來的有效位是0仍是1,如果0則分去低位樹,是1則分去高位樹
                if ((e.hash & bit) == 0) {
                    if ((e.prev = loTail) == null)
                        loHead = e;
                    else
                        loTail.next = e;
                    loTail = e;
                    ++lc;
                }
                else {
                    if ((e.prev = hiTail) == null)
                        hiHead = e;
                    else
                        hiTail.next = e;
                    hiTail = e;
                    ++hc;
                }
            }

            if (loHead != null) {
                if (lc <= UNTREEIFY_THRESHOLD)
                    tab[index] = loHead.untreeify(map);//分裂後的低位樹結點太少轉爲線性鏈表
                else {
                    tab[index] = loHead;
                    if (hiHead != null) //若高位樹爲null則表明整棵樹全保留在了低位,樹沒有變化因此不用進行後面的treeify
                        loHead.treeify(tab);
                }
            }
            if (hiHead != null) {//這段與上面對於低位部分的分析相對應
                if (hc <= UNTREEIFY_THRESHOLD)
                    tab[index + bit] = hiHead.untreeify(map);
                else {
                    tab[index + bit] = hiHead;//高位所處的位置爲本來位置+舊數組的大小即bit
                    if (loHead != null)
                        hiHead.treeify(tab);
                }
            }
        }
View Code

 

下面開始要進入插入和刪除操做部分分析了,爲了便於說明把以前那篇幾張關鍵的圖貼過來方便與代碼進行對照

旋轉

如圖所示是基本的左旋和右旋操做,這部分對着圖看很容易理解

        //左旋操做,見圖中右向左,p是x,r是y
        static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
                                              TreeNode<K,V> p) {
            TreeNode<K,V> r, pp, rl;
            if (p != null && (r = p.right) != null) {//r是p的右兒子,也就是圖中的y
                if ((rl = p.right = r.left) != null)//r的左兒子β成爲p的右兒子
                    rl.parent = p;
                if ((pp = r.parent = p.parent) == null)//p的父親成爲r的父親
                    (root = r).red = false;//若p是根結點,r的顏色改黑色
                else if (pp.left == p)//r取代p本來的位置
                    pp.left = r;
                else
                    pp.right = r;
                r.left = p;//p成爲r的右兒子
                p.parent = r;
            }
            return root;
        }

        //右旋操做,見圖中左向右,p是y,l是x
        static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,
                                               TreeNode<K,V> p) {
            TreeNode<K,V> l, pp, lr;
            if (p != null && (l = p.left) != null) {//l是p的左兒子,即圖中x
                if ((lr = p.left = l.right) != null)//l的右兒子β成爲p的左兒子
                    lr.parent = p;
                if ((pp = l.parent = p.parent) == null)//p的父親成爲l的父親
                    (root = l).red = false;//若p是根結點,l的顏色改黑色
                else if (pp.right == p)//l取代p本來的位置
                    pp.right = l;
                else
                    pp.left = l;
                l.right = p;//p成爲l的右兒子
                p.parent = l;
            }
            return root;
        }
View Code

插入

插入分爲如圖所示的3中狀況,具體描述和處理方法見文章最前面的連接。

狀況1

插入操做涉及到兩段很長的代碼,首先是putTreeVal只要有h和k值符合的結點就不作插入,這裏k必須是==或者equals纔算是相等,返回找到的結點由調用的方法修改已有結點的value值,不然插入一個新結點並返回null。前面提到過,結點在樹中的排序按照hash值大小,再按照key的大小,最後比較key計算Hash的大小進行排列,對應方法中的查找邏輯。

        final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
                                       int h, K k, V v) {
            Class<?> kc = null;
            boolean searched = false;
            TreeNode<K,V> root = (parent != null) ? root() : this;//this.parent爲null表明已經是根結點,不然經過root()獲取根結點
            for (TreeNode<K,V> p = root;;) {
                int dir, ph; K pk;
                if ((ph = p.hash) > h)//p.hash>h時dir=-1,p.hash<h時dir=1
                    dir = -1;
                else if (ph < h)
                    dir = 1;
                else if ((pk = p.key) == k || (k != null && k.equals(pk)))
                    return p;//p.hash=h且p.key與k相等時,已存在k值對應的結點則返回
                else if ((kc == null &&
                          (kc = comparableClassFor(k)) == null) ||
                         (dir = compareComparables(kc, k, pk)) == 0) {///k是不可比較的類或者k和p.key經過compareTo比較相等
                    if (!searched) {
                        //這部分只會在在k和p.key經過compareTo比較相等時執行一次,若未能在在左右子樹中尋找到k==p.key或者k.equals(p.key)的狀況則下次不會再進入
                        TreeNode<K,V> q, ch;
                        searched = true;
                        if (((ch = p.left) != null &&
                             (q = ch.find(h, k, kc)) != null) ||
                            ((ch = p.right) != null &&
                             (q = ch.find(h, k, kc)) != null))
                            return q;//從p的左子樹或者右子樹中找到符合條件的結點則返回
                    }
                    dir = tieBreakOrder(k, pk);//比較k和p.key的hash值大小,-1表示k<p.key
                }
                //p.hash=hash可是key值不相等且p的左右子樹中也沒有找到符合的結點
                TreeNode<K,V> xp = p;
                if ((p = (dir <= 0) ? p.left : p.right) == null) {//找到了新增結點該插入的位置
                    Node<K,V> xpn = xp.next;//鏈表關係上的下一個結點
                    TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);//新建一個結點,插入到p與它在鏈表上的下一個結點之間
                    if (dir <= 0)//根據dir大小把p的左兒子或者右兒子設爲新增的結點
                        xp.left = x;
                    else
                        xp.right = x;
                    xp.next = x;
                    x.parent = x.prev = xp;
                    if (xpn != null)
                        ((TreeNode<K,V>)xpn).prev = x;
                    moveRootToFront(tab, balanceInsertion(root, x));//插入後進行紅黑樹的性質修復,並檢查root是不是直接在table數組上
                    return null;
                }
            }
        }
View Code

 balanceInsertion是插入後用於維持紅黑樹性質的修復操做,這裏涉及到了上面圖中展現的3中狀況不一樣的操做

        static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
                                                    TreeNode<K,V> x) {
            x.red = true;//插入的結點設爲紅色
            for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
                if ((xp = x.parent) == null) {
                    x.red = false;//x的父親爲null表明x是根結點,x改黑色直接結束
                    return x;
                }
                else if (!xp.red || (xpp = xp.parent) == null)
                    return root;//若x的父結點爲黑色或者x的父親爲根結點(實際上根應該是黑色)插入紅色結點不影響紅黑樹性質
                if (xp == (xppl = xpp.left)) {//若x的父親爲左兒子
                    if ((xppr = xpp.right) != null && xppr.red) {
                        //xppr爲x的叔叔,且叔叔爲紅色,圖中的狀況1,x的叔叔和父親改成紅色,x的爺爺改成黑色,x指針上移到爺爺的位置
                        xppr.red = false;
                        xp.red = false;
                        xpp.red = true;
                        x = xpp;
                    }
                    else {
                        if (x == xp.right) {
                            //狀況2,x的叔叔是黑色且x是右兒子。對x上升至父親後執行一次左旋
                            root = rotateLeft(root, x = xp);
                            xpp = (xp = x.parent) == null ? null : xp.parent;
                        }
                        if (xp != null) {
                            //狀況3,x的叔叔是黑色且x是左兒子。x的父親改黑色,x的爺爺改紅色後對x的爺爺進行右旋
                            xp.red = false;
                            if (xpp != null) {
                                xpp.red = true;
                                root = rotateRight(root, xpp);
                            }
                        }
                    }
                }
                else {//如下爲對稱的操做
                    if (xppl != null && xppl.red) {
                        xppl.red = false;
                        xp.red = false;
                        xpp.red = true;
                        x = xpp;
                    }
                    else {
                        if (x == xp.left) {
                            root = rotateRight(root, x = xp);
                            xpp = (xp = x.parent) == null ? null : xp.parent;
                        }
                        if (xp != null) {
                            xp.red = false;
                            if (xpp != null) {
                                xpp.red = true;
                                root = rotateLeft(root, xpp);
                            }
                        }
                    }
                }
            }
        }
View Code

刪除

最後是刪除操做部分,刪除操做須要尋找一個後驅結點來頂替原結點的位置,在結點無兒子時刪除後不需作其餘調整,結點只有一個兒子時那個兒子是後驅,不然右子樹中的最小結點做爲後驅。

        /**
         * Removes the given node, that must be present before this call.
         * This is messier than typical red-black deletion code because we
         * cannot swap the contents of an interior node with a leaf
         * successor that is pinned by "next" pointers that are accessible
         * independently during traversal. So instead we swap the tree
         * linkages. If the current tree appears to have too few nodes,
         * the bin is converted back to a plain bin. (The test triggers
         * somewhere between 2 and 6 nodes, depending on tree structure).
         * 移除給定的結點,這個方法相比通常的紅黑樹刪除更加雜亂,由於咱們沒法交換內部結點的內容他們被next指針給限制了,這個指針是在遍歷的時候獨立的。
         * 所以咱們交換樹的鏈接。若是當前的樹結點太少,須要轉換爲線性鏈表,一般這個值設定爲2-6個結點
         */
        final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
                                  boolean movable) {
            int n;
            if (tab == null || (n = tab.length) == 0)
                return;
            int index = (n - 1) & hash;//index = hash mod n
            TreeNode<K,V> first = (TreeNode<K,V>)tab[index], root = first, rl;
            TreeNode<K,V> succ = (TreeNode<K,V>)next, pred = prev;//succ指向要刪除結點的後一個點,pred指向要刪除結點的前一個
            if (pred == null)
                tab[index] = first = succ;//若要刪除的結點的前一個爲空,則first和tab[index]都指向要刪除結點的後一個結點
            else
                pred.next = succ;//若要刪除結點的前驅非空,則前一個結點的next指針指向該結點的後驅
            if (succ != null)
                succ.prev = pred;//後驅結點不爲空時,後驅結點的前置指針設爲刪除結點的前置結點
            if (first == null)
                return;//若刪除的結點是樹中的惟一結點則直接結束
            if (root.parent != null)
                root = root.root();//確保root指向根結點
            if (root == null || root.right == null ||
                (rl = root.left) == null || rl.left == null) {
                tab[index] = first.untreeify(map);  // 根自身或者左右兒子其中一個爲空說明結點數過少(不超過2)轉爲線性表並結束
                return;
            }
            TreeNode<K,V> p = this, pl = left, pr = right, replacement;//p指向要刪除的結點
            if (pl != null && pr != null) {
                TreeNode<K,V> s = pr, sl;
                while ((sl = s.left) != null) //刪除結點的左右兒子都不爲空時,尋找右子樹中最左的葉結點做爲後繼,s指向這個後繼結點
                    s = sl;
                boolean c = s.red; s.red = p.red; p.red = c; //交換後繼結點和要刪除結點的顏色
                TreeNode<K,V> sr = s.right;
                TreeNode<K,V> pp = p.parent;
                if (s == pr) { 
                    p.parent = s;//p是s的直接右兒子,交換p和s的位置
                    s.right = p;
                }
                else {
                    TreeNode<K,V> sp = s.parent;
                    if ((p.parent = sp) != null) {
                        if (s == sp.left)
                            sp.left = p;//p放到s本來的位置
                        else
                            sp.right = p;
                    }
                    if ((s.right = pr) != null)
                        pr.parent = s;//s放到p本來的位置
                }
                p.left = null;
                if ((p.right = sr) != null)
                    sr.parent = p;//s本來的右子樹成爲p的右子樹
                if ((s.left = pl) != null)
                    pl.parent = s;//s本來的左子樹成爲p的左子樹
                if ((s.parent = pp) == null)
                    root = s;//若p本來是根則新的根是s
                else if (p == pp.left)
                    pp.left = s;//若p是某個結點的左兒子,則s成爲該結點的左兒子
                else
                    pp.right = s;//若p是某個結點的右兒子,則s成爲該結點的右兒子
                if (sr != null)//若s結點有右兒子(s必定沒有左兒子),則replacement爲這個右兒子不然爲p
                    replacement = sr;
                else
                    replacement = p;
            }
            else if (pl != null)//若p的左右兒子有一方爲null,則replacement爲非空的一方,不然爲p本身
                replacement = pl;
            else if (pr != null)
                replacement = pr;
            else
                replacement = p;
            if (replacement != p) {//p有兒子或者s有兒子
                TreeNode<K,V> pp = replacement.parent = p.parent;
                if (pp == null)//用replacement來替換p
                    root = replacement;
                else if (p == pp.left)
                    pp.left = replacement;
                else
                    pp.right = replacement;
                p.left = p.right = p.parent = null;//移除p結點
            }
            //以replacement爲中心,進行紅黑樹性質的修復,replacement可能爲s的右兒子或者p的兒子或者p本身
            TreeNode<K,V> r = p.red ? root : balanceDeletion(root, replacement);

            if (replacement == p) {  //p沒有兒子或者s沒有兒子,直接移除p
                TreeNode<K,V> pp = p.parent;
                p.parent = null;
                if (pp != null) {
                    if (p == pp.left)
                        pp.left = null;
                    else if (p == pp.right)
                        pp.right = null;
                }
            }
            if (movable)
                moveRootToFront(tab, r);//整理根結點
        }
View Code

 

一樣有刪除和刪除以後維持紅黑樹性質的修復操做,這裏涉及到圖中展現的4種不一樣狀況的操做 

        static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root,
                                                   TreeNode<K,V> x) {
            for (TreeNode<K,V> xp, xpl, xpr;;)  {
                if (x == null || x == root)
                    return root;//刪除結點爲空或者刪除的是根結點,直接返回
                else if ((xp = x.parent) == null) {
                    x.red = false;//刪除後x成爲根結點,x的顏色改成黑色
                    return x;
                }
                else if (x.red) {
                    x.red = false;//將一個紅色的結點提高到刪除結點的位置不會改變黑高
                    return root;
                }
                else if ((xpl = xp.left) == x) {//x的父親是左兒子
                    if ((xpr = xp.right) != null && xpr.red) {
                        //狀況1,x的兄弟是紅色的
                        xpr.red = false;
                        xp.red = true;
                        root = rotateLeft(root, xp);
                        xpr = (xp = x.parent) == null ? null : xp.right;
                    }
                    if (xpr == null)
                        x = xp;//若x沒有兄弟,x上升到父親的位置
                    else {
                        TreeNode<K,V> sl = xpr.left, sr = xpr.right;
                        if ((sr == null || !sr.red) &&
                            (sl == null || !sl.red)) {
                            //狀況2,x兄弟是黑色,他的兩個兒子是黑色的
                            xpr.red = true;
                            x = xp;
                        }
                        else {
                            if (sr == null || !sr.red) {
                                //狀況3,x兄弟是黑色,他的右兒子是黑色,左兒子紅色
                                if (sl != null)
                                    sl.red = false;
                                xpr.red = true;
                                root = rotateRight(root, xpr);
                                xpr = (xp = x.parent) == null ?
                                    null : xp.right;
                            }
                            //狀況4
                            if (xpr != null) {
                                xpr.red = (xp == null) ? false : xp.red;
                                if ((sr = xpr.right) != null)
                                    sr.red = false;
                            }
                            if (xp != null) {
                                xp.red = false;
                                root = rotateLeft(root, xp);
                            }
                            x = root;
                        }
                    }
                }
                else { //如下爲對稱操做
                    if (xpl != null && xpl.red) {
                        xpl.red = false;
                        xp.red = true;
                        root = rotateRight(root, xp);
                        xpl = (xp = x.parent) == null ? null : xp.left;
                    }
                    if (xpl == null)
                        x = xp;
                    else {
                        TreeNode<K,V> sl = xpl.left, sr = xpl.right;
                        if ((sl == null || !sl.red) &&
                            (sr == null || !sr.red)) {
                            xpl.red = true;
                            x = xp;
                        }
                        else {
                            if (sl == null || !sl.red) {
                                if (sr != null)
                                    sr.red = false;
                                xpl.red = true;
                                root = rotateLeft(root, xpl);
                                xpl = (xp = x.parent) == null ?
                                    null : xp.left;
                            }
                            if (xpl != null) {
                                xpl.red = (xp == null) ? false : xp.red;
                                if ((sl = xpl.left) != null)
                                    sl.red = false;
                            }
                            if (xp != null) {
                                xp.red = false;
                                root = rotateRight(root, xp);
                            }
                            x = root;
                        }
                    }
                }
            }
        }
View Code
相關文章
相關標籤/搜索