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
    🧩
    Data Structures
    COMP2117
    Progress0 / 37 topics
    Topics
    1. Abstract Data Types2. Complexity Analysis3. Big Oh Notation4. Stacks (Linked Lists and Array Implementations)5. Recursion and analyzing recursive algorithms6. Divide and Conquer Algorithms7. Sorting Algorithms8. Selection Sort9. Insertion Sort10. Merge Sort11. Quick Sort12. Bubble Sort13. Heap Sort14. Shell Sort15. Radix Sort16. Bucket Sort17. Queue18. Dequeuer19. Priority Queues (linked and array implementations of queues)20. Linked List and Its Various Types21. Sorted Linked List22. Searching an Unsorted Array23. Binary Search for Sorted Arrays24. Hashing and Indexing25. Open Addressing and Chaining26. Trees and Tree Traversals27. Binary Search Trees28. Heaps29. M-Way Trees30. Balanced Trees31. Graphs32. Breadth-First Traversal33. Depth-First Traversal34. Topological Order35. Shortest Path36. Adjacency Matrix and Adjacency List Implementations37. Memory Management and Garbage Collection
    COMP2117›Recursion and analyzing recursive algorithms
    Data StructuresTopic 5 of 37

    Recursion and analyzing recursive algorithms

    6 minread
    1,097words
    Intermediatelevel

    Recursion and Analyzing Recursive Algorithms

    What is Recursion?

    Recursion is a programming technique where a function calls itself in order to solve a problem. The idea is to break down a complex problem into smaller, simpler subproblems. Each recursive call solves a smaller instance of the original problem until a base case is reached, which stops the recursion.

    Components of a Recursive Function

    A recursive function has two main components:

    1. Base Case: The condition that stops the recursion. Without a base case, the function would call itself indefinitely, leading to a stack overflow.
    2. Recursive Case: The part where the function calls itself to solve smaller instances of the problem.

    Example of Recursion: Factorial Calculation

    The factorial of a number n (written as n!) is the product of all positive integers less than or equal to n. A recursive definition of factorial is:

    • Base case: factorial(0) = 1
    • Recursive case: factorial(n) = n * factorial(n-1)

    In C++, a recursive implementation of factorial would look like this:

    #include <iostream>
    using namespace std;
    
    int factorial(int n) {
        // Base case: factorial(0) is 1
        if (n == 0)
            return 1;
        // Recursive case: n * factorial(n-1)
        return n * factorial(n - 1);
    }
    
    int main() {
        int num = 5;
        cout << "Factorial of " << num << " is " << factorial(num) << endl; // Output: 120
        return 0;
    }
    

    In this example:

    • The base case is n == 0, which stops the recursion.
    • The recursive case is n * factorial(n-1), where the function keeps calling itself with a smaller value of n.

    How Recursion Works: The Call Stack

    Each recursive call pushes a new frame onto the call stack, holding the current state of the function. When the base case is reached, the function starts returning values and the call stack begins to unwind.

    For example, calling factorial(3) will result in the following sequence:

    1. factorial(3) calls factorial(2)
    2. factorial(2) calls factorial(1)
    3. factorial(1) calls factorial(0)
    4. factorial(0) returns 1 (base case)
    5. factorial(1) returns 1 * 1 = 1
    6. factorial(2) returns 2 * 1 = 2
    7. factorial(3) returns 3 * 2 = 6

    Analyzing Recursive Algorithms

    Analyzing recursive algorithms involves understanding their time complexity and space complexity. Let’s break down how to analyze them.

    1. Time Complexity

    The time complexity of a recursive algorithm depends on:

    • The number of times the function is called.
    • The amount of work done at each call.

    To analyze this, we often use a recurrence relation, which expresses the time complexity of the algorithm in terms of itself.

    Example 1: Factorial Time Complexity

    For factorial(n), the recurrence relation is:

    • T(n) = T(n-1) + O(1)

    Here, T(n) represents the time complexity of computing factorial(n). The function does a constant amount of work (O(1)) and calls itself with n-1. This recurrence can be solved as follows:

    • T(n) = T(n-1) + O(1)
    • T(n-1) = T(n-2) + O(1)
    • Continue expanding this: T(n) = O(1) + O(1) + ... (n times)

    Thus, the time complexity of factorial(n) is O(n), because the function is called n times, and each call takes constant time.

    Example 2: Fibonacci Time Complexity

    The Fibonacci sequence can also be defined recursively:

    • F(n) = F(n-1) + F(n-2)
    • Base case: F(0) = 0, F(1) = 1

    Here’s a C++ implementation:

    #include <iostream>
    using namespace std;
    
    int fibonacci(int n) {
        if (n == 0)
            return 0;
        if (n == 1)
            return 1;
        return fibonacci(n-1) + fibonacci(n-2);
    }
    
    int main() {
        int num = 5;
        cout << "Fibonacci of " << num << " is " << fibonacci(num) << endl; // Output: 5
        return 0;
    }
    

    The recurrence relation for the Fibonacci function is:

    • T(n) = T(n-1) + T(n-2) + O(1)

    This relation grows exponentially because each call spawns two more recursive calls. As a result, the time complexity is O(2^n), which is inefficient for large values of n.

    2. Space Complexity

    The space complexity of a recursive algorithm is determined by:

    • The amount of extra space used (for example, for temporary variables).
    • The depth of the recursion (how many recursive calls are active at the same time).

    In recursive algorithms, the space complexity is influenced by the call stack. Each recursive call adds a new frame to the call stack, so the space complexity is proportional to the maximum depth of the recursion.

    Example 1: Factorial Space Complexity

    For the factorial function, each recursive call is placed on the call stack. Since the recursion depth is n, the space complexity is O(n).

    Example 2: Fibonacci Space Complexity

    For the recursive Fibonacci function, the maximum depth of the recursion is n. However, because multiple recursive calls are made at each level, the space complexity is also O(n) (the maximum depth of the recursion tree).

    Optimizing Recursive Algorithms

    Recursive algorithms can often be optimized to reduce their time or space complexity. One common optimization technique is memoization.

    Example: Optimized Fibonacci with Memoization

    Memoization stores previously computed values to avoid redundant recursive calls. This reduces the time complexity from O(2^n) to O(n).

    Here’s the Fibonacci function with memoization in C++:

    #include <iostream>
    using namespace std;
    
    int fibonacci(int n, int memo[]) {
        if (n == 0)
            return 0;
        if (n == 1)
            return 1;
        if (memo[n] != -1)
            return memo[n];
    
        memo[n] = fibonacci(n-1, memo) + fibonacci(n-2, memo);
        return memo[n];
    }
    
    int main() {
        int num = 5;
        int memo[num + 1];
        for (int i = 0; i <= num; i++)
            memo[i] = -1;  // Initialize the memo array with -1
    
        cout << "Fibonacci of " << num << " is " << fibonacci(num, memo) << endl; // Output: 5
        return 0;
    }
    

    In this optimized version:

    • We use an array memo to store the results of previous Fibonacci calculations.
    • The time complexity is O(n), and the space complexity is also O(n).

    Conclusion

    • Recursion is a powerful technique for solving problems by breaking them down into smaller subproblems.
    • When analyzing recursive algorithms, focus on the time complexity (how many recursive calls are made and how much work is done in each call) and space complexity (based on the depth of the recursion and the call stack).
    • Some recursive algorithms can be optimized using techniques like memoization to improve efficiency.
    Previous topic 4
    Stacks (Linked Lists and Array Implementations)
    Next topic 6
    Divide and Conquer Algorithms

    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 time6 min
      Word count1,097
      Code examples0
      DifficultyIntermediate