Advanced Structures
Self-balancing trees
AVL and red-black: guaranteed log-time ops
In Lesson 10 you watched a binary search tree decay: insert already-sorted keys and every value keeps going right, so the tree becomes a straight chain of height and search collapses from to . That is not a rare edge case - sorted or near-sorted input is everywhere (timestamps, autoincrement IDs, alphabetized names). Self-balancing trees fix it permanently: after every insert they do a small, cheap fix-up so the height can never drift far from . This lesson gives you the intuition, the real bound, and a tree you can rotate with your own hands.
Why height is the whole game
Every operation on a search tree - find, insert, delete - walks one root-to-leaf path, so its cost is exactly the height . A perfectly balanced tree of nodes has ; a fully skewed one has . Same nodes, same ordering rule, wildly different cost. The entire job of a balancing scheme is to keep small no matter what order the data arrives in.
The bound each scheme guarantees
An AVL tree never lets the two subtrees of any node differ in height by more than . That single rule provably caps the height at
so it is at most ~44% taller than a perfect tree, and every operation stays . A red-black tree keeps a looser invariant (no root-to-leaf path is more than twice as long as another), giving - a little taller, but fewer rotations per update. Both are ; they just trade lookup speed against update churn.
A rotation, in one picture
Rotate it yourself
This is the centerpiece. Insert values and watch each one fall into place, then - in AVL mode - watch the tree rotate the instant a node's two sides differ by more than one. Flip to plain BST mode to turn balancing off, press Insert 1->7, and watch the same sorted keys build a tall degenerate chain while the height readout climbs. Then try B-tree mode and watch an overfull node split.
rotate - skew - split
AVL mode. Insert values - the tree rotates whenever a node's two sides differ by more than 1.
An insert needs at most one rotation
Red-black trees and B-trees, and why they exist
You rarely hand-roll these. Red-black trees are the balance scheme hiding inside most standard-library ordered maps - C++ std::map, Java TreeMap, the Linux kernel's scheduler. They rebalance with rotations plus a one-bit color rule, favoring fewer rotations per update over the tightest possible height. Whenever you rely on an ordered map for guaranteed lookups, a red-black tree is doing the work.
B-trees answer a different question: what if the tree lives on disk? Reading one node from disk (a page) costs thousands of times more than a comparison in RAM. So a B-tree makes each node wide - dozens or hundreds of keys - so the tree is only a handful of levels deep, and a lookup touches a handful of pages instead of of them. When a node overfills, it splits and pushes its middle key up (try B-tree mode above). Height grows as where is the branching factor, and the giant crushes it flat. This is why B-trees (and B+-trees) index nearly every relational database and filesystem on earth.
The B-tree height, and why it is tiny
With minimum degree , a B-tree of keys has height at most
Take (a plausible page). A billion keys fit in a tree of height ~3 - three disk reads to find any row. A binary tree would need ~30. That factor of ten in disk trips is the entire reason databases are usable.
Insert with rebalancing is a small addition to the ordinary BST recursion - update the height, check the balance factor, and rotate if it exceeds one:
def insert(node, val):
if node is None:
return Node(val)
if val < node.val: node.left = insert(node.left, val)
elif val > node.val: node.right = insert(node.right, val)
else: return node # no duplicates
node.height = 1 + max(h(node.left), h(node.right))
bf = h(node.left) - h(node.right) # balance factor
if bf > 1 and h(node.left.left) >= h(node.left.right): return rotate_right(node) # LL
if bf < -1 and h(node.right.right)>= h(node.right.left): return rotate_left(node) # RR
if bf > 1: node.left = rotate_left(node.left); return rotate_right(node) # LR
if bf < -1: node.right = rotate_right(node.right); return rotate_left(node) # RL
return nodeCheck yourself
After a single AVL insert, how many rotations at most are needed to restore balance?
Why do databases favor wide B-tree nodes over binary trees?
Recall: what invariant does an AVL tree hold, and what height bound does it buy?
Think about the balance factor at every node. Try to state it, then check.
Lock it in
- Every search-tree operation walks one root-to-leaf path, so its cost is exactly the height - balancing exists to keep near whatever the insertion order.
- An AVL tree keeps subtree heights within 1 (); a red-black tree holds a looser bound with fewer rotations per update. Both stay .
- A rotation is a constant-time pointer swap that shortens the tree without breaking in-order ordering; a single insert needs at most one single or double rotation.
- B-trees make each node wide so the tree is only a few levels deep, minimizing costly disk reads - which is why they index nearly every database and filesystem.
Primary source
Ask your teacher