1. 程式人生 > >B樹學習----查詢 插入 刪除

B樹學習----查詢 插入 刪除

參考演算法導論第三版

1.B樹的定義

任何和關鍵字相聯絡的“衛星資料”將於關鍵字一樣存放在同一個節點中。

一棵B樹T是具有以下性質的有根樹(根為T.root):

1.每個節點x都有下面屬性:

a. x.n, 當前儲存在節點x中的關鍵字個數。

b. x.n, n個關鍵字本身x.key1, x.key2, ..., x.keyz, ..., x.keyx.n, 以非降序存放,是的x.key1 <= x.key2 <= ... <= x.keyx.n.

c. x.leaf, 一個bool值,如果x是葉節點,則為TRUE;如果x為內部節點,則為FALSE.

2.每個內部節點x還包含x.n+1個指向其孩子的指標x.c

1, x.c2, ... , x.cx.n+1。葉節點沒有孩子,所以它們的ci屬性沒有定義。

3.關鍵字x.keyi對儲存在各子樹種的關鍵範圍加以分割:如果ki為任意一個儲存在以x.ci為根的子樹中的關鍵字,那麼

    

4.每個葉節點具有相同的深度,即樹的高度h。

5.每個節點所包含的關鍵字個數有上界和下界。用一個被稱為最小度數(minmum degree)的固定整數 t>=2來表示這些界。

a.除了根節點以外的每個節點必須至少有t-1個關鍵字。因此,除了根節點以外的每個節點至少有t個孩子。如果樹非空,根節點至少有一個關鍵字。

b.每個節點至多可包含2t-1個關鍵字。因此,一個內部節點至多可有2t個孩子。當一個節點恰好有2t-1個關鍵字是,則稱該節點是滿的(full)。

t = 2時的B樹是最簡單的。每個內部節點有2個、3個或4個孩子,即一棵2-3-4樹。然而在實際中,t的值越大,B樹的高度就越小。

B樹的高度

B樹上大部分的操作所需的磁碟存取次數與B樹的高度是成正比的。現在來分析B樹最壞情況下的高度。

定理18.1 如果 n >= 1,那麼對任意一棵包含n個關鍵字、高度為h、最小讀書t>=2的B樹T來說,有

                                        

搜尋B樹

  

建立一棵空的B樹

     

向B樹種插入一個關鍵字

將一個滿的節點y(有2t-1個關鍵字)按其中間關鍵字(median key) y.keyt分裂成兩個各含t-1個關鍵字的節點。中間節點被提升到y的父節點,以標識兩棵樹的劃分點。但是如果y的父節點也是滿的,就必須在插入新的關鍵字之前將其分裂,最終滿節點的分裂會沿著樹向上傳播。

當沿著樹往下查詢新的關鍵字所屬位置時,就分裂沿途遇到的每個滿節點(包括葉節點本身)。因此,每當要分裂一個滿節點y是,就能確保它的父節點不是滿的。

分裂B樹中的節點

過程B-TREE-SPLIT-CHILD的輸入是一個非滿的內部節點x和一個使x.ci為x的滿子節點的下標i。該過程把這個子節點分裂成兩個,並調整x,使之包含更多的孩子。要分裂一個滿的根,首先要讓根成為一個新的空根節點的孩子,才能使用B-TREE-SPLIT-CHILD.樹的高度因此增加1,分裂是樹長高的唯一途徑。



以沿樹單程下行方式向B樹插入關鍵字

在一棵高度為h的B樹T中,以沿樹單程下行方式插入一個關鍵字k的操作需要O(h)次磁碟存取。所需要的CPU時間為O(th) = O(tlogn)。過程B-TREE-SPLIT-CHILD來保證地櫃始終不會降至一個滿節點上。



輔助的遞迴過程B-TREE-NONFULL將關鍵字插入節點x,要求假定在呼叫該過程時x是非滿的。操作B-TREE-INSERT和遞迴操作B-TREE-INSERT-NONFULL保證了這個假設成立。



從B樹中刪除關鍵字

B樹上的刪除操作與插入操作類似,只是略微複雜一下,因為可以從任意一個節點中刪除一個關鍵字,而不僅僅是葉節點,而且當從一個內部節點刪除一個關鍵字是,還要重新安排這個節點的孩子。與插入操作一樣,必須防止因刪除操作二導致樹的結構違反B樹性質。就像必須保證一個節點不會因為插入而變得太大一樣,必須保證一個節點不會在刪除期間變得太小(根節點除外)。

與插入情況相對稱,除了根結點外(根結點個數不能少於1),B樹的關鍵字數不能少於t-1個。對於簡單刪除情況,如果我們定位到關鍵字處在某個結點中,如果這個結點中關鍵字個數恰好是t-1個,如果直接刪除這個關鍵字,就會違反B樹規則。

此時,需要考慮兩種處理方案:

1)把這個結點與其相鄰結點合併,合併時需要把父結點的一個關鍵字加進來,除非相鄰的那個結點的關鍵字數也是t-1個,否則,合併後會超出2t-1的限制,同樣違反B樹規則。而且,因為從父結點拉下一個關鍵字,導致父結點的關鍵字數少1,如果原來父結點關鍵字數是t-1,那麼父結點違反B樹規則,這種情況下,必須進行回溯處理。(對於下圖(a)初始樹,刪除結點Z就會出現這種情況)

2)從相鄰結點借一個關鍵字過來,這種情況要求,相鄰結點必須有多於t-1個關鍵字,借的過程中,需要轉經父結點,否則違反B樹規則。

為了避免回溯,要求我們在從樹根向下搜尋關鍵字的過程中,凡是遇到途經的結點,如果該結點的關鍵字數是t-1,則我們需要想辦法從其他地方搞個關鍵字過來,使得該結點的關鍵字數至少為t。

搞,也是從相鄰結點搞,如果相鄰結點有的話,當然,也要經過父結點進行週轉。如果沒有,就說明相鄰結點的關鍵字個數也是t-1,這種情況,直接對該結點與其相鄰結點進行合併,以滿足要求。

B樹的結點的合併基於如下情況呼叫:內結點x的第i個子結點y和第i+1個子結點z的關鍵字數都是t-1,此時需要把內結點x的第i個關鍵字下移與y和z的合併,形成一個結點y。

B樹中結點的合併:

B-TREE-MERGE-CHILD(x, i, y,z)

n[y] ← 2t -1

2 for j ← t +1 to 2t -1

3   do keyj[y] ← keyj-t[z]

keyt[y] ← keyi[x]

5 if not leaf[y]

6  then for j ← t +1 to 2t -1

7        do cj[y] ← cj-t[z]

8 for j ← i +1 to n[x]

9  do cj[x] ← cj+1[x]

10 n[x] ← n[x] -1

11 FREE-NODE(z)

12 DISK-WRITE(y)

13 DISK-WRITE(z)

14 DISK-WRITE(x)

B樹的刪除:

B-TREE-DELETE(T,k)

rroot[T]
if n[r] = 1
 3    then DISK_READ(c1[r])
 4       DISK_READ(c2[r])
 5       y ←c1[r]
 6       z ←c2[r]
 7       if n[y] = n[z] = t-1                   ▹ Cases 2c or 3b
 8         then  B-TREE-MERGE-CHILD(r, 1, y, z) 
 9            root[T] ← y
 10           FREE-NODE(r)
 11           B-TREE-DELETE-NONONE(y, k)
12      else B-TREE-DELETE-NONONE (r, k)
13 else B-TREE-DELETE-NONONE (r, k)
考慮到根結點的特殊性,對根結點為1,並且兩個子結點都是t-1的情況進行了特殊的處理:
先對兩個子結點進行合併,然後把原來的根刪除,把樹根指向合併後的子結點y。
這樣B樹的高度就減少了1。這也是B樹高度唯一會減少的情況。 

除了這種情況以外,就直接呼叫子過程B-TREE-DELETE-NONONE (x, k)。

B-TREE-DELETE-NONONE (x, k)

i ← 1
if leaf[x]                                       ▹ Cases 1
 3     then while i <= n[x] and k > keyi[x]
 4            do ii + 1
 5               if k = keyi[x]
 6                 then for j ← i+1 to n[x]
 7                        do keyj-1[x] ←keyj[x]
 8                      n[x] ← n[x] - 1
 9                      DISK-WRITE(x)
 10              else error:”the key does not exist”
 11    else while i <= n[x] and k > keyi[x]
12           do ii + 1
 13              DISK-READ(ci[x])
 14              y ←ci[x]
 15              if i <= n[x]
 16                then DISK-READ(ci+1[x])
 17                     z ←ci+1[x]
 18              if k = keyi[x]                          ▹ Cases 2
19                then if n[y] > t-1                   ▹ Cases 2a
 20                       then k′←B-TREE-SEARCH-PREDECESSOR(y)
 21                            B-TREE-DELETE-NONONE (y, k′)
 22                            keyi[x] ←k
 23                     else if n[z] > t-1               ▹ Cases 2b
 24                       then k′←B-TREE-SEARCH-SUCCESSOR (z)
 25                            B-TREE-DELETE-NONONE (z, k′)
 26                            keyi[x] ←k
 27                     else B-TREE-MERGE-CHILD(x, i, y, z)▹ Cases 2c
 28                          B-TREE-DELETE-NONONE (y, k)
 29              else                                   ▹ Cases 3
 30                if i >1
 31                  then DISK-READ(ci-1[x])
 32                       p ←ci-1[x]
 33                if n[y] = t-1 
 34                  then if i>1 and n[p] >t-1               ▹ Cases 3a
 35                         then B-TREE-SHIFT-TO-RIGHT-CHILD(x,i,p,y)
 36                       else if i <= n[x] and n[z] > t-1    ▹ Cases 3a
 37                         then B-TREE-SHIFT-TO-LEFT-CHILD(x,i,y,z)
 38                       else if i>1                       ▹ Cases 3b
 39                         then B-TREE-MERGE-CHILD(x, i, p, y)  
 40                              y ← p
 41                       else B-TREE-MERGE-CHILD(x, i, y, z)▹ Cases 3b
 42                B-TREE-DELETE-NONONE (y, k)
查詢前驅
B-TREE-SEARCH-PREDECESSOR(y)
1  x ← y
i ← n[x]
3  while not leaf[x]
4    do DISK_READ(ci+1[x])
5       x ←ci+1[x]
6       i ← n[x]
7  return keyi[x]
查詢後繼
B-TREE-SEARCH-SUCCESSOR (z)
1  x ← z
2  while not leaf[x]
3    do DISK_READ(c1[x])
4       x ←c1[x]
5  return key1[x]
轉移到右邊的子結點
B-TREE-SHIFT-TO-RIGHT-CHILD(x,i,y,z)
1 n[z] ← n[z] +1
2 j ← n[z]
3 while j > 1
4   do keyj[z] ←keyj-1[z]
5      j ← j -1
6 key1[z] ←keyi[x]
7 keyi[x] ←keyn[y][y]
8 if not leaf[z]
9   then j ← n[z]
10       while j > 0
11         do cj+1[z] ←cj[z]
12            j ← j -1
13       c1[z] ←cn[y]+1[y]
14 n[y] ← n[y] -1

15 DISK-WRITE(y)

16 DISK-WRITE(z)

17 DISK-WRITE(x)

轉移到左邊的子結點
B-TREE-SHIFT-TO-LEFT-CHILD(x,i,y,z)
1 n[y] ← n[y] +1
2 keyn[y][y] ← keyi[x]
3 keyi[x] ←key1[z]
4 n[z] ← n[z] -1
5 j ← 1
6 while j <= n[z]
7   do keyj[z] ←keyj+1[z]
8      j ← j +1
9 if not leaf[z]
10  then cn[y]+1[y] ←c1[z]
11       j ← 1
12       while j <= n[z]+1
13         do cj[z] ←cj+1[z]
14            j ← j + 1

15 DISK-WRITE(y)

16 DISK-WRITE(z)

17 DISK-WRITE(x)

注意:每次遞迴呼叫前,程式都能保證包括關鍵字的子樹根的關鍵字數至少為t(除了根結點外),

這是B-TREE-DELETE-NONONE子過程能夠正確執行的關鍵,類似的,

可以用迴圈不變式證明B-TREE-DELETE-NONONE子過程的正確性。

B樹的刪除

B樹刪除續