1. 程式人生 > >關於B樹的學習總結和B+樹,B*樹的簡介

關於B樹的學習總結和B+樹,B*樹的簡介

概念

B樹,英文是B-tree,是一種平衡多路樹,這個不叫B減樹,就是B樹。

B樹是一種多路樹。因為他的子節點不止2個,可以是多個。

B樹是一種平衡樹。所謂平衡樹,指的是他的左右兩個子樹的高度差小於等於1,而且左右子樹的子樹高度差也小於等於1。其實B樹算是一種特殊的平衡樹,因為B樹的要求更高,要求左右子樹高度相同,也就是說,根節點到每個葉子節點的距離都相同。

約定

1,ceil(x),這是一個向上取整的函式,比如ceil(1.1)=2。注意這不是四捨五入,而且是得到比引數大的那個整數。

2,B樹可以用階數來定義,階數代表了B樹的節點最多可以擁有的子節點數,後面用m來代表B樹的階數。

一個m階B樹的特性

1,除了葉子節點外,樹中每個節點,最少有ceil(m/2)個子節點,最多有m個子節點。

2,只要根節點不是葉子節點(那種情況只能是整個樹就一個根節點),那麼根節點最少有2個子節點。

3,所有葉子節點出現在同一層,即根節點到所有子節點的距離相同。

4,除了根節點之外,樹中每個節點最少有ceil(m/2)-1個關鍵字,最多有m-1個關鍵字。

5,假設一個節點中有x個關鍵字(K1,K2,……Kx),那麼需要滿足以下條件:

  • 有x+1個子節點(P0,P1,……Px)。
  • 節點中的x個關鍵字升序排列,即K(n-1)<Kn
  • 子節點Pn中的元素都小於Kn,都大於K(n-1),也就是說,節點和子節點中的關鍵字排序是P0,K1,P1,K2,P2,……Kx,Px。

比如下面的樹:


每個方框是一個節點

節點中的數字是關鍵字

Pn是指向子節點的指標

最上面是根節點,最下面是葉子節點,葉子節點沒有指向子節點的指標

B樹的查詢

在上面的B樹中,比如我們要找關鍵字32

1,比較根節點,32大於第一個關鍵字17,小於第二個關鍵字35,那麼查詢17和35之間的指標(P2)指向的節點(26和30那個)。

2,子節點中有關鍵字26和30,目標關鍵字32大於30,那麼查詢大於30的那個指標(P3)指向的節點(31和32那個)。

3,找到的子節點中有關鍵字31和32,在這個節點中可以找到目標關鍵字32。

向B樹中新增關鍵字

向一棵m階B樹中新增關鍵字時,需要考慮節點中的關鍵字數是否超過了上限。

m階B樹每個節點最多有m-1個關鍵字。

如果節點在新增完關鍵字之後(排好序),關鍵字數已經達到了m,該節點將分裂成兩個節點。

節點分裂的方式是,最中間的關鍵字向上移到父節點,大於和小於中間關鍵字的部分分別成為兩個新的節點。可以看到,節點分裂的時候父節點得到了一個新的關鍵字,如果分裂前父節點關鍵字樹已經飽和了(m-1個),會導致父節點也分裂,在最壞的情況下,分裂會一直進行到根節點,根節點一分裂,整個樹的高度都會加一。

舉個例子,以下五階B樹:


1,加入關鍵字40。可以很快定位到加入元素的位置是第三個葉子節點,加完之後樹變成了這樣:


2,加入關鍵字56。定位到新增關鍵字的位置,第四個葉子節點,加完之後樹變成這樣:


3,  加入關鍵字45。應該新增到第三個葉子節點,加完之後樹變成這樣:


還沒完,5階B樹每個節點最多4個關鍵字,這樣第三個葉子節點關鍵字超上限了,需要分裂。中間關鍵字35上升到父元素,因為35是從P3的節點來的,所以35在父節點中的位置就在小於P3的關鍵字30和大於P3的關鍵字50之間。葉子節點中小於35的關鍵字(31和32)組成新節點,大於35的關鍵字(40和45)組成新節點,然後樹變成這樣:


4,  加入關鍵字51。應該新增到第5個葉子節點,加完是這樣:


第五個葉子節點關鍵字數超上限,分裂之後中間元素56上移到父元素,變成這樣:


父節點得到了關鍵字56之後,關鍵字數也超上限了,也需要分裂,中間關鍵字35上移,大於和小於35的關鍵字分別形成新的節點,P3和P4指標的位置其實還是不變的,最終樹變成這樣:


整個樹增加了一層。

從B樹中刪除關鍵字

從B樹的節點中刪除關鍵字時,需要考慮刪除之後節點的關鍵字數量是否小於下限。

m階B樹的節點最少包含ceil(m/2)-1個關鍵字。

如果刪除之後節點中的關鍵字小於下限,則需要從子樹中獲取關鍵字,或從相鄰兄弟節點獲取關鍵字,或和相鄰兄弟節點合併。如果從子樹獲取關鍵字之後子樹依然能保持B樹的基本要求,則可以從子樹中獲取,否則看相鄰兄弟節點元素是否富餘,富餘的話可以從兄弟節點獲取元素,相鄰兄弟節點也不富餘就需要和兄弟節點合併。

從相鄰兄弟節點獲取關鍵字時,當然不是把兄弟節點的關鍵字直接拿過來,而是把當前節點和兄弟節點之間的那個父節點關鍵字拿過來,然後兄弟節點給父節點補一個關鍵字。

和兄弟節點合併時,需要把當前節點和兄弟節點之間的那個父節點關鍵字下移,然後和該節點還有兄弟節點組成新的節點,基於B樹節點關鍵字的數量要求,這樣合併出來的新節點關鍵字數不會超上限。這種方式在極端情況下可能會導致根節點下移合併,也就是樹的層數會減少1層。

舉個例子,以下五階B樹:


1,刪除關鍵字21。最普通的情況,第一個葉子節點刪除21之後,剩餘關鍵字數量3,不需要變動,刪完了變成這樣:


2,刪除關鍵字26。刪除26之後節點只剩下了30一個關鍵字,已經不滿足B樹的要求,可以從子節點中獲取關鍵字。在這個例子中只能從第一個葉子節點中獲取,把葉子節點中最接近被刪除關鍵字的元素22移到父節點,刪完了是這樣:


3,刪除關鍵字29。節點刪除29之後關鍵字只剩下了28,已經低於下限,沒有子節點可以獲取關鍵字,考慮從相鄰兄弟節點獲取。第一個葉子節點已經處於下限,只能從第三個葉子節點獲取關鍵字。步驟如下:把第二葉子節點和第三葉子節點之間的父元素關鍵字30移到第二葉子節點,然後第三葉子節點把距離30最近的關鍵字31補給父節點,移完之後樹是這樣的:


4,刪除關鍵字12。刪除12之後第一葉子節點就只剩下了5,低於下限。在這個例子中該節點沒有子節點,也不能從相鄰兄弟節點中獲取關鍵字(兄弟節點只有28和30兩個關鍵字,不能再減少),所以只能和相鄰兄弟節點合併。合併的步驟如下:把第一葉子節點和第二葉子節點之間的父節點關鍵字22下移,然後和第一葉子節點和第二葉子節點組成新的節點,合併完成之後的樹是這樣的:


此時父節點只有一個關鍵字31,小於下限,和直接刪除該節點的關鍵字不同,無法再從子節點中獲取關鍵字,在這個例子中也無法從相鄰的兄弟節點獲取,只能考慮和相鄰兄弟節點合併,合併步驟:父節點關鍵字35下移,和兩個子節點組成新的節點,這個節點實際上成為了根節點,整個樹的高度減少了1,合併完的結果如下:


最基本的刪除例子就是這樣,實際上在層數比較多的情況下,刪除操作可能會更復雜一些。

關於B+樹

B+樹是由B樹變來的,B+樹和B樹有這樣的區別:

1,B+樹的非葉子節點不記錄資料本身,只記錄引用的連線。基於此特點,B+樹在非葉子節點的檔案會非常小。葉子節點會包含所有的關鍵字。

2,B+樹每個葉子節點都有指向相鄰的下一個兄弟葉子節點的指標。基於此特點,B+樹在範圍查詢上的效率比B樹高了很多。

3,這條區別是有爭議的,有人說B+樹的節點中關鍵字和子節點個數相同,也有人說B+樹和B樹一樣關鍵字比子節點少一個。

如果我們認為上述第三條中關鍵字和子節點個數是相同的,那B+樹就是這樣的:


向B+樹中新增關鍵字時,也存在節點分裂的情況,在節點分裂時,會生成一個新的節點,把原節點中一半的元素複製到新節點,在父節點中新增指向新節點的關鍵字和指標。

關於B*樹

B*樹是由B+樹變化來的,在B+樹的基礎上,B*樹的非葉子節點也有指向下一個兄弟節點的指標。

B*樹要求每個節點至少有2/3m個關鍵字,比B+樹的空間利用率高。

B*樹在新增關鍵字時也存在分裂的問題,在節點分裂時,可能會移動一部分關鍵字到兄弟節點中,然後在原節點中新增關鍵字並修改父節點的關鍵字和指標。如果兄弟節點已滿,則新建一個節點,把原節點和兄弟節點各移1/3關鍵字到新建節點,然後對應修改父節點。B*樹的新增關鍵字時的效率比B+樹要低。

以下是一個B*樹的例子: