1. 程式人生 > >樹及樹的演算法(5) —— B樹(上)

樹及樹的演算法(5) —— B樹(上)

1970年,魯道夫·貝爾(R.Bayer)的先於紅黑樹提出了B樹。這種適用於外查詢的樹,是一種平衡的多叉樹,又稱B-樹。

B樹與紅黑樹最大的不同在於,B樹的結點可以有許多子女,從幾個到幾千個。那為什麼又說B樹與紅黑樹很相似呢?因為與紅黑樹一樣,一棵含n個結點的B樹的高度也為Olog2n),但可能比一棵紅黑樹的高度小許多,應為它的分支因子比較大。所以,B樹可以在Olog2n)時間內,實現各種如插入(insert),刪除(delete)等動態集合操作。

如下圖所示,即是一棵B樹,一棵關鍵字為英語中子音字母的B樹,現在要從樹中查詢字母R(包含n[x]個關鍵字的內結點xxn[x]+1]個子女(也就是說,一個內結點

x若含有n[x]個關鍵字,那麼x將含有n[x]+1個子女)。所有的葉結點都處於相同的深度,帶陰影的結點為查詢字母R時要檢查的結點):

 

相信,從上圖你能輕易的看到,一個內結點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個關鍵字資訊: (nP0K1P1K2P2......KnPn)。其中:

a)         Ki (i=1...n)為關鍵字,且關鍵字按順序升序排序K(i-1)< Ki

b)        Pi為指向子樹根的節點,且指標P(i-1)指向子樹種所有結點的關鍵字均小於Ki

,但都大於K(i-1)

c)        關鍵字的個數n必須滿足: [ceil(m / 2)-1]<= n <= m-1

針對上面第5點,再闡述下:B樹中每一個結點能包含的關鍵字數有一個上界和下界。這兩個界可以用一個稱作B樹的最小度數tt>=2)表示。

每個非根的結點必須至少含有t-1個關鍵字。每個非根的內結點至少有t個子女。如果樹是非空的,則根結點至少包含一個關鍵字;

每個結點可包含之多2t-1個關鍵字。所以一個內結點至多可有2t個子女。如果一個結點恰好有2t-1個關鍵字,我們就說這個結點是滿的(而稍後介紹的B*樹作為B樹的一種常用變形,B*樹中要求每個內結點至少為2/3滿,而不是像這裡的B樹所要求的至少半滿);

當關鍵字數t=2t=2的意思是,tmin=2t可以>=2)時的B樹是最簡單的(有很多人會因此誤認為B樹就是二叉查詢樹,但二叉查詢樹就是二叉查詢樹,B樹就是B樹,B樹的真正最準確的定義為:一棵含有tt>=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.  此時記憶體中有兩個檔名1735和三個儲存其他磁碟頁面地址的資料。根據演算法我們發現17<29<35,因此我們找到指標p2

3.  根據p2指標,我們定位到磁碟塊3,並將其中的資訊匯入記憶體。【磁碟IO操作 2次】

4.  此時記憶體中有兩個檔名2630和三個儲存其他磁碟頁面地址的資料。根據演算法我們發,26<29<30,因此我們找到指標p2

5.  根據p2指標,我們定位到磁碟塊8,並將其中的資訊匯入記憶體。【磁碟IO操作 3次】

6.  此時記憶體中有兩個檔名2829。根據演算法我們查詢到文,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+-treeB 樹更適合實際應用中作業系統的檔案索引和資料庫索引?

1B+-tree的磁碟讀寫代價更低

B+-tree的內部結點並沒有指向關鍵字具體資訊的指標。因此其內部結點相對B 樹更小。如果把所有同一內部結點的關鍵字存放在同一盤塊中,那麼盤塊所能容納的關鍵字數量也越多。一次性讀入記憶體中的需要查詢的關鍵字也就越多。相對來說IO讀寫次數也就降低了。

舉個例子,假設磁碟中的一個盤塊容納16bytes,而一個關鍵字2bytes,一個關鍵字具體資訊指標2bytes。一棵9B-tree(一個結點最多8個關鍵字)的內部結點需要2個盤快。而B+ 樹內部結點只需要1個盤快。當需要把內部結點讀入記憶體中的時候,B 樹就比B+ 樹多一次盤塊查詢時間(在磁碟中就是碟片旋轉的時間)

2B+-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+樹要低,空間使用率更高。