1. 程式人生 > 實用技巧 >二叉樹、紅黑樹、B&B+樹資料結構

二叉樹、紅黑樹、B&B+樹資料結構

前言

沒有必要過度關注本文中二叉樹的增刪改導致的結構改變,規則操作什麼的瞭解一下就好,看不下去就跳過,本文過多的XX樹操作圖片純粹是為了作為規則記錄,該文章主要目的是增強下個人對各種常用XX樹的設計及緣由的瞭解,也從中瞭解到常用的實現案例使用XX樹實現的原因。

資料在計算機中的儲存結構主要為順序儲存結構鏈式儲存結構索引儲存結構雜湊儲存結構,其中鏈式儲存結構最常見的示例是連結串列與樹,鏈式儲存結構主要有以下特點:

  • 優點:邏輯相鄰的節點物理上不必相鄰,插入、刪除靈活,只需改變節點中的指標指向
  • 缺點:儲存空間利用率低,需通過指標維護節點間的邏輯關係;查詢效率比順序儲存慢

度:當前節點下的子節點個數

二叉樹

二叉樹是每個節點最多有兩個子樹的樹結構,左側子樹節點稱為“左子樹”(left subtree),右側子樹節點稱為“右子樹”(right subtree)。每個節點最多有2個子節點的樹(即每個定點的度小於3)。

二叉樹的特點

  • 至少有一個節點(根節點)
  • 每個節點最多有兩顆子樹,即每個節點的度小於3。
  • 左子樹和右子樹是有順序的,次序不能任意顛倒。
  • 即使樹中某節點只有一棵子樹,也要區分它是左子樹還是右子樹。

滿二叉樹

除了葉子節點外每一個節點都有兩個子節點,且所有葉子節點都在二叉樹的同一高度上。

完全二叉樹

如果二叉樹中除去底層節點後為滿二叉樹,且底層節點依次從左到右分佈,則此二叉樹被稱為完全二叉樹。

二叉查詢樹(Binary Search Tree - BST,又稱二叉排序樹、二叉搜尋樹)

二叉查詢樹根節點的值大於其左子樹中任意一個節點的值,小於其右子樹中任意一節點的值,且該規則適用於樹中的每一個節點。

二叉查詢樹的查詢效率介於O(log n)~O(n)之間,理想的排序情況下查詢效率為O(log n),極端情況下BST就是一個連結串列結構(如下圖),此時元素查詢的效率相等於連結串列查詢O(n)。

二叉查詢樹需要注意的是刪除節點操作時的不同情況,刪除節點根據節點位置會有以下三種情況:

  • 刪除節點的度為0,則直接刪除
  • 刪除節點的度為1,則該子節點替代刪除節點
  • 刪除節點的度為2,則從左子樹中尋找值最大的節點替代刪除節點。對樹結構改動最少、節點值最進行刪除節點值的必然是左子樹中的最大葉子節點值與右子樹中的最小葉子節點值

為什麼不用右子樹中的最小葉子節點值取代刪除節點?個人認為是為了維持範圍值(純屬臆測):

  • 右子樹中的最小葉子節點值大於刪除節點左子樹中的所有節點,但若該葉子節點比刪除節點大很多,這將會大大擴大左子樹的範圍值,左子樹可插入的範圍值也會大大增大,對左子樹的查詢效率造成較大的影響
  • 左子樹中的最大葉子節點值也大於刪除節點左子樹中其它所有的節點,雖然是使用該節點替代刪除節點會縮小的左子樹的值範圍,但也減少左子樹的插入範圍值,對左子樹的查詢影響不大

由上可以看出,二叉查詢樹(BST)無法根據節點的結構改變(新增或刪除)動態平衡樹的排序結構,也因此對某些操作的效率造成一定的影響,而AVL樹在BST的結構特點基礎上添加了旋轉平衡功能解決了這些問題。

平衡二叉搜尋樹 (Balanced binary search trees,又稱AVL樹、平衡二叉查詢樹)

