The Four Classical Red-Black Invariants & The 2-3-4 B-Tree Bijection
In a standard Red-Black Tree (CLRS Chapter 13), every node (or incoming parent link) is colored either Red or Black subject to four invariants:
- Root Rule: The root is always Black.
- External Leaf Rule: Every
nullleaf sentinel is Black. - Red Rule (No Red-Red Parent-Child): If a node is Red, both of its children are Black.
- Black-Height Rule (Perfect 2-3-4 Balance): Every simple path from any node $u$ down to any descendant
nullleaf contains the exact same number of Black nodes, denoted $\text{bh}(u)$ (the black-height).
Sedgewick's Left-Leaning (LLRB 2-3) Simplification: If we forbid 4-nodes from persisting at rest and force every 3-node's red glue edge to lean left, then right-leaning red links are illegal and two consecutive left-leaning red links represent a temporary 4-node awaiting a split (flipColors). Every LLRB insertion ascending the recursion stack runs the exact same 3 if-statements:
if (isRed(h.right) && !isRed(h.left)) h = rotateLeft(h);— Lean a right-leaning 3-node to the left.if (isRed(h.left) && isRed(h.left.left)) h = rotateRight(h);— Balance a skewed temporary 4-node so its two children are both red.if (isRed(h.left) && isRed(h.right)) flipColors(h);— Split the temporary 4-node into two 2-nodes and pass the middle key up to the parent as a red link!
| Metric / Operation | Red-Black Tree (Standard 2-3-4) | LLRB Tree (2-3 Isomorphism) | AVL Tree Comparison |
|---|---|---|---|
| Worst-Case Height $h(T)$ | ≤ 2 log₂(N + 1) | ≤ 2 log₂(N + 1) | AVL is tighter: $\le 1.44 \log_2(N+2)$ |
insert(k) Rotations |
≤ 2 Rotations Worst-Case | O(log N) Worst / O(1) Avg | AVL: $\le 2$ rotations worst-case |
delete(k) Rotations |
≤ 3 Rotations Worst-Case! | O(log N) | AVL: $O(\log N)$ rotations |
| Fixup Complexity (Amortized) | O(1) Recolors + Rotations | O(1) Amortized Splits | Standard RB wins on write-heavy workloads |
Lemma: Why a Red-Black Tree of $N$ Internal Nodes Has Height $h \le 2 \log_2(N + 1)$
Step 1 (Subtree Size Lower Bound via Black-Height): By induction on black-height $\text{bh}(x)$, the subtree rooted at any node $x$ contains at least $2^{\text{bh}(x)} - 1$ internal nodes. Base case: if $\text{bh}(x) = 0$, $x$ is a null leaf with $2^0 - 1 = 0$ internal nodes. Inductive step: for an internal node $x$, each child $c$ has black-height either $\text{bh}(x)$ (if $c$ is Red) or $\text{bh}(x) - 1$ (if $c$ is Black). By the Inductive Hypothesis, each child subtree has at least $2^{\text{bh}(x)-1} - 1$ nodes, so:
$$\text{size}(x) \ge 1 + 2\left(2^{\text{bh}(x)-1} - 1\right) = 2^{\text{bh}(x)} - 1$$
Step 2 (Relating $h$ to $\text{bh}(\text{root})$): By the Red Rule (no two consecutive Red nodes on any path), at least half of the nodes along any root-to-leaf path of length $h$ must be Black: $\text{bh}(\text{root}) \ge h / 2$. Substituting into Step 1:
$$N \ge 2^{\text{bh}(\text{root})} - 1 \ge 2^{h/2} - 1 \implies 2^{h/2} \le N + 1 \implies h \le 2 \log_2(N + 1)$$
Trace how AVL LL/LR/RR/RL rotations and Sedgewick Left-Leaning Red-Black (LLRB) local transformations (rotateLeft, rotateRight, flipColors) preserve in-order sequence and subtree sizes while bounding tree height.
Invariant & State
Hardware Reality: Why std::map, Java TreeMap, and Linux rbtree Choose Red-Black Over AVL
If AVL trees are shorter ($1.44 \log_2 N$ vs $2.00 \log_2 N$), why do C++ std::map, Java TreeMap, and the Linux kernel use standard Red-Black Trees instead? Because in a standard Red-Black Tree, insertion requires at most 2 rotations and deletion requires at most 3 rotations in the worst case (compared to $O(\log N)$ rotations for AVL deletion!). In concurrent or persistent environments where each rotation modifies cache lines or copies nodes along the spine, bounding structural pointer mutations to $O(1)$ worst-case rotations is decisive.
Implementation: Complete Sedgewick LLRB Insertion Engine
// C++20: Sedgewick Left-Leaning Red-Black (LLRB) Tree Insertion in 45 Lines
struct LLRBNode {
int key;
bool red = true; // New nodes are always inserted with a RED incoming link
LLRBNode *left = nullptr, *right = nullptr;
explicit LLRBNode(int k) : key(k) {}
};
inline bool isRed(const LLRBNode* x) noexcept { return x != nullptr && x->red; }
LLRBNode* rotateLeft(LLRBNode* h) noexcept {
LLRBNode* x = h->right;
h->right = x->left;
x->left = h;
x->red = h->red;
h->red = true;
return x;
}
LLRBNode* rotateRight(LLRBNode* h) noexcept {
LLRBNode* x = h->left;
h->left = x->right;
x->right = h;
x->red = h->red;
h->red = true;
return x;
}
void flipColors(LLRBNode* h) noexcept {
h->red = !h->red;
h->left->red = !h->left->red;
h->right->red = !h->right->red;
}
LLRBNode* insertNode(LLRBNode* h, int key) {
if (!h) return new LLRBNode(key);
if (key < h->key) h->left = insertNode(h->left, key);
else if (key > h->key) h->right = insertNode(h->right, key);
// The 3-line LLRB invariant restoration pipeline:
if (isRed(h->right) && !isRed(h->left)) h = rotateLeft(h);
if (isRed(h->left) && isRed(h->left->left)) h = rotateRight(h);
if (isRed(h->left) && isRed(h->right)) flipColors(h);
return h;
}
Failure Modes & Adversarial Pitfalls
- Forgetting to Force
root->red = falseAfter Insertion: WhenflipColorssplits the root of the entire tree,root->redflips totrue(which corresponds to growing the 2-3 tree upward by 1 black level). Always setroot->red = falseat the top-levelinsert(key)wrapper! - LLRB Deletion Asymmetry vs Standard RB Deletion: While LLRB insertion is 15 lines of code, LLRB deletion (
moveRedLeft/moveRedRight) must push red links downward top-down to guarantee the target leaf is not a 2-node, performing $O(\log N)$ rotations on the way down and up, whereas classical CLRS Red-Black deletion performs at most 3 rotations.