Recursion is a technique in programming where a function calls itself in order to solve a problem. This method can be particularly useful for problems that can be broken down into smaller subproblems of the same type. Understanding recursion is essential for solving problems in fields like algorithms, tree traversals, and dynamic programming.
In a recursive function, the function calls itself with a modified argument until it reaches a base case. The base case is a condition that stops the recursion and prevents it from continuing infinitely.
A recursive function typically has two components:
def recursive_function(parameters):
if base_case_condition(parameters):
return base_case_value # Base case: stop recursion
else:
return recursive_function(modified_parameters) # Recursive case
The factorial of a number n
, denoted n!
, is the product of all positive integers less than or equal to n
. The factorial function can be defined recursively as:
n! = n * (n-1)!
1! = 1
def factorial(n):
if n == 0:
return 1 # Base case
else:
return n * factorial(n-1) # Recursive case
# Test the function
print(factorial(5)) # Output: 120
factorial(5)
, the function calls factorial(4)
, factorial(3)
, and so on, until it hits the base case factorial(0)
and returns 1
.The Fibonacci sequence is a series of numbers in which each number is the sum of the two preceding ones, starting from 0 and 1. The sequence is: 0, 1, 1, 2, 3, 5, 8, 13, ...
.
The Fibonacci function can be defined recursively as:
fib(n) = fib(n-1) + fib(n-2)
fib(0) = 0
, fib(1) = 1
def fibonacci(n):
if n <= 1:
return n # Base case
else:
return fibonacci(n-1) + fibonacci(n-2) # Recursive case
# Test the function
print(fibonacci(6)) # Output: 8
fibonacci(6)
, the function calls fibonacci(5)
and fibonacci(4)
, and so on, until the base cases fibonacci(1)
and fibonacci(0)
are reached, returning 1
and 0
respectively.Tail recursion is a special kind of recursion where the recursive call is the last operation in the function. If a language supports tail call optimization (TCO), the function can reuse the stack frame and avoid growing the stack. However, Python does not support tail call optimization, so tail recursion still consumes additional stack space.
def factorial_tail(n, accumulator=1):
if n == 0:
return accumulator # Base case
else:
return factorial_tail(n-1, accumulator*n) # Tail recursive case
# Test the function
print(factorial_tail(5)) # Output: 120
accumulator
) along with each function call, eliminating the need for a further multiplication after the recursive call.While both recursion and iteration can solve the same problems, there are key differences:
In Python, recursion may not always be the best choice for deep recursion due to the recursion depth limit and the lack of tail call optimization.
You can increase the recursion depth limit using:
import sys
sys.setrecursionlimit(10000)
However, this is not recommended for general use and should be used with caution.
Objective: Solve common problems using recursion to understand its behavior and application.
Sum of Natural Numbers:
sum_n(n)
that calculates the sum of the first n
natural numbers (i.e., 1 + 2 + 3 + ... + n
).def sum_n(n):
if n == 0:
return 0
return n + sum_n(n-1)
print(sum_n(5)) # Output: 15
Power of a Number:
power(x, n)
to compute x^n
(x raised to the power n).def power(x, n):
if n == 0:
return 1
return x * power(x, n-1)
print(power(2, 3)) # Output: 8
Reverse a String:
reverse_string(s)
that reverses a string.def reverse_string(s):
if len(s) == 0:
return s
return reverse_string(s[1:]) + s[0]
print(reverse_string("hello")) # Output: "olleh"
Binary Search:
binary_search(arr, target, low, high)
to find an element in a sorted list.def binary_search(arr, target, low, high):
if low > high:
return -1
mid = (low + high) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
return binary_search(arr, target, mid + 1, high)
else:
return binary_search(arr, target, low, mid - 1)
arr = [1, 3, 5, 7, 9, 11]
print(binary_search(arr, 5, 0, len(arr)-1)) # Output: 2
Tower of Hanoi:
tower_of_hanoi(n, source, auxiliary, target)
to move n
disks from the source rod to the target rod.def tower_of_hanoi(n, source, auxiliary, target):
if n == 1:
print(f"Move disk 1 from {source} to {target}")
return
tower_of_hanoi(n-1, source, target, auxiliary)
print(f"Move disk {n} from {source} to {target}")
tower_of_hanoi(n-1, auxiliary, source, target)
tower_of_hanoi(3, 'A', 'B', 'C')
Recursion is a powerful programming technique that simplifies solving problems that involve repeated subproblems. Understanding recursion and its potential limitations is crucial for writing efficient code. While recursion can make code more elegant and concise, it may come at the cost of memory usage and performance, so it’s important to consider the problem at hand and choose the best approach for your solution.