AVL樹是最早被髮明的自平衡二叉搜尋樹,樹中任一節點的兩個子樹的高度差最大為1,所以它也被稱為高度平衡樹,其查詢、插入和刪除在平均和最壞情況下的時間複雜度都是O(log n)。

平衡二叉搜尋樹由Adelson-Velskii和Landis在1962年提出,因此又被命名為AVL樹。平衡因子(平衡係數)是AVL樹用於旋轉平衡的判斷因子,某節點的左子樹與右子樹的高度(深度)差值即為該節點的平衡因子。

AVL樹的特點

  • 具有二叉查詢樹的特點(左子樹任一節點小於父節點,右子樹任一節點大於父節點),任何一個節點的左子樹與右子樹都是平衡二叉樹
  • 任一節點的左右子樹高度差小於1,即平衡因子為範圍為[-1,1] 如上左圖根節點平衡因子=1,為AVL樹;右圖根節點平衡因子=2,固非AVL樹,只是BST。

為什麼選擇AVL樹而不是BST?

大多數BST操作(如搜尋、最大值、最小值、插入、刪除等)的時間複雜度為O(h),其中h是BST的高度。對於極端情況下的二叉樹,這些操作的成本可能變為O(n)。如果確保每次插入和刪除後樹的高度都保持O(log n),則可以保證所有這些操作的效率都是O(log n)。

節點插入、旋轉

AVL樹插入節點的如下:

  1. 根據BST入邏輯將新節點插入樹中
  2. 從新節點往上遍歷檢查每個節點的平衡因子,若發現有節點平衡因子不在[-1,1]範圍內(即失衡節點u),則通過旋轉重新平衡以u為根的子樹

旋轉的方式:

  1. 左旋轉:用於平衡RR情況,對失衡節點u(unbalanced)及子樹進行左旋
  2. 右旋轉:用於平衡LL情況,對失衡節點u及子樹進行右旋
  3. 左右旋轉:用於平衡LR情況,對失衡節點失衡u的左子節點ul左旋,再對失衡節點u右旋
  4. 右左旋轉:用於平衡情況,對失衡節點u失衡方向的右子節點ur右旋,再對失衡節點u左旋

LL - 插入節點是失衡節點u左子節點ul上的左子樹節點

gif圖中的高度是從葉子節點開始計算的,因為插入節點後是從下往上檢測節點的平衡因子,所以葉子節點高度恆為1更方便平衡因子的運算

LR - 插入節點是失衡節點u左子節點ul上的右子樹節點

RR - 插入節點是失衡節點u右子節點ur上的右子樹節點

RL - 插入節點是失衡節點u右子節點ur上的左子樹節點

規律總結:

  • 失衡節點到其最底部葉子節點的高度不會超過4
  • 失衡節點哪裡不平衡就會往哪裡的反向旋轉
  • 新增的節點到失衡節點的路徑如果是一條直線(即LL或RR),則只需對失衡節點u進行反向旋轉
  • 新增的節點到失衡節點的路徑如果是一條曲線(即LR或RL),則需先對該路徑上失衡節點u的子節點(ul/ur)進行旋轉,再對失衡節點進行旋轉
  • 失衡節點u旋轉後會成為它原來子樹(ul/ur)中的一顆子樹,如果u旋轉時替代u的子樹已有u旋轉方向上的子樹,那麼該子樹會斷裂成為u的子樹(如下LR的u右旋,uls已有右子樹T2,故會T2斷裂以BST的規則重新插入成為u的子樹)
     u                               u                           uls
    / \                            /   \                        /  \ 
   ul  T3  Left Rotate (us)      uls    T3  Right Rotate(u)   ul     u
  /  \     - - - - - - - - ->    /  \      - - - - - - - ->  /      / \
T1   uls                       ul    T2                    T1      T2 T3
       \                      / 
        T2                   T1 
複製程式碼

節點刪除步驟

  1. 對刪除節點D根據BST規則執行刪除
  2. 選擇平衡,該步驟與插入區別不大,從D節點往上遍歷檢查每個節點的平衡因子,若發現有節點失衡,則通過旋轉重新平衡以u為根的子樹

