【Java入門提升篇】Day25 史上最詳細的HashMap紅黑樹解析

  噹噹噹噹噹噹當,很久不見,最近又是換工做,又是換房子,忙的不可開交,斷更了一小段時間,最重要的一篇遲遲出不來,每次都猶抱琵琶半遮面,想要把它用通俗易懂的方式進行說明,確實有必定的難度,可愁煞我也,但本身挖的坑,哭着也要把它補上。請容許我當一回標題黨。前端

  好了,言歸正傳,本篇主要內容即是介紹HashMap的男二號——TreeNode(男一號仍是給Node吧,畢竟是TreeNode的爺爺,並且普通節點通常來講也比TreeNode要多),本篇主要從如下幾個方面介紹:編程

  1.紅黑樹介紹安全

  2.TreeNode結構併發

  3.樹化的過程ide

  4.紅黑樹的左旋和右旋函數

  5.TreeNode的左旋和右旋this

  6.紅黑樹的插入spa

  7.TreeNode的插入設計

  8.紅黑樹的刪除3d

  9.TreeNode的刪除

  講解紅黑樹的部分算是理論部分,講解TreeNode的部分則是代碼實踐部分,配合服用效果更加。

  保守估計,仔細食用本篇大約須要半小時,請各位細細品嚐。

紅黑樹介紹

  什麼是紅黑樹?嗯,首先,它是一顆樹,所謂的樹,即是長的像這樣的東西

 

  

  不像樹?emmmm,你把它想象成一顆倒過來的樹就行了,A~H都是樹的節點,每一個節點有零個或者多個子節點,或者說多個孩子,但除了根節點之外,每一個節點都只有一個父節點,也稱只有一個父親(老王嘿嘿一笑)。最上面的A是根節點,最下面的D、H、F、G是葉子節點。每個非根節點有且只有一個父節點;樹是具備一層一層的層次結構,這裏A位於第一層,B、C位於第二層,依次類推。將左邊的B節點部分(包括BDEH)拿出來,則又是一顆樹,稱爲樹的子樹。

  好了,知道樹是什麼東西了,那麼紅黑樹是什麼樣的呢?

  紅黑樹,本質上來講是一顆二叉搜索樹。嗯,仍是先說說這個二叉搜索樹吧。二叉表明它的節點最多有兩個子節點,並且左右有順序,不能顛倒,分別叫左孩子和右孩子,這兩個節點互爲兄弟節點,嗯,其實叫法根現實裏的叫法差很少,如下圖爲例,四、9互爲兄弟,7是他們的父親,9是2的叔叔,8是2的堂兄弟,很簡單吧。說完了稱謂,再來講說用途,既然叫作搜索樹表示它的用途是爲了更快的搜索和查找而設計的,因此這棵樹自己知足必定的排序規則,即樹中的任何節點的值大於它的左孩子,且小於它的右孩子。 任意節點的左、右子樹也分別爲二叉查找樹。嗯,結合下圖意會一下:

  

  而紅黑樹,就跟它的名字同樣,又紅又黑,紅黑並進,理實交融,節點是非紅即黑的,看起來就像這樣

 

  

  紅黑樹的主要特性:
  (1)每一個節點要麼是黑色,要麼是紅色。(節點非黑即紅)
  (2)根節點是黑色。
  (3)每一個葉子節點(NIL)是黑色。 
  (4)若是一個節點是紅色的,則它的子節點必須是黑色的。(也就是說父子節點不能同時爲紅色)
  (5)從一個節點到該節點的子孫節點的全部路徑上包含相同數目的黑節點。(這一點是平衡的關鍵)

  說簡單也簡單,其實就是一顆比較平衡的又紅又黑的二叉樹嘛。

