Red-Black Trees (RBT)
The 5 fundamental RBT invariants, black-height mathematical proof, 3-case insertion recoloring/rotations, and 4-case deletion handling.
Red-Black Trees relax the rigid geometric balance of AVL trees by encoding structural balance into node color bits, bounding maximum height to . This compromise guarantees logarithmic searches while constraining worst-case insertion rebalancing to at most two rotations and deletion rebalancing to at most three.
1. Executive Summary & Learning Objectives
#Invented by Rudolf Bayer in 1972 as Symmetric Binary B-Trees and formalized by Leo Guibas and Robert Sedgewick in 1978, a Red-Black Tree is a self-balancing binary search tree that uses one additional bit of storage per node—its color (RED or BLACK)—to ensure that no simple path from the root to a leaf is more than twice as long as any other.
By the end of this chapter, you will be able to:
- Formulate the five foundational Red-Black Tree invariants and compute the black-height of any arbitrary node.
- Reproduce the inductive proof demonstrating that a red-black tree with internal nodes has height .
- Execute the three canonical insertion fixup cases, correctly distinguishing recoloring operations from parent-rotation and grandparent-rotation rebalancing.
- Dissect the four double-black deletion fixup cases and trace how extra blackness is absorbed or rotated out.
- Evaluate trade-offs between Red-Black trees and AVL trees for read-heavy versus write-heavy systems (e.g., Linux CFS scheduler, C++
std::map).
2. The 5 Structural Red-Black Tree Invariants
#Every valid Red-Black Tree must strictly satisfy all five properties at all times:
| Invariant Number | Invariant Name | Formal Specification | Architectural Purpose |
|---|---|---|---|
| 1 | Node Color Property | Every node is colored either RED or BLACK. | Binary classification used to maintain balance. |
| 2 | Root Property | The root node is always BLACK. | Serves as an invariant baseline for black-height. |
| 3 | Leaf Property | Every external leaf (NIL sentinel node) is BLACK. | Standardizes path termination with zero black-height contribution. |
| 4 | Red Property | If a node is RED, both of its children must be BLACK. | Prohibits consecutive RED nodes; bounds path length variance. |
| 5 | Black-Height Property | For every node , all simple paths from to descendant leaves contain the exact same number of BLACK nodes (). | Enforces global structural balance across all subtrees. |
Canonical Red-Black Tree Layout
The table below illustrates a valid Red-Black Tree containing keys with black-height :
| Key | Node Color | Parent | Left Child | Right Child | Black-Height | Verification Path to Leaves |
|---|---|---|---|---|---|---|
| 20 | BLACK | null | 10 | 30 | Root node (Black-Height baseline: 2) | |
| 10 | RED | 20 | 5 | 15 | Left child of root (Children must be Black) | |
| 30 | BLACK | 20 | NIL | 40 | Right child of root | |
| 5 | BLACK | 10 | NIL | NIL | Leaf child (Path to NIL: 1 black node) | |
| 15 | BLACK | 10 | NIL | NIL | Leaf child (Path to NIL: 1 black node) | |
| 40 | RED | 30 | NIL | NIL | Leaf child (Path to NIL: 0 additional black nodes) |
Path Verification:
- Path : Black nodes = (Count = 2).
- Path : Black nodes = (Count = 2).
- Path : Black nodes = (Count = 2).
- Path : Black nodes = (Count = 2). All paths encounter exactly 2 black nodes! Invariant 5 is globally satisfied.
3. Mathematical Proof of Height Bound
#Theorem
#A Red-Black Tree with internal nodes has height at most:
Formal Proof in Three Steps
#Step 1: Subtree Size Lemma
Claim: The subtree rooted at any node contains at least internal nodes.
Proof by Structural Induction on the height of :
- Base Case: If height , is an external sentinel leaf (
NIL). Its black-height is , and internal nodes . The base case holds. - Inductive Step: Consider an internal node with height and two children .
- If child is
RED, (since the child's red color does not augment black-height). - If child is
BLACK, . - Therefore, in both scenarios: .
- By inductive hypothesis, each child's subtree contains at least internal nodes.
- Summing the subtrees and counting internal node itself:
- If child is
The lemma is proven for all internal nodes.
Step 2: Linking Black-Height to Tree Height
According to Invariant 4 (Red Property), no two RED nodes can appear consecutively on any simple path from the root to a leaf. Therefore, on any simple path from the root to a leaf, at least half of the nodes (excluding the root itself) must be BLACK. Consequently:
Step 3: Combining the Inequalities
Let be the number of internal nodes in the entire tree:
Taking the base-2 logarithm of both sides:
4. Insertion Mechanics & The 3 Uncle Fixup Cases
#Insertion Strategy
#- Perform standard Binary Search Tree insertion to insert the new node at a leaf position.
- Color the new node RED:
- Coloring
REDpreserves Invariant 5 (Black-Height) across all paths. - If is the root, recolor it
BLACKto satisfy Invariant 2. - If 's parent is
BLACK, all invariants hold; the algorithm terminates immediately.
- Coloring
- If 's parent is
RED, Invariant 4 is violated (Double Red). We invokeInsertFixup(z).
The 3 Canonical Uncle Cases (Parent is Left Child of Grandparent)
#Let be the newly inserted node, its parent, its grandparent, and its uncle (sibling of , i.e., ):
| Case | Geometric Condition | Uncle Color | Surgical Action | Post-Action Status |
|---|---|---|---|---|
| Case 1: Red Uncle | 's uncle is RED | RED | Color Flip: Recolor parent , uncle , grandparent . | Advance ; repeat loop upward. |
| Case 2: Triangle (Inside Child) | 's uncle is BLACK, is a right child () | BLACK | Rotate Parent: Perform LeftRotate(p). Advance . | Transforms immediately into a straight line (Case 3). |
| Case 3: Line (Outside Child) | 's uncle is BLACK, is a left child () | BLACK | Rotate Grandparent & Swap Colors: Recolor , . Perform RightRotate(g). | All invariants satisfied; TERMINATE. |
Step-by-Step Structural Transformation Matrix
#The table below illustrates the pointer and color mutations across the 3 insertion fixup cases:
| Case | Configuration Before Fixup | Primary Transformation | Configuration After Fixup |
|---|---|---|---|
| Case 1: Color Flip | Grandparent has two red children and . Node is child of . | Recolor , , . | Grandparent becomes the active node . No rotations needed. Invariant 5 preserved. |
| Case 2: Triangle to Line | Grandparent , parent (left child of ), node (right child of ), uncle . | Execute LeftRotate(p). | Node becomes parent of . Both are in a straight left-child line beneath . |
| Case 3: Line Rotation | Grandparent , parent (left child of ), node (left child of ), uncle . | Recolor , . Execute RightRotate(g). | Node becomes subtree root with left child and right child . Fully balanced! |
(Note: If parent is the right child of grandparent , symmetric mirror cases apply with Left and Right swapped).
5. Complete Implementation: Insertion Fixup
#export enum Color {
RED,
BLACK,
}
export class RBNode<T> {
key: T;
color: Color = Color.RED;
left: RBNode<T> | null = null;
right: RBNode<T> | null = null;
parent: RBNode<T> | null = null;
constructor(key: T) {
this.key = key;
}
}
export class RedBlackTree<T> {
root: RBNode<T> | null = null;
private leftRotate(x: RBNode<T>): void {
const y = x.right!;
x.right = y.left;
if (y.left !== null) y.left.parent = x;
y.parent = x.parent;
if (x.parent === null) {
this.root = y;
} else if (x === x.parent.left) {
x.parent.left = y;
} else {
x.parent.right = y;
}
y.left = x;
x.parent = y;
}
private rightRotate(y: RBNode<T>): void {
const x = y.left!;
y.left = x.right;
if (x.right !== null) x.right.parent = y;
x.parent = y.parent;
if (y.parent === null) {
this.root = x;
} else if (y === y.parent.left) {
y.parent.left = x;
} else {
y.parent.right = x;
}
x.right = y;
y.parent = x;
}
public insert(key: T): void {
const z = new RBNode(key);
let y: RBNode<T> | null = null;
let x = this.root;
while (x !== null) {
y = x;
if (z.key < x.key) {
x = x.left;
} else if (z.key > x.key) {
x = x.right;
} else {
return; // Duplicate key: ignore
}
}
z.parent = y;
if (y === null) {
this.root = z;
} else if (z.key < y.key) {
y.left = z;
} else {
y.right = z;
}
z.color = Color.RED;
this.insertFixup(z);
}
private insertFixup(z: RBNode<T>): void {
while (z.parent !== null && z.parent.color === Color.RED) {
if (z.parent === z.parent.parent?.left) {
const uncle = z.parent.parent.right;
// CASE 1: Uncle is RED -> Color Flip
if (uncle !== null && uncle.color === Color.RED) {
z.parent.color = Color.BLACK;
uncle.color = Color.BLACK;
z.parent.parent.color = Color.RED;
z = z.parent.parent;
} else {
// CASE 2: Uncle is BLACK & z is Right child -> Rotate Parent
if (z === z.parent.right) {
z = z.parent;
this.leftRotate(z);
}
// CASE 3: Uncle is BLACK & z is Left child -> Rotate Grandparent & Recolor
z.parent!.color = Color.BLACK;
z.parent!.parent!.color = Color.RED;
this.rightRotate(z.parent!.parent!);
}
} else {
// Symmetric mirror cases
const uncle = z.parent.parent?.left ?? null;
if (uncle !== null && uncle.color === Color.RED) {
z.parent.color = Color.BLACK;
uncle.color = Color.BLACK;
z.parent.parent!.color = Color.RED;
z = z.parent.parent!;
} else {
if (z === z.parent.left) {
z = z.parent;
this.rightRotate(z);
}
z.parent!.color = Color.BLACK;
z.parent!.parent!.color = Color.RED;
this.leftRotate(z.parent!.parent!);
}
}
}
this.root!.color = Color.BLACK;
}
}6. Step-by-Step Dry Run State Trace Table
#Consider sequentially inserting keys into an initially empty Red-Black Tree.
| Step | Operation | Active Node | Tree State Before Fixup | Triggered Condition | Fixup Actions Performed | Final Subtree Colors |
|---|---|---|---|---|---|---|
| 1 | Insert | Single node | is root | Invariant 2 enforcement: recolor root . | ||
| 2 | Insert | Parent is BLACK | No violation! Invariants hold immediately. | |||
| 3 | Insert | Parent is RED, Uncle is NIL (BLACK) | Case 3 (Mirror): Recolor . Left-Rotate around grandparent . | (new root), , | ||
| 4 | Insert | Attaches as right child of | Double Red ( and ). Uncle is RED! | Case 1: Color Flip: Recolor . Root recolored BLACK. | (root), |
7. Deletion Mechanics & The 4 Double-Black Fixup Cases
#When deleting an internal node , it is replaced by its successor . Splicing out node with replacement child reduces the case to removing a node with at most one child:
- If was
RED: No black-height property is violated. All invariants remain valid! - If was
BLACK: The path passing through is now short by 1 black node. We conceptually assign an extra unit of blackness to , making Double-Black.
Let be the Double-Black node, its parent, and its sibling:
| Case | Condition at Sibling | Rebalancing Action | Outcome / Transformation |
|---|---|---|---|
| Case 1 | Sibling is RED | Left-rotate parent . Recolor , . | Converts to Case 2, 3, or 4 where sibling is BLACK. |
| Case 2 | Sibling is BLACK, and both of 's children are BLACK | Recolor . Absorb extra black into parent . | Parent becomes Double-Black. Advance and propagate upward. |
| Case 3 | Sibling is BLACK, inner child is RED, outer child is BLACK | Right-rotate sibling away from inner child. Swap colors of and inner child. | Transforms immediately into Case 4 with an outer RED child. |
| Case 4 | Sibling is BLACK, outer child is RED | Left-rotate parent . Sibling takes 's color; recolor and outer child BLACK. | Double-black is completely eliminated! Algorithm terminates. |
8. Red-Black Tree vs. AVL Tree Trade-Off Matrix
#| Metric / Dimension | AVL Tree | Red-Black Tree |
|---|---|---|
| Balance Invariant | ||
| Strict Height Bound | ||
| Lookup Speed | faster (due to shallower tree) | Slightly slower (longer search paths) |
| Insertion Rotations | rotations | rotations |
| Deletion Rotations | rotations (can cascade to root) | rotations strictly guaranteed |
| Memory Overhead | 2 bits (balance factor) or 1 integer | 1 bit (color: RED/BLACK) |
| Primary Industry Adoption | Search-heavy index systems, static dictionaries | Linux kernel (rbtree.c), C++ std::map, Java TreeMap |
9. Common Traps, Edge Cases & Implementation Pitfalls
#- Failure to Recolor the Root:
- Color flip propagation (Case 1) can push a
REDcolor all the way to the tree root. The fixup routine must unconditionally resetroot.color = BLACKupon loop termination.
- Color flip propagation (Case 1) can push a
- Sentinel Node Parent Pointer Corruption:
- In implementations using a shared global
NILsentinel node, mutations toNIL.parentby rotation logic can corrupt subsequent sentinel queries. Using a dedicated sentinel object with immutable null pointers prevents this fault.
- In implementations using a shared global
- Triangle vs. Line Child Identification:
- Incorrectly matching Case 2 instead of Case 3 occurs when testing 's relationship to its grandparent rather than its parent. Ensure is checked against .
10. Real-World Applications & Practice Problems
#Production Systems
#- Linux Completely Fair Scheduler (CFS): Linux tracks runnable tasks ordered by virtual runtime (
vruntime) using an augmented Red-Black tree (linux/rbtree.h), enabling selection of the minimum runtime process. - Memory Allocators (jemalloc): Uses Red-Black trees to track free memory chunks indexed by address and size for rapid buddy-allocation coalescing.
Standard Practice Problems
#- Red-Black Tree Insertion Fixup — Implement
insertFixupwith full uncle classification and rotations. - Double-Black Deletion Fixup — Implement
deleteFixupcovering all 4 sibling color permutations.
11. References & Academic Attribution
#- Bayer, R. (1972). Symmetric binary B-Trees: Data structure and maintenance algorithms. Acta Informatica, 1(4), 290–306.
- Guibas, L. J., & Sedgewick, R. (1978). A dichromatic framework for balanced trees. 19th Annual Symposium on Foundations of Computer Science (SFCS), 8–21. IEEE.
- Cormen, T. H., Leiserson, C. E., Rivest, R. L., & Stein, C. (2022). Introduction to Algorithms (4th ed.), Chapter 13. MIT Press.