AVL, Red-Black trees. O(log n) operations guaranteed.
Quick unlock - significant prerequisite investment but simple final step. Verify prerequisites first.
A plain Binary Search Tree (BST) is fast only when it stays tree-shaped. Balanced trees are the engineering answer to the worst case: they enforce small local rules so the whole tree can’t silently collapse into a linked list.
Balanced trees (AVL and Red-Black) maintain a BST plus a local balance invariant. When an insert/delete breaks the invariant, they fix it using a small set of local rotations (and sometimes recoloring). Those local rules imply the height is O(log n), so search/insert/delete are guaranteed O(log n).
A Balanced Tree is a Binary Search Tree that actively prevents itself from becoming too tall.
Why this matters: In a regular BST, search/insert/delete follow one root-to-leaf path. If the tree height is h, these operations take O(h). In the best case h ≈ log₂ n, but in the worst case (inserting sorted keys) h = n − 1, so operations degrade to O(n).
Balanced trees enforce a local invariant at each node—some small constraint about the relationship between its children. The key idea is:
Two classic families:
1) AVL trees
2) Red-Black trees
Both guarantee O(log n) search/insert/delete, but they trade off how strict the balancing is and how much work they do to maintain it.
A helpful mental model: balancing is not about making the tree perfectly symmetric—it's about ensuring you never get a long skinny path.
If n is the number of keys and the height is h, then:
Balanced trees ensure h ≤ c · log₂(n + 1) for some constant c.
So you keep predictable performance even under adversarial insertion orders.
Balanced trees rely on a single surgical tool: the rotation.
Why rotations exist: When a subtree becomes too tall on one side, we want to “tilt” it to redistribute height—but we must preserve the BST in-order property:
A rotation changes the shape while keeping the in-order sequence of keys exactly the same.
Consider a node x with right child y. A left rotation makes y the parent and x the left child.
Before:
After rotating left at x:
Key ordering remains valid because:
Symmetric: rotate right at y when y has left child x.
Before:
After rotating right at y:
Think in terms of in-order traversal:
For the left rotation case, the in-order order of the local nodes/subtrees is:
After rotation, in-order becomes:
It’s identical.
Rotations are used because they change subtree heights in a controlled way.
If we define height as:
A rotation modifies only a constant-sized region, so height updates are local:
Sometimes a single rotation is not enough. Two common cases:
These “double rotations” still act locally but correct more complex imbalance patterns.
| Operation | Trigger pattern | Local effect | Preserves BST order? |
|---|---|---|---|
| Left rotation | right-heavy at x | promotes x.right | Yes |
| Right rotation | left-heavy at y | promotes y.left | Yes |
| LR (double) | left child is right-heavy | two-step rebalance | Yes |
| RL (double) | right child is left-heavy | two-step rebalance | Yes |
Rotations are the backbone of both AVL and Red-Black trees. The difference is the invariant that tells you when to rotate (and what extra metadata to update).
A balanced tree is defined less by rotations and more by the rule that determines when rotations are required.
AVL trees track a height-based constraint at every node.
Define the balance factor:
bf(node) = height(left) − height(right)
AVL requires:
bf(node) ∈ {−1, 0, +1} for every node
Why this helps: If every node’s subtrees differ in height by at most 1, then long skewed chains can’t form.
Insertion is normal BST insertion first. Then, as you walk back up toward the root, you update heights and check balance factors.
If a node becomes unbalanced, bf becomes ±2, and you fix it using one of four cases:
A useful feature: for AVL insertion, after you rebalance at the first unbalanced node on the path upward, the subtree height is restored in a way that often stops further fixes.
Deletion can reduce heights, which may cause multiple ancestors to become unbalanced. AVL deletion may require rebalancing repeatedly up to the root.
Red-Black trees store one extra bit per node: color ∈ {red, black}.
They satisfy (one common formulation):
1) Every node is red or black.
2) The root is black.
3) All leaves (nulls) are black.
4) If a node is red, then both its children are black. (No two consecutive reds.)
5) For any node, every path from that node to any descendant leaf has the same number of black nodes. (Equal black-height.)
Why this helps:
Red-Black balancing is looser than AVL. It allows more shape variation, but still guarantees logarithmic height.
Balanced trees work because local constraints imply global height bounds.
Let bh be the black-height of the root (number of black nodes on any root→leaf path, excluding null leaf depending on convention).
From rule (4), between black nodes you can have at most one red node. So any root→leaf path has length at most:
h ≤ 2 · bh
Also, a subtree with black-height bh has at least 2ᵇʰ − 1 internal nodes (because each black level must branch enough to maintain that black-height; formally, the minimum-node RB tree of black-height bh is a perfect black-only tree).
So if n ≥ 2ᵇʰ − 1, then:
n + 1 ≥ 2ᵇʰ
log₂(n + 1) ≥ bh
Combine with h ≤ 2 · bh:
h ≤ 2 · log₂(n + 1)
Therefore search/insert/delete are O(log n).
AVL trees are even tighter. Let N(h) be the minimum number of nodes in an AVL tree of height h.
To minimize nodes for height h, you want children heights to be as small as allowed while still making height h:
So:
N(h) = 1 + N(h − 1) + N(h − 2)
With base cases:
This is Fibonacci-like growth, implying N(h) grows exponentially in h, so h grows logarithmically in n.
A standard bound is:
h ≤ 1.44 · log₂(n + 2) − 1
The exact constant isn’t the key takeaway; the takeaway is: AVL forces h = O(log n) via a stricter local rule.
| Feature | AVL | Red-Black | ||
|---|---|---|---|---|
| Local invariant | bf | ≤ 1 | color rules + equal black-height | |
| Height bound | tighter (shorter trees) | looser (slightly taller) | ||
| Search performance | typically slightly better | typically slightly worse | ||
| Insert/delete fix-up | may do more rotations/height updates | often fewer rotations; recoloring common | ||
| Typical use | read-heavy workloads, indexing | general-purpose maps/sets in many libs |
Both are excellent. The right choice is often about engineering tradeoffs, not asymptotic complexity.
Balanced trees show up whenever you need an ordered collection with predictable performance.
Balanced trees implement:
But unlike hash tables, they also support order-aware operations efficiently:
Because height is O(log n):
Hash tables are great for average-case O(1) lookups, but:
Balanced trees give a clean, deterministic guarantee.
Rotations aren’t only for BSTs. The broader pattern is:
You’ll see this in other data structures (e.g., B-trees have splits/merges; heaps have sift-up/down).
If a balanced tree maintains height h = O(log n), then:
The “magic” is that the balancing work per operation is bounded by a constant number of rotations per violated node, and the number of visited nodes is proportional to the height.
In other words:
Time(operation) = O(height) = O(log n)
That guarantee is exactly why balanced trees are still a core part of CS—even in a world full of hash tables.
Insert keys in this order into an AVL tree: 30, 20, 10. Show how the AVL invariant is restored.
Start with empty tree.
Insert 30:
Insert 20 (BST rule: 20 < 30 so it goes left of 30):
30
/
20
Heights:
Balance factors:
Insert 10 (10 < 30, 10 < 20 → left of 20):
30
/
20
/
10
Heights bottom-up:
Balance factors:
Classify the imbalance at node 30:
This is the LL case.
Fix with a right rotation at 30.
Before (local):
30
/
20
/
10
After right rotation:
20
/ \
10 30
Update heights:
Balance factors:
AVL invariant restored.
Insight: A single rotation fixed a global-looking problem (the tree got tall) by repairing the first node whose local balance factor became ±2. That’s the core pattern of AVL rebalancing.
Insert keys: 30, 10, 20 into an AVL tree. Show why a single rotation is not enough and how the LR double rotation works.
Insert 30:
Insert 10 (goes left of 30):
30
/
10
bf(30) = +1 (OK)
Insert 20:
Tree:
30
/
10
\
20
Compute heights:
Balance factors:
Classify imbalance at 30:
This is the LR case.
Attempting a single right rotation at 30 would produce:
10
\
30
/
20
This still has imbalance because 30 becomes left-heavy (a zig-zag shape).
Correct fix: double rotation
Step 1: left rotate at node 10:
Before:
10
\
20
After:
20
/
10
Step 2: right rotate at node 30 using new left child 20:
Before (local):
30
/
20
/
10
After:
20
/ \
10 30
Update heights:
All balance factors are 0, AVL invariant holds.
Insight: Double rotations resolve a zig-zag by first straightening the child subtree, then rotating the parent. Thinking in shapes (straight vs zig-zag) makes the cases easier to remember than memorizing LL/LR/RL/RR.
Suppose a Red-Black tree has black-height bh = 4 at the root. Bound its height h and lower-bound the number of internal nodes n.
Use the Red-Black property: no red node can have a red child.
Therefore along any root→leaf path, red nodes can only appear between black nodes.
So the maximum path length happens when you alternate colors:
black, red, black, red, ...
If the black-height is bh = 4, then there are 4 black nodes on each root→leaf path (by definition of black-height).
Between consecutive black nodes, there can be at most 1 red node.
So the number of red nodes on a longest path is at most 4 (or 3 depending on conventions), but the standard safe inequality is:
h ≤ 2 · bh
Plug in bh = 4:
h ≤ 2 · 4 = 8
Now lower-bound n from black-height.
A subtree of black-height bh has at least 2ᵇʰ − 1 internal nodes.
Reason: the minimum-node configuration is when all nodes are black and perfectly balanced, giving a perfect binary tree with bh levels of black nodes.
Compute:
2ᵇʰ − 1 = 2⁴ − 1 = 16 − 1 = 15
So n ≥ 15
Insight: Red-Black trees control height indirectly: black-height forces exponential growth in nodes as bh increases, and the no-consecutive-red rule ensures height is at most twice bh.
Balanced trees prevent BST degeneration by enforcing a local invariant at every node.
A rotation is a constant-sized transformation that preserves BST in-order order while changing local heights.
AVL trees use balance factor bf = height(left) − height(right) and require bf ∈ {−1, 0, +1}.
Red-Black trees use color rules and equal black-height to enforce logarithmic height with fewer strict constraints.
Local invariants imply global bounds: AVL height is tightly O(log n); Red-Black height satisfies h ≤ 2 · log₂(n + 1).
Search/insert/delete time in these trees is O(height) ⇒ guaranteed O(log n).
AVL often gives slightly faster lookups; Red-Black often gives simpler/faster updates in practice.
The core balancing workflow is: BST operation → detect violated local invariant → repair with rotations (and recoloring for RB).
Thinking a rotation “sorts” nodes: rotations don’t change in-order order; they only reshape pointers locally.
Forgetting to update heights (AVL) or parent pointers after rotations, causing later invariant checks to be wrong.
Mixing up zig-zag vs straight cases (LR/RL vs LL/RR), leading to the wrong rotation sequence.
Assuming Red-Black trees are “almost perfectly balanced”; they can be noticeably less strict than AVL while still being O(log n).
AVL practice: Insert 10, 20, 30, 25 into an AVL tree (in that order). Draw the tree after each insertion and show which rotation(s) occur.
Hint: After inserting 30 you should see an RR imbalance at 10. After inserting 25, check balance factors on the path from 25 back to the root; look for a zig-zag.
After 10,20,30:
Tree becomes:
20
/ \
10 30
Insert 25:
Tree:
20
/ \
10 30
/
25
Check bf:
No rotation needed.
Rotation reasoning: Prove (by listing in-order sequences) that a right rotation preserves the BST in-order order for the involved nodes/subtrees.
Hint: Name the local parts: y as root, x = y.left, and β = x.right. Write in-order before and after using (subtree) placeholders.
Before right rotation at y:
In-order is: (x.left), x, (β), y, (y.right).
After rotation, x becomes root, y becomes x.right, β becomes y.left.
In-order is: (x.left), x, (β), y, (y.right).
The sequences match, so BST order is preserved.
Red-Black bound: If a Red-Black tree has n = 255 internal nodes, show that its height h is at most 16 using h ≤ 2 · log₂(n + 1).
Hint: Compute log₂(n + 1) with n + 1 = 256.
n + 1 = 256.
log₂(256) = 8.
So h ≤ 2 · log₂(n + 1) = 2 · 8 = 16.
Prerequisites: Binary Search Trees, Big O Notation
Next natural nodes: Heaps / Priority Queues, Hash Tables, B-Trees, Order Statistics Trees