TreeNode結構

  既然咱們已經知道紅黑樹長什麼樣了,那麼咱們再來看看HashMap中的TreeNode代碼裏是如何表示的:

    /**
     * 用於Tree bins 的Entry。 擴展LinkedHashMap.Entry(進而擴展Node),所以能夠用做常規節點或連接節點的擴展。
     */
    static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
        TreeNode<K,V> parent;  // 紅黑樹父節點
        TreeNode<K,V> left;
        TreeNode<K,V> right;
        TreeNode<K,V> prev;    // 刪除後須要取消連接
        boolean red;
        TreeNode(int hash, K key, V val, Node<K,V> next) {
            super(hash, key, val, next);
        }
     //省略後續代碼

   TreeNode繼承自LinkedHashMap中的內部類——LinkedHashMap.Entry,而這個內部類又繼承自Node,因此算是Node的孫子輩了。咱們再來看看它的幾個屬性,parent用來指向它的父節點,left指向左孩子,right指向右孩子,prev則指向前一個節點(原鏈表中的前一個節點),注意,這些字段跟Entry,Node中的字段同樣,是使用默認訪問權限的,因此子類能夠直接使用父類的屬性。

樹化的過程

  在前幾篇中已經有所介紹,當HashMap桶中的元素個數超過必定數量時,就會樹化,也就是將鏈表轉化爲紅黑樹的結構。

    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        ...省略部分代碼...
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        //當桶中元素個數超過閾值(8)時就進行樹化
                        if (binCount >= TREEIFY_THRESHOLD - 1)
                            treeifyBin(tab, hash);
                        break;
                    }
         ...省略部分代碼...
    }

    final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            TreeNode<K,V> hd = null, tl = null;
            do {
                //將節點替換爲TreeNode
                TreeNode<K,V> p = replacementTreeNode(e, null);
                if (tl == null)
            //hd指向頭結點 hd
= p; else { //這裏實際上是將單鏈錶轉化成了雙向鏈表,tl是p的前驅,每次循環更新指向雙鏈表的最後一個元素,用來和p相連,p是當前節點 p.prev = tl; tl.next = p; } tl = p; } while ((e = e.next) != null); if ((tab[index] = hd) != null) //將鏈表進行樹化 hd.treeify(tab); } }

  從代碼中能夠看到,在treeifyBin函數中,先將全部節點替換爲TreeNode,而後再將單鏈錶轉爲雙鏈表,方便以後的遍歷和移動操做。而最終的操做,其實是調用TreeNode的方法treeify進行的。

final void treeify(Node<K,V>[] tab) {
            //樹的根節點
            TreeNode<K,V> root = null;
            //x是當前節點,next是後繼
            for (TreeNode<K,V> x = this, next; x != null; x = next) {
                next = (TreeNode<K,V>)x.next;
                x.left = x.right = null;
                //若是根節點爲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;;) {
                        //p指向遍歷中的當前節點,x爲待插入節點,k是x的key,h是x的hash值,ph是p的hash值,dir用來指示x節點與p的比較,-1表示比p小,1表示比p大,不存在相等狀況,由於HashMap中是不存在兩個key徹底一致的狀況。
                        int dir, ph;
                        K pk = p.key;
                        if ((ph = p.hash) > h)
                            dir = -1;
                        else if (ph < h)
                            dir = 1;
                        //若是hash值相等,那麼判斷k是否實現了comparable接口,若是實現了comparable接口就使用compareTo進行進行比較,若是仍舊相等或者沒有實現comparable接口,則在tieBreakOrder中比較
                        else if ((kc == null &&
                                  (kc = comparableClassFor(k)) == null) ||
                                 (dir = compareComparables(kc, k, pk)) == 0)
                            dir = tieBreakOrder(k, pk);

                        TreeNode<K,V> xp = p;
                        if ((p = (dir <= 0) ? p.left : p.right) == null) {
                            x.parent = xp;
                            if (dir <= 0)
                                xp.left = x;
                            else
                                xp.right = x;
                 //進行插入平衡處理 root
= balanceInsertion(root, x); break; } } } }
       //確保給定節點是桶中的第一個元素 moveRootToFront(tab, root); }
//這裏不是爲了總體排序,而是爲了在插入中保持一致的順序 static int tieBreakOrder(Object a, Object b) { int d; //用二者的類名進行比較,若是相同則使用對象默認的hashcode進行比較 if (a == null || b == null || (d = a.getClass().getName(). compareTo(b.getClass().getName())) == 0) d = (System.identityHashCode(a) <= System.identityHashCode(b) ? -1 : 1); return d; }

  這裏的邏輯其實不復雜,僅僅是循環遍歷當前樹,而後找到能夠該節點能夠插入的位置,依次和遍歷節點比較,比它大則跟其右孩子比較,小則與其左孩子比較,依次遍歷,直到找到左孩子或者右孩子爲null的位置進行插入。

  真正複雜一點的地方在於balanceInsertion函數,這個函數中,將紅黑樹進行插入平衡處理,保證插入節點後仍保持紅黑樹的性質。這個函數稍後在TreeNode的插入中進行介紹,這裏先看看moveRootToFront,這個函數是將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;
                //first指向鏈表第一個節點
                TreeNode<K,V> first = (TreeNode<K,V>)tab[index];
                if (root != first) {
                    //若是root不是第一個節點,則將root放到第一個首節點位置
                    Node<K,V> rn;
                    tab[index] = root;
                    TreeNode<K,V> rp = root.prev;
                    if ((rn = root.next) != null)
                        ((TreeNode<K,V>)rn).prev = rp;
                    if (rp != null)
                        rp.next = rn;
                    if (first != null)
                        first.prev = root;
                    root.next = first;
                    root.prev = null;
                }
                //這裏是防護性編程,校驗更改後的結構是否知足紅黑樹和雙鏈表的特性
                //由於HashMap並無作併發安全處理,可能在併發場景中意外破壞告終構
                assert checkInvariants(root);
            }
        }    

紅黑樹的左旋和右旋

  左旋和右旋,顧名思義嘛,就是將節點以某個節點爲中心向左或者向右進行旋轉操做以保持二叉樹的平衡,讓咱們看圖說話

 

 

  圖畫的有點大。將就着看一下吧,左旋至關於以要旋轉的節點爲中心,將子樹總體向左旋轉,該節點變成子樹的根節點,原來的父節點A變成了左孩子,若是右孩子C有左孩子D,則將其變爲A的右孩子。提及來好像有點繞,能夠聯繫圖進行形象化的理解,當節點A向左旋轉以後,C的左孩子D能夠理解爲由於重力做用掉到A的右孩子位置,嗯,就是這樣。右旋也是相似理解便可。

TreeNode的左旋和右旋

  瞭解了左旋和右旋,讓咱們看看代碼裏是怎樣實現的:

        /**
         * 左旋
         */
        static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
                                              TreeNode<K,V> p) {
            //這裏的p即上圖的A節點,r指向右孩子即C,rl指向右孩子的左孩子即D,pp爲p的父節點
            TreeNode<K,V> r, pp, rl;
            if (p != null && (r = p.right) != null) {
                if ((rl = p.right = r.left) != null)
                    rl.parent = p;
                //將p的父節點的孩子節點指向r
                if ((pp = r.parent = p.parent) == null)
                    (root = r).red = false;
                else if (pp.left == p)
                    pp.left = r;
                else
                    pp.right = r;
                //將p置爲r的左節點
                r.left = p;
                p.parent = r;
            }
            return root;
        }

        /**
         * 右旋
         */
        static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,
                                               TreeNode<K,V> p) {
             //這裏的p即上圖的A節點,l指向左孩子即C,lr指向左孩子的右孩子即E,pp爲p的父節點
            TreeNode<K,V> l, pp, lr;
            if (p != null && (l = p.left) != null) {
                if ((lr = p.left = l.right) != null)
                    lr.parent = p;
                if ((pp = l.parent = p.parent) == null)
                    (root = l).red = false;
                else if (pp.right == p)
                    pp.right = l;
                else
                    pp.left = l;
                l.right = p;
                p.parent = l;
            }
            return root;
        }

  其實,也很簡單嘛。23333  

