10.1 Recursion

A method calling itself to repeat a certain number of times.

Popcorn Hack: What is recursion?

A method that calls itself as many times as needed to perform a certain task.

Vocabulary

  • Base Case - this sets the final requirement for when the recursion will stop
  • Formal Parameters - Attributes within the method (local variables)

Method Calling Itself

We will first be looking at how a method can call itself.

/* 
DO NOT RUN THIS
if you do run this accidentally press `ctrl + c` twice
*/

public class Recursion1 {
  public static void recursionCount(int n) { // original method definition
    System.out.println(n);
    recursionCount(n - 1); // calling the method again
  }

  public static void main(String[] args) {
    recursionCount(3); // original call of the method
  }
}

Recursion1.main(null);

In this example, you will receive an overflow error because the method is calling itself infinitely. To avoid this you would create a base case.

A base case usually uses an if-then statement to turn the loop off after a certain condition is met.

public class Recursion2 {
  public static void recursionCount(int n) {
    if (n <= 0) { // added base case, telling the program to stop when n is less than or equal to 0
      System.out.println("done");
    } else {
      System.out.println(n);
      recursionCount(n - 1); // method recursion
    }
  }

  public static void main(String[] args) {
    recursionCount(3); // calling original function
  }
}

Recursion2.main(null);
3
2
1
done

Popcorn Hack: Create your own quick recursive program.

// Add code here

public class MyRecursion {
    public static int recursion(int n){
        if(n > 0) {
            return n + recursion(n-1);
        } else {
            return 0;
        }
    }

    public static void main(String [] args){
        int sum = recursion(14);
        System.out.println(sum);
    }
}

MyRecursion.main(null);
105

Calling With Different Parameters

You can also call the method with multiple different parameters, also known as local variables.

In this example we call an integer and string.

public class Recursion3 {
  public static void recursionPrint(int n, String a) { // formal parameters are within this method
    if (n <= 0) { // base case
      System.out.println("done");
      return;
    }

    System.out.println("n = " + n + " a = " + a); // printing each var with each call of the method

    String addChar = a + "!"; // mutating string with each recursion
    int addN = n - 1; // mutating integer with each recursion

    recursionPrint(addN, addChar); // recursive function
  }

  public static void main(String[] args) {
    recursionPrint(5, "test"); // initial call
  }
}
Recursion3.main(null);
n = 5 a = test
n = 4 a = test!
n = 3 a = test!!
n = 2 a = test!!!
n = 1 a = test!!!!
done

Here we just compound each string and subtract one from every previous number.

Capturing Progression Through Recursion

You can also capture the progression through recursion. This is useful for doing specific tasks in specific steps through recursion.

public class Recursion4 {
  public static void recursionCount(int n, int progress) {
    if (n <= 0) {
      System.out.println("done");
    } else {
      if (n == progress) {
        System.out.println("Reached halfway through the array."); // special action done only when the program has reached a certain point
      }
      System.out.println(n);
      recursionCount(n - 1, progress); // recursive call
    }
  }

  public static void main(String[] args) {
    int n = 6;
    int progress = n / 2; // progress is set to half of the array length
    recursionCount(n, progress); // initial call
  }
}

Recursion4.main(null);
6
5
4
Reached halfway through the array.
3
2
1
done

Popcorn Hack: What other situations could we use capturing progression for?

  • Filling up a water tank
  • Distance from one point to another

Converting Recursion to Iteration

Any recursive process can be made iterative, however this is not needed for the AP test. It is, however, still useful.

Recursive Version

public class Recursion5 {
  public static void recursionCount(int n) {
    if (n <= 0){
      System.out.println("done");
    } else {
      System.out.println(n);
      recursionCount(n - 1); // recursive function
    }
  }

  public static void main(String[] args) {
    recursionCount(3); // initial call
  }
}

Recursion5.main(null);
3
2
1
done

Iterative Version

public class Iteration {
  public static void iterationCount(int n) {
    for (int i = n; i >= 1; i--) { // using a for loop to iterate, notice we go only so i is greater than 1 and not 0
      System.out.println(i);
    }
    System.out.println("done");
  }
  
  public static void main(String[] args) {
    iterationCount(3); // same initial call
  }
}

Iteration.main(null);
3
2
1
done

Popcorn Hacks: We we go only so i is greater than or equal to 1 and not 0. Why?

We make it 1 so that the recursion function stops printing numbers at 1

Traversing With Recursion

You can traverse many things with recursion. Here I will show traversal through a string, array, and an ArrayList object.

String Traversal

You can traverse through a string, finding a specific character in the string.

public class FindChar {
    public static boolean findChar(String s, char target, int index) {
        if (index >= s.length()) { // checks for if the index we are looking for is greater than the word length
            return false;
        }

        if (s.charAt(index) == target) { // checks for if there is a character the index we are searching
            return true;
        }

        return findChar(s, target, index + 1); // recalls method, recursion
    }

    public static void main(String[] args) {
        String sampleString = "words"; // example string
        char targetChar = 's'; // character we are looking for

        System.out.println("Sample String: " + sampleString);

        boolean isCharFound = findChar(sampleString, targetChar, 0); // checks if the character we are looking for is found, based on boolean

        if (isCharFound) {
            System.out.println("Character '" + targetChar + "' is found at "); // if it is found, show this
        } else {
            System.out.println("Character '" + targetChar + "' is not found."); // if not show this
        }
    }
}

FindChar.main(null);
Sample String: words
Character 's' is found at 

You can also find and print a certain range of characters from the string.

public class PrintRange {
    public static void printRange(String s, int startIndex, int endIndex) {
        if (startIndex < 0 || endIndex > s.length() || startIndex >= endIndex) { // checks for invalid declaration or end (negative index, index larger than string length, index start has reached the end)
            return;
        }

        System.out.print(s.charAt(startIndex)); // print the characters in the range
        printRange(s, startIndex + 1, endIndex); // recursive calling
    }

    public static void main(String[] args) {
        String sampleString = "example"; // string
        int startIndex = 0;
        int endIndex = 5;
        
        System.out.println("Word: " + sampleString);
        System.out.println("Start: " + startIndex + ", End: " + endIndex);

        System.out.print("Characters in the specified range: ");
        printRange(sampleString, startIndex, endIndex); // initial call
    }
}

PrintRange.main(null);
Word: example
Start: 0, End: 5
Characters in the specified range: examp

Popcorn Hack: Make it so that we can find two different ranges and print them.

public class PrintTwoRanges {
    public static void printFirstRange(String s, int startIndex, int endIndex) {
        if (startIndex < 0 || endIndex > s.length() || startIndex >= endIndex) {
            return;
        }

        System.out.print(s.charAt(startIndex)); // print the character at the startIndex
        printFirstRange(s, startIndex + 1, endIndex); // recursive call
    }

    public static void printSecondRange(String s1, int startIndex1, int endIndex1){
        if(startIndex1 < 0 || endIndex1 > s1.length() || startIndex1 >= endIndex1){
            return;
        }

        System.out.print(s1.charAt(startIndex1)); // print the character at the startIndex1
        printSecondRange(s1, startIndex1 + 1, endIndex1); // recursive call
    }

    public static void main(String[] args) {
        String sampleString = "example"; // the string to print characters from
        int startIndex = 0; // start index for the first range
        int endIndex = 3; // end index for the first range (exclusive)

        int startIndex1 = 3; // start index for the second range
        int endIndex1 = 7; // end index for the second range (exclusive)
        
        System.out.println("Word: " + sampleString);
        
        System.out.print("First range (" + startIndex + " to " + (endIndex - 1) + "): ");
        printFirstRange(sampleString, startIndex, endIndex); // print the first range
        
        System.out.print("\nSecond range (" + startIndex1 + " to " + (endIndex1 - 1) + "): ");
        printSecondRange(sampleString, startIndex1, endIndex1); // print the second range
    }
}

PrintTwoRanges.main(null);

Word: example
First range (0 to 2): exa
Second range (3 to 6): mple

Array Traversal

You can also traverse arrays using recursion.

public class ArrayTraversal {
  public static int findIndex(int[] arr, int targetIndex, int currentIndex) {
    if (currentIndex < 0 || currentIndex >= arr.length) { // checks for invalid (index less than 0, index greater than array length)
      return -1; // returns invalid
    }

    if (currentIndex == targetIndex) { // if the index is found
      return currentIndex;
    }

    return findIndex(arr, targetIndex, currentIndex + 1); // recursion
  }

  public static void main(String[] args) {
    int[] sampleArray = {1, 4, 6, 8, 4, 9, 12};
    int targetIndex = 2; // target

    int foundIndex = findIndex(sampleArray, targetIndex, 0);

    if (foundIndex != -1) { // checks what has been outputted
      int value = sampleArray[foundIndex]; // found index value
      System.out.println("Index " + targetIndex + " is found in the array at position " + foundIndex + " with a value of " + value); // found
    } else {
      System.out.println("Index " + targetIndex + " is not found in the array."); // not found
    }
  }
}

ArrayTraversal.main(null);
Index 2 is found in the array at position 2 with a value of 6

ArrayList Object Traversal

We can also traverse the ArrayList object.

import java.util.ArrayList;

public class ArrayListTraversal {
    public static int findIndex(ArrayList<Integer> list, int targetIndex, int currentIndex) {
        if (currentIndex < 0 || currentIndex >= list.size()) { // checks for invalid
            return -1;
        }

        if (currentIndex == targetIndex) { // finds index
            return currentIndex;
        }

        return findIndex(list, targetIndex, currentIndex + 1); // recusion
    }

    public static void main(String[] args) {
        ArrayList<Integer> sampleList = new ArrayList<>(); // created ArrayList object
        sampleList.add(1);
        sampleList.add(4);
        sampleList.add(6);
        sampleList.add(8);
        sampleList.add(4);
        sampleList.add(9);
        sampleList.add(12);

        int targetIndex = 2; // target

        int foundIndex = findIndex(sampleList, targetIndex, 0);

        if (foundIndex != -1) { // checks for if found or not
            int value = sampleList.get(foundIndex); // gets found index from ArrayList object
            System.out.println("Index " + targetIndex + " is found in the ArrayList at position " + foundIndex + " with a value of " + value);
        } else {
            System.out.println("Index " + targetIndex + " is not found in the ArrayList.");
        }
    }
}

ArrayListTraversal.main(null);
Index 2 is found in the ArrayList at position 2 with a value of 6

Conclusion

Must Knows:

  • How to create recursive code (recalling a method within itself, with parameters that change)
  • Record progression through recursion (completing certain tasks through the progression)
  • Traversal of strings, arrays, and the ArrayList object

Overall, recursion is useful as an alternative to iteration. Recursion is usually chosen for problems that can naturally be divided into parts, like sorting which will be talked about in the next part.

target number - 24

intArray - 0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40

Popcorn hack:

How many times iterated through for Linear Search?

Answer: 13

Example of Binary Search with Recursion

public class BinarySearch {
    public static void main(String[] args) {
        int[] intArray = {0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40}; // Example array
        int target = 24; // Example target value
        int result = recBinarySearch(intArray, 0, intArray.length - 1, target);

        if (result == -1) {
            System.out.println("Target not found in the array.");
        } else {
            System.out.println("Target found at index: " + result);
        }
    }

    public static int recBinarySearch(int[] intArray, int lowPosition, int highPosition, int target) { //method
        if (lowPosition > highPosition) {
            return -1; // Element not found in the array
        } else {
            int midPosition = (lowPosition + highPosition) / 2;
            if (intArray[midPosition] < target) {
                return recBinarySearch(intArray, midPosition + 1, highPosition, target);
            } else if (intArray[midPosition] > target) {
                return recBinarySearch(intArray, lowPosition, midPosition - 1, target);
            } else {
                return midPosition; // Element found at midPosition
            }
        }
    }
}

BinarySearch.main(null);
Target found at index: 12

Call 1

Index = 0 - 20, midPosition = 10, midPosition value = 20

Since 24 > 20,

then…

lowPosition = midPosition + 1 = 11 highPosition = highPosition = 20

Call 2

Index = 11-20, midPosition index = 15, midPosition value = 30

Since 24 < 30,

then…

lowPosition = lowPosition = 11 high position = midPosition - 1 = 14

Call 3

Index = 11-14, midPosition index = 12, midPosition value = 24

Since 24 = 24,

then…

return midPosition;

In total, our recursive calls to the method 3 times.

Recursive Logic behind Merge Sort

What is Merge Sort? Merge sort is a recursive sorting algorithm that can be used to sort elements in an array or ArrayList

  • Follows a divide and conquer approach

Link

Notes:

List: [38,27,43,3,9,82,10] 

sudocode version:
mergeSort(List) {
    mergeSort(left)
    mergeSort(right)
    merge(left & right)

} 
  • Must finish call above it in order to finish the call

Call 1:

  1. Splitting List into half
  2. Left side: [38, 27, 43, 3]
  3. Must finish call 1 in order to do the right side and do the merge
  4. Recursively calls mergesort and splits the list in half again

Call 2:

  1. New Left side List: [38, 27]
  2. Method calls are stacking on top of each other

image

Notes:

  1. Element 5 can’t be split into the left or the right, nor can it be merged with itself
  2. Consider the left call complete!

image

Notes:

  1. Same thing applies with the right, element 25 can’t be split to the left or the right, nor can it merge with itself.
  2. Now we will merge them back in order: [5, 25]

Important concepts:

  1. When making new recursive call, we are NOT making a new list, array, or a new set of elements.
  2. Basically updating all the way back to the original list

img

Notes:

  1. When merging back together, it will merge back from least to greatest.

img

Popcorn Hack: What will the final list be?

Answer: -2 0 2 14 (right call) -9, -2, 0, 2, 5, 8, 14, 25

The mergeSort Method

//sudocode
mergeSort(myArray, low, high) {
    if (low < high) {
        middle = (low + high)/2; //find middle
        mergeSort(myArray, low, middle); //make a recursive call from low to middle (look at left hand side)
        mergeSort(myArray, middle + 1, high); //once low is no longer less than high, make a new recursive call (look at right hand side)
        merge (myArray, low, middle, high); //merge back together
    }
}
int [] myArray = {3, 4, 6, 8, 1, 2, 5, 7};

Steps:

  1. Split the Array in half
  2. Left side: {3, 4, 6, 8}; Right side {1, 2, 5, 7};

img

  1. Compare the first indexes in each individual array (which would be index 0 and index 4)

img

img

  1. Since 1<3, our new index 0 value would be 1 when we merge the array back together

img

img

  1. Since 5>3, our new index 2 value would be 3 when we merge the array back together

Popcorn Hack: What will the final array return?

Answer: {1, 2, 3, 4, 5, 6, 7, 8}

Wait, but there’s an issue…

img

img

  • After comparing index 3 and index 7, we then need to compare index 3 and index, but there is no index 8…
  • Will recieve an index out of bounds exception.

No worries! Since we are done with the sort, we can just move the rest of the elements to the end of the array because we are already done sorting.

Index 3 will now become index 7.

Hacks

  • Create a 2D array either with just a regular array variable that you can recursively traverse through.
  • Each value in this 2D array must be a string that you can individually traverse through.
  • You must output a result of all string values that have a user inputted letter.
import java.util.Scanner;

public class String2DArrayTraversal {

    public static void traverseString2DArray(String[][] array, char letter, int row, int col) {
        if (row < 0 || col < 0 || row >= array.length || col >= array[row].length) {
            return;
        }

        if (array[row][col].indexOf(letter) != -1) {
            System.out.println(array[row][col]);
        }

        if (col + 1 < array[row].length) {
            traverseString2DArray(array, letter, row, col + 1);
        } else if (row + 1 < array.length) {
            traverseString2DArray(array, letter, row + 1, 0);
        }
    }

    // 2D array of words
    public static void main(String[] args) {
        String[][] stringArray = {
            {"song", "baby", "temple"},
            {"angel", "wire", "pioneer"},
            {"tenant", "pottery", "kidney"}
        };

        // checks strings that have user inputted eltter
        Scanner scanner = new Scanner(System.in);
        System.out.print("Enter a letter to search for: ");
        String input = scanner.nextLine();

        if (input.length() != 1) {
            System.out.println("Please enter a single letter.");
        } else {
            char letter = input.charAt(0);
            System.out.println("\nStrings containing the letter '" + letter + "':");
            // Start the traversal at the first row and first column
            traverseString2DArray(stringArray, letter, 0, 0);
        }

        scanner.close();
    }
}

String2DArrayTraversal.main(null);

Enter a letter to search for: 
Strings containing the letter 'r':
wire
pioneer
pottery

Number Version

I essentially created the same code except instead of the scanner checking for a certain letter, it checks which numbers have a user inputted number.

import java.util.Scanner;
import java.util.InputMismatchException;

public class Int2DArrayTraversal {

    public static void traverseInt2DArray(int[][] array, int num, int row, int col) {
        if (row < 0 || col < 0 || row >= array.length || col >= array[row].length) {
            return;
        }
    
        // Convert both numbers to strings to check if the array element contains the digit(s)
        String numStr = Integer.toString(num);
        String currentNumStr = Integer.toString(array[row][col]);
    
        // Check if the current number as a string contains the numStr
        if (currentNumStr.contains(numStr)) {
            System.out.println(array[row][col]);
        }
    
        // Move to the next column
        if (col + 1 < array[row].length) {
            traverseInt2DArray(array, num, row, col + 1);
        } else if (row + 1 < array.length) {
            // Move to the next row when the end of the current row is reached
            traverseInt2DArray(array, num, row + 1, 0);
        }
    }

    public static void main(String[] args) {
        int[][] numArray = {
            {12, 43, 75},
            {183, 423, 986},
            {1203, 1722, 2023}
        };

        Scanner scanner = new Scanner(System.in);
        int input = 0;
        boolean validInput = false;

        while (!validInput) {
            System.out.print("Enter a number to search for: ");
            try {
                input = scanner.nextInt();
                validInput = true;
            } catch (InputMismatchException ime) {
                System.out.println("That's not a valid number. Please try again.");
                scanner.next(); // Consume the invalid token to avoid infinite loop
            }
        }

        System.out.println("\nSearching for the number '" + input + "' in the 2D array.");
        traverseInt2DArray(numArray, input, 0, 0);

        scanner.close();
    }
}

Int2DArrayTraversal.main(null);

Enter a number to search for: 
Searching for the number '3' in the 2D array.
43
183
423
1203
2023

Create your own merge chart (like the first image in this 10.2 lesson) with your own values from a list, array, or arraylist (doesn’t matter). Explain how recursion works in the merge chat.

public class MergeSort {
    public static void main(String [] args){


        int [] nums = {13, 5, 27, 16, 10, 38, 1};
        System.out.println("Before Merge Sort: " + Arrays.toString(nums));
        mergeSort(nums);
        System.out.println("After Merge Sort: " + Arrays.toString(nums));

    }

    private static void mergeSort(int [] array){
        int arrayLength = array.length;

        if(arrayLength < 2){
            return;
        }

        int mid = arrayLength / 2;
        int [] leftHalf = new int[mid];
        int [] rightHalf = new int[arrayLength - mid];

        for(int i = 0; i < mid; i ++){
            leftHalf[i] = array[i];
        }
        for(int i = mid; i < arrayLength; i ++){
            rightHalf[i - mid] = array[i];
        }

        mergeSort(leftHalf);
        mergeSort(rightHalf);

        merge(array, leftHalf, rightHalf);
    }

    private static void merge(int[] array, int [] leftHalf, int [] rightHalf){
        int leftSize = leftHalf.length;
        int rightSize = rightHalf.length;

        int i = 0, j = 0, k = 0;

        while(i < leftSize && j < rightSize){
            if(leftHalf[i] <= rightHalf[j]){
                array[k] = leftHalf[i];
                i++;
            } else {
                array[k] = rightHalf[j];
                j++;
            }
            k++;
        }

        while (i < leftSize){
            array[k] = leftHalf[i];
            i++;
            k++;
        }

        while(j < rightSize){
            array[k] = rightHalf[j];
            j++;
            k++;
        }
    }
}

MergeSort.main(null);
Before Merge Sort: [13, 5, 27, 16, 10, 38, 1]
After Merge Sort: [1, 5, 10, 13, 16, 27, 38]