吐血整理各種二叉樹(BST、AVL、Red Black、B、B+)

前言

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

數據在計算機中的存儲結構主要爲順序存儲結構鏈式存儲結構索引存儲結構散列存儲結構,其中鏈式存儲結構最常見的示例是鏈表與樹,鏈式存儲結構主要有以下特點:

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

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

二叉樹

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

二叉樹的特點

  • 至少有一個節點(根節點)
  • 每個節點最多有兩顆子樹,即每個節點的度小於3。
  • 左子樹和右子樹是有順序的,次序不能任意顛倒。
  • 即使樹中某節點只有一棵子樹,也要區分它是左子樹還是右子樹。
    %E4%BA%8C%E5%8F%89%E6%A0%91.png?Expires=1591326930&OSSAccessKeyId=TMP.3KiHuNYYL97adcahhiN6be4zWpRPhnBXEJ5ixKYyB365uwxVD6aW2McB2Mozti7w1VVeNLkzr4yrsGHtjS5YcMRNgj5eN5&Signature=8vcX%2B1aRNRT7kWl10exUKeGFyKQ%3D

滿二叉樹

除了葉子節點外每一個節點都有兩個子節點,且所有葉子節點都在二叉樹的同一高度上。
%E6%BB%A1%E4%BA%8C%E5%8F%89%E6%A0%91.png

完全二叉樹

如果二叉樹中除去底層節點後爲滿二叉樹,且底層節點依次從左到右分佈,則此二叉樹被稱爲完全二叉樹。
%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%8F%89%E6%A0%91.png

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

二叉查找樹根節點的值大於其左子樹中任意一個節點的值,小於其右子樹中任意一節點的值,且該規則適用於樹中的每一個節點。
%E4%BA%8C%E5%8F%89%E6%9F%A5%E6%89%BE%E6%A0%91.png

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

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

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

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

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

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

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

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

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

AVL樹的特點

  • 具有二叉查找樹的特點(左子樹任一節點小於父節點,右子樹任一節點大於父節點),任何一個節點的左子樹與右子樹都是平衡二叉樹
  • 任一節點的左右子樹高度差小於1,即平衡因子爲範圍爲[-1,1]
    AVL.png
    如上左圖根節點平衡因子=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更方便平衡因子的運算

AVL-LL.png
AVL_LL.gif

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

AVL-LR.png
AVL_LR.gif

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

AVL-RR.png
AVL_RR.gif

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

AVL-RL.png
AVL_RL.gif

規律總結:

  • 失衡節點到其最底部葉子節點的高度不會超過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爲根的子樹

例子

AVL_delete.gif

  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即改爲黑色
    Red-Black-Tree_insert12_uncle_red.gif
  2. 插入35:父節點25是黑節點,屬2.2,直接插入
    Red-Black-Tree_insert35_parent_black.gif
  3. 插入42:父節點35是紅色節點,uncle節點12是紅色節點,屬2.3.1,將父節點35改爲黑色,祖父節點25改爲紅色,根節點保持黑色
    Red-Black-Tree_insert42_uncle_red.gif
  4. 插入38:父節點42是紅色節點,uncle節點是黑色的空節點,屬2.3.2,42§是35(g)的右節點,38(n)是42的左節點,爲RL情況,對42(p=avl.ur)右旋,再對35(g=avl.u)左旋
    Red-Black-Tree_insert38_parent_red.gif

引申疑問自答

  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
    Red-Black-Tree_del_step2_15.gif

  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進行右旋
      Red-Black-Tree_del_LL.gif

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

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

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

    • d兄弟b是黑色,且b的子節點都是黑色,則執行重新着色
      a. 如果父節點是黑色的,則由父節點開始往下重新着色
      Red-Black-Tree_del_2black_a.gif
      b. 如果b父節點p是紅色的,則不需要爲p之前的節點重新着色,只需將節點p改爲黑色(紅+雙黑=單黑)
      Red-Black-Tree_del_2black_b.gif

    • d兄弟b是紅色,則將b向上移動(b左旋或右旋),併爲b與父節點重新p着色
      如果正常順序添加上圖節點刪除節點d的兄弟b只會是黑色,需對其子節點添加一節點再刪除添加的節點是可使b變紅。
      Red-Black-Tree_del_2black_3_L.gif
      Red-Black-Tree_del_2black_3_R.gif

    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-Tree_find.gif

插入

設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的成爲右子樹
    B-Tree_Insert_2_1_and_2_2b.gif

插入流程2.2.a 示例

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

刪除

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-Tree_del_1a.gif
    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
    B-Tree_del_1b.gif
    c. nkl與nkr鍵數都少於m/2個,則合併nkl與nkr爲一個節點,刪除k,結束刪除操作。圖例:m=4, m/2=2 , 刪除k=99, k左側與右側子節點都小於m/2=2,合併兩個子節點45與100
    B-Tree_del_1c.gif
  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規則
    B-Tree_del_2.gif
    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位置
    B-Tree_del_221.gif
    2.2.2 n的左側節點nl.key數 < m/2且右側節點nr.key數 >= m/2:取刪除key指向的父節點側key下移到刪除key位置,從n的側節點取最小的key上移到父節點np.key位置,相當於2.2.1的鏡像操作
    B-Tree_del_222.gif
    2.2.3 nl.key數 < m/2 && nr.key數 < m/2 && np.key數 >= m/2:取刪除key指向的父節點側key下移到刪除key位置,將節點n與nl合併,若n爲父節點下的最左側節點,則n與nr合併。
    B-Tree_del_223.gif
    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
      B-Tree_del_224b.gif
    • d. npb的key上移到ng後同樣會使key缺失,n節點合併後也會導致np的子節點缺1,將npb缺失key連接的節點遷移到np缺失的子節點位置。若ng出現與np的情況,則以操作np的方式往上遞歸操作ng。

B-Tree_del_224.gif

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、數據節點都在葉子上
沒有多餘的搜索鍵 可能存在冗餘搜索鍵
內部節點的刪除非常複雜,並且樹必須進行大量轉換 刪除任何節點都很容易,因爲所有節點都可以在葉子上找到
插入會花費更多時間,有時無法預測 插入更容易,結果始終相同

B%2BTree.jpg

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和紅黑樹)都會假定所有數據都在主內存中,但我們必須考慮無法容納在主內存中的大量數據。當鍵的數量很大時,將以塊形式從磁盤讀取數據,與主存儲器訪問時間相比,磁盤訪問時間非常高。

結語

搞圖搞得最多最耗時的一次筆記[吐血]如有錯誤,還請指出。
%E9%99%88%E6%85%A7%E6%95%8F%E8%80%81%E5%B8%88%E8%AE%B2%E8%A7%A3_1.gif

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章