Recursion Tree Method: Visualizing Recurrence Relations
Learn the recursion tree method to solve recurrence relations like T(n)=2T(n/2)+n.
20+ years shipping performance-critical code where algorithms decide the bill. Drawn from code that ran under real load.
- ✓Basic understanding of recursion and recurrence relations
- ✓Familiarity with asymptotic notation (Big O, Theta, Omega)
- ✓Knowledge of divide-and-conquer algorithms (e.g., merge sort)
- Recursion trees visualize recursive calls as a tree, with each node representing a subproblem cost.
- Sum the costs at each level, then sum across levels to get total time complexity.
- Works for recurrences of the form T(n) = aT(n/b) + f(n).
- Useful for analyzing divide-and-conquer algorithms like merge sort and quick sort.
Imagine you're organizing a large pile of papers. You split the pile into smaller piles, give each to a friend, and they split further. The time to organize the whole pile is the time to split plus the time each friend spends. A recursion tree shows this splitting process as a tree, where each branch is a sub-pile. By adding up the time at each level, you can figure out the total time.
When analyzing recursive algorithms, you often encounter recurrence relations like T(n) = 2T(n/2) + n. While the Master Theorem provides a quick solution, it doesn't always apply, and it doesn't give you intuition. The recursion tree method fills that gap: it lets you visualize the recursion as a tree, compute the work done at each level, and sum across levels to find the total time complexity. This method is especially useful for recurrences where the Master Theorem fails, such as when the work function is not polynomial or when the recursion is unbalanced. In this tutorial, you'll learn how to draw recursion trees, compute level costs, and derive closed-form solutions. We'll cover common patterns like balanced trees (merge sort), unbalanced trees (quick sort worst-case), and recurrences with fractional divisions. By the end, you'll be able to solve recurrences confidently and understand the performance of recursive algorithms at a glance.
What is a Recursion Tree?
A recursion tree is a visual representation of the recursive calls made by an algorithm. Each node represents a subproblem, and its children represent the recursive calls it makes. The root node corresponds to the original problem of size n. The tree expands until it reaches base cases (size 1 or constant). The total time complexity is the sum of the costs of all nodes. For a recurrence T(n) = aT(n/b) + f(n), the tree has branching factor a, and each child reduces the problem size by a factor of b. The cost at each node is f(n) for the root, f(n/b) for its children, and so on. By summing the costs level by level, we can derive a closed-form expression for T(n).
Building a Recursion Tree Step by Step
Let's build a recursion tree for T(n) = 2T(n/2) + n. Start with the root node representing T(n) with cost n. Then, draw two children representing T(n/2) each with cost n/2. Continue until you reach base cases (size 1). The tree has log2(n) levels (0 to log2(n)). At level i, there are 2^i nodes, each with cost n/2^i. The total cost at level i is 2^i (n/2^i) = n. Since there are log2(n)+1 levels, total cost = n (log2(n)+1) = O(n log n). This matches the known complexity of merge sort.
Solving Unbalanced Recurrences: T(n) = T(n-1) + n
Not all recurrences are balanced. Consider T(n) = T(n-1) + n, which arises in algorithms like selection sort. The recursion tree is a chain: root cost n, child cost n-1, then n-2, ..., down to 1. There are n levels, and the total cost is the sum of 1 to n = n(n+1)/2 = O(n^2). This is a linear tree, not a branching tree. The recursion tree method still works: draw a single child at each node, sum the costs. For T(n) = T(n-1) + f(n), the total cost is sum_{k=1}^n f(k).
Handling Non-Constant Work per Level: T(n) = 2T(n/2) + n log n
Sometimes the work per node is not a simple polynomial. For example, T(n) = 2T(n/2) + n log n. The recursion tree has log n levels. At level i, there are 2^i nodes, each with cost (n/2^i) log(n/2^i). The total cost at level i is 2^i (n/2^i) log(n/2^i) = n log(n/2^i). Summing over i from 0 to log n, we get n sum_{i=0}^{log n} log(n/2^i) = n sum_{k=0}^{log n} log(2^k) (by letting k = log n - i) = n sum_{k=0}^{log n} k = n (log n)(log n + 1)/2 = O(n log^2 n). This is a case where the Master Theorem does not apply directly (since f(n) is not polynomial), but the recursion tree gives the answer.
Recursion Tree for Fractional Divisions: T(n) = T(n/3) + T(2n/3) + n
Some recurrences have uneven splits, like T(n) = T(n/3) + T(2n/3) + n, which appears in quick select or median-of-medians. The recursion tree is not perfectly balanced. The longest path from root to leaf is when we always take the 2n/3 branch, giving height log_{3/2}(n). The shortest path is log_3(n). The work per level is still n (since at each level, the sum of subproblem sizes is n). However, the number of levels is not uniform across all branches. To find an upper bound, we consider the longest path: total cost <= n * (log_{3/2}(n) + 1) = O(n log n). This is a common technique: use the longest path to bound the total work.
Common Mistakes and How to Avoid Them
- Forgetting base case costs: Always include the cost of base cases (usually O(1)). In the tree, leaves have constant cost. If the number of leaves is significant, their total cost may dominate. For T(n) = 2T(n/2) + n, leaves are at level log n, with 2^{log n} = n leaves, each O(1), total O(n). This is already accounted for in the level sum. 2. Incorrect level count: For T(n) = aT(n/b), the tree height is log_b(n). If b is not an integer, use floor/ceil carefully. 3. Assuming uniform level costs: Not all recurrences have constant level costs. Always compute the cost per node and multiply by number of nodes. 4. Ignoring the work at the root: The root cost is f(n), which is often the dominant term. 5. Using Master Theorem when it doesn't apply: The recursion tree is a safer bet.
The Infinite Loop That Took Down a Search Engine
- Always verify recurrence assumptions with a recursion tree.
- Be cautious when the Master Theorem doesn't apply; use recursion trees.
- Add recursion depth limits to prevent stack overflow in production.
- Test with worst-case inputs to catch exponential blow-ups.
- Document the recurrence and its solution for future maintainers.
python -c "import math; n=8; a=2; b=2; f=lambda n: n; levels=int(math.log(n,b))+1; print('Levels:', levels)"python -c "n=8; total=0; for i in range(4): total+= (2**i)*(n/(2**i)); print(total)"| File | Command / Code | Purpose |
|---|---|---|
| recursion_tree_example.py | def merge_sort_cost(n): | What is a Recursion Tree? |
| build_tree.py | def tree_level_costs(n): | Building a Recursion Tree Step by Step |
| unbalanced_tree.py | def total_cost(n): | Solving Unbalanced Recurrences |
| nlogn_recurrence.py | def cost_nlogn(n): | Handling Non-Constant Work per Level |
| fractional_tree.py | def cost_fractional(n): | Recursion Tree for Fractional Divisions |
| common_mistakes.py | def wrong_cost(n): | Common Mistakes and How to Avoid Them |
Key takeaways
Interview Questions on This Topic
Draw the recursion tree for T(n) = 3T(n/2) + n^2 and find its time complexity.
Frequently Asked Questions
20+ years shipping performance-critical code where algorithms decide the bill. Drawn from code that ran under real load.
That's Recursion. Mark it forged?
3 min read · try the examples if you haven't