Balanced Trees

Why a search tree must stay short, how AVL trees fix their shape with rotations, and what red-black trees and B-trees are used for.

15 minLesson 6 of 6
On this page
  1. Why height matters
  2. AVL trees: a strict height rule
  3. Rotations
  4. Single and double rotations, traced
  5. Red-black trees: looser rules, fewer fixes
  6. B-trees: wide and shallow, for disks
  7. Common mistakes
  8. Check yourself
  9. What to practise next

A sign-up service stores its users in a binary search tree, keyed by user ID. In testing, IDs were random and lookups took a few dozen steps at most. In production, IDs are handed out in order: 1, 2, 3, and so on. Each new user is bigger than everyone before, so each one becomes the right child of the last. After a million sign-ups the tree is a stick, a million levels deep, and finding the newest user takes a million steps.

The BST code is fine. The shape is the problem, and it came from the order of the data. A balanced tree is a search tree that repairs its own shape after every change, so it stays short no matter what order the data arrives in.

Why height matters

Search, insert and delete in a BST each walk one path from the root down, so they all cost O(h), where h is the height. Compare the best and worst shapes:

NodesShortest possible heightStick height
1,000101,000
1,000,000201,000,000
1,000,000,000301,000,000,000

A balanced tree does not need to be perfectly short. It only needs a height that stays within a constant factor of log₂ n. That is enough for every operation to be O(log n) in the worst case, not only on lucky inputs.

AVL trees: a strict height rule

The AVL tree, the oldest balanced BST, adds one rule to the BST rule:

Each node stores its own height, as defined in Binary Trees. Left height minus right height is the node's balance factor, and it must be −1, 0 or +1. That keeps the height below about 1.44 log₂ n.

Insert as in a normal BST. Then, on the way back up to the root, each node on the path updates its height and checks its balance factor. Only those nodes can have changed. If one is off by 2, a rotation fixes it on the spot.

Rotations

A rotation changes the shape of a small part of the tree without breaking the search order. In a right rotation, a node x and its left child y swap roles: y moves up, x becomes its right child. Their three subtrees, called A, B and C, are reattached so that the left-to-right order stays the same.

x y C A B rotate right rotate left y x A B C order: A y B x C order: A y B x C
A right rotation lifts y and lowers x. Reading left to right, the order A, y, B, x, C does not change, so the BST rule still holds.

Of the three subtrees, only B changes parent, from y to x. That is safe: everything in B is bigger than y and smaller than x. A rotation changes three links, so it costs O(1). A left rotation is the mirror image.

Rotations in an AVL tree
// Each node has val, left, right and height.
function height(node) {
  return node === null ? 0 : node.height;
}
function update(node) {
  node.height = 1 + Math.max(height(node.left), height(node.right));
}
 
// x's left child y moves up, and x becomes y's right child.
function rotateRight(x) {
  const y = x.left;
  x.left = y.right; // the middle subtree B changes parent
  y.right = x;
  update(x); // x is lower now, so update it first
  update(y);
  return y;  // the new top of this subtree
}
// rotateLeft is the mirror: swap every left and right.
# Each node has val, left, right and height.
def height(node):
    return 0 if node is None else node.height
 
def update(node):
    node.height = 1 + max(height(node.left), height(node.right))
 
# x's left child y moves up, and x becomes y's right child.
def rotate_right(x):
    y = x.left
    x.left = y.right  # the middle subtree B changes parent
    y.right = x
    update(x)  # x is lower now, so update it first
    update(y)
    return y   # the new top of this subtree
# rotate_left is the mirror: swap every left and right.

Single and double rotations, traced

When a node is off by 2, look at the path from it down toward the new value. If the path goes in a straight line (left-left or right-right), one rotation at the unbalanced node fixes it. If it zig-zags (left-right or right-left), first rotate the child to straighten the line, then rotate the node. Watch both cases while inserting 10, 20, 30, 25 and 28.

AVL inserts: 10, 20, 30, 25, 28Use the arrows or ← → keys
insert like a normal BSTwalk back up; at each node:  update its height  if one side is 2 taller:    if the path zig-zags: rotate the child first    rotate the node toward the shorter side
  1. 1020
    node
    10
    left h
    0
    right h
    1

    1Insert 10, then 20. 10's left subtree has height 0 and its right has height 1. A difference of 1 is allowed.

  2. 102030
    node
    10
    left h
    0
    right h
    2

    2Insert 30. It goes right, then right again. At 10, the left height is 0 and the right height is 2. Off by 2: this node must be fixed.

  3. 201030
    node
    20
    left h
    1
    right h
    1

    3The path from 10 goes right, right: a straight line. One left rotation at 10 lifts 20 to the top. Height drops from 3 to 2.

  4. 20103025
    node
    20
    left h
    1
    right h
    2

    4Insert 25: smaller than 30, so it becomes 30's left child. 30 leans left by 1 and the root leans right by 1. Both are allowed.

  5. 2010302528
    node
    30
    left h
    2
    right h
    0

    5Insert 28: right of 20, left of 30, right of 25. Walking back up, 30 has a left height of 2 and a right height of 0. Off by 2.

  6. 2010302825
    node
    30
    left h
    2
    right h
    0

    6The path from 30 goes left, then right: a zig-zag. A right rotation at 30 alone would just move the zig-zag to the other side. So first rotate left at 25 to straighten it.

  7. 2010282530
    node
    28
    left h
    1
    right h
    1

    7Now 30, 28, 25 is a straight line to the left. Rotate right at 30: 28 moves up with 25 and 30 as its children.

  8. 2010282530
    height
    3
    plain BST height
    5
    rotations
    3

    8Every node is balanced again and inorder still reads 10, 20, 25, 28, 30. A plain BST with the same inserts would have height 5. This one has height 3, after 3 rotations.

1 / 8

An insert causes at most one fix (a single or a double rotation), and the walk back up is O(log n). A delete can need a rotation at several levels on the way up, but each is O(1), so it is O(log n) too.

Red-black trees: looser rules, fewer fixes

A red-black tree colours every node red or black and keeps these rules:

  • The root is black.
  • A red node never has a red child.
  • Every path from a node down to an empty spot passes the same number of black nodes.

Together, these mean the longest path can be at most twice the shortest, so the height stays below about 2 log₂ n. That is less strict than AVL, so lookups may take a few more steps. In exchange, inserts and deletes need fewer rotations: most fixes are cheap recolourings, and an insert never needs more than two rotations. For data that changes a lot, that is a good trade. That is why the sorted maps in standard libraries are usually red-black trees: Java's TreeMap is one, and so is C++'s std::map in the common implementations.

B-trees: wide and shallow, for disks

Binary trees assume following a pointer is cheap. On a disk, each node visited can be a separate read, thousands of times slower than memory. So the goal becomes: make the tree as shallow as possible, even if each node does more work.

A B-tree node holds many sorted keys, often hundreds, and has one more child than it has keys. The keys split the range of values, just as a single BST key splits it into left and right. All leaves sit at the same depth. When a node overflows, it splits in two and passes its middle key up to its parent, so the tree grows at the root and stays perfectly even.

2040 510 253035 5060 below 20 20 to 40 above 40
A small B-tree. The keys in a node split the values below it: the middle child holds everything between 20 and 40.

With 500 children per node, the fourth level already holds 500 × 500 × 500 = 125 million nodes, each full of keys. A lookup reads four nodes, fewer if the top levels are cached in memory. A balanced binary tree needs 27 levels for just 125 million keys. That is why databases and file systems store their indexes as B-trees, usually a variant called the B+ tree.

ApproachTimeSpaceWhy
Plain BSTO(n) worst caseO(n)Height depends on insert order.
AVL treeO(log n)O(n)Height below about 1.44 log₂ n. Fastest lookups, more rotations.
Red-black treeO(log n)O(n)Height below 2 log₂ n. Cheaper inserts and deletes.
B-treeO(log n)O(n)Log base is the node width, often hundreds, so very few disk reads.

Common mistakes

Check yourself

Quick check

You insert 1, 2 and 3, in that order, into an empty AVL tree. What is the root afterwards?

An AVL node is off by 2, and the path from it to the new value goes left, then right. What fixes it?

Why do databases use B-trees rather than red-black trees for indexes on disk?

What to practise next

That closes the trees module. The next step generalises trees: once nodes can connect in any pattern, with cycles and many parents, you have a graph. Start with Graph Basics.