Chapter 6: Recursion


Subtopic Title: What Is Recursion?

Definition

  • Recursion is a problem-solving process that breaks a problem into smaller versions of itself until a direct solution is found.

Causes

  • Triggered when a problem can be reduced into smaller, identical problems.
  • Used when iteration is complex or less intuitive.

Goals / Objectives

  • To solve complex problems by simplifying them into smaller, manageable parts.
  • To reach a base case that allows solving the original problem.

Importance

  • Offers an alternative to iteration.
  • Useful for problems with repetitive or nested structures.
  • Makes complex solutions easier to understand and implement.

Procedures

  • Break the problem into smaller versions.
  • Solve the smallest version directly (base case).
  • Use the solution of the smaller problem to solve the larger one.
  • Continue until the original problem is solved.

Advantages & Disadvantages

Advantages:

  • Simplifies complex problems.
  • Can be more elegant and readable than iteration.

Disadvantages:

  • May be less efficient due to repeated calls and memory usage.
  • Requires careful design to avoid infinite recursion.

Impact / Effect

  • Leads to a complete solution by solving smaller subproblems.
  • Can increase memory usage due to stack frames.
  • May slow down execution compared to iteration.

Examples

  • Solving factorial using recursion.
  • Towers of Hanoi problem.
  • Recursive array processing.

Key Takeaways

  • Recursion solves problems by breaking them into smaller versions.
  • It needs a base case to stop the recursion.
  • Useful when iteration is hard to implement.
  • Can be less efficient than iteration due to memory and time overhead.

Subtopic Title: Terminology

Definition

  • Recursion: The process of solving a problem by reducing it to smaller versions of itself.
  • Recursive definition: A definition where something is defined in terms of a smaller version of itself.
  • Stopping case: The case for which the solution is obtained directly.
  • Recursive case: The case in a recursive algorithm where the problem is specified as a smaller version of the original problem.
  • Recursive algorithm: An algorithm that solves a problem by reducing it to smaller versions of itself.
  • Recursive method: A method that calls itself. Its body contains a statement that causes the same method to execute before completing the current call.

Causes

  • Not specified in notes

Goals / Objectives

  • To define and clarify key terms related to recursion.
  • To distinguish between different components of recursive logic.

Importance

  • Helps in understanding the structure and behavior of recursive solutions.
  • Clarifies how recursion works and how it differs from iteration.

Procedures

  • Use recursive definitions to build algorithms and methods.
  • Identify stopping and recursive cases to structure recursive logic.
  • Implement recursive methods that call themselves with modified parameters.

Advantages & Disadvantages

Advantages:

  • Provides clarity in understanding recursive logic.
  • Helps in designing correct recursive algorithms.

Disadvantages:

  • Not specified in notes

Impact / Effect

  • Enables accurate implementation and tracing of recursive methods.
  • Supports better debugging and analysis of recursive behavior.

Examples

  • Recursive method: factorial(n) where the method calls itself with n-1.
  • Stopping case: if (n == 0) return 1;
  • Recursive case: return n * factorial(n - 1);

Key Takeaways

  • Recursion involves breaking problems into smaller versions of themselves.
  • A recursive method must include both a stopping case and a recursive case.
  • Understanding terminology is essential for writing and analyzing recursive code.
  • Recursive methods use the program stack to manage multiple calls.

Subtopic Title: Factorial (Recursive Definition)

Definition

  • Factorial of a number n (written as n!) is the product of all positive integers from n down to 1.
  • Recursive definition:
    • 0! = 1
    • n! = n × (n - 1)! for n > 0

Causes

  • Used when calculating permutations, combinations, or solving mathematical problems involving repeated multiplication.

Goals / Objectives

  • To compute the factorial of a number using recursion.
  • To demonstrate how recursion simplifies repetitive multiplication.

Importance

  • Serves as a classic example of recursion.
  • Helps learners understand recursive structure and base cases.

Procedures

  • Recursive breakdown:
    • 4! = 4 × 3!
    • 3! = 3 × 2!
    • 2! = 2 × 1!
    • 1! = 1 × 0!
    • 0! = 1 (base case)
  • Recursive method implementation:
public int factorial(int n) {
  if (n == 0)
    return 1; // Stopping case
  else
    return n * factorial(n - 1); // Recursive case
}

Advantages & Disadvantages

Advantages:

  • Simple and elegant for small values of n.
  • Easy to understand and trace.

Disadvantages:

  • Can be inefficient for large n due to deep recursion and stack usage.
  • Risk of stack overflow if base case is missing or unreachable.

Impact / Effect

  • Demonstrates how recursion works through repeated method calls.
  • Shows how the program stack handles recursive calls.
  • Helps visualize recursion through box traces and stack frames.

Examples

  • factorial(4):
    • 4 × 3 × 2 × 1 = 24
  • Box trace for factorial(3):
    n = 3 → return 3 × factorial(2)
    n = 2 → return 2 × factorial(1)
    n = 1 → return 1 × factorial(0)
    n = 0 → return 1
    

Key Takeaways

  • Factorial is a classic recursive problem with a clear base case.
  • Recursive calls reduce the problem until the base case is reached.
  • Each call waits for the result of the next before completing.
  • Recursive methods use the program stack to manage calls.

Subtopic Title: Program Stack & Recursion Trace

Definition

  • Program stack: A Last-In-First-Out (LIFO) memory structure used to manage method calls.
  • Stack frame (also called activation record): A memory block that stores argument values, local variables, and return address for each method call.
  • Recursion trace: A visual representation of recursive method execution, showing argument values, executed statements, and return values.

Causes

  • Triggered automatically when a method is called, especially during recursion.
  • Each recursive call creates a new stack frame.

Goals / Objectives

  • To understand how recursive calls are managed in memory.
  • To visualize the flow of recursive execution and return values.

Importance

  • Essential for debugging and tracing recursive logic.
  • Helps explain why recursion consumes more memory.
  • Clarifies how control returns to previous calls after recursion completes.

Procedures

  • Each method call pushes a new stack frame onto the program stack.
  • The top frame is the active one (currently executing).
  • When a method returns, its frame is popped off the stack.
  • Recursion trace uses arrows:
    • Down arrow () for new recursive calls.
    • Up arrow () for returning values.

Advantages & Disadvantages

Advantages:

  • Makes recursive flow easier to visualize and understand.
  • Supports step-by-step tracing of recursive logic.

Disadvantages:

  • Can lead to stack overflow if recursion is too deep.
  • Requires more memory than iteration due to multiple stack frames.

Impact / Effect

  • Recursive methods use more memory due to repeated stack frame allocation.
  • Execution slows down compared to iteration because of overhead in managing stack frames.
  • Helps learners grasp recursion through box traces and visual flow.

Examples

  • Box trace for factorial(3):
    n = 3 → return 3 × factorial(2)
    n = 2 → return 2 × factorial(1)
    n = 1 → return 1 × factorial(0)
    n = 0 → return 1
    
  • Box trace for sumSeries(3):
    n = 3 → return sumSeries(2) + 0.33
    n = 2 → return sumSeries(1) + 0.5
    n = 1 → return 1
    

Key Takeaways

  • Each recursive call creates a new stack frame.
  • The program stack follows LIFO: last call is executed first.
  • Recursion trace shows how values flow through recursive calls.
  • Understanding stack behavior is key to mastering recursion.

Subtopic Title: Designing Recursive Methods & CountDown Implementations

Definition

  • Recursive method design involves creating a method that solves a problem by calling itself with modified parameters until a base case is reached.
  • CountDown is a recursive method that prints numbers in descending order from n to 1.

Causes

  • Used when a task involves repetitive steps that reduce toward a base condition.
  • Triggered by problems that naturally fit a recursive structure.

Goals / Objectives

  • To design recursive methods that are correct, efficient, and easy to understand.
  • To explore different ways of implementing recursion with the same output.

Importance

  • Shows how recursion can be structured in multiple ways.
  • Highlights the role of stopping cases and recursive calls in method design.
  • Demonstrates how small changes in logic affect recursion depth and behavior.

Procedures

Steps for Designing Recursive Methods

  1. List all stopping (base) cases
  2. List recursive cases
    • Ensure each recursive case moves toward a stopping case
  3. Arrange cases in correct sequence

General Structures of Recursive Methods

  • Structure A: Stopping and recursive cases have different actions

    if (stopping case)
      solve directly;
    else
      recursively solve smaller version;
  • Structure B: Common action in both cases

    perform common step;
    if (recursive case)
      recursively solve smaller version;
  • Structure C: No action in stopping case

    if (recursive case)
      recursively solve smaller version;

CountDown Implementations

Implementation 1

public void countDown(int n) {
  if (n == 1)
    System.out.println(n);
  else {
    System.out.println(n);
    countDown(n - 1);
  }
}
  • Stopping case is checked first.
  • println appears in both cases (redundant).

Implementation 2

public void countDown(int n) {
  if (n >= 1) {
    System.out.println(n);
    countDown(n - 1);
  }
}
  • Removes redundant println.
  • Recursive case is checked.
  • When n = 1, it calls countDown(0) (stopping case with no action).

Implementation 3

public void countDown(int n) {
  System.out.println(n);
  if (n > 1)
    countDown(n - 1);
}
  • Displays n first, then checks recursive case.
  • Uses n > 1 instead of n >= 1, resulting in one less recursive call.

Advantages & Disadvantages

Advantages:

  • Flexible design options for different use cases.
  • Helps understand recursion flow and stopping logic.

Disadvantages:

  • Small changes can affect recursion depth.
  • Risk of unnecessary calls or stack overflow if not carefully designed.

Impact / Effect

  • Different implementations affect how many recursive calls are made.
  • Efficient design reduces redundancy and improves clarity.
  • Helps students understand recursion through visual traces.

Examples

Box Trace for countDown(3)

countDown(3)
→ display 3
→ countDown(2)
→ display 2
→ countDown(1)
→ display 1

Key Takeaways

  • Recursive methods need clear stopping and recursive cases.
  • CountDown shows how logic placement affects recursion depth.
  • Multiple implementations can achieve the same output with different efficiency.
  • Always ensure recursive calls move toward the base case.

Subtopic Title: Recursive Series & Combinations


Part A: Recursive Series – sumSeries(n)

Definition

  • sumSeries(n) computes the sum of the series:
    1 + 1/2 + 1/3 + ... + 1/n
    where n is a positive integer.

Causes

  • Used when calculating harmonic series or approximations in mathematics.

Goals / Objectives

  • To compute the sum of a decreasing fractional series using recursion.

Importance

  • Demonstrates recursion with non-integer operations.
  • Helps understand recursive accumulation of values.

Procedures

  • Recursive method:
public double sumSeries(int n) {
  if (n <= 0)
    return 0.0;
  else if (n == 1)
    return 1;
  else
    return sumSeries(n - 1) + 1.0 / n;
}
  • Each call adds 1/n to the result of sumSeries(n - 1).

Advantages & Disadvantages

Advantages:

  • Simple and readable recursive structure.
  • Handles fractional values effectively.

Disadvantages:

  • Less efficient than iteration for large n.
  • Floating-point precision may affect accuracy.

Impact / Effect

  • Accumulates fractional values recursively.
  • Shows how recursion can be used beyond integers.

Examples

Box Trace for sumSeries(3)

n = 3 → return sumSeries(2) + 0.33
n = 2 → return sumSeries(1) + 0.5
n = 1 → return 1
Final result: 1.83

Key Takeaways

  • Recursive series can handle fractional values.
  • Each call builds on the result of the previous one.
  • Base case ensures recursion stops at n = 1.
  • Useful for understanding recursive accumulation.

Part B: Combinations – C(n, k)

Definition

  • C(n, k) computes the number of ways to choose k objects from n objects.
  • Recursive definition:
    • C(n, k) = 0 if k > n
    • C(n, k) = 1 if k = 0 or k = n
    • C(n, k) = C(n - 1, k - 1) + C(n - 1, k) if 0 < k < n

Causes

  • Used in combinatorics and probability calculations.

Goals / Objectives

  • To calculate combinations recursively using mathematical properties.

Importance

  • Shows how recursion can solve mathematical problems with multiple base cases.
  • Useful in statistics, probability, and algorithm design.

Procedures

  • Recursive method:
public int c(int n, int k) {
  if (k == 0 || k == n)
    return 1;
  else if (k > n)
    return 0;
  else
    return c(n - 1, k - 1) + c(n - 1, k);
}
  • Combines results from two smaller subproblems.

Advantages & Disadvantages

Advantages:

  • Elegant recursive structure.
  • Handles multiple base cases.

Disadvantages:

  • Can be inefficient for large n and k due to repeated calls.
  • May require memoization for optimization.

Impact / Effect

  • Builds combination values from smaller subproblems.
  • Demonstrates recursion with branching logic.

Examples

  • C(4, 2) would compute:
    • C(3, 1) + C(3, 2)
    • Each of those would further break down recursively.

Key Takeaways

  • Recursive combinations use multiple base cases.
  • Each call splits into two smaller problems.
  • Useful for understanding branching recursion.
  • Efficiency can be improved with optimization techniques.

Subtopic Title: Recursively Processing Arrays & Linked Chains


Part A: Recursively Processing Arrays

Definition

  • Recursive array processing involves handling array elements by breaking the array into smaller parts and applying recursion to each part.

Causes

  • Used when array elements need to be processed in a specific order (e.g., first-to-last, last-to-first, or by halves).
  • Triggered by problems that benefit from divide-and-conquer strategies.

Goals / Objectives

  • To process array elements recursively using different strategies.
  • To demonstrate how recursion can traverse arrays without loops.

Importance

  • Shows how recursion can replace iteration for array traversal.
  • Helps understand recursive logic in data structures.

Procedures

Three Recursive Strategies:

  1. displayArray1()

    • Displays the first element, then recursively displays the rest.
  2. displayArray2()

    • Recursively displays all preceding elements, then displays the last element.
  3. displayArray3()

    • Divides the array into two halves and recursively displays each half.

These methods are typically private and used internally in ADTs (Abstract Data Types).

Advantages & Disadvantages

Advantages:

  • Offers flexible traversal strategies.
  • Can simplify complex array operations.

Disadvantages:

  • May be less efficient than iteration.
  • Requires careful handling of array indices and base cases.

Impact / Effect

  • Enables recursive traversal of arrays in different directions.
  • Supports modular design in ADT implementations.
  • Can increase memory usage due to recursive calls.

Examples

  • displayArray1(): 0 → 1 → 2 → 3 → 4 → 5 → 6
  • displayArray2(): 6 ← 5 ← 4 ← 3 ← 2 ← 1 ← 0
  • displayArray3(): Divides array into left and right halves and displays recursively.

Key Takeaways

  • Arrays can be processed recursively using different strategies.
  • Recursive methods can replace loops for traversal.
  • Each method has a unique order of processing.
  • Useful for understanding divide-and-conquer logic.

Part B: Recursively Processing a Linked Chain

Definition

  • Recursive linked chain processing involves traversing a chain of linked nodes using recursion.

Causes

  • Used when working with linked lists or node-based structures.
  • Triggered by the need to process each node in sequence.

Goals / Objectives

  • To process each node in a linked chain recursively.
  • To demonstrate recursion in dynamic data structures.

Importance

  • Highlights recursion in non-array structures.
  • Essential for understanding linked list operations.

Procedures

  • Use a reference to the first node as the method parameter.
  • Process the current node.
  • Recursively process the rest of the chain.

Example: toString() method in SimpleList.java uses recursion to build a string representation of the list.

Advantages & Disadvantages

Advantages:

  • Simplifies traversal logic.
  • Avoids manual loop constructs.

Disadvantages:

  • May be harder to trace and debug.
  • Can lead to stack overflow in long chains.

Impact / Effect

  • Enables clean traversal of linked structures.
  • Supports recursive design in list-based ADTs.

Examples

  • Processing nodes: Node1 → Node2 → Node3 → ... → NodeN

Key Takeaways

  • Linked chains can be processed recursively node by node.
  • Recursion simplifies traversal logic in dynamic structures.
  • Each node is handled in its own recursive call.
  • Useful for implementing methods like toString() in linked lists.

Subtopic Title: Time Efficiency, Towers of Hanoi & Fibonacci Analysis


Part A: Time Efficiency of Recursive Methods

Definition

  • Time efficiency refers to how fast a recursive method executes, often measured using Big-O notation.

Causes

  • Influenced by the number of recursive calls and how much work each call performs.
  • Depends on whether recursion is linear, exponential, or optimized.

Goals / Objectives

  • To analyze how efficient a recursive method is compared to its iterative counterpart.
  • To understand the performance impact of recursion.

Importance

  • Helps determine whether recursion is suitable for a given problem.
  • Critical for performance-sensitive applications.

Procedures

  • Example: countDown(n) has time efficiency of O(n) because it makes n recursive calls.

Advantages & Disadvantages

Advantages:

  • Some recursive methods are efficient and easy to implement.

Disadvantages:

  • Recursive methods often use more memory and time than iteration.
  • Poorly designed recursion can lead to exponential time complexity.

Impact / Effect

  • Recursive methods may execute slower due to overhead from stack frames.
  • Efficiency affects usability in real-world systems.

Examples

  • countDown(n)O(n) time complexity.
  • Fibonacci(n)O(2^n) time complexity (inefficient).

Key Takeaways

  • Efficiency depends on recursion depth and structure.
  • Linear recursion is manageable; exponential recursion is costly.
  • Always analyze time complexity before choosing recursion.

Part B: Towers of Hanoi

Definition

  • Towers of Hanoi is a puzzle involving moving disks between three pegs following specific rules.

Causes

  • Designed as a recursive problem due to its repetitive structure.

Goals / Objectives

  • To move all disks from the start peg to the end peg using a temporary peg.
  • To follow the rules without placing larger disks on smaller ones.

Importance

  • Classic example of recursion.
  • Demonstrates divide-and-conquer strategy.

Procedures

Rules:

  1. Move one disk at a time (must be topmost).
  2. No disk may rest on a smaller disk.
  3. Temporary storage on the second peg is allowed.

Recursive Algorithm:

solveTowers(numberOfDisks, startPole, tempPole, endPole)
if (numberOfDisks == 1)
  Move disk from startPole to endPole
else {
  solveTowers(numberOfDisks - 1, startPole, endPole, tempPole)
  Move disk from startPole to endPole
  solveTowers(numberOfDisks - 1, tempPole, startPole, endPole)
}

Advantages & Disadvantages

Advantages:

  • Recursive solution is simple and elegant.
  • Avoids complex nested loops.

Disadvantages:

  • Inefficient for large number of disks.
  • Requires many recursive calls.

Impact / Effect

  • Solves a complex problem using simple recursive steps.
  • Shows how recursion can simplify logic.

Examples

  • Moving 3 disks:
    • Move disk 1 from A to C
    • Move disk 2 from A to B
    • Move disk 1 from C to B
    • Continue until all disks are moved to C

Key Takeaways

  • Towers of Hanoi is a perfect fit for recursion.
  • Recursive algorithm breaks problem into smaller disk moves.
  • Each move follows strict rules.
  • Demonstrates power of recursion in problem-solving.

Part C: Fibonacci Analysis

Definition

  • Fibonacci sequence: A series where each number is the sum of the two preceding ones.
    • Starts with 1, 1, 2, 3, 5, 8, 13...

Causes

  • Used in mathematical modeling, nature patterns, and algorithm design.

Goals / Objectives

  • To compute Fibonacci numbers using recursion.
  • To analyze why the naive recursive solution is inefficient.

Importance

  • Highlights the downside of recursion when overlapping subproblems exist.
  • Encourages optimization techniques like memoization.

Procedures

Recursive Algorithm:

Fibonacci(n)
if (n <= 1)
  return 1
else
  return Fibonacci(n - 1) + Fibonacci(n - 2)

Call Analysis:

  • Fibonacci(6) results in:
    • F2 computed 5 times
    • F3 computed 3 times
    • F4 computed 2 times
    • F5 and F6 computed once

Efficiency:

  • Time complexity: O(2^n) (exponential)

Iterative Alternative:

  • Uses loop to compute values in O(n) time.

Advantages & Disadvantages

Advantages:

  • Recursive version is easy to write and understand.

Disadvantages:

  • Extremely inefficient due to repeated calculations.
  • Exponential growth in number of calls.

Impact / Effect

  • Recursive Fibonacci is a poor solution for large n.
  • Demonstrates need for optimization in recursive algorithms.

Examples

  • Recursive computation of F6 involves 41 calls.
  • Iterative computation of F6 uses 6 steps.

Key Takeaways

  • Naive recursion can be inefficient.
  • Fibonacci is a classic case for memoization or iteration.
  • Always analyze call patterns before using recursion.
  • Iterative version is significantly faster and uses less memory.

Subtopic Title: Tail Recursion & Mutual Recursion


Part A: Tail Recursion

Definition

  • Tail recursion occurs when the last action in a recursive method is the recursive call itself.

Causes

  • Happens naturally when recursion is used for straightforward repetition.
  • Triggered by problems that don’t require post-processing after the recursive call.

Goals / Objectives

  • To simplify recursion so it can be converted into iteration.
  • To reduce overhead by making recursion more efficient.

Importance

  • Tail-recursive methods are easier to optimize.
  • Can be rewritten as loops to improve performance.

Procedures

Tail Recursive Method:

public static void countDown(int n) {
  if (n >= 1) {
    System.out.println(n);
    countDown(n - 1);
  }
}

Converted to Iteration:

public static void countDown(int n) {
  while (n >= 1) {
    System.out.println(n);
    n--;
  }
}

Advantages & Disadvantages

Advantages:

  • Easier to convert to iteration.
  • Reduces memory usage compared to regular recursion.

Disadvantages:

  • Still uses recursion unless explicitly converted.
  • May not be supported by all compilers for optimization.

Impact / Effect

  • Improves efficiency when converted to loops.
  • Reduces stack frame usage.

Examples

  • countDown(n) is a tail-recursive method.
  • Iterative version performs the same task with better efficiency.

Key Takeaways

  • Tail recursion ends with the recursive call.
  • Can be converted to iteration for better performance.
  • Useful for repetitive tasks like countdowns.
  • Helps reduce memory overhead.

Part B: Mutual Recursion

Definition

  • Mutual recursion (also called indirect recursion) occurs when two or more methods call each other in a cycle.

Causes

  • Arises in problems with alternating logic or layered parsing.
  • Triggered when one method’s logic depends on another’s result.

Goals / Objectives

  • To handle problems with interdependent conditions.
  • To demonstrate recursion beyond single-method calls.

Importance

  • Shows complexity of recursive relationships.
  • Useful in parsing expressions and grammar rules.

Procedures

  • Method A calls Method B
  • Method B calls Method A
  • Cycle continues until a stopping condition is met

Advantages & Disadvantages

Advantages:

  • Can model complex interdependent logic.
  • Useful in compiler design and expression parsing.

Disadvantages:

  • Difficult to trace and debug.
  • Can lead to infinite recursion if not carefully designed.

Impact / Effect

  • Adds complexity to recursion flow.
  • Requires careful design to avoid logical errors.

Examples

  • Parsing expressions:
    • isExpression()isTerm()isFactor()isExpression() (cycle)

Key Takeaways

  • Mutual recursion involves multiple methods calling each other.
  • Adds complexity and requires careful stopping logic.
  • Useful in specific domains like grammar parsing.
  • Harder to trace than single-method recursion.

Subtopic Title: Iteration vs Recursion & Final Discussion

Definition

  • Iteration uses loops to repeat actions.
  • Recursion uses method calls to repeat actions by reducing the problem.

Causes

  • Choice depends on problem structure and efficiency needs.

Goals / Objectives

  • To compare recursion and iteration.
  • To guide decision-making based on problem nature.

Importance

  • Helps choose the right approach for solving problems.
  • Critical for performance and clarity in programming.

Procedures

  • Evaluate the problem:
    • Is it naturally recursive?
    • Is efficiency critical?
  • Choose recursion for repetitive, nested, or divide-and-conquer problems.
  • Choose iteration for straightforward repetition.

Advantages & Disadvantages

Recursion Advantages:

  • Elegant for nested or repetitive structures.
  • Easier to implement for some problems.

Recursion Disadvantages:

  • Slower due to memory and call overhead.
  • Harder to debug.

Iteration Advantages:

  • Faster and more memory-efficient.
  • Easier to trace and debug.

Iteration Disadvantages:

  • Can be harder to write for complex problems.

Impact / Effect

  • Affects performance, readability, and maintainability.
  • Influences memory usage and execution time.

Examples

  • Fibonacci: Iterative version is O(n), recursive is O(2^n)
  • CountDown: Both recursive and iterative versions work, but iteration is more efficient.

Key Takeaways

  • Use iteration when it’s simpler and more efficient.
  • Use recursion when the problem is naturally recursive.
  • Efficiency matters in performance-critical systems.
  • Today’s computers can handle recursion, but choose wisely.