18. B-Trees
Exercises
18.1-1
By property 5.a, such a tree could have "empty" nodes, which makes no sense.
18.1-2
Only for t∈{2,3} the lower and upper bounds are valid by property 5.
18.1-3
The allowed configurations are:
Tree 1 (Root [2]): Left child [1] (1 key), Right child [3, 4, 5] (3 keys).
Tree 2 (Root [3]): Left child [1, 2] (2 keys), Right child [4, 5] (2 keys).
Tree 3 (Root [4]): Left child [1, 2, 3] (3 keys), Right child [5] (1 key).
Tree 4 (Root [2, 4]): Left child [1] (1 key), Middle child [3] (1 key), Right child [5] (1 key).
18.1-4
We must assume that every single node in the tree is packed to its absolute maximum capacity. This gives
18.1-5
It is a 2-3-4 tree (see Exercise 13.1-4).
18.2-1
Use the Data Structure Visualizations tool and select the options Max. Degree 4 (corresponds to minimum degree 2) and Preemptive Split / Merge. Insert the keys in oder from the book and watch how the initially empty B-tree grows.
18.2-2
There is only one redundant Disk-Read in a scenario where the root node is split. Namely, after splitting it, the B-Tree-Insert procedure calls B-Tree-Insert-Nonfull with the new root. Since it is not a leaf node, the helper function blindly fetches one of the freshly split halves from the disk, even though it was just manipulated in memory a moment prior.
Because a Disk-Write is only ever issued immediately following a mutation of the specific node's data, the algorithm never writes a block to disk that is identical to what is already stored there.
18.2-3
According to Exercise 18.1-4, the minimum height would be 1. Notice that insertion can never reduce the height of a B-tree. The book uses the preemptive split strategy, which forces the split of the root as soon as it becomes full (contains 3 keys). To insert 15 keys into a tree we would need 4 full leaves as direct children of the root. Just prior to putting anything into the 4th child, the root will be split, since it would have 3 keys. Therefore, the height would increase to 2.
★ 18.2-4
We may expect between ⌊n/3⌋ and ⌈n/2⌉ nodes if the keys were inserted in a random order, distributing the keys evenly and keeping the nodes relatively full. However, sequential insertions completely break normal expectations.
Suppose we insert keys in a strictly increasing order. Consequently, each new key will be placed into the rightmost leaf. Let h be the height of the resulting B-tree. The nodes on the rightmost path (which there are h+1 of) will contain 2 or 3 keys, but the rest of the tree will strictly contain 1 key per node. Therefore, the number of nodes in the tree will be n−Θ(lgn)=O(n).
Follow Exercise 18.2-1 and insert the keys {1,…,15} in sequential order. The final B-tree will have 11 nodes most of them having a single key.
18.2-5
To implement this, let t be the minimum degree for internal nodes (as before) and t′ (where t′>t) be the minimum degree for leaf nodes. Since the definition of a full node now depends on its type, we first define a quick helper function to abstract the capacity limit:
The other procedures are depicted below (only changes are listed). We don't need to alter the tree creation code.
18.2-6
In this case, the while loop of lines 2–3 would take O(lgt) time within each node, and the total CPU time becomes O((lgt)h)=O((lgt)(logtn))=O((lgt)(lgn/lgt))=O(lgn).
18.2-7
Assume that the CPU time was optimized based on the previous exercise. The total disk read time T can be approximated by
To minimize T we need to minimize f(t), thus
This is the general equation we would use to numerically solve for the optimal t given any constants a and b. We can use WolframAlpha to solve this equation with concrete values for these constants. An optimal value of t for the case in which a=5 milliseconds and b=10 microseconds is t=129.
18.3-1
18.3-2
GeeksforGeeks has a nice article about the delete operation in B-tree that contains a full implementation in various programming languages. It completely follows the algorithm from the book. The only difference is that disk operations are not included; these should be inserted to bring nodes into memory or write changes back to disk.
Problems
18-1 Stacks on secondary storage
a.
The worst-case number of disk accesses is Θ(n), since each stack operation accesses a disk. The CPU time is Θ(nm).
b.
The worst-case number of disk accesses required for n PUSH operations is Θ(⌈n/m⌉). The CPU time is Θ(n).
c.
The asymptotic times fall back to those from part (a). The worst-case sequence is m+1 PUSH operations, followed by a pattern of POP, POP, PUSH, PUSH, POP, POP, etc. The single block kept in memory is continuously invalidated.
d.
Let the two consecutive blocks be B1 and B2. Whenever the top of the stack moves beyond B2, then write B1 to disk, let B2 become B1 and read the new B2 from disk.Whenever the top of stack moves before B1, then write B2 to disk, let B1 become B2 and read the new B1 from disk. Obviously, a disk read/write occurs only after m stack operations in the worst-case, which ensures amortized costs as specified.
18-2 Joining and splitting 2-3-4 trees
a.
Define x.height as follows:
Recall that all leaves of a B-tree are at the same depth, so the heights of all children of a node are the same. The property can be easily maintained in O(1) time for all operations:
Node creation: When a new leaf is allocated during insertion, its height is immediately set to 0.
Splitting: When a node is split, the newly allocated sibling simply copies the height of the original node. If the root splits, the newly allocated parent root evaluates the above formula to get a height of .
Merging: When two nodes are merged, the resulting node just retains the height of the original siblings.
b.
The core idea is to attach the shorter tree to the taller tree at the exact level where their heights match. If both trees are exactly the same height, the join is trivial and takes O(1) time:
Allocate a new root node T.root and set k as its only key.
Make T′.root its left child and T′′.root its right child.
Set T.root.height=h′+1.
Return T as the new B-tree.
Otherwise, assume h′>h′′ (the other direction is symmetrical). We must attach T′′ to the rightmost edge of T′.
Traverse the Right Spine: Start at T′.root and walk down the rightmost child pointers.
Preemptive Split: As you descend, check if the child you are about to step into is full. If it is, immediately call
B-Tree-Splt-Childon it. This guarantees that whatever node we eventually insert k into will have enough room.Stop at the Correct Height: Use the x.height attribute to stop your descent exactly when you reach a node x that is at height h′′+1.
Insert and Attach:
Because of the preemptive splits, node x is guaranteed to have at most 2 keys.
Append k to the end of node x's keys.
Append T′′.root to the end of node x's child pointers (so it sits to the right of k).
Return T′.
There is one edge case that must be handled separately, as in B-Tree-Insert. We should split the root if it is full before step 1.
c.
Let's trace the path p from the root down to k. At any given node x along this path, the algorithm determines it must descend into a specific child pointer x.cj to find k. By the definition of a B-tree, everything to the left of x.cj is strictly less than any key inside x.cj. Therefore:
The keys x.key1,…,x.keyj−1 belong to S′.
The subtrees x.c1,…,x.cj−1 belong entirely to S′.
Because the path continues down into x.cj, all elements found further down the path will be strictly greater than the left-hanging subtrees and keys of the current node x.cj. Therefore, to form the sequence {T0′,T1′,…,Tm′} and {k1′,k2′,…,km′} in strictly increasing order y<ki′<z, we simply collect the left-hanging subtrees and keys top-down as we walk path p.
The leftmost subtree of the root becomes T0′.
It is followed by k1′ (which is root.key1).
We continue collecting left-to-right at the root, and then proceed down the path, appending the left-hanging components of each subsequent node.
Because we collect the trees {T0′,T1′,…,Tm′} top-down along path p, their heights are monotonically decreasing, that is height(Ti′)≤height(Ti−1′).
The set S′′ (keys greater than k) is formed by the exact symmetrical logic, but the collection direction must be reversed to maintain the increasing order. We must collect the right-hanging subtrees and keys bottom-up, hence height(Ti′′)≥height(Ti−1′′).
d.
Based on part (c), we can form the subtrees and keys to construct S′ and S′′. The process is the same for both of them, so we describe it only for S′:
Let T′=Tm
For i=m…1
Let T′=Join(T′,Ti−1,ki)
Return T′
Observe that the total running time is O(lgn), since the differences in heights telescope.
Last updated