Skip to content

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

A rotation is a local pointer swap that changes a node's depth without breaking the left-<-node-<-right ordering. When a node leans too far left, a right rotation pulls its left child up to take its place and pushes the heavy node down on the right. The in-order sequence is untouched (it is still sorted) - only the shape gets shorter. That is the magic: a constant-time edit that buys back a whole level of height.

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.

Insert-driven tree animator

rotate - skew - split

Empty tree - insert a value to grow one
comparing nowpath takenjust insertedrotatingsplitting

AVL mode. Insert values - the tree rotates whenever a node's two sides differ by more than 1.

nodes 0height 0balanced would be 0-

An insert needs at most one rotation

A single insert can only unbalance nodes along the one path it touched. AVL fixes the lowest unbalanced ancestor with a single or double rotation - and that one fix-up restores the entire tree, because the rotated subtree returns to exactly the height it had before the insert. So rebalancing costs rotations on top of the walk. Cheap insurance for a permanent guarantee.

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 node
AVL insert with rebalancing

Check 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

Rotate and split your own trees in the two classic visualizers this demo is modeled on: VisuAlgo - BST / AVL animates AVL insertion and rotation step by step, and the USF Data Structure Visualizations (David Galles) let you drive AVL trees, red-black trees, and B-trees side by side and watch each rebalance the moment it skews.

Ask your teacher

Want to trace a red-black recolor, or see exactly why the double rotation is two singles? Ask and we will step through it. And here is the thread that ties this to everything: self-balancing is what makes ordered maps and database indexes trustworthy - a guaranteed that never degrades to . B-trees minimize disk reads the same way arrays exploit cache locality back in Lesson 3: both respect the memory hierarchy, keeping the data you will touch next as close and as few hops away as possible.