Chapter 1: Introduction to Data Structures and Algorithms
1.05Beginner6 topics14 min2 interactive animations

Classifying algorithms

Algorithms can also be classified by how they are written and how they run: iterative or recursive, deterministic or randomised, exact or approximate, online or offline, sequential or parallel, in place or not. These labels describe trade-offs you will meet in every chapter.

Learning goals

  • Place an algorithm on each classification axis
  • Convert between iterative and recursive forms
  • Explain when randomisation and approximation are the right choice
  • Tell online from offline algorithms

Prerequisites

Algorithm fundamentals
1.05.1

Iterative vs recursive

An iterative algorithm repeats with a loop; a recursive algorithm solves a problem by calling itself on a smaller one. Anything recursive can be written iteratively (using your own stack if needed) and vice versa.

Recursion matches self-similar problems (trees, divide and conquer) and costs a stack frame per call. Loops are lighter. Step through the factorial animation to watch the call stack grow and unwind.

Java
1static long factIterative(int n) { long r = 1; for (int i = 2; i <= n; i++) r *= i; return r; }
2static long factRecursive(int n) { return n <= 1 ? 1 : n * factRecursive(n - 1); }

Factorial: calls, base case and return path

Interactive · edit the input

Your input

Try

Recursion tree · 1 call

factorial(4)…
Running now
Call

Call factorial(4). It is frame number 1 on the call stack.

Step 1 / 20
Java
1static long factorial(int n) {
2 if (n == 0) return 1; // base case
3 return n * factorial(n - 1); // recursive case
4}

Live state

1 calls

n

4

depth

1

No condition evaluated on this step

Call stack (top first)

  1. factorial(4)
Execution trace (dry run table)Show 20 steps
StepLineStateWhat happens
1L1n=4 depth=1Call factorial(4). It is frame number 1 on the call stack.
2L2n=4n is not 0, so this call cannot answer yet. It needs factorial(3).
3L3n=4Pause factorial(4) and call factorial(3). The pending multiplication by 4 waits on the stack.
4L1n=3 depth=2Call factorial(3). It is frame number 2 on the call stack.
5L2n=3n is not 0, so this call cannot answer yet. It needs factorial(2).
6L3n=3Pause factorial(3) and call factorial(2). The pending multiplication by 3 waits on the stack.
7L1n=2 depth=3Call factorial(2). It is frame number 3 on the call stack.
8L2n=2n is not 0, so this call cannot answer yet. It needs factorial(1).
9L3n=2Pause factorial(2) and call factorial(1). The pending multiplication by 2 waits on the stack.
10L1n=1 depth=4Call factorial(1). It is frame number 4 on the call stack.
11L2n=1n is not 0, so this call cannot answer yet. It needs factorial(0).
12L3n=1Pause factorial(1) and call factorial(0). The pending multiplication by 1 waits on the stack.
13L1n=0 depth=5Call factorial(0). It is frame number 5 on the call stack.
14L2n=0Base case reached: no more recursion. Everything below this point is the return path.
15L2n=0 return=1return 1.
16L3n=1 return=1factorial(1) resumes: 1 × 1 = 1. Return it to the caller.
17L3n=2 return=2factorial(2) resumes: 2 × 1 = 2. Return it to the caller.
18L3n=3 return=6factorial(3) resumes: 3 × 2 = 6. Return it to the caller.
19L3n=4 return=24factorial(4) resumes: 4 × 6 = 24. Return it to the caller.
20L3return=24factorial(4) = 24, using 5 calls and a maximum stack depth of 5.
1.05.2

Deterministic vs randomized

A deterministic algorithm follows the same steps for the same input. A randomised algorithm uses random choices, so its running time (Las Vegas: always correct) or even its answer (Monte Carlo: correct with high probability) varies.

Randomised quick sort picks a random pivot so no input is consistently bad. A Bloom filter is Monte Carlo: it may report false positives but never false negatives.

KindAnswerTimeExample
Deterministicalways the samefixed for an inputmerge sort
Las Vegasalways correctrandomrandomised quick sort
Monte Carlocorrect with high probabilityfixedMiller-Rabin primality test
1.05.3

Exact vs approximation

An exact algorithm returns the optimal answer. An approximation returns an answer within a provable factor of optimal when exact solutions are too expensive (many NP-hard problems).

Example: the greedy 2-approximation for vertex cover picks both ends of any uncovered edge and is guaranteed to use at most twice the optimal number of vertices. Chapter 61 covers approximation formally.

1.05.4

Online vs offline

An online algorithm must answer as each input item arrives, without seeing the future. An offline algorithm sees all of the input first and can reorder or preprocess it.

The running median is online: after every new number it must report the median so far. Sorting all numbers first is offline. Watch the two-heap solution update after every arrival.

Running median with two heaps

Interactive · edit the input

Your input

Try

stream

50
151
12
33
84
75
96
107

low (max-heap)

empty

high (min-heap)

empty

medians so far

empty
Setup

Split the numbers seen so far into a lower half (max-heap, top = largest of the lower half) and an upper half (min-heap, top = smallest of the upper half). The median sits at the two tops. Heaps are drawn with their top first.

Step 1 / 37
Java
1class MedianFinder {
2 PriorityQueue<Integer> low = new PriorityQueue<>(Comparator.reverseOrder()); // max-heap
3 PriorityQueue<Integer> high = new PriorityQueue<>(); // min-heap
4
5 void add(int x) {
6 low.add(x);
7 high.add(low.poll()); // every low value <= every high value
8 if (high.size() > low.size()) {
9 low.add(high.poll()); // keep low the same size or one larger
10 }
11 }
12
13 double median() {
14 return low.size() > high.size() ? low.peek() : (low.peek() + high.peek()) / 2.0;
15 }
16}

Live state

0 inserted

low.size

0

high.size

0

No condition evaluated on this step
Execution trace (dry run table)Show 37 steps
StepLineStateWhat happens
1L1low.size=0 high.size=0Split the numbers seen so far into a lower half (max-heap, top = largest of the lower half) and an upper half (min-heap, top = smallest of the upper half). The median sits at the two tops. Heaps are drawn with their top first.
2L6x=5 low.size=1 high.size=0low.add(5). The new value always enters the lower half first.
3L7x=5 moved=5 low.size=0 high.size=1high.add(low.poll()): the largest value of the lower half (5) moves up. This guarantees every low value is <= every high value.
4L8low.size=0 high.size=1The upper half is bigger than the lower half, so rebalance.
5L9moved=5 low.size=1 high.size=0Move 5 back to the lower half.
6L14median=5 low.size=1 high.size=0Odd count: the median is the top of low, 5.
7L6x=15 low.size=2 high.size=0low.add(15). The new value always enters the lower half first.
8L7x=15 moved=15 low.size=1 high.size=1high.add(low.poll()): the largest value of the lower half (15) moves up. This guarantees every low value is <= every high value.
9L8low.size=1 high.size=1Sizes are balanced: low is equal or one larger.
10L14median=10 low.size=1 high.size=1Even count: the median is the average of the two tops, (5 + 15) / 2 = 10.
11L6x=1 low.size=2 high.size=1low.add(1). The new value always enters the lower half first.
12L7x=1 moved=5 low.size=1 high.size=2high.add(low.poll()): the largest value of the lower half (5) moves up. This guarantees every low value is <= every high value.
13L8low.size=1 high.size=2The upper half is bigger than the lower half, so rebalance.
14L9moved=5 low.size=2 high.size=1Move 5 back to the lower half.
15L14median=5 low.size=2 high.size=1Odd count: the median is the top of low, 5.
16L6x=3 low.size=3 high.size=1low.add(3). The new value always enters the lower half first.
17L7x=3 moved=5 low.size=2 high.size=2high.add(low.poll()): the largest value of the lower half (5) moves up. This guarantees every low value is <= every high value.
18L8low.size=2 high.size=2Sizes are balanced: low is equal or one larger.
19L14median=4 low.size=2 high.size=2Even count: the median is the average of the two tops, (3 + 5) / 2 = 4.
20L6x=8 low.size=3 high.size=2low.add(8). The new value always enters the lower half first.
21L7x=8 moved=8 low.size=2 high.size=3high.add(low.poll()): the largest value of the lower half (8) moves up. This guarantees every low value is <= every high value.
22L8low.size=2 high.size=3The upper half is bigger than the lower half, so rebalance.
23L9moved=5 low.size=3 high.size=2Move 5 back to the lower half.
24L14median=5 low.size=3 high.size=2Odd count: the median is the top of low, 5.
25L6x=7 low.size=4 high.size=2low.add(7). The new value always enters the lower half first.
26L7x=7 moved=7 low.size=3 high.size=3high.add(low.poll()): the largest value of the lower half (7) moves up. This guarantees every low value is <= every high value.
27L8low.size=3 high.size=3Sizes are balanced: low is equal or one larger.
28L14median=6 low.size=3 high.size=3Even count: the median is the average of the two tops, (5 + 7) / 2 = 6.
29L6x=9 low.size=4 high.size=3low.add(9). The new value always enters the lower half first.
30L7x=9 moved=9 low.size=3 high.size=4high.add(low.poll()): the largest value of the lower half (9) moves up. This guarantees every low value is <= every high value.
31L8low.size=3 high.size=4The upper half is bigger than the lower half, so rebalance.
32L9moved=7 low.size=4 high.size=3Move 7 back to the lower half.
33L14median=7 low.size=4 high.size=3Odd count: the median is the top of low, 7.
34L6x=10 low.size=5 high.size=3low.add(10). The new value always enters the lower half first.
35L7x=10 moved=10 low.size=4 high.size=4high.add(low.poll()): the largest value of the lower half (10) moves up. This guarantees every low value is <= every high value.
36L8low.size=4 high.size=4Sizes are balanced: low is equal or one larger.
37L14median=7.5 low.size=4 high.size=4Even count: the median is the average of the two tops, (7 + 8) / 2 = 7.5.
1.05.5

Sequential vs parallel

A sequential algorithm does one step at a time. A parallel algorithm splits work across threads or cores. Divide and conquer algorithms such as merge sort parallelise naturally because the two halves are independent.

Parallelism helps only when the work is large enough to pay for the splitting and joining. In Java, Arrays.parallelSort and parallel streams do this for you; measure before trusting them.

Java
1int[] big = new Random(1).ints(5_000_000).toArray();
2Arrays.parallelSort(big); // splits, sorts in parallel, merges
3long evens = Arrays.stream(big).parallel().filter(x -> x % 2 == 0).count();
1.05.6

In-place vs out-of-place

An in-place algorithm uses O(1) extra memory beyond its input (or O(log n) for a recursion stack) and rearranges the input itself: insertion sort, heap sort, quick sort. An out-of-place one needs memory that grows with the input: merge sort's buffer, counting sort's output array.

In-place saves memory but destroys the original order, which matters if the caller still needs the input.

AlgorithmExtra spaceIn place?
Insertion sortO(1)yes
Heap sortO(1)yes
Quick sortO(log n) stackyes (usually counted as in place)
Merge sort (arrays)O(n)no
Counting sortO(n + k)no

Practice ladder

Now try it yourself

Easy

  • Write factorial iteratively and recursively
  • Is binary search iterative or recursive? Can it be both?

Medium

  • Explain why randomised quick sort is Las Vegas
  • Is “find the median of a stream” online or offline?

Advanced

  • Convert a recursive depth-first traversal into an iterative one with an explicit stack

Key takeaways

  • Recursive and iterative are two forms of the same computation with different stack costs.
  • Randomisation removes bad inputs; approximation trades exactness for tractability.
  • Online algorithms answer as data arrives; in-place algorithms save memory.