Recurrence analysis
To predict how fast a recursive algorithm is, write its cost as a recurrence and solve it. The master theorem solves the most common shape, T(n) = a·T(n/b) + n^d, in one step.
Learning goals
- Turn a recursive method into a recurrence relation
- Count the nodes of a recursion tree and the depth of the stack
- Apply the three cases of the master theorem
- Relate time, recursion stack space and memoization
Prerequisites
Recurrence setup
A recurrence expresses the cost of an input of size n in terms of smaller inputs. Read it off the code: the number of recursive calls gives a, how much the size shrinks gives b (or the subtraction), and the work outside the calls gives f(n).
Examples: binary search T(n) = T(n/2) + O(1). Merge sort T(n) = 2T(n/2) + O(n). Factorial T(n) = T(n - 1) + O(1). Naive Fibonacci T(n) = T(n - 1) + T(n - 2) + O(1). Tower of Hanoi T(n) = 2T(n - 1) + O(1).
| Algorithm | Recurrence | Solution |
|---|---|---|
| Binary search | T(n) = T(n/2) + 1 | Θ(log n) |
| Merge sort | T(n) = 2T(n/2) + n | Θ(n log n) |
| Factorial | T(n) = T(n-1) + 1 | Θ(n) |
| Naive Fibonacci | T(n) = T(n-1) + T(n-2) + 1 | Θ(φⁿ) |
| Hanoi | T(n) = 2T(n-1) + 1 | Θ(2ⁿ) |
Recursive tree size
The number of calls is the number of nodes in the recursion tree. With branching factor a and depth h the tree has up to a^h leaves. For the linear recursion factorial it is n + 1 nodes; for naive fib(n) about 1.6^n; for Hanoi 2^n - 1.
Counting nodes is often the fastest way to find the time when the work per call is constant. Run the Fibonacci animation with n = 5, 6, 7, 8 and compare the node counts to φⁿ.
Call stack depth
The stack depth is the longest chain of pending calls, which is the height of the recursion tree, not its size. Naive Fibonacci makes an exponential number of calls but is only n deep, so it uses O(n) stack. Merge sort is log₂ n deep. Recursive quick sort is n deep in the worst case.
Depth matters for StackOverflowError and for the space complexity of the algorithm (the stack counts as memory).
| Algorithm | Calls | Depth (stack) |
|---|---|---|
| factorial(n) | n + 1 | n + 1 |
| fib(n) naive | about 1.6^n | n |
| Merge sort | 2n - 1 | log₂ n |
| Binary search | log₂ n | log₂ n |
| Quick sort (worst) | n | n |
Master theorem cases
For T(n) = a·T(n/b) + Θ(n^d) with a ≥ 1, b > 1 and d ≥ 0, compare d with the critical exponent log_b a. Case 1: if d < log_b a, then T(n) = Θ(n^(log_b a)); the leaves dominate. Case 2: if d = log_b a, then T(n) = Θ(n^d log n); every level costs the same. Case 3: if d > log_b a, then T(n) = Θ(n^d); the root dominates.
Use the calculator: drag a, b and d, or use the presets. The table shows the work per level of the tree. Whichever case you are in, the shape of that column tells you why: growing, flat or shrinking.
| Recurrence | log_b a | d | Case | Solution |
|---|---|---|---|---|
| T(n) = T(n/2) + 1 | 0 | 0 | 2 | Θ(log n) |
| T(n) = 2T(n/2) + n | 1 | 1 | 2 | Θ(n log n) |
| T(n) = 3T(n/2) + n (Karatsuba) | 1.585 | 1 | 1 | Θ(n^1.585) |
| T(n) = 7T(n/2) + n² (Strassen) | 2.807 | 2 | 1 | Θ(n^2.807) |
| T(n) = T(n/2) + n | 0 | 1 | 3 | Θ(n) |
Master theorem calculator
Change a, b and dT(n) = 2·T(n / 2) + n
Critical exponent log_b a
1
Compare with d
d = 1 = 1
Solution · case 2
Θ(n^1 log n)
Case 2: f(n) = n^1 grows at the same rate as n^1, so every level does about the same work and there are log n levels.
Work per level of the recursion tree (n = 2^7 = 128)
| Level | Subproblems | Size each | Work each | Level total | |
|---|---|---|---|---|---|
| 0 | 1 | 128 | 128 | 128 | |
| 1 | 2 | 64 | 64 | 128 | |
| 2 | 4 | 32 | 32 | 128 | |
| 3 | 8 | 16 | 16 | 128 | |
| 4 | 16 | 8 | 8 | 128 | |
| 5 | 32 | 4 | 4 | 128 | |
| 6 | 64 | 2 | 2 | 128 | |
| 7 | 128 | 1 | 1 | 128 |
Case 1: level totals grow toward the leaves. Case 2: they stay equal. Case 3: they shrink away from the root. Add them up and the biggest term decides the answer. This table assumes f(n) = n^d with no log factor.
Time-space trade-off
Spending memory can save time. Memoization stores answers to subproblems so each is computed once: naive Fibonacci uses O(n) stack and O(φⁿ) time, the memoized version uses O(n) stack plus an O(n) table and O(n) time.
The reverse also happens: recomputing instead of storing saves memory. Choose by looking at both the recurrence and the stack depth. A bottom-up loop (lesson 5.07) gets the time benefit with O(1) stack.
| Fibonacci version | Time | Extra space |
|---|---|---|
| Naive recursion | O(φⁿ) | O(n) stack |
| Memoized recursion | O(n) | O(n) table + O(n) stack |
| Bottom-up with an array | O(n) | O(n) |
| Bottom-up with two variables | O(n) | O(1) |
Practice ladder
Now try it yourself
Easy
- Write the recurrence for binary search and solve it
- Draw the recursion tree of fib(5) and count its nodes
Medium
- Solve T(n) = 4T(n/2) + n² with the master theorem
- Find the recurrence of recursive power (halving) and its solution
- Explain why T(n) = 2T(n-1) + 1 is 2ⁿ - 1
Advanced
- Solve T(n) = T(n/3) + T(2n/3) + n with a recursion tree
- State the master theorem with a log factor
- Compare the Akra-Bazzi bound for T(n) = T(n/5) + T(7n/10) + n
Key takeaways
- Read a, b and f(n) from the code to write the recurrence; solve it with a tree or the master theorem.
- Compare d with log_b a: leaves dominate, all levels equal, or the root dominates.
- Time is calls, space is depth; memoization trades memory for far fewer calls.