The CLRS Augmentation Theorem & Two Canonical Augmented Trees
Why do rotations preserve local subtree summaries in $O(1)$ time? Because a rotation (rotateLeft or rotateRight) on edge $(x, y)$ only changes the descendant sets of two nodes: $x$ and $y$! The entire subtree rooted at the new top node contains the exact same set of nodes as before the rotation, so no ancestor above the rotated pair needs its associative summary recomputed from scratch (though ancestor updates along the $O(\log N)$ insertion/deletion path take $O(\log N)$ total time anyway).
1. Order-Statistic Tree (size[u] Augmentation)
Augment each node $u$ with its subtree size $\text{size}(u) = 1 + \text{size}(u.\text{left}) + \text{size}(u.\text{right})$ (where $\text{size}(\text{null}) = 0$). Then:
select(u, k)(0-indexed $k$-th smallest key): Let $L = \text{size}(u.\text{left})$ be the number of nodes in $u$'s left subtree. If $k == L$, return $u$; if $k < L$, recurse intoselect(u.left, k); if $k > L$, subtract the $L + 1$ skipped elements and recurse intoselect(u.right, k - L - 1)!rank(root, key)(count of keys $< \text{key}$): Walk down from the root; every time we branch right (because $u.\text{key} < \text{key}$), add $\text{size}(u.\text{left}) + 1$ to our running accumulator.
2. Interval Tree (maxEnd[u] Augmentation for 1D Overlap Search)
Suppose we store closed intervals $I_i = [lo_i, hi_i]$ ordered primarily by their low endpoint $lo_i$ as the BST key, and augment each node $u$ with the maximum high endpoint anywhere in $u$'s subtree:
$$\text{maxEnd}(u) = \max\big(u.hi, \; \text{maxEnd}(u.\text{left}), \; \text{maxEnd}(u.\text{right})\big)$$
To find any interval in the tree that overlaps query $Q = [q_{lo}, q_{hi}]$ (recall two closed intervals overlap iff $u.lo \le q_{hi}$ AND $q_{lo} \le u.hi$), we inspect a **single root-to-leaf path** in $O(\log N)$ time:
- If $u$ overlaps $Q$, return $u$.
- If $u.\text{left} \ne \text{null}$ and $\text{maxEnd}(u.\text{left}) \ge q_{lo}$, go left (
u = u.left). - Otherwise, go right (
u = u.right).
| Augmented Structure & Operation | Stored Node Field $f(u)$ | Local Maintenance $g(\cdot)$ | Query / Update Time |
|---|---|---|---|
Order-Statistic select(k) / rank(x) |
size (subtree count) |
$1 + \text{size}(L) + \text{size}(R)$ | O(log N) |
Interval Tree findAnyOverlap(Q) |
maxEnd (subtree $\max hi$) |
$\max(u.hi, \text{maxEnd}(L), \text{maxEnd}(R))$ | O(log N) |
| Subtree Affine / Hash / Monoid Tree | agg (associative monoid) |
$\text{agg}(L) \oplus \text{val}(u) \oplus \text{agg}(R)$ | O(log N) |
Correctness Proof: Why Interval Tree Search Never Needs to Backtrack When Going Left!
Students always ask: "In Step 2, if $\text{maxEnd}(u.\text{left}) \ge q_{lo}$ and we go left, what if the interval in $u.\text{left}$ that achieved $\text{maxEnd} \ge q_{lo}$ has its low endpoint $> q_{hi}$, so the left subtree has NO overlap while the right subtree DOES have an overlap?"
Proof that this is impossible: Let $I^* = [lo^*, hi^*] \in u.\text{left}$ be the interval in $u$'s left subtree that achieves $hi^* = \text{maxEnd}(u.\text{left}) \ge q_{lo}$.
- Case A: If $lo^* \le q_{hi}$, then since we already know $hi^* \ge q_{lo}$, $I^*$ itself overlaps $Q = [q_{lo}, q_{hi}]$! So an overlapping interval genuinely exists in $u.\text{left}$.
- Case B: Suppose $lo^* > q_{hi}$. Because $I^*$ lies in the left subtree of $u$ and the BST is ordered by low endpoints, every interval $J = [lo_J, hi_J]$ in $u$'s right subtree satisfies: $$lo_J \ge u.lo \ge lo^* > q_{hi}!$$ Thus no interval in $u.\text{right}$ can possibly overlap $Q$ either! Going left when $\text{maxEnd}(u.\text{left}) \ge q_{lo}$ is 100% safe and never misses a valid match in $u.\text{right}$.
Explore the Augmentation Theorem: maintain subtree maxEnd = max(hi, L.maxEnd, R.maxEnd) for O(log N) interval overlap search and Linux VMA gap allocation, or subtree size sz for O(log N) select(k) / rank(x).
Invariant & State
Hardware Reality: Linux Kernel VMAs & GCC Policy-Based Data Structures
Where do Augmented BSTs appear in production? First, the Linux kernel's Virtual Memory Area manager (mm/mmap.c) uses an augmented Red-Black Interval Tree (rb_subtree_gap) to locate free virtual address holes for mmap() in $O(\log N)$ time. Second, in C++ competitive and systems programming, GNU libstdc++ provides __gnu_pbds::tree with tree_order_statistics_node_update, giving a drop-in Red-Black Order-Statistic Tree supporting find_by_order(k) (select) and order_of_key(x) (rank) in $O(\log N)$ time.
Implementation: Interval Tree Overlap Search & Order-Statistic Select/Rank
// C++20: Augmented BST Primitives — Interval Overlap Search + Order-Statistic Select & Rank
#include <algorithm>
#include <cstdint>
struct AugmentedNode {
int64_t lo, hi; // Interval [lo, hi]; 'lo' acts as the primary BST key
int64_t maxEnd; // Max 'hi' in subtree
int32_t sz = 1; // Subtree node count for Order-Statistic rank/select
AugmentedNode *left = nullptr, *right = nullptr;
AugmentedNode(int64_t l, int64_t h) : lo(l), hi(h), maxEnd(h) {}
};
// O(1) Local Pull called after every child modification or rotation
inline void pullAugmented(AugmentedNode* u) noexcept {
if (!u) return;
u->sz = 1 + (u->left ? u->left->sz : 0) + (u->right ? u->right->sz : 0);
int64_t m = u->hi;
if (u->left) m = std::max(m, u->left->maxEnd);
if (u->right) m = std::max(m, u->right->maxEnd);
u->maxEnd = m;
}
// 1. O(log N) Interval Overlap Search (Single root-to-leaf walk, zero backtracking!)
const AugmentedNode* findOverlappingInterval(const AugmentedNode* u, int64_t qLo, int64_t qHi) noexcept {
while (u != nullptr) {
if (u->lo <= qHi && qLo <= u->hi) return u; // Overlap found!
if (u->left != nullptr && u->left->maxEnd >= qLo) {
u = u->left;
} else {
u = u->right;
}
}
return nullptr;
}
// 2. O(log N) Order-Statistic Select (0-indexed k-th smallest by 'lo')
const AugmentedNode* selectKth(const AugmentedNode* u, int32_t k) noexcept {
while (u != nullptr) {
int32_t leftSz = u->left ? u->left->sz : 0;
if (k == leftSz) return u;
if (k < leftSz) {
u = u->left;
} else {
k -= (leftSz + 1);
u = u->right;
}
}
return nullptr;
}
Failure Modes & Adversarial Pitfalls
- Augmenting with Global Rank Instead of Local Subtree Size Breaks $O(\log N)$ Insertion: If you store each node's global sorted index
globalRank[u]inside the node, inserting a new minimum element increments the global rank of all $N$ nodes, forcing $\Theta(N)$ updates! By storing only subtree-localsize[u], inserting a node only incrementssizeon its $O(\log N)$ ancestors. - Forgetting to Call
pullAugmentedInside Rotations: Any rotationrotateLeft(x)orrotateRight(x)changes the children of bothxandy. OmittingpullAugmented(x); pullAugmented(y);inside the rotation helper leaves stalesizeandmaxEndvalues that cause silent out-of-boundsselector missed interval overlaps.