Learn
Lesson · Data Structures · Search trees

Augmenting a Tree

Store the size of each subtree at its root and a search tree answers two new questions in O(log n): the rank of a key, and the i-th smallest key. The rule for what else can be added is sharp — any attribute computable from a node’s own key and its two children’s attributes in constant time. A median field is not one. And every rotation must update the field, or it goes quietly stale.

augment size[x] = size[left] + size[right] + 1 one integer per node select(i) left = size[left]; i = left+1 → here; i ≤ left → left; else i −= left+1, right rank(k) walk to k, adding size[left]+1 at every RIGHT turn both visit one root-to-leaf path O(height) = O(log n) in a balanced tree maintainable: size, sum, min, max NOT maintainable: a median field
One integer a node, two new queries, and the rule for what else fits

Core ideas

What this lesson establishes

One mechanism, one criterion, and one maintenance obligation. The criterion is what makes this a technique rather than a trick, and the obligation is where it breaks in practice.

1

Subtree size turns a search into counting

At a node, the size of the left subtree tells you how many keys are smaller than this one within this subtree. That single number converts the search-tree walk into an arithmetic walk: select subtracts as it goes right, rank accumulates as it goes right, and both finish in one root-to-leaf path.

2

An attribute can ride along if a node can compute it from its children

The criterion is constant-time computability from the node’s own key and the attribute values at its two children. Size qualifies: 1 + left + right. So do sum, min, max, and the count of keys satisfying a fixed predicate. A median does not: knowing the medians of two subtrees and one key does not determine the median of the whole, so maintaining it would cost more than a constant per node and the technique gives nothing.

3

Every structural change must update the attribute

An insertion increments the size at each node on its path. A rotation rearranges three subtrees and must recompute the sizes at the two nodes involved, bottom-up. Forget it and the field is wrong while the tree is correct, which means select returns the wrong key and nothing complains. The lab rotates a size-annotated tree and prints the sizes before and after for exactly that reason.

The material

Two queries from one integer, and the rule for adding a third

How select and rank walk, what a range count costs, which attributes are maintainable, and what a rotation has to do.

Definition · Size-augmented search tree

A search tree in which every node x stores size[x] = size[left(x)] + size[right(x)] + 1, the number of nodes in the subtree rooted at x. An empty subtree has size 0.

The field costs one machine word per node and is maintained by insertion, deletion and rotation. Nothing else about the tree changes.

select(i), the i-th smallest key, counting from 1. At a node, let L be the size of its left subtree. If i = L + 1 the answer is this node’s key, because exactly L keys in this subtree are smaller. If i ≤ L the answer is in the left subtree at the same rank. Otherwise it is in the right subtree at rank i − (L + 1), because the left subtree and this node account for L + 1 of the keys below it.

rank(k), how many keys are at most k. Walk toward k from the root, keeping a running count. Each time the walk goes right, every key in the left subtree and the node itself are below k, so add L + 1. Each time it goes left, add nothing. When k is found, add L + 1 one last time.

tree from 50 25 75 12 37 62 87 6 18 sizes in brackets 50 [9] 25 [5] 75 [3] 12 [3] 37 [1] 62 [1] 87 [1] 6 [1] 18 [1] in-order 6 12 18 25 37 50 62 75 87 rank 1 2 3 4 5 6 7 8 9
select(3): at 50, L = 5, 3 ≤ 5 go left at 25, L = 3, 3 ≤ 3 go left at 12, L = 1, 3 > 1+1 → i = 1 go right at 18, L = 0, 1 = 0+1 ANSWER 18 4 nodes visited rank(62): at 50, 62 > 50 → add 5+1 = 6, go right at 75, 62 < 75 → add 0, go left at 62, found → add 0+1 = 1, ANSWER 7 3 nodes visited

Four nodes for select(3) and three for rank(62), on a tree of height 3. Both walks are one path and nothing else, which is the whole claim: O(height), and O(log n) once the tree is balanced. Without the size field, select(3) would need an in-order traversal counting as it went — O(n) — and rank would need the same. The 3 and the 4 are counts on the tree drawn, whose height is 3 because that insertion order was kind; the claim is O(height), and on the sorted order of the shape-problem lesson the identical queries would walk n. The augmentation buys O(log n) only on top of something that keeps the tree short, which is why an order-statistic tree is an AVL tree with a size field rather than a search tree with one.

Counting a range, as two walks

How many keys lie in [20, 70]? Count the keys at most 70 and subtract the keys below 20. Each count is a rank-style walk adding a subtree size at every right turn, so the query is two paths rather than a scan: the lab reports 4 — 25, 37, 50, 62 — from 7 nodes visited across both walks. Listing the keys would of course cost the number of keys listed; counting them does not.

What can be augmented, and what cannot

Theorem · The augmentation criterion

An attribute f can be maintained on a search tree at constant extra cost per structural change if f(x) is computable in constant time from the key at x and the values f(left(x)) and f(right(x)).

Insertion and deletion change f only on one root-to-leaf path, and a rotation changes it only at the two nodes it rearranges; in each case recomputing bottom-up from the criterion costs a constant per node touched. Size satisfies it with 1 + left + right, sum with key + left + right, min with min(key, left). A median does not: the median of a set is not a constant-time function of one key and the medians of two subsets, so the field would have to be rebuilt and the technique buys nothing.

That criterion is the reusable part of this lesson. It is why an interval tree can store the maximum endpoint in a subtree, why an order-statistic tree stores sizes, and why nobody stores a median. The question to ask about any proposed field is not whether it is useful but whether a node could recompute it from its two children without looking further down.

The rotation that has to update the field

Rotate left at 25 in the tree above. The node 37 rises, 25 becomes its left child, and the sizes must be recomputed at 25 first and then at 37:

before after rotating LEFT at 25 50 [9] 50 [9] 25 [5] 75 [3] 37 [5] 75 [3] 12 [3] 37 [1] 25 [4] 12 [3] 25: 3 + 0 + 1 = 4 recomputed first, from its own children 37: 4 + 0 + 1 = 5 then the new subtree root 50: 5 + 3 + 1 = 9 unchanged, as it must be

The order matters: recompute the node that descended before the node that rose, because the riser’s value depends on it. The lab checks every size in the tree against a fresh recount after the rotation and reports whether they all agree — which is the kind of check worth having, because a stale size field produces wrong answers from a structurally perfect tree and no error anywhere.

Interactive

Augmenting a tree with subtree sizes

Rank and select become walks, and the rotation has to maintain the field

Subtree sizes, and the walks they buyrank, select and a range count, none of them a scan
a node the walk visitedits subtree size

Method

Querying and maintaining an augmented tree

The walk is arithmetic, so the discipline is to write the arithmetic down at each node rather than following the shape by eye.

1

Write the left-subtree size at every node you visit

L is the only thing either query needs from a node besides its key. Writing it down at each step is what makes the two rules mechanical instead of a guess about which way the target lies.

2

For select, compare i with L + 1 and adjust when you go right

i = L + 1 stops. i ≤ L goes left with i unchanged. Otherwise go right with i reduced by L + 1. Forgetting the reduction is the standard error and it gives an answer that is too small by the number of keys skipped.

3

For rank, accumulate L + 1 at every right turn and only there

Going left passes over nothing that is below the target. Going right passes over the whole left subtree and the node, so add L + 1. At the target, add L + 1 once more for the keys below it in its own left subtree, plus itself.

4

After any structural change, recompute the field bottom-up

An insertion increments along its path. A rotation recomputes at the descending node first and the rising node second. Then check the invariant size[x] = size[left] + size[right] + 1 somewhere, because nothing else in the structure will notice if it fails.

Walks and maintenance

Score 0 / 0

select(5), rank(37), and the keys in [20, 70]

Three queries on the same nine-node tree, with the node count for each. The in-order sequence is 6 12 18 25 37 50 62 75 87.

50 [9] 25 [5] 75 [3] 12 [3] 37 [1] 62 [1] 87 [1] 6 [1] 18 [1] select(5) node L test action 50 5 5 ≤ 5 go left 25 3 5 > 3+1 → i = 1 go right 37 0 1 = 0+1 ANSWER 37 3 nodes visited rank(37) node running action 50 L=5 37 < 50, add 0 go left 25 L=3 37 > 25, add 3+1 = 4 go right 37 L=0 found, add 0+1 = 5 ANSWER 5 3 nodes visited count [20, 70] = (keys ≤ 70) − (keys < 20) = 7 − 3 = 4 7 nodes visited across the two walks the four keys are 25 37 50 62

select(5) and rank(37) are inverse queries and both cost three nodes on this tree, which is the point of the pair: one integer per node bought two operations that a plain search tree cannot perform at all without a full traversal.

The range count is 4 from seven nodes visited, and the seven is worth noticing: it is two independent walks down a tree of height 3, not a scan of the four keys. If the query had been to list the keys the cost would include them; counting them does not, and that asymmetry is exactly what the augmentation is for.

For a faded rehearsal, compute select(9) and rank(62) on the same tree. The supplied first step for select(9) is that L = 5 at the root and 9 > 6, so the walk goes right with i = 3. Finish both, predict the node counts, and check them in the lab — three each. Then rotate left at 25 and write down the two sizes that change, in the order they must be recomputed.

Common mistakes

1
Forgetting to reduce i when select goes rightGoing right means the left subtree and the current node have been passed over, so i must drop by L + 1. Without it the walk asks for the same rank in a smaller subtree and returns a key too far to the left. The symptom is an answer that is plausible and consistently too small, which is the hardest kind to notice.
2
Adding to the rank on a left turnGoing left passes over nothing at all that is below the target — every key skipped is larger. Only a right turn passes keys, and it passes exactly L + 1 of them. Adding on both turns gives the number of nodes visited rather than the rank, and the two coincide often enough on small trees to be mistaken for correct.
3
Treating the augmentation as freeIt costs a word per node, an increment along every insertion path, and a recomputation at every rotation — in the right order, the descending node before the rising one. Skip the rotation update and the field goes stale silently: the tree is correct, the queries are wrong, and no invariant in the structure is violated.

Check your understanding

✓
Finish when a proposed extra field prompts the question “could a node compute this from its two children?”

You should be able to run select and rank on a drawn size-annotated tree writing the arithmetic at each node, count the nodes a range query visits and say why it is not proportional to the answer, state the augmentation criterion and apply it to size, sum, min and a median, and recompute the sizes after a rotation in the correct order.

Note: Every structure so far has been a container: it holds keys and answers questions about them. The last one is not. “Union–Find” maintains a partition of n elements and answers only “are these two in the same set” — and the two rules that make it fast are so cheap that leaving one out is the natural mistake, which the lab punishes with a tree of depth 11 on twelve elements.

Kept in this browser only, for you. Nothing is sent anywhere and nothing checks it. Carry it to another device.

Feedback and recommendations

Something to change about this lesson? Write it down, tick it off when it is handled, and export the list when you want to act on it. Kept in this browser only: nothing is sent anywhere, and nobody is notified.

No recommendations yet for this lesson.