← Explainer Library

Interactive Explainer

Trees, Branch by Branch

The data structure that quietly powers ordered maps, priority queues, autocomplete, and half the algorithms textbook. Build a binary search tree by typing values, run all four traversals on it, then switch to a heap and watch sift-up and sift-down preserve the heap invariant on every operation.

Prelude

Why trees keep showing up

A sorted array lets you binary-search in $O(\log n)$ but insertion is $O(n)$ because every element after the insertion point has to shift. A linked list flips the trade: insertion is $O(1)$ at a known position, but search is $O(n)$.

A balanced binary tree gives you both in $O(\log n)$. That single fact — that you can support ordered insert and ordered search in the same logarithmic time — is the reason trees show up everywhere from database indexes (B-trees) to the priority queues inside Dijkstra and A* (binary heaps) to the rope structures that back modern text editors.

Two trees worth knowing well. The binary search tree (BST) maintains a left-less-than, right-greater invariant — built for ordered queries. The heap maintains a parent-less-than-children invariant — built for "give me the smallest/largest right now". Same shape, completely different invariant. The rest of the tree zoo (AVL, red-black, B-tree, trie, segment tree) are refinements of these two ideas.
Step 1

Anatomy

A binary tree is a collection of nodes, each carrying a value and up to two children — left and right. One node is distinguished as the root; a node with no children is a leaf; the height is the longest path from root to leaf.

Step 2

Build a BST, search inside it

Type values one at a time. Each goes into the tree by the BST rule: smaller than current → go left; larger → go right; insert when you hit an empty slot. Then type a value to search for; the path is highlighted as it walks down.

empty tree.

Things worth trying:

Step 3

Four traversals, one tree

A traversal visits every node exactly once. Four canonical orders, all defined by where the root sits relative to the recursive calls on its subtrees:

Pick a traversal below; the nodes light up one at a time in that order. Then look at the trail of values it produces.

The in-order traversal of a BST is a sorted list — try inserting unsorted values above and then running in-order here. It's the simplest BST sanity check.

Step 4

Why balance matters

The cost of every BST operation — insert, search, predecessor, in-order successor, delete — is proportional to the depth of the node touched. A balanced tree caps depth at $\log_2 n$; an unbalanced one can let it grow to $n$.

Self-balancing BSTs solve this by rotating nodes whenever the height of two siblings drifts too far apart:

The trap. A plain BST without balancing is a terrible general-purpose data structure — its worst case is hit by sorted input, which is more common than uniform random in practice. Always reach for a balanced variant in production. Reach for the plain BST only when implementing it for teaching (like this article) or when input order is guaranteed adversarial-free.
Step 5

Heaps — parent dominates children

A binary heap is a complete binary tree with a single invariant: every parent is $\le$ both its children (min-heap) or $\ge$ both (max-heap). It is most naturally stored as an array — for a node at index $i$:

Because the tree is always complete, no array slots are wasted. Two operations preserve the invariant:

Below: insert values into a heap and watch them sift up; extract the root and watch the last element sift down. Both the tree view and the array view stay synchronised.

empty heap.
array view

Index 0 is the root. Indices $2i+1$ and $2i+2$ are its children. The two views are the same tree.

Step 6

Heapify — building a heap in O(n)

The obvious way to build a heap of $n$ items is to call insert $n$ times. That's $n \log n$ work. Floyd's heapify does it in $O(n)$ by working bottom-up.

Why $O(n)$ instead of $O(n \log n)$? Sift-down on a node at depth $d$ costs at most $h - d$ where $h = \lfloor \log_2 n \rfloor$ is the tree's height. Half the nodes are leaves (cost 0); a quarter are one level up (cost 1); an eighth are two levels up (cost 2); the sum collapses to a constant times $n$. Press heapify random 12 above and watch the bottom-up passes.

Heapify is also the building block of heap sort: build a max-heap in place ($O(n)$), then repeatedly extract-max into the last array slot ($n \log n$). Total $O(n \log n)$, in-place, no auxiliary memory. The reason it isn't the default sort in most languages is cache behaviour — quicksort and Timsort dominate in practice despite the same asymptotic.

Step 7

Where this gets used

Use caseStructureWhy
Ordered map / set (C++ std::map, Java TreeMap) red-black BST $O(\log n)$ insert + ordered iteration; in-order traversal yields the sorted keys for free.
Database / filesystem index B-tree, B+-tree Wide nodes minimise disk reads; same logarithmic guarantee as a binary tree but with $\log_B n$ depth.
Priority queue (Dijkstra, A*, event simulation) binary heap, Fibonacci heap $O(\log n)$ extract-min; Fibonacci heaps amortise decrease-key to $O(1)$ which is the bottleneck inside Dijkstra.
Top-k streaming (top trending tweets, top-k logs) min-heap of size $k$ Compare each new element to the heap root; replace and sift if larger. $O(n \log k)$ total.
Heap sort, partial sort binary heap In-place sort; partial sort gives the top-k in $O(n + k \log n)$.
Autocomplete, prefix matching trie Tree keyed on string characters; prefix lookup in $O(|\text{prefix}|)$ regardless of dictionary size.
Range queries (sum / min on arrays) segment tree, Fenwick tree $O(\log n)$ range-query and point-update. The competitive-programming workhorse.
Decision trees, random forests, XGBoost tree of split predicates Tree as a function from feature vector to label. Different problem, same scaffolding.
Step 8

Sharp edges

Step 9

Reading list