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
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)
1 n[y] ← 2t -1
2 for j ← t +1 to 2t -1
3 do keyj[y] ← keyj-t[z]
4 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)
1 r ← root[T]
2 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)
1 i ← 1
2 if leaf[x] ▹ Cases 1
3 then while i <= n[x] and k > keyi[x]
4 do i ← i + 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 i ← i + 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
2 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子過程的正確性。