Chapter 4: Complexity, Memory and Performance Analysis
4.05Beginner7 topics14 min4 interactive animations

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

Asymptotic notationProbability fundamentals
4.05.1

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 input

Your input

Try

comparisons when searching for each value

empty
Setup

Count the comparisons for every possible target. The same algorithm does very different amounts of work depending on where (or whether) the target is.

Step 1 / 10
Java
1static int comparisons(int[] arr, int target) {
2 int count = 0;
3 for (int x : arr) {
4 count++;
5 if (x == target) return count; // best case 1, worst case n
6 }
7 return count; // absent: always n
8}
9// average over all n present targets, equally likely: (1 + 2 + ... + n) / n = (n + 1) / 2

Live state

n

8

No condition evaluated on this step
Execution trace (dry run table)Show 10 steps
StepLineStateWhat happens
1L1n=8Count the comparisons for every possible target. The same algorithm does very different amounts of work depending on where (or whether) the target is.
2L5target=14 comparisons=1Searching for 14 takes 1 comparison.
3L5target=3 comparisons=2Searching for 3 takes 2 comparisons.
4L5target=9 comparisons=3Searching for 9 takes 3 comparisons.
5L5target=21 comparisons=4Searching for 21 takes 4 comparisons.
6L5target=5 comparisons=5Searching for 5 takes 5 comparisons.
7L5target=18 comparisons=6Searching for 18 takes 6 comparisons.
8L5target=7 comparisons=7Searching for 7 takes 7 comparisons.
9L5target=12 comparisons=8Searching for 12 takes 8 comparisons.
10L9best=1 average=4.5 worst (present)=8 absent target=8 return=4.5Best 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”.
4.05.2

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 input

Your input

Try

comparisons when searching for each value

empty
Setup

Count the comparisons for every possible target. The same algorithm does very different amounts of work depending on where (or whether) the target is.

Step 1 / 10
Java
1static int comparisons(int[] arr, int target) {
2 int count = 0;
3 for (int x : arr) {
4 count++;
5 if (x == target) return count; // best case 1, worst case n
6 }
7 return count; // absent: always n
8}
9// average over all n present targets, equally likely: (1 + 2 + ... + n) / n = (n + 1) / 2

Live state

n

8

No condition evaluated on this step
Execution trace (dry run table)Show 10 steps
StepLineStateWhat happens
1L1n=8Count the comparisons for every possible target. The same algorithm does very different amounts of work depending on where (or whether) the target is.
2L5target=14 comparisons=1Searching for 14 takes 1 comparison.
3L5target=3 comparisons=2Searching for 3 takes 2 comparisons.
4L5target=9 comparisons=3Searching for 9 takes 3 comparisons.
5L5target=21 comparisons=4Searching for 21 takes 4 comparisons.
6L5target=5 comparisons=5Searching for 5 takes 5 comparisons.
7L5target=18 comparisons=6Searching for 18 takes 6 comparisons.
8L5target=7 comparisons=7Searching for 7 takes 7 comparisons.
9L5target=12 comparisons=8Searching for 12 takes 8 comparisons.
10L9best=1 average=4.5 worst (present)=8 absent target=8 return=4.5Best 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”.
4.05.3

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 01
Average: found, uniform position5.5
Worst: absent or last10

Best, average and worst case of linear search

Interactive · edit the input

Your input

Try

comparisons when searching for each value

empty
Setup

Count the comparisons for every possible target. The same algorithm does very different amounts of work depending on where (or whether) the target is.

Step 1 / 10
Java
1static int comparisons(int[] arr, int target) {
2 int count = 0;
3 for (int x : arr) {
4 count++;
5 if (x == target) return count; // best case 1, worst case n
6 }
7 return count; // absent: always n
8}
9// average over all n present targets, equally likely: (1 + 2 + ... + n) / n = (n + 1) / 2

Live state

n

8

No condition evaluated on this step
Execution trace (dry run table)Show 10 steps
StepLineStateWhat happens
1L1n=8Count the comparisons for every possible target. The same algorithm does very different amounts of work depending on where (or whether) the target is.
2L5target=14 comparisons=1Searching for 14 takes 1 comparison.
3L5target=3 comparisons=2Searching for 3 takes 2 comparisons.
4L5target=9 comparisons=3Searching for 9 takes 3 comparisons.
5L5target=21 comparisons=4Searching for 21 takes 4 comparisons.
6L5target=5 comparisons=5Searching for 5 takes 5 comparisons.
7L5target=18 comparisons=6Searching for 18 takes 6 comparisons.
8L5target=7 comparisons=7Searching for 7 takes 7 comparisons.
9L5target=12 comparisons=8Searching for 12 takes 8 comparisons.
10L9best=1 average=4.5 worst (present)=8 absent target=8 return=4.5Best 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”.
4.05.4

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.

