BTree B+Tree


簡介

B 樹是為了磁盤或其它存儲設備而設計的一種多叉平衡查找樹。(相對於二叉,B樹每個內結點有多個分支,即多叉)
B樹又可以寫成B-樹/B-Tree,並不是B“減”樹,橫杠為連接符,容易被誤導
首先我們介紹一下一棵 m 階B-tree的特性

m 階的定義:一個節點能擁有的最大子節點數來表示這顆樹的階數
舉個例子:
如果一個節點最多有 n 個key,那么這個節點最多就會有 n+1 個子節點,這棵樹就叫做 n+1(m=n+1)階樹

包括以下5條特性

1.每個結點x(假設為x)有如下屬性: - x.n,表示當前存儲在結點x中的關鍵字個數。 - x.n的各個關鍵字本身:x.key1, x.key2, … 以非降序存放(升序),使得 x.key1 ≤ x.key2 ≤ … - x.leaf,是一個布爾值,如果x是葉子結點,則為TRUE, 如果x為內部結點,則為FALSE。 2.每個'內部結點x'還包含x.n+1個指向它的孩子的指針x.c1, x.c2, … 。 葉子結點沒有孩子結點,所以他的ci屬性沒有定義。 - key和指針互相間隔,節點兩端是指針,所以節點中指針比key多一個。 - 每個指針要么為null,要么指向另外一個節點。 3.關鍵字x.keyi對存儲在各子樹中的關鍵字進行分割:如果ki為任意一個存儲在以x.ci為根的子樹中的關鍵字,那么: k1 ≤ x.key1 ≤ k2 x.key2 ≤ … ≤ x.keyx.n ≤ kx.n+1 難理解可以這么說: > 如果某個指針在節點node最左邊且不為null,則其指向節點的所有key小於(key1),其中(key1)為node的第一個key的值。 > 如果某個指針在節點node最右邊且不為null,則其指向節點的所有key大於(keym),其中(keym)為node的最后一個key的值。 > 如果某個指針在節點node的左右相鄰key分別是keyi和keyi+1且不為null,則其指向節點的所有key小於(keyi+1)且大於(keyi)。 4.每個葉子結點具有相同的深度,即樹的高度h 5.每個結點所包含的的關鍵字個數有上界和下界。用一個被稱作B樹的最小度數的估計整數t(t ≥ 2)來表示這些界: 除了根結點以外的每個結點必須**至少**有t-1個關鍵字。因此,除了根節點以外的每個內部結點**至少**有t個孩子。(因為上面說了右x.n+1個指向它的孩子的指針) 如果樹非空,根結點至少有一個關鍵字。 每個結點**最多**包含2t-1個關鍵字。因此,一個內部節點至多可有2t個孩子。當一個結點恰好有2t-1個關鍵字時,稱該結點是滿的(full)。 

3階B-tree示意圖

 

 

拿中間一層最左邊舉例說明:
1.  x.n = 2 有倆個關鍵字
    分別為 x.key1 = 8  x.key2 = 12 且 8<12
    x.leaf = false為內部節點
2.  含有3個指向它孩子的指針P1 P2 P3
3.  關鍵字x.key1=8 它的左邊指針P1 對 子樹 3 5 分割 滿足 3和5都小於8
    關鍵字x.key1=8 它的右邊指針P2 對 子樹 9 10 分割 滿足 9和10都大於8(同為12的左指針)
    關鍵字x.key2=12 它的右邊指針P3 對 子樹 13 15 分割 滿足 13和15都大於12

實際磁盤舉例:

 


來模擬下查找文件29的過程:
(1) 根據根結點指針找到文件目錄的根磁盤塊1,將其中的信息導入內存。
【磁盤IO操作1次】
(2) 此時內存中有兩個文件名17,35和三個存儲其他磁盤頁面地址的數據。根據算法我們發現17<29<35,因此我們找到指針p2。
(3) 根據p2指針,我們定位到磁盤塊3,並將其中的信息導入內存。
【磁盤IO操作2次】
(4) 此時內存中有兩個文件名26,30和三個存儲其他磁盤頁面地址的數據。根據算法我們發現26<29<30,因此我們找到指針p2。
(5) 根據p2指針,我們定位到磁盤塊8,並將其中的信息導入內存。
【磁盤IO操作3次】
(6) 此時內存中有兩個文件名28,29。根據算法我們查找到文件29,並定位了該文件內存的磁盤地址。

 

