Recap - Recursion
Recursion is a technique in programming where a function calls itself directly or indirectly to
solve a problem. It’s a powerful tool that simplifies complex problems by breaking them down
into smaller, more manageable sub-problems.
Key Components of Recursion:
● Base Case: This is the condition under which the recursive function stops calling itself.
Without a base case, the recursion would continue indefinitely, leading to a stack
overflow.
● Recursive Case: This is the part of the function where it continues to call itself with a
modified argument, gradually reducing the problem size.
Example 1: Factorial
Factorial of a number n is defined as the product of all positive integers less than or equal to n.
Mathematically, n! = n * (n-1) * (n-2) * ... * 1.
In terms of recursion:
● Base Case: factorial(0) = 1 or factorial(1) = 1
● Recursive Case: factorial(n) = n * factorial(n - 1)
Here’s how you can implement it in C:
#include <stdio.h>
int factorial(int n) {
if (n == 0 || n == 1) { // Base Case
return 1;
}
return n * factorial(n - 1); // Recursive Case
}
int main() {
int num = 5;
printf("Factorial of %d is %d\n", num, factorial(num));
return 0;
}
Example 2: Fibonacci Sequence
The Fibonacci sequence is a series of numbers where each number is the sum of the two
preceding ones. The sequence starts as 0, 1, 1, 2, 3, 5, 8, ...
In terms of recursion:
● Base Case: fibonacci(0) = 0, fibonacci(1) = 1
● Recursive Case: fibonacci(n) = fibonacci(n-1) + fibonacci(n-2)
#include <stdio.h>
int fibonacci(int n) {
if (n == 0) return 0; // Base Case 1
if (n == 1) return 1; // Base Case 2
return fibonacci(n - 1) + fibonacci(n - 2); // Recursive Case
}
int main() {
int num = 7;
printf("Fibonacci of %d is %d\n", num, fibonacci(num));
return 0;
}
While recursion makes the Fibonacci sequence easy to understand, it's not efficient due to
repeated calculations, which we'll discuss later.
Example 3: Sum of Digits
This function adds all the digits of a given number. For example, the sum of digits of 123 is 1 + 2
+ 3 = 6.
● Base Case: If the number is 0, the sum is 0.
● Recursive Case: sumOfDigits(n) = (n % 10) + sumOfDigits(n / 10)
#include <stdio.h>
int sumOfDigits(int n) {
if (n == 0) { // Base Case
return 0;
}
return (n % 10) + sumOfDigits(n / 10); // Recursive Case
}
int main() {
int num = 1234;
printf("Sum of digits of %d is %d\n", num, sumOfDigits(num));
return 0;
}
Recursion vs. Iteration
● Recursion: It’s often more elegant and concise for problems that naturally fit a recursive
structure. However, recursion can lead to higher memory usage due to the function call
stack.
● Iteration: Iterative solutions are usually more memory-efficient because they don’t
involve the overhead of multiple function calls. However, they can sometimes be less
intuitive, especially for problems like tree traversals or permutations.
Example: Factorial
Let’s compare the recursive and iterative approaches to calculating the factorial of a number.
Recursive:
int factorial(int n) {
if (n == 0 || n == 1) {
return 1;
}
return n * factorial(n - 1);
}
Iterative:
int factorialIterative(int n) {
int result = 1;
for(int i = 2; i <= n; i++) {
result *= i;
}
return result;
}
When to Use Recursion?
● Use recursion when the problem can naturally be divided into similar sub-problems.
● Avoid recursion if it leads to excessive memory use or stack overflow.
Common Recursive Problems
Problem 1: Binary Search
Binary Search is an efficient algorithm for finding an element in a sorted array. It works by
repeatedly dividing the search interval in half.
Algorithm:
1. Compare the target value with the middle element of the array.
2. If the target value matches the middle element, the search is done.
3. If the target value is less than the middle element, search in the left half.
4. If the target value is greater than the middle element, search in the right half.
#include <stdio.h>
int binarySearch(int arr[], int low, int high, int x) {
if (high >= low) {
int mid = low + (high - low) / 2;
if (arr[mid] == x) return mid;
if (arr[mid] > x) return binarySearch(arr, low, mid - 1, x);
return binarySearch(arr, mid + 1, high, x);
}
return -1; // Element not found
}
int main() {
int arr[] = {2, 3, 4, 10, 40};
int n = sizeof(arr) / sizeof(arr[0]);
int x = 10;
int result = binarySearch(arr, 0, n - 1, x);
(result == -1) ? printf("Element is not present in array\n") : printf("Element is present at index %d\n",
result);
return 0;
}
Problem 2: String Reversal
Reversing a string is a common problem where recursion can be used effectively. The idea is to
swap the characters from the ends of the string moving inward.
#include <stdio.h>
void reverseString(char str[], int start, int end) {
if (start >= end) return; // Base Case
char temp = str[start];
str[start] = str[end];
str[end] = temp;
reverseString(str, start + 1, end - 1); // Recursive Case
}
int main() {
char str[] = "recursion";
reverseString(str, 0, sizeof(str) - 2); // -2 to exclude the null character
printf("Reversed string is %s\n", str);
return 0;
}
Problem 3: Find the Maximum Element in an Array
Finding the maximum element in an array can also be done recursively by comparing the
current element with the maximum of the rest of the array.
#include <stdio.h>
int findMax(int arr[], int n) {
if (n == 1) return arr[0]; // Base Case
int maxOfRest = findMax(arr, n - 1); // Recursive Case
return (arr[n-1] > maxOfRest) ? arr[n-1] : maxOfRest;
}
int main() {
int arr[] = {12, 10, 30, 50, 100, 40};
int n = sizeof(arr) / sizeof(arr[0]);
printf("The maximum element is %d\n", findMax(arr, n));
return 0;
}
Debugging Recursive Code
Common Issues:
● Stack Overflow: This occurs when the recursion depth is too deep. It’s a common issue
in recursion when the base case is not properly defined or when the problem size
doesn’t reduce sufficiently with each recursive call.
● Infinite Recursion: Happens if there’s no proper base case or if the recursive case
doesn’t converge towards the base case. This leads to the function calling itself
indefinitely.
Tips for Debugging
Print Statements: Use print statements inside the recursive function to trace the function calls
and understand the flow of recursion.
Example: In the factorial function, you can print the current value of n before each recursive call
to see the sequence of calls:
int factorial(int n) {
printf("Calling factorial(%d)\n", n);
if (n == 0 || n == 1) {
return 1;
}
return n * factorial(n - 1);
}
● Base Case Verification: Always double-check that your base case is correct and that it
will be reached in every possible execution path. This is critical for preventing infinite
recursion.
● Test with Simple Cases: Start testing your recursive functions with the simplest
possible cases (e.g., the base case) to ensure that your function handles them correctly.
● Dry Run: Manually trace through the recursive function with a small input to see if it
behaves as expected. This helps you understand how the function evolves with each
recursive call.
● Use a Debugger: If your environment supports it, use a debugger to step through the
recursive calls and examine the state of variables at each step.
Practice Problems
Problem 1: Calculate the Power of a Number
Write a recursive function to calculate the power of a number x^n. The function should take two
arguments, the base x and the exponent n.
Example:
Input: x = 2, n = 3
Output: 8
Hint: Use the relation x^n = x * x^(n-1).
Problem 2: Palindrome Check
Write a recursive function to check if a string is a palindrome. A palindrome is a word that reads
the same backward as forward.
Example:
Input: str = "madam"
Output: "madam" is a palindrome
Hint: Compare the first and last characters of the string, then recursively check the substring
that excludes these characters.
Problem 3: GCD of Two Numbers
Write a recursive function to find the greatest common divisor (GCD) of two numbers using
Euclid's algorithm.
Example:
Input: a = 48, b = 18
Output: 6
Hint: Use the relation GCD(a, b) = GCD(b, a % b).
Problem 4: Recursive Sum of an Array
Write a recursive function that calculates the sum of all elements in an array.
Example:
Input: arr = [1, 2, 3, 4, 5]
Output: 15
Hint: Sum the first element with the sum of the remaining elements.
Problem 5: Generate All Subsets of a Set
Write a recursive function to generate all possible subsets of a given set of integers. This is a
classic problem often referred to as the "power set" problem.
Example:
Input: set = {1, 2, 3}
Output: { {}, {1}, {2}, {3}, {1, 2}, {1, 3}, {2, 3}, {1, 2, 3} }
Hint: For each element in the set, you can either include it in a subset or exclude it, leading to a
recursive exploration of both possibilities.