紅黑樹的插入

  如今來看看一個比較麻煩一點的操做,紅黑樹的插入,首先找到這個節點要插入的位置,即一層一層比較,大的放右邊,小的放左邊,直到找到爲null的節點放入便可,可是如何在插入的過程保持紅黑樹的特性呢,想一想好像比較頭疼,可是再仔細想一想其實就會發現,其實只有這麼幾種狀況:

  1.插入的爲根節點,則直接把顏色改爲黑色便可。

  2.插入的節點的父節點是黑色節點,則不須要調整,由於插入的節點會初始化爲紅色節點,紅色節點是不會影響樹的平衡的。

  3.插入的節點的祖父節點爲null,即插入的節點的父節點是根節點,直接插入便可(由於根節點確定是黑色)。

  4.插入的節點父節點和祖父節點都存在,而且其父節點是祖父節點的左節點。這種狀況稍微麻煩一點,又分兩種子狀況:

    i.插入節點的叔叔節點是紅色,則將父親節點和叔叔節點都改爲黑色,而後祖父節點改爲紅色便可。

    ii.插入節點的叔叔節點是黑色或不存在:

      a.若插入節點是其父節點的右孩子,則將其父節點左旋,

      b.若爲左孩子,則將其父節點變成黑色節點,將其祖父節點變成紅色節點,而後將其祖父節點右旋。

  5.插入的節點父節點和祖父節點都存在,而且其父節點是祖父節點的右節點。這種狀況跟上面是相似的,分兩種子狀況:

    i.插入節點的叔叔節點是紅色,則將父親節點和叔叔節點都改爲黑色,而後祖父節點改爲紅色便可。

    ii.插入節點的叔叔節點是黑色或不存在:

      a.若插入節點是其父節點的左孩子,則將其父節點右旋

      b.若爲右孩子,則將其父節點變成黑色節點,將其祖父節點變成紅色節點,而後將其祖父節點左旋。

  而後重複進行上述操做,直到變成1或2狀況時則結束變換。說半天,可能仍是雲裏霧裏,一圖勝千言,讓咱們從無到有構建一顆紅黑樹,假設插入的順序爲:10,5,9,3,6,7,19,32,24,17(數字是我拍腦殼瞎想的。)

  先來插個10,爲情景1,直接改爲黑色便可,再插入5,爲情景2,比10小,放到10的左孩子位置,插入9,比10小,可是比5大,放到5的右孩子位置,此時,爲情景4iia,左旋後變成了情景4iib,變色右旋便可完成轉化。插入3後爲情景4i,將父節點和叔叔節點同時變色便可,插入6不須要調整,插入7後爲情景5i,變色便可。插入19不須要調整,插入32,變成了5iib,左旋變色便可,插入24,變成5iia,右旋後變成5i,變色便可,最後插入17,完美。

 

  看圖說話是否是就簡單明瞭了,看在我畫圖這麼辛苦的份上,點個關注給個贊可好(滑稽)。

TreeNode的插入

  瞭解了紅黑樹的刪除以後,咱們再來看下TreeNode中是怎樣用代碼實現的:

      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;;) {
//情景1:父節點爲null
if ((xp = x.parent) == null) { x.red = false; return x; }
          //情景2,3:父節點是黑色節點或者祖父節點爲null
else if (!xp.red || (xpp = xp.parent) == null) return root;
          //情景4:插入的節點父節點和祖父節點都存在,而且其父節點是祖父節點的左節點
if (xp == (xppl = xpp.left)) {
            //情景4i:插入節點的叔叔節點是紅色
if ((xppr = xpp.right) != null && xppr.red) { xppr.red = false; xp.red = false; xpp.red = true; x = xpp; }
            //情景4ii:插入節點的叔叔節點是黑色或不存在
else {
              //情景4iia:插入節點是其父節點的右孩子
if (x == xp.right) { root = rotateLeft(root, x = xp); xpp = (xp = x.parent) == null ? null : xp.parent; }
              //情景4iib:插入節點是其父節點的左孩子
if (xp != null) { xp.red = false; if (xpp != null) { xpp.red = true; root = rotateRight(root, xpp); } } } }
          //情景5:插入的節點父節點和祖父節點都存在,而且其父節點是祖父節點的右節點
else {
            //情景5i:插入節點的叔叔節點是紅色
if (xppl != null && xppl.red) { xppl.red = false; xp.red = false; xpp.red = true; x = xpp; }
            //情景5ii:插入節點的叔叔節點是黑色或不存在
else {
·              //情景5iia:插入節點是其父節點的左孩子 
if (x == xp.left) { root = rotateRight(root, x = xp); xpp = (xp = x.parent) == null ? null : xp.parent; }
              //情景5iib:插入節點是其父節點的右孩子
if (xp != null) { xp.red = false; if (xpp != null) { xpp.red = true; root = rotateLeft(root, xpp); } } } } } }

  其實就是一毛同樣的,對號入座便可。

