0% found this document useful (0 votes)
30 views

Chapter 2 - Understanding Recursion

Chapter 2 - Understanding Recursion

Uploaded by

gracious pezoh
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
30 views

Chapter 2 - Understanding Recursion

Chapter 2 - Understanding Recursion

Uploaded by

gracious pezoh
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 17

Chapter 2: Understanding Recursion

Lesson 1: Notion of Recursion - Introduction and Use Cases

Objective:

By the end of this lesson, students will:

• Understand the concept of recursion.


• Be able to identify use cases where recursion is applicable.
• Grasp the fundamental principles behind how recursion works.

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.

2. How Recursion Works

A recursive function solves a problem by performing the following steps:

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

Using recursion, the factorial function can be defined as:

• Base Case: 0!=1


• Recursive Case: n!=n×(n−1)!

3. Characteristics of Recursive Functions


• Base Case: Every recursive function must have at least one base case. If not, the function
will call itself indefinitely, leading to infinite recursion and a potential stack overflow.
• Self-reference: A function must call itself in a well-defined manner.
• Changing State: Each recursive call should bring the problem closer to the base case.

4. Examples of Recursion in Real Life

• File Systems: Navigating directories within directories is a recursive process.

5. Use Cases of Recursion

Recursion is widely used for problems that can be divided into smaller sub-problems. Some
common use cases include:

• Mathematical Problems: Calculating factorials, Fibonacci sequences, or power of


numbers.
• Data Structures: Traversing trees and graphs.
• Sorting Algorithms: Algorithms like quicksort and mergesort rely on recursion.
• Searching: Binary search can be implemented recursively.
• Backtracking Problems: Solving puzzles (e.g., Sudoku, maze solving).

6. Advantages and Disadvantages of Recursion

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.

7. Comparing Recursion with Iteration

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.

8. Best Practices for Using Recursion

• Ensure there’s always a clear base case.


• Avoid deep recursion where possible (can lead to stack overflow).
• Use recursion when the problem has a natural recursive structure.

9. Common Pitfalls of Recursion


• Missing Base Case: Leads to infinite recursion.
• Incorrect Base Case: Causes incorrect results or unexpected behavior.
• Stack Overflow: If recursion is too deep, it can exceed memory limits.

10. Simple Code Example in C Language

Here's a simple recursive function to calculate the factorial of a number:

#include <stdio.h>

// Recursive function to calculate factorial


int factorial(int n) {
// Base Case
if (n == 0) {
return 1;
}
// Recursive Case
else {
return n * factorial(n - 1);
}
}

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.

11. Homework / Practice Questions

1. Explain the difference between a base case and a recursive case.


2. Write a recursive function in C that calculates the nth Fibonacci number.
3. Identify two real-world examples of recursion.
4. Compare and contrast recursion and iteration in terms of efficiency and readability.

Summary

• Recursion is a method where a function calls itself to solve smaller instances of a


problem.
• Every recursive function must have a base case and a recursive case.
• Recursion can be a powerful tool, but it comes with its trade-offs, including potential
inefficiency and stack overflow.
Lesson 2: Base Case and Recursive Case

Objective:

By the end of this lesson, students will:

• Understand the concepts of base case and recursive case in recursion.


• Be able to identify and implement the base and recursive cases in a function.
• Grasp how recursion terminates using the base case.

1. Introduction to Base Case and Recursive Case

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.

2. The Role of the Base Case

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.

3. The Role of the Recursive Case

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.

Example: For calculating n!:

• Recursive Case: n!=n×(n−1)!


• The function calls itself with the parameter n−1.
4. Detailed Example: Calculating Factorial Using C

Let's revisit the factorial example to clearly distinguish the base case from the recursive case:

#include <stdio.h>

// Recursive function to calculate factorial


int factorial(int n) {
// Base Case
if (n == 0) {
return 1;
}
// Recursive Case
else {
return n * factorial(n - 1);
}
}

int main() {
int number = 5;
printf("Factorial of %d is %d\n", number, factorial(number));
return 0;
}

Explanation:

• Base Case: if (n == 0) return 1;


• Recursive Case: return n * factorial(n - 1);

The function continues calling itself with smaller values of n until it reaches 0, where it
terminates.

5. Why Are Base and Recursive Cases Important?

• 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.

6. Visualizing Recursion with Factorial

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

7. Identifying Base and Recursive Cases in Other Problems

Example 1: Fibonacci Sequence

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

• Base Cases: F(0)=0 and F(1) =1


• Recursive Case: F(n)=F(n−1)+F(n−2)

Code Example:

#include <stdio.h>

// Recursive function to calculate Fibonacci


int fibonacci(int n) {
// Base Cases
if (n == 0) {
return 0;
}
if (n == 1) {
return 1;
}
// Recursive Case
return fibonacci(n - 1) + fibonacci(n - 2);
}

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

The GCD of two integers can be found using the formula:

GCD(a,b)=GCD(b,amod b)
With the base case being when b=0, the GCD is a.

Code Example:

#include <stdio.h>

// Recursive function to calculate GCD


int gcd(int a, int b) {
// Base Case
if (b == 0) {
return a;
}
// Recursive Case
return gcd(b, a % b);
}

int main() {
int a = 48, b = 18;
printf("GCD of %d and %d is %d\n", a, b, gcd(a, b));
return 0;
}

8. Tips for Implementing Base and Recursive Cases

• Always define the base case first.


• Ensure that each recursive call brings you closer to the base case.
• Test your recursive function with different inputs, including edge cases (e.g., smallest
possible values).

9. Common Mistakes in Recursion

• No Base Case: Forgetting to include a base case results in infinite recursion.


• Incorrect Base Case: Setting a wrong base case leads to incorrect results or never-
ending recursion.
• Not Progressing Toward the Base Case: If each recursive call doesn’t reduce the
problem size, it won’t reach the base case.

10. Practice Problems

1. Write a recursive function to calculate the sum of all integers from 1 to n.


2. Implement a recursive function to check whether a given string is a palindrome.
3. Write a recursive function to reverse an array.

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.

Lesson 3: Recursion vs Iteration

Objective:

By the end of this lesson, students will:

• Understand the differences between recursion and iteration.


• Recognize when to use recursion versus iteration.
• Analyze the pros and cons of each approach.
• Be able to convert a recursive solution into an iterative one and vice versa.

1. Introduction to Recursion vs Iteration

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.

2. Key Differences Between Recursion and Iteration

Aspect Recursion Iteration


Definition Function calls itself to solve a Uses loops to repeat a block of code.
problem.
Termination Terminates when a base case is Terminates when a loop condition
reached. becomes false.
Memory Usage Requires more memory (stack space) Uses less memory since no
for each call. additional stack is used.
Code Often more readable and elegant for Usually less elegant but more
Readability complex problems. straightforward.
Overhead Function calls add overhead, making Generally faster due to lower
it slower. overhead.

3. Comparing Recursion and Iteration with Examples

Let's compare recursion and iteration using two classic examples: factorial calculation and the
Fibonacci sequence.

Example 1: Factorial Calculation

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.

Example 2: Fibonacci Sequence

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.

4. When to Use Recursion vs Iteration

Use Recursion When Use Iteration When


The problem has a natural recursive structure (e.g., The problem is simple and doesn’t
tree traversal, factorial, Fibonacci). require breaking into smaller sub-
problems.
You want a cleaner, more intuitive solution. Efficiency and performance are
priorities.
You need to solve problems involving divide-and- You need to minimize memory usage.
conquer or backtracking (e.g., quicksort, mergesort,
maze solving).
Important Note: While recursion can lead to more elegant code, always be cautious of the risk
of stack overflow, especially for deep recursions.

5. Advantages and Disadvantages

Aspect Recursion Iteration


Pros Cleaner and easier to read for problems with More memory-efficient and faster.
a recursive structure.
Helps solve complex problems (e.g., tree Suitable for problems without a
traversal). natural recursive structure.
Cons Higher memory usage and slower execution Can be less intuitive and harder to
due to function call overhead. read for complex problems.
Risk of stack overflow if recursion depth is May require more lines of code for
high. certain problems.

6. Converting Recursion to Iteration

Sometimes, recursion can be converted into iteration using data structures like stacks to simulate
the function call stack.

Example: Converting a Recursive Factorial to an Iterative Version

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.

Tail Recursive Example:

int factorial_tail(int n, int accumulator) {


if (n == 0) return accumulator;
return factorial_tail(n - 1, n * accumulator);
}

This can be called as factorial_tail(5, 1).

• 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.

9. Homework / Practice Questions

1. Convert the recursive Fibonacci function into an iterative version.


2. Write a recursive and an iterative version of a function that finds the sum of all elements
in an array.
3. Identify three real-world scenarios where recursion is more suitable than iteration.
4. Explain why recursion might be less efficient than iteration in terms of memory usage.

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.

Lesson 5: Problem Solving Using Recursion (Factorials, Fibonacci)

Objective:

By the end of this lesson, students will:

• Apply recursion to solve real-world problems.


• Implement recursive solutions to calculate factorials and Fibonacci numbers.
• Analyze the efficiency of recursive solutions.

1. Introduction to Problem Solving with Recursion


In previous lessons, we explored the theoretical aspects of recursion, including the base case,
recursive case, and differences between recursion and iteration. Now, we will focus on practical
applications by solving problems such as calculating factorials and Fibonacci numbers using
recursion.

2. Problem 1: Calculating Factorials Using Recursion

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

The recursive formula for calculating factorial is:

• Base Case: 0!=1


• Recursive Case: n!=n×(n−1)!

Implementation in C

#include <stdio.h>

// Recursive function to calculate factorial


int factorial(int n) {
// Base Case
if (n == 0) {
return 1;
}
// Recursive Case
return n * factorial(n - 1);
}

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

3. Problem 2: Generating Fibonacci Numbers Using Recursion

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>

// Recursive function to calculate Fibonacci number


int fibonacci(int n) {
// Base Cases
if (n == 0) {
return 0;
}
if (n == 1) {
return 1;
}
// Recursive Case
return fibonacci(n - 1) + fibonacci(n - 2);
}

int main() {
int terms;
printf("Enter the number of terms for the Fibonacci sequence: ");
scanf("%d", &terms);

printf("Fibonacci Sequence: ");


for (int i = 0; i < terms; i++) {
printf("%d ", fibonacci(i));
}
printf("\n");
return 0;
}

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:

• Input: 5 → Output: Fibonacci Sequence: 0 1 1 2 3


• Input: 8 → Output: Fibonacci Sequence: 0 1 1 2 3 5 8 13

4. Analyzing the Efficiency of Recursive Solutions


Factorial Analysis:

• Time Complexity: O(n) – The function calls itself n times.


• Space Complexity: O(n) – Each recursive call adds to the call stack.

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.

5. Optimizing Recursive Solutions

Memoization in Fibonacci:

#include <stdio.h>

#define MAX 1000


int memo[MAX];

// Recursive function to calculate Fibonacci with memoization


int fibonacci_memo(int n) {
if (memo[n] != -1) {
return memo[n];
}

// 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);

// Initialize memo array


for (int i = 0; i < MAX; i++) {
memo[i] = -1;
}

printf("Fibonacci Sequence with Memoization: ");


for (int i = 0; i < terms; i++) {
printf("%d ", fibonacci_memo(i));
}
printf("\n");
return 0;
}

Explanation:

• The memo array stores already calculated Fibonacci values, preventing redundant
computations.
• This optimization reduces the time complexity to O(n).

6. Practice Problems

1. Write a recursive function to calculate the sum of all integers from 1 to n.


2. Implement a recursive function to find the greatest common divisor (GCD) of two
numbers using Euclid's algorithm.
3. Write a recursive function to determine whether a string is a palindrome.

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

Ques�on 1: Basic Understanding of Recursion

a) Define recursion and explain how it differs from itera�on.


b) Iden�fy and describe the two main components of a recursive func�on.

Ques�on 2: Recursion vs Itera�on

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.

Ques�on 3: Analyzing Recursive Func�ons

a) Describe how a stack is used in execu�ng recursive func�ons.


b) Explain why a recursive func�on might lead to a stack overflow error. How can this be prevented?

Ques�on 4: Memoiza�on and Op�miza�on

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

Ques�on 5: Wri�ng Recursive Func�ons

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 6: Conver�ng Recursion to Itera�on

The Fibonacci sequence can be calculated both recursively and itera�vely.


a) Write a recursive func�on to calculate the nth Fibonacci number.
b) Convert the recursive solu�on into an itera�ve version. Explain the differences in terms of efficiency.

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.

Bonus Ques�ons (10 Marks - Op�onal)

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.

You might also like