README
Recursion in C
š Introduction
Recursion is a programming technique where a function calls itself to solve a problem. The problem is broken down into smaller subproblems of the same type until reaching a simple base case that can be solved directly.
šÆ How Recursion Works
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā RECURSION CONCEPT ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā ā
ā Problem ā
ā ā ā
ā ā¼ ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā ā Is it a base case? ā ā
ā āāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā ā ā
ā āāāāāāāāā“āāāāāāāā ā
ā ā ā ā
ā Yes No ā
ā ā ā ā
ā ā¼ ā¼ ā
ā āāāāāāāāāāā āāāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā ā Return ā ā Solve smaller problem ā ā
ā ā result ā ā by calling self ā ā
ā āāāāāāāāāāā āāāāāāāāāāāā¬āāāāāāāāāāāāāāā ā
ā ā ā
ā ā¼ ā
ā Combine results ā
ā ā ā
ā ā¼ ā
ā Return ā
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
š§ Basic Structure
Every recursive function needs:
- ā¢Base Case(s): Condition to stop recursion
- ā¢Recursive Case: Call to itself with smaller problem
return_type function_name(parameters) {
// Base case
if (base_condition) {
return base_value;
}
// Recursive case
return function_name(smaller_problem);
}
š Classic Example: Factorial
Mathematical Definition:
- ā¢0! = 1
- ā¢n! = n Ć (n-1)!
int factorial(int n) {
// Base case
if (n <= 1) {
return 1;
}
// Recursive case
return n * factorial(n - 1);
}
Execution Trace for factorial(4):
factorial(4)
ā
āāā 4 * factorial(3)
ā ā
ā āāā 3 * factorial(2)
ā ā ā
ā ā āāā 2 * factorial(1)
ā ā ā ā
ā ā ā āāā returns 1 (base case)
ā ā ā
ā ā āāā returns 2 * 1 = 2
ā ā
ā āāā returns 3 * 2 = 6
ā
āāā returns 4 * 6 = 24
š Call Stack Visualization
Stack grows ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā factorial(1) ā returns 1 ā 4th call (base case)
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā factorial(2) ā 2 * 1 = 2 ā 3rd call
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā factorial(3) ā 3 * 2 = 6 ā 2nd call
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā factorial(4) ā 4 * 6 = 24 ā 1st call (original)
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā main() ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
š¢ Fibonacci Sequence
Definition:
- ā¢F(0) = 0
- ā¢F(1) = 1
- ā¢F(n) = F(n-1) + F(n-2)
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);
}
Call Tree for fibonacci(5):
fib(5)
/ \
fib(4) fib(3)
/ \ / \
fib(3) fib(2) fib(2) fib(1)
/ \ / \ / \ |
fib(2) fib(1) ... ... 1
/ \ |
fib(1) fib(0)
| |
1 0
Note: This naive implementation is inefficient (exponential time).
š Types of Recursion
1. Direct Recursion:
Function calls itself directly.
void directRecursion() {
// ...
directRecursion(); // Calls itself
}
2. Indirect Recursion:
Function A calls function B, which calls function A.
void functionA() {
// ...
functionB();
}
void functionB() {
// ...
functionA(); // Indirect recursion
}
3. Tail Recursion:
Recursive call is the last operation.
// Tail recursive
int factorial_tail(int n, int accumulator) {
if (n <= 1) return accumulator;
return factorial_tail(n - 1, n * accumulator); // Last operation
}
// Usage:
int result = factorial_tail(5, 1);
4. Head Recursion:
Recursive call happens before other processing.
void headRecursion(int n) {
if (n <= 0) return;
headRecursion(n - 1); // Call first
printf("%d ", n); // Process after
}
// Prints: 1 2 3 4 5
š Common Recursive Problems
1. Sum of Array:
int sumArray(int arr[], int size) {
if (size <= 0) return 0;
return arr[size - 1] + sumArray(arr, size - 1);
}
2. Power Function:
int power(int base, int exp) {
if (exp == 0) return 1;
return base * power(base, exp - 1);
}
3. String Reversal:
void printReverse(char str[], int index) {
if (str[index] == '\0') return;
printReverse(str, index + 1);
printf("%c", str[index]);
}
4. Binary Search:
int binarySearch(int arr[], int left, int right, int target) {
if (left > right) return -1;
int mid = left + (right - left) / 2;
if (arr[mid] == target) return mid;
if (arr[mid] > target) return binarySearch(arr, left, mid - 1, target);
return binarySearch(arr, mid + 1, right, target);
}
5. GCD (Euclidean Algorithm):
int gcd(int a, int b) {
if (b == 0) return a;
return gcd(b, a % b);
}
āļø Recursion vs Iteration
| Aspect | Recursion | Iteration |
|---|---|---|
| Readability | Often cleaner for recursive problems | May be more complex |
| Memory | Uses stack space (risk of overflow) | Constant space usually |
| Performance | Function call overhead | Generally faster |
| Debugging | Can be harder | Usually easier |
| Use case | Tree/graph traversal, divide & conquer | Simple loops |
Factorial: Both Ways
// Recursive
int factorialRecursive(int n) {
if (n <= 1) return 1;
return n * factorialRecursive(n - 1);
}
// Iterative
int factorialIterative(int n) {
int result = 1;
for (int i = 2; i <= n; i++) {
result *= i;
}
return result;
}
ā ļø Common Mistakes
1. Missing Base Case:
// WRONG - infinite recursion!
int factorial(int n) {
return n * factorial(n - 1);
}
// CORRECT
int factorial(int n) {
if (n <= 1) return 1; // Base case
return n * factorial(n - 1);
}
2. Base Case Never Reached:
// WRONG - n never decreases!
int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n); // Should be n-1
}
3. Stack Overflow:
// This will crash for large n
void stackOverflow(int n) {
int array[1000]; // Large local variable
stackOverflow(n + 1);
}
šØ Optimization Techniques
1. Memoization:
Store computed results to avoid recalculating.
int memo[100] = {0};
int fibMemo(int n) {
if (n <= 0) return 0;
if (n == 1) return 1;
if (memo[n] != 0) return memo[n];
memo[n] = fibMemo(n - 1) + fibMemo(n - 2);
return memo[n];
}
2. Tail Recursion Optimization:
Compilers can optimize tail-recursive functions.
// Tail recursive - can be optimized
int factorial_tail(int n, int acc) {
if (n <= 1) return acc;
return factorial_tail(n - 1, n * acc);
}
// Usage
int result = factorial_tail(5, 1);
š Recursion Depth
The maximum depth of recursion is limited by stack size.
#include <stdio.h>
int depth = 0;
void measureDepth() {
depth++;
if (depth % 10000 == 0) {
printf("Depth: %d\n", depth);
}
measureDepth(); // Will eventually crash
}
// Typical stack allows ~10,000-100,000 calls
š Key Takeaways
- ā¢Every recursion needs a base case - or it runs forever
- ā¢Each call must move toward the base case
- ā¢Recursion uses stack memory - can overflow
- ā¢Some problems are naturally recursive - trees, graphs
- ā¢Memoization can dramatically improve performance
- ā¢Tail recursion can be optimized by compilers
- ā¢Iteration is often more efficient for simple problems
āļø Next Topic
Continue to Scope and Lifetime to understand how variables behave in different parts of your program.