索引數據結構之B-Tree與B+Tree(上篇)

掃描下方二維碼或者微信搜索公衆號菜鳥飛呀飛,便可關注微信公衆號,閱讀更多Spring源碼分析Java併發編程Netty源碼系列文章。html

微信公衆號

樹是一種十分常見的數據結構,根據子結點的個數,咱們能夠將樹分爲二叉樹和多叉樹。每一個結點最多兩個子結點的樹稱之爲二叉樹,比較典型的二叉樹有二叉搜索樹、徹底二叉樹、滿二叉樹、二叉平衡樹、紅黑樹等。子結點的個數大於 2 的樹稱之爲多叉樹,常見的多叉樹有 B 樹和 B+樹。算法

B 樹和 B+樹是一種多路搜索樹,它由二叉搜索樹演變而來,經常使用於數據庫的索引結構中,且 B+樹和 B 樹具備不少類似的地方,也比較容易弄混,所以本文將二者放在一塊兒進行學習,進行對比。數據庫

B-Tree

B-Tree 又叫作 B 樹,不少人見到有 B+樹(B+Tree),因此常常會把 B-Tree 和 B 樹當作是兩種樹,實際上 B-Tree 和 B 樹是同一種樹(單詞 B-Tree 翻譯過來就是 B 樹)。(這個」不少人「就包括筆者,筆者是個菜鳥,最開始把 B-Tree、B 樹,B+Tree 當成是三種樹,還常常把它們理解爲 B 減樹,B 樹,B 加樹,後來去網上查了查才搞清楚)。編程

對於樹這種數據結構,有一個描述樹結構的概念叫作度(也叫作階),它描述的是一個結點中子結點的個數,例如一個二叉樹,每一個結點最多有 2 個子結點,所以二叉樹的度(階)爲 2。對於 B-Tree 而言,一樣也有階的概念,例如一個 5 階的 B-Tree,表示的是每一個結點最多有 5 個子結點。微信

對於一個階數爲 m 的 B-Tree,它有以下性質:數據結構

  1. 每一個結點最多有 m 個子結點;
  2. 每一個非葉子結點(根結點除外)至少含有 m/2 個子結點;
  3. 若是根結點不是葉子結點,那麼根結點至少有兩個子結點;
  4. 對於一個非葉子結點而言,它最多能存儲 m-1 個關鍵字(所謂的關鍵字,咱們能夠理解爲就是節點上存放的數據);
  5. 每一個節點上,全部的關鍵字都是有序的,從左至右,依次從小到大排列;
  6. 每一個關鍵字的左子樹的值均小於當前關鍵字,右子樹的值均大於當前關鍵字;
  7. 每一個節點都存有索引和數據(記住這一點很是重要,這是和後面介紹的 B+Tree 的最重要的區別之一)。

從上面的性質來看,對於 B-Tree 的根結點而言,關鍵字數量的範圍爲 1<= k <= m-1;非根結點,關鍵字的範圍爲 m/2 <= k <= m-1。知道了這些性質,下面咱們分別看看 B-Tree 的插入、查找、刪除過程。併發

1. 插入

在向一個 m 階的 B-Tree 中插入數據時,爲了保證上述 B-Tree 的性質,因此在插入關鍵字(插入數據)時咱們須要按照以下規則插入:向當前結點中插入關鍵字後,判斷當前結點的關鍵字數量是否小於等於 m-1,若是小於,則插入結束;不然須要將當前結點進行分裂,如何分裂呢?在 m/2 處拆分,造成左右兩部分,即兩個新的子結點,而後將 m/2 處的關鍵字移到父節點當中(從最中間分裂)。數據結構和算法

對於一個 5 階的 B-Tree(也就是說,非根結點,關鍵字數量的範圍爲 2 <= k <= 4),咱們依次向樹中插入以下數據:50,30,40,25,其流程以下。 首先依次插入 50、30、40、25,插入後結點狀態以下; 源碼分析

圖1

而後當咱們再插入 20 時,當前結點中就存儲了 5 個關鍵字,因爲當前樹是一顆 5 階樹,所以每一個結點最多隻能存放 4 個關鍵字,所以此時就須要將當前結點分裂。怎麼分裂呢?就是從最中間(m/2)處將結點分紅左右兩部分(5/2 向上取整是 3),所以從數字 30 所在的地方進行分裂,而後將數字 30 放入到父結點當中(因爲此時父結點爲空,所以新建一個結點,而後將 30 放入到該結點中),而後將 30 左邊的兩個數 20、25 構成一個新的結點,右邊的兩個數 40、50 構成一個新的節點,這兩個新的結點分別指向關鍵字 30 的左右兩邊。示意圖以下:學習