例子

  1. 根據BST規則刪除節點133,155替代133位置
  2. 從155位置往上檢測到100為失衡節點u,左高右低為LR情況,對u左子節點ul=37左旋,再對u節點執行右旋(可以看成對50同時插入2個子節點導致100節點失衡)

AVL樹虛擬碼

AVLTree{
    private Node root;
    
    private class Node{
        // height從葉子節點開始計算(即葉子節點恆為1,方便遍歷父節點的平衡因子計算)
        int val,height;
        Node left;
        Node right;
        public Node(int val){
            height = 1;
            this.val = val;
        }
    }
    
    public void insert(int val){
        if(root == null){
            root = new Node(val);
        } else {
            insert(root, val);
        }
    }

    /**
     * 將值val按AVL規則插入到節點樹node下
     * 1. 根據BST規則遍歷插入val節點,更新插入經過的路徑上的節點高度
     * 2. 檢測是否有失衡節點,沒有失衡則直接設定高度,失衡則旋轉再調整高度
     * @param node 插入節點到node子樹
     * @param val 插入的節點值
     */
    private insert(Node node, Integer val);
    
    /**
     * 獲取節點的失衡因子:left.height - right.height
     */
    int getBalance(Node node);
    
    /**
     * 節點左旋,調整旋轉的節點高度height
     */
    leftRotate(Node node);
    
    /**
     * 節點右旋,調整旋轉的節點高度height
     */
    rightRotate(Node node);    
}
複製程式碼

紅黑樹(Red - Black Tree)

紅黑樹是一種自平衡二叉搜尋樹(BST),且紅黑樹節點遵循以下規則:

  • 每個節點只能是紅色或黑色
  • 根節點總是黑色的
  • 紅色節點的父或子節點都必然是黑色的(兩個紅色的節點不會相連)
  • 任一節點到其所有後代NULL節點的每條路徑都具有相同數量的黑色節點
  • 每個Null節點都是黑色的

相比AVL樹

AVL樹比紅黑樹更加平衡,但AVL樹可能在插入和刪除過程中引起更多旋轉。因此,如果應用程式涉及許多頻繁的插入和刪除操作,則應首選Red Black樹(如 Java 1.8中的HashMap)。如果插入和刪除操作的頻率較低,而搜尋操作的頻率較高,則AVL樹應優先於紅黑樹。

個人引申的疑問

  1. 為什麼紅黑樹也算平衡樹呢?它的平衡因子是什麼?
  2. 為什麼AVL比紅黑樹更平衡?為什麼AVL樹插入和刪除會引起更多選擇呢?

原因可從後續的插入步驟與演示案例得出

插入節點

紅黑樹插入節點後違反的主要規則是兩個連續的紅色節點。

插入步驟:

  1. 將新節點n根據BST規則插入,且新使節點顏色為紅色

  2. 根據n的父節點p情況執行不同的操作 2.1 n沒有父節點p,即N為根,將n的顏色更改為黑色 2.2 p為黑色,直接插入 2.3 p為紅色,則根據不同的情況執行操作 2.3.1:n的uncle節點u是紅色(uncle節點:父節點p父節點下的另一節點|n祖父節點g的另一子節點)

    • a. 將節點p與節點u改為黑色
    • b. 將g的顏色改為紅色
    • c. 將祖父節點g當成節點n,對n重複b、c步驟

    2.3.2:n的uncle節點n是黑色,則會有以下案例(類同AVL樹,g=avl.u,p=avl.ul|ur):

    • LL:p是g左節點,x是p的左節點,對g進行右旋
    • LR:p是g的左節點,x是p的右節點
    • RL:p是g的右節點,x是p的右節點
    • RR:p是g的右節點,x是p的左節點 調整旋轉了的節點顏色
  3. 檢查根節點,根節點為紅色則染黑

演示案例

  1. 插入12:父節點25是紅色節點,uncle節點75是紅色節點,屬插入的2.3.1情況,將父節點25改為黑色,將祖父節g點改為紅色,最後將根節點g即改為黑色
  2. 插入35:父節點25是黑節點,屬2.2,直接插入
  3. 插入42:父節點35是紅色節點,uncle節點12是紅色節點,屬2.3.1,將父節點35改為黑色,祖父節點25改為紅色,根節點保持黑色
  4. 插入38:父節點42是紅色節點,uncle節點是黑色的空節點,屬2.3.2,42(p)是35(g)的右節點,38(n)是42的左節點,為RL情況,對42(p=avl.ur)右旋,再對35(g=avl.u)左旋

引申疑問自答

  1. 紅黑樹是根據節點的值與顏色維持平衡的,即可把顏色看成平衡因子,所以即使左右子樹的高度差>=2也不一定像AVL樹一樣為了保持平衡而旋轉
  2. AVL樹的結構主要是圍繞節點值與左右子樹高度來保持平衡的,從節點值的角度考慮自然比紅黑樹更平衡,且值搜尋時AVL的效率更高,但插入與刪除較多時AVL樹旋轉操作會比紅黑樹更多,效率自然更慢

以上也是Java 8的HashMap中樹節點實現結構採用紅黑樹而不是AVL樹的原因

刪除節點

刪除節點主要違反的規則是子樹中黑色高度的更改,導致根節點到葉子路徑的黑色高度降低。

紅黑樹刪除時一個比較複雜的過程,為了更容易的理解刪除過程,可以使用雙黑概念去簡化理解該過程。 雙黑概念指當刪除黑色節點後使用了另一個黑色節點替代刪除節點的位置(也可以當成有兩個null的黑色葉子節點因刪除重疊成1個),這也意味著根節點到替代節點的原路徑上少了一個黑色節點導致違反了到任一葉子節點路徑上含相同的黑色節點數的節點規則(黑色)。當刪除時出現雙黑情況,則需要通過旋轉將節點轉換為單黑色(重疊的兩個黑色null節點重新鋪展為2個)。

刪除步驟

  1. 執行標準的BST刪除,設刪除節點為d(delete),替代節點為r(replace)

  2. 如果替換節點r或刪除節點d其中一個為紅色,則將替換節點r標記為黑色(因d是r的父級,紅黑樹不允許兩個連續紅色節點),結束刪除流程,否則進行步驟3

  3. r和d都是黑色,則按以下步驟進行操作 3.1 將r塗為雙黑色(注:如果d是含值葉子節點,那麼Null節點替代也是雙黑) 3.2 若r不是根節點,設d的同級兄弟節點為b(brother,類似avl.ul|avl.ur),b的父節點為p(parent,類似avl.u),根據以下三種不同的情況進行相應的操作:

    • d兄弟b是黑色,且b至少有1個紅色的子節點,則對b進行旋轉(規律類同)。設b的紅色子節點為r(類似avl的插入節點),根據b和r的位置,可以將這種情況分為四個子情況(LL、LR、RL、RR): LL:b是其父節點(類似avl.u)的左子節點(類似avl.ul),r是b的左子節點或b的兩個子節點都是紅色,則對p進行右旋

      LR:b是其父節點的左子節點(類似avl.ul),r是b的右子節點,則對b進行左旋,再對p進行右旋

      RR:b是其父節點的右子節點(類似avl.ur),r是b的右子節點,則對p進行左旋

      RL:b是其父節點的右子節點(類似avl.ur),r是b的左子節點,則對b進行右旋,再對p進行左旋

    • d兄弟b是黑色,且b的子節點都是黑色,則執行重新著色 a. 如果父節點是黑色的,則由父節點開始往下重新著色

      b. 如果b父節點p是紅色的,則不需要為p之前的節點重新著色,只需將節點p改為黑色(紅+雙黑=單黑)

    • d兄弟b是紅色,則將b向上移動(b左旋或右旋),併為b與父節點重新p著色 如果正常順序新增上圖節點刪除節點d的兄弟b只會是黑色,需對其子節點新增一節點再刪除新增的節點是可使b變紅。

    3.3 如果以上操作後u為根,則將其設為黑色,然後返回(完整樹的黑色高度減少1)。

