B族樹詳解(二叉搜尋樹、B-樹、B+樹、B*樹)
二叉搜尋樹
二叉搜尋樹:
1.所有非葉子結點至多擁有兩個兒子(Left和Right);
2.所有結點儲存一個關鍵字;
3.非葉子結點的左指標指向小於其關鍵字的子樹,右指標指向大於其關鍵字的子樹;
如:
B樹的搜尋,從根結點開始,如果查詢的關鍵字與結點的關鍵字相等,那麼就命中;
否則,如果查詢關鍵字比結點關鍵字小,就進入左兒子;如果比結點關鍵字大,就進入
右兒子;如果左兒子或右兒子的指標為空,則報告找不到相應的關鍵字;
如果B樹的所有非葉子結點的左右子樹的結點數目均保持差不多(平衡),那麼B樹
的搜尋效能逼近二分查詢;但它比連續記憶體空間的二分查詢的優點是,改變
(插入與刪除結點)不需要移動大段的記憶體資料,甚至通常是常數開銷;
如:
但B樹在經過多次插入與刪除後,有可能導致不同的結構:
右邊也是一個B樹,但它的搜尋效能已經是線性的了;同樣的關鍵字集合有可能導致不同的
樹結構索引;所以,使用B樹還要考慮儘可能讓B樹保持左圖的結構,和避免右圖的結構,也就
是所謂的“平衡”問題;
實際使用的B樹都是在原B樹的基礎上加上平衡演算法,即“平衡二叉樹”;如何保持B樹
結點分佈均勻的平衡演算法是平衡二叉樹的關鍵;平衡演算法是一種在B樹中插入和刪除結點的
策略;
B-樹
是一種多路搜尋樹(並不是二叉的):
1.
2.根結點的兒子數為[2, M];
3.除根結點以外的非葉子結點的兒子數為[M/2, M];
4.每個結點存放至少M/2-1(取上整)和至多M-1個關鍵字;(至少2個關鍵字)
5.非葉子結點的關鍵字個數=指向兒子的指標個數-1;
6.非葉子結點的關鍵字:K[1], K[2], …, K[M-1];且K[i] < K[i+1];
7.非葉子結點的指標:P[1], P[2], …, P[M];其中P[1]指向關鍵字小於K[1]的
子樹,P[M]指向關鍵字大於
8.所有葉子結點位於同一層;
如:(M=3)
B-樹的搜尋,從根結點開始,對結點內的關鍵字(有序)序列進行二分查詢,如果
命中則結束,否則進入查詢關鍵字所屬範圍的兒子結點;重複,直到所對應的兒子指標為
空,或已經是葉子結點;
B-樹的特性:
1.關鍵字集合分佈在整顆樹中;
2.任何一個關鍵字出現且只出現在一個結點中;
3.搜尋有可能在非葉子結點結束;
4.其搜尋效能等價於在關鍵字全集內做一次二分查詢;
5.自動層次控制;
由於限制了除根結點以外的非葉子結點,至少含有M/2個兒子,確保了結點的至少
利用率,其最底搜尋效能為:
其中,M為設定的非葉子結點最多子樹個數,N為關鍵字總數;
所以B-樹的效能總是等價於二分查詢(與M值無關),也就沒有B樹平衡的問題;
由於M/2的限制,在插入結點時,如果結點已滿,需要將結點分裂為兩個各佔
M/2的結點;刪除結點時,需將兩個不足M/2的兄弟結點合併;
B樹的操作:
1、B-樹的查詢
B-樹的查詢過程:根據給定值查詢結點和在結點的關鍵字中進行查詢交叉進行。首先從根結點開始重複如下過程:
若比結點的第一個關鍵字小,則查詢在該結點第一個指標指向的結點進行;若等於結點中某個關鍵字,則查詢成功;若在兩個關鍵字之間,則查詢在它們之間的指標指向的結點進行;若比該結點所有關鍵字大,則查詢在該結點最後一個指標指向的結點進行;若查詢已經到達某個葉結點,則說明給定值對應的資料記錄不存在,查詢失敗。
2. B-樹的插入
插入的過程分兩步完成:
(1)利用前述的B-樹的查詢演算法查詢關鍵字的插入位置。若找到,則說明該關鍵字已經存在,直接返回。否則查詢操作必失敗於某個最低層的非終端結點上。
(2)判斷該結點是否還有空位置。即判斷該結點的關鍵字總數是否滿足n<m-1。若滿足,則說明該結點還有空位置,直接把關鍵字k插入到該結點的合適位置上。若不滿足,說明該結點己沒有空位置,需要把結點分裂成兩個。
分裂的方法是:生成一新結點。把原結點上的關鍵字和k按升序排序後,從中間位置把關鍵字(不包括中間位置的關鍵字)分成兩部分。左部分所含關鍵字放在舊結點中,右部分所含關鍵字放在新結點中,中間位置的關鍵字連同新結點的儲存位置插入到父結點中。如果父結點的關鍵字個數也超過(m-1),則要再分裂,再往上插。直至這個過程傳到根結點為止。
3、B-樹的刪除
在B-樹上刪除關鍵字k的過程分兩步完成:
(1)利用前述的B-樹的查詢演算法找出該關鍵字所在的結點。然後根據 k所在結點是否為葉子結點有不同的處理方法。
(2)若該結點為非葉結點,且被刪關鍵字為該結點中第i個關鍵字key[i],則可從指標son[i]所指的子樹中找出最小關鍵字Y,代替key[i]的位置,然後在葉結點中刪去Y。
因此,把在非葉結點刪除關鍵字k的問題就變成了刪除葉子結點中的關鍵字的問題了。
在B-樹葉結點上刪除一個關鍵字的方法是
首先將要刪除的關鍵字 k直接從該葉子結點中刪除。然後根據不同情況分別作相應的處理,共有三種可能情況:
(1)如果被刪關鍵字所在結點的原關鍵字個數n>=ceil(m/2),說明刪去該關鍵字後該結點仍滿足B-樹的定義。這種情況最為簡單,只需從該結點中直接刪去關鍵字即可。
(2)如果被刪關鍵字所在結點的關鍵字個數n等於ceil(m/2)-1,說明刪去該關鍵字後該結點將不滿足B-樹的定義,需要調整。
調整過程為:如果其左右兄弟結點中有“多餘”的關鍵字,即與該結點相鄰的右(左)兄弟結點中的關鍵字數目大於ceil(m/2)-1。則可將右(左)兄弟結點中最小(大)關鍵字上移至雙親結點。而將雙親結點中小(大)於該上移關鍵字的關鍵字下移至被刪關鍵字所在結點中。
(3)如果左右兄弟結點中沒有“多餘”的關鍵字,即與該結點相鄰的右(左)兄弟結點中的關鍵字數目均等於ceil(m/2)-1。這種情況比較複雜。需把要刪除關鍵字的結點與其左(或右)兄弟結點以及雙親結點中分割二者的關鍵字合併成一個結點,即在刪除關鍵字後,該結點中剩餘的關鍵字加指標,加上雙親結點中的關鍵字Ki一起,合併到Ai(是雙親結點指向該刪除關鍵字結點的左(右)兄弟結點的指標)所指的兄弟結點中去。如果因此使雙親結點中關鍵字個數小於ceil(m/2)-1,則對此雙親結點做同樣處理。以致於可能直到對根結點做這樣的處理而使整個樹減少一層。
總之,設所刪關鍵字為非終端結點中的Ki,則可以指標Ai所指子樹中的最小關鍵字Y代替Ki,然後在相應結點中刪除Y。對任意關鍵字的刪除都可以轉化為對最下層關鍵字的刪除。
如圖示:
a、被刪關鍵字Ki所在結點的關鍵字數目不小於ceil(m/2),則只需從結點中刪除Ki和相應指標Ai,樹的其它部分不變。
b、被刪關鍵字Ki所在結點的關鍵字數目等於ceil(m/2)-1,則需調整。調整過程如上面所述。
c、被刪關鍵字Ki所在結點和其相鄰兄弟結點中的的關鍵字數目均等於ceil(m/2)-1,假設該結點有右兄弟,且其右兄弟結點地址由其雙親結點指標Ai所指。則在刪除關鍵字之後,它所在結點的剩餘關鍵字和指標,加上雙親結點中的關鍵字Ki一起,合併到Ai所指兄弟結點中(若無右兄弟,則合併到左兄弟結點中)。如果因此使雙親結點中的關鍵字數目少於ceil(m/2)-1,則依次類推。
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+樹的操作:
查詢
查詢以典型的方式進行,類似於二叉查詢樹。起始於根節點,自頂向下遍歷樹,選擇其分離值在要查詢值的任意一邊的子指標。在節點內部典型的使用是二分查詢來確定這個位置。
插入
節點要處於違規狀態,它必須包含在可接受範圍之外數目的元素。
- 首先,查詢要插入其中的節點的位置。接著把值插入這個節點中。
- 如果沒有節點處於違規狀態則處理結束。
- 如果某個節點有過多元素,則把它分裂為兩個節點,每個都有最小數目的元素。在樹上遞歸向上繼續這個處理直到到達根節點,如果根節點被分裂,則建立一個新根節點。為了使它工作,元素的最小和最大數目典型的必須選擇為使最小數不小於最大數的一半。
刪除
- 首先,查詢要刪除的值。接著從包含它的節點中刪除這個值。
- 如果沒有節點處於違規狀態則處理結束。
- 如果節點處於違規狀態則有兩種可能情況:
- 它的兄弟節點,就是同一個父節點的子節點,可以把一個或多個它的子節點轉移到當前節點,而把它返回為合法狀態。如果是這樣,在更改父節點和兩個兄弟節點的分離值之後處理結束。
- 它的兄弟節點由於處在低邊界上而沒有額外的子節點。在這種情況下把兩個兄弟節點合併到一個單一的節點中,而且我們遞迴到父節點上,因為它被刪除了一個子節點。持續這個處理直到當前節點是合法狀態或者到達根節點,在其上根節點的子節點被合併而且合併後的節點成為新的根節點。
l 例1:
往下圖的3階B+樹中插入關鍵字9
首先查詢9應插入的葉節點(最左下角的那一個),插入發現沒有破壞B+樹的性質,完畢。插完如下圖所示:
l 例2:
往下圖的3階B+樹插入20
首先查詢20應插入的葉節點(第二個葉子節點),插入,如下圖
發現第二個葉子節點已經破壞了B+樹的性質,則把之分解成[20 21], [37 44]兩個,並把21往父節點移,如下圖
發現父節點也破壞了B+樹的性質,則把之再分解成[15 21], [44 59]兩個,並把21往其父節點移,如下圖
這次沒有破壞B+樹的性質(如果還是不滿足B+樹的性質,可以遞迴上去,直到滿足為至),插入完畢。
l 例3:
往下圖的3階B+樹插入100
首先查詢100應插入的葉節點(最後一個節點), 插入,如下圖
修改其所有父輩節點的鍵值為100(只有插入比當前樹的最大數大的數時要做此步),如下圖
然後重複Eg.2的方法拆分節點,最後得
三, 3階B+樹的刪除舉例:
l 例1:
刪除下圖3階B+樹的關鍵字91
首先找到91所在葉節點(最後一個節點),刪除之,如下圖
沒有破壞B+樹的性質,刪除完畢
l 例2:
刪除下圖3階B+樹的關鍵字97
首先找到97所在葉節點(最後一個節點),刪除之,然後修改該節點的父輩的鍵字為91(只有刪除樹中最大數時要做此步),如下圖
l 例3:
刪除下圖3階B+樹的關鍵字51
首先找到51所在節點(第三個節點),刪除之,如下圖
破壞了B+樹的性質,從該節點的兄弟節點(左邊或右邊)借節點44,並修改相應鍵值,判斷沒有破壞B+樹,完畢,如下圖
l 例4:
刪除下圖3階B+樹的關鍵字59
首先找到59所在葉節點(第三個節點),刪除之,如下圖
破壞B+樹性質,嘗試借節點,無效(因為左兄弟節點被借也會破壞B+樹性質),合併第二第三葉節點並調整鍵值,如下圖
完畢。
l 例5:
刪除下圖3階B+樹的關鍵字63
首先找到63所在葉節點(第四個節點),刪除之,如下圖
合併第四五葉節點並調整鍵值,如下圖
發現第二層的第二個節點不滿足B+樹性質,從第二層的第一個節點借59,並調整鍵值,如下圖
B*樹
是B+樹的變體,在B+樹的非根和非葉子結點再增加指向兄弟的指標;
B*樹定義了非葉子結點關鍵字個數至少為(2/3)*M,即塊的最低使用率為2/3
(代替B+樹的1/2);
B+樹的分裂:當一個結點滿時,分配一個新的結點,並將原結點中1/2的資料
複製到新結點,最後在父結點中增加新結點的指標;B+樹的分裂隻影響原結點和父
結點,而不會影響兄弟結點,所以它不需要指向兄弟的指標;
B*樹的分裂:當一個結點滿時,如果它的下一個兄弟結點未滿,那麼將一部分
資料移到兄弟結點中,再在原結點插入關鍵字,最後修改父結點中兄弟結點的關鍵字
(因為兄弟結點的關鍵字範圍改變了);如果兄弟也滿了,則在原結點與兄弟結點之
間增加新結點,並各複製1/3的資料到新結點,最後在父結點增加新結點的指標;
所以,B*樹分配新結點的概率比B+樹要低,空間使用率更高;
Mysql等資料庫中採用B+樹而不是B樹的原因:
1、B-樹和B+樹最重要的一個區別就是B+樹只有葉節點存放資料,其餘節點用來索引,而B-樹是每個索引節點都會有Data域。
這就決定了B+樹更適合用來儲存外部資料,也就是所謂的磁碟資料。
2、從Mysql(Inoodb)的角度來看,B+樹是用來充當索引的,一般來說索引非常大,尤其是關係性資料庫這種資料量大的索引能達到億級別,所以為了減少記憶體的佔用,索引也會被儲存在磁碟上。 3、B+樹的磁碟讀寫代價更低 B+樹的內部結點並沒有指向關鍵字具體資訊的指標。因此其內部結點相對B 樹更小。如果把所有同一內部結點的關鍵字存放在同一盤塊中,那麼盤塊所能容納的關鍵字數量也越多。一次性讀入記憶體中的需要查詢的關鍵字也就越多。相對來說IO讀寫次數也就降低了。 舉個例子,假設磁碟中的一個盤塊容納16bytes,而一個關鍵字2bytes,一個關鍵字具體資訊指標2bytes。一棵9階B-tree(一個結點最多8個關鍵字)的內部結點需要2個盤快。而B+樹內部結點只需要1個盤快(全部關鍵字都在葉結點的緣故?)。當需要把內部結點讀入記憶體中的時候,B-樹就比B+樹多一次盤塊查詢時間(在磁碟中就是碟片旋轉的時間)(B+樹的內結點只有索引的作用,何來“把內部結點讀入記憶體”...,對於B+樹找到葉結點就可以,另外B+樹可以順序查詢)。 4、B+樹的查詢效率更加穩定 由於非終結點並不是最終指向檔案內容的結點,而只是葉子結點中關鍵字的索引。所以任何關鍵字的查詢必須走一條從根結點到葉子結點的路。所有關鍵字查詢的路徑長度相同,導致每一個數據的查詢效率相當。
5、B+樹所有的Data域在葉子節點,一般來說都會進行一個優化,就是將所有的葉子節點用指標串起來。這樣遍歷葉子節點就能獲得全部資料,這樣就能進行區間訪問啦。
小結
B樹:二叉樹,每個結點只儲存一個關鍵字,等於則命中,小於走左結點,大於
走右結點;
B-樹:多路搜尋樹,每個結點儲存M/2-1到M-1個關鍵字,非葉子結點儲存指向關鍵
字範圍的子結點;
所有關鍵字在整顆樹中出現,且只出現一次,非葉子結點可以命中;
B+樹:在B-樹基礎上,為葉子結點增加連結串列指標,所有關鍵字都在葉子結點
中出現,非葉子結點作為葉子結點的索引;B+樹總是到葉子結點才命中;
B*樹:在B+樹基礎上,為非葉子結點也增加連結串列指標,將結點的最低利用率
從1/2提高到2/3;