圖2

繼續向樹中插入數據 1五、十、13。當插入到 13 時,咱們發現結點中的關鍵字的數量又超過了 m-1,所以又須要進行結點的分裂了。此時將中間的數 15 拆分出去,放到父結點當中,剩下的左右兩部分分別構成新的結點。

圖3

再繼續向樹中一次插入數據:1八、60、5五、4五、2六、1七、八、三、5。最後該 B-Tree 的結構以下圖所示。

圖4

文章中貼出的全是靜態圖,若是想體驗 B-Tree 數據插入的動態過程,能夠去下面這個學習網站中去手動插入數據,體驗一下數據插入的動態過程。(網址:www.cs.usfca.edu/~galles/vis… 一個很是不錯的學習數據結構與算法的網站)

2. 查找

B-Tree 的查找操做相對比較簡單,和二叉查找樹的查找相似。

  1. 先從根結點開始查找,依次遍歷根結點的關鍵字,找到第一個不小於要查找數據的關鍵字;
  2. 判斷要查找的數據是否等於當前關鍵字,若是等於則返回數據;
  3. 若是不等於,則表示要查找的數據是否小於當前關鍵字,所以進入當前關鍵字的左子樹查找,查找過程和根結點的查找過程相似,重複上述步驟便可。

以上面 B-Tree 的數據爲例,查找數字 10。首先從根結點開始查找,第一個不小於 10 的關鍵字是 20,因爲要查找的數據10不等於關鍵字20,所以進入關鍵字 20 的左子樹查找,此時指針指向關鍵字 八、15 所在的結點,在該結點中第一個不小於 10 的關鍵字是 15,因此進入關鍵字 15 的左子樹查找,此時指針指向關鍵字 十、13 所在的結點,發現該結點中關鍵字 10 等於要查找的數據,所以返回。(以下圖所示,紅色表示查找過程當中的路徑)

圖5

若是要數字 60,過程和上面同樣,先從根結點開始,發現根結點中全部的關鍵字都小於 60,因此進入根結點最後一個關鍵字的右子樹查找,即進入關鍵字 20 的右子樹查找。一樣,發現關鍵字 30、50 所在的結點中,全部的關鍵字都小於 60,所以進入當前節點最後一個關鍵字 50 的右子樹查找,最終查找到 60,返回數據。以下圖所示,紅色表示查找過程當中的路徑)

圖6

3. 刪除

B-Tree 在數據的插入過程當中,爲了知足 B-Tree 的性質,所以中間會出現結點的分裂過程,一樣,在數據的刪除過程當中,有可能由於刪除了某個關鍵字而致使不知足 B-Tree 的相關性質了,所以在刪除過程當中會出現結點的合併等狀況,刪除過程相對比較複雜,但整體來講,能夠歸結爲如下三種場景。

  1. 若是是葉子結點,刪除關鍵字後,葉子結點中關鍵字的數量很多於 m/2 個,那麼直接刪除關鍵字便可;
  2. 若是是葉子結點,刪除關鍵字後,葉子結點中關鍵字的數量少於 m/2 個,這個時候就不知足 B-Tree 的性質了,所以須要向兄弟結點借關鍵字。若是兄弟結點中關鍵字個數大於 m/2,那麼就能夠借,先將父節點移到當前節點中,而後兄弟結點的一個關鍵字移到父結點中;若是兄弟結點的關鍵字數量個數小於等於 m/2,假設兄弟結點借出一個關鍵字後,那麼它本身的關鍵字數量就少於 m/2 了,又不符合 B-Tree 的性質了,所以這個時候不能借,此時須要將要刪除的關鍵字刪除後,將父節點移到此處,而後將當前節點和兄弟結點合併。
  3. 若是是非葉子節點刪除關鍵字,那麼就須要先刪除當前關鍵字,而後用右子樹中最小的關鍵字補上當前位置,再從右子樹中刪除剛剛補充上去的關鍵字,這個刪除操做就又是B-Tree的刪除操做了。(右子樹中最小的關鍵字必定是在葉子結點中,因此刪除過程就是刪除葉子結點中的關鍵字了,也就是場景 1 和場景 2 的流程了)。

下面結合具體示例,針對上面 3 個場景分別舉例來講明刪除操做的流程,如下面的數據爲例。

圖7

