03. Complexity Analysis — Recurrence Relations & Master Theorem
1743 words
9 min read
Visual companion
Python
Type and operator map
Python Week 1: the first filter for runtime behavior
View
Revision summary
What this note is really saying
Short form
# 03. Complexity Analysis — Recurrence Relations & Master Theorem > **What problem does this solve?** While big-O gives us a way to express complexity, we need systematic methods to **derive** the complexity of recursive algorithms.

03. Complexity Analysis — Recurrence Relations & Master Theorem
What problem does this solve? While big-O gives us a way to express complexity, we need systematic methods to derive the complexity of recursive algorithms. The Master Theorem is a recipe that instantly gives us the complexity of any divide-and-conquer recurrence.
1. Recurrence Relations — The Language of Recursive Algorithms
Mental Model
A recurrence relation describes the runtime of a recursive algorithm in terms of itself. It's like a domino chain: to know how long the nth domino takes to fall, you need to know how long the (n-1)th takes, plus the time between falls.
Every recursive algorithm follows this pattern:
Where:
- (a) = number of subproblems
- (n/b) = size of each subproblem
- (f(n)) = cost of dividing and combining
Step-by-Step: Writing Recurrences
Example 1: Binary Search
python# runnable def binary_search(arr, target, left, right): if left > right: # Base case: T(0) = 1 return -1 mid = (left + right) // 2 # O(1) work if arr[mid] == target: # Check found return mid elif arr[mid] > target: return binary_search(arr, target, left, mid - 1) # 1 subproblem, size n/2 else: return binary_search(arr, target, mid + 1, right) # Recurrence: T(n) = T(n/2) + 1
Example 2: Merge Sort
python# runnable def merge_sort(arr): if len(arr) <= 1: # Base case: T(1) = 1 return arr mid = len(arr) // 2 left = merge_sort(arr[:mid]) # T(n/2) right = merge_sort(arr[mid:]) # T(n/2) return merge(left, right) # O(n) — merging # Recurrence: T(n) = 2T(n/2) + n
Example 3: Towers of Hanoi
python# runnable def hanoi(n, source, target, aux): if n == 1: # T(1) = 1 print(f"Move disk 1: {source} -> {target}") return hanoi(n - 1, source, aux, target) # T(n-1) print(f"Move disk {n}: {source} -> {target}") # 1 move hanoi(n - 1, aux, target, source) # T(n-1) # Recurrence: T(n) = 2T(n-1) + 1
Example 4: Quick Sort (Worst Case)
python# runnable def quicksort(arr, low, high): if low >= high: # Base case return pivot = partition(arr, low, high) # O(n) partition if pivot == low: # Worst case: pivot at one extreme quicksort(arr, low + 1, high) # T(n-1) else: quicksort(arr, low, pivot - 1) # T(n/2) quicksort(arr, pivot + 1, high) # T(n/2) # Best case: T(n) = 2T(n/2) + n → O(n log n) # Worst case: T(n) = T(n-1) + n → O(n²)
2. Solving Recurrences by Unwinding (Substitution)
Method
- Substitute the recurrence into itself repeatedly
- Look for the pattern
- Stop when you reach the base case
- Simplify
Example: Binary Search (T(n) = T(n/2) + 1)
pseudoStep 1: T(n) = T(n/2) + 1 Step 2: T(n) = [T(n/4) + 1] + 1 = T(n/4) + 2 Step 3: T(n) = [T(n/8) + 1] + 2 = T(n/8) + 3 Step 4: T(n) = T(n/2^k) + k
Now, when (n/2^k = 1), (k = \log_2 n).
Example: Merge Sort (T(n) = 2T(n/2) + n)
pseudoStep 1: T(n) = 2T(n/2) + n Step 2: T(n) = 2[2T(n/4) + n/2] + n = 4T(n/4) + 2n Step 3: T(n) = 4[2T(n/8) + n/4] + 2n = 8T(n/8) + 3n Step 4: T(n) = 2^k T(n/2^k) + kn
When (n/2^k = 1), (k = \log_2 n), (2^k = n).
Example: Towers of Hanoi (T(n) = 2T(n-1) + 1)
pseudoStep 1: T(n) = 2T(n-1) + 1 Step 2: T(n) = 2[2T(n-2) + 1] + 1 = 2^2 T(n-2) + 2 + 1 Step 3: T(n) = 2^2[2T(n-3) + 1] + 3 = 2^3 T(n-3) + 4 + 2 + 1 Step 4: T(n) = 2^k T(n-k) + (2^k - 1)
When (n - k = 1), (k = n - 1).
3. The Master Theorem
What It Is
The Master Theorem gives an instant solution for recurrences of the form:
where (a \ge 1), (b > 1), and (f(n) > 0).
The Three Cases
Compute (n^{\log_b a}) (the critical exponent). Compare (f(n)) with it.
Case 1: (f(n) = O(n^{\log_b a - \varepsilon})) for some (\varepsilon > 0) → (T(n) = \Theta(n^{\log_b a})) (Leaves dominate — more work is done in the subproblems)
Case 2: (f(n) = \Theta(n^{\log_b a} \log^k n)) for some (k \ge 0) → (T(n) = \Theta(n^{\log_b a} \log^{k+1} n)) (Equal work at each level)
Case 3: (f(n) = \Omega(n^{\log_b a + \varepsilon})) for some (\varepsilon > 0) → (T(n) = \Theta(f(n))) (Root dominates — more work is done at the top level)
Step-by-Step Examples
Example 1: Binary Search (T(n) = T(n/2) + 1)
| Parameter | Value |
|---|---|
| a | 1 |
| b | 2 |
| (\log_b a) | (\log_2 1 = 0) |
| (n^{\log_b a}) | (n^0 = 1) |
| f(n) | 1 = (\Theta(1) = \Theta(n^{\log_b a})) |
Case 2 applies (k = 0): (T(n) = \Theta(n^0 \log n) = \Theta(\log n))
Example 2: Merge Sort (T(n) = 2T(n/2) + n)
| Parameter | Value |
|---|---|
| a | 2 |
| b | 2 |
| (\log_b a) | (\log_2 2 = 1) |
| (n^{\log_b a}) | (n^1 = n) |
| f(n) | n = (\Theta(n) = \Theta(n^{\log_b a})) |
Case 2 applies (k = 0): (T(n) = \Theta(n \log n))
Example 3: Tree Traversal (T(n) = 2T(n/2) + 1)
| Parameter | Value |
|---|---|
| a | 2 |
| b | 2 |
| (\log_b a) | 1 |
| (n^{\log_b a}) | n |
| f(n) | 1 = (O(n^{1-\varepsilon})) for (\varepsilon = 0.5) |
Case 1 applies: (T(n) = \Theta(n))
(This is traversing a binary tree: each node visited once, O(n)).
Example 4: 4-way Merge Sort (T(n) = 4T(n/2) + n)
| Parameter | Value |
|---|---|
| a | 4 |
| b | 2 |
| (\log_b a) | (\log_2 4 = 2) |
| (n^{\log_b a}) | (n^2) |
| f(n) | n = (O(n^{2-\varepsilon})) for (\varepsilon = 0.5) |
Case 1 applies: (T(n) = \Theta(n^2))
(4 subproblems of half size is more work than the linear merge at each level).
Example 5: Quick Select (Average) (T(n) = T(n/2) + n)
| Parameter | Value |
|---|---|
| a | 1 |
| b | 2 |
| (\log_b a) | 0 |
| (n^{\log_b a}) | 1 |
| f(n) | n = (\Omega(n^{0+\varepsilon})) for (\varepsilon = 0.5) |
Case 3 applies: (T(n) = \Theta(n))
(But this is average case; worst case is T(n) = T(n-1) + n = O(n²)).
4. Recursion Tree Method
Mental Model
Draw a tree where each node is a recursive call, and label each node with the non-recursive work done at that level. Sum across levels.
Example: (T(n) = 3T(n/4) + n^2)
(Diagram)
| Level | Nodes | Work per node | Total work |
|---|---|---|---|
| 0 | 1 | (n^2) | (n^2) |
| 1 | 3 | ((n/4)^2) | (3(n/4)^2 = \frac{3}{16}n^2) |
| 2 | 9 | ((n/16)^2) | (9(n/16)^2 = \frac{9}{256}n^2) |
| k | (3^k) | ((n/4^k)^2) | ((\frac{3}{16})^k n^2) |
Total: (T(n) = n^2 \sum_{k=0}^{\log_4 n} (\frac{3}{16})^k)
Since (3/16 < 1), this is a decreasing geometric series, so the root dominates:
Three Patterns of Recursion Trees
| Pattern | Growth | Example | Total |
|---|---|---|---|
| Decreasing | Each level is a constant fraction less | (T(n) = 2T(n/8) + n) | (O(n)) |
| Equal | Each level does same total work | (T(n) = 2T(n/2) + n) | (O(n \log n)) |
| Increasing | Each level does more total work | (T(n) = 4T(n/2) + n) | (O(n^2)) |
5. Quick Reference: Common Recurrences and Their Solutions
| Recurrence | Algorithm | Solution | Case |
|---|---|---|---|
| (T(n) = T(n/2) + 1) | Binary Search | (\Theta(\log n)) | Master 2 |
| (T(n) = 2T(n/2) + 1) | Tree Traversal | (\Theta(n)) | Master 1 |
| (T(n) = 2T(n/2) + n) | Merge Sort | (\Theta(n \log n)) | Master 2 |
| (T(n) = 4T(n/2) + n) | 4-way Merge | (\Theta(n^2)) | Master 1 |
| (T(n) = T(n/2) + n) | Quick Select (avg) | (\Theta(n)) | Master 3 |
| (T(n) = 3T(n/2) + n) | Karatsuba | (\Theta(n^{\log_2 3}) \approx \Theta(n^{1.58})) | Master 1 |
| (T(n) = T(n-1) + 1) | Linear Recursion | (\Theta(n)) | Unwinding |
| (T(n) = T(n-1) + n) | Worst Quick Sort | (\Theta(n^2)) | Unwinding |
| (T(n) = 2T(n-1) + 1) | Towers of Hanoi | (\Theta(2^n)) | Unwinding |
Practice Questions
Q1. Solve using the Master Theorem: (T(n) = 9T(n/3) + n)
Q2. Solve using the Master Theorem: (T(n) = T(2n/3) + 1)
Q3. Draw the recursion tree for (T(n) = 2T(n/2) + n^2) and solve it.
Q4. Which case of the Master Theorem applies to (T(n) = 8T(n/2) + n^3)?
Q5. Solve by unwinding: (T(n) = 3T(n-1) + 1)
Q6. A recurrence (T(n) = 2T(n/4) + n^{0.5}). Which Master Theorem case?
Q7. What recurrence does Quick Sort have in the best case? What about the worst case?
Q8. Show that (T(n) = T(n-1) + 2) is (O(n)).
Q9. A divide-and-conquer algorithm divides the problem into 7 subproblems each of size (n/3), and takes (O(n^2)) to combine. What is its complexity?
Q10. Prove that (T(n) = 2T(n/2 + 17) + n) is still (O(n \log n)). (Hint: the "+17" doesn't affect asymptotic behavior for large n.)
AnswersA1. a=9, b=3, (\log_b a = \log_3 9 = 2), (n^{\log_b a} = n^2). f(n) = n = (O(n^{2-\varepsilon})) for (\varepsilon = 1). Case 1: (T(n) = \Theta(n^2)).A2. a=1, b=3/2, (\log_b a = \log_{1.5} 1 = 0), (n^0 = 1). f(n) = 1 = (\Theta(1) = \Theta(n^0)). Case 2 (k=0): (T(n) = \Theta(\log n)).A3. Root: (n^2). Level 1: (2 \cdot (n/2)^2 = n^2/2). Level 2: (4 \cdot (n/4)^2 = n^2/4). Total = (n^2(1 + 1/2 + 1/4 + ...) = 2n^2 = \Theta(n^2)).A4. a=8, b=2, (\log_b a = \log_2 8 = 3), (n^3). f(n) = n^3 = (\Theta(n^3)). Case 2 (k=0): (T(n) = \Theta(n^3 \log n)).A5. Unwinding: (T(n) = 3^k T(n-k) + (3^k - 1)/2). When n-k = 1, k = n-1. (T(n) = 3^{n-1} \cdot 1 + (3^{n-1} - 1)/2 = O(3^n)).A6. a=2, b=4, (\log_b a = \log_4 2 = 0.5), (n^{0.5}). f(n) = n^{0.5} = (\Theta(n^{0.5})). Case 2 (k=0): (T(n) = \Theta(n^{0.5} \log n)).A7. Best case: (T(n) = 2T(n/2) + n = O(n \log n)). Worst case: (T(n) = T(n-1) + n = O(n^2)).A8. (T(n) = T(n-1) + 2 = T(n-2) + 4 = ... = T(1) + 2(n-1) = O(n)).A9. a=7, b=3, (\log_b a = \log_3 7 \approx 1.77). (n^{\log_b a} \approx n^{1.77}). f(n) = n^2. Compare: n^2 vs n^{1.77}. Since n^2 grows faster, (f(n) = \Omega(n^{\log_b a + \varepsilon})), Case 3: (T(n) = \Theta(n^2)).A10. For n > threshold, (n/2 + 17 \le 0.6n) (say). So (T(n) \le 2T(0.6n) + n). Master Theorem: a=2, b=1/0.6 ≈ 1.67, (\log_b a = \log_{1.67} 2 \approx 1.36). f(n) = n is dominated by (n^{1.36}), so Case 1: (T(n) = O(n^{1.36})). Actually, the "+17" just shifts the base case — the divide step still splits roughly in half for large n, so the recurrence is essentially (2T(n/2) + n = O(n \log n)). Join Discord Previous02. Algorithm Analysis & Big-O NotationNext04. Searching Algorithms — Linear Search & Binary Search