【算法與數據結構】B-樹學習筆記

  B-tree(多路搜索樹,並非二叉的)是一種常見的數據結構。使用B-tree結構能夠顯著減小定位記錄時所經歷的中間過程,從而加快存取速度。按照翻譯,B 一般認爲是Balance的簡稱.這個數據結構通常用於數據庫的索引,綜合效率較高。     node

         B-tree中,每一個結點包含:算法

  一、本結點所含關鍵字的個數;數據庫

  二、指向父結點的指針;數組

  三、關鍵字;數據結構

  四、指向子結點的指針;ide

      B-tree是一種多路搜索樹(並非二叉的),對於一棵M階樹:函數

       1.定義任意非葉子結點最多隻有M個孩子;且M>2性能

       2.根結點的孩子數爲[2, M],除非根結點爲葉子節點spa

       3.除根結點之外的非葉子結點的兒子數爲[M/2, M]翻譯

       4.非葉子結點的關鍵字個數=指向兒子的指針個數-1;

       5.  與3相應,每一個非葉子結點存放至少M/2-1(取上整)和至多M-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]指向關鍵字大於K[M-1]的子樹,其它P[i]指向關鍵字屬於(K[i-1], K[i])的子樹;

       8.全部葉子結點位於同一層;

    例如:(M=3

      B-tree有如下特性:

  一、關鍵字集合分佈在整棵樹中;

  二、任何一個關鍵字出現且只出如今一個結點中;

  三、搜索有可能在非葉子結點結束;

  四、其搜索性能等價於在關鍵字全集內作一次二分查找;

  五、自動層次控制;

因此B-樹的性能老是等價於二分查找(與M值無關),也就沒有B樹平衡的問題;

  因爲M/2的限制,在插入結點時,若是結點已滿,須要將結點分裂爲兩個各佔M/2的結點;刪除結點時,需將兩個不足M/2的兄弟結點合併。

    鑑於B-tree具備良好的定位特性,其常被用於對檢索時間要求苛刻的場合,例如:

  一、B-tree索引是數據庫中存取和查找文件(稱爲記錄或鍵值)的一種方法。

  二、硬盤中的結點也是B-tree結構的。與內存相比,硬盤必須花成倍的時間來存取一個數據元素,這是由於硬盤的機械部件讀寫數據的速度遠遠趕不上純電子媒體的內存。與一個結點兩個分支的二元樹相比,B-tree利用多個分支(稱爲子樹)的結點,減小獲取記錄時所經歷的結點數,從而達到節省存取時間的目的。

    

      在大多數系統中,B-樹上的算法執行時間主要由讀、寫磁盤的次數來決定,每次讀寫儘量多的信息可提升算法得執行速度。
     B-樹中的結點的規模通常是一個磁盤頁,而結點中所包含的關鍵字及其孩子的數目取決於磁盤頁的大小。
  注意:
     ①對於磁盤上一棵較大的B-樹,一般每一個結點擁有的孩子數目(即結點的度數)m爲50至2000不等
     ②一棵度爲m的B-樹稱爲m階B-樹。
     ③選取較大的結點度數可下降樹的高度,以及減小查找任意關鍵字所需的磁盤訪問次數。
       下圖給出了一棵高度爲3的1001階B-樹。

     

      說明:
  ①每一個結點包含1000個關鍵字,故在第三層上有100多萬個葉結點,這些葉節點可容納10億多個關鍵字。
  ②圖中各結點內的數字表示關鍵字的數目。
  ③一般根結點可始終置於主存中,所以在這棵B-樹中查找任一關鍵字至多隻需二次訪問外存。
      B-樹的存儲結構
#define Max l000 //結點中關鍵字的最大數目:Max=m-1,m是B-樹的階
#define Min 500 //非根結點中關鍵字的最小數目:Min=┌m/2┐-1
typedef int KeyType; //KeyType應由用戶定義
typedef struct node{ //結點定義中省略了指向關鍵字表明的記錄的指針
   int keynum; //結點中當前擁有的關鍵字的個數,keynum《Max
   KeyType key[Max+1]; //關鍵字向量爲key[1..keynum],key[0]不用。
   struct node *parent; //指向雙親結點
   struct node *son[Max+1];//孩子指針向量爲son[0..keynum]
 }BTreeNode;
typedef BTreeNode *BTree;
 注意:
     爲簡單起見,以上說明省略了輔助信息域。在實用中,與每一個關鍵字存儲在一塊兒的不是相關的輔助信息域,而是一個指向另外一磁盤頁的指針。磁盤頁中包含有該關鍵字所表明的記錄,而相關的輔助信息正是存儲在此記錄中。
     有的B-樹(如第10章介紹的B+樹)是將全部輔助信息都存於葉結點中,而內部結點(不妨將根亦看做是內部結點)中只存放關鍵字和指向孩子結點的指針,無須存儲指向輔助信息的指針,這樣使內部結點的度數儘量最大化。

      

B-樹上的基本運算

一、B-樹的查找

(1)B-樹的查找方法
    在B-樹中查找給定關鍵字的方法相似於二叉排序樹上的查找。不一樣的是在每一個結點上肯定向下查找的路徑不必定是二路而是keynum+1路的。
  對結點內的存放有序關鍵字序列的向量key[l..keynum] 用順序查找或折半查找方法查找。若在某結點內找到待查的關鍵字K,則返回該結點的地址及K在key[1..keynum]中的位置;不然,肯定K在某個key[i]和key[i+1]之間結點後,從磁盤中讀son[i]所指的結點繼續查找……。直到在某結點中查找成功;或直至找到葉結點且葉結點中的查找仍不成功時,查找過程失敗。
【例】下圖中左邊的虛線表示查找關鍵字1的過程,它失敗於葉結點的H和K之間空指針上;右邊的虛線表示查找關鍵字S的過程,併成功地返回S所在結點的地址和S在key[1..keynum]中的位置2。

   

(2)B-樹的查找算法
BTreeNode *SearchBTree(BTree T,KeyType K,int *pos)
 { //在B-樹T中查找關鍵字K,成功時返回找到的結點的地址及K在其中的位置*pos
//失敗則返回NULL,且*pos無定義
  int i;
  T→key[0]=k; //設哨兵.下面用順序查找key[1..keynum]
  for(i=T->keynum;K<t->key[i];i--); //從後向前找第1個小於等於K的關鍵字
  if(i>0 && T->key[i]==1){ //查找成功,返回T及i
    *pos=i;
    return T;
   } //結點內查找失敗,但T->key[i]<K<T->key[i+1],下一個查找的結點應爲
     //son[i]
  if(!T->son[i]) //*T爲葉子,在葉子中仍未找到K,則整個查找過程失敗
    return NULL;
    //查找插入關鍵字的位置,則應令*pos=i,並返回T,見後面的插入操做
  DiskRead(T->son[i]); //在磁盤上讀人下一查找的樹結點到內存中
  return SearchBTree(T->Son[i],k,pos); //遞歸地繼續查找於樹T->son[i]
 }

(3)查找操做的時間開銷
     B-樹上的查找有兩個基本步驟:
 ①在B-樹中查找結點,該查找涉及讀盤DiskRead操做,屬外查找;
 ②在結點內查找,該查找屬內查找。
     查找操做的時間爲:
 ①外查找的讀盤次數不超過樹高h,故其時間是O(h);
 ②內查找中,每一個結點內的關鍵字數目keynum<m(m是B-樹的階數),故其時間爲O(nh)。
 注意:
 ①實際上外查找時間可能遠遠大於內查找時間。
 ②B-樹做爲數據庫文件時,打開文件以後就必須將根結點讀人內存,而直至文件關閉以前,此根一直駐留在內存中,故查找時能夠不計讀入根結點的時間。

B樹的插入、刪除操做

      下面我們經過另一個實例來對這棵B樹的插入(insert),刪除(delete)基本操做進行詳細的介紹。

但在此以前,我們還得簡單回顧下一棵m階的B 樹 (m叉樹)的特性,以下:

  1. 樹中每一個結點含有最多含有m個孩子,即m知足:ceil(m/2)<=m<=m。

  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,但都大於K(i-1)。 
           c)   除根結點以外的結點的關鍵字的個數n必須知足: [ceil(m / 2)-1]<= n <= m-1(葉子結點也必須知足此條關於關鍵字數的性質,根結點除外)。

       ok,下面我們以一棵5階(m=5,即除根結點和葉子結點以外的內結點最多5個孩子,最少3個孩子)B樹實例進行講解(以下圖所示):

備註:關鍵字數(2-4個)針對--非根結點(包括葉子結點在內),孩子數(3-5個)--針對根結點和葉子結點以外的內結點。固然,根結點是必須至少有2個孩子的,否則就成直線型搜索樹了。

下圖中關鍵字爲大寫字母,順序爲字母升序。

結點定義以下:

typedef struct{

   int Count;         // 當前節點中關鍵元素數目

   ItemType Key[4];   // 存儲關鍵字元素的數組

   long Branch[5];    // 僞指針數組,(記錄數目)方便判斷合併和分裂的狀況

} NodeType;

 

插入(insert)操做

插入一個元素時,首先在B樹中是否存在,若是不存在,即在葉子結點處結束,而後在葉子結點中插入該新的元素,注意:若是葉子結點空間足夠,這裏須要向右移動該葉子結點中大於新插入關鍵字的元素,若是空間滿了以至沒有足夠的空間去添加新的元素,則將該結點進行「分裂」,將一半數量的關鍵字元素分裂到新的其相鄰右結點中,中間關鍵字元素上移到父結點中(固然,若是父結點空間滿了,也一樣須要「分裂」操做),並且當結點中關鍵元素向右移動了,相關的指針也須要向右移。若是在根結點插入新元素,空間滿了,則進行分裂操做,這樣原來的根結點中的中間關鍵字元素向上移動到新的根結點中,所以致使樹的高度增長一層。

一、我們經過一個實例來逐步講解下。插入如下字符字母到一棵空的B 樹中(非根結點關鍵字數小了(小於2個)就合併,大了(超過4個)就分裂):C N G A H E K Q M F W L T Z D P R X Y S,首先,結點空間足夠,4個字母插入相同的結點中,以下圖:

 

二、當我們試着插入H時,結點發現空間不夠,以至將其分裂成2個結點,移動中間元素G上移到新的根結點中,在實現過程當中,我們把A和C留在當前結點中,而H和N放置新的其右鄰居結點中。以下圖:

 

三、當我們插入E,K,Q時,不須要任何分裂操做

 

 

 

 

 

 

 

 

 

 

 

 

四、插入M須要一次分裂,注意M剛好是中間關鍵字元素,以至向上移到父節點中

 

五、插入F,W,L,T不須要任何分裂操做

 

六、插入Z時,最右的葉子結點空間滿了,須要進行分裂操做,中間元素T上移到父節點中,注意經過上移中間元素,樹最終仍是保持平衡,分裂結果的結點存在2個關鍵字元素。

 

七、插入D時,致使最左邊的葉子結點被分裂,D剛好也是中間元素,上移到父節點中,而後字母P,R,X,Y陸續插入不須要任何分裂操做(別忘了,樹中至多5個孩子)。

 

八、最後,當插入S時,含有N,P,Q,R的結點須要分裂,把中間元素Q上移到父節點中,可是狀況來了,父節點中空間已經滿了,因此也要進行分裂,將父節點中的中間元素M上移到新造成的根結點中,注意之前在父節點中的第三個指針在修改後包括D和G節點中。這樣具體插入操做的完成,下面介紹刪除操做,刪除操做相對於插入操做要考慮的狀況多點。

 

 

刪除(delete)操做

首先查找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。

一、首先刪除元素H,固然首先查找H,H在一個葉子結點中,且該葉子結點元素數目3大於最小元素數目ceil(m/2)-1=2,則操做很簡單,我們只須要移動K至原來H的位置,移動L至K的位置(也就是結點中刪除元素後面的元素向前移動)

 

二、下一步,刪除T,由於T沒有在葉子結點中,而是在中間結點中找到,我們發現他的繼承者W(字母升序的下個元素),將W上移到T的位置,而後將原包含W的孩子結點中的W進行刪除,這裏剛好刪除W後,該孩子結點中元素個數大於2,無需進行合併操做。

 

三、下一步刪除R,R在葉子結點中,可是該結點中元素數目爲2,刪除致使只有1個元素,已經小於最小元素數目ceil(5/2)-1=2,而由前面咱們已經知道:若是其某個相鄰兄弟結點中比較豐滿(元素個數大於ceil(5/2)-1=2),則能夠向父結點借一個元素,而後將最豐滿的相鄰兄弟結點中上移最後或最前一個元素到父節點中(有沒有看到紅黑樹中左旋操做的影子?),在這個實例中,右相鄰兄弟結點中比較豐滿(3個元素大於2),因此先向父節點借一個元素W下移到該葉子結點中,代替原來S的位置,S前移;而後X在相鄰右兄弟結點中上移到父結點中,最後在相鄰右兄弟結點中刪除X,後面元素前移。

 

四、最後一步刪除E, 刪除後會致使不少問題,由於E所在的結點數目恰好達標,恰好知足最小元素個數(ceil(5/2)-1=2),而相鄰的兄弟結點也是一樣的狀況,刪除一個元素都不能知足條件,因此須要該節點與某相鄰兄弟結點進行合併操做;首先移動父結點中的元素(該元素在兩個須要合併的兩個結點元素之間)下移到其子結點中,而後將這兩個結點進行合併成一個結點。因此在該實例中,我們首先將父節點中的元素D下移到已經刪除E而只有F的結點中,而後將含有D和F的結點和含有A,C的相鄰兄弟結點進行合併成一個結點。

 

五、也許你認爲這樣刪除操做已經結束了,其實否則,在看看上圖,對於這種特殊狀況,你當即會發現父節點只包含一個元素G,沒達標(由於非根節點包括葉子結點的關鍵字數n必須知足於2=<n<=4,而此處的n=1),這是不可以接受的。若是這個問題結點的相鄰兄弟比較豐滿,則能夠向父結點借一個元素。假設這時右兄弟結點(含有Q,X)有一個以上的元素(Q右邊還有元素),而後我們將M下移到元素不多的子結點中,將Q上移到M的位置,這時,Q的左子樹將變成M的右子樹,也就是含有N,P結點被依附在M的右指針上。因此在這個實例中,我們沒有辦法去借一個元素,只能與兄弟結點進行合併成一個結點,而根結點中的惟一元素M下移到子結點,這樣,樹的高度減小一層。

 

爲了進一步詳細討論刪除的狀況,再舉另一個實例

這裏是一棵不一樣的5序B樹,那我們試着刪除C

 

因而將刪除元素C的右子結點中的D元素上移到C的位置,可是出現上移元素後,只有一個元素的結點的狀況。

又由於含有E的結點,其相鄰兄弟結點纔剛脫貧(最少元素個數爲2),不可能向父節點借元素,因此只能進行合併操做,因而這裏將含有A,B的左兄弟結點和含有E的結點進行合併成一個結點。

 

這樣又出現只含有一個元素F結點的狀況,這時,其相鄰的兄弟結點是豐滿的(元素個數爲3>最小元素個數2),這樣就能夠想父結點借元素了,把父結點中的J下移到該結點中,相應的若是結點中J後有元素則前移,而後相鄰兄弟結點中的第一個元素(或者最後一個元素)上移到父節點中,後面的元素(或者前面的元素)前移(或者後移);注意含有K,L的結點之前依附在M的左邊,如今變爲依附在J的右邊。這樣每一個結點都知足B樹結構性質。

 

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

相關文章
相關標籤/搜索