紅黑樹的刪除

  講完插入,接下來咱們來講說刪除,刪除的話,比插入還要複雜一點,請各位看官先深呼吸,作好閱讀準備。

  以前已經說過,紅黑樹是一顆特殊的二叉搜索樹,因此進行刪除操做時,實際上是先進行二叉搜索樹的刪除,而後再進行調整。因此,其實這裏分爲兩部份內容:1.二叉搜索樹的刪除,2.紅黑樹的刪除調整。

  二叉搜索樹的刪除主要有這麼幾種情景:

  情景1:待刪除的節點無左右孩子。

  情景2:待刪除的節點只有左孩子或者右孩子。

  情景3:待刪除的節點既有左孩子又有右孩子。

  對於情景1,直接刪除便可,情景2,則直接把該節點的父節點指向它的左孩子或者右孩子便可,情景3稍微複雜一點,須要先找到其右子樹的最左孩子(或者左子樹的最右孩子),即左(右)子樹中序遍歷時的第一個節點,而後將其與待刪除的節點互換,最後再刪除該節點(若是有右子樹,則右子樹上位)。總之,就是先找到它的替代者,找到以後替換這個要刪除的節點,而後再把這個節點真正刪除掉。

  其實二叉搜索樹的刪除整體來講仍是比較簡單的,刪除完以後,若是替代者是紅色節點,則不須要調整,若是是黑色節點,則會致使左子樹和右子樹路徑中黑色節點數量不一致,須要進行紅黑樹的調整,跟上面同樣,替代節點爲其父節點的左孩子與右孩子的狀況相似,因此這裏只說其爲左孩子的情景(PS:上一步的尋找替換節點使用的是右子樹的最左節點,因此該節點若是有孩子,只能是右孩子):

  情景1:只有右孩子且爲紅色,直接用右孩子替換該節點而後變成黑色便可。

  (D表明替代節點,即要被刪除的節點,以前在通過二叉搜索樹的刪除後,D節點其實已經被刪除了,這裏爲了方便理解這個變化過程,因此把這個節點也畫出來了,因此當前的初始狀態是待刪除節點與其替換節點互換位置與顏色以後的狀態)

  情景2:只有右孩子且爲黑色,那麼刪除該節點會致使父節點的左子樹路徑上黑色節點減一,此時只能去借助右子樹,從右子樹中借一個紅色節點過來便可,具體取決於右子樹的狀況,這裏又分紅兩種:

    i.兄弟節點是紅色,則此時父節點是黑色,且兄弟節點確定有兩個孩子,且兄弟節點的左右子樹路徑上均有兩個黑色節點,此時只需將兄弟節點與父節點顏色互換,而後將父節點左旋,左旋後,兄弟節點的左子樹SL掛到了父節點p的右孩子位置,這時會致使p的右子樹路徑上的黑色節點比左子樹多一,此時再SL置爲紅色便可。

 

    ii.兄弟節點是黑色,那麼就只能打它孩子的主意了,這裏主要關注遠侄子(兄弟節點的右孩子,即SR)的顏色狀況,這裏分紅兩種狀況:

      a.遠侄子SR是黑色,近侄子任意(白色表明顏色可爲任意顏色),則先將S轉爲紅色,而後右旋,再將SL換成P節點顏色,P塗成黑色,S也塗成黑色,再進行左旋便可。其實簡單說就是SL上位,替換父節點位置。

 

      b.遠侄子SR爲紅色,近侄子任意(該子樹路徑中有且僅有一個黑色節點),則先將兄弟節點與父節點顏色互換,將SR塗成黑色,再將父節點左旋便可。

 

  emmmm...好像也不是很麻煩嘛(逃)。

TreeNode的刪除節點

  TreeNode刪除節點其實也是兩步走,先進行二叉搜索樹的刪除,而後再進行紅黑樹的調整,跟以前的狀況分析是一致的。

final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab, boolean movable) {

     ......

     //p是待刪除節點,replacement用於後續的紅黑樹調整,指向的是p或者p的繼承者。
     //若是p是葉子節點,p==replacement,不然replacement爲p的右子樹中最左節點
     if (replacement != p) {
        //若p不是葉子節點,則讓replacement的父節點指向p的父節點
        TreeNode<K,V> pp = replacement.parent = p.parent;
        if (pp == null)
            root = replacement;
        else if (p == pp.left)
            pp.left = replacement;
        else
            pp.right = replacement;
        p.left = p.right = p.parent = null;
    }

    //若待刪除的節點p時紅色的,則樹平衡未被破壞,無需進行調整。
    //不然刪除節點後須要進行調整
    TreeNode<K,V> r = p.red ? root : balanceDeletion(root, replacement);

    //p爲葉子節點,則直接將p從樹中清除
    if (replacement == p) {  // detach
        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;
        }
    }
}

  麻煩的地方就在刪除節點後的調整了,全部邏輯都在balanceDeletion函數裏,兩個參數分別表示根節點和刪除節點的繼承者,來看看它的具體實現:

static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root, TreeNode<K,V> x) {
    for (TreeNode<K,V> xp, xpl, xpr;;)  {
        //x爲空或x爲根節點,直接返回
        if (x == null || x == root)
            return root; 
        //x爲根節點,染成黑色,直接返回(由於調整事後,root並不必定指向刪除操做事後的根節點,若是以前刪除的是root節點,則x將成爲新的根節點)
        else if ((xp = x.parent) == null) {
            x.red = false; 
            return x;
        }
        //若是x爲紅色,則無需調整,返回
        else if (x.red) {
            x.red = false;
            return root; 
        }
        //x爲其父節點的左孩子
        else if ((xpl = xp.left) == x) {
            //若是它有紅色的兄弟節點xpr,那麼它的父親節點xp必定是黑色節點
            if ((xpr = xp.right) != null && xpr.red) { 
                xpr.red = false;
                xp.red = true; 
                //對父節點xp作左旋轉
                root = rotateLeft(root, xp); 
                //從新將xp指向x的父節點,xpr指向xp新的右孩子
                xpr = (xp = x.parent) == null ? null : xp.right; 
            }
            //若是xpr爲空,則向上繼續調整,將x的父節點xp做爲新的x繼續循環
            if (xpr == null)
                x = xp; 
            else {
                //sl和sr分別爲其近侄子和遠侄子
                TreeNode<K,V> sl = xpr.left, sr = xpr.right;
            
if ((sr == null || !sr.red) && (sl == null || !sl.red)) { xpr.red = true; //若sl和sr都爲黑色或者不存在,即xpr沒有紅色孩子,則將xpr染紅 x = xp; //本輪結束,繼續向上循環 } else { //不然的話,就須要進一步調整 if (sr == null || !sr.red) { if (sl != null) //若左孩子爲紅,右孩子不存在或爲黑 sl.red = false; //左孩子染黑 xpr.red = true; //將xpr染紅 root = rotateRight(root, xpr); //右旋 xpr = (xp = x.parent) == null ? null : xp.right; //右旋後,xpr指向xp的新右孩子,即上一步中的sl } if (xpr != null) { xpr.red = (xp == null) ? false : xp.red; //xpr染成跟父節點一致的顏色,爲後面父節點xp的左旋作準備 if ((sr = xpr.right) != null) sr.red = false; //xpr新的右孩子染黑,防止出現兩個紅色相連 } if (xp != null) { xp.red = false; //將xp染黑,並對其左旋,這樣就能保證被刪除的X所在的路徑又多了一個黑色節點,從而達到恢復平衡的目的 root = rotateLeft(root, xp); } //到此調整已經完畢,進入下一次循環後將直接退出 x = root; } } } //x爲其父節點的右孩子,跟上面相似 else { // symmetric 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; } } } } }

  呼。。。終於。。醞釀了好多天的一篇文章總算是寫完了,爲了儘可能確認轉換的準確性,找了不少資料進行參考,過程當中花了很多時間,曾屢次準備放棄。。。不過總算是沒有死在孃胎裏,也算是完成了一樁心事,開心。.

  以後還會繼續更新,歡迎你們繼續關注。也歡迎你們前來打臉

相關文章
相關標籤/搜索