Resources: Let Us Group Up, Recursion_Draw
**Understanding Recursion: Detailed Notes
Recursion is a fundamental concept in programming that can seem a bit tricky at first, but it's
incredibly powerful once you grasp it.
1. What is Recursion?
At its core, recursion is when a function calls itself.
It is not just about calling itself repeatedly in an infinite loop; if you do that, you will
encounter a "Stack Overflow exception".
A common joke to help understand it is: "In order to understand recursion, one must first
understand recursion".
2. The "Inception" Analogy
To better understand recursion, think of the film Inception:
Recursive Function = A Dream: Each time a recursive function calls itself, it is like
going into another layer of a dream .
Calling Itself = Dream Inside a Dream: Just as characters go deeper into a dream
inside a dream inside a dream, a recursive function makes calls to itself, creating nested
layers .
The "Kick" (Exit Condition) = Waking Up: In Inception, a sudden jolt or "kick" is
needed to wake someone up from a dream . Similarly, every recursive function must
have an "exit condition". This condition tells the function when to stop calling itself and
start returning results .
"Limbo" (Stack Overflow Exception): If you go "too deep" in Inception without a kick,
you enter limbo, a dream realm where you can experience years in minutes . In
programming, if a recursive function does not have an exit condition, or if it is never met,
it will keep calling itself deeper and deeper until it runs out of memory on the "call stack,"
resulting in a Stack Overflow exception . This is essentially the program's version of
entering limbo .
3. Key Components of a Recursive Function
Every recursive function needs two main parts:
Recursive Call: The function calls itself, usually with a modified input that brings it closer
to the exit condition.
Base Case (Exit Condition): A condition that stops the recursion. When this condition is
met, the function returns a value without making another recursive call, allowing the
process to "work its way back up" .
4. Common Use Cases for Recursion
Recursion is particularly useful for problems that can be broken down into smaller, similar
sub-problems. It shines in situations with an unknown number of nested elements :
Navigating Tree-Like Structures:
Computer Folder Structures: Imagine navigating through folders on your
computer. Each folder can contain other folders, which contain more folders, until
you reach a folder with just files . Recursion is perfect for this, as you go down into
subfolders and then "navigate your way back up" .
Comment Threads: Similar to folder structures, comments on YouTube or blog
posts can have replies, which have replies, and so on .
Parsing Logical Expressions: For example, expressions with brackets that contain
more expressions that need to be parsed .
Calculating Mathematical Sequences: A very common example is the Fibonacci
sequence .
5. Example: The Fibonacci Sequence (Python)
Let us look at the Fibonacci sequence as a concrete example of recursion .
What it is: The Fibonacci sequence starts with 0 and 1. Subsequent numbers are
calculated by adding the two previous numbers .
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, . . .
How recursion calculates it:
To find the 10th number (where n starts from 0, so we would input 9 for the 10th
number), we need to add the 8th and 9th numbers .
But we do not know those numbers, so the function calls itself for the 8th and 9th
numbers .
This continues until we hit the base cases: if the number is 0 or 1, we return 0 or 1
respectively .
Once we hit these base cases, the results start returning up the "call stack" until the
final result is computed . For the 10th number in the sequence (index 9), the result
would be 34.
Example usage:
To get the 10th number in the sequence (index 9, since n starts
from 0)
result = fibonacci(9)
print(f"The 10th Fibonacci number is: {result}") # Output: The 10th Fibonacci
number is: 34
Think like this:
To get the nth number:
If n == 0 → return 0 (base case)
If n == 1 → return 1 (base case)
Otherwise → return Fibonacci(n-1) + Fibonacci(n-2)
def fibonacci(n):
if n == 0: # base case
return 0
elif n == 1: # base case
return 1
else:
return fibonacci(n - 1) + fibonacci(n - 2)
>>>print(fibonacci(5)) # Output: 5
fibonacci(5)
→ fibonacci(4) + fibonacci(3)
→ (fibonacci(3) + fibonacci(2)) + (fibonacci(2) + fibonacci(1))
→ (fibonacci(2)) + (fibonacci(2) + fibonacci(1) + fibonacci(0))
6. Factorial – “Multiply as You Go Back Up”
❓ What is factorial?
Factorial means: multiply the number by every number smaller than it, until you get to 1.
We write it like this:
n! = n×(n − 1)×(n − 2)×. . . ×1
Example:
4! = 4×3×2×1 = 24
3! = 3×2×1 = 6
How do we do this recursively?
We ask:
What is the smallest version of this problem we can solve immediately? (That is the base
case)
How do we move from a smaller problem to a bigger one? (That is the recursive step)
Think like this:
f actorial(1) = 1 → base case (we stop here)
f actorial(2) = 2 × factorial(1)
f actorial(3) = 3 × factorial(2)
f actorial(4) = 4 × factorial(3)
def factorial(n):
if n == 1: # base case
return 1
else:
return n * factorial(n - 1) # recursive step
print(factorial(4))
factorial(4)
→ 4 * factorial(3)
→ 3 * factorial(2)
→ 2 * factorial(1)
→ 1 (base case)
= 4 * 3 * 2 * 1 = 24
6. Performance and Efficiency Considerations
While recursive functions can lead to "nice simple code," they are not always the most
efficient way of doing things .
Call Stack Overhead: Each time a function is called (whether recursively or not),
information about that call (like local variables and where to return to) is pushed onto
something called the "call stack." When the function finishes, this information is popped
off . This "pushing and popping methods from the call stack" takes time and can "result in
poor performance" .
Redundant Calculations (in some cases): In the Fibonacci example, the recursive
solution ends up calling the function with the "same number multiple times" . For
instance, to calculate fibonacci(5) , it needs fibonacci(4) and fibonacci(3) .
fibonacci(4) then needs fibonacci(3) and fibonacci(2) . Notice fibonacci(3) is
calculated twice! This inefficiency becomes more pronounced with "higher numbers" .
Iterative Solutions Often Better for Performance: For tasks like calculating Fibonacci,
you would "actually get better performance by doing it in a loop instead of using
recursion" .
7. When to Use Recursion
As with all things in programming, it is important that you pick the right tool for the job
.
Recursion is an elegant solution for problems that inherently have a recursive structure
(like tree traversals or certain mathematical definitions).
However, if performance is critical and an iterative (loop-based) solution is
straightforward and efficient, that might be the better choice .
8. Tree Recursion
We have already covered tree recursion. The Fibonacci sequence.
fib(4)
/ \
fib(3) fib(2)
/ \ / \
fib(2) fib(1) fib(1) fib(0)
/ \
fib(1) fib(0)
Each function call splits into two more calls… That is why it’s called “tree recursion.”
Let Us Group Up
Q1: Count Down and Print
Question:
Write a function count_down(n) that prints all numbers from n to 1 recursively.
>>>count_down(5)
# Expected Output:
5
4
3
2
1
Q2: Sum of Digits
Question:
Write a recursive function sum_digits(n) that adds all the digits of a positive number.
Sample Call:
>>>sum_digits(1234)
# Expected Output:
10 (1 + 2 + 3 + 4)
Q3: Count Binary Strings
Question:
How many binary strings of length n are there where you can choose either ‘0’ or ‘1’ at each
position?
Sample Call:
count_strings(3)
# Expected Output:
8 → (2^3 strings)
Q1:
def count_down(n):
if n == 0:
return
print(n)
count_down(n - 1)
Q2:
def sum_digits(n):
if n < 10:
return n
return n % 10 + sum_digits(n // 10)
Q3:
def count_strings(n):
if n == 0:
return 1
return count_strings(n - 1) + count_strings(n - 1)