A BST offers O(log n) search, insert, and delete — but only when the tree is balanced. The height determines the worst-case cost of every operation.
O(log n), operations are fastO(n), operations degrade to a linked list| Scenario | Height | Search cost |
|---|---|---|
| Perfectly balanced (n = 1M) | ~20 | 20 comparisons |
| Random insertion (n = 1M) | ~40 | 40 comparisons |
| Sorted insertion (n = 1M) | 1,000,000 | 1M comparisons |
Key insight: self-balancing trees pay a small rebalancing cost per mutation to guarantee O(log n) height at all times. The amortised overhead is negligible compared to the worst-case degenerate scenario.
For every node x: all keys in the left subtree are less than x.key, and all keys in the right subtree are greater.
Balanced BST: Degenerate BST:
50 10
/ \ \
30 70 20
/ \ / \ \
20 40 60 80 30
\
40
\
50
Inserting [10, 20, 30, 40, 50] into a naive BST produces a right-skewed chain. Every operation becomes O(n).
Self-balancing trees solve this by restructuring after each mutation. The mechanisms differ — rotations, colour flips, node splits — but the goal is always the same: keep h = O(log n).
An AVL tree (Adelson-Velsky & Landis, 1962) is a BST where, for every node, the heights of the left and right subtrees differ by at most 1.
BF(node) = height(left subtree) - height(right subtree)
| Balance factor | Meaning |
|---|---|
BF = -1 | Right-heavy by one level — acceptable |
BF = 0 | Perfectly balanced at this node |
BF = +1 | Left-heavy by one level — acceptable |
BF = -2 or +2 | Violation — requires rotation |
An AVL tree with n nodes has height at most 1.44 × log2(n). Tighter than Red-Black trees, making AVL faster for lookup-heavy workloads.
AVL trees are strictly balanced — they tolerate less imbalance than Red-Black trees. This means more rotations on insert/delete, but faster searches.
When the imbalance is on the outer edge (left-left or right-right), a single rotation restores balance.
BF=+2 BF=0
z y
/ \ rotate / \
y T4 right(z) x z
/ \ --------> / \ / \
x T3 T1 T2 T3 T4
/ \
T1 T2
Node y becomes the new root. z becomes y's right child. T3 moves from y's right to z's left.
BF=-2 BF=0
z y
/ \ rotate / \
T1 y left(z) z x
/ \ --------> / \ / \
T2 x T1 T2 T3 T4
/ \
T3 T4
Mirror of LL. Node y becomes root. z becomes y's left child.
Single rotations run in O(1) — they reassign at most three pointers and update two heights.
When the imbalance is on the inner edge (left-right or right-left), a double rotation is required.
Step 1: Left rotate y Step 2: Right rotate z
z z x
/ \ / \ / \
y T4 -> x T4 -> y z
/ \ / \ / \ / \
T1 x y T3 T1 T2 T3 T4
/ \ / \
T2 T3 T1 T2
First rotate left at y, then rotate right at z.
Step 1: Right rotate y Step 2: Left rotate z
z z x
/ \ / \ / \
T1 y -> T1 x -> z y
/ \ / \ / \ / \
x T4 T2 y T1 T2 T3 T4
/ \ / \
T2 T3 T3 T4
Mirror of LR. First rotate right at y, then rotate left at z.
How to pick the rotation: check the balance factor of the violating node and its heavy child. Same sign → single rotation. Different signs → double rotation.
|BF| = 2, perform the appropriate rotation|BF| = 2, perform the appropriate rotation| Operation | Average | Worst case |
|---|---|---|
| Search | O(log n) | O(log n) |
| Insert | O(log n) | O(log n) — 1 rotation |
| Delete | O(log n) | O(log n) — up to O(log n) rotations |
| Space | O(n) | O(n) — one height/BF per node |
AVL trees guarantee at most one rotation on insert but may need multiple rotations on delete. This asymmetry is why Red-Black trees are sometimes preferred for write-heavy workloads.
1. Every node is either red or black
2. The root is always black
3. Every leaf (NIL sentinel) is black
4. If a node is red, both its children are black (no consecutive reds)
5. All paths from any node to its NIL leaves contain the same number of black nodes (black-height)
A Red-Black tree with n internal nodes has height at most 2 × log2(n + 1). Looser than AVL's 1.44, but still O(log n).
The black-height of a node is the number of black nodes on any path from that node (exclusive) down to a NIL leaf. Property 5 ensures this is well-defined.
At most 2 rotations per insert, at most 3 rotations per delete. Trade slightly worse search performance for fewer structural changes.
The colour constraints encode balance information implicitly. Property 4 (no consecutive reds) and Property 5 (equal black-height) together force the longest path to be at most twice the shortest.
Every Red-Black tree is isomorphic to a 2-3-4 tree:
| 2-3-4 node | Red-Black equivalent |
|---|---|
| 2-node | Single black node |
| 3-node | Black node with one red child |
| 4-node | Black node with two red children |
New nodes are always inserted as red — this preserves black-height
Red violations (two consecutive reds) are fixed by recolouring or rotations
Black violations only arise during deletion and require more complex fixups
The colour scheme is not arbitrary — it is a compact encoding of the 2-3-4 tree structure into a binary tree. Understanding this mapping makes Red-Black tree operations far more intuitive.
| Case | Uncle colour | Fix |
|---|---|---|
| Case 1 | Uncle is red | Recolour parent & uncle black, grandparent red. Move violation up. Repeat. |
| Case 2 | Uncle is black, inner child | Rotate node to outer position (converts to Case 3) |
| Case 3 | Uncle is black, outer child | Rotate grandparent, swap colours of parent & grandparent. Done. |
At most 2 rotations per insertion (Case 2 + Case 3)
Recolouring (Case 1) may propagate up O(log n) times but involves no rotations
After any rotation, the fix-up terminates immediately
Compare with AVL: AVL does at most 1 rotation on insert but must always walk to the root updating heights. Red-Black insertion can terminate early when no further violations exist.
Deletion is the most complex Red-Black operation. Removing a black node reduces the black-height on that path, creating a "double-black" deficit.
| Case | Sibling | Sibling's children | Fix |
|---|---|---|---|
| 1 | Red | — | Rotate sibling up, recolour. Converts to Case 2/3/4. |
| 2 | Black | Both black | Recolour sibling red, push double-black up. Repeat. |
| 3 | Black | Near child red | Rotate near child up, recolour. Converts to Case 4. |
| 4 | Black | Far child red | Rotate sibling up, transfer colours. Done. |
At most 3 rotations per deletion. Recolouring may propagate O(log n) times (Case 2 bubbling). Deletion case analysis is symmetric for left/right.
| Property | AVL | Red-Black |
|---|---|---|
| Height bound | 1.44 log n | 2 log n |
| Search speed | Faster (shorter height) | Slightly slower |
| Insert rotations | At most 1 | At most 2 |
| Delete rotations | Up to O(log n) | At most 3 |
| Storage overhead | Height or BF per node | 1 bit (colour) per node |
| Implementation complexity | Moderate | Higher |
std::map, Java TreeMap)A 2-3 tree is a perfectly balanced search tree where every internal node has either 2 or 3 children, and all leaves are at the same depth.
| Node type | Keys | Children | Search |
|---|---|---|---|
| 2-node | 1 key | 2 | Left < key < Right |
| 3-node | 2 keys (a, b) | 3 | Left < a, Mid between, Right > b |
O(log n) with base between 2 and 3A 2-3-4 tree adds a 4-node (3 keys, 4 children). This directly corresponds to a Red-Black tree.
| Node type | Keys | Children |
|---|---|---|
| 2-node | 1 | 2 |
| 3-node | 2 | 3 |
| 4-node | 3 | 4 |
2-node: [B] Black node, no red children
3-node: [B]-R Black with one red child
4-node: R-[B]-R Black with two red children
Top-down 2-3-4 insertion maps directly to top-down Red-Black insertion with colour flips.
Sedgewick's Algorithms teaches Red-Black trees entirely through this 2-3-4 mapping. Understanding the isomorphism makes Red-Black rotations intuitive rather than magical.
A Left-Leaning Red-Black (LLRB) tree adds one extra invariant: red links lean left. This eliminates half the cases in standard Red-Black tree operations.
| Operation | Standard RB | LLRB |
|---|---|---|
| Insert cases | 6 (3 + mirror) | 3 |
| Delete cases | 8+ | ~4 |
| Code lines | 150–200 | 40–60 |
rotateLeft(h) // fix right-leaning red link
rotateRight(h) // temporarily create right-leaning red
flipColours(h) // split a 4-node (both children red)
LLRB trees are isomorphic to 2-3 trees (not 2-3-4), because the left-leaning constraint forbids 4-nodes from persisting — they are always split immediately.
LLRB trees are the best choice for teaching and for implementations where code simplicity matters more than raw performance. The ~50-line implementation is remarkably elegant.
An AA tree is a Red-Black tree with an additional constraint: right children cannot be red. Equivalently, only right-leaning "horizontal" links at the same level are allowed.
| Operation | What it does |
|---|---|
| skew | Right rotation to eliminate a left horizontal link |
| split | Left rotation to eliminate two consecutive right horizontal links (and increment level) |
skew and split back upskew and split back upAA trees are arguably the simplest balanced BST to implement correctly. The two-operation approach eliminates the complex case analysis of Red-Black trees.
Scapegoat trees do not maintain any auxiliary information (no heights, no colours, no levels). Instead, they detect and fix imbalance lazily.
log1/α(n), find a scapegoat ancestorsize(child) > α × size(node)| Alpha | Behaviour |
|---|---|
0.5 | Perfectly balanced — rebuild often |
0.75 | Good balance vs rebuild trade-off (common default) |
1.0 | Never rebalance — degenerate BST |
Search: O(log n) worst case
Insert: O(log n) amortised (O(n) worst case for a single rebuild)
Space: O(n) — no extra metadata per node
Weight-balanced trees (BB[α] trees, Nievergelt & Reingold, 1973) maintain balance based on the size of subtrees rather than height.
size(left) >= alpha * size(node)
size(right) >= alpha * size(node)
Typical alpha: 2/11 to 1/4
Bound: alpha <= 1 - 1/sqrt(2) ~ 0.293
rank(x), select(k), range_count(lo, hi) are all O(log n)Data.Map and Data.Set use weight-balanced treesA persistent data structure preserves all previous versions. Instead of modifying in place, each update creates a new path from the modified node to the root.
Version 1: Version 2 (insert 25):
30 30'
/ \ / \
20 40 -> 20' 40 (shared)
/ / \
10 10 25 (new)
Shared: 10, 40 (unchanged)
New: 30', 20', 25
Cost: O(log n) new nodes per update
| Tree type | Suitability |
|---|---|
| Weight-balanced | Excellent — Haskell's default |
| Red-Black | Good — Clojure, Scala |
| AVL | Good — slightly more copying |
| 2-3 trees | Excellent — clean functional style |
| Tree | Height bound | Insert rot. | Delete rot. | Extra storage | Best for |
|---|---|---|---|---|---|
| AVL | 1.44 log n | ≤1 | ≤O(log n) | BF / height | Read-heavy |
| Red-Black | 2 log n | ≤2 | ≤3 | 1 bit colour | General-purpose |
| LLRB | 2 log n | ≤2 | ≤2 | 1 bit colour | Simple impl. |
| AA | 2 log n | ≤1 split | ≤O(log n) | Level integer | Teaching |
| 2-3 | log2 to log3 | Splits up | Merges up | Multi-key | Foundation |
| 2-3-4 | log2 to log4 | Splits up | Merges up | Multi-key | RB mapping |
| Scapegoat | log1/α n | Amort. | Amort. | None | Min. metadata |
| Weight-bal. | O(log n) | ≤1 | ≤1 | Subtree size | Order-stat, FP |
O(log n) search, insert, and deleteO(log n) overhead