從 B-Tree 中刪除關鍵字 29,因爲關鍵字 29 所在的節點是葉子結點,當將 29 刪除後,當前結點的關鍵字數量爲 3,也就是說刪除剩下的關鍵字數量很多於 m/2(5/2=2),知足上面提到的場景 1,那麼就能夠將關鍵字直接刪除。

圖8

繼續從 B-Tree 中刪除關鍵字 55,因爲關鍵字 55 所在的節點是葉子結點,當將 55 刪除後,當前結點剩下的關鍵字數量爲 1 了,小於 m/2,所以須要向兄弟結點借關鍵字。當前結點的兄弟結點中有 4 個關鍵字(40、4五、4七、49),大於 m/2,因此能夠借出關鍵字,符合場景 2 的第一種狀況。所以先將關鍵字 55 刪除,而後將父節點中關鍵字 50 移動到當前結點,再將兄弟結點中的關鍵字 49 移動到父結點中,示意圖以下。

圖9

繼續從 B-Tree 中刪除關鍵字 17,因爲關鍵字 17 所在的節點是葉子結點,當將 17 刪除後,當前結點剩下的關鍵字數量爲 1 了,小於 m/2,所以須要向兄弟結點借關鍵字。當前結點的兄弟結點中有 2 個關鍵字(十、13),小於 m/2,因此不能夠借出關鍵字,符合場景 2 的第二種狀況。所以先將關鍵字 17 刪除,而後將父節點中關鍵字 15 移動到當前結點,而後將當前結點與兄弟結點合併(關鍵字 十、13 所在的結點)。示意圖以下。

圖10

而後咱們發現,在關鍵字 17 刪除的時候,咱們從父結點(關鍵字 8 所在的結點)中移下來一個關鍵字,它的父結點只剩下一個關鍵字了,父結點又不符合 B-Tree 的性質了,因此咱們還要繼續操做。讓父結點找本身的兄弟結點繼續借關鍵字,父結點此時左邊沒有兄弟結點,所以找右邊的兄弟結點(關鍵字 30 和 49 所在的結點)借關鍵字。結果發現右邊兄弟結點的關鍵字個數也不大於 m/2,若是兄弟結點借出關鍵字後,又不符合性質了,因此這個時候又符合上面咱們提到的場景 2 的第二種狀況,所以須要合併結點。因此接下來的操做是:將關鍵字 8 的父結點移到 8 所在的結點上,而後合併關鍵字 8 和 30、49 所在的結點。示意圖以下:

圖11

繼續從 B-Tree 中刪除關鍵字 20,此時 20 處於的結點是非葉子結點,所以知足場景 3。因此直接刪除關鍵字 20,而後從 20 的右子樹中取出最小的關鍵字 25 填充到 20 所在的位置,最後將 25 這個關鍵字從右子樹的結點中刪除,對於 25 這個關鍵字的刪除流程,又能夠分別對應上面葉子結點的刪除場景了。示意圖以下:

圖12

以上就是 B-Tree 樹中關鍵字的刪除流程,相對於插入和查找過程,刪除過程更加複雜,所以最好去這個可視化網站去學習下。(PC 端打開,網址:www.cs.usfca.edu/~galles/vis… 在這個可視化網站中,在刪除非葉子結點的關鍵字的時候,取的是左子樹中最大的關鍵字填充的,而本文講解的時候說的是取右子樹中最小的關鍵字填充,這二者其實本質上沒有任何區別,都是爲了知足 B-Tree 的性質,而且保證每一個結點上全部關鍵字是有序的。在學習數據結構和算法的過程當中,沒有必要死摳細節,重要的是學習思惟。

總結

總結時刻。本文主要講解了 B-Tree 相關的性質,結合示意圖詳細介紹了插入、查找、刪除的過程。在文中的示例中,我特地沒有往 B-Tree 中添加劇復的數據,那麼若是往 B-Tree 中插入重複的數據後又應該怎麼辦呢?若是出現重複的數時,咱們只須要在插入的時候決定將重複的數放入到左子樹中或者右子樹中,這個具體放在哪邊,能夠本身定義。在查找數據的時候,就不能在找到一個符合要求的數據後就立馬中止查找了,還須要繼續日後查找,直到出現第一個不符合要求的數據才中止查找。一樣,刪除的時候,也須要刪除全部的數據。

篇幅有限,所以B+Tree 和數據庫索引相關的知識下一篇博客介紹。

相關

微信公衆號
相關文章
相關標籤/搜索