4.05.5

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 input

Your input

Try

backing array (capacity 1)

·

cost of each append

empty
Setup

Start with capacity 1. Each append costs 1 write, plus size copies whenever the array is full. Policy: Double (×2).

Step 1 / 24
Java
1void add(int x) {
2 if (size == data.length) { // full: allocate a bigger array
3 data = Arrays.copyOf(data, newCapacity(data.length)); // copies `size` elements
4 }
5 data[size++] = x; // the cheap part: 1 write
6}
7// doubling: newCapacity = 2 * capacity; grow-by-1: capacity + 1; Java ArrayList: capacity + capacity / 2

Live state

0 copies0 writes

capacity

1

size

0

No condition evaluated on this step
Execution trace (dry run table)Show 24 steps
StepLineStateWhat happens
1L1capacity=1 size=0Start with capacity 1. Each append costs 1 write, plus size copies whenever the array is full. Policy: Double (×2).
2L5size=1 capacity=1 cost of this append=1 total cost=1 amortised=1Append #1 costs 1. Total so far 1, so the average is 1.00 per append.
3L3capacity=1 new capacity=2 copies=1Full: allocate 2 slots and copy 1 element. This single append costs 2.
4L5size=2 capacity=2 cost of this append=2 total cost=3 amortised=1.5Append #2 costs 2. Total so far 3, so the average is 1.50 per append.
5L3capacity=2 new capacity=4 copies=2Full: allocate 4 slots and copy 2 elements. This single append costs 3.
6L5size=3 capacity=4 cost of this append=3 total cost=6 amortised=2Append #3 costs 3. Total so far 6, so the average is 2.00 per append.
7L5size=4 capacity=4 cost of this append=1 total cost=7 amortised=1.75Append #4 costs 1. Total so far 7, so the average is 1.75 per append.
8L3capacity=4 new capacity=8 copies=4Full: allocate 8 slots and copy 4 elements. This single append costs 5.
9L5size=5 capacity=8 cost of this append=5 total cost=12 amortised=2.4Append #5 costs 5. Total so far 12, so the average is 2.40 per append.
10L5size=6 capacity=8 cost of this append=1 total cost=13 amortised=2.17Append #6 costs 1. Total so far 13, so the average is 2.17 per append.
11L5size=7 capacity=8 cost of this append=1 total cost=14 amortised=2Append #7 costs 1. Total so far 14, so the average is 2.00 per append.
12L5size=8 capacity=8 cost of this append=1 total cost=15 amortised=1.88Append #8 costs 1. Total so far 15, so the average is 1.88 per append.
13L3capacity=8 new capacity=16 copies=8Full: allocate 16 slots and copy 8 elements. This single append costs 9.
14L5size=9 capacity=16 cost of this append=9 total cost=24 amortised=2.67Append #9 costs 9. Total so far 24, so the average is 2.67 per append.
15L5size=10 capacity=16 cost of this append=1 total cost=25 amortised=2.5Append #10 costs 1. Total so far 25, so the average is 2.50 per append.
16L5size=11 capacity=16 cost of this append=1 total cost=26 amortised=2.36Append #11 costs 1. Total so far 26, so the average is 2.36 per append.
17L5size=12 capacity=16 cost of this append=1 total cost=27 amortised=2.25Append #12 costs 1. Total so far 27, so the average is 2.25 per append.
18L5size=13 capacity=16 cost of this append=1 total cost=28 amortised=2.15Append #13 costs 1. Total so far 28, so the average is 2.15 per append.
19L5size=14 capacity=16 cost of this append=1 total cost=29 amortised=2.07Append #14 costs 1. Total so far 29, so the average is 2.07 per append.
20L5size=15 capacity=16 cost of this append=1 total cost=30 amortised=2Append #15 costs 1. Total so far 30, so the average is 2.00 per append.
21L5size=16 capacity=16 cost of this append=1 total cost=31 amortised=1.94Append #16 costs 1. Total so far 31, so the average is 1.94 per append.
22L3capacity=16 new capacity=32 copies=16Full: allocate 32 slots and copy 16 elements. This single append costs 17.
23L5size=17 capacity=32 cost of this append=17 total cost=48 amortised=2.82Append #17 costs 17. Total so far 48, so the average is 2.82 per append.
24L7appends=17 total cost=48 return=2.824Doubling: 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.
4.05.6

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.

4.05.7

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.