Recursion is a programming technique where a function calls itself in order to solve a problem. The idea is to break down a complex problem into smaller, simpler subproblems. Each recursive call solves a smaller instance of the original problem until a base case is reached, which stops the recursion.
A recursive function has two main components:
The factorial of a number n (written as n!) is the product of all positive integers less than or equal to n. A recursive definition of factorial is:
factorial(0) = 1factorial(n) = n * factorial(n-1)In C++, a recursive implementation of factorial would look like this:
#include <iostream>
using namespace std;
int factorial(int n) {
// Base case: factorial(0) is 1
if (n == 0)
return 1;
// Recursive case: n * factorial(n-1)
return n * factorial(n - 1);
}
int main() {
int num = 5;
cout << "Factorial of " << num << " is " << factorial(num) << endl; // Output: 120
return 0;
}
In this example:
n == 0, which stops the recursion.n * factorial(n-1), where the function keeps calling itself with a smaller value of n.Each recursive call pushes a new frame onto the call stack, holding the current state of the function. When the base case is reached, the function starts returning values and the call stack begins to unwind.
For example, calling factorial(3) will result in the following sequence:
factorial(3) calls factorial(2)factorial(2) calls factorial(1)factorial(1) calls factorial(0)factorial(0) returns 1 (base case)factorial(1) returns 1 * 1 = 1factorial(2) returns 2 * 1 = 2factorial(3) returns 3 * 2 = 6Analyzing recursive algorithms involves understanding their time complexity and space complexity. Let’s break down how to analyze them.
The time complexity of a recursive algorithm depends on:
To analyze this, we often use a recurrence relation, which expresses the time complexity of the algorithm in terms of itself.
For factorial(n), the recurrence relation is:
T(n) = T(n-1) + O(1)Here, T(n) represents the time complexity of computing factorial(n). The function does a constant amount of work (O(1)) and calls itself with n-1. This recurrence can be solved as follows:
T(n) = T(n-1) + O(1)T(n-1) = T(n-2) + O(1)T(n) = O(1) + O(1) + ... (n times)Thus, the time complexity of factorial(n) is O(n), because the function is called n times, and each call takes constant time.
The Fibonacci sequence can also be defined recursively:
F(n) = F(n-1) + F(n-2)F(0) = 0, F(1) = 1Here’s a C++ implementation:
#include <iostream>
using namespace std;
int fibonacci(int n) {
if (n == 0)
return 0;
if (n == 1)
return 1;
return fibonacci(n-1) + fibonacci(n-2);
}
int main() {
int num = 5;
cout << "Fibonacci of " << num << " is " << fibonacci(num) << endl; // Output: 5
return 0;
}
The recurrence relation for the Fibonacci function is:
T(n) = T(n-1) + T(n-2) + O(1)This relation grows exponentially because each call spawns two more recursive calls. As a result, the time complexity is O(2^n), which is inefficient for large values of n.
The space complexity of a recursive algorithm is determined by:
In recursive algorithms, the space complexity is influenced by the call stack. Each recursive call adds a new frame to the call stack, so the space complexity is proportional to the maximum depth of the recursion.
For the factorial function, each recursive call is placed on the call stack. Since the recursion depth is n, the space complexity is O(n).
For the recursive Fibonacci function, the maximum depth of the recursion is n. However, because multiple recursive calls are made at each level, the space complexity is also O(n) (the maximum depth of the recursion tree).
Recursive algorithms can often be optimized to reduce their time or space complexity. One common optimization technique is memoization.
Memoization stores previously computed values to avoid redundant recursive calls. This reduces the time complexity from O(2^n) to O(n).
Here’s the Fibonacci function with memoization in C++:
#include <iostream>
using namespace std;
int fibonacci(int n, int memo[]) {
if (n == 0)
return 0;
if (n == 1)
return 1;
if (memo[n] != -1)
return memo[n];
memo[n] = fibonacci(n-1, memo) + fibonacci(n-2, memo);
return memo[n];
}
int main() {
int num = 5;
int memo[num + 1];
for (int i = 0; i <= num; i++)
memo[i] = -1; // Initialize the memo array with -1
cout << "Fibonacci of " << num << " is " << fibonacci(num, memo) << endl; // Output: 5
return 0;
}
In this optimized version:
memo to store the results of previous Fibonacci calculations.Open this section to load past papers