The Global BST Invariant, Successor Geometry & Hibbard's Three Deletion Cases
Notice a critical distinction between the BST property and the Heap property: the BST property is a global subtree invariant, not merely a local parent-child condition. A node $y$ inside $x$'s left-right grandchild slot must satisfy $x.\text{left}.\text{key} < y.\text{key} < x.\text{key}$; merely checking parent vs. child locally fails to validate a BST.
Finding the In-Order Successor of a node $x$ splits into two structural cases in $O(h)$ time:
- $x.\text{right} \ne \text{null}$: The successor is $\text{minimum}(x.\text{right})$, found by walking left pointers from $x.\text{right}$ until
left == null.Crucially, this successor node $y$ is guaranteed to have $y.\text{left} == \text{null}$. - $x.\text{right} == \text{null}$: The successor is the lowest ancestor of $x$ whose left subtree contains $x$ (found by walking up
parentpointers while coming from a right child, or tracking the last left-turn ancestor from the root).
Hibbard Deletion (treeDelete(T, z)): To delete node $z$, CLRS uses a helper transplant(T, u, v) that replaces subtree $u$ with subtree $v$ in $O(1)$ pointer updates:
- Case 1 ($z.\text{left} == \text{null}$): Replace $z$ directly with $z.\text{right}$ (covers both 0-child leaf and right-only child).
- Case 2 ($z.\text{right} == \text{null}$): Replace $z$ directly with $z.\text{left}$.
- Case 3 ($z$ has two children): Find $z$'s successor $y = \text{minimum}(z.\text{right})$. Because $y$ is the minimum of $z.\text{right}$, $y$ has no left child ($y.\text{left} == \text{null}$). If $y$ is not $z$'s immediate right child ($y.\text{parent} \ne z$), first transplant $y$ with $y.\text{right}$ and attach $z.\text{right}$ to $y.\text{right}$. Finally, transplant $z$ with $y$ and attach $z.\text{left}$ to $y.\text{left}$.
| BST Operation | Balanced Tree ($h = \Theta(\log N)$) | After $O(N^2)$ Asymmetric Hibbard Deletes | Adversarial Sorted Input |
|---|---|---|---|
search(k) / insert(k) |
O(log N) | Θ(√N) Expected | O(N) |
successor(x) / predecessor(x) |
O(log N) Worst / O(1) Amortized | Θ(√N) Expected | O(N) |
delete(z) (Hibbard / Symmetric) |
O(log N) | Θ(√N) (Hibbard) | O(N) |
Eppinger / Culberson–Munro Theorem: Why Asymmetric Hibbard Deletion Degrades to $\Theta(\sqrt{N})$
Hibbard (1962) proved that deleting a random element from a randomly built BST yields a tree whose key permutations look random, leading computer scientists to assume for 13 years that expected depth remained $O(\log N)$. In 1975, Jonassen and Knuth proved via exact Markov chain analysis on $N=3$ that alternating random insertions and Hibbard deletions breaks the random BST distribution.
Why? In Case 3, unconditionally pulling the replacement node $y$ from the right subtree ($y = \min(z.\text{right})$) systematically empties right subtrees and lengthens left subtrees. Culberson and Munro (1989) proved that after $\Omega(N^2)$ random insert/delete pairs, the expected internal path length degrades from $1.386 N \log_2 N$ to $\Theta(N^{3/2})$, meaning expected search depth becomes $\Theta(\sqrt{N})$. Randomly alternating between the in-order successor and in-order predecessor restores empirical $O(\log N)$ behavior, though a formal proof of symmetric randomized Hibbard deletion remains one of the longest-standing open problems in theoretical computer science!
Visualizing Case 3 Hibbard Transplant ($y.\text{parent} \ne z$)
Step through Hibbard’s 3-case BST deletion (splicing out the in-order successor y = min(z.right) when deleting a 2-child node z), or inspect the exact bijection between Random BST depth and QuickSort pivot comparisons.
Invariant & State
Hardware Reality: Pointer Transplant vs Key Copy
Why does CLRS implement transplant by rewiring pointers to physically move node $y$ into $z$'s position rather than simply copying z->key = y->key and deleting $y$? In production C++ containers like std::map and intrusive kernel trees, iterator and pointer stability is part of the API contract: external callers hold direct raw pointers or iterators to node $y$, and mutating z->key while freeing node $y$ would invalidate every external pointer to $y$!
Implementation: Pointer-Stable CLRS Transplant & Symmetric Hibbard Delete
// C++20: Pointer-Stable CLRS BST Transplant & Deletion (Zero Iterator Invalidation)
struct BSTNode {
int key;
BSTNode *left = nullptr, *right = nullptr, *parent = nullptr;
};
void transplant(BSTNode*& root, BSTNode* u, BSTNode* v) {
if (u->parent == nullptr) {
root = v;
} else if (u == u->parent->left) {
u->parent->left = v;
} else {
u->parent->right = v;
}
if (v != nullptr) {
v->parent = u->parent;
}
}
BSTNode* treeMinimum(BSTNode* x) {
while (x != nullptr && x->left != nullptr) x = x->left;
return x;
}
void treeDelete(BSTNode*& root, BSTNode* z) {
if (z->left == nullptr) {
transplant(root, z, z->right);
} else if (z->right == nullptr) {
transplant(root, z, z->left);
} else {
BSTNode* y = treeMinimum(z->right); // Guaranteed: y->left == nullptr
if (y->parent != z) {
transplant(root, y, y->right);
y->right = z->right;
y->right->parent = y;
}
transplant(root, z, y);
y->left = z->left;
y->left->parent = y;
}
delete z;
}
Failure Modes & Adversarial Pitfalls
- Local-Only BST Validation Bug: Writing
isValidBST(u)by checking onlyu->left->key < u->key && u->right->key > u->keyis broken! A root10with left child5whose right child is12passes local parent-child checks everywhere while violating the global BST invariant ($12 > 10$ inside $10$'s left subtree). Always propagate open range bounds(minKey, maxKey)down the recursion. - Unbalanced BST Degeneration on Pre-Sorted Streams: Inserting chronological timestamps or auto-incrementing database IDs into an unbalanced BST produces a right-leaning linked list of height $h = N - 1$, turning $O(\log N)$ lookups into $O(N)$ scans.