天天看點

BTree與B+Tree

b 樹是為了磁盤或其它儲存設備而設計的一種多叉(下面你會看到,相對于二叉,b樹每個内結點有多個分支,即多叉)平衡查找樹。

b 樹又叫平衡多路查找樹。一棵m階的b 樹 (m叉樹)的特性如下:

樹中每個結點最多含有m個孩子(m>=2);

除根結點和葉子結點外,其它每個結點至少有[ceil(m / 2)]個孩子(其中ceil(x)是一個取上限的函數);

若根結點不是葉子結點,則至少有2個孩子(特殊情況:沒有孩子的根結點,即根結點為葉子結點,整棵樹隻有一個根節點);

所有葉子結點都出現在同一層,葉子結點不包含任何關鍵字資訊(可以看做是外部接點或查詢失敗的接點,實際上這些結點不存在,指向這些結點的指針都為null);

每個非終端結點中包含有n個關鍵字資訊: (n,p0,k1,p1,k2,p2,......,kn,pn)。其中:

       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。

BTree與B+Tree

來模拟下查找檔案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,并定位了該檔案記憶體的磁盤位址。

插入操作

生 成從空樹開始,逐個插入關鍵字。但是由于b_樹節點關鍵字必須大于等于[ceil(m/2)-1],是以每次插入一個關鍵字不是在樹中添加一個葉子結點, 而是首先在最底層的某個非終端節點中添加一個“關鍵字”,該結點的關鍵字不超過m-1,則插入完成;否則要産生結點的“分裂”,将一半數量的關鍵字元素分裂到新的其相鄰右結點中,中間關鍵字元素上移到父結點中。

1、咱們通過一個執行個體來逐漸講解下。插入以下字元字母到一棵空的b 樹中(非根結點關鍵字數小了(小于2個)就合并,大了(超過4個)就分裂):c n g a h e k q m f w l t z d p r x y s,首先,結點空間足夠,4個字母插入相同的結點中,如下圖:

BTree與B+Tree

2、當咱們試着插入h時,結點發現空間不夠,以緻将其分裂成2個結點,移動中間元素g上移到新的根結點中,在實作過程中,咱們把a和c留在目前結點中,而h和n放置新的其右鄰居結點中。如下圖:

BTree與B+Tree

3、當咱們插入e,k,q時,不需要任何分裂操作

BTree與B+Tree

4、插入m需要一次分裂,注意m恰好是中間關鍵字元素,以緻向上移到父節點中

BTree與B+Tree

5、插入f,w,l,t不需要任何分裂操作

BTree與B+Tree

6、插入z時,最右的葉子結點空間滿了,需要進行分裂操作,中間元素t上移到父節點中,注意通過上移中間元素,樹最終還是保持平衡,分裂結果的結點存在2個關鍵字元素。

BTree與B+Tree

7、插入d時,導緻最左邊的葉子結點被分裂,d恰好也是中間元素,上移到父節點中,然後字母p,r,x,y陸續插入不需要任何分裂操作(别忘了,樹中至多5個孩子)。

BTree與B+Tree

8、最後,當插入s時,含有n,p,q,r的結點需要分裂,把中間元素q上移到父節點中,但是情況來了,父節點中空間已經滿了,是以也要進行分裂,将父節點中的中間元素m上移到新形成的根結點中,注意以前在父節點中的第三個指針在修改後包括d和g節點中。這樣具體插入操作的完成。

BTree與B+Tree

删除操作

首先查找b樹中需删除的元素,如果該元素在b樹中存在,則将該元素在其結點中進行删除,如果删除該元素後,首先判斷該元素是否有左右孩子結點,如果有,則上移孩子結點中的某相近元素到父節點中,然後是移動之後的情況;如果沒有,直接删除後,移動之後的情況。

删除元素,移動相應元素之後,如果某結點中元素數目(即關鍵字數)小于ceil(m/2)-1,則需要看其某相鄰兄弟結點是否豐滿(結點中元素個數大于ceil(m/2)-1)(還記得第一節中關于b樹的第5個特性中的c點麼?: c)除根結點之外的結點(包括葉子結點)的關鍵字的個數n必須滿足:  (ceil(m / 2)-1)<= n <= m-1。m表示最多含有m個孩子,n表示關鍵字數。在本小節中舉的一顆b樹的示例中,關鍵字數n滿足:2<=n<=4),如果豐滿,則向父節點借一個元素來滿足條件;如果其相鄰兄弟都剛脫貧,即借了之後其結點數目小于ceil(m/2)-1,則該結點與其相鄰的某一兄弟結點進行“合并”成一個結點,以此來滿足條件。那咱們通過下面執行個體來詳細了解吧。

以上述插入操作構造的一棵5階b樹(樹中最多含有m(m=5)個孩子,是以關鍵字數最小為ceil(m  / 2)-1=2。還是這句話,關鍵字數小了(小于2個)就合并,大了(超過4個)就分裂)為例,依次删除h,t,r,e。

BTree與B+Tree

1、首先删除元素h,當然首先查找h,h在一個葉子結點中,且該葉子結點元素數目3大于最小元素數目ceil(m/2)-1=2,則操作很簡單,咱們隻需要移動k至原來h的位置,移動l至k的位置(也就是結點中删除元素後面的元素向前移動)

BTree與B+Tree

2、下一步,删除t,因為t沒有在葉子結點中,而是在中間結點中找到,咱們發現他的繼承者w(字母升序的下個元素),将w上移到t的位置,然後将原包含w的孩子結點中的w進行删除,這裡恰好删除w後,該孩子結點中元素個數大于2,無需進行合并操作。

BTree與B+Tree

3、下一步删除r,r在葉子結點中,但是該結點中元素數目為2,删除導緻隻有1個元素,已經小于最小元素數目ceil(5/2)-1=2,而由前面我們已經知道:如果其某個相鄰兄弟結點中比較豐滿(元素個數大于ceil(5/2)-1=2),則可以向父結點借一個元素,然後将最豐滿的相鄰兄弟結點中上移最後或最前一個元素到父節點中(有沒有看到紅黑樹中左旋操作的影子?),在這個執行個體中,右相鄰兄弟結點中比較豐滿(3個元素大于2),是以先向父節點借一個元素w下移到該葉子結點中,代替原來s的位置,s前移;然後x在相鄰右兄弟結點中上移到父結點中,最後在相鄰右兄弟結點中删除x,後面元素前移。

BTree與B+Tree

4、最後一步删除e, 删除後會導緻很多問題,因為e所在的結點數目剛好達标,剛好滿足最小元素個數(ceil(5/2)-1=2),而相鄰的兄弟結點也是同樣的情況,删除一個元素都不能滿足條件,是以需要該節點與某相鄰兄弟結點進行合并操作;首先移動父結點中的元素(該元素在兩個需要合并的兩個結點元素之間)下移到其子結點中,然後将這兩個結點進行合并成一個結點。是以在該執行個體中,咱們首先将父節點中的元素d下移到已經删除e而隻有f的結點中,然後将含有d和f的結點和含有a,c的相鄰兄弟結點進行合并成一個結點。

BTree與B+Tree

5、也許你認為這樣删除操作已經結束了,其實不然,在看看上圖,對于這種特殊情況,你立即會發現父節點隻包含一個元素g,沒達标(因為非根節點包括葉子結點的關鍵字數n必須滿足于2=<n<=4,而此處的n=1),這是不能夠接受的。如果這個問題結點的相鄰兄弟比較豐滿,則可以向父結點借一個元素。假設這時右兄弟結點(含有q,x)有一個以上的元素(q右邊還有元素),然後咱們将m下移到元素很少的子結點中,将q上移到m的位置,這時,q的左子樹将變成m的右子樹,也就是含有n,p結點被依附在m的右指針上。是以在這個執行個體中,咱們沒有辦法去借一個元素,隻能與兄弟結點進行合并成一個結點,而根結點中的唯一進制素m下移到子結點,這樣,樹的高度減少一層。

BTree與B+Tree

為了進一步詳細讨論删除的情況,再舉另外一個執行個體:

這裡是一棵不同的5序b樹,那咱們試着删除c

BTree與B+Tree

于是将删除元素c的右子結點中的d元素上移到c的位置,但是出現上移元素後,隻有一個元素的結點的情況。

又因為含有e的結點,其相鄰兄弟結點才剛脫貧(最少元素個數為2),不可能向父節點借元素,是以隻能進行合并操作,于是這裡将含有a,b的左兄弟結點和含有e的結點進行合并成一個結點。

BTree與B+Tree

這樣又出現隻含有一個元素f結點的情況,這時,其相鄰的兄弟結點是豐滿的(元素個數為3>最小元素個數2),這樣就可以想父結點借元素了,把父結點中的j下移到該結點中,相應的如果結點中j後有元素則前移,然後相鄰兄弟結點中的第一個元素(或者最後一個元素)上移到父節點中,後面的元素(或者前面的元素)前移(或者後移);注意含有k,l的結點以前依附在m的左邊,現在變為依附在j的右邊。這樣每個結點都滿足b樹結構性質。

BTree與B+Tree

從以上操作可看出:除根結點之外的結點(包括葉子結點)的關鍵字的個數n滿足:(ceil(m  / 2)-1)<= n <= m-1,即2<=n<=4。這也佐證了咱們之前的觀點。删除操作完。

在b_樹中關鍵字分布在整個b_樹,并且在上層結點中出現過的關鍵字不再出現在最底層的結點中。順序鍊中所有的關鍵字不能連接配接在一起。

一顆m階的b+樹和m階的b_樹的差異在于:

1.有n棵子樹的結點中含有n個關鍵字; (而b樹是n棵子樹有n-1個關鍵字)

2.所有的葉子結點中包含了全部關鍵字的資訊,及指向含有這些關鍵字記錄的指針,且葉子結點本身依關鍵字的大小自小而大的順序連結。(而b樹的葉子節點并沒有包括全部需要查找的資訊)

3.所有的非終端結點可以看成是索引部分,結點中僅含有其子樹根結點中最大(或最小)關鍵字。 (而b 樹的非終節點也包含需要查找的有效資訊)

BTree與B+Tree

1) b+-tree的磁盤讀寫代價更低

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

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

2) b+-tree的查詢效率更加穩定

由于非終結點并不是最終指向檔案内容的結點,而隻是葉子結點中關鍵字的索引。是以任何關鍵字的查找必須走一條從根結點到葉子結點的路。所有關鍵字查詢的路徑長度相同,導緻每一個資料的查詢效率相當。

原文連結:http://chqz1987.blog.163.com/blog/static/51438311201312013746271/

繼續閱讀