前言
推出一個新系列,《看圖輕鬆理解資料結構和演算法》,主要使用圖片來描述常見的資料結構和演算法,輕鬆閱讀並理解掌握。本系列包括各種堆、各種佇列、各種列表、各種樹、各種圖、各種排序等等幾十篇的樣子。
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+樹,
插入操作
假設現在構建一棵四階B+樹,開始插入“A”,直接作為根節點,
插入“B”,大於“A”,放右邊,
插入“C”,按順序排到最後,
繼續插入“D”,直接新增的結果如下圖,此時超過了節點可以存放容量,對於四階B+樹每個節點最多存放3個項,此時需要執行分裂操作,
分裂操作為,先選取待分裂節點中間位置的項,這裡選“C”,然後將“C”項放到父節點中,因為這裡還沒有父節點,那麼直接建立一個新的父節點存放“C”,而原來小於“C”的那些項作為左子樹,原來大於等於“C”的那些項作為右子樹。這裡注意下非葉子節點存放的都是關鍵字,用作索引的,所以父節點存放的“C”項不包括資料,資料仍然存放在右子樹。此外,還需要新增一個指標,由左子樹指向右子樹。
繼續插入“M”,“M”大於“C”,往右子節點,
分別與“C”“D”比較,大於它們,放到最右邊,
插入“L”,“L”大於“B”,往右子樹,
“L”逐一與節點內項的值比較,根據大小放到指定位置,此時觸發分裂操作,
選取待分裂節點中間位置的項“L”,然後將“L”項放到父節點中,按大小順序將“L”放到指定位置,而原來小於“L”的那些項作為左子樹,原來大於等於“L”的那些項作為右子樹。父節點存放的“L”項不包括資料,資料仍然存放在右子樹。此外,還需要在左子樹中新增一個指向右子樹的指標。
繼續插入“K”,從根節點開始查詢,逐一比較關鍵字,“K”大於“C”而小於“L”,往第二個分支,
在子節點中逐一比較,“K”最終落在最右邊,
繼續插入“J”,從根節點開始查詢,逐一比較關鍵字,“J”大於“C”而小於“L”,往第二個分支,
在子節點中找到“J”的相應位置,此時超過了節點的容量,需要進行分裂操作,
選取待分裂節點中間位置的項“J”,然後將“J”項放到父節點中,按大小順序將“J”放到指定位置,而原來小於“J”的那些項作為左子樹,原來大於等於“J”的那些項作為右子樹。父節點存放的“J”項不包括資料,資料仍然存放在右子樹。此外,還需要在左子樹中新增一個指向右子樹的指標。
繼續插入“I”,從根節點開始查詢,逐一比較關鍵字,“I”大於“C”而小於“J”“L”,往第二個分支,
逐一比較找到“I”的插入位置,
繼續插入“H”,從根節點開始查詢,逐一比較關鍵字,“H”大於“C”而小於“J”“L”,往第二個分支,
“H”逐一與節點內的值比較,根據大小放到指定位置,此時觸發分裂操作,
選取待分裂節點中間位置的項“H”,然後將“H”項放到父節點中,按大小順序將“H”放到指定位置,而原來小於“H”的那些項作為左子樹,原來大於等於“H”的那些項作為右子樹。父節點存放的“H”項不包括資料,資料仍然存放在右子樹。此外,還需要在左子樹中新增一個指向右子樹的指標。
但此時父節點超出了容量,父節點需要繼續分裂操作,
選取待分裂節點中間位置的項“J”,然後將“J”項放到父節點中,但還不存在父節點,需要建立一個作為父節點。原來小於“J”的那些項作為左子樹,原來大於“J”的那些項作為右子樹。這是非葉子節點的分裂,操作物件都是用作索引的關鍵字,不必考慮資料存放問題。
插入“G”,從根節點開始查詢,“G”小於“J”,往第一個分支,
逐一比較節點內項的值,“G”大於“C”小於“H”,往第二個分支,
逐一比較節點內項的值,找到“G”的位置並插入,
插入“F”,從根節點開始查詢,“F”小於“J”,往第一個分支,
逐一比較節點內項的值,“F”大於“C”小於“H”,往第二個分支,
逐一比較節點內項的值,找到“F”的位置並插入,此時觸發分裂操作,
選取待分裂節點中間位置的項“F”,然後將“F”項放到父節點中,按大小順序將“F”放到指定位置,而原來小於“F”的那些項作為左子樹,原來大於等於“F”的那些項作為右子樹。父節點存放的“F”項不包括資料,資料仍然存放在右子樹。此外,還需要在左子樹中新增一個指向右子樹的指標。
最後插入“E”,從根節點開始查詢,“E”小於“J”,往第一個分支,
逐一比較節點內項的值,“E”大於“C”小於“F”,往第二個分支,
逐一比較節點內項的值,找打“E”適當的位置並插入。
從上面插入操作可以總結,插入主要就是涉及到分裂操作,而且要注意到非節點只儲存了關鍵字作為索引,而資料都儲存在葉子節點上,此外還需要使用指標將葉子節點連線起來。最終我們可以看到葉子節點的項按從小到大排列,因為有了指標使得可以很方便遍歷資料。
查詢操作
對B+樹的查詢與B樹的查詢差不多,從根節點開始查詢,通過比較項的值找到對應的分支,然後繼續往子樹上查詢。
比如查詢“H”,“H”小於“J”,往第一個分支,
逐一比較節點中的項,發現應該往第四個分支,
逐一比較,找到“H”。
遍歷操作
遍歷操作首先是要先找到樹最左邊的葉子節點,然後就可以通過指標完成整棵樹的遍歷了。
從根節點開始,一直往第一個分支走,
繼續往第一個分支走,
發現已經到葉子節點了,這就是要找的遍歷的開端,
第一個葉子節點有兩個項,接著根據指標跳到第二個葉子節點,
第二個節點有三個項,根據指標繼續往下一個節點,
該節點有兩個項,根據指標繼續往下一個節點,
不斷根據指標往下,
往下,
完成整棵樹的遍歷。
-------------推薦閱讀------------
我的開源專案彙總(機器&深度學習、NLP、網路IO、AIML、mysql協議、chatbot)
跟我交流,向我提問:
歡迎關注: