Lecture W5W1: Binary Trees, Part 2: One Tree, Two Interfaces, and Keeping It Short
September 23, 2026
Monday ended with every tree operation costing \(O(h)\) and no promise about \(h\). Today we make the promise. First, though, we sort out what the tree is for, because “one structure, two jobs” is the part that was confusing.
find(k), insert(x), delete(k), find_min(), find_next(k). “Is 37 in here? What comes after it?”get_at(i), set_at(i, x), insert_at(i, x), delete_at(i). “What is the third item? Put this one fourth.”
| Operation | Sorted array | Hash table | Binary tree |
|---|---|---|---|
find(k) |
\(\log n\) | \(1\) (e) | \(h\) |
insert(x), delete(k) |
\(n\) | \(1\) (a, e) | \(h\) |
find_min(), find_max() |
\(1\) | \(n\) | \(h\) |
find_prev(k), find_next(k) |
\(\log n\) | \(n\) | \(h\) |
(e) expected: an average over the hash function’s randomness. (a) amortized: an average over a sequence of operations, because of the occasional table doubling. Everything else is worst case.
The hash table is the fastest storage for a Set if you never ask an order question. The moment you do, it is a linear scan. The sorted array answers order questions but cannot change cheaply. The tree does everything in \(O(h)\), and \(h\) is the whole story.
| Operation | Array | Linked list | Binary tree |
|---|---|---|---|
get_at(i), set_at(i, x) |
\(1\) | \(n\) | \(h\) |
insert_first, delete_first |
\(n\) | \(1\) | \(h\) |
insert_last, delete_last |
\(1\) (a) | \(n\) | \(h\) |
insert_at(i, x), delete_at(i) |
\(n\) | \(n\) | \(h\) |
(a) amortized: insert_last on a dynamic array is \(O(1)\) averaged over a sequence because of the occasional resize.
Arrays are fast to index and slow to splice; linked lists are the reverse. Neither does insert_at(i) in better than linear time. The tree column is the only one with no \(n\) in it. Again: everything is \(h\).
One tree shape, with one set of \(O(h)\) operations from Monday, can be organized by key to serve the Set interface or organized by position to serve the Sequence interface. If we can keep \(h = O(\log n)\), it beats every other storage choice on the operations they are bad at, and ties them within a log factor on the ones they are good at.
A binary search tree stores items so that traversal order is increasing key order. Equivalently, at every node, keys in the left subtree \(\leq\) node’s key \(\leq\) keys in the right subtree.
find(k): compare, go left or right, \(O(h)\). insert(x): find where it would be, attach a leaf. find_min: go left until you cannot. find_next(k): find, then successor.find_next and find_min cost the same as find. That is the row the hash table cannot do.| Operation | Cost |
|---|---|
find(k), insert(x), delete(k) |
\(O(h)\) |
find_min(), find_max(), find_prev(k), find_next(k) |
\(O(h)\) |
build(X) |
\(O(n \log n)\) |
Every row \(O(h)\). Now the second organization, then we fix \(h\).
insert_after, delete, successor from Monday. No keys. The items can be anything, in any order the client chooses.get_at(i) therefore asks: “which node is \(i\)-th in traversal order?” It returns that node’s item. It is not searching for a value; there is no value to search for.Augmentation: each node also stores size, the number of nodes in its subtree (itself included). A leaf has size 1; a node’s size is 1 plus its children’s sizes.
At node X with left-subtree size \(n_L\):
X is the answer.X itself.

| Operation | How | Cost |
|---|---|---|
get_at(i), set_at(i, x) |
steer by sizes | \(O(h)\) |
insert_at(i, x) |
get_at(i), then insert_before; add 1 to sizes on the path up |
\(O(h)\) |
delete_at(i) |
get_at(i), then delete; subtract 1 on the path up |
\(O(h)\) |
insert_first, insert_last, and their deletes |
subtree_first/subtree_last, then the above |
\(O(h)\) |
build(X) |
build a balanced tree from the list directly | \(O(n)\) |
Same shape, same \(O(h)\), one integer per node. The Set stores a key and compares; the Sequence stores a size and counts.
| Tree as a Set | Tree as a Sequence | |
|---|---|---|
| Organized by | key | position in traversal order |
| Extra field per node | key |
size |
How find / get_at steers |
compare \(k\) with node.key |
compare \(i\) with node.left.size |
| What must stay true after every change | BST property | sizes on the changed path |
| Cost of everything | \(O(h)\) | \(O(h)\) |
Which brings us, for the last time, to \(h\).

insert and delete, spend \(O(\log n)\) extra work to push the tree back toward balanced. The fix must preserve traversal order, or it would break both the Set and the Sequence.
rotate_left(y): y’s right child x moves up to y’s place; y becomes x’s left child; x’s old left subtree B becomes y’s right subtree.x and y. Sizes too, if you keep them. Everything else is untouched.rotate_right is the mirror image. Rotations are the only way we will ever move a node that is not a leaf.The skew of a node is \(h(\text{right}) - h(\text{left})\), where an empty subtree has height \(-1\). A node is balanced if its skew is \(-1\), \(0\), or \(+1\). A tree is AVL if every node is balanced.

If every node’s subtrees differ in height by at most one, the tree’s height is \(O(\log n)\). So: keep that true after every change, and every \(O(h)\) row in both tables becomes \(O(\log n)\).
height (and size) from the children. \(O(1)\) per node.
Let x be the lowest unbalanced node.
x.right:
rotate_left(x).rotate_right(x.right), then rotate_left(x).x.left:
rotate_right(x).rotate_left(x.left), then rotate_right(x).






Every rotation in the walkthrough, animated: courses.lucascordova.phd/dsaviz
2 (+1), using today’s conventions (leaf height \(0\), skew \(= h(\text{right}) - h(\text{left})\)).x had height \(k + 1\) (tall side \(k\), short side \(k - 1\)).x has both sides at height \(k\), so its height is \(k + 1\) again.x now sees exactly the subtree height it saw before the insert. Their skews are unchanged, so they are still balanced.


| Set interface | Sorted array | Hash table | AVL tree |
|---|---|---|---|
find(k) |
\(\log n\) | \(1\) (e) | \(\log n\) |
insert(x), delete(k) |
\(n\) | \(1\) (a, e) | \(\log n\) |
find_min, find_max, find_prev, find_next |
\(\log n\) or \(1\) | \(n\) | \(\log n\) |
| Sequence interface | Array | Linked list | AVL tree |
|---|---|---|---|
get_at(i), set_at(i, x) |
\(1\) | \(n\) | \(\log n\) |
insert_at(i, x), delete_at(i) |
\(n\) | \(n\) | \(\log n\) |
(e) expected, (a) amortized, as before. The AVL column has neither.
Worst case, no randomness, no amortization. That is the promise Monday could not make.
Take one worksheet per pair.
Side 1: insert a sequence of keys into an empty AVL tree, drawing the tree after each insert and naming every rotation you perform.
Side 2: delete three keys from the resulting tree, one at a time, with the same bookkeeping, then answer two questions about heights.
12 minutes. Worksheets come in at the end; one per pair, both names on it.
Do it on paper first, then check yourselves: dsaviz AVL tree
Insert \(50, 30, 70, 20, 40, 10, 45, 42\):
rotate_right(50). New root \(30\), children \(20\) (with \(10\)) and \(50\) (with \(40\), \(70\)). Height \(2\).rotate_right(45), then rotate_left(40). \(42\) takes \(40\)’s place with children \(40\) and \(45\).From the final tree, delete \(10\), then \(20\), then \(70\):
rotate_right(50), then rotate_left(30). New root \(42\) with \(30\) (\(20\), \(40\)) and \(50\) (\(45\), \(70\)). Height \(2\).The binary tree is a storage choice that serves two interfaces: organize it by key and it is a Set, organize it by position (with a size per node) and it is a Sequence. Everything costs \(O(h)\). The AVL property, every node’s subtrees within one in height, forces \(h \leq 1.44 \log_2 n\), and rotations, which rewire three pointers without touching traversal order, restore that property after any insert or delete in \(O(\log n)\). Result: every operation of both interfaces in \(O(\log n)\), worst case.
Topics: Binary heaps and priority queues. A tree stored in an array with no pointers at all, which gives up find to make insert and delete_max cheap and simple. Then heap sort, our third \(O(n \log n)\) sort.
Supplemental reading (highly recommended): CLRS Chapter 13 for today (red-black trees are CLRS’s balanced tree; the balancing idea is the same); Chapter 6 for Monday.
Upcoming deliverables:
