Hints — Module 11: Multiway Trees and Digital Searching
Graduated hints, three per stage. Reach for the next one only when the previous did not unstick you.
Stage 1: B-tree search and insertion with node splitting
- The defining discipline of a B-tree is that it grows upward, never downward: a node that overflows splits and pushes its median key up to its parent, and the only way a new level appears is when the root itself splits. Keep that one invariant in view and correctness follows — there is nothing to rebalance.
- Search down to a leaf, insert there, then let overflow propagate back up. The natural shape in an arena is a recursive helper on a node index that returns one of three outcomes to its caller: the key was a duplicate, the insertion was absorbed with no overflow, or the child split and here is the promoted median plus the index of the new right sibling.
- Use
keys.binary_search(&key):Ok(_)is a duplicate,Err(p)is both the child index to descend into and the sorted slot to insert at. Split whenkeys.len() == order(m keys, one too many): withmid = order/2, the median iskeys[mid], the left node keepskeys[..mid], the right sibling takeskeys[mid+1..](and the matchingchildrenslice if internal).keys_inorderinterleaves child 0, key 0, child 1, key 1, …, last child.
Stage 2: B-tree invariants and the height bound
is_validshould be written as if it distrusts your Stage-1 code: it independently re-derives every clause of the B-tree definition, so a bug in insertion shows up as a broken invariant rather than a wrong answer.heightexploits the fact that all leaves share a level (Property 4).- Check the invariants with a recursion that carries the open key interval
(lo, hi)each subtree must lie inside, as(Option<i64>, Option<i64>)withNonemeaning ±∞. Track a single "leaf level" cell: the first leaf you reach sets it, every other leaf must match it. Forheight, walk the leftmost child spine and count levels. - Per-node bounds:
min_keys = if is_root { 1 } else { (m+1)/2 - 1 }andmax = m - 1; also require strictly increasing keys,children.len() == keys.len() + 1at internal nodes, andkeys[i-1] < child keys < keys[i]via the passed-down(lo, hi).heightis0for an empty tree, else1 + (levels down the leftmost spine).
Stage 3: Digital searching: tries
- A trie branches on the bits of the key, not on comparisons: the path from the root spells the key out, one bit per level, so there is no ordering logic at all — just "which link do I follow next?" Fixed 32-bit keys, most-significant bit first, give 32 levels and keep left/right order equal to numeric order.
- Store two links per node, indexed by the bit value 0 or 1, plus an
is_keymark set on the node reached after all 32 bits are consumed. Insertion creates missing nodes as it descends; search fails the moment a link is null.removemust prune: after unmarking, walk back up and detach every node that is now unmarked and childless, or insert/remove cycles leak a chain per key. - Extract bit
ias(key >> (31 - i)) & 1. Insert loop: fori in 0..32, iflink[b]isNULLallocate a fresh node there, then advancep = link[b]; finally,is_keyalready set means duplicate. To prune, record the path as(parent, bit)pairs on the way down, then iterate it in reverse breaking as soon as a node is still marked or still has a child; recycle detached slots on afreelist.
Stage 4: Patricia: compressed binary tries
- Patricia removes one-way branching by storing, in each branch node, the index of the next bit that actually distinguishes the keys below it — agreed-upon bits are skipped entirely. The price is that a branch can route you but cannot vouch for the bits it skipped, so a search ends with exactly one full key comparison at a leaf. The invariant that makes it all work: bit indices strictly increase along every root-to-leaf path.
- Two node kinds: a
Leaf { key }and aBranch { bit, link: [_; 2] }. Search descends blindly, followinglink[bit-of-key-at-branch.bit]until it hits a leaf, then compares. Insertion is a blind hunt to a leafL, then find the critical bit where the new key first differs fromL, then re-descend to splice a new branch at that bit. - The critical bit is
d = (key ^ found).leading_zeros()(MSB-first index of the first differing bit). Re-descend from the root following branches whosebit < d, tracking(parent, side), and stop at the first node that is a leaf or a branch testing a bit> d; splice a newBranch { bit: d, .. }there whose two links are the fresh leaf and the displaced node, ordered bybit64(key, d). Correct code holdsnode_count() ≤ 2·keys - 1.