1. 程式人生 > >B樹的插入、刪除操作

B樹的插入、刪除操作

               上面第2小節學習簡單介紹了利用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(葉子結點也必須滿足此條關於關鍵字數的性質,根結點除外)。

      那好!下面咱們以一棵5階(即樹中任一結點至多含有4個關鍵字,5棵子樹)B樹例項進行講解(如下圖所示):

備註:

1.    關鍵字數(2-4個)針對--非根結點(包括葉子結點在內),孩子數(3-5個)--針對根結點和葉子結點之外的內結點。當然,根結點是必須至少有2個孩子的,不然就成直線型搜尋樹了。

2.    曾在一次面試中被問到,一棵含有N個總關鍵字數的m階的B樹的最大高度是多少?答曰:log_ceilm/2(上面中關於mB樹的第1點特性已經提到:樹中每個結點含有最多含有m個孩子,即m滿足:ceil(m/2)<=m<=m。而樹中每個結點含孩子數越少,樹的高度則越大,故如此)。在2012微軟4月份的筆試中也問到了此問題。更多原理請看上文第3小節末:B樹的高度。

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

結點定義如下:

typedef struct{ 
int Count; // 當前節點中關鍵元素數目 
ItemType Key[4]; // 儲存關鍵字元素的陣列 
long Branch[5]; // 偽指標陣列,(記錄數目)方便判斷合併和分裂的情況 
} NodeType;

如圖:



 

一.插入(insert)操作

    插入一個元素時,首先在B樹中是否存在,如果不存在,即在葉子結點處結束,然後在葉子結點中插入該新的元素,注意:如果葉子結點空間足夠,這裡需要向右移動該葉子結點中大於新插入關鍵字的元素,如果空間滿了以致沒有足夠的空間去新增新的元素,則將該結點進行“分裂”,將一半數量的關鍵字元素分裂到新的其相鄰右結點中,中間關鍵字元素上移到父結點中(當然,如果父結點空間滿了,也同樣需要“分裂”操作),而且當結點中關鍵元素向右移動了,相關的指標也需要向右移。如果在根結點插入新元素,空間滿了,則進行分裂操作,這樣原來的根結點中的中間關鍵字元素向上移動到新的根結點中,因此導致樹的高度增加一層。如下圖所示:

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


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

 

 

3、當咱們插入E,K,Q時,不需要任何分裂操作。 如圖:

 


 
 

4、插入M需要一次分裂,注意M恰好是中間關鍵字元素,以致向上移到父節點中。如圖:

 

 

 5、插入F,W,L,T不需要任何分裂操作。如圖:

 

 

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



 

       7、插入D時,導致最左邊的葉子結點被分裂,D恰好也是中間元素,上移到父節點中,然後字母P,R,X,Y陸續插入不需要任何分裂操作(別忘了,樹中至多5個孩子)。如圖:


 

       8、最後,當插入S時,含有N,P,Q,R的結點需要分裂,把中間元素Q上移到父節點中,但是情況來了,父節點中空間已經滿了,所以也要進行分裂,將父節點中的中間元素M上移到新形成的根結點中,注意以前在父節點中的第三個指標在修改後包括D和G節點中。這樣具體插入操作的完成,下面介紹刪除操作,刪除操作相對於插入操作要考慮的情況多點。如圖:

二. 刪除(delete)操作


1)刪除操作的兩個步驟
     第一步驟:在樹中查詢被刪關鍵字K所在的地點
     第二步驟:進行刪去K的操作

2)刪去K的操作
     B-樹是二叉排序樹的推廣,中序遍歷B-樹同樣可得到關鍵字的有序序列(具體遍歷演算法【參見練習】)。任一關鍵字K的中序前趨(後繼)必是K的左子樹(右子樹)中最右(左)下的結點中最後(最前)一個關鍵字。


     若被刪關鍵字K所在的結點非樹葉,則用K的中序前趨(或後繼)K'取代K,然後從葉子中刪去K'。從葉子*x開始刪去某關鍵字K的三種情形為:


     情形一:若x->keynum>Min,則只需刪去K及其右指標(*x是葉子,K的右指標為空)即可使刪除操作結束。

注意: Min=【M/2】-1


     情形二:若x->keynum=Min,該葉子中的關鍵字個數已是最小值,刪K及其右指標後會破壞B-樹的性質(3)。若*x的左(或右)鄰兄弟結點*y中的關鍵字數目大於Min,則將*y中的最大(或最小)關鍵字上移至雙親結點*parent中,而將*parent中相應的關鍵字下移至x中。顯然這種移動使得雙親中關鍵字數目不變;*y被移出一個關鍵字,故其keynum減1,因它原大於Min,故減少1個關鍵字後keynum仍大於等於Min;而*x中已移入一個關鍵字,故刪K後*x中仍有Min個關鍵字。涉及移動關鍵字的三個結點均滿足B-樹的性質(3)。 請讀者驗證,上述操作後仍滿足B-樹的性質(1)。移動完成後,刪除過程亦結束。


     情形三:若*x及其相鄰的左右兄弟(也可能只有一個兄弟)中的關鍵字數目均為最小值Min,則上述的移動操作就不奏效,此時須*x和左或右兄弟合併。不妨設*x有右鄰兄弟*y(對左鄰兄弟的討論與此類似),在*x中刪去K後,將雙親結點*parent中介於*x和*y之間的關鍵字K,作為中間關鍵字,與並x和*y中的關鍵字一起"合併"為一個新的結點取代*x和*y。因為*x和*y原各有Min個關鍵字,從雙親中移人的K'抵消了從*x中刪除的K,故新結點中恰有2Min(即2「m/2」-2≤m-1)個關鍵字,沒有破壞B-樹的性質(3)。但由於K'從雙親中移到新結點後,相當於從*parent中刪去了K',若parent->keynum原大於Min,則刪除操作到此結束;否則,同樣要通過移動*parent的左右兄弟中的關鍵字或將*parent與其 左右兄弟合併的方法來維護B-樹性質。最壞情況下,合併操作會向上傳播至根,當根中只有一個關鍵字時,合併操作將會使根結點及其兩個孩子合併成一個新的根,從而使整棵樹的高度減少一層。如圖: 
     

 
     
  分析:
     第1個被刪的關鍵字h是在葉子中,且該葉子的keynum>Min(5階B-樹的Min=2),故直接刪去即可。第2個刪去的r不在葉子中,故用中序後繼s取代r,即把s複製到r的位置上,然後從葉子中刪去s。第3個刪去的p所在的葉子中的關鍵字數目是最小值Min,但其右兄弟的keynum>Min,故可以通過左移,將雙親中的s移到p所在的結點,而將右兄弟中最小(即最左邊)的關鍵字t上移至雙親取代s。當刪去d時,d所在的結點及其左右兄弟均無多餘的關鍵字,故需將刪去d後的結點與這兩個兄弟中的一個(圖中是選擇左兄弟(ab))及其雙親中分隔這兩個被合併結點的關鍵字c合併在一起形成一個新結點(abce)。但因為雙親中失去c後keynum<Min,故必須對該結點做調整操作,此時它只有一個右兄弟,且右兄弟無多餘的關鍵字,不可能通過移動關鍵字來解決。因此引起再次合併,因根只有一個關鍵字,故合併後樹高度減少一層,從而得到上圖的最後一個圖。