《數據結構與算法分析》B+Tree

參考資料:《數據結構與算法分析》算法

前言

基於磁盤存儲的數據檢索代價是昂貴的,如數據結構爲二叉樹,那麼磁盤訪問次數爲log2N,代價過於昂貴,故減小磁盤訪問次數成了核心問題。一種新型數據結構隨之誕生——M叉數,他的時間複雜度爲LogMN,固然跟二叉樹極端狀況下會退化成鏈表同樣,M叉樹也存在極端狀況下會退化成二叉樹的風險。因此須要約定一些規範來規避退化風險。實現這種思想的一種方法是使用B+Tree數據結構

特性

階爲M的B+Tree具備如下特性:spa

  1. 數據項存儲在樹葉上。
  2. 非樹葉節點存儲M-1個關鍵字以指示搜索的方向;關鍵字 i 表明子樹 i+1 中的最小關鍵字。
  3. 樹的根或者是一片樹葉,或者其兒子數在2M之間。
  4. 除跟外,全部非樹葉節點的兒子數在[M/2 (向上取整) ]和M之間。
  5. 全部的樹葉都在相同的深度上並有[L/2 (向上取整) ]和L之間個數據項。(L的肯定稍後描述)

例一

  M=5 L=5 (在這個例子中L和M剛好是相同的,但這不是必需的)(圖一)指針

全部的非樹葉節點的兒子數都在3和5之間(從而得知有2到4個關鍵字); 根可能只有兩個兒子。code

因爲L是5,所以每片樹葉有3到5個數據項。blog

要求節點半滿將保證B+Tree不會退化成簡單的二叉樹。事件

  每一個節點表明一個磁盤區塊,因而咱們能夠根據所存儲的項的大小肯定ML (項能夠是[關鍵字+分支指針]也能夠是[數據記錄])如:數據結構與算法

例二

  設:數據記錄總數爲1千萬,一個區塊能容納8192字節,每一個關鍵字使用32字節,每一個分支指針使用4字節,每一個數據記錄使用256字節。二叉樹

則有:搜索

  M爲228 8192 <=(32+4)* M - 32

  L爲32 8192 / 256

由M可知:

  1. 非樹葉節點存儲關鍵字數量114~227(半滿~滿)
  2. 跟節點分支指針數量2~228
  3. 非跟節點分支指針數量114~228(半滿~滿)
  4. 樹葉節點數據項個數16~32(半滿~滿)
  5. 樹葉總數625000~312500(全半滿~全滿)

非跟節點分支指針數量可知二叉樹深度範圍:

  log114N ~ log228N 3.40317.. ~ 2.96869.. 4 ~ 3

添加項

想要把57插入到圖一的B+Tree中

注意咱們可能要爲此從新組織該樹葉上的全部數據。

然而,與磁盤訪問相比(在這種狀況下它還包含一次磁盤寫),這些操做能夠忽略不計。

插入後以下(圖二):

固然,插入是57相對簡單的,由於該樹葉尚未被裝滿。

想要把55插入到圖二的B+Tree中

55想要插入其中的那片樹葉已經滿了。不過解法卻不復雜:

  因爲咱們如今有 L+1 項,所以把它們分紅兩片樹葉,這兩片樹葉保證都有所須要的記錄的最小個數。

  咱們造成兩片樹葉,每葉3項。(寫這兩片樹葉須要2次磁盤訪問,更新它們的父節點須要第3次磁盤訪問)

注意:在父節點中關鍵字和分支均發生了變化,可是這種變化是以容易計算的受控的方式處理的。

插入後以下(圖三):

分裂

雖然分裂節點是耗時的(它至少須要2次附加的磁盤寫),但它相對不多發生。

例如:若是L是32,那麼當節點被分裂時,具備16和17項的兩片樹葉分別被創建。

  對於有17項的那片樹葉,咱們能夠再執行15次插入而不用另外的分裂。

  換句話說,對於每次分裂,大體存在 L/2 次非分裂的插入。

  前面的例子中的節點分裂之因此行得通是由於其父節點的兒子數量還沒有滿員。但是若是滿員了又會怎麼樣呢?

想要把40插入到圖三的B+Tree中

此時須要把數據項35~39且如今又要包含40的樹葉拆分紅2片樹葉。

可是這將使其父節點有6個而女子,但是它只能有5個兒子。(由於M爲5)

所以,解法就要分裂這個父節點。

當父親節點被分裂時,必須更新那些關鍵字以及還有父親節點的父親的值,這就招致額外的兩次磁盤寫(從而此次插入花費5次磁盤寫)

然而,雖然因爲有大量的狀況須要考慮兒使得程序確實不那麼簡單,可是這些關鍵字仍是以受控制的方式變化。

插入後以下(圖四):

  正如這裏的情形所示,當一個非樹葉節點分裂時,它的父親將獲得一個兒子。若是圖親節點的兒子個數已經達到規定的限度怎麼辦呢?

在這種狀況下,繼續沿樹向上分裂節點直到找到一個不須要再分裂的父親節點,或者達到樹根。

  若是樹根的兒子數達到規定的限度,則分裂樹根。

若是分裂樹根,那麼咱們就獲得了兩個樹根。顯然這是不可接受的,但咱們能夠創建一個新的根,這個根以分裂後獲得的兩個樹根做爲它的兩個兒子。

這就是爲何准許樹根能夠最少有兩個兒子的特權的緣由。

這是B+Tree增長高度的惟一方式。

  不用說,一路向上分裂直到根的狀況是一種特別少見的異常事件,由於一顆具備4層的樹意味着在整個插入序列中已經被分裂了3次(假設沒有刪除發生)

  事實上,任何非樹葉節點的分裂也是至關少見的。

領養

  還有其餘一些方法處理兒子過多的狀況。一種方法是在相鄰節點有空間時把一個兒子交給該節點節點領養。

把29插入到圖四的B+Tree中

能夠把32移到下一片樹葉而騰出一個空間。

插入後以下(圖五):

這種方法要求對父節點進行修改,由於有些關鍵字收到了影響。

然而,它趨向於使得節點更滿,從而再長時間的運行中節省空間。

刪除項

  咱們能夠經過查找要刪除的項並在找到後刪除它來執行刪除操做。

  問題在於,若是被刪項的所在樹葉的數據項數已是最小值,那麼刪除後他的項數就低於最小值了。

咱們能夠經過在鄰節點自己沒有達到最小值時領養一個鄰項來矯正這種狀況。

若是鄰節點已經達到最小值,那麼能夠與該鄰節點聯合以造成一片滿葉子。

聯合

  聯合意味着其父節點失去一個兒子。

若是失去兒子的節點又引發父節點的兒子數低於最小值,那麼咱們使用相同的策略繼續進行。

這個過程能夠一直上行到根。根不可能只有一個兒子(要是容許根有一個兒子那可就愚蠢了)

如過‘領養’使得根只剩下一個兒子,那麼刪除該根並讓它的這個兒子做爲樹的新根。

這是B+Tree下降高度的惟一的方式。

想要把99從圖五的B+Tree中刪除。

因爲刪除99後,99所在的樹葉只有剩下2個數據項,違反了半滿約束(葉子節點最少容納 M/2 個數據項)

而它的鄰節點的數據項數也已是最小值3了,所以咱們把它們合併成一片有5個數據項的新葉子。

結果,他們的父節點只有2個兒子了。這時該父節點能夠從它的鄰節點領養,由於鄰節點有4個兒子。

領養的結果使得父節點和父節點的鄰節點雙方都有3個兒子。

刪除後以下(圖六):

相關文章
相關標籤/搜索