In mathematics and computer science, recursion refers to the process where a function calls itself in its definition. It is a method used to solve problems by breaking them down into smaller, similar subproblems. Recursive problems typically have a "base case," which provides a simple, non-recursive solution for the smallest instances of the problem, and a recursive case, which solves the problem by reducing it to smaller subproblems.
A recursive function typically follows the structure:
The factorial function is defined as the product of all positive integers less than or equal to . Using recursion, the factorial can be defined as:
Here:
A recurrence relation (or simply recurrence) is an equation that defines a sequence of values in terms of previous terms in the sequence. Recurrences are commonly used in computer science to model the running time of recursive algorithms, particularly divide-and-conquer algorithms.
Formulating a recurrence involves expressing the problem in a recursive way, based on the structure of the problem. When formulating recurrences, you identify:
Let's consider the Merge Sort algorithm, a classic divide-and-conquer algorithm used to sort an array. The idea behind Merge Sort is to:
To analyze the running time of Merge Sort, we can formulate a recurrence relation. Let represent the time complexity of Merge Sort for an array of size .
Base case: If the array has only one element (or is empty), no sorting is needed, so the time complexity is constant. Thus, .
Recursive case: To sort an array of size , Merge Sort divides the array into two subarrays of size , recursively sorts each subarray, and then merges the two sorted subarrays. The merging step takes time. Therefore, the recurrence relation for is:
Here:
This recurrence helps describe the time complexity of Merge Sort, which can be solved using methods such as the recursion-tree method or the Master Theorem.
When you are tasked with formulating a recurrence relation for a recursive algorithm or problem, follow these general steps:
Identify the base case(s): This is the simplest form of the problem that can be solved directly without recursion. It typically involves small input sizes or trivial computations.
Define the recursive case(s): Determine how the problem can be broken down into smaller subproblems. This usually involves dividing the input into smaller pieces and solving each piece recursively. If the input size is , break it down into subproblems of smaller sizes, such as , , etc.
Express the time complexity or solution for the problem in terms of the time complexity or solution of the subproblems. Typically, this results in a recurrence relation that expresses the total work as a function of the work done on the subproblems and any additional work done outside of the recursion (e.g., merging, computing results).
The Fibonacci sequence is another classic example of a recursive problem. The Fibonacci numbers are defined as:
This is a direct recursive definition, but if we were to express the time complexity of a naive recursive approach to calculate Fibonacci numbers, the recurrence relation would be:
Here:
Once a recurrence relation is formulated, the next step is solving it to determine the time complexity or behavior of the algorithm. Common techniques to solve recurrences include:
The Master Theorem is a powerful tool for solving recurrences of the form:
Where , , and . The Master Theorem provides solutions based on the values of , , and .
Using this theorem, you can quickly determine the time complexity for many divide-and-conquer recurrences.
Formulating recurrences is essential for analyzing recursive algorithms and solving problems with a recursive structure. By expressing the problem in terms of smaller subproblems and combining the results of these subproblems, recurrences help characterize the behavior of algorithms and solutions. Understanding how to correctly formulate and solve recurrences is a critical skill in both mathematics and computer science.
Open this section to load past papers