關鍵字插入操作

生成從空樹開始,逐個插入關鍵字。但是由於B_樹節點關鍵字必須大於等於[ceil(m/2)-1],(其中ceil(x)是一個取上限的函數)所以每次插入一個關鍵字;首先在最底層(葉子節點那一層)的某個非終端節點中添加一個“關鍵字”,該結點的關鍵字不超過m-1,則插入完成;否則要產生結點的“分裂”,將一半數量的關鍵字分裂到新的其相鄰右結點中,中間關鍵字上移到父結點中。

下面一個實例:我們需要用C N G A H E K Q M F W L T Z D P R X Y S來構造5階B樹
這設計一個 B樹的階數最優選取

  • 首先,根結點空間足夠,4個字母插入相同的結點中,如下圖:


     
    記住排序哦
  • 當咱們試着插入H時,結點發現空間不夠,以致將其分裂成2個結點,移動中間關鍵字G上移到新的根結點中,在實現過程中,咱們把A和C留在當前結點中,而H和N放置新的其右鄰居結點中。如下圖:


     
  • 插入E,K,Q時,不需要任何分裂操作


     
  • 插入M需要一次分裂,注意M恰好是中間關鍵字,以致向上移到父節點中


     
  • 插入F,W,L,T不需要任何分裂操作


     
  • 插入Z時,最右的葉子結點空間滿了,需要進行分裂操作,中間關鍵字T上移到父節點中,注意通過上移中間關鍵字,樹最終還是保持平衡,分裂結果的結點存在2個關鍵字。


     
  • 插入D時,導致最左邊的葉子結點被分裂,D恰好也是中間關鍵字,上移到父節點中,然后字母P,R,X,Y陸續插入不需要任何分裂操作(別忘了,樹中至多5個孩子)。


     
  • 最后,當插入S時,含有N,P,Q,R的結點需要分裂,把中間關鍵字Q上移到父節點中,但是情況來了,父節點中空間已經滿了,所以也要進行分裂,將父節點中的中間關鍵字M上移到新形成的根結點中,注意以前在父節點中的第三個指針在修改后包括D和G節點中。這樣具體插入操作的完成


     

關鍵字刪除操作

首先查找B樹中需刪除的關鍵字,如果該關鍵字在B樹中存在,則將該關鍵字在其結點中進行刪除,如果刪除該關鍵字后,首先判斷其結點是否有左右孩子結點,如果有,則上移孩子結點中的某相近關鍵字到父節點中,然后是判斷移動之后的情況;如果沒有,直接刪除后,判斷刪除之后的情況。
刪除元素,移動相近元素之后,如果某結點中關鍵字數小於ceil(m/2)-1,
(m為階數)則需要看其某相鄰兄弟結點是否豐滿
如果豐滿,則向父節點借一個元素來滿足條件;如果其相鄰兄弟都剛脫貧,即借了之后其結點數目小於ceil(m/2)-1則該結點與其相鄰的某一兄弟結點進行“合並”成一個結點,以此來滿足條件。
(結點中關鍵字個數大於ceil(m/2)-1除根結點之外的結點的關鍵字的個數n必須滿足: ceil(m / 2)-1)<= n <= m-1。m表示最多含有m個孩子,n表示關鍵字數.)

