The assignment
consists of two parts:
(1) adding delete to your implementation of B-Tree operations (14 points),
and
(2) Amortized analysis exercises (6 points).
Part 1: Implementation of B-Tree Deletion
For the deletion operation, I followed the strategy of the text,
using a non-recursive top-level routine
B-Tree-Delete()
that called a recursive routine
B-Tree-Delete-Fullenuf() ( an abbreviation for
B-Tree-Delete-Fullenough() ).
This is a common strategy for dealing with recursive routines -- the
top-level routine takes care of special initial boundary cases.
Also, in general, I found that several routines could be designed by
"reading" a related routine backward. This was somewhat true of
B-Tree-Delete(), which I checked by reading
B-Tree-Insert() backward; here is the pseudocode:
B-Tree-Delete(T,k) r = T.root if r == NIL or r.n == 0 print "Error: "Attempt to delete from empty tree." else B-Tree-Delete-Fullenuf(r,k) if r.n == 0 if r.leaf T.root = NIL else T.root = r.c_1 deallocate r
For B-Tree-Delete-Fullenuf(), I followed the pseudocode given in the lab session. But note a couple of subtleties. First, in case 3b, there are two subcases: when x.c_{i} is merged with x.c_{i-1} or with x.c_{i+1}. These two cases can be handled by just decrementing i if needed and then calling B-Tree-Merge-Children() as follows:
if i == x.n + 1 // Then we need to merge from left, not from right i = i - 1 B-Tree-Merge-Children( x, i )(rather than having two separate calls to B-Tree-Merge-Children()).
k' = B-Tree-Minimum( x.c_(i+1) )and similarly, the predecessor of k is the right-most key in the subtree to its left. Here is pseudocode for B-Tree-Minimum()
B-Tree-Minimum(x) while not x.leaf x = x.c_1 return x.key_1
Overall strategy for B-Tree-Delete-Fullenuf()
My strategy to implement B-Tree-Delete-Fullenuf()
was to implement and test case 1 (and "case 0") first, then
the "find the predecessor/successor" cases 2a and 2b,
then the "merge" of case 2c (also used in case 3b), then
the "borrow-left" and
"borrow-right" subcases of case 3a, and finally the "merge" case 3b.
As I have mentioned, the "borrow-left" operation is like the "rotate" operation in red-black trees (I think that the split-child and merge-children operations could be mostly performed by a sequence of "borrow" operations also, so in that sense split and merge are kinds of "super-rotates"). The pseudocode for B-Tree-Borrow-Left() is below; the steps in B-Tree-Borrow-Right() are similar.
B-Tree-Borrow-Left( x, i ) y = x.c_(i-1) // rename left child to simplify pseudocode z = x.c_i // rename child to simplify pseudocode shift all keys (and child pointers if not z.leaf) up one index // in z to make room for one more key & child pointer, and increment z.n z.key_1 = x.key_(i-1) // "rotate" keys key_(i-1) = y.key_(y.n) // z.c_1 = y.c_(y.n+1) // and move a child pointer into z decrement y.n
Implementation of B-Tree-Merge-Children()
The B-Tree-Merge-Children() operation is similar to
B-Tree-Split-Child().
As in B-Tree-Split-Child(), to simplify the pseudocode,
we let y = x.c_{i}, and
z = x.c_{i+1}, where it is assumed that both
y and z have t-1 keys.
Here is the pseudocode:
B-Tree-Merge-Children( x, i ) y = x.c_i z = x.c_(i+1) copy the t-1 keys of z into the top t-1 key positions of y and if not y.leaf copy the t child pointers of z into the top t child pointer positions of y y.key_t = x.key_i // move the splitting key down to y y.n = 2t - 1 // y is now a full node shift the keys from index i+1 to x.n and the child pointers from index i+2 to x.n+1 down one index in node x, and decrement x.n deallocate z
Hints: In the previous assignment, near the end, there is some Helpful code, and some useful pseudocode.
Testing:
Run your implementation on the test files:
treetest3,
treetest4,
treetest5, and
treetest6.
Note that treetest6 for this assignment was generated
using random numbers, so
there are a number of duplicate keys, both for insert and delete.
The implementation discussed above and in the text can handle duplicates,
so just
insert any duplicate keys. That way when you delete any duplicate keys, each
will be deleted and you won't generate any "search key not found" messages.
The result of treetest6,
should have the keys 133 and 357 in its root.
What To Hand In: