看圖輕鬆理解資料結構與演算法系列(B+樹)

超人汪小建發表於2018-09-06

前言

推出一個新系列,《看圖輕鬆理解資料結構和演算法》,主要使用圖片來描述常見的資料結構和演算法,輕鬆閱讀並理解掌握。本系列包括各種堆、各種佇列、各種列表、各種樹、各種圖、各種排序等等幾十篇的樣子。

B+樹

B+樹是B樹的一種變體,也屬於平衡多路查詢樹,大體結構與B樹相同,包含根節點、內部節點和葉子節點。多用於資料庫和作業系統的檔案系統中,由於B+樹內部節點不儲存資料,所以能在記憶體中存放更多索引,增加快取命中率。另外因為葉子節點相連遍歷操作很方便,而且資料也具有順序性,便於區間查詢。

B+樹特點

  • B+樹可以定義一個m值作為預定範圍,即m路(階)B+樹。
  • 根節點可能是葉子節點,也可能是包含兩個或兩個以上子節點的節點。
  • 內部節點如果擁有k個關鍵字則有k+1個子節點。
  • 非葉子節點不儲存資料,只儲存關鍵字用作索引,所有資料都儲存在葉子節點中。
  • 非葉子節點有若干子樹指標,如果非葉子節點關鍵字為k1,k2,...kn,其中n=m-1,那麼第一個子樹關鍵字判斷條件為小於k1,第二個為大於等於k1而小於k2,以此類推,最後一個為大於等於kn,總共可以劃分出m個區間,即可以有m個分支。(判斷條件其實沒有嚴格的要求,只要能實現對B+樹的資料進行定位劃分即可,有些實現使用了m個關鍵字來劃分割槽間,也是可以的)
  • 所有葉子節點通過指標鏈相連,且葉子節點本身按關鍵字的大小從小到大順序排列。
  • 自然插入而不進行刪除操作時,葉子節點項的個數範圍為[floor(m/2),m-1],內部節點項的個數範圍為[ceil(m/2)-1,m-1]。
  • 另外通常B+樹有兩個頭指標,一個指向根節點一個指向關鍵字最小的葉子節點。
  • 在進行刪除操作時,涉及到索引節點填充因子和葉子節點填充因子,一般可設葉子節點和索引節點的填充因子都不少於50%。

以下是一棵4階B+樹,

image

插入操作

假設現在構建一棵四階B+樹,開始插入“A”,直接作為根節點,

image

插入“B”,大於“A”,放右邊,

image

插入“C”,按順序排到最後,

image

繼續插入“D”,直接新增的結果如下圖,此時超過了節點可以存放容量,對於四階B+樹每個節點最多存放3個項,此時需要執行分裂操作,

image

分裂操作為,先選取待分裂節點中間位置的項,這裡選“C”,然後將“C”項放到父節點中,因為這裡還沒有父節點,那麼直接建立一個新的父節點存放“C”,而原來小於“C”的那些項作為左子樹,原來大於等於“C”的那些項作為右子樹。這裡注意下非葉子節點存放的都是關鍵字,用作索引的,所以父節點存放的“C”項不包括資料,資料仍然存放在右子樹。此外,還需要新增一個指標,由左子樹指向右子樹。

image

繼續插入“M”,“M”大於“C”,往右子節點,

image

分別與“C”“D”比較,大於它們,放到最右邊,

image

插入“L”,“L”大於“B”,往右子樹,

image

“L”逐一與節點內項的值比較,根據大小放到指定位置,此時觸發分裂操作,

image

選取待分裂節點中間位置的項“L”,然後將“L”項放到父節點中,按大小順序將“L”放到指定位置,而原來小於“L”的那些項作為左子樹,原來大於等於“L”的那些項作為右子樹。父節點存放的“L”項不包括資料,資料仍然存放在右子樹。此外,還需要在左子樹中新增一個指向右子樹的指標。

image

繼續插入“K”,從根節點開始查詢,逐一比較關鍵字,“K”大於“C”而小於“L”,往第二個分支,

image

在子節點中逐一比較,“K”最終落在最右邊,

image

繼續插入“J”,從根節點開始查詢,逐一比較關鍵字,“J”大於“C”而小於“L”,往第二個分支,

image

在子節點中找到“J”的相應位置,此時超過了節點的容量,需要進行分裂操作,

image

選取待分裂節點中間位置的項“J”,然後將“J”項放到父節點中,按大小順序將“J”放到指定位置,而原來小於“J”的那些項作為左子樹,原來大於等於“J”的那些項作為右子樹。父節點存放的“J”項不包括資料,資料仍然存放在右子樹。此外,還需要在左子樹中新增一個指向右子樹的指標。

image

繼續插入“I”,從根節點開始查詢,逐一比較關鍵字,“I”大於“C”而小於“J”“L”,往第二個分支,

image

逐一比較找到“I”的插入位置,

image

繼續插入“H”,從根節點開始查詢,逐一比較關鍵字,“H”大於“C”而小於“J”“L”,往第二個分支,

image

“H”逐一與節點內的值比較,根據大小放到指定位置,此時觸發分裂操作,

image

選取待分裂節點中間位置的項“H”,然後將“H”項放到父節點中,按大小順序將“H”放到指定位置,而原來小於“H”的那些項作為左子樹,原來大於等於“H”的那些項作為右子樹。父節點存放的“H”項不包括資料,資料仍然存放在右子樹。此外,還需要在左子樹中新增一個指向右子樹的指標。

但此時父節點超出了容量,父節點需要繼續分裂操作,

image

選取待分裂節點中間位置的項“J”,然後將“J”項放到父節點中,但還不存在父節點,需要建立一個作為父節點。原來小於“J”的那些項作為左子樹,原來大於“J”的那些項作為右子樹。這是非葉子節點的分裂,操作物件都是用作索引的關鍵字,不必考慮資料存放問題。

image

插入“G”,從根節點開始查詢,“G”小於“J”,往第一個分支,

image

逐一比較節點內項的值,“G”大於“C”小於“H”,往第二個分支,

image

逐一比較節點內項的值,找到“G”的位置並插入,

image

插入“F”,從根節點開始查詢,“F”小於“J”,往第一個分支,

image

逐一比較節點內項的值,“F”大於“C”小於“H”,往第二個分支,

image

逐一比較節點內項的值,找到“F”的位置並插入,此時觸發分裂操作,

image

選取待分裂節點中間位置的項“F”,然後將“F”項放到父節點中,按大小順序將“F”放到指定位置,而原來小於“F”的那些項作為左子樹,原來大於等於“F”的那些項作為右子樹。父節點存放的“F”項不包括資料,資料仍然存放在右子樹。此外,還需要在左子樹中新增一個指向右子樹的指標。

image

最後插入“E”,從根節點開始查詢,“E”小於“J”,往第一個分支,

image

逐一比較節點內項的值,“E”大於“C”小於“F”,往第二個分支,

image

逐一比較節點內項的值,找打“E”適當的位置並插入。

image

從上面插入操作可以總結,插入主要就是涉及到分裂操作,而且要注意到非節點只儲存了關鍵字作為索引,而資料都儲存在葉子節點上,此外還需要使用指標將葉子節點連線起來。最終我們可以看到葉子節點的項按從小到大排列,因為有了指標使得可以很方便遍歷資料。

查詢操作

對B+樹的查詢與B樹的查詢差不多,從根節點開始查詢,通過比較項的值找到對應的分支,然後繼續往子樹上查詢。

比如查詢“H”,“H”小於“J”,往第一個分支,

image

逐一比較節點中的項,發現應該往第四個分支,

image

逐一比較,找到“H”。

image

遍歷操作

遍歷操作首先是要先找到樹最左邊的葉子節點,然後就可以通過指標完成整棵樹的遍歷了。

從根節點開始,一直往第一個分支走,

image

繼續往第一個分支走,

image

發現已經到葉子節點了,這就是要找的遍歷的開端,

image

第一個葉子節點有兩個項,接著根據指標跳到第二個葉子節點,

image

第二個節點有三個項,根據指標繼續往下一個節點,

image

該節點有兩個項,根據指標繼續往下一個節點,

image

不斷根據指標往下,

image

往下,

image

完成整棵樹的遍歷。

image

-------------推薦閱讀------------

我的開源專案彙總(機器&深度學習、NLP、網路IO、AIML、mysql協議、chatbot)

為什麼寫《Tomcat核心設計剖析》

我的2017文章彙總——機器學習篇

我的2017文章彙總——Java及中介軟體

我的2017文章彙總——深度學習篇

我的2017文章彙總——JDK原始碼篇

我的2017文章彙總——自然語言處理篇

我的2017文章彙總——Java併發篇


跟我交流,向我提問:

看圖輕鬆理解資料結構與演算法系列(B+樹)

歡迎關注:

看圖輕鬆理解資料結構與演算法系列(B+樹)

相關文章