B-Tree(B樹)

大多數自平衡搜尋樹(如AVL和紅黑樹)都會假定所有資料都在主記憶體中,但我們必須考慮無法容納在主記憶體中的大量資料。當鍵的數量很大時,將以塊形式從磁碟讀取資料,與主儲存器訪問時間相比,磁碟訪問時間非常高。 B樹是一種自平衡搜尋樹,設計的主要思想是減少磁碟訪問次數。大多數樹操作(增、刪、查、最大值、最小值等)都需要都需要O(h)磁碟訪問,h為樹的高度。B樹通過在節點中放置最大可能的鍵來保持B樹的高度較低。通常,B樹節點的大小保持與磁碟塊大小相等。由於B樹的高度較低,因此與平衡的二叉搜尋樹(如AVL樹、紅黑樹等)相比,大多數操作的磁碟訪問次數顯著減少。

磁碟塊是一個虛擬的概念, 是作業系統(軟體)中最小的邏輯儲存單位,作業系統與磁碟打交道的最小單位是磁碟塊。

一顆m階(m指一個節點中最多包含的子節點數)B樹特點如下:

  • 所有葉子處於同一水平位置
  • 除根節點外的每個節點都必須至少包含m/2-1個key,並且最多具有m-1個key,除根以外的所有非葉子節點必須至少具有m/2個子節點
  • 節點的子節點數等於節點的key數加1
  • 節點的所有key都按鍵值升序排序,兩個鍵k1和k2之間的子key包含k1和k2範圍內的所有鍵
  • 與其他平衡二叉搜尋樹一樣,搜尋、插入和刪除的時間複雜度為O(log n) p.s:B樹由術語最小度t定義,t的值取決於磁碟塊的大小,數值上m = 2t

搜尋

B-樹搜尋類似於搜尋二叉樹,演算法與遞迴演算法相似。在B樹中,搜尋過程也是從根節點開始,通過與節點key值比較進行搜尋,搜尋操作的時間複雜度為O(log n)。具體的搜尋步驟如下:

  1. 將搜尋值與樹中根節點的第一個key進行比較
  2. 匹配則顯示“找到給定節點”並結束搜尋,否則進入步驟3
  3. 檢查搜尋值是大於還是小於當前key值
    • 搜尋值小於當前key:左子樹中獲取第一個key進行比較,重複2、3步驟
    • 搜尋值大於當前key:將搜尋值與同一節點中的下一個key進行比較,重複2、3步驟,直到精確匹配,或搜尋值與葉子節點中的最後一個key值相比較
  4. 如果葉節點中的最後一個鍵值也不匹配,則顯示“找不到元素”並結束搜尋

插入

設B樹的階為m,則插入流程如下:

  1. 如果樹為空,則建立一個具有新鍵值的新節點,並將其作為根節點插入到樹中,結束插入流程。
  2. 如果樹不為空,則從根節點開始根據BST邏輯找到適合新增新鍵值的節點P,根據節點P的鍵空間情況(key數量 < m - 1,則key未滿)進行不同的操作 2.1 節點P鍵未滿:將新元素由小到大升序排序的方式新增到節點P的key中,結束插入流程 2.2 節點P鍵已滿,則根據P是否為根節點進行相應的操作:
    • a. 節點P非根節點:向父節點插入P的key中間值來拆分節點P(中間值按最小的傳送),重複該操作,直到將傳送值固定到節點中為止。若傳送到根節點使根節點鍵溢位,則執行步驟b
    • b. 節點P為根節點:根節點P的中間key值將成為樹的新根節點,該key左右的key值成為根節點的左右子樹,樹的高度將增加一

4階B樹插入示例

以下示例皆為4階B樹(m=4),則有以下規則:

  • 4階B樹的每個節點的最多key數目為3,最小key數目為1(m = 4, max_key_num = m - 1 =3, min_key_num = m/2 - 1 =1)
  • 除根節點外的每個節點至少包含2個子節點,最多包含4個子節點(m = 4, min_sub_num = m/2 = 2)

