Balanced Search Trees
An ordinary BST can degrade to height ; balanced search trees guarantee by maintaining invariants and repairing them after every update. We meet rotations, the local restructuring primitive, then red-black trees, whose color invariants force logarithmic height, and finally B-trees, which trade tall-and-thin for short-and-wide to win on disk.
╌╌╌╌
The previous lesson left us with a sharp problem. A binary search tree does everything in time, which is when the tree is balanced and when it is not, and we cannot control the order in which keys arrive. A balanced search tree removes the dependence on luck. It maintains a structural invariant strong enough to force , and after every insertion or deletion it does a small amount of rebalancing to restore that invariant.1 The guarantee becomes worst-case, not best-case: per operation, on every input.
Different balanced trees enforce different invariants. AVL trees bound the height difference of sibling subtrees, red-black trees color the nodes, and B-trees pack many keys per node, but they all share one mechanical primitive for reshaping the tree without disturbing its sorted order: the rotation.
Rotations: local surgery that preserves order
A rotation rearranges three pointers to change a tree's shape, and hence its
height, while keeping the BST property intact. A right rotation at a node
makes 's left child the new subtree root, with becoming 's right
child; a left rotation is the exact inverse. The subtree that was in the middle
(call it ) switches parents but stays between and in key
order.
In both trees the sorted order is — a rotation never violates the BST property.2 What it does change is height: rotating can pull a deep subtree up a level and push a shallow one down — the mechanism a balanced tree uses to repair itself.
- 1
- 2becomes x's right child
- 3if then
- 4
- 5splice y where x was
- 6if then
- 7
- 8else if then
- 9
- 10else
- 11
- 12x hangs under y
- 13
A rotation touches only a constant number of pointers, so it runs in time. Every balanced-tree rebalancing operation is built from a handful of rotations (plus, for red-black trees, recolorings) along a single root-to-leaf path, hence work to repair the tree after an update.
For a concrete instance, suppose has
left child , and 's children are (keys ) and (keys in
), while 's right child is (keys ). A right rotation at
promotes to the subtree root: becomes 's right child,
(the middle
subtree) detaches from and reattaches as 's left child,
and , stay put. Reading the keys left to right before the rotation,
; after it, , unchanged.
Only three pointers moved ('s right, 's left, and the parent link that
now points at ), yet the node that was at depth () is now at depth
and has dropped one level, the height change a balanced tree exploits.
Red-black trees: balance by color
A red-black tree is a BST in which every node carries one extra bit, its color — red or black.3 Five invariants on those colors squeeze the tree into logarithmic height:
Properties 4 and 5 are what force balance. Property 5 says all root-to-leaf paths have the same number of black nodes; property 4 says reds cannot cluster, so reds can at most double a path's length by interleaving. Together they bound how lopsided the tree can get.
The black-height of a node is the number of black nodes on any path from down to a leaf, not counting itself; property 5 asserts precisely that this number is well defined. Consider a concrete tree:
Check property 5 at the root: the path crosses two black nodes ( and nil), and so does the longer path ( and nil). The red nodes , , , stretch some paths without contributing to any black count, and property 4 guarantees they never appear twice in a row, which is what caps the stretching at a factor of two.
Why the height is
The argument is short and worth seeing, because it explains why the color rules take exactly this form. Let be the black-height of node (the number of black nodes on any path from down to a leaf, not counting ).
Now let be the tree's height. By property 4 at least half the nodes on any root-to-leaf path are black, so the root's black-height is at least . Applying the lemma at the root with internal nodes,
So every operation that walks the tree (search, insert, delete, successor) runs in worst-case time, provided updates preserve the invariants. That is the job of the fix-up procedures.
Insertion: recolor when you can, rotate when you must
starts as an ordinary BST insertion: walk down to a leaf position and attach the new node there. Then color red. The choice of color is deliberate. A red adds zero to every black count, so property 5 survives untouched; the only properties at risk are property 2 (if is the root) and property 4 (if 's parent happens to be red). Coloring black instead would add one black node to exactly the paths through and break property 5 — a global violation that is much harder to localize. So insertion trades a possible global breakage for a possible local one, and repairs the local one by walking up the tree.4
The fix-up loop runs while 's parent is red. Write for the parent, for the grandparent (which must be black, since is red and the tree was valid before), and for the uncle — 's other child. Assume is 's left child; the other orientation mirrors every case left-for-right. Exactly three cases arise:
Case 1 (red uncle): recolor and climb. If is red, paint and
black and red, with no rotation at all. Locally, no two reds
is restored;
globally, every path through still crosses the same number of blacks,
because the black that left reappears one level down on both sides.
But is now red, so if its parent is also red the violation has moved two
levels up. Set and repeat.
Case 2 (black uncle, zig-zag): straighten. If is black and is 's right child, the red pair forms a bent path (left, then right). A left rotation at straightens it: the old rises, the old drops to become its left child, and both are still red. Nothing is fixed yet — this move exists only to convert the configuration into Case 3, with renamed to the node that is now the lower red.
Case 3 (black uncle, zig-zig): rotate and finish. Now is 's left child and the two reds lie on a straight line. Right-rotate at , then swap the colors of and : the subtree's new root is black, its children and are red. Every path that used to pass through black now passes through black instead, so all black counts are preserved, and the new subtree root is black, so no red-red pair can exist at its top. The loop terminates.
After the loop ends, one line remains: color the root black. If the loop pushed the red up to the root (or the very first insertion created a red root), this restores property 2 without disturbing property 5, since adding a black at the root adds one to every path's count equally.
The cost accounting: each Case 1 iteration is recoloring and lifts two levels, so there are at most of them; Cases 2 and 3 execute at most once each and end the loop. An insertion therefore performs recolorings but at most two rotations. Deletion has a symmetric (if fussier) fix-up with four cases per side and at most three rotations, also overall; CLRS works through it in full.5
A worked trace
Insert the keys into an empty tree, in that order. Every fix-up case appears exactly once.
- Insert . The tree was empty, so becomes the root; the final recolor-the-root step paints it black.
- Insert . It lands as the red right child of black . No red parent, no violation.
- Insert . It lands as the red right child of red : a violation. The uncle ('s left child) is nil, hence black, and lies on a straight line with under — the mirror of Case 3. Left-rotate at and swap colors: (black) is the new root with red children and .
- Insert . It lands as the red right child of red . The uncle is red, so Case 1 applies. Recolor: and turn black, turns red, and jumps to . The loop exits ( has no parent) and the final line repaints the root black. No rotation.
- Insert . It descends and lands as the red left child of red . The uncle (left child of ) is nil, hence black, and the reds form a bent path under , the mirror of Case 2. Right-rotate at : now is the lower parent with below it, a straight line. The mirror of Case 3 finishes: left-rotate at , swap colors, and the subtree root is black with red children and .
Check the final tree against the invariants: the root is black; the reds and have only (nil) black children; and every root-to-nil path crosses exactly two black nodes. Height for keys, comfortably inside the bound.
The contrast is clearest on the worst possible input for a plain BST: keys arriving already sorted. A naive tree threads them into a single descending path of height ; the red-black invariants instead rebalance on the fly into a bushy tree of height , with every root-to-leaf path carrying the same black-height.
Red-black versus AVL
AVL trees reach the same guarantee with a different invariant: the heights of any node's two subtrees differ by at most , enforced by rotations. The two invariants trade against each other.
- Height. The AVL condition is stricter, giving height at most , versus the red-black bound of . Searches in an AVL tree therefore inspect fewer nodes in the worst case.
- Update cost. The looser red-black invariant is cheaper to maintain. A red-black insertion performs at most rotations and a deletion at most , with everything else recoloring; an AVL insertion also stops after one rebalancing step, but an AVL deletion can cascade rotations all the way up the path, of them in the worst case.
The practical split follows: read-heavy workloads with rare updates favor AVL's shorter trees, while mixed insert/delete workloads favor red-black's cheaper repairs, which is why red-black trees back many standard-library ordered maps.6 Both cost per operation either way; the difference is in the constants.
B-trees: balance for the disk
Red-black and AVL trees minimize the number of nodes on a root-to-leaf path, which is the right cost in memory. But when the data lives on a disk or SSD, the cost that dominates is not comparisons but block transfers: reading one page is millions of times slower than a memory comparison, and a binary tree of keys has height — up to disk reads per search. The fix is to make each node short and wide, sizing it to fill one disk block.
A of minimum degree packs between and keys per node (with one more child than keys) and keeps all leaves at the same depth.7 The high fan-out pushes the height down to
so with on the order of a thousand keys per block, a billion-key tree is just or levels deep — two or three disk reads instead of thirty. Updates keep the leaves level by splitting full nodes and merging underfull ones, all local work. B-trees (and the leaf-linked B+-tree) are the index structure databases and filesystems rely on for exactly this reason. The full treatment — node invariants, search, split/merge, and the height proof — is in the dedicated B-Trees lesson.
Other ways to stay balanced
Red-black and AVL trees enforce balance deterministically by structural rules, but they are two points in a much larger design space, and the alternatives trade worst-case guarantees for simpler code or new capabilities.
Randomized balance. A treap (Seidel and Aragon, 1996) gives each key a
random priority and keeps the tree a BST on keys and a heap on priorities;
rotations restore the heap order after an insert. Because the priorities are
random, the expected height is with no explicit balance bookkeeping,
the entire insert is attach as a leaf, then rotate up while your priority beats your parent's.
A skip list reaches the same expected bound with a different
mechanism, covered in
Skip Lists & Probabilistic Structures.
Self-adjusting balance. A splay tree (Sleator and Tarjan, 1985) keeps no balance information at all. After every access it splays the touched node to the root by a sequence of rotations; individually an operation can be , but any sequence of operations costs amortized, and frequently accessed keys drift near the root, giving the tree a built-in caching effect that static-balance trees lack.
Simpler code, same bound. Sedgewick's left-leaning red-black tree (2008) restricts red links to lean left, collapsing the many insert/delete cases down to a handful and making red-black trees far easier to implement correctly. Weight-balanced and scapegoat trees balance by subtree sizes rather than heights, rebuilding an entire subtree from scratch when it grows too lopsided, which keeps amortized with no per-node balance field.
Persistence. Because a rotation touches only nodes on one path, balanced BSTs are naturally persistent: copying just the changed path (path copying) yields a new version of the tree while the old one survives intact, in extra space per update. This is why immutable/functional languages use balanced BSTs (typically red-black or weight-balanced) as their standard ordered-map implementation.8
Takeaways
- A balanced search tree maintains a structural invariant that forces height , repairing it after each update so every operation is worst-case , not merely best-case.
- Rotations are the primitive that reshapes a tree to change its height while preserving the BST property and sorted order.
- Red-black trees color nodes and enforce no-two-reds plus equal black-height; a counting argument shows .
- Insert-fixup has three cases: red uncle means recolor and climb two levels (Case 1); black uncle means straighten a zig-zag (Case 2) and finish with one rotation plus a color swap (Case 3). Total: recolorings, at most rotations per insert, at most per delete.
- AVL trees reach the same bound with a stricter height-difference invariant: shorter trees, more rotations.
- trade tall-and-thin for short-and-wide, packing keys per node so height is , minimizing disk block transfers, the dominant cost for on-disk data.
Footnotes
- Erickson, Ch. — Balanced Binary Search Trees: invariant-plus-rebalancing forces worst-case height. ↩
- CLRS, Ch. 13 — Red-Black Trees (§13.2): rotations as the restructuring primitive that preserves the BST property. ↩
- CLRS, Ch. 13 — Red-Black Trees (§13.1): the color invariants that bound height to . ↩
- CLRS, Ch. 13 — Red-Black Trees (§13.3): RB-Insert-Fixup and its three-case analysis; at most two rotations per insertion. ↩
- CLRS, Ch. 13 — Red-Black Trees (§13.4): RB-Delete-Fixup, four cases per side, at most three rotations. ↩
- Skiena, §3.4 — Balanced Search Trees: red-black trees as the practical balanced BST behind library ordered maps. ↩
- CLRS, Ch. 18 — B-Trees (§18.1): the minimum-degree structure with to keys per node, minimizing disk transfers. ↩
- Seidel & Aragon,
Randomized search trees
(treaps, 1996); Sleator & Tarjan,Self-adjusting binary search trees
(splay trees, 1985); Sedgewick,Left-leaning red-black trees
(2008); Driscoll, Sarnak, Sleator & Tarjan,Making data structures persistent
(1989). ↩
╌╌ END ╌╌