<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【144期】考考基礎(chǔ)部分,你能說出 TreeMap 原理實現(xiàn)及常用方法嗎?

          共 38224字,需瀏覽 77分鐘

           ·

          2021-03-01 00:07

          程序員的成長之路
          互聯(lián)網(wǎng)/程序員/技術(shù)/資料共享 
          關(guān)注


          閱讀本文大概需要 7.5 分鐘。

          作者:工匠初心
          cnblogs.com/LiaHon/p/11221634.html

          目錄

          • TreeMap概述
          • 紅黑樹回顧
          • TreeMap構(gòu)造
          • put方法
          • get 方法
          • remove方法
          • 遍歷
          • 總結(jié)

          一. TreeMap概述

          • TreeMap存儲K-V鍵值對,通過紅黑樹(R-B tree)實現(xiàn);
          • TreeMap繼承了NavigableMap接口,NavigableMap接口繼承了SortedMap接口,可支持一系列的導(dǎo)航定位以及導(dǎo)航操作的方法,當(dāng)然只是提供了接口,需要TreeMap自己去實現(xiàn);
          • TreeMap實現(xiàn)了Cloneable接口,可被克隆,實現(xiàn)了Serializable接口,可序列化;
          • TreeMap因為是通過紅黑樹實現(xiàn),紅黑樹結(jié)構(gòu)天然支持排序,默認(rèn)情況下通過Key值的自然順序進行排序;

          二. 紅黑樹回顧

          因為TreeMap的存儲結(jié)構(gòu)是紅黑樹,我們回顧一下紅黑樹的特點以及基本操作,紅黑樹的原理可參考關(guān)于紅黑樹(R-B tree)原理:
          https://www.cnblogs.com/LiaHon/p/11203229.html
          下圖為典型的紅黑樹:
          紅黑樹規(guī)則特點:
          • 節(jié)點分為紅色或者黑色;
          • 根節(jié)點必為黑色;
          • 葉子節(jié)點都為黑色,且為null;
          • 連接紅色節(jié)點的兩個子節(jié)點都為黑色(紅黑樹不會出現(xiàn)相鄰的紅色節(jié)點);
          • 從任意節(jié)點出發(fā),到其每個葉子節(jié)點的路徑中包含相同數(shù)量的黑色節(jié)點;
          • 新加入到紅黑樹的節(jié)點為紅色節(jié)點;
          紅黑樹自平衡基本操作:
          • 變色:在不違反上述紅黑樹規(guī)則特點情況下,將紅黑樹某個node節(jié)點顏色由紅變黑,或者由黑變紅;
          • 左旋:逆時針旋轉(zhuǎn)兩個節(jié)點,讓一個節(jié)點被其右子節(jié)點取代,而該節(jié)點成為右子節(jié)點的左子節(jié)點
          • 右旋:順時針旋轉(zhuǎn)兩個節(jié)點,讓一個節(jié)點被其左子節(jié)點取代,而該節(jié)點成為左子節(jié)點的右子節(jié)點

          三. TreeMap構(gòu)造

          我們先看一下TreeMap中主要的成員變量

          /**
           * 我們前面提到TreeMap是可以自動排序的,默認(rèn)情況下comparator為null,這個時候按照key的自然順序進行排
           * 序,然而并不是所有情況下都可以直接使用key的自然順序,有時候我們想讓Map的自動排序按照我們自己的規(guī)則,
           * 這個時候你就需要傳遞Comparator的實現(xiàn)類
           */

          private final Comparator<? super K> comparator;

          /**
           * TreeMap的存儲結(jié)構(gòu)既然是紅黑樹,那么必然會有唯一的根節(jié)點。
           */

          private transient Entry<K,V> root;

          /**
           * Map中key-val對的數(shù)量,也即是紅黑樹中節(jié)點Entry的數(shù)量
           */

          private transient int size = 0;

          /**
           * 紅黑樹結(jié)構(gòu)的調(diào)整次數(shù)
           */

          private transient int modCount = 0;

          上面的主要成員變量根節(jié)點root是Entry類的實體,我們來看一下Entry類的源碼

          static final class Entry<K,Vimplements Map.Entry<K,V{
              //key,val是存儲的原始數(shù)據(jù)
              K key;
              V value;
              //定義了節(jié)點的左孩子
              Entry<K,V> left;
              //定義了節(jié)點的右孩子
              Entry<K,V> right;
              //通過該節(jié)點可以反過來往上找到自己的父親
              Entry<K,V> parent;
              //默認(rèn)情況下為黑色節(jié)點,可調(diào)整
              boolean color = BLACK;

              /**
               * 構(gòu)造器
               */

              Entry(K key, V value, Entry<K,V> parent) {
                  this.key = key;
                  this.value = value;
                  this.parent = parent;
              }

              /**
               * 獲取節(jié)點的key值
               */

              public K getKey() {return key;}

              /**
               * 獲取節(jié)點的value值
               */

              public V getValue() {return value;}

              /**
               * 用新值替換當(dāng)前值,并返回當(dāng)前值
               */

              public V setValue(V value) {
                  V oldValue = this.value;
                  this.value = value;
                  return oldValue;
              }

              public boolean equals(Object o) {
                  if (!(o instanceof Map.Entry))
                      return false;
                  Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                  return valEquals(key,e.getKey()) && valEquals(value,e.getValue());
              }

              public int hashCode() {
                  int keyHash = (key==null ? 0 : key.hashCode());
                  int valueHash = (value==null ? 0 : value.hashCode());
                  return keyHash ^ valueHash;
              }

              public String toString() {
                  return key + "=" + value;
              }
          }

          Entry靜態(tài)內(nèi)部類實現(xiàn)了Map的內(nèi)部接口Entry,提供了紅黑樹存儲結(jié)構(gòu)的java實現(xiàn),通過left屬性可以建立左子樹,通過right屬性可以建立右子樹,通過parent可以往上找到父節(jié)點。
          大體的實現(xiàn)結(jié)構(gòu)圖如下:
          TreeMap構(gòu)造函數(shù):

          //默認(rèn)構(gòu)造函數(shù),按照key的自然順序排列
          public TreeMap() {comparator = null;}
          //傳遞Comparator具體實現(xiàn),按照該實現(xiàn)規(guī)則進行排序
          public TreeMap(Comparator<? super K> comparator) {this.comparator = comparator;}
          //傳遞一個map實體構(gòu)建TreeMap,按照默認(rèn)規(guī)則排序
          public TreeMap(Map<? extends K, ? extends V> m) {
              comparator = null;
              putAll(m);
          }
          //傳遞一個map實體構(gòu)建TreeMap,按照傳遞的map的排序規(guī)則進行排序
          public TreeMap(SortedMap<K, ? extends V> m) {
              comparator = m.comparator();
              try {
                  buildFromSorted(m.size(), m.entrySet().iterator(), nullnull);
              } catch (java.io.IOException cannotHappen) {
              } catch (ClassNotFoundException cannotHappen) {
              }
          }

          四. put方法

          put方法為Map的核心方法,TreeMap的put方法大概流程如下:
          我們來分析一下源碼
          public V put(K key, V value) {
              Entry<K,V> t = root;
              /**
               * 如果根節(jié)點都為null,還沒建立起來紅黑樹,我們先new Entry并賦值給root把紅黑樹建立起來,這個時候紅
               * 黑樹中已經(jīng)有一個節(jié)點了,同時修改操作+1。
               */

              if (t == null) {
                  compare(key, key); 
                  root = new Entry<>(key, value, null);
                  size = 1;
                  modCount++;
                  return null;
              }
              /**
               * 如果節(jié)點不為null,定義一個cmp,這個變量用來進行二分查找時的比較;定義parent,是new Entry時必須
               * 要的參數(shù)
               */

              int cmp;
              Entry<K,V> parent;
              // cpr表示有無自己定義的排序規(guī)則,分兩種情況遍歷執(zhí)行
              Comparator<? super K> cpr = comparator;
              if (cpr != null) {
                  /**
                   * 從root節(jié)點開始遍歷,通過二分查找逐步向下找
                   * 第一次循環(huán):從根節(jié)點開始,這個時候parent就是根節(jié)點,然后通過自定義的排序算法
                   * cpr.compare(key, t.key)比較傳入的key和根節(jié)點的key值,如果傳入的key<root.key,那么
                   * 繼續(xù)在root的左子樹中找,從root的左孩子節(jié)點(root.left)開始:如果傳入的key>root.key,
                   * 那么繼續(xù)在root的右子樹中找,從root的右孩子節(jié)點(root.right)開始;如果恰好key==root.key,
                   * 那么直接根據(jù)root節(jié)點的value值即可。
                   * 后面的循環(huán)規(guī)則一樣,當(dāng)遍歷到的當(dāng)前節(jié)點作為起始節(jié)點,逐步往下找
                   *
                   * 需要注意的是:這里并沒有對key是否為null進行判斷,建議自己的實現(xiàn)Comparator時應(yīng)該要考慮在內(nèi)
                   */

                  do {
                      parent = t;
                      cmp = cpr.compare(key, t.key);
                      if (cmp < 0)
                          t = t.left;
                      else if (cmp > 0)
                          t = t.right;
                      else
                          return t.setValue(value);
                  } while (t != null);
              }
              else {
                  //從這里看出,當(dāng)默認(rèn)排序時,key值是不能為null的
                  if (key == null)
                      throw new NullPointerException();
                  @SuppressWarnings("unchecked")
                  Comparable<? super K> k = (Comparable<? super K>) key;
                  //這里的實現(xiàn)邏輯和上面一樣,都是通過二分查找,就不再多說了
                  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);
              }
              /**
               * 能執(zhí)行到這里,說明前面并沒有找到相同的key,節(jié)點已經(jīng)遍歷到最后了,我們只需要new一個Entry放到
               * parent下面即可,但放到左子節(jié)點上還是右子節(jié)點上,就需要按照紅黑樹的規(guī)則來。
               */

              Entry<K,V> e = new Entry<>(key, value, parent);
              if (cmp < 0)
                  parent.left = e;
              else
                  parent.right = e;
              /**
               * 節(jié)點加進去了,并不算完,我們在前面紅黑樹原理章節(jié)提到過,一般情況下加入節(jié)點都會對紅黑樹的結(jié)構(gòu)造成
               * 破壞,我們需要通過一些操作來進行自動平衡處置,如【變色】【左旋】【右旋】
               */

              fixAfterInsertion(e);
              size++;
              modCount++;
              return null;
          }
          put方法源碼中通過fixAfterInsertion(e)方法來進行自平衡處理,我們回顧一下插入時自平衡調(diào)整的邏輯
          接下來我們看一看這個方法

          private void fixAfterInsertion(Entry<K,V> x) {
              //新插入的節(jié)點為紅色節(jié)點
              x.color = RED;
              //我們知道父節(jié)點為黑色時,并不需要進行樹結(jié)構(gòu)調(diào)整,只有當(dāng)父節(jié)點為紅色時,才需要調(diào)整
              while (x != null && x != root && x.parent.color == RED) {
                  //如果父節(jié)點是左節(jié)點,對應(yīng)上表中情況1和情況2
                  if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
                      Entry<K,V> y = rightOf(parentOf(parentOf(x)));
                      //如果叔父節(jié)點為紅色,對應(yīng)于“父節(jié)點和叔父節(jié)點都為紅色”,此時通過變色即可實現(xiàn)平衡
                      //此時父節(jié)點和叔父節(jié)點都設(shè)置為黑色,祖父節(jié)點設(shè)置為紅色
                      if (colorOf(y) == RED) {
                          setColor(parentOf(x), BLACK);
                          setColor(y, BLACK);
                          setColor(parentOf(parentOf(x)), RED);
                          x = parentOf(parentOf(x));
                      } else {
                          //如果插入節(jié)點是黑色,插入的是右子節(jié)點,通過【左右節(jié)點旋轉(zhuǎn)】(這里先進行父節(jié)點左旋)
                          if (x == rightOf(parentOf(x))) {
                              x = parentOf(x);
                              rotateLeft(x);
                          }
                          //設(shè)置父節(jié)點和祖父節(jié)點顏色
                          setColor(parentOf(x), BLACK);
                          setColor(parentOf(parentOf(x)), RED);
                          //進行祖父節(jié)點右旋(這里【變色】和【旋轉(zhuǎn)】并沒有嚴(yán)格的先后順序,達成目的就行)
                          rotateRight(parentOf(parentOf(x)));
                      }
                  } else {
                      //父節(jié)點是右節(jié)點的情況
                      Entry<K,V> y = leftOf(parentOf(parentOf(x)));
                      //對應(yīng)于“父節(jié)點和叔父節(jié)點都為紅色”,此時通過變色即可實現(xiàn)平衡
                      if (colorOf(y) == RED) {
                          setColor(parentOf(x), BLACK);
                          setColor(y, BLACK);
                          setColor(parentOf(parentOf(x)), RED);
                          x = parentOf(parentOf(x));
                      } else {
                          //如果插入節(jié)點是黑色,插入的是左子節(jié)點,通過【右左節(jié)點旋轉(zhuǎn)】(這里先進行父節(jié)點右旋)
                          if (x == leftOf(parentOf(x))) {
                              x = parentOf(x);
                              rotateRight(x);
                          }
                          setColor(parentOf(x), BLACK);
                          setColor(parentOf(parentOf(x)), RED);
                          //進行祖父節(jié)點左旋(這里【變色】和【旋轉(zhuǎn)】并沒有嚴(yán)格的先后順序,達成目的就行)
                          rotateLeft(parentOf(parentOf(x)));
                      }
                  }
              }
              //根節(jié)點必須為黑色
              root.color = BLACK;
          }

          源碼中通過 rotateLeft 進行【左旋】,通過 rotateRight 進行【右旋】。都非常類似,我們就看一下【左旋】的代碼,【左旋】規(guī)則如下:“逆時針旋轉(zhuǎn)兩個節(jié)點,讓一個節(jié)點被其右子節(jié)點取代,而該節(jié)點成為右子節(jié)點的左子節(jié)點”。

          private void rotateLeft(Entry<K,V> p) {
              if (p != null) {
                  /**
                   * 斷開當(dāng)前節(jié)點p與其右子節(jié)點的關(guān)聯(lián),重新將節(jié)點p的右子節(jié)點的地址指向節(jié)點p的右子節(jié)點的左子節(jié)點
                   * 這個時候節(jié)點r沒有父節(jié)點
                   */
                  Entry<K,V> r = p.right;
                  p.right = r.left;
                  //將節(jié)點p作為節(jié)點r的父節(jié)點
                  if (r.left != null)
                      r.left.parent = p;
                  //將節(jié)點p的父節(jié)點和r的父節(jié)點指向同一處
                  r.parent = p.parent;
                  //p的父節(jié)點為null,則將節(jié)點r設(shè)置為root
                  if (p.parent == null)
                      root = r;
                  //如果節(jié)點p是左子節(jié)點,則將該左子節(jié)點替換為節(jié)點r
                  else if (p.parent.left == p)
                      p.parent.left = r;
                  //如果節(jié)點p為右子節(jié)點,則將該右子節(jié)點替換為節(jié)點r
                  else
                      p.parent.right = r;
                  //重新建立p與r的關(guān)系
                  r.left = p;
                  p.parent = r;
              }
          }

          就算是看了上面的注釋還是并不清晰,看下圖你就懂了

          五. get 方法

          get方法是通過二分查找的思想,我們看一下源碼

          public V get(Object key) {
              Entry<K,V> p = getEntry(key);
              return (p==null ? null : p.value);
          }
          /**
           * 從root節(jié)點開始遍歷,通過二分查找逐步向下找
           * 第一次循環(huán):從根節(jié)點開始,這個時候parent就是根節(jié)點,然后通過k.compareTo(p.key)比較傳入的key和
           * 根節(jié)點的key值;
           * 如果傳入的key<root.key, 那么繼續(xù)在root的左子樹中找,從root的左孩子節(jié)點(root.left)開始;
           * 如果傳入的key>root.key, 那么繼續(xù)在root的右子樹中找,從root的右孩子節(jié)點(root.right)開始;
           * 如果恰好key==root.key,那么直接根據(jù)root節(jié)點的value值即可。
           * 后面的循環(huán)規(guī)則一樣,當(dāng)遍歷到的當(dāng)前節(jié)點作為起始節(jié)點,逐步往下找
           */

          //默認(rèn)排序情況下的查找
          final Entry<K,V> getEntry(Object key) {
              
              if (comparator != null)
                  return getEntryUsingComparator(key);
              if (key == null)
                  throw new NullPointerException();
              @SuppressWarnings("unchecked")
              Comparable<? super K> k = (Comparable<? super K>) key;
              Entry<K,V> p = root;
              while (p != null) {
                  int cmp = k.compareTo(p.key);
                  if (cmp < 0)
                      p = p.left;
                  else if (cmp > 0)
                      p = p.right;
                  else
                      return p;
              }
              return null;
          }
          /**
           * 從root節(jié)點開始遍歷,通過二分查找逐步向下找
           * 第一次循環(huán):從根節(jié)點開始,這個時候parent就是根節(jié)點,然后通過自定義的排序算法
           * cpr.compare(key, t.key)比較傳入的key和根節(jié)點的key值,如果傳入的key<root.key,那么
           * 繼續(xù)在root的左子樹中找,從root的左孩子節(jié)點(root.left)開始:如果傳入的key>root.key,
           * 那么繼續(xù)在root的右子樹中找,從root的右孩子節(jié)點(root.right)開始;如果恰好key==root.key,
           * 那么直接根據(jù)root節(jié)點的value值即可。
           * 后面的循環(huán)規(guī)則一樣,當(dāng)遍歷到的當(dāng)前節(jié)點作為起始節(jié)點,逐步往下找
           */

          //自定義排序規(guī)則下的查找
          final Entry<K,V> getEntryUsingComparator(Object key) {
              @SuppressWarnings("unchecked")
              K k = (K) key;
              Comparator<? super K> cpr = comparator;
              if (cpr != null) {
                  Entry<K,V> p = root;
                  while (p != null) {
                      int cmp = cpr.compare(k, p.key);
                      if (cmp < 0)
                          p = p.left;
                      else if (cmp > 0)
                          p = p.right;
                      else
                          return p;
                  }
              }
              return null;
          }

          六. remove方法

          remove方法可以分為兩個步驟,先是找到這個節(jié)點,直接調(diào)用了上面介紹的getEntry(Object key),這個步驟我們就不說了,直接說第二個步驟,找到后的刪除操作。

          public V remove(Object key) {
              Entry<K,V> p = getEntry(key);
              if (p == null)
                  return null;

              V oldValue = p.value;
              deleteEntry(p);
              return oldValue;
          }

          通過deleteEntry(p)進行刪除操作,刪除操作的原理我們在前面已經(jīng)講過
          • 刪除的是根節(jié)點,則直接將根節(jié)點置為null;
          • 待刪除節(jié)點的左右子節(jié)點都為null,刪除時將該節(jié)點置為null;
          • 待刪除節(jié)點的左右子節(jié)點有一個有值,則用有值的節(jié)點替換該節(jié)點即可;
          • 待刪除節(jié)點的左右子節(jié)點都不為null,則找前驅(qū)或者后繼,將前驅(qū)或者后繼的值復(fù)制到該節(jié)點中,然后刪除前驅(qū)或者后繼(前驅(qū):左子樹中值最大的節(jié)點,后繼:右子樹中值最小的節(jié)點);

          private void deleteEntry(Entry<K,V> p) {
              modCount++;
              size--;
           //當(dāng)左右子節(jié)點都不為null時,通過successor(p)遍歷紅黑樹找到前驅(qū)或者后繼
              if (p.left != null && p.right != null) {
                  Entry<K,V> s = successor(p);
                  //將前驅(qū)或者后繼的key和value復(fù)制到當(dāng)前節(jié)點p中,然后刪除節(jié)點s(通過將節(jié)點p引用指向s)
                  p.key = s.key;
                  p.value = s.value;
                  p = s;
              } 
              Entry<K,V> replacement = (p.left != null ? p.left : p.right);
              /**
               * 至少有一個子節(jié)點不為null,直接用這個有值的節(jié)點替換掉當(dāng)前節(jié)點,給replacement的parent屬性賦值,給
               * parent節(jié)點的left屬性和right屬性賦值,同時要記住葉子節(jié)點必須為null,然后用fixAfterDeletion方法
               * 進行自平衡處理
               */

              if (replacement != null) {
                  //將待刪除節(jié)點的子節(jié)點掛到待刪除節(jié)點的父節(jié)點上。
                  replacement.parent = p.parent;
                  if (p.parent == null)
                      root = replacement;
                  else if (p == p.parent.left)
                      p.parent.left  = replacement;
                  else
                      p.parent.right = replacement;
                  p.left = p.right = p.parent = null;
                  /**
                   * p如果是紅色節(jié)點的話,那么其子節(jié)點replacement必然為紅色的,并不影響紅黑樹的結(jié)構(gòu)
                   * 但如果p為黑色節(jié)點的話,那么其父節(jié)點以及子節(jié)點都可能是紅色的,那么很明顯可能會存在紅色相連的情
                   * 況,因此需要進行自平衡的調(diào)整
                   */

                  if (p.color == BLACK)
                      fixAfterDeletion(replacement);
              } else if (p.parent == null) {//這種情況就不用多說了吧
                  root = null;
              } else { 
                  /**
                   * 如果p節(jié)點為黑色,那么p節(jié)點刪除后,就可能違背每個節(jié)點到其葉子節(jié)點路徑上黑色節(jié)點數(shù)量一致的規(guī)則,
                   * 因此需要進行自平衡的調(diào)整
                   */
           
                  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;
                  }
              }
          }

          操作的操作其實很簡單,場景也不多,我們看一下刪除后的自平衡操作方法fixAfterDeletion

          private void fixAfterDeletion(Entry<K,V> x) {
              /**
               * 當(dāng)x不是root節(jié)點且顏色為黑色時
               */

              while (x != root && colorOf(x) == BLACK) {
                  /**
                   * 首先分為兩種情況,當(dāng)前節(jié)點x是左節(jié)點或者當(dāng)前節(jié)點x是右節(jié)點,這兩種情況下面都是四種場景,這里通過
                   * 代碼分析一下x為左節(jié)點的情況,右節(jié)點可參考左節(jié)點理解,因為它們非常類似
                   */

                  if (x == leftOf(parentOf(x))) {
                      Entry<K,V> sib = rightOf(parentOf(x));

                      /**
                       * 場景1:當(dāng)x是左黑色節(jié)點,兄弟節(jié)點sib是紅色節(jié)點
                       * 兄弟節(jié)點由紅轉(zhuǎn)黑,父節(jié)點由黑轉(zhuǎn)紅,按父節(jié)點左旋,
                       * 左旋后樹的結(jié)構(gòu)變化了,這時重新賦值sib,這個時候sib指向了x的兄弟節(jié)點
                       */

                      if (colorOf(sib) == RED) {
                          setColor(sib, BLACK);
                          setColor(parentOf(x), RED);
                          rotateLeft(parentOf(x));
                          sib = rightOf(parentOf(x));
                      }

                      /**
                       * 場景2:節(jié)點x、x的兄弟節(jié)點sib、sib的左子節(jié)點和右子節(jié)點都為黑色時,需要將該節(jié)點sib由黑變
                       * 紅,同時將x指向當(dāng)前x的父節(jié)點
                       */

                      if (colorOf(leftOf(sib))  == BLACK &&
                          colorOf(rightOf(sib)) == BLACK) {
                          setColor(sib, RED);
                          x = parentOf(x);
                      } else {
                          /**
                           * 場景3:節(jié)點x、x的兄弟節(jié)點sib、sib的右子節(jié)點都為黑色,sib的左子節(jié)點為紅色時,
                           * 需要將sib左子節(jié)點設(shè)置為黑色,sib節(jié)點設(shè)置為紅色,同時按sib右旋,再將sib指向x的
                           * 兄弟節(jié)點
                           */

                          if (colorOf(rightOf(sib)) == BLACK) {
                              setColor(leftOf(sib), BLACK);
                              setColor(sib, RED);
                              rotateRight(sib);
                              sib = rightOf(parentOf(x));
                          }
                          /**
                           * 場景4:節(jié)點x、x的兄弟節(jié)點sib都為黑色,而sib的左右子節(jié)點都為紅色或者右子節(jié)點為紅色、
                           * 左子節(jié)點為黑色,此時需要將sib節(jié)點的顏色設(shè)置成和x的父節(jié)點p相同的顏色,
                           * 設(shè)置x的父節(jié)點為黑色,設(shè)置sib右子節(jié)點為黑色,左旋x的父節(jié)點p,然后將x賦值為root
                           */

                          setColor(sib, colorOf(parentOf(x)));
                          setColor(parentOf(x), BLACK);
                          setColor(rightOf(sib), BLACK);
                          rotateLeft(parentOf(x));
                          x = root;
                      }
                  } else {//x是右節(jié)點的情況
                      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);
          }

          當(dāng)待操作節(jié)點為左節(jié)點時,上面描述了四種場景,而且場景之間可以相互轉(zhuǎn)換,如deleteEntry后進入了場景1,經(jīng)過場景1的一些列操作后,紅黑樹的結(jié)構(gòu)并沒有調(diào)整完成,而是進入了場景2,場景2執(zhí)行完成后跳出循環(huán),將待操作節(jié)點設(shè)置為黑色,完成。
          我們下面用圖來說明一下四種場景幫助理解,當(dāng)然大家最好自己手動畫一下。

          場景1:

          當(dāng)x是左黑色節(jié)點,兄弟節(jié)點sib是紅色節(jié)點,需要兄弟節(jié)點由紅轉(zhuǎn)黑,父節(jié)點由黑轉(zhuǎn)紅,按父節(jié)點左旋,左旋后樹的結(jié)構(gòu)變化了,這時重新賦值sib,這個時候sib指向了x的兄弟節(jié)點。
          但經(jīng)過這一系列操作后,并沒有結(jié)束,而是可能到了場景2,或者場景3和4

          場景2:

          節(jié)點x、x的兄弟節(jié)點sib、sib的左子節(jié)點和右子節(jié)點都為黑色時,需要將該節(jié)點sib由黑變紅,同時將x指向當(dāng)前x的父節(jié)點
          經(jīng)過場景2的一系列操作后,循環(huán)就結(jié)束了,我們跳出循環(huán),將節(jié)點x設(shè)置為黑色,自平衡調(diào)整完成。

          場景3:

          節(jié)點x、x的兄弟節(jié)點sib、sib的右子節(jié)點都為黑色,sib的左子節(jié)點為紅色時,需要將sib左子節(jié)點設(shè)置為黑色,sib節(jié)點設(shè)置為紅色,同時按sib右旋,再將sib指向x的兄弟節(jié)點
          并沒有完,場景3的一系列操作后,會進入到場景4

          場景4:

          節(jié)點x、x的兄弟節(jié)點sib都為黑色,而sib的左右子節(jié)點都為紅色或者右子節(jié)點為紅色、左子節(jié)點為黑色,此時需要將sib節(jié)點的顏色設(shè)置成和x的父節(jié)點p相同的顏色,設(shè)置x的父節(jié)點顏色為黑色,設(shè)置sib右孩子的顏色為黑色,左旋x的父節(jié)點p,然后將x賦值為root
          四種場景講完了,刪除后的自平衡操作不太好理解,代碼層面的已經(jīng)弄明白了,但如果讓我自己去實現(xiàn)的話,還是差了一些,還需要再研究。

          七. 遍歷

          遍歷比較簡單,TreeMap的遍歷可以使用map.values(), map.keySet(),map.entrySet(),map.forEach(),這里不再多說。

          八. 總結(jié)

          本文詳細(xì)介紹了TreeMap的基本特點,并對其底層數(shù)據(jù)結(jié)構(gòu)紅黑樹進行了回顧,同時講述了其自動排序的原理,并從源碼的角度結(jié)合紅黑樹圖形對put方法、get方法、remove方法進行了講解,最后簡單提了一下遍歷操作,若有不對之處,請批評指正,望共同進步,謝謝!
          <END>

          推薦閱讀:

          【143期】你知道 Java 是如何實現(xiàn)線程間通信的嗎?

          【142期】阿里面試:分析為什么B+樹更適合作為索引的結(jié)構(gòu)以及索引原理

          【138期】面試官:談?wù)劤S玫腎terator中hasNext()、next()、remove()方法吧

          5T技術(shù)資源大放送!包括但不限于:C/C++,Linux,Python,Java,PHP,人工智能,單片機,樹莓派,等等。在公眾號內(nèi)回復(fù)「2048」,即可免費獲取!!

          微信掃描二維碼,關(guān)注我的公眾號

          朕已閱 

          瀏覽 41
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  天天视频免费入口 | 91久久婷婷国产 | 操逼片儿| 一级片在线直播 | 国产一区二区三区在线视频 |