插入流程 2.1 & 2.2.b 示例

  1. 插入125(插入流程2.1):根節點P未滿,按升序將50插入到P
  2. 插入50(插入流程2.2.b):根節點P已滿,將最小的中間值100獨立為新的根節點,小於100的成為左子樹,大於100的成為右子樹

插入流程2.2.a 示例

  1. 插入115,插入節點後鍵溢位,取中間值114插入到父節點
  2. 根節點key數量溢位,取中間值112成為新的根節點,小於112的key作為112根節點的左子節點,大於112的key作為112根節點的右子節點,原插入節點位置的水平子樹成為根節點左右子節點的子節點

刪除

B樹的刪除比插入要複雜得多,因為我們可以從任何節點(不僅是葉子)中刪除key,而且從內部節點刪除key時,我們將不得不重新排列節點的子節點。 從B樹中刪除鍵的各種情況(設刪除鍵k所在節點為n):

  1. k所在節點n為樹中節點(非葉子節點也非根節點),則根據以下不同的情況執行子節點key上移或合併完成刪除操作 a. 節點n中在k之前的子節點kln(key left node)鍵數至少有m/2個,則在kln節點中查詢最接近k的鍵k0,將k0替換k,結束刪除操作。圖例:m=4, m/2=2 , 刪除k=75,k0=55 b. 節點n中在k之前的子節點kln鍵數少於m/2個,且k後的子節點krn(key的右側節點)鍵數至少有m/2個,則在krn節點中查詢最接近k的鍵k0,將k0替換k,結束刪除操作。圖例:m=4, m/2=2 , 刪除k=55,k0=88 c. nkl與nkr鍵數都少於m/2個,則合併nkl與nkr為一個節點,刪除k,結束刪除操作。圖例:m=4, m/2=2 , 刪除k=99, k左側與右側子節點都小於m/2=2,合併兩個子節點45與100
  2. k所在節點n為葉子節點,則根據葉子節點n的key數是否少於m/2進行不同的刪除操作 2.1 n.key數 >= m/2:則直接刪除k(B樹非根節點需至少包含m/2-1個key),結束刪除流程。圖例:m=4,m/2=2,刪除k=5,所在節點n,key數=2,刪除後節點key數為1,符合至少m/2-1個key規則 2.2 n.key數 < m/2 , 即n.key數=m/2 -1:刪除key,刪除後檢查父節點與同父節點的相鄰節點key數目進行相應的節點遷移以確保所有節點符合至少m/2-1個key規則,。 2.2.1 n的左側節點nl.key數 >= m/2:取刪除key指向的父節點側key下移到刪除key位置,從n的側節點取最大的key上移到父節點np.key位置 2.2.2 n的左側節點nl.key數 < m/2且右側節點nr.key數 >= m/2:取刪除key指向的父節點側key下移到刪除key位置,從n的側節點取最小的key上移到父節點np.key位置,相當於2.2.1的映象操作 2.2.3 nl.key數 < m/2 && nr.key數 < m/2 && np.key數 >= m/2:取刪除key指向的父節點側key下移到刪除key位置,將節點n與nl合併,若n為父節點下的最左側節點,則n與nr合併。 2.2.4 nl.key數 < m/2 && nr.key數 < m/2 && np.key數 = m/2 -1:最麻煩的一種情況,具體步驟如下。
    • a. 取刪除key指向的父節點np.key下移到刪除key位置,將節點n與同父節點np下的相鄰節點合併
    • b. 下移後父節點np.key數必然少於m/2-1,np從其父節點ng獲取最接近下移np.key的鍵ng.key
    • c. ng.key下移後會導致ng與np節點的相連key缺失,根據BST規則父節點的key比np及其子節點下的key值都要大或都要小,從np同父節點ng下的key數>2的相鄰節點且npb(先左後右)取靠近np一側的npb.key替代ng的缺失key;若npg.key數 < m/2,則將npg與n節點合併,結束刪除流程,反之進入步驟c
    • d. npb的key上移到ng後同樣會使key缺失,n節點合併後也會導致np的子節點缺1,將npb缺失key連線的節點遷移到np缺失的子節點位置。若ng出現與np的情況,則以操作np的方式往上遞迴操作ng。

B+ Tree(B+樹)

實現動態多級索引時,通常會採用B樹和B+樹的資料結構。但是,B樹有一個缺點是它將與特定鍵值對應的資料指標(指向包含鍵值的磁碟檔案塊的指標)以及該鍵值儲存在B樹的節點中。該設計大大減少了可壓縮到B樹節點中的條目數,從而增加了B樹中級別數與記錄的搜尋時間。 B+樹通過僅在樹的葉節點處儲存資料指標來消除上述B樹的缺點,因而B+樹的葉節點結構與B樹的內部節點結構完全不同。資料指標在B+樹中僅存在於葉節點,因此葉節點必須將所有鍵值及其對應的資料指標儲存到磁碟檔案塊以便訪問。此外,葉節點也用於連結以提供對記錄的有序訪問。因此,葉節點才是第一級索引,而內部節點只是索引到其它級別索引的多層索引。葉節點的一些鍵值也出現在內部節點中,主要是作為簡化搜尋記錄的一種媒介。 B+樹與具有同級的B樹相比,具有同級的B+樹可以在其內部節點中儲存更多鍵,顯著改善對任何給定關鍵字的搜尋時間,同樣的鍵數B+樹級別較低且含指向下一個節點的指標P的存在使B+樹在從磁碟訪問記錄時非常快速有效。如設B樹與B+樹某一級別內部節點都有100K的容量,由於B樹的節點除了存鍵和資料指標,所以實際存的鍵容量連一半50K可能都沒有,但B+樹的100K容量都用於存鍵了,所以索引自然更高效。

B樹與B+樹的區別

B樹B+樹
所有節點都有資料指標 資料指標集中在葉節點
葉節點不儲存為連結串列結構 葉節點儲存為連結串列結構
並非所有鍵都在葉節點上,搜尋需花費更多時間(重複中序遍歷) 所有鍵都在葉節點上,搜尋更快更準確(根據key找到大致葉節點後基於葉節點的連結串列查詢)
樹中不會有重複鍵 鍵重複出現,且所有key、資料節點都在葉子上
沒有多餘的搜尋鍵 可能存在冗餘搜尋鍵
內部節點的刪除非常複雜,並且樹必須進行大量轉換 刪除任何節點都很容易,因為所有節點都可以在葉子上找到
插入會花費更多時間,有時無法預測 插入更容易,結果始終相同

XX樹實現案例

資料結構實現案例理由
紅黑樹 JDK 1.8的HashMap HashMap的增刪操作多,相比AVL樹使用紅黑樹實現可以減少樹的旋轉操作
B-Tree MongoDB索引 1. 普通的二叉樹或平衡樹無法支撐資料庫的大資料量(參考B-Tree簡介)
2. MongoDB是非關係型聚合資料庫,B樹恰好將鍵欄位和資料欄位聚合在一起,而B+樹中的內部節點不儲存資料,葉節點間連結串列連線的優勢在MongoDB的JSON資料格式面前也不明顯
3. B樹所有節點都有資料指標,MongoDB存Mongodb使用B樹只要找到指定的索引,就可進行資料訪問,避免了葉節點的訪問。
B+Tree MySQL索引 關係型資料庫最常用的是資料遍歷與範圍操作,基於B-Tree的設計理由與B-Tree的缺點,B+樹所有資料都儲存在葉節點中,並且通過指標串在一起,因此很容易進行間隔遍歷甚至或遍歷

B-Tree緣由:大多數自平衡搜尋樹(如AVL和紅黑樹)都會假定所有資料都在主記憶體中,但我們必須考慮無法容納在主記憶體中的大量資料。當鍵的數量很大時,將以塊形式從磁碟讀取資料,與主儲存器訪問時間相比,磁碟訪問時間非常高。

結語

搞圖搞得最多最耗時的一次筆記[吐血]如有錯誤,還請指出。

**************轉摘:https://juejin.im/post/6844904205526777864