實例:
最初態5階B樹 實現依次刪除H,T,R,E
關鍵字數最小為ceil(m / 2)-1=2。最多為m-1=4

 

 

  • 首先刪除關鍵字H,當然首先查找H,H在一個葉子結點中,且該葉子結點關鍵字數目3大於最小元關鍵字數目2,則操作很簡單,咱們只需要移動K至原來H的位置,移動L至K的位置(也就是結點中刪除關鍵字后面的關鍵字向前移動)

     

     

  • 下一步,刪除T,因為T在中間結點中,它有孩子,且關鍵字數目=2,所以需要向孩子借一個關鍵字W(一般借升序的下個關鍵字),將W上移到T的位置,然后將原包含W的孩子結點中的W進行刪除,這里恰好刪除W后,該孩子結點中關鍵字數目大於2,無需進行合並操作。

     

     

  • 下一步刪除R,R在葉子結點中,但是該結點中關鍵字數目為2,刪除導致只有1個關鍵字,已經小於最小關鍵字數目ceil(5/2)-1=2,而由前面我們已經知道:如果其某個相鄰兄弟結點中比較豐滿(關鍵字個數大於ceil(5/2)-1=2),則可以向父結點借一個關鍵字,然后將最豐滿的相鄰兄弟結點中上移最后或最前一個關鍵字到父節點中(有沒有看到[紅黑樹]中左旋操作的影子?),在這個實例中,右相鄰兄弟結點中比較豐滿(3個關鍵字大於2),所以先向父節點借一個關鍵字W下移到該葉子結點中,代替原來S的位置,S前移;然后X在相鄰右兄弟結點中上移到父結點中,最后在相鄰右兄弟結點中刪除X,后面關鍵字前移。

     

     

  • 最后一步刪除E, 刪除后會導致很多問題,因為E所在的結點數目剛好達標,剛好滿足最小關鍵字個數(ceil(5/2)-1=2),而相鄰的兄弟結點也是同樣的情況,刪除一個關鍵字都不能滿足條件,所以需要該節點與某相鄰兄弟結點進行合並操作;首先移動父結點中的關鍵字(該關鍵字在兩個需要合並的兩個結點關鍵字之間)下移到其子結點中,然后將這兩個結點進行合並成一個結點。所以在該實例中,咱們首先將父節點中的關鍵字D下移到已經刪除E而只有F的結點中,然后將含有D和F的結點和含有A,C的相鄰兄弟結點進行合並成一個結點。

     
    還沒完哦

     

  • 也許你認為這樣刪除操作已經結束了,其實不然,在看看上圖,對於這種特殊情況,你立即會發現父節點只包含一個關鍵字G,沒達標
    (因為非根節點包括葉子結點的關鍵字數n必須滿足於2=<n<=4,而此處的n=1)這是不能夠接受的。
    如果這個問題結點的相鄰兄弟比較豐滿,則可以向父結點借一個關鍵字。然后父親再向孩子借一個關鍵字。但是它的兄弟也很貧窮。所以這時只能與兄弟結點進行合並成一個結點,而根結點中的唯一關鍵字M下移到子結點,這樣,樹的高度減少一層。

     

    至此完成刪除操作。
    補充一下最后一個關鍵字刪除的另外一種情況:也是5階B樹
     

     

  • 刪除關鍵字C后發現只剩下一個關鍵字F所以我們打算去借,這個借首先想到孩子,然后再說父親刪除關鍵字C后D關鍵字上移到C的位置,但是出現上移關鍵字D后,只有一個關鍵字E的結點的情況。且它相鄰兄弟結點才剛脫貧(最少關鍵字個數為2),不可能向父節點借關鍵字,所以只能進行合並操作,合並操作就要向父結點借一個元素,所以又把D借下來。於是這里將含有A,B的左兄弟結點,加上D關鍵字和含有E的結點進行合並成一個結點。

     
    刪除C向孩子借D

     
    孩子合並

     

  • 這樣又出現只含有一個關鍵字F結點的情況,這時,發現其相鄰的兄弟結點是豐滿的(關鍵字個數為3>最小關鍵字個數2),這樣就可以想父結點借關鍵字,把父結點中的J下移到該結點中,相應的如果結點中J后有元素則前移,然后相鄰兄弟結點中的第一個關鍵字(或者最后一個關鍵字)上移到父節點中,后面的關鍵字(或者前面的關鍵字)前移(或者后移);注意含有K,L的結點以前依附在M的左邊,現在變為依附在J的右邊。這樣每個結點都滿足B樹結構性質。

 

一顆m階的B+樹和m階的B樹的差異在於:

 
3階B+樹

1.非葉子結點的子樹指針與關鍵字個數相同;
2.非葉子結點的子樹指針P[i],指向關鍵字值屬於[K[i], K[i+1])的子樹(B-樹是開區間)
3.為所有葉子結點增加一個鏈指針。圖中Q是通過指針連在一起的。
4.所有關鍵字都在葉子結點出現。(5 8 9 10 15 18 20 26 ...等等)葉子結點相當於是存儲(關鍵字)數據的數據層;
5.B+樹只有達到葉子結點才命中(B-樹可以在非葉子結點命中)
6.所有的非終端結點可以看成是索引部分,結點中的關鍵字是有其孩子指向的子樹中最大(或最小)關鍵字。比如第二層5 它的子樹為5 8 9 (而B 樹的非終節點也包含需要查找的有效信息)

B+樹在MyISAM索引實現

葉節點的data域存放的是數據記錄的地址

 
原理圖


MyISAM的索引方式也叫做“非聚集”的,之所以這么稱呼是為了與InnoDB的聚集索引區分。

 

B+樹在InnoDB索引實現

 
原理圖
  • 第一個重大區別是InnoDB的數據文件本身就是索引文件。
    從上文知道,MyISAM索引文件和數據文件是分離的,索引文件僅保存數據記錄的地址。
    而在InnoDB中,表數據文件本身就是按B+Tree組織的一個索引結構,這棵樹的葉節點data域保存了完整的數據記錄。
    這個索引的key是數據表的主鍵,因此InnoDB表數據文件本身就是主索引。

  • 從示意圖,可以看到葉節點包含了完整的數據記錄。這種索引叫做聚集索引。因為InnoDB的數據文件本身要按主鍵聚集,所以InnoDB要求表必須有主鍵(MyISAM可以沒有),如果沒有顯式指定,則MySQL系統會自動選擇一個可以唯一標識數據記錄的列作為主鍵,如果不存在這種列,則MySQL自動為InnoDB表生成一個隱含字段作為主鍵,這個字段長度為6個字節,類型為長整形。

  • 第二個與MyISAM索引的不同是InnoDB的輔助索引data域存儲相應記錄主鍵的值而不是地址。換句話說,InnoDB的所有輔助索引都引用主鍵作為data域。例如

     

    聚集索引這種實現方式使得按主鍵的搜索十分高效,但是輔助索引搜索需要檢索兩遍索引:首先檢索輔助索引獲得主鍵,然后用主鍵到主索引中檢索獲得記錄。

     

所以應該注意的地方

  • 為什么不建議使用過長的字段作為主鍵?
    因為所有輔助索引都引用主索引,過長的主索引會令輔助索引變得過大。
  • 在InnoDB中不要用非單調的字段作為主鍵。
    因為InnoDB數據文件本身是一顆B+Tree,非單調的主鍵會造成在插入新記錄時數據文件為了維持B+Tree的特性而頻繁的分裂調整,十分低效,而使用自增字段作為主鍵則是一個很好的選擇。

B樹JAVA代碼實現例子

七、為什么說B+樹比B樹更適合數據庫索引?

1、 B+樹的磁盤讀寫代價更低:B+樹的內部節點並沒有指向關鍵字具體信息的指針,因此其內部節點相對B樹更小,如果把所有同一內部節點的關鍵字存放在同一盤塊中,那么盤塊所能容納的關鍵字數量也越多,一次性讀入內存的需要查找的關鍵字也就越多,相對IO讀寫次數就降低了。

2、B+樹的查詢效率更加穩定:由於非終結點並不是最終指向文件內容的結點,而只是葉子結點中關鍵字的索引。所以任何關鍵字的查找必須走一條從根結點到葉子結點的路。所有關鍵字查詢的路徑長度相同,導致每一個數據的查詢效率相當。

3、由於B+樹的數據都存儲在葉子結點中,分支結點均為索引,方便掃庫,只需要掃一遍葉子結點即可,但是B樹因為其分支結點同樣存儲着數據,我們要找到具體的數據,需要進行一次中序遍歷按序來掃,所以B+樹更加適合在區間查詢的情況,所以通常B+樹用於數據庫索引。

文章參考:
http://lday.me/2018/02/21/0020_b_tree_summary/
https://blog.csdn.net/endlu/article/details/51720299
http://blog.codinglabs.org/articles/theory-of-mysql-index.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM