1. 程式人生 > >B樹,B-樹和B+樹的區別

B樹,B-樹和B+樹的區別

B樹

       即二叉搜尋樹:

       1.所有非葉子結點至多擁有兩個兒子(Left和Right);

       2.所有結點儲存一個關鍵字;

       3.非葉子結點的左指標指向小於其關鍵字的子樹,右指標指向大於其關鍵字的子樹;

       如:

       B樹的搜尋,從根結點開始,如果查詢的關鍵字與結點的關鍵字相等,那麼就命中;

否則,如果查詢關鍵字比結點關鍵字小,就進入左兒子;如果比結點關鍵字大,就進入

右兒子;如果左兒子或右兒子的指標為空,則報告找不到相應的關鍵字;

       如果B樹的所有非葉子結點的左右子樹的結點數目均保持差不多(平衡),那麼B樹

的搜尋效能逼近二分查詢;但它比連續記憶體空間的二分查詢的優點是,改變B樹結構

(插入與刪除結點)不需要移動大段的記憶體資料,甚至通常是常數開銷;

       如:

   但B樹在經過多次插入與刪除後,有可能導致不同的結構:

   右邊也是一個B樹,但它的搜尋效能已經是線性的了;同樣的關鍵字集合有可能導致不同的

樹結構索引;所以,使用B樹還要考慮儘可能讓B樹保持左圖的結構,和避免右圖的結構,也就

是所謂的“平衡”問題;      

       實際使用的B樹都是在原B樹的基礎上加上平衡演算法,即“平衡二叉樹”;如何保持B樹

結點分佈均勻的平衡演算法是平衡二叉樹的關鍵;平衡演算法是一種在B樹中插入和刪除結點的

策略;

B-樹

-樹其實就是我們平時所說的B樹,除了B-樹外,還有另外一種叫B+樹,我們這裡先介紹什麼是B-樹: 
B-樹是一種平衡的多路查詢樹,它在檔案系統中很有用(原因之前已經介紹了)。B-樹的結構有如下的特點: 
**一棵度為m的B-樹稱為m階B-樹。一個結點有k個孩子時,必有k-1個關鍵字才能將子樹中所有關鍵字劃分 
為k個子集。B-樹中所有結點的孩子結點最大值稱為B-樹的階,通常用m表示。從查詢效率考慮,一般要求 
m≥3。一棵m階的B-樹或者是一棵空樹,或者是滿足下列要求的m叉樹:**

  • 樹中的每個結點至多有m顆子樹。
  • 若根結點不是葉子結點,則至少有兩顆子樹。
  • 除根結點外,所有非終端結點至少有[ m/2 ] ( 向上取整 )顆子樹。
  • 所有的非終端結點中包括如下資訊的資料

(n,A0,K1,A1,K2,A2,….,Kn,An) 
其中:Ki(i=1,2,…,n)為關鍵碼,且Ki < K(i+1),

Ai 為指向子樹根結點的指標(i=0,1,…,n),且指標A(i-1) 所指子樹中所有結點的關鍵碼均小於Ki (i=1,2,…,n),An 所指子樹中所有結點的關鍵碼均大於Kn. 
這裡寫圖片描述 
n 為關鍵碼的個數。

  • 所有的葉子結點都出現在同一層次上,並且不帶資訊(可以看作是外部結點或查詢失敗的結點,實際上這些結點不存在,指向這些結點的指標為空)。

3、B-樹的基本操作–查詢介紹

我們先給出如下的一個4階的B-樹結構。 
這裡寫圖片描述

如上圖所示,這是我們的一個4階的B-樹,現在假設我們需要查詢45這個數是否在B-樹中。

  1. 從根節點出發,發現根節點a有1個關鍵字為35,其中45>35,往右子樹走,進入節點c
  2. 發現結點c有2個關鍵字,其中其中43<45<78,所以進入結點g。
  3. 發現結點g有3個關鍵字,其中3<45<47,所以繼續往下走,發現進入了結束符結點:F,所以45不在B-樹中

OK,我們從上述的查詢的過程可以得出,在B-樹的查詢過程為:

  1. 在B- 樹中查詢結點
  2. 在結點中查詢關鍵字。

由於B- 樹通常儲存在磁碟上, 則前一查詢操作是在磁碟上進行的, 而後一查詢操作是在記憶體中進行的, 即 
在磁碟上找到指標p 所指結點後, 先將結點中的資訊讀入記憶體, 然後再利用順序查詢或折半查詢查詢等於K 
的關鍵字。顯然, 在磁碟上進行一次查詢比在記憶體中進行一次查詢的時間消耗多得多. 
因此, 在磁碟上進行查詢的次數、即待查詢關鍵字所在結點在B- 樹上的層次樹, 是決定B樹查詢效率的首要 
因素,對於有n個關鍵字的m階B-樹,從根結點到關鍵字所在結點的路徑上路過的結點數不超過: 
這裡寫圖片描述

4、B-樹的插入

其實B-樹的插入是很簡單的,它主要是分為如下的兩個步驟:

 1. 使用之前介紹的查詢演算法查找出關鍵字的插入位置,如果我們在B-樹中查詢到了關鍵字,則直接返回。否則它一定會失敗在某個最底層的終端結點上。
 2.然後,我就需要判斷那個終端結點上的關鍵字數量是否滿足:n<=m-1,如果滿足的話,就直接在該終端結點上新增一個關鍵字,否則我們就需要產生結點的“分裂”。
     分裂的方法是:生成一新結點。把原結點上的關鍵字和k(需要插入的值)按升序排序後,從中間位置把關鍵字(不包括中間位置的關鍵字)分成兩部分。左部分所含關鍵字放在舊結點中,右部分所含關鍵字放在新結點中,中間位置的關鍵字連同新結點的儲存位置插入到父結點中。如果父結點的關鍵字個數也超過(m-1),則要再分裂,再往上插。直至這個過程傳到根結點為止。
  • 1
  • 2
  • 3
  • 4

下面我們來舉例說明,首先假設這個B-樹的階為:3。樹的初始化時如下: 
這裡寫圖片描述

首先,我需要插入一個關鍵字:30,可以得到如下的結果: 
這裡寫圖片描述

再插入26,得到如下的結果:

這裡寫圖片描述

OK,此時如圖所示,在插入的那個終端結點中,它的關鍵字數已經超過了m-1=2,所以我們需要對結點進分裂,所以我們先對關鍵字排序,得到:26 30 37 ,所以它的左部分為(不包括中間值):26,中間值為:30,右部為:37,左部放在原來的結點,右部放入新的結點,而中間值則插入到父結點,並且父結點會產生一個新的指標,指向新的結點的位置,如下圖所示: 
這裡寫圖片描述

OK,然後我們繼續插入新的關鍵字:85,得到如下圖結果: 
這裡寫圖片描述

正如圖所示,我需要對剛才插入的那個結點進行“分裂”操作,操作方式和之前的一樣,得到的結果如下: 
這裡寫圖片描述

哦,當我們分裂完後,突然發現之前的那個結點的父親結點的度為4了,說明它的關鍵字數超過了m-1,所以需要對其父結點進行“分裂”操作,得到如下的結果: 
這裡寫圖片描述

好,我們繼續插入一個新的關鍵字:7,得到如下結果: 
這裡寫圖片描述

同樣,需要對新的結點進行分裂操作,得到如下的結果: 
這裡寫圖片描述
到了這裡,我就需要繼續對我們的父親結點進行分裂操作,因為它的關鍵字數超過了:m-1. 
這裡寫圖片描述

哦,終於遇到這種情況了,我們的根結點出現了關鍵子數量超過m-1的情況了,這個時候我們需要對父親結點進行分列操作,但是根結點沒父親啊,所以我們需要重新建立根結點了。 
這裡寫圖片描述

好了,到了這裡我們也知道怎麼進行B-樹的插入操作。

5、B-樹的刪除操作

B-樹的刪除操作同樣是分為兩個步驟:

  1. 利用前述的B-樹的查詢演算法找出該關鍵字所在的結點。然後根據 k(需要刪除的關鍵字)所在結點是否為葉子結點有不同的處理方法。如果沒有找到,則直接返回。
  2. 若該結點為非葉結點,且被刪關鍵字為該結點中第i個關鍵字key[i],則可從指標son[i]所指的子樹中找出最小關鍵字Y,代替key[i]的位置,然後在葉結點中刪去Y。

如果是葉子結點的話,需要分為下面三種情況進行刪除。

  • 如果被刪關鍵字所在結點的原關鍵字個數n>=[m/2] ( 上取整),說明刪去該關鍵字後該結點仍滿足B-樹的定義。這種情況最為簡單,只需刪除對應的關鍵字:k和指標:A 即可。
  • 如果被刪關鍵字所在結點的關鍵字個數n等於( 上取整)[ m/2 ]-1,說明刪去該關鍵字後該結點將不滿足B-樹的定義,需要調整。

調整過程為:如果其左右兄弟結點中有“多餘”的關鍵字,即與該結點相鄰的右兄弟(或左兄弟)結點中的關鍵字數目大於( 上取整)[m/2]-1。則可將右兄弟(或左兄弟)結點中最小關鍵字(或最大的關鍵字)上移至雙親結點。而將雙親結點中小(大)於該上移關鍵字的關鍵字下移至被刪關鍵字所在結點中。

  • 被刪關鍵字所在結點和其相鄰的兄弟結點中的關鍵字數目均等於(上取整)[m/2]-1。假設該結點有右兄弟,且其右兄弟結點地址由雙親結點中的指標Ai所指,則在刪去關鍵字之後,它所在結點中剩餘的關鍵字和指標,加上雙親結點中的關鍵字Ki一起,合併到 Ai所指兄弟結點中(若沒有右兄弟,則合併至左兄弟結點中)。

下面,我們給出刪除葉子結點的三種情況: 
第一種:關鍵字的數不小於(上取整)[m/2],如下圖刪除關鍵字:12 
這裡寫圖片描述 
刪除12後的結果如下,只是簡單的刪除關鍵字12和其對應的指標。 
這裡寫圖片描述

第二種:關鍵字個數n等於( 上取整)[ m/2 ]-1,而且該結點相鄰的右兄弟(或左兄弟)結點中的關鍵字數目大於( 上取整)[m/2]-1。 
這裡寫圖片描述

如上圖,所示,我們需要刪除50這個關鍵字,所以我們需要把50的右兄弟中最小的關鍵字:61上移到其父結點,然後替換小於61的關鍵字53的位置,53則放至50的結點中。然後,我們可以得到如下的結果: 
這裡寫圖片描述

第三種:關鍵字個數n等於( 上取整)[ m/2 ]-1,而且被刪關鍵字所在結點和其相鄰的兄弟結點中的關鍵字數目均等於(上取整)[m/2]-1

這裡寫圖片描述

如上圖所示,我們需要刪除53,那麼我們就要把53所在的結點其他關鍵字(這裡沒有其他關鍵字了)和父親結點的61這個關鍵字一起合併到70這個關鍵字所佔的結點。得到如下所示的結果: 
這裡寫圖片描述

Ok,我已經分別對上述的四種刪除的情況都做了舉例,大家如果還有什麼不清楚的,可以看看程式碼,估計就可以明白了

B+樹

       B+樹是B-樹的變體,也是一種多路搜尋樹:

       1.其定義基本與B-樹同,除了:

       2.非葉子結點的子樹指標與關鍵字個數相同;

       3.非葉子結點的子樹指標P[i],指向關鍵字值屬於[K[i], K[i+1])的子樹

(B-樹是開區間);

       5.為所有葉子結點增加一個鏈指標;

       6.所有關鍵字都在葉子結點出現;

       如:(M=3)

   B+的搜尋與B-樹也基本相同,區別是B+樹只有達到葉子結點才命中(B-樹可以在

非葉子結點命中),其效能也等價於在關鍵字全集做一次二分查詢;

       B+的特性:

       1.所有關鍵字都出現在葉子結點的連結串列中(稠密索引),且連結串列中的關鍵字恰好

是有序的;

       2.不可能在非葉子結點命中;

       3.非葉子結點相當於是葉子結點的索引(稀疏索引),葉子結點相當於是儲存

(關鍵字)資料的資料層;

       4.更適合檔案索引系統;

B*樹

       是B+樹的變體,在B+樹的非根和非葉子結點再增加指向兄弟的指標;

   B*樹定義了非葉子結點關鍵字個數至少為(2/3)*M,即塊的最低使用率為2/3

(代替B+樹的1/2);

       B+樹的分裂:當一個結點滿時,分配一個新的結點,並將原結點中1/2的資料

複製到新結點,最後在父結點中增加新結點的指標;B+樹的分裂隻影響原結點和父

結點,而不會影響兄弟結點,所以它不需要指向兄弟的指標;

       B*樹的分裂:當一個結點滿時,如果它的下一個兄弟結點未滿,那麼將一部分

資料移到兄弟結點中,再在原結點插入關鍵字,最後修改父結點中兄弟結點的關鍵字

(因為兄弟結點的關鍵字範圍改變了);如果兄弟也滿了,則在原結點與兄弟結點之

間增加新結點,並各複製1/3的資料到新結點,最後在父結點增加新結點的指標;

       所以,B*樹分配新結點的概率比B+樹要低,空間使用率更高;

小結

       B樹:二叉樹,每個結點只儲存一個關鍵字,等於則命中,小於走左結點,大於

走右結點;

       B-樹:多路搜尋樹,每個結點儲存M/2到M個關鍵字,非葉子結點儲存指向關鍵

字範圍的子結點;

       所有關鍵字在整顆樹中出現,且只出現一次,非葉子結點可以命中;

       B+樹:在B-樹基礎上,為葉子結點增加連結串列指標,所有關鍵字都在葉子結點

中出現,非葉子結點作為葉子結點的索引;B+樹總是到葉子結點才命中;

       B*樹:在B+樹基礎上,為非葉子結點也增加連結串列指標,將結點的最低利用率

從1/2提高到2/3;