Exercises — Module 11 (B-trees & Digital Searching)
Self-contained problems on this module's material — B-tree insertion and its height bound, B-tree deletion, B*-trees, binary tries, trie pruning, and Patricia. You can work every one without the books: each states the problem in full, gives a hint to peek at when stuck, and a worked answer sketch to check against after you try. Computational answers here are reproduced by the code you write in the lab (or a few lines at a REPL).
Ratings follow Knuth's scale (00–50; M = needs mathematics, ▶ = especially
instructive). Where a problem mirrors a TAOCP exercise, its number is noted for
readers who own Volume 3.
Tracking
| # | Topic | Rating | Status |
|---|---|---|---|
| 1 | Insert into an order-7 B-tree by hand | 20 | ⬜ |
| 2 | ▶ B-tree deletion: borrow and merge | 22 | ⬜ |
| 3 | Attainability of Theorem B; the matching lower bound | M24 | ⬜ |
| 4 | ▶ B*-trees: the 2-for-3 split and occupancy | 25 | ⬜ |
| 5 | Trace Algorithm T; draw the trie | 20 | ⬜ |
| 6 | Trie pruning: which nodes may be removed | 23 | ⬜ |
| 7 | ▶ Knuth's exact Patricia: back pointers, nodes | 24 | ⬜ |
| 8 | Exact average trie depth (Mellin machinery) | M28 | ⬜ |
Problems
1. Insert into an order-7 B-tree by hand (rating 20 · cf. 6.2.4–1)
Problem. Insert the keys in that order into an initially empty B-tree of order . A node holds at most keys; by Algorithm B it overflows when it reaches keys and then splits, the median rising into the parent. Draw the tree after each split, give the final tree, and state its height. Confirm the height obeys Theorem B.
Hint. With a node overflows at keys; the split uses , so (in -indexed key order) stay, rises, and move to a new right sibling — three keys on each side, both .
Answer sketch. Keys – fill one leaf: [1 2 3 4 5 6]. Inserting
overflows it; the median rises, giving root [4] over leaves [1 2 3] and
[5 6 7]. Keys fill the right leaf to [5 6 7 8 9 10]; inserting
overflows it, and its median rises to the root. Keys then extend the
last leaf. The final tree is
[4 8]
/ | \
[1 2 3] [5 6 7] [9 10 11 12 13]
height . Theorem B with , gives , so is within the bound. Note that ascending input — the pattern that degrades a plain BST into a linked list — leaves the B-tree perfectly balanced; it only ever grew at the root (the two splits after and after ).
2. ▶ B-tree deletion: borrow and merge (rating 22 · cf. 6.2.4–2)
Problem. The lab implements only insertion (overflow split). Design deletion as its mirror image. To remove a key : if sits in an internal node, first swap it with its in-order neighbour (predecessor or successor) so the actual removal happens in a leaf; delete it there. A non-root node that drops below the minimum keys underflows and must be repaired by either borrowing from a sibling with a key to spare or merging with a minimal sibling. Describe both repairs, verify their key-counts stay legal, and explain why all five B-tree properties survive and the tree shrinks only at the root.
Hint. Underflow is the dual of overflow. If an adjacent sibling has keys, rotate one through the parent (borrow); if every adjacent sibling has exactly keys, fuse the deficient node, the parent separator, and one sibling into a single node (merge). Check that a merge never exceeds keys.
Answer sketch. Borrow. The deficient node has keys; a sibling has . Move the parent's separator key down into the deficient node and promote the sibling's adjacent (nearest) key up into the separator slot, moving the corresponding child link too. The deficient node returns to keys, the sibling stays , the parent is unchanged — done, no propagation.
Merge. All siblings are minimal ( keys). Combine the deficient node
( keys), the parent separator ( key), and a minimal sibling (
keys) into one node holding keys. This is always
: for even , gives ; for odd ,
gives — legal either way. The parent loses one key
and one child; if it now underflows, recurse upward. If the root loses its last
key, delete it and make the merged node the new root — the only step that
lowers the tree, exactly mirroring how B5 is the only step that raises it. Leaf
levels stay uniform because borrowing and merging move keys sideways, never
changing any leaf's depth, so property 4 is preserved. Test the implementation
against is_valid from stage 2, which already checks every property.
3. Attainability of Theorem B; the matching lower bound (rating M24 · cf. 6.2.4–3)
Problem. Theorem B bounds the height above: with . Prove the matching lower bound by counting the maximum number of keys a height- B-tree can hold, and identify which trees attain each bound. Evaluate both bounds for , .
Hint. The upper-bound proof in the lesson counts the stingiest legal tree (every node minimal) and gets . For the lower bound, count the fullest tree: every node holds keys and has children.
Answer sketch. In a completely full tree of height , level has nodes (), each carrying keys, so
Hence , i.e. . Together with Theorem B,
The upper bound is attained by the minimal tree Knuth's proof exhibits (root with key and children, every deeper node with exactly children); the lower bound is attained by the completely full tree just counted. These are the two extremal instances that bracket every B-tree — the same "pin down the extreme, bound everything" move as Lamé's theorem in Module 01. For , : and , so the height must be , , or — the search costs at most page reads whatever the insertion order.
4. ▶ B*-trees: the 2-for-3 split and occupancy (rating 25 · cf. 6.2.4–B*)
Problem. A B*-tree raises the minimum occupancy from to by splitting less eagerly. When a node overflows and an adjacent sibling is full, instead of a split you perform a split: pool the two full siblings, the overflow key, and the parent separator, and redistribute into three nodes with two new separators promoted. (When a sibling has room, you shift a key to it and do not split at all.) Show that each of the three resulting nodes ends up at least about full, versus the ordinary B-tree's .
Hint. Count the pot. Two full nodes hold keys; add the overflow key and the parent separator, then subtract the two keys that become new separators. Divide what remains among three nodes.
Answer sketch. The two full siblings hold keys; with the incoming overflow key and the parent separator that is keys governing the span. Two of them are promoted upward as the pair of new separators, leaving keys to distribute over three new nodes — at least per node. Dividing by the capacity ,
so occupancy approaches as grows (finite gives values like at ), against the ordinary split's guarantee of only keys, i.e. . The price is more elaborate splitting logic and touching four pages instead of three; the reward is denser pages, hence a smaller tree and fewer I/Os. The invariant "every non-root node is full" is preserved by construction because a split is only triggered when both participants are full, and it leaves all three products comfortably above the floor.
5. Trace Algorithm T; draw the trie (rating 20 · cf. 6.3–1)
Problem. Using Algorithm T over -bit keys, most-significant-bit first, insert the set (that is ). At depth the trie branches on bit . Draw the resulting binary trie, count its nodes (root and internal nodes included), and identify every one-way node (a node with a single child). What do those one-way nodes reveal?
Hint. All four keys begin with bit , so the root's HI side is empty and the entire tree hangs beneath one LO edge. Keys share the prefix ; keys share .
Answer sketch. The trie has nodes. From the root, all keys take LO (leading bit ) to a node testing bit : its LO side leads to the pair , its HI side to . On the LO side, bit is a one-way corridor to a node that finally splits on bit into leaves and ; symmetrically on the HI side, bit is a corridor to the bit- split of and .
(root, bit0)
0|
(bit1)
0/ \1
(bit2=0) (bit2=1) <- one-way corridors
| |
(bit3) (bit3)
0/ \1 0/ \1
0000 0001 0110 0111
The one-way nodes — the root and the two bit- nodes — test bits on which every key below them agrees, so they store no branching information at all: pure overhead. That waste (up to such nodes for a shared -bit prefix) is exactly what Patricia removes by recording the next distinguishing bit and jumping to it. This -bit toy mirrors the -bit structure the lab builds.
6. Trie pruning: which nodes may be removed (rating 23 · cf. 6.3–9)
Problem. Deletion in a binary trie runs Algorithm T backwards: unmark the key's terminal node, then prune. Characterize exactly which nodes a single deletion may remove, and argue why pruning is necessary — the lesson warns that without it, insert/remove cycles "leak a -node chain per key."
Hint. A node is removable iff it is unmarked (no key ends there) and childless. Walk back up the just-searched path, detaching such nodes, and stop at the first node that is either marked or still has a child.
Answer sketch. Pruning removes precisely the maximal suffix of the deleted key's root-to-leaf path made of nodes that are both unmarked and, once the node below is detached, childless. Concretely: unmark the terminal node; if it is now childless, detach it; its parent may in turn become unmarked-and-childless, so detach that too; continue upward and stop at the first node that is marked (another key terminates there) or still has a child (another key branches off there). Nodes off the path, marked nodes, and genuine branch points are never touched. Pruning is necessary because inserting a key can create a chain of up to fresh nodes; if deletion only unmarked the terminal node, that chain would persist. Repeated insert/remove of the same key would then accumulate dead structure that no longer mirrors the set's contents — and stage 3's reinsert-cycle test is built to notice exactly this residue.
7. ▶ Knuth's exact Patricia: back pointers, nodes (rating 24 · cf. 6.3–P)
Problem. The lab builds the compressed radix-trie form of Patricia: separate leaves plus two-way branch nodes, nodes for keys. Knuth's own Algorithm P is one twist cleverer — it has no leaf nodes at all, storing each key inside a branch node reached by a tagged back pointer, so keys use exactly nodes. Describe the conversion: what each branch node stores, how tagged links replace leaves, and why membership still needs exactly one full key comparison.
Hint. leaves branches . If each branch node also holds one key, and a child link that would have pointed down to a leaf is instead tagged to point back up to the branch that stores that key, the branches alone carry all the keys.
Answer sketch. In the compressed form, each of the branch nodes tests one bit index (strictly increasing down every root-to-leaf path) and has two child links, each going either to a deeper branch or to one of the leaves that hold the keys. Knuth's form deletes the leaves: every branch node additionally stores one key, and whenever a child link would have descended to a leaf it is tagged, meaning "follow this link back up to the key-bearing node it names" rather than "descend." A search performs bit tests, following untagged links downward until it crosses a tagged link, which delivers the single key to test. Because the branch nodes only ever tested the bits that distinguish keys and skipped the rest (Patricia's first idea), the descent never verified the skipped bits, so it must still finish with one full comparison of the probe key against the reached key — the same single end-of-search comparison the lesson describes for the leaf form. Counting: branch nodes plus a header hold all keys, i.e. nodes total, independent of key length. This is a reconstruction/design exercise; the back-pointer bookkeeping (which link is tagged, and re-splicing it on insertion) is the delicate part Knuth works out in §6.3.
8. Exact average trie depth (Mellin machinery) (rating M28 · cf. 6.3–34)
Problem. The lesson's first-moment sketch argues the expected depth of a random binary trie on keys is . Turn the sketch into the exact statement: show the expected number of the other keys sharing a fixed key's first bits is , locate where that count crosses , and state the exact average depth the lesson quotes — plus a tiny oscillating term. Explain why extracting that constant needs Mellin-transform machinery, which is why this is a term project.
Hint. For uniform random keys, another key agrees with a fixed one on the first bits with probability ; the expected number that do is , which drops below once . The exact average is a harmonic-style sum over a divide-and-conquer recurrence.
Answer sketch. Fix a key . Each other key independently matches 's first bits with probability , so by linearity the expected number sharing an -bit prefix with is — below as soon as . A search path is "crowded" only while other keys still share its prefix, so typical branching ceases near depth : a random trie is about as shallow as a perfectly balanced BST, with no balancing code at all. The exact average depth of a random binary trie has the form
whose constant is and whose is a periodic fluctuation of minuscule amplitude () — matching the lesson's " plus a tiny oscillating term." Getting the constant and the oscillation exactly means writing the average as a harmonic sum and evaluating it by the Mellin transform: the poles of the transformed kernel contribute the term, the constant, and the periodic term in one stroke. That analytic machinery — Knuth's tour de force in §6.3 — is well beyond a hand computation, which is what earns the problem its M28 term-project rating. Treat the above as a guided road map, not a finished derivation.
Your solutions
Use this space to log your own work — a restated problem, your approach, and how it compared with the sketch above.
Exercise N (rating RR)
Approach. Answer / proof. Compared with the sketch: what you did differently, what you missed.