樹及樹的演算法(5) —— B樹(上)
1970年,魯道夫·貝爾(R.Bayer)的先於紅黑樹提出了B樹。這種適用於外查詢的樹,是一種平衡的多叉樹,又稱B-樹。
B樹與紅黑樹最大的不同在於,B樹的結點可以有許多子女,從幾個到幾千個。那為什麼又說B樹與紅黑樹很相似呢?因為與紅黑樹一樣,一棵含n個結點的B樹的高度也為O(log2n),但可能比一棵紅黑樹的高度小許多,應為它的分支因子比較大。所以,B樹可以在O(log2n)時間內,實現各種如插入(insert),刪除(delete)等動態集合操作。
如下圖所示,即是一棵B樹,一棵關鍵字為英語中子音字母的B樹,現在要從樹中查詢字母R(包含n[x]個關鍵字的內結點x,x有n[x]+1]個子女(也就是說,一個內結點
相信,從上圖你能輕易的看到,一個內結點x若含有n個關鍵字,那麼x將含有n+1個子女。如含有2個關鍵字D H的內結點有3個子女,而含有3個關鍵字Q T X的內結點有4個子女。
一、B樹(B-樹)的概念
B 樹又叫平衡多路查詢樹。一棵m階的B 樹 (m叉樹)的特性如下:
1. 樹中每個結點最多含有m個孩子(m>=2);
2. 除根結點和葉子結點外,其它每個結點至少有[ceil(m / 2)]個孩子(其中ceil(x)是一個向上去整的函式);
3. 如果根結點不是葉子結點,則至少有2個孩子(特殊情況:沒有孩子的根結點,即根結點為葉子結點,整棵樹只有一個根節點);
4. 所有葉子結點都出現在同一層,葉子結點不包含任何關鍵字資訊(可以看做是外部接點或查詢失敗的接點,實際上這些結點不存在,指向這些結點的指標都為null);
5. 每個非終端結點中包含有n個關鍵字資訊: (n,P0,K1,P1,K2,P2,......,Kn,Pn)。其中:
a) Ki (i=1...n)為關鍵字,且關鍵字按順序升序排序K(i-1)< Ki。
b)
Pi為指向子樹根的節點,且指標P(i-1)指向子樹種所有結點的關鍵字均小於Ki
c) 關鍵字的個數n必須滿足: [ceil(m / 2)-1]<= n <= m-1。
針對上面第5點,再闡述下:B樹中每一個結點能包含的關鍵字數有一個上界和下界。這兩個界可以用一個稱作B樹的最小度數t(t>=2)表示。
每個非根的結點必須至少含有t-1個關鍵字。每個非根的內結點至少有t個子女。如果樹是非空的,則根結點至少包含一個關鍵字;
每個結點可包含之多2t-1個關鍵字。所以一個內結點至多可有2t個子女。如果一個結點恰好有2t-1個關鍵字,我們就說這個結點是滿的(而稍後介紹的B*樹作為B樹的一種常用變形,B*樹中要求每個內結點至少為2/3滿,而不是像這裡的B樹所要求的至少半滿);
當關鍵字數t=2(t=2的意思是,tmin=2,t可以>=2)時的B樹是最簡單的(有很多人會因此誤認為B樹就是二叉查詢樹,但二叉查詢樹就是二叉查詢樹,B樹就是B樹,B樹的真正最準確的定義為:一棵含有t(t>=2)個關鍵字的平衡多路查詢樹)。每個內結點可能因此而含有2個、3個或4個子女,亦即一棵2-3-4樹,然而在實際中,通常採用大得多的t值。
B樹中的每個結點根據實際情況可以包含大量的關鍵字資訊和分支(當然是不能超過磁碟塊的大小,根據磁碟驅動(disk drives)的不同,一般塊的大小在512B~4K左右);這樣樹的深度降低了,這就意味著查詢一個元素只要很少結點從外存磁碟中讀入記憶體,很快訪問到要查詢的資料。
B樹的概念介紹完了,對於這麼複雜和抽象的演算法,要想把它吃透,只有拿出我們看家本領了,具體化:
為了簡單,這裡用少量資料構造一棵3叉樹的形式,實際應用中的B樹結點中關鍵字很多的。上面的圖中比如根結點,其中17比表示一個磁碟檔案的檔名;小紅方塊表示這個17檔案內容在硬碟中的儲存位置;p1表示指向17左子樹的指標。
其結構可以簡單定義為:
typedef struct {
/*檔案數*/
int file_num;
/*檔名(key)*/
char * file_name[max_file_num];
/*指向子節點的指標*/
BTNode * BTptr[max_file_num+1];
/*檔案在硬碟中的儲存位置*/
FILE_HARD_ADDR offset[max_file_num];
}BTNode;
假如每個盤塊可以正好存放一個B樹的結點(正好存放2個檔名)。那麼一個BTNODE結點就代表一個盤塊,而子樹指標就是存放另外一個盤塊的地址。
下面,咱們來模擬下查詢檔案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,並定位了該檔案記憶體的磁碟地址。
分析上面的過程,發現需要3次磁碟IO操作和3次記憶體查詢操作。關於記憶體中的檔名查詢,由於是一個有序表結構,可以利用折半查詢提高效率。至於IO操作時影響整個B樹查詢效率的決定因素。
當然,如果我們使用平衡二叉樹的磁碟儲存結構來進行查詢,磁碟4次,最多5次,而且檔案越多,B樹比平衡二叉樹所用的磁碟IO操作次數將越少,效率也越高。
二、B+樹的概念
B+樹是應檔案系統所需而產生的一種B-tree的變形樹。一棵m階的B+樹和m階的B樹的差異在於:
1. 有n棵子樹的結點中含有n個關鍵字; (而B 樹是n棵子樹有n-1個關鍵字)
2. 所有的葉子結點中包含了全部關鍵字的資訊,及指向含有這些關鍵字記錄的指標,且葉子結點本身依關鍵字的大小自小而大的順序連結。 (而B 樹的葉子節點並沒有包括全部需要查詢的資訊)
3. 所有的非終端結點可以看成是索引部分,結點中僅含有其子樹根結點中最大(或最小)關鍵字。 (而B 樹的非終節點也包含需要查詢的有效資訊)
為什麼說B+-tree比B 樹更適合實際應用中作業系統的檔案索引和資料庫索引?
(1)B+-tree的磁碟讀寫代價更低
B+-tree的內部結點並沒有指向關鍵字具體資訊的指標。因此其內部結點相對B 樹更小。如果把所有同一內部結點的關鍵字存放在同一盤塊中,那麼盤塊所能容納的關鍵字數量也越多。一次性讀入記憶體中的需要查詢的關鍵字也就越多。相對來說IO讀寫次數也就降低了。
舉個例子,假設磁碟中的一個盤塊容納16bytes,而一個關鍵字2bytes,一個關鍵字具體資訊指標2bytes。一棵9階B-tree(一個結點最多8個關鍵字)的內部結點需要2個盤快。而B+ 樹內部結點只需要1個盤快。當需要把內部結點讀入記憶體中的時候,B 樹就比B+ 樹多一次盤塊查詢時間(在磁碟中就是碟片旋轉的時間)。
(2)B+-tree的查詢效率更加穩定
由於非終結點並不是最終指向檔案內容的結點,而只是葉子結點中關鍵字的索引。所以任何關鍵字的查詢必須走一條從根結點到葉子結點的路。所有關鍵字查詢的路徑長度相同,導致每一個數據的查詢效率相當。
B+-tree的應用:VSAM(虛擬儲存存取法)檔案(來源論文 the ubiquitous Btree 作者:D COMER - 1979 )
三、B*樹的概念
B*樹是B+樹的變體,在B+ 樹非根和非葉子結點再增加指向兄弟的指標;B*樹定義了非葉子結點關鍵字個數至少為(2/3)*M,即塊的最低使用率為2/3(代替B+樹的1/2)。給出了一個簡單例項,如下圖所示:
B+樹的分裂:當一個結點滿時,分配一個新的結點,並將原結點中1/2的資料複製到新結點,最後在父結點中增加新結點的指標;B+樹的分裂隻影響原結點和父結點,而不會影響兄弟結點,所以它不需要指向兄弟的指標。
B*樹的分裂:當一個結點滿時,如果它的下一個兄弟結點未滿,那麼將一部分資料移到兄弟結點中,再在原結點插入關鍵字,最後修改父結點中兄弟結點的關鍵字(因為兄弟結點的關鍵字範圍改變了);如果兄弟也滿了,則在原結點與兄弟結點之間增加新結點,並各複製1/3的資料到新結點,最後在父結點增加新結點的指標。
所以,B*樹分配新結點的概率比B+樹要低,空間使用率更高。