Complexity comparison
Five comparisons that decide real design choices: arrays versus linked lists, hashing's expected versus worst case, balanced trees, the comparison-sorting lower bound, and adjacency lists versus matrices for graphs.
Learning goals
- Compare structures operation by operation
- Explain the n log n sorting lower bound
- Choose a graph representation from density
Prerequisites
Arrays vs linked lists
Arrays win on indexed access and traversal because their elements are contiguous: O(1) reads and few cache misses. Linked lists win only on insertion or removal at a position you already hold, where an array must shift elements. In practice, even such workloads often favour arrays until n is very large.
Count the memory reads for reaching element k in the animation.
| Operation | Array / ArrayList | Linked list |
|---|---|---|
| Access by index | O(1) | O(n) |
| Search by value | O(n) | O(n) |
| Insert at end | amortised O(1) | O(1) with a tail pointer |
| Insert at front | O(n) | O(1) |
| Insert at known node | O(n) | O(1) |
| Memory per element | 4-8 bytes | about 24-32 bytes |
Reading element k: array vs linked list
Interactive · edit the inputarray memory (value, address)
The 7 values sit in consecutive memory cells, 4 bytes apart, starting at address 1000.
1static int getFromArray(int[] arr, int k) {2return arr[k]; // address = base + k * 4: one memory read3}45static int getFromList(Node head, int k) {6Node cur = head;7for (int i = 0; i < k; i++) {8cur = cur.next; // pointer chase: one read per hop9}10return cur.value;11}
Live state
k
5
base
1000
Execution trace (dry run table)Show 3 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | k=5 base=1000 | The 7 values sit in consecutive memory cells, 4 bytes apart, starting at address 1000. |
| 2 | L2 | k=5 address=1020 | Address = 1000 + 5 × 4 = 1020. One multiplication and one addition give the location, no matter how large k is. |
| 3 | L2 | return=8 | return 8 with a single memory read: O(1) random access. |
Hashing expected/worst-case
A hash table gives expected O(1) get, put and remove when the hash function spreads keys evenly and the load factor is bounded. In the worst case all keys land in one bucket: O(n) for a list-based bucket, O(log n) for Java 8+ HashMap, which converts long buckets to balanced trees. Resizing costs O(n) occasionally but is amortised O(1).
Adversarial or poorly distributed keys are the practical risk; mutable keys and missing hashCode implementations are the common bugs.
| Operation | Expected | Worst case (Java 8+) |
|---|---|---|
| get / contains | O(1) | O(log n) |
| put | O(1) amortised | O(log n) |
| remove | O(1) | O(log n) |
| iterate all | O(n + capacity) | same |
Balanced trees
A balanced binary search tree keeps its height O(log n) with rotations, so search, insert and delete are O(log n) in the worst case, and keys stay in sorted order for range and nearest-key queries. AVL trees have height at most about 1.44 log₂ n and red-black trees at most 2 log₂(n + 1); Java's TreeMap is a red-black tree.
An unbalanced tree degrades to a chain of height n on sorted input.
| Structure | Search | Sorted order | Range query |
|---|---|---|---|
| Hash table | O(1) expected | no | no |
| Sorted array | O(log n) | yes | yes (insert O(n)) |
| Balanced BST | O(log n) worst | yes | yes |
| Plain BST | O(n) worst | yes | yes |
Sorting lower bound
Any algorithm that sorts only by comparing elements needs Ω(n log n) comparisons in the worst case. A sorting algorithm is a binary decision tree whose leaves are the possible orderings; there are n! of them, and a binary tree with n! leaves has height at least log₂(n!) = Θ(n log n). So merge sort and heap sort are asymptotically optimal among comparison sorts.
Non-comparison sorts (counting, radix, bucket) escape the bound by using the structure of the keys. The chart compares measured growth of the quadratic and n log n sorts.
| n | log₂(n!) (minimum comparisons) |
|---|---|
| 4 | about 4.6, so at least 5 |
| 10 | about 21.8, so at least 22 |
| 100 | about 525 |
| 1,000 | about 8,530 |
Comparisons as the input grows
Measured by running each trace- Bubble275
- Selection276
- Insertion162
- Quick (last pivot)104
- Heap147
- Merge80
- Lower bound ⌈log₂ n!⌉80
Numbers at the right are comparisons for n = 24. The dashed line is the fewest comparisons any comparison sort can use in the worst case. Switch to “sorted” and “reverse” to see which algorithms notice the order.
Graph adjacency list vs matrix
An adjacency matrix is a V × V array: O(V²) memory, O(1) test for an edge, O(V) to list the neighbours of a vertex. An adjacency list stores each vertex's neighbours: O(V + E) memory, O(degree) edge test and O(degree) neighbour listing. Use the matrix for dense graphs (E close to V²) or when constant-time edge checks matter; use lists for sparse graphs, which are the majority.
A road network with 10⁶ intersections and 3 × 10⁶ roads fits as a list but would need 10¹² cells as a matrix.
| Operation | Adjacency list | Adjacency matrix |
|---|---|---|
| Memory | O(V + E) | O(V²) |
| Is there an edge (u, v)? | O(deg u) | O(1) |
| Visit neighbours of u | O(deg u) | O(V) |
| Traverse whole graph (BFS/DFS) | O(V + E) | O(V²) |
| Add an edge | O(1) | O(1) |
1int V = 4;2boolean[][] matrix = new boolean[V][V];3List<List<Integer>> list = new ArrayList<>();4for (int i = 0; i < V; i++) list.add(new ArrayList<>());5int[][] edges = {{0, 1}, {1, 2}, {2, 3}};6for (int[] e : edges) { matrix[e[0]][e[1]] = matrix[e[1]][e[0]] = true; list.get(e[0]).add(e[1]); list.get(e[1]).add(e[0]); }
Practice ladder
Now try it yourself
Easy
- When is a linked list better than an ArrayList?
- Which graph representation suits 10⁶ vertices and 3 × 10⁶ edges?
Medium
- Explain why a hash table has an O(log n) worst case in Java 8+
- Compute the minimum number of comparisons needed to sort 5 items
Advanced
- Prove that log₂(n!) is Ω(n log n) using the n/2 largest factors
Key takeaways
- Arrays beat linked lists for almost everything except holding a node and editing around it.
- Hashing is expected O(1); balanced trees give O(log n) with order.
- Comparison sorting needs Ω(n log n); graph density picks list or matrix.