Recursive complexity: recurrence relations
Writing the running time of a recursive method as a recurrence T(n), and solving it by unrolling, the recursion tree and the master theorem. The exam has “write the recurrence and give the Big-O” questions.
In week 10 we wrote recursive methods. Now the question is: how fast are these methods? In loops we counted the number of rounds. In recursion, however, the method’s time depends on its own time: the time of factorial(n) is the time of factorial(n - 1) plus a little work. We write this as a recurrence relation and then solve it. The exam asks for two steps: extract the recurrence from the code, then find the Big-O from the recurrence. If you recognize the ten recurrences on this page, you can answer most of the questions by heart.
Golden rules:
- T(n) = (how many calls) · T(size of each call) + (work done outside the calls).
- Recursion that shrinks by n − 1 has linear depth; one that shrinks by n / 2 has logarithmic depth.
- Two calls + n − 1 = exponential. Two calls + n / 2 = linear or n log n.
- In a recursion tree: number of levels × work per level.
From code to recurrence
Ask three questions: how much work is there in the base case? How many recursive calls are there and with what size each? How much work is there outside the calls?
| Code | Recurrence |
|---|---|
return n * f(n - 1); | |
for (i < n) ...; f(n - 1); | |
return f(n / 2) + 1; | |
for (i < n) ...; f(n / 2); | |
f(n / 2); f(n / 2); | |
f(left half); f(right half); merge (n); | |
f(n - 1); f(n - 1); |
Constant work (c) doesn’t affect the Big-O; it is usually written as 1.
Method 1: unrolling
Substitute the recurrence into itself a few times, see the pattern, and carry it down to the base case.
,
T(n) = T(n-1) + n
= T(n-2) + (n-1) + n
= T(n-3) + (n-2) + (n-1) + n
...
= T(1) + 2 + 3 + ... + n
= 1 + 2 + ... + n = n(n+1)/2 → O(n²)
,
T(n) = T(n/2) + 1
= T(n/4) + 2
= T(n/8) + 3
...
= T(n/2^k) + k when n/2^k = 1, k = log2 n
= 1 + log2 n → O(log n)
,
T(n) = 2T(n-1) + 1
= 4T(n-2) + 2 + 1
= 8T(n-3) + 4 + 2 + 1
...
= 2^n T(0) + (2^(n-1) + ... + 2 + 1)
= 2^n - 1 → O(2^n)
The values the program computed directly (the ratios as n doubles are in parentheses):
T(n-1)+1: T(10)=10 T(1000)=1000 (x100.00) T(2000)=2000 (x2.00) T(4000)=4000 (x2.00)
T(n-1)+n: T(10)=55 T(1000)=500500 (x9100.00) T(2000)=2001000 (x4.00) T(4000)=8002000 (x4.00)
T(n/2)+1: T(16)=5 T(1024)=11 (x2.20) T(2048)=12 (x1.09) T(1048576)=21 (x1.75)
2T(n-1)+1: T(5)=31 T(10)=1023 (x33.00) T(20)=1048575 (x1025.00) T(21)=2097151 (x2.00)
Note the last line: when n increases by only 1 (20 → 21), the value doubles. This is exponential growth.
Method 2: the recursion tree
Each node shows a call’s own work; the total time is the sum of all the nodes.
level 0: n total n
level 1: n/2 n/2 total n
level 2: n/4 n/4 n/4 n/4 total n
...
level log n: 1 1 1 1 ... 1 (n leaves) total n
The number of levels is , with n work at each level: . For n = 8 that is 8 · 3 + 8 = 32, and 11 264 for n = 1024. The program found the same:
2T(n/2)+n: T(8)=32 T(1024)=11264 (x352.00) T(2048)=24576 (x2.18) T(4096)=53248 (x2.17)
The tree of has a single branch: n, n/2, n/4, … The total is . The work at the root dominates: O(n).
In the tree of the work per level is 1, 2, 4, …, n. The total is . The leaves dominate: O(n).
TIP · Who dominates in the tree? If the work per level decreases going down, the root dominates (the answer is the work at the root). If it is constant, multiply by the number of levels (a log n factor). If it increases, the leaves dominate (the answer is the number of leaves). The master theorem is the formula form of this.
Method 3: the master theorem
For recurrences of the form (, ). Compute and compare it with d:
| Case | Result | Intuition |
|---|---|---|
| the work at the root dominates | ||
| every level is equal | ||
| the leaves dominate |
| Recurrence | a, b, d | k | Result |
|---|---|---|---|
| 1, 2, 0 | 0 | → | |
| 1, 2, 1 | 0 | → | |
| 2, 2, 0 | 1 | → | |
| 2, 2, 1 | 1 | → | |
| 2, 2, 2 | 1 | → | |
| 3, 3, 1 | 1 | → | |
| 4, 2, 1 | 2 | → | |
| 8, 2, 2 | 3 | → |
Checking with the program (expected ratio when n doubles: x2 for n, x4 for n², x8 for n³):
4T(n/2)+n: T(1024)=2096128 T(2048)=8386560 (x4.00) T(4096)=33550336 (x4.00)
T(n/2)+n: T(1024)=2047 T(2048)=4095 (x2.00) T(4096)=8191 (x2.00)
2T(n/2)+1: T(1024)=2047 T(2048)=4095 (x2.00) T(4096)=8191 (x2.00)
8T(n/2)+n^2: T(256)=33488896 T(512)=268173312 (x8.01) T(1024)=2146435072 (x8.00)
2T(n/2)+n^2: T(1024)=2096128 T(2048)=8386560 (x4.00) T(4096)=33550336 (x4.00)
PITFALL · The master theorem does not apply to n − 1. In forms like or there is no division, there is subtraction. Solve these by unrolling.
Counting in real code
So that the recurrences don’t stay abstract, we counted operations in real recursive code:
static void halfLoop(int n) { // T(n) = T(n/2) + n
if (n <= 1) return;
for (int i = 0; i < n; i++) ops++;
halfLoop(n / 2);
}
static void twoCalls(int n) { // T(n) = 2T(n-1) + 1
ops++;
if (n == 0) return;
twoCalls(n - 1);
twoCalls(n - 1);
}
static void splitLoop(int lo, int hi) { // T(n) = 2T(n/2) + n
if (hi - lo < 1) return;
for (int i = lo; i <= hi; i++) ops++;
int mid = (lo + hi) / 2;
splitLoop(lo, mid);
splitLoop(mid + 1, hi);
}
halfLoop n=1024 ops=2046 halfLoop n=2048 ops=4094 halfLoop n=4096 ops=8190
bsearch(miss) n=1024 calls=11 bsearch(miss) n=2048 calls=12 bsearch(miss) n=1048576 calls=21
twoCalls n=3 calls=15 twoCalls n=10 calls=2047 twoCalls n=11 calls=4095 twoCalls n=12 calls=8191
splitLoop n=1024 ops=10240 splitLoop n=2048 ops=22528 splitLoop n=4096 ops=49152
loopThenOne n=10 ops=55 loopThenOne n=1000 ops=500500 loopThenOne n=2000 ops=2001000
For splitLoop, 1024 · 10 = 10 240: exactly n log₂ n. For loopThenOne (a loop of n + f(n − 1)), n(n+1)/2.
Space complexity
Recursion has a hidden cost: every open call holds a stack frame. The extra memory is the length of the deepest call chain.
| Method | Time | Extra memory (stack) |
|---|---|---|
Recursive list sum / size | O(n) | O(n) |
| Recursive binary search | O(log n) | O(log n) |
| Naive fib | around O(2ⁿ) | O(n) (depth n, not width) |
| Merge sort | O(n log n) | O(log n) stack + O(n) auxiliary array |
In naive fib the number of calls is exponential but the number of calls open at the same time is at most n: one branch is not left before it finishes.
Common mistakes
| Wrong | Correct |
|---|---|
| Two recursive calls are always O(2ⁿ) | Yes with n − 1; with n / 2 it is O(n) or O(n log n) |
| → O(n log n) | A geometric series: O(n) |
| → O(n²) | n per level, log n levels: O(n log n) |
| Applying the master theorem to | Use the unrolling method |
| Computing wrongly () | |
| Recursion uses no extra memory | Frames as deep as the depth: O(n) for a list |
| If there is a call inside a loop, counting only the loop | The loop work + the work of the calls, both enter the recurrence |
Test yourself
Exam-style questions. Solve them on paper first, then reveal the answer.
Write the recurrence for the running time of factorial(n) (if (n <= 1) return 1; return n * factorial(n - 1);) and give its Big-O.
Show answer
Each call does constant work (a comparison, a multiplication) and calls itself once with n − 1. Unrolling: . Linear: O(n).
Solve , . What is and the Big-O?
Show answer
Unrolling: . T(10) = 55. With the program: T(1000) = 500 500, T(2000) = 2 001 000 (x4.00). Example code: doing a loop of n in each call and calling again with n − 1 (the recursive form of selection sort).
Solve , for . What is the Big-O?
Show answer
16 → 8 → 4 → 2 → 1: four halvings, each +1, plus T(1) = 1. Total 5 = . Measurement: T(1024) = 11, T(1 048 576) = 21. This is the recurrence of binary search.
Solve , for . What is the Big-O?
Show answer
Closed form : 8 · 3 + 8 = 32. In the recursion tree the total work at each level is n, and the number of levels is . Measurement: T(1024) = 11 264, T(2048) = 24 576 (x2.18). The recurrence of merge sort.
Solve , . What is and the Big-O?
Show answer
T(1) = 1, T(2) = 3, T(3) = 7, T(4) = 15, T(5) = 31: . When n increases by one, the time doubles (T(20) = 1 048 575, T(21) = 2 097 151). The Towers of Hanoi and the g(n) method from week 10 fit this recurrence.
How many calls does f(10) make in total (including the first)? What is the Big-O?
static void f(int n) {
if (n == 0) return;
f(n - 1);
f(n - 1);
}Show answer
The number of calls is C(n) = 2C(n−1) + 1, C(0) = 1, that is . C(10) = 2047. With a counter: n = 10 → 2047, 11 → 4095, 12 → 8191. Since both calls are made with n − 1, the tree is full and exponential.
Use the master theorem to give the Big-O of .
Show answer
a = 4, b = 2, d = 1. . d = 1 < 2: the leaves dominate, . Measurement: when n doubles T grows x4.00.
What is the Big-O of this method? Write its recurrence first.
static void h(int n) {
if (n <= 1) return;
for (int i = 0; i < n; i++)
count++;
h(n / 2);
}Show answer
The loop is n work, then a single call with n/2. Unrolling: . A geometric series, the total is O(n). Counter: n = 1024 → 2046, 2048 → 4094 (x2). Master theorem: a = 1, b = 2, d = 1 > log₂1 = 0 → Θ(n).
Give the Big-O for (a) (b) (c) .
Show answer
(a) : equal, . (b) : ; in the measurement n x2 → T x8.00. (c) : ; T(1024) = 2047, T(2048) = 4095.
What is the running time of recursive binary search on a sorted array of size n? About how many calls are made for an unsuccessful search when ?
Show answer
Each call does constant work and makes a single call with half of the range: T(n) = T(n/2) + c → O(log n). With 2²⁰ elements there are 20 halvings and one more call for the empty range: the counter said 21. About 20 steps for a million elements.
For , draw the recursion tree. How many levels are there, how much work is done on each level, and what is the total?
Show answer
level 0: n = n
level 1: n/2 n/2 = n
level 2: n/4 n/4 n/4 n/4 = n
...
level k: 2^k pieces, each n/2^k = n
last level: n copies of T(1) = nWe stop when n/2^k = 1: . Each level is n work, the total is → O(n log n).What is the space complexity (extra memory) of the recursive sum(Node p) (return p.data + sum(p.next);) on a list of n nodes, and of recursive binary search?
Show answer
The extra memory is the maximum number of stack frames open at the same time. sum opens one frame for each node, and all of them wait until the base case: n + 1 frames, O(n). In binary search the depth is log n: O(log n). Written with a loop, both use O(1) memory.
Verification: Each recurrence was computed directly in verify/SEN2211/W11.java (java -Xss64m W11.java) and compared with closed formulas (e.g. n log n + n for 2T(n/2)+n: T(8) = 32, T(1024) = 11264). The operation/call counts were also counted in real recursive code; when n doubles the ratio (x2, x4, +1, ~x2.2) was seen to match the claimed class.