Complexity scenarios
The same algorithm can cost different amounts on different inputs. This lesson separates best, average, worst, expected and amortised cost, shows how the input distribution changes the answer, and explains the role of randomness in bounds.
Learning goals
- State best, average and worst cases for linear search
- Distinguish average case, expected case and amortised cost
- Say which case a bound covers
Prerequisites
Best case
The best case is the input that makes the algorithm do the least work: the target is the first element of a linear search (1 comparison), or the array is already sorted for insertion sort (n - 1 comparisons). It is rarely useful for planning because it is unrepresentative.
Run the three cases in the animation to see the comparison counts.
Best, average and worst case of linear search
Interactive · edit the inputcomparisons when searching for each value
Count the comparisons for every possible target. The same algorithm does very different amounts of work depending on where (or whether) the target is.
1static int comparisons(int[] arr, int target) {2int count = 0;3for (int x : arr) {4count++;5if (x == target) return count; // best case 1, worst case n6}7return count; // absent: always n8}9// average over all n present targets, equally likely: (1 + 2 + ... + n) / n = (n + 1) / 2
Live state
n
8
Execution trace (dry run table)Show 10 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | n=8 | Count the comparisons for every possible target. The same algorithm does very different amounts of work depending on where (or whether) the target is. |
| 2 | L5 | target=14 comparisons=1 | Searching for 14 takes 1 comparison. |
| 3 | L5 | target=3 comparisons=2 | Searching for 3 takes 2 comparisons. |
| 4 | L5 | target=9 comparisons=3 | Searching for 9 takes 3 comparisons. |
| 5 | L5 | target=21 comparisons=4 | Searching for 21 takes 4 comparisons. |
| 6 | L5 | target=5 comparisons=5 | Searching for 5 takes 5 comparisons. |
| 7 | L5 | target=18 comparisons=6 | Searching for 18 takes 6 comparisons. |
| 8 | L5 | target=7 comparisons=7 | Searching for 7 takes 7 comparisons. |
| 9 | L5 | target=12 comparisons=8 | Searching for 12 takes 8 comparisons. |
| 10 | L9 | best=1 average=4.5 worst (present)=8 absent target=8 return=4.5 | Best case 1, worst case 8 (absent target: 8), average 4.50 over these targets. With distinct values the average is (n + 1) / 2 = 4.5. Best/worst describe inputs; average assumes a distribution of inputs, here “each present value equally likely”. |
Average case
The average case is the expected cost over a stated distribution of inputs. If the target is equally likely to be at any of the n positions, a successful linear search makes (n + 1)/2 comparisons on average. An average claim is only meaningful with the distribution attached.
Real inputs often are not uniform, so average-case results should be checked against the actual workload.
Best, average and worst case of linear search
Interactive · edit the inputcomparisons when searching for each value
Count the comparisons for every possible target. The same algorithm does very different amounts of work depending on where (or whether) the target is.
1static int comparisons(int[] arr, int target) {2int count = 0;3for (int x : arr) {4count++;5if (x == target) return count; // best case 1, worst case n6}7return count; // absent: always n8}9// average over all n present targets, equally likely: (1 + 2 + ... + n) / n = (n + 1) / 2
Live state
n
8
Execution trace (dry run table)Show 10 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | n=8 | Count the comparisons for every possible target. The same algorithm does very different amounts of work depending on where (or whether) the target is. |
| 2 | L5 | target=14 comparisons=1 | Searching for 14 takes 1 comparison. |
| 3 | L5 | target=3 comparisons=2 | Searching for 3 takes 2 comparisons. |
| 4 | L5 | target=9 comparisons=3 | Searching for 9 takes 3 comparisons. |
| 5 | L5 | target=21 comparisons=4 | Searching for 21 takes 4 comparisons. |
| 6 | L5 | target=5 comparisons=5 | Searching for 5 takes 5 comparisons. |
| 7 | L5 | target=18 comparisons=6 | Searching for 18 takes 6 comparisons. |
| 8 | L5 | target=7 comparisons=7 | Searching for 7 takes 7 comparisons. |
| 9 | L5 | target=12 comparisons=8 | Searching for 12 takes 8 comparisons. |
| 10 | L9 | best=1 average=4.5 worst (present)=8 absent target=8 return=4.5 | Best case 1, worst case 8 (absent target: 8), average 4.50 over these targets. With distinct values the average is (n + 1) / 2 = 4.5. Best/worst describe inputs; average assumes a distribution of inputs, here “each present value equally likely”. |
Worst case
The worst case is the most expensive input of a given size: the target is last or absent, giving n comparisons. Worst-case bounds are guarantees that hold for every input, which makes them the standard for planning and for real-time systems.
Quick sort is O(n²) in the worst case but O(n log n) on average; heap sort is O(n log n) in all cases.
| Linear search (n = 10) | Comparisons |
|---|---|
| Best: target at index 0 | 1 |
| Average: found, uniform position | 5.5 |
| Worst: absent or last | 10 |
Best, average and worst case of linear search
Interactive · edit the inputcomparisons when searching for each value
Count the comparisons for every possible target. The same algorithm does very different amounts of work depending on where (or whether) the target is.
1static int comparisons(int[] arr, int target) {2int count = 0;3for (int x : arr) {4count++;5if (x == target) return count; // best case 1, worst case n6}7return count; // absent: always n8}9// average over all n present targets, equally likely: (1 + 2 + ... + n) / n = (n + 1) / 2
Live state
n
8
Execution trace (dry run table)Show 10 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | n=8 | Count the comparisons for every possible target. The same algorithm does very different amounts of work depending on where (or whether) the target is. |
| 2 | L5 | target=14 comparisons=1 | Searching for 14 takes 1 comparison. |
| 3 | L5 | target=3 comparisons=2 | Searching for 3 takes 2 comparisons. |
| 4 | L5 | target=9 comparisons=3 | Searching for 9 takes 3 comparisons. |
| 5 | L5 | target=21 comparisons=4 | Searching for 21 takes 4 comparisons. |
| 6 | L5 | target=5 comparisons=5 | Searching for 5 takes 5 comparisons. |
| 7 | L5 | target=18 comparisons=6 | Searching for 18 takes 6 comparisons. |
| 8 | L5 | target=7 comparisons=7 | Searching for 7 takes 7 comparisons. |
| 9 | L5 | target=12 comparisons=8 | Searching for 12 takes 8 comparisons. |
| 10 | L9 | best=1 average=4.5 worst (present)=8 absent target=8 return=4.5 | Best case 1, worst case 8 (absent target: 8), average 4.50 over these targets. With distinct values the average is (n + 1) / 2 = 4.5. Best/worst describe inputs; average assumes a distribution of inputs, here “each present value equally likely”. |
Expected case
Expected cost averages over the algorithm's own random choices, not over the input. Randomised quick sort picks pivots at random, so for every input its expected number of comparisons is about 2n ln n ≈ 1.39 n log₂ n. No particular input is bad on average.
Average-case analysis assumes a random input; expected-case analysis assumes a random algorithm.
Amortized analysis
Amortised cost is the average cost per operation over a worst-case sequence of operations. ArrayList.add is O(n) when the array is full and must be copied, yet n adds in a row cost O(n) in total, so each add is amortised O(1) with no assumptions about the input.
The next lesson develops three methods (aggregate, accounting, potential).
Dynamic array: amortised cost of append
Interactive · edit the inputbacking array (capacity 1)
cost of each append
Start with capacity 1. Each append costs 1 write, plus size copies whenever the array is full. Policy: Double (×2).
1void add(int x) {2if (size == data.length) { // full: allocate a bigger array3data = Arrays.copyOf(data, newCapacity(data.length)); // copies `size` elements4}5data[size++] = x; // the cheap part: 1 write6}7// doubling: newCapacity = 2 * capacity; grow-by-1: capacity + 1; Java ArrayList: capacity + capacity / 2
Live state
capacity
1
size
0
Execution trace (dry run table)Show 24 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | capacity=1 size=0 | Start with capacity 1. Each append costs 1 write, plus size copies whenever the array is full. Policy: Double (×2). |
| 2 | L5 | size=1 capacity=1 cost of this append=1 total cost=1 amortised=1 | Append #1 costs 1. Total so far 1, so the average is 1.00 per append. |
| 3 | L3 | capacity=1 new capacity=2 copies=1 | Full: allocate 2 slots and copy 1 element. This single append costs 2. |
| 4 | L5 | size=2 capacity=2 cost of this append=2 total cost=3 amortised=1.5 | Append #2 costs 2. Total so far 3, so the average is 1.50 per append. |
| 5 | L3 | capacity=2 new capacity=4 copies=2 | Full: allocate 4 slots and copy 2 elements. This single append costs 3. |
| 6 | L5 | size=3 capacity=4 cost of this append=3 total cost=6 amortised=2 | Append #3 costs 3. Total so far 6, so the average is 2.00 per append. |
| 7 | L5 | size=4 capacity=4 cost of this append=1 total cost=7 amortised=1.75 | Append #4 costs 1. Total so far 7, so the average is 1.75 per append. |
| 8 | L3 | capacity=4 new capacity=8 copies=4 | Full: allocate 8 slots and copy 4 elements. This single append costs 5. |
| 9 | L5 | size=5 capacity=8 cost of this append=5 total cost=12 amortised=2.4 | Append #5 costs 5. Total so far 12, so the average is 2.40 per append. |
| 10 | L5 | size=6 capacity=8 cost of this append=1 total cost=13 amortised=2.17 | Append #6 costs 1. Total so far 13, so the average is 2.17 per append. |
| 11 | L5 | size=7 capacity=8 cost of this append=1 total cost=14 amortised=2 | Append #7 costs 1. Total so far 14, so the average is 2.00 per append. |
| 12 | L5 | size=8 capacity=8 cost of this append=1 total cost=15 amortised=1.88 | Append #8 costs 1. Total so far 15, so the average is 1.88 per append. |
| 13 | L3 | capacity=8 new capacity=16 copies=8 | Full: allocate 16 slots and copy 8 elements. This single append costs 9. |
| 14 | L5 | size=9 capacity=16 cost of this append=9 total cost=24 amortised=2.67 | Append #9 costs 9. Total so far 24, so the average is 2.67 per append. |
| 15 | L5 | size=10 capacity=16 cost of this append=1 total cost=25 amortised=2.5 | Append #10 costs 1. Total so far 25, so the average is 2.50 per append. |
| 16 | L5 | size=11 capacity=16 cost of this append=1 total cost=26 amortised=2.36 | Append #11 costs 1. Total so far 26, so the average is 2.36 per append. |
| 17 | L5 | size=12 capacity=16 cost of this append=1 total cost=27 amortised=2.25 | Append #12 costs 1. Total so far 27, so the average is 2.25 per append. |
| 18 | L5 | size=13 capacity=16 cost of this append=1 total cost=28 amortised=2.15 | Append #13 costs 1. Total so far 28, so the average is 2.15 per append. |
| 19 | L5 | size=14 capacity=16 cost of this append=1 total cost=29 amortised=2.07 | Append #14 costs 1. Total so far 29, so the average is 2.07 per append. |
| 20 | L5 | size=15 capacity=16 cost of this append=1 total cost=30 amortised=2 | Append #15 costs 1. Total so far 30, so the average is 2.00 per append. |
| 21 | L5 | size=16 capacity=16 cost of this append=1 total cost=31 amortised=1.94 | Append #16 costs 1. Total so far 31, so the average is 1.94 per append. |
| 22 | L3 | capacity=16 new capacity=32 copies=16 | Full: allocate 32 slots and copy 16 elements. This single append costs 17. |
| 23 | L5 | size=17 capacity=32 cost of this append=17 total cost=48 amortised=2.82 | Append #17 costs 17. Total so far 48, so the average is 2.82 per append. |
| 24 | L7 | appends=17 total cost=48 return=2.824 | Doubling: total cost 48 < 3n = 51, so the amortised cost per append is O(1) (here 2.82). The expensive copies are rare and pay for themselves: aggregate analysis. Accounting method: charge 3 coins per append: 1 for the write, 1 to copy itself later, 1 to copy an older element. |
Input distribution
Average results depend on how inputs are distributed. Uniformly random keys make a plain binary search tree about 1.39 log₂ n deep on average, but already-sorted keys make it a chain of depth n. Hash tables assume well-spread hashes; adversarial keys can break that.
When the distribution is unknown or adversarial, prefer worst-case guarantees or randomise.
Randomized algorithm bounds
Bounds for randomised algorithms come in two forms. Expected bounds average over the random choices (randomised quick sort: O(n log n) expected). High-probability bounds say the cost is within a bound with probability at least 1 - 1/nᶜ, which is stronger and survives repeated use.
Las Vegas algorithms always return the correct answer with random running time; Monte Carlo algorithms have bounded time and a small probability of error.
Practice ladder
Now try it yourself
Easy
- Give the best, average and worst comparison counts for linear search on 100 elements
- Which input makes insertion sort fastest?
Medium
- Explain the difference between average-case and amortised cost
- Why is the expected cost of randomised quick sort independent of the input?
Advanced
- Compute the average comparisons of an unsuccessful and a successful linear search if the target is absent with probability 1/2
Key takeaways
- Always say which case a bound covers and under what distribution.
- Expected cost averages over the algorithm's randomness; average cost averages over inputs.
- Amortised cost is an average over operations with a worst-case guarantee.