ScholarQuill logoScholarQuillUniversity Notes
  • Notes
  • Past Papers
  • Blogs
  • Todo
Login
ScholarQuill logoScholarQuillUniversity Notes
Login
NotesPast PapersBlogsTodo
More
SubjectsDiscussionCGPA CalculatorGPA CalculatorStudent PortalCourse Outline
About
About usPrivacy PolicyReportContact
Notes
Past Papers
Blogs
Todo
Analytics
    Current Subject
    🧩
    Design & Analysis of Algorithms
    DC-321
    Progress0 / 28 topics
    Topics
    1. Introduction2. Role of Algorithms in Computing3. Analysis on Nature of Input and Size of Input4. Asymptotic Notations5. Big-O Notation6. Big-Ω Notation7. Big-Θ Notation8. Little-o Notation9. Little-ω Notation10. Sorting Algorithm Analysis11. Loop Invariants12. Recursion and Recurrence Relations13. Algorithm Design Techniques14. Brute Force Approach15. Divide-and-Conquer Approach16. Merge Sort17. Quick Sort18. Greedy Approach19. Dynamic Programming20. Elements of Dynamic Programming21. Search Trees22. Heaps23. Hashing24. Graph Algorithms25. Shortest Paths26. Sparse Graphs27. String Matching28. Introduction to Complexity Classes
    DC-321›Recursion and Recurrence Relations
    Design & Analysis of AlgorithmsTopic 12 of 28

    Recursion and Recurrence Relations

    12 minread
    2,076words
    Intermediatelevel

    Recursion and Recurrence Relations

    Recursion and recurrence relations are fundamental concepts in computer science, particularly in algorithm design and analysis. Both are essential for understanding how problems can be broken down into smaller subproblems and how their solutions relate to each other. Let's explore each of these concepts in detail.


    1. Recursion

    Recursion is a method where a function calls itself in order to solve smaller instances of the same problem. It is a natural way to express problems that can be divided into smaller subproblems, which can be solved independently and then combined to form the solution to the original problem.

    Key Elements of Recursion:

    1. Base Case: The simplest case, where the function stops calling itself and returns a known value. Without a base case, a recursive function would continue calling itself infinitely.
    2. Recursive Case: The part of the function where the function calls itself with smaller or simpler inputs, working towards the base case.

    Example of Recursion: Factorial Function

    The factorial of a number nnn, denoted as n!n!n!, is defined as the product of all integers from 1 to nnn. Recursively, this can be defined as:

    n!=n×(n−1)!with0!=1n! = n \times (n-1)! \quad \text{with} \quad 0! = 1n!=n×(n−1)!with0!=1

    Recursive Implementation:

    def factorial(n):
        if n == 0:        # Base case
            return 1
        else:
            return n * factorial(n - 1)  # Recursive case
    

    In this example:

    • Base case: When n=0n = 0n=0, the function returns 1.
    • Recursive case: The function calls itself with n−1n-1n−1, and the result is multiplied by nnn.

    Benefits of Recursion:

    • Recursion often leads to simpler, more elegant code.
    • It is useful for problems that naturally involve self-similar subproblems, such as tree traversal, searching, and divide-and-conquer algorithms.

    Drawbacks of Recursion:

    • Recursive functions can have higher memory overhead due to the function call stack, especially if not optimized properly.
    • Some problems can be inefficient with naive recursion due to repeated calculations (e.g., computing Fibonacci numbers).

    2. Recurrence Relations

    A recurrence relation is an equation or inequality that describes a function in terms of its value at smaller inputs. In algorithms, recurrence relations are used to express the time complexity of recursive algorithms.

    A recurrence relation for a recursive algorithm expresses how the time complexity of a problem depends on the time complexities of its subproblems.

    General Form of a Recurrence Relation:

    A recurrence relation typically has the following form:

    T(n)=a⋅T(nb)+f(n)T(n) = a \cdot T\left(\frac{n}{b}\right) + f(n)T(n)=a⋅T(bn​)+f(n)

    Where:

    • T(n)T(n)T(n) is the time complexity of a problem of size nnn.
    • aaa is the number of subproblems.
    • nb\frac{n}{b}bn​ is the size of each subproblem (often reduced by a factor of bbb).
    • f(n)f(n)f(n) represents the time spent outside the recursive calls (e.g., combining results, dividing the problem).

    Example of Recurrence Relation: Merge Sort

    The Merge Sort algorithm divides the problem into two halves, recursively sorts each half, and then merges the sorted halves. The recurrence relation for the time complexity T(n)T(n)T(n) of Merge Sort is:

    T(n)=2T(n2)+O(n)T(n) = 2T\left(\frac{n}{2}\right) + O(n)T(n)=2T(2n​)+O(n)

    Where:

    • 2T(n2)2T\left(\frac{n}{2}\right)2T(2n​) represents the time to sort the two halves of the array.
    • O(n)O(n)O(n) represents the time spent in merging the two sorted halves.

    3. Solving Recurrence Relations

    To analyze the time complexity of recursive algorithms, we need to solve the recurrence relations that describe their behavior. There are several methods for solving recurrence relations:

    a. Substitution Method (Induction)

    The substitution method involves guessing the solution to the recurrence and then using mathematical induction to prove it. This method is useful when the recurrence relation is straightforward, but it requires a good guess for the solution.

    Example:

    Solve the recurrence relation for Merge Sort:

    T(n)=2T(n2)+O(n)T(n) = 2T\left(\frac{n}{2}\right) + O(n)T(n)=2T(2n​)+O(n)
    1. Guess: Let's assume T(n)=O(nlog⁡n)T(n) = O(n \log n)T(n)=O(nlogn).
    2. Inductive Step: Assume T(k)≤cklog⁡kT(k) \leq c k \log kT(k)≤cklogk for all k<nk < nk<n. Then for T(n)T(n)T(n):
    T(n)=2T(n2)+O(n)=2⋅c(n2log⁡n2)+O(n)=cnlog⁡n−cn+O(n)T(n) = 2T\left(\frac{n}{2}\right) + O(n) = 2 \cdot c \left(\frac{n}{2} \log \frac{n}{2}\right) + O(n) = c n \log n - c n + O(n)T(n)=2T(2n​)+O(n)=2⋅c(2n​log2n​)+O(n)=cnlogn−cn+O(n)

    Since O(n)O(n)O(n) terms are dominated by nlog⁡nn \log nnlogn, we conclude that T(n)=O(nlog⁡n)T(n) = O(n \log n)T(n)=O(nlogn).

    Thus, Merge Sort has a time complexity of O(nlog⁡n)O(n \log n)O(nlogn).

    b. Master Theorem

    The Master Theorem is a shortcut for solving recurrences of the form:

    T(n)=aT(nb)+O(nd)T(n) = a T\left(\frac{n}{b}\right) + O(n^d)T(n)=aT(bn​)+O(nd)

    Where:

    • aaa is the number of subproblems.
    • bbb is the factor by which the problem size is divided.
    • ddd is the cost of dividing the problem and combining the subproblems.

    The Master Theorem provides a direct way to determine the time complexity by comparing aaa and bdb^dbd.

    The three cases of the Master Theorem are:

    1. Case 1: If a>bda > b^da>bd, the time complexity is O(nlog⁡ba)O(n^{\log_b a})O(nlogb​a).
    2. Case 2: If a=bda = b^da=bd, the time complexity is O(ndlog⁡n)O(n^d \log n)O(ndlogn).
    3. Case 3: If a<bda < b^da<bd, the time complexity is O(nd)O(n^d)O(nd).

    Example: Quick Sort

    The recurrence relation for Quick Sort is:

    T(n)=2T(n2)+O(n)T(n) = 2T\left(\frac{n}{2}\right) + O(n)T(n)=2T(2n​)+O(n)

    This matches the form T(n)=aT(nb)+O(nd)T(n) = a T\left(\frac{n}{b}\right) + O(n^d)T(n)=aT(bn​)+O(nd) with a=2a = 2a=2, b=2b = 2b=2, and d=1d = 1d=1.

    • a=bda = b^da=bd (since 2=212 = 2^12=21), so by Case 2 of the Master Theorem, the time complexity is O(nlog⁡n)O(n \log n)O(nlogn).

    Thus, Quick Sort also has a time complexity of O(nlog⁡n)O(n \log n)O(nlogn) in the average case.

    c. Recursion Tree Method

    The recursion tree method involves drawing a tree where each node represents a subproblem, and the edges represent recursive calls. By summing the costs of all the levels in the tree, we can determine the total time complexity.

    For the recurrence T(n)=2T(n2)+O(n)T(n) = 2T\left(\frac{n}{2}\right) + O(n)T(n)=2T(2n​)+O(n), the recursion tree looks like:

    Level 0: O(n)
    Level 1: 2 * O(n/2) = O(n)
    Level 2: 4 * O(n/4) = O(n)
    ...
    

    Each level has cost O(n)O(n)O(n), and the tree has log⁡n\log nlogn levels, so the total cost is O(nlog⁡n)O(n \log n)O(nlogn).


    4. Common Recurrences

    Here are some common recurrence relations and their solutions:

    • Merge Sort: T(n)=2T(n2)+O(n)T(n) = 2T\left(\frac{n}{2}\right) + O(n)T(n)=2T(2n​)+O(n) → Solution: O(nlog⁡n)O(n \log n)O(nlogn)
    • Quick Sort (average case): T(n)=2T(n2)+O(n)T(n) = 2T\left(\frac{n}{2}\right) + O(n)T(n)=2T(2n​)+O(n) → Solution: O(nlog⁡n)O(n \log n)O(nlogn)
    • Binary Search: T(n)=T(n2)+O(1)T(n) = T\left(\frac{n}{2}\right) + O(1)T(n)=T(2n​)+O(1) → Solution: O(log⁡n)O(\log n)O(logn)
    • Fibonacci Sequence: T(n)=T(n−1)+T(n−2)+O(1)T(n) = T(n-1) + T(n-2) + O(1)T(n)=T(n−1)+T(n−2)+O(1) → Solution: O(2n)O(2^n)O(2n)

    Summary

    • Recursion is a technique where a function calls itself to solve smaller subproblems, and it is crucial for breaking down complex problems.
    • Recurrence relations describe the time complexity of recursive algorithms. They are often solved using methods like substitution, the Master Theorem, or the recursion tree method.
    • Solving recurrence relations helps determine the overall efficiency of recursive algorithms, allowing us to classify them in terms of time complexity.

    Understanding recursion and recurrence relations is essential for analyzing and designing efficient algorithms, especially when using divide-and-conquer or dynamic programming techniques.

    Previous topic 11
    Loop Invariants
    Next topic 13
    Algorithm Design Techniques

    Past Papers

    Open this section to load past papers

    Click on Show Past Papers to see past papers.
    On This Page
      Reading Stats
      Est. reading time12 min
      Word count2,076
      Code examples0
      DifficultyIntermediate