B+樹要點梳理

by_mzy發表於2024-07-15

B+樹重要操作

中間節點

  • 中間節點的key,與其對應的指標的原則是,小於key的元素在其指標指向的節點中
  • 中間節點的key可以看成是右斜著排放的,即小於等於key的節點由key對應的指標指定,最有一個指標指向大於最右側key的節點

分裂

  • 當中間節點數量滿了時,進行分裂,新生成一個相鄰的中間節點right_internal_node
  • 計算原先保留多少元素, 例如保留left_count = K/2
  • 將left_count對應的key作為upper_key後續插入到中間節點
  • 從left_count + 1下標開始,將key複製到新生成的節點中
  • 將left_count下表對應的指標留在左節點
    • 將left_count +1 下表對應的指標開始複製到新生成的節點中

分裂後插入上層中間節點

  • 經過分裂有三個重要元素:left_internal_node(也就是原先被分裂節點), right_internal_node(新生成的節點),upper_key
  • 上層中間節點為upper_internal_node
  • 注意原先upper_internal_node有一個old_key和old_ptr指標指向left_internal_node,這個old_key肯定是比upper_key大的
    • 將(upper_key, left_internal_node)作為一對插入到upper_internal_node
    • 將old_key對應的old_ptr改成指向right_internal_node

image

刪除

借入資料

中間節點刪除資料後,如果數量少於最低值K/2,那麼它可以嘗試從它的相鄰節點借入資料。那麼它的相鄰節點在哪裡呢?第一類相鄰節點是與中間節點共享一個父節點的節點,很容易透過其父節點找到。第二類相鄰節點,不與其共享一個父節點:想象一下當節點是父節點的最左邊節點時,那麼在其父節點下,它只有一個右側相鄰節點,這個時候它的左側相鄰節點不在其父節點下;當節點是父節點的最右邊節點時,那麼在其父節點下,它只有一個左側相鄰節點,這時候它的右側相鄰節點不在其父節點下,而在右側樹分支下。

image

  • 注意第二類的借入在實現的時候也可以不考慮,只不過這樣會過早的帶來節點合併。

節點合併

當節點確實從左右都沒有節點可以借入資料時,那麼它就可以與左右節點合併了。正因為其左右節點也沒有多的節點可以借,說明其左右節點不大於K/2,那麼合併後節點節點數量是會小於K的。

節點合併,我們可以總是選擇與其相同父節點下的相鄰節點進行合併。節點合併後,需要使父節點刪除掉被合併資料的節點(需要注意的是在實際操作中,我們可以選擇被刪除的節點最終保留下來,也可以選擇相鄰節點最終保留下來,這只是實現方式)。

降級

父節點刪除某個子節點後,需要檢查父節點的key數量是否滿足K/2要求,如果父節點不滿足,那麼父節點也需要執行借入動作,如果父節點無法接入,那麼就需要執行合併動作。並最終出發祖父節點的刪除動作。整個流程會遞迴上去最終可能會到根節點,若根節點刪除了資料,且一個key也沒有了。那麼就要將其子節點設定成新的root了(奇怪?沒有key還有子節點?注意中間節點指標比key多一個,就是說沒有key,也至少有一個指標)。

葉子節點

插入

分裂

  • 當葉子節點數量滿了時,進行分裂,新生成一個相鄰sibling節點
  • 計算原先保留多少元素, 例如保留left_count = K/2
  • 將下標left_count開始的key移動到新生成的節點
    • 將left_count對應的key作為upper_key後續插入到中間節點
  • 將下標left_count開始的value移動到新生成的節點

分裂後插入上層中間節點

  • 新分裂出的節點的第一個key為upper_key
  • 如果葉子節點沒有上層中間節點,那麼就新建一箇中間節點作為根節點,把原先葉子節點以及新分裂出的葉子節點直接插入到新的中間節點。同時將upper_key插入到新中間節點中。即它有1個key,兩個指標。
  • 如果有上層中間節點
    • 首先將upper_key插入到中間節點
    • 其次將新分裂出的節點指標也插入到中間節點,這裡需要注意的是由於中間節點的指標數量比key數量多一個。所以指標插入的index要比upper_key插入的index大1
      image

刪除

  • 刪除後,嘗試從其右側的節點借資料,前提是右側節點包含m/2 +1個元素
  • 若節點(且該節點不是中間節點的最左側節點)最左邊一個元素被刪除了,則需要更新中間對應的key

借入資料

借入資料的流程是簡單的,與中間節點類似。找到其相鄰節點並拿到資料。同樣需要注意的是它的相鄰節點也可能不在其父節點下。

節點合併

與中間節點的操作類似,找到相鄰節點,複製資料。通知父節點刪除一個節點即可。

要記錄哪些指標

  • 從B+樹的定義上來看,至少每個葉子節點需要記錄一個指向右側相鄰節點的指標

其他指標要不要記錄呢?

  • 節點是否要記錄父節點指標?
  • 中間節點是否要記錄右側節點的指標?
  • 節點是否要記錄其左側節點的指標?

這個因設計實現而異了,記錄更多的指標,那麼在尋找定位的時候肯定更加容易,但是也帶來了很多維護成本,這個在設計的時候做考量。

如何找到相鄰的節點

相同父節點下的相鄰節點

很容易,很自然,進行遍歷就可以或者對key進行find或二分法(注意所有節點裡面的key都是有序的)

非相同父節點下的相鄰節點

  • 最節點的方式是記錄節點(包括中間節點)的左側、右側指標

另外一種方式,如果不想過多的記錄指標,那麼可以透過搜尋回溯的方式來找目標節點到相鄰節點。 某個最左側中間節點為例:

  • 從它開始往上回溯,當回溯到的節點不是其父節點的最左側節點時,說明我們已經找到了共同祖先
  • 然後從共同祖先中的左側節點(對應下圖1的左側節點3)開始往下找其最右側的子節點,當往下搜尋的層數與向上回溯的層數相同時,我們就找到了目標節點的相鄰節點。
  • 最右側中間節點的搜尋過程是類似的。
    image

例如想找到節點5的做相鄰節點4,就往上回溯首先到1, 5是1的最左側節點,則繼續向上到2,1不是2的最左側節點,所有2就是共同祖先了。1的左側節點3往下,找到最右側節點4, 4與5是相同層級的,那麼就是5的相鄰左節點了。

另外一個問題是如何進行回溯呢?一種實現方式是子節點記錄父節點的指標;另一種實現方式是,記錄搜尋過程的搜尋棧(即從根節點到葉子節點經過了哪些中間節點,記錄到一個列表裡面)。

相關文章