Repetition: Iteration and Recursion

CSCI 1933 – Introduction to Algorithms and Data Structures
Adriana Picoral

Control Structures – conditionals

  • Conditionals
  • if/else (not much different from Python)
  • switch (similar to python match)
public int foo(int n) {
  if (n > 0) return 1;
  else if (n < 0) return -1;
  else return 0;
}

Control Structures – loops

  • Iteratives - repetition
  • while (not much different from python)
  • for (C style)
  • do while (not covered)

Code Repetition

Code repetition can be accomplished through:

  • Iteration (loops) – repeat code n times (for loop) or until an end condition is met (while loop)
  • Recursion – function calls itself repeatedly until a specific end condition is met

Code Repetition – loops

Repetition through loops – use for with start, end condition, and step to repeat code n times

public double bar(double a, double b) {
  double result = 0.0;
  for (int i = 0; i < b; i++) {
    result += a;
  }
  return result;
}

The bar method above calculates a × b (multiplication through repeated addition)

Code Repetition – loops

Repetition through loops – use while with an end condition to repeat code until end condition is met

public double baz(double a, double b) {
  double result = 0.0;
  int i = 0;
  while (i < b) {
    result += a;
    i++;
  }
  return result;
}

The baz method above calculates a × b (multiplication through repeated addition)

Code Repetition – recursion

Repetition through recursion – function calls itself repeatedly until a specific end condition (base case) is met

public double myRecursiveMethod(double a, double b) {
  if (b == 0) return 0.0;
  return a + myRecursiveMethod(a, b-1);
}

The myRecursiveMethod method above calculates a × b (multiplication through repeated addition)

Recursion Stack

Let’s trace through with myRecursiveMethod(3.0, 4.0):

Building the stack (going down):

Call 1: myRecursiveMethod(3.0, 4.0)

  • Check: b == 0? No (b is 4)
  • Return: 3.0 + myRecursiveMethod(3.0, 3.0) ← waits for this result
  • Stack: [Call 1]

Recursion Stack

Call 2: myRecursiveMethod(3.0, 3.0)

  • Check: b == 0? No (b is 3)
  • Return: 3.0 + myRecursiveMethod(3.0, 2.0)← waits for this result
  • Stack: [Call 1, Call 2]

Recursion Stack

Call 3: myRecursiveMethod(3.0, 2.0)

  • Check: b == 0? No (b is 2)
  • Return: 3.0 + myRecursiveMethod(3.0, 1.0) ← waits for this result
  • Stack: [Call 1, Call 2, Call 3]

Recursion Stack

Call 4: myRecursiveMethod(3.0, 1.0)

  • Check: b == 0? No (b is 1)
  • Return: 3.0 + myRecursiveMethod(3.0, 0.0) ← waits for this result
  • Stack: [Call 1, Call 2, Call 3, Call 4]

Recursion Stack

Call 5: myRecursiveMethod(3.0, 0.0)

  • Check: b == 0? Yes! Base case reached
  • Return: 0.0 immediately
  • Stack: [Call 1, Call 2, Call 3, Call 4, Call 5] ← maximum depth

Recursion Stack – Unwinding the stack

Unwinding the stack (going up):

  • Call 5 returns: 0.0

Stack: [Call 1, Call 2, Call 3, Call 4]

Recursion Stack – Unwinding the stack

  • Call 4 completes: 3.0 + 0.0 = 3.0 → returns 3.0

Stack: [Call 1, Call 2, Call 3]

Recursion Stack – Unwinding the stack

  • Call 3 completes: 3.0 + 3.0 = 6.0 → returns 6.0

Stack: [Call 1, Call 2]

Recursion Stack – Unwinding the stack

  • Call 2 completes: 3.0 + 6.0 = 9.0 → returns 9.0

Stack: [Call 1]

Recursion Stack – Unwinding the stack

  • Call 1 completes: 3.0 + 9.0 = 12.0 → returns 12.0

Stack: [] (empty)

Final result: 12.0

Recursive Algorithm Basics

  • Reduction formula – making the problem size smaller – this needs to get closer to the base case
  • Base Case – the trivial (easiest) case, check for it first! It the solution you already know.

Key: find what is the smaller/simpler version of a problem

StackOverflowError – A stack overflow error occurs when a program attempts to use more memory space in the call stack than what Java allocates

Iterative Algorithm Basics

  • Starting state – the trivial (easiest) case
  • Progress
  • Terminating criteria
  • Incremental building upon a starting state

Practice

Write a Java class called Factorial with two public methods, both return the factorial of an int argument n:

  • One method is called factorialLoop(int n) – it solves factorial through iteration
  • The other method is called factorialRecursion(int n) – it solves factorial through recursion

Both methods should be static – no need for instance variables. Submit your Factorial.java file to gradescope. Test cases: 0! = 1, 5! = 120, 6! = 720

Solution

public class Factorial {
    public static int factorialLoop(int n) {
        int result = 1;
        for (int i = 1; i <= n; i++) {
            result *= i;
        }
        return result;
    }

    public static int factorialRecursion(int n) {
        if (n <= 1) return 1;
        return n * factorialRecursion(n-1);
    }

}

Practice

Write two static methods (one with an interative loop and another with recursion) to sum all integers between a and b (inclusive). For a = 3 and b = 5 your methods should compute 3 + 4 + 5 and return 12

Your methods signatures should be:

  • public static int sumIter(int a, int b)
  • public static int sumRecur(int a, int b)

Submit your SumInts.java to gradescope

Solution

public class SumInts {

    public static int sumIter(int a, int b) {
        int total = 0;
        while (a <= b) {
            total = total + a;
            a++;
        }
        return total;
    }

    public static int sumRecur(int a, int b) {
        if (a > b) return 0;
        return a + sumRecur(a + 1, b);
    }

    public static void main(String[] args) {
        System.out.println("sumIter(1, 10) is: " + sumIter(1, 10));
        System.out.println("sumRecur(1, 10) is: " + sumRecur(1, 10));
    }
}

Tracing sumIter(1, 3)

line a<=b a total
1 4 - 1 0
2 5 true 1 0
3 6 - 1 1
4 7 - 2 1
5 5 true 2 1
6 6 - 2 3
line a<=b a total
7 7 - 3 3
8 5 true 3 3
9 6 - 3 6
10 7 - 4 6
11 5 false 3 3
12 9 - 3 3

Call Stack sumRecur(1, 3)

sumRecur(1, 3) → waits for sumRecur(2, 3) to return
  sumRecur(2, 3) → waits for sumRecur(3, 3) to return
    sumRecur(3, 3) → waits for sumRecur(4, 3) to return
      sumRecur(4, 3) → returns 0
    sumRecur(3, 3) → returns 3 + 0 = 3
  sumRecur(2, 3) → returns 2 + 3 = 5
sumRecur(1, 3) → returns 1 + 5 = 6

Tail Recursion (Iterative Recursion)

public static int sumRecur(int a, int b, int total)

Write another sumRecur method (Java lets you overload methods), but add an accumulator parameter

  • The accumulator builds up the result as it recurses

Tail Recursion (Iterative Recursion)

public static int sumRecur(int a, int b, int total) {
  if (a > b) return total;
  return sumRecur(a + 1, b, total + a);
}

Tail Recursion sumRecur(int a, int b, int total)

  • The addition happens before the recursive call (accumulated in total)
  • The recursive call is the last operation (nothing happens after it returns)
  • No pending operations need to be remembered

Call stack example for sumRecur(1, 3, 0):

sum3(1, 3, 0) → sum3(2, 3, 1)
sum3(2, 3, 1) → sum3(3, 3, 3)
sum3(3, 3, 3) → sum3(4, 3, 6)
sum3(4, 3, 6) → returns 6

Tail Recursion (Iterative Recursion)

  • The recursive call is in the “tail position” - it’s the very last thing the function does
  • In languages with tail call optimization, the compiler can replace the recursion with a loop, making it as efficient as iteration
  • It doesn’t build up the call stack since there’s nothing to remember after each call

Java doesn’t guarantee tail call optimization, but the pattern is still more memory-efficient.

Looking Back

  • How is thinking recursively different from thinking iteratively?
  • Alternatively: how is an iterative process different from a recursive process?
  • Can you differentiate recursive and iterative thinking from the recursion and iterative language constructs?
  • When would you opt for a while-loop over a for-loop, or vice-versa? Give examples.
  • Write an iterative method to count the number of integers between a and b (inclusive) that are evenly divisible by all of 3, 5, and 7. Use a while- loop.
  • Re-write your solution from above using a for-loop.