Lecture W5M3: Binary Trees, Part 1
September 21, 2026
first, successor, insert-before/after, and delete on a small tree, each in \(O(h)\) time.Hash tables gave us \(O(1)\) find and nothing about order. Sorted arrays gave us order and \(O(n)\) insert. Today we build the structure that does both, and end with the one question it leaves open.
| Set interface | Sorted array | Hash table | Goal |
|---|---|---|---|
build(X) |
\(n \log n\) | \(n\) (e) | \(n \log n\) |
find(k) |
\(\log n\) | \(1\) (e) | \(\log n\) |
insert(x), delete(k) |
\(n\) | \(1\) (a, e) | \(\log n\) |
find_min(), find_max() |
\(1\) | \(n\) | \(\log n\) |
find_prev(k), find_next(k) |
\(\log n\) | \(n\) | \(\log n\) |
Neither column gets every row. The goal column asks for every operation logarithmic, order operations included. (e) expected, (a) amortized
item, parent, left, right. Nothing else holds the tree together.

The depth of a node is the number of edges on the path from the root to it. The height of a node is the length of the longest downward path from it to a leaf. The height \(h\) of the tree is the height of its root.
The traversal order of a binary tree is: everything in the left subtree, then the node itself, then everything in the right subtree, applied recursively.

list(X): list(X.left), then X, then list(X.right). Each node does \(O(1)\) work, so listing everything costs \(O(n)\).subtree_first(X): if X.left exists, recurse on it; otherwise X is first.subtree_last(X) is the mirror image: follow right pointers until you cannot.find_min and find_max for the Set will be exactly these two calls on the root.
successor(X): if X.right exists, return subtree_first(X.right).X was last.predecessor is the mirror: subtree_last(X.left), or climb until you arrive from a right child.insert_after(X, Y): if X.right is empty, make Y the right child of X.Y the left child of successor(X). Why is that slot guaranteed empty? If the successor had a left child, that child would come between them in traversal order.insert_before is the mirror, using predecessor.
X is a leaf, detach it from its parent. Done.X has a left child, swap its item with its predecessor’s item and delete from the predecessor’s node instead. The predecessor of a node with a left child is the last node of that left subtree, which has no right child, so it is closer to being a leaf.X has a right child: swap with the successor and recurse.


A binary search tree (BST) is a binary tree whose traversal order is increasing key order. Equivalently, the BST property: for every node, every key in its left subtree is at most its key, and every key in its right subtree is at least its key.
Same structure, same six algorithms. We have only decided where each item goes.

| Operation | How | Cost |
|---|---|---|
find(k) |
compare, go left or right | \(O(h)\) |
insert(x) |
find where it would be, then insert_before/after |
\(O(h)\) |
delete(k) |
find, then delete the node |
\(O(h)\) |
find_min, find_max |
subtree_first, subtree_last on the root |
\(O(h)\) |
find_prev(k), find_next(k) |
find, then predecessor/successor |
\(O(h)\) |
build(X) |
sort, then build a balanced tree recursively | \(O(n \log n)\) |
Every row is \(O(h)\). The goal column wanted \(O(\log n)\). So the entire question is: how big is \(h\)?
get_at(i) by walking the whole traversal is \(O(n)\). Too slow.node.size. When a leaf is added or removed, add \(\pm 1\) to every ancestor’s size, along the \(O(h)\) path we walked anyway.
X with left subtree size \(n_L\):X is the answer;insert_at, delete_at, get_at, set_at) is then \(O(h)\) via get_at plus today’s insert or delete.
Take one worksheet per pair.
Side 1: given a drawn tree, write its traversal order, then find subtree_first, two successors, and where an insert lands.
Side 2: build a BST from a key sequence, delete its root, then find an insertion order that makes the tree as tall as possible.
10 minutes. Worksheets come in at the end; one per pair, both names on it.
Tree: root \(M\) with left child \(G\) and right child \(T\); \(G\) has children \(C\) and \(K\); \(T\) has right child \(W\); \(K\) has left child \(J\).
subtree_first(M) \(= C\) (keep going left).successor(G) \(=\) first of \(G\)’s right subtree \(= J\). successor(K) \(= M\) (climb: \(K \to G\) from the right, \(G \to M\) from the left).insert_after(M, P): \(M\) has a right child, so \(P\) becomes the left child of successor(M) \(= T\).Insert \(41, 17, 65, 8, 30, 52, 79\) into an empty BST:
delete(41): \(41\) has a left child, so swap with its predecessor \(30\) (the last node of the left subtree), which is a leaf; detach it. New root \(30\).
HW3: Binary Search Trees, Analyzed and Measured. Due Wednesday, September 30 at 9:59 PM. 100 points.
insert, find, height, in-order traversal, with counters for comparisons and rotations. Python starter provided; any language is allowed.results.csv in the given format, plot height against \(\log_2 n\), and write one page on where the data and the analysis agree and where they do not.insert on a sorted sequence of \(65{,}536\) keys will exhaust Python’s default recursion limit. That is the analysis showing up in your runtime. Write insert iteratively.A binary tree is three pointers per node and one rule for order: left subtree, node, right subtree. From that rule come first, last, successor, predecessor, insert-before/after, and delete, each walking one path, each \(O(h)\). Assign the order a meaning and the same tree is a Set (keys sorted, find by comparing) or a Sequence (positions, steered by subtree sizes). Everything is \(O(h)\), and \(h\) can be \(n\). Wednesday makes it \(\log n\).
Topics: Binary trees, part 2. Rotations, the AVL balance rule, and why it forces \(h = O(\log n)\); how insert and delete rebalance in \(O(\log n)\) with \(O(1)\) rotations per insert.
Supplemental reading (highly recommended): CLRS Chapter 12 for today; Chapter 13 for Wednesday (CLRS treats red-black trees; the balancing idea is the same, and the AVL details are in lecture).
Upcoming deliverables:
