Chapter 18
|
Document last modified: |
Overview
Question 18.7
- What's the big deal about operations that are "one-pass without backing up"?
- The root is normally locked in memory. Why is that reasonable?
Cormen Implementation as C++
class Node_Rep;
typedef Node_Rep * Node;
class Node_Rep {
public:
Item key[2t - 1]; -- keys
Node c[ 2t ]; -- child nodes
Boolean leaf;
Integer n; -- number of keys
};
variable Node T;

Example

Question 18.8 For the B-tree T at right that contains the 26 lower-case letter keys, what is:
- T.key1
- T.c2.key3
- T.c1.n
- T.c1.c1.n
- T.c1.c1.key3
- The minimum degree of the tree is t=3.
- What is the height, h?
- What is n?
- Is that consistent with
h ≤ logt (n + 1)/2?
Searching
| (Node, Integer) B_Tree_Search (preserves Node x, preserves Item k) i ¬ 1 while i ≤ x.n and k > x.keyi do i ¬ i + 1 if i ≤
x.n and k = x.keyi then if x.leaf then |
Question
18.9
| (Node, Integer) B_Tree_Search (preserves Node x, preserves Item k) i ¬ 1 while i ≤ x.n and k > x.keyi do i ¬ i + 1 if i ≤
x.n and k = x.keyi then if x.leaf then |
Insertion
Assume the B-tree at right has t=2.

nodes each node having t-1 keys. Example: Insert I into above, because JKL is full, requires moving key (e.g. median K) to parent and splitting JL.
Question 18.12 Why move K to parent?

Question 18.13
Example of Insertion with Splitting
We will apply the stated rules above first and examine the insertion algorithms later.
For our sanity, let t = 2. A node is then full if it has 2(2)-1 = 3 keys in it, and each node can have up to 4 children.
Insert: 5 9 3 7 1 2 8 6 0 4:
| Step 1: Insert 5
Step 2: Insert 9 Root node not full.
Step 3: Insert 3 Root node not full.
|
Step 4: Insert 7 Root node is full, do aggressive node splitting. Allocate a new (empty) node; make it the root, split pulling median key 5 into the new root:
Then insert 7; with 9
|
Step 5: Insert 1
with 3![]() Step 6: Insert 2
with 3
|
Step 8: Insert 6 It would go in with 7 8 9, but that node is full. So split it, bringing its median child 8 into the root: ![]() Then insert 6 with 7:
|
| Step 9: Insert 0 0 would go in with 1 2 3, which is full, so split it, sending the median child 2 up to the root: ![]() Now put 0 in with 1
|
Step 10: Insert 4 It would be nice to just stick 4 in with 3, but the B-Tree algorithm requires splitting the full root (or any full node encountered - Aggressive Node Splitting). Note that, if we don't and one of the leaves becomes full, there would be nowhere to put the median key of that split since the root would be full, thus, this split of the root is necessary to prevent backing the median key up more than one level:
Now assured there is room for the median key in case of a
lower-level split, we can insert 4, : |
|
Question 18.14
|
Splitting a Full Node
- Splitting a Full Node:
- If the leaf is already full (with 2t - 1 keys) then it must be split into two nodes each node having t - 1 keys.
- The median value of the original full node moves up to the parent node to become the value that divides the two new child nodes.
- When the median value is moved up to the parent, the parent node might also be full, therefore causing it to split.
- When this node splitting happens all the way back up to the root causing the root to split, the B-Tree grows in height by 1 level.
- B-Trees height therefore grow (and shrink) at the root.
- Aggressive Node Splitting
- Full nodes are split while traveling down the tree to the insertion point.
- This is done in order to avoid this possible propagation of values back up the tree (because of splitting).
- So, when the leaf is reached where key k must be inserted, then if the leaf must be split, it is guaranteed that the parent will not also be full.
Figure 18.5 for example of node splitting when t=4.
Figure 18.6 example of root node splitting when t=4
Question 18.15 What is the key difference between splitting the root and any other node?
t = 2, i = 2
| void B_Tree_Split_Child (Node x, Integer i) -- pre: x.n < (2t - 1) -- i index where parent should receive median
key from y |
Question 18.16 t=2
- Explain why t=index of median key in node to split?
- Why the Disk_Writes?
- Perform split necessary to insert 6.
- Perform the additional split necessary to insert 4.
Inserting a key in a single pass down the tree
Figure 18.7 Inserting keys
t = 3, the minimum degree, maximum of 5 keys
Question 18.17
- Explain what occurs at (c), (d) and (e).
- How many Disk_Writes are required in (b) and (d)?
| t=2 B_Tree_Insert( T, 0)
|
![]() |
| void B_Tree_Insert (Tree T, Item k) r ¬ T.root if r.n = (2t - 1) then -- root is full, create new root, s ¬ Allocate_Node () -- split old root into 2 leaves T.root ¬ s s.leaf ¬ FALSE s.n ¬ 0 s.c1 ¬ r B_Tree_Split_Child (s, 1) B_Tree_Insert_Nonfull (s, k) else B_Tree_Insert_Nonfull (r, k) |
B_Tree_Insert(T, 7)
|
| void B_Tree_Insert_Nonfull (Node x, Item k) i ¬
x.n while i ³ 1
and k <
x.keyi do -- search for k's insertion point, |
![]() B_Tree_InsertNonfull(T, 2)
|
|
else
i ¬ i + 1 if x.ci.n =
(2t - 1) then
-- if full child node if k > x.keyi
then
-- x.keyi holds median key from child x.ci B_Tree_Insert_Nonfull (x.ci, k) -- assured that child i is not full, insert k |
B_Tree_InsertNonfull(T, 6)![]()
|
Question 18.18
- When are Disk_Reads performed?
- When are Disk_Writes performed?
- Where is Aggressive Node Splitting performed?
- What is the best-case? the worst-case?
Deletion
Example
Figure 18.8 Deleting keys
t = 3, the minimum degree
maximum of 5 keys
minimum 2 keys
|
Case 1: If key k is in node x and x is a leaf, delete the key k from x. Assumes more than t-1 keys in leaf x. k=F x = DEF Question 18.19a - Draw the tree after deleting K.
Case 2a: If the key k is an internal node x and the child y that precedes k in node x has at least t keys then find the predecessor k' of k in the subtree rooted at y.
k=M x=CGM y=JKL k'=L predecessor of M
Question 18.19 Draw the tree (a) after deleting G.
Case 2c: If both y and z have only t-1 keys, merge k and all of z into y, so x loses both k and the pointer to z, and y now contains 2t-1 keys.
k=G x=CGL y=DE z=JK Question 18.20a - Draw the tree (c) after deleting C.
Case 2b: Use symmetry of predecessor of 2a with successor. If the key k is an internal node x and the child y that follows k in node x has at least t keys then find the successor k' of k in the subtree rooted at y.
Question 18.20b - Draw the tree after deleting G.
|
|
Case 3: If the key k is not present in internal node x, determine root x.ci of the appropriate child subtree that must contain k, if k is in the tree at all.
Question 18.21 Why is it necessary to only descend into nodes that have at least t keys? Consider Cases 1, 2a and 2c. Case 3b: Merge.
Recursion cannot descend into CL with only t-1 keys. CL's immediate siblings, TX, also has t-1 keys.
Case 1: If key k is in node x and x is a leaf delete the key k from x. k=D x=DEJK
Question 18.22
Case 3a: Borrow.
k=B x=CLPTX CLPTX.c1=AB
Move key C (not the child pointers) into AB to give ABC, now has t keys, delete B. Move key E from EJK (with t keys) to x giving ELPTX. Note that child pointer
ELPTX.c1 remains to AC.
Move
child pointer of EJK to AC. The old pointer from the left of E is now the
pointer from the right of C.
Question 18.23
|
Analysis
Question 18.24 What does the t describe?
Delete Implementation
The text does not provide an algorithm for deletion, below is one presented to provide some insight to delete operation.
| void B_Tree_Delete ( Node root, Item k ) -- pre: root node of B-tree -- post: if k in B-tree, remove corresponding node remove( root, k ) if root ¹ NIL and
root.n = 0 then |
| void remove (Node x, Item k) -- pre: x points to root node of subtree -- post: if k in subtree, remove corresponding node from subtree i ¬ x.n if i ³ 1 and k ¹
x.keyi then -- not found, check
child if not x.leaf and x.ci.n
< t - 1 then Disk_Write (x) |
| void copy_predecessor (Node x, int i) -- pre: x points to non-leaf node with entry at i -- post: if k in subtree, remove corresponding node from subtree leaf ¬ x.ci -- x.ci is node to the left of x while leaf.cleaf.n ¹ NIL do -- Go as far right to a leaf x.keyi
¬ leaf.keyleaf.n-1 -- Copy predecessor key |
| void restore (Node x, int i) -- pre: x points to non-leaf node with entry at x.ci has one too few entries -- post: An entry is taken from elsewhere to restore minimum number -- of entries in the node to which x.ci points if
i = x.n then
-- rightmost child |
| void move_left (Node x, int i) -- pre: x points to node with more than minimum number of entries in child i -- and one too few in child i-1 -- post: leftmost entry of child i is moved to x, which has sent an entry to child i-1
left ¬ x.ci-1 left.keyleft.n ¬ x.keyi-1 -- take parent entry left.n ¬ left.n + 1 left.cleft.n ¬ right.c1 left.keyi-1 ¬ right.key1 -- add right to parent right.n ¬ right.n - 1 for j ¬ 1 to
right.n-1 do --
move right entries to fill hole right.cright.n ¬ right.cright.n+1 Disk_Write( left ) |
| void move_right (Node x, int i) -- pre: x points to node with more than minimum number of entries in child i -- and one too few in child i+1 -- post: rightmost entry of child i is moved to x, which has sent an entry to child i+1
left ¬ x.ci right.cright.n+1 ¬ right.cright.n for j ¬ right.n
downto 1 do
-- make room for new entry right.n ¬ right.n + 1 right.c1 ¬ left.cleft.n left.n ¬ left.n - 1 x.keyi ¬ left.keyleft.n Disk_Write( x ) |
| void combine (Node x, int i) -- pre: x points to a node with child i and i-1 with too few entries to move -- post: nodes at i and i-1 have been combined into one node left ¬
x.ci-1 left.keyleft.n ¬ x.keyi-1 left.n ¬ left.n + 1 left.cleft.n ¬ right.c1 for j ¬ 1 to
right.n-1 do x.n ¬ x.n - 1 for j ¬ i-1 to
x.n-1 do right = NIL Disk_Write( x ) |