Chapter 2 - Understanding Recursion
Chapter 2 - Understanding Recursion
Objective:
1. Introduction to Recursion
Recursion is a programming technique where a function calls itself directly or indirectly to solve
a problem. It's a powerful method used to break down complex problems into simpler, smaller
problems, making them easier to solve. This approach mimics the way humans often solve
problems by dividing them into more manageable sub-problems.
Example: Consider the process of solving a maze. One possible strategy is to take a step
forward, and if you hit a dead end, you backtrack and try a different path. This is similar to how
recursion works, constantly trying different paths until the correct solution is found.
Simple Definition: A recursive function is a function that calls itself in order to solve a smaller
instance of the same problem.
1. Base Case: The simplest case where the function doesn't need to call itself further. It’s
the condition that stops the recursion.
2. Recursive Case: This is where the function calls itself with modified arguments,
gradually approaching the base case.
Illustrative Example: A simple example of recursion can be seen in calculating the factorial of
a number n:
n!=n×(n−1)×(n−2)×…×1
Recursion is widely used for problems that can be divided into smaller sub-problems. Some
common use cases include:
Advantages Disadvantages
Makes code shorter and cleaner. Can be inefficient due to repeated calculations.
Solves complex problems elegantly. Consumes more memory (stack space).
Mimics real-world problem-solving. Can lead to stack overflow if not handled correctly.
While recursion and iteration can both achieve the same results, recursion is often more intuitive
for problems involving self-similarity. However, iteration is typically more efficient in terms of
memory and speed.
Recursion Iteration
Function calls itself repeatedly. Uses loops (for, while) to repeat.
Requires more memory (stack usage). Requires less memory.
Simpler to implement in certain cases. Often more efficient in execution.
#include <stdio.h>
int main() {
int number = 5;
printf("Factorial of %d is %d\n", number, factorial(number));
return 0;
}
Explanation:
• The factorial function calls itself with n-1 until n reaches 0, where the base case
returns 1.
• The recursive calls then multiply each value of n as the function unwinds back up the
stack.
Summary
Objective:
When implementing recursion, two key components are always present: the base case and the
recursive case. These components define how a recursive function operates and when it should
terminate.
• Base Case: The condition under which the recursion stops. It represents the simplest
instance of the problem, and no further recursive calls are needed.
• Recursive Case: This is where the function continues to call itself with a modified
version of the original problem, working toward reaching the base case.
Understanding these two concepts is crucial, as they form the foundation of every recursive
function.
The base case acts as the exit condition for the recursive function. Without it, the function would
call itself indefinitely, leading to infinite recursion and eventually a stack overflow (exceeding
the memory limit allocated for function calls).
Example: In calculating the factorial of a number n the base case is when n=0:
0!=1
This means that when the function reaches n=0, it should stop making further recursive calls.
The recursive case defines how the problem should be broken down into smaller sub-problems.
In this part of the function, you call the function itself with updated parameters.
Let's revisit the factorial example to clearly distinguish the base case from the recursive case:
#include <stdio.h>
int main() {
int number = 5;
printf("Factorial of %d is %d\n", number, factorial(number));
return 0;
}
Explanation:
The function continues calling itself with smaller values of n until it reaches 0, where it
terminates.
• Base Case: Prevents infinite recursion and ensures that the function eventually stops.
• Recursive Case: Breaks down the original problem into smaller problems, gradually
moving toward the base case.
Important Note: A recursive function must always progress toward the base case. Otherwise, it
will run indefinitely, causing a stack overflow.
Let’s trace the calculation of factorial(3) to illustrate the base and recursive cases:
1. factorial(3) → 3 * factorial(2)
2. factorial(2) → 2 * factorial(1)
3. factorial(1) → 1 * factorial(0)
4. factorial(0) → 1 (Base Case reached)
The recursion then unwinds:
• factorial(1) = 1 * 1 = 1
• factorial(2) = 2 * 1 = 2
• factorial(3) = 3 * 2 = 6
The Fibonacci sequence is a series of numbers in which each number after the first two is
the sum of the two preceding ones. It typically starts with 0 and 1. The sequence is
defined as follows:
• F(0)=0
• F(1)=1
• F(n)=F(n−1)+F(n−2) for n≥2
Code Example:
#include <stdio.h>
int main() {
int number = 5;
printf("Fibonacci of %d is %d\n", number, fibonacci(number));
return 0;
}
Example 2: Finding the Greatest Common Divisor (GCD) Using Euclid's Algorithm
GCD(a,b)=GCD(b,amod b)
With the base case being when b=0, the GCD is a.
Code Example:
#include <stdio.h>
int main() {
int a = 48, b = 18;
printf("GCD of %d and %d is %d\n", a, b, gcd(a, b));
return 0;
}
Summary
• The base case is the simplest condition where the recursion stops.
• The recursive case breaks the problem into smaller sub-problems, making recursive calls
that eventually reach the base case.
• Identifying and correctly implementing these two cases is essential for writing efficient
and accurate recursive functions.
Objective:
Recursion and iteration are two fundamental techniques for repeating tasks in programming.
While both can achieve similar outcomes, they work in fundamentally different ways.
• Recursion: A function calls itself to solve smaller instances of the same problem.
• Iteration: Uses loops (for, while) to repeatedly execute a block of code until a
condition is met.
Let's compare recursion and iteration using two classic examples: factorial calculation and the
Fibonacci sequence.
Recursive Implementation:
#include <stdio.h>
int factorial_recursive(int n) {
// Base Case
if (n == 0) {
return 1;
}
// Recursive Case
return n * factorial_recursive(n - 1);
}
int main() {
int number = 5;
printf("Factorial (Recursive) of %d is %d\n", number,
factorial_recursive(number));
return 0;
}
Iterative Implementation:
#include <stdio.h>
int factorial_iterative(int n) {
int result = 1;
for (int i = 1; i <= n; i++) {
result *= i;
}
return result;
}
int main() {
int number = 5;
printf("Factorial (Iterative) of %d is %d\n", number,
factorial_iterative(number));
return 0;
}
Comparison:
• The recursive solution is shorter and more intuitive but uses more memory due to
multiple function calls.
• The iterative solution is more efficient in terms of memory and execution speed but
requires more lines of code.
Recursive Implementation:
#include <stdio.h>
int fibonacci_recursive(int n) {
// Base Cases
if (n == 0) return 0;
if (n == 1) return 1;
// Recursive Case
return fibonacci_recursive(n - 1) + fibonacci_recursive(n - 2);
}
int main() {
int number = 5;
printf("Fibonacci (Recursive) of %d is %d\n", number,
fibonacci_recursive(number));
return 0;
}
Iterative Implementation:
#include <stdio.h>
int fibonacci_iterative(int n) {
int a = 0, b = 1, next;
if (n == 0) return a;
for (int i = 2; i <= n; i++) {
next = a + b;
a = b;
b = next;
}
return b;
}
int main() {
int number = 5;
printf("Fibonacci (Iterative) of %d is %d\n", number,
fibonacci_iterative(number));
return 0;
}
Comparison:
• The recursive solution is elegant but inefficient for large values of n because it
recalculates values multiple times, leading to exponential time complexity.
• The iterative solution has linear time complexity, making it far more efficient.
Sometimes, recursion can be converted into iteration using data structures like stacks to simulate
the function call stack.
Recursive Factorial:
int factorial_recursive(int n) {
if (n == 0) return 1;
return n * factorial_recursive(n - 1);
}
Iterative Equivalent:
int factorial_iterative(int n) {
int result = 1;
for (int i = 1; i <= n; i++) {
result *= i;
}
return result;
}
7. Practical Considerations
• Tail Recursion: If possible, use tail recursion, where the recursive call is the last
operation in the function. Many compilers optimize tail recursion, converting it to
iteration internally, reducing memory usage.
• Space Complexity: Recursive functions generally have higher space complexity due to
the additional call stack. Iterative functions, on the other hand, maintain a single
execution frame.
8. Real-World Applications
Recursion Iteration
Solving complex problems like tree/graph Iterating through arrays, performing
traversal, backtracking algorithms (e.g., maze mathematical calculations, or looping
solving, N-Queens problem). through user inputs.
Sorting algorithms (quicksort, mergesort). Simple tasks like finding the
maximum/minimum in a list or searching.
Summary
• Recursion involves a function calling itself, and it is best suited for problems with a
natural recursive structure.
• Iteration involves using loops and is generally more efficient in terms of memory and
execution speed.
• Understanding when to use recursion versus iteration is crucial for writing efficient code.
Objective:
The factorial of a non-negative integer n (denoted as n!) is the product of all positive integers up
to n:
n!=n×(n−1)×(n−2)×…×1
Implementation in C
#include <stdio.h>
int main() {
int number;
printf("Enter a number to find its factorial: ");
scanf("%d", &number);
if (number < 0) {
printf("Factorial is not defined for negative numbers.\n");
} else {
printf("Factorial of %d is %d\n", number, factorial(number));
}
return 0;
}
Explanation:
• The factorial function calls itself with n - 1 until n reaches 0, at which point it returns
1.
• The recursion then unwinds, multiplying the values to produce the final result.
Test Cases:
• Input: 5 → Output: Factorial of 5 is 120
• Input: 0 → Output: Factorial of 0 is 1
The Fibonacci sequence is a series of numbers where each number is the sum of the two
preceding ones:
Implementation in C
#include <stdio.h>
int main() {
int terms;
printf("Enter the number of terms for the Fibonacci sequence: ");
scanf("%d", &terms);
Explanation:
• The fibonacci function calls itself twice for each value of n, until reaching the base
cases F(0) and F(1).
• The main function iterates to print each term of the sequence up to the specified number.
Test Cases:
Fibonacci Analysis:
• Time Complexity: O(2n) – Each call splits into two additional calls, resulting in
exponential growth.
• Space Complexity: O(n) – The depth of recursion depends on n.
Note: The recursive Fibonacci solution is inefficient for large n due to redundant calculations.
Implementing it iteratively or using memoization (storing already calculated results)
significantly improves performance.
Memoization in Fibonacci:
#include <stdio.h>
// Base Cases
if (n == 0) return 0;
if (n == 1) return 1;
// Recursive Case
memo[n] = fibonacci_memo(n - 1) + fibonacci_memo(n - 2);
return memo[n];
}
int main() {
int terms;
printf("Enter the number of terms for the Fibonacci sequence: ");
scanf("%d", &terms);
Explanation:
• The memo array stores already calculated Fibonacci values, preventing redundant
computations.
• This optimization reduces the time complexity to O(n).
6. Practice Problems
7. Summary
• Recursion is a powerful tool for solving complex problems by breaking them into smaller
sub-problems.
• The factorial and Fibonacci problems showcase how recursion can be used in
mathematical computations.
• While recursion can be elegant, it’s important to consider efficiency, especially with
problems like the Fibonacci sequence, where optimizations like memoization can be
applied.
Ques�ons: Understanding Recursion
Sec�on A: Theory
a) List three advantages and three disadvantages of using recursion over itera�on.
b) Give one example of a problem where recursion is more suitable and one where itera�on is more
efficient. Jus�fy your choices.
a) What is memoiza�on, and how does it improve the efficiency of recursive func�ons?
b) Provide a scenario where memoiza�on significantly enhances the performance of a recursive solu�on.
Sec�on B: Prac�cal
Write a recursive C program to find the GCD (Greatest Common Divisor) of two numbers using Euclid's
algorithm. Include both the base case and the recursive case in your explana�on.
Ques�on 7: Prac�cal Applica�on with Memoiza�on (20 Marks) Using C, implement a program that
calculates the factorial of a given number using recursion with memoiza�on.
a) Write the code and explain how memoiza�on is implemented in the program.
b) Describe how memoiza�on improves the performance of the program compared to the non-
memoized version.
Ques�on 8: Complex Recursive Problems a) Explain how recursion can be used to solve the Tower of
Hanoi problem.
b) Write a recursive func�on in C to reverse a string and explain how it works.