Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Appendix A: Complexity Cheat Sheet

Two conventions for reading these tables:

  • Average assumes random or well-distributed data. Worst is the adversarial case. Where they differ, the gap is usually the whole story. A hash table is O(1) average and O(n) worst, and which one you get depends on your hash function and your adversary.
  • ★ marks amortized bounds: cheap on average across a sequence of operations, with occasional expensive ones. A dynamic array append is O(1)★ because the resize that costs O(n) happens rarely enough to average out.

A.1 Linear Structures

StructureAccess by indexSearch by valueInsertDeleteSpace
Static arrayO(1)O(n)n/an/aO(n)
Sorted arrayO(1)O(log n)O(n)O(n)O(n)
Dynamic arrayO(1)O(n)O(1)★ at end, O(n) elsewhereO(n)O(n)
Singly linked listO(n)O(n)O(1) given the nodeO(1) given the previous nodeO(n)
Doubly linked listO(n)O(n)O(1) given the nodeO(1) given the nodeO(n)
Stackn/aO(n)O(1)★O(1)★O(n)
Queuen/aO(n)O(1)★O(1)★O(n)
DequeO(1)O(n)O(1)★ at either endO(1)★ at either endO(n)

The linked-list rows come with the caveat that makes them much less useful than they look: insertion and deletion are O(1) only once you already hold the relevant node. Getting there is O(n), so an insert-at-position is O(n) overall.

A.2 Trees and Ordered Maps

StructureSearch (avg)Search (worst)InsertDeleteSpace
BST (unbalanced)O(log n)O(n)O(log n) / O(n)O(log n) / O(n)O(n)
AVL treeO(log n)O(log n)O(log n)O(log n)O(n)
Red-black treeO(log n)O(log n)O(log n)O(log n)O(n)
Splay treeO(log n)★O(n) single opO(log n)★O(log n)★O(n)
TreapO(log n) expectedO(n)O(log n) expectedO(log n) expectedO(n)
Skip listO(log n) expectedO(n)O(log n) expectedO(log n) expectedO(n) expected
B-tree / B+ treeO(log_B n) I/OsO(log_B n) I/OsO(log_B n)O(log_B n)O(n)
TrieO(L)O(L)O(L)O(L)O(N·A)
Radix / Patricia trieO(L)O(L)O(L)O(L)O(N)

Where L = key length, N = total characters stored across all keys, A = alphabet size.

Two rows worth reading carefully. Skip list space is O(n) expected, not O(n log n): with promotion probability p = ½, the expected total number of nodes across all levels is 2n. The O(log n) is the expected height, which is a different quantity. And splay trees have no per-operation guarantee at all: a single access can cost O(n), and only a sequence of m operations is bounded, at O(m log n). That makes them unsuitable for latency-sensitive work regardless of their excellent amortized behavior.

A.3 Hash-Based Structures

StructureSearch (avg)Search (worst)InsertDeleteSpace
Hash table (chaining)O(1)O(n)O(1)★O(1)O(n + m)
Hash table (open addressing)O(1)O(n)O(1)★O(1)O(m)
Cuckoo hashingO(1)O(1) worst caseO(1)★ expectedO(1)O(n)
Perfect hashing (static)O(1)O(1)n/an/aO(n)

Where m = number of buckets. Cuckoo hashing is the notable row: it is one of the few hash schemes with a genuine O(1) worst-case lookup, because a key can only live in one of two positions. Insertion pays for it, and can fail and require a full rehash.

Java’s HashMap converts a bucket to a red-black tree past 8 entries, giving O(log n) rather than O(n) in the worst case: a defense against deliberate collision flooding.

A.4 Heaps and Priority Queues

StructureFind minInsertDelete minDecrease keyMergeSpace
Binary heapO(1)O(log n)O(log n)O(log n)O(n)O(n)
d-ary heapO(1)O(log_d n)O(d·log_d n)O(log_d n)O(n)O(n)
Binomial heapO(log n)O(1)★O(log n)O(log n)O(log n)O(n)
Fibonacci heapO(1)O(1)O(log n)★O(1)★O(1)O(n)
Pairing heapO(1)O(1)O(log n)★O(log log n)★O(1)O(n)

Building a heap from n existing elements is O(n), not O(n log n): Floyd’s bottom-up heapify. This surprises people and is worth remembering.

Fibonacci heaps have the best bounds on this table and lose to binary heaps on most real workloads; see Chapter 19.

A.5 Graphs

For a graph with V vertices and E edges:

OperationAdjacency listAdjacency matrix
SpaceO(V + E)O(V²)
Add edgeO(1)O(1)
Check edge (u,v)O(deg(u))O(1)
Iterate neighbors of uO(deg(u))O(V)
BFS / DFSO(V + E)O(V²)
AlgorithmComplexityRequires
BFS / DFSO(V + E)n/a
Topological sortO(V + E)DAG
Dijkstra (binary heap)O((V + E) log V)Non-negative weights
Dijkstra (Fibonacci heap)O(E + V log V)Non-negative weights
Bellman-FordO(V·E)Detects negative cycles
Floyd-WarshallO(V³)All pairs
Kruskal MSTO(E log E)Union-find
Prim MST (binary heap)O(E log V)n/a
Union-Find (path compression + union by rank)O(α(n)) ★n/a
Union-Find with rollbackO(log n)No path compression

That last row is a common trap. Rollback requires undoing parent changes, which path compression makes impossible to track cheaply, so rollback DSU uses union by rank alone and costs O(log n), not O(α(n)).

α(n) is the inverse Ackermann function, below 5 for any n that fits in the observable universe.

A.6 Probabilistic and Specialized

StructureQueryInsertSpaceError
Bloom filterO(k)O(k)~1.44·log₂(1/ε)·n bitsFalse positives only
Counting Bloom filterO(k)O(k)4× a Bloom filterFalse positives only
Cuckoo filterO(1)O(1)★~(log₂(1/ε) + 3)·n bitsFalse positives; supports delete
HyperLogLogO(1)O(1)O(log log n), ~12KB for billions~2% cardinality error
Count-Min SketchO(k)O(k)O((1/ε)·log(1/δ))Overestimates only
Skip listO(log n) expectedO(log n) expectedO(n) expectedNone, exact

A Bloom filter with 1% false-positive rate needs about 9.6 bits per element regardless of how large the elements are, which is the property that makes it useful.

A.7 Competitive Programming Structures

StructureBuildQueryUpdateSpace
Prefix sum arrayO(n)O(1)Rebuild O(n)O(n)
Fenwick tree (BIT)O(n)O(log n)O(log n)O(n)
Segment treeO(n)O(log n)O(log n)O(n)
Segment tree + lazy propagationO(n)O(log n)O(log n) rangeO(n)
Sparse tableO(n log n)O(1)Not supportedO(n log n)
Sqrt decompositionO(n)O(√n)O(1)O(n)
Mo’s algorithmn/aO(√n) ★ per queryn/aO(n)
Heavy-light decompositionO(n)O(log² n)O(log² n)O(n)
Link-cut treeO(n)O(log n)★O(log n)★O(n)
Wavelet treeO(n log σ)O(log σ)StaticO(n log σ)
Suffix array (SA-IS)O(n)O(m log n)StaticO(n)
Suffix automatonO(n)O(m)IncrementalO(n), ≤ 2n−1 states
Suffix treeO(n)O(m)StaticO(n)
Palindromic tree (eertree)O(n)O(1)★IncrementalO(n)
Li Chao treeO(n)O(log C)O(log C)O(n)

Where σ = alphabet size, m = pattern length, C = coordinate range.

Mo’s algorithm is offline and processes q queries in O((n + q)√n) total; the O(√n) figure is the amortized per-query share, not a bound on any single query.

A.8 Complexity Growth Reference

How the classes actually behave, for intuition about when each becomes infeasible:

nO(log n)O(n)O(n log n)O(n²)O(2ⁿ)
10310331001,024
100710066410,00010³⁰
1,000101,0009,96610⁶n/a
10⁶2010⁶2×10⁷10¹²n/a
10⁹3010⁹3×10¹⁰10¹⁸n/a

Rough practical ceilings at roughly 10⁸ simple operations per second: O(n²) is fine to n ≈ 10,000; O(n log n) to n ≈ 10⁷; O(n) to n ≈ 10⁸; O(2ⁿ) to n ≈ 25; O(n!) to n ≈ 11.