Verified for the 2025 AP Computer Science A exam•Last Updated on June 18, 2024
Searching and sorting are fundamental operations in computer science, and recursion provides elegant solutions for both. This study guide explores how recursion can be applied to create efficient search algorithms like binary search and powerful sorting algorithms like merge sort. These recursive approaches demonstrate how complex problems can be broken down into simpler subproblems to achieve efficient solutions.
Binary search is an efficient algorithm for finding a target value in a sorted collection. Unlike sequential search (checking each element one by one), binary search eliminates half of the remaining elements in each step.
public static int binarySearch(int[] arr, int target, int left, int right) { // Base case: element not found if (left > right) { return -1; } // Find middle index int mid = (left + right) / 2; // Base case: element found if (arr[mid] == target) { return mid; } // Recursive case: search left half else if (target < arr[mid]) { return binarySearch(arr, target, left, mid - 1); } // Recursive case: search right half else { return binarySearch(arr, target, mid + 1, right); } } // Initial call int index = binarySearch(myArray, targetValue, 0, myArray.length - 1);
public static int binarySearch(ArrayList<Integer> list, int target, int left, int right) { if (left > right) { return -1; } int mid = (left + right) / 2; if (list.get(mid) == target) { return mid; } else if (target < list.get(mid)) { return binarySearch(list, target, left, mid - 1); } else { return binarySearch(list, target, mid + 1, right); } }
public static int binarySearch(String[] arr, String target, int left, int right) { if (left > right) { return -1; } int mid = (left + right) / 2; int comparison = target.compareTo(arr[mid]); if (comparison == 0) { return mid; // Found it! } else if (comparison < 0) { return binarySearch(arr, target, left, mid - 1); // Search left } else { return binarySearch(arr, target, mid + 1, right); // Search right } }
Search Algorithm | Time Complexity |
---|---|
Sequential Search | O(n) – Linear time |
Binary Search | O(log n) – Logarithmic time |
For large datasets, the efficiency improvement is dramatic:
Array Size | Sequential Search (worst case) | Binary Search (worst case) |
---|---|---|
1,000 | 1,000 comparisons | ~10 comparisons |
1,000,000 | 1,000,000 comparisons | ~20 comparisons |
1,000,000,000 | 1,000,000,000 comparisons | ~30 comparisons |
Let's trace the execution of binary search on the array [10, 20, 30, 40, 50, 60, 70]
looking for the value 30
:
Call | left | right | mid | arr[mid] | Action |
---|---|---|---|---|---|
1st | 0 | 6 | 3 | 40 | 30 < 40, search left half |
2nd | 0 | 2 | 1 | 20 | 30 > 20, search right half |
3rd | 2 | 2 | 2 | 30 | 30 == 30, return 2 |
Merge sort is a recursive sorting algorithm that follows the divide-and-conquer approach:
public static void mergeSort(int[] arr, int left, int right) { if (left < right) { // Find the middle point int mid = (left + right) / 2; // Sort first and second halves mergeSort(arr, left, mid); mergeSort(arr, mid + 1, right); // Merge the sorted halves merge(arr, left, mid, right); } } private static void merge(int[] arr, int left, int mid, int right) { // Calculate sizes of subarrays int n1 = mid - left + 1; int n2 = right - mid; // Create temporary arrays int[] leftArray = new int[n1]; int[] rightArray = new int[n2]; // Copy data to temp arrays for (int i = 0; i < n1; ++i) leftArray[i] = arr[left + i]; for (int j = 0; j < n2; ++j) rightArray[j] = arr[mid + 1 + j]; // Merge temp arrays int i = 0, j = 0; int k = left; while (i < n1 && j < n2) { if (leftArray[i] <= rightArray[j]) { arr[k] = leftArray[i]; i++; } else { arr[k] = rightArray[j]; j++; } k++; } // Copy remaining elements while (i < n1) { arr[k] = leftArray[i]; i++; k++; } while (j < n2) { arr[k] = rightArray[j]; j++; k++; } } // Initial call mergeSort(myArray, 0, myArray.length - 1);
public static void mergeSort(ArrayList<Integer> list, int left, int right) { if (left < right) { int mid = (left + right) / 2; mergeSort(list, left, mid); mergeSort(list, mid + 1, right); merge(list, left, mid, right); } } private static void merge(ArrayList<Integer> list, int left, int mid, int right) { // Create temporary lists ArrayList<Integer> leftList = new ArrayList<>(); ArrayList<Integer> rightList = new ArrayList<>(); // Copy data to temp lists for (int i = left; i <= mid; i++) leftList.add(list.get(i)); for (int i = mid + 1; i <= right; i++) rightList.add(list.get(i)); // Merge temp lists int i = 0, j = 0, k = left; while (i < leftList.size() && j < rightList.size()) { if (leftList.get(i) <= rightList.get(j)) { list.set(k, leftList.get(i)); i++; } else { list.set(k, rightList.get(j)); j++; } k++; } // Copy remaining elements while (i < leftList.size()) { list.set(k, leftList.get(i)); i++; k++; } while (j < rightList.size()) { list.set(k, rightList.get(j)); j++; k++; } }
For the array [38, 27, 43, 3, 9, 82, 10]
:
[38, 27, 43, 3]
and [9, 82, 10]
[38, 27]
, [43, 3]
, [9, 82]
, [10]
[38]
, [27]
, [43]
, [3]
, [9]
, [82]
, [10]
[27, 38]
, [3, 43]
, [9, 82]
, [10]
[3, 27, 38, 43]
, [9, 10, 82]
[3, 9, 10, 27, 38, 43, 82]
Sort Algorithm | Time Complexity |
---|---|
Selection Sort | O(n²) – Quadratic time |
Insertion Sort | O(n²) – Quadratic time |
Merge Sort | O(n log n) – Linearithmic time |
For large datasets, the efficiency gain is significant:
Array Size | Selection/Insertion Sort (worst case) | Merge Sort (worst case) |
---|---|---|
1,000 | ~1,000,000 operations | ~10,000 operations |
1,000,000 | ~1,000,000,000,000 operations | ~20,000,000 operations |
Both binary search and merge sort can be implemented iteratively or recursively:
// Iterative binary search public static int binarySearchIterative(int[] arr, int target) { int left = 0; int right = arr.length - 1; while (left <= right) { int mid = (left + right) / 2; if (arr[mid] == target) { return mid; } else if (target < arr[mid]) { right = mid - 1; } else { left = mid + 1; } } return -1; }
The recursive version can be more elegant, but both approaches have the same time complexity. The iterative version uses constant space, while the recursive version uses O(log n) space for the call stack.
Recursive searching and sorting algorithms demonstrate the power of the divide-and-conquer approach in computer science. Binary search provides efficient lookup in sorted collections by eliminating half of the remaining elements in each step, achieving logarithmic time complexity. Merge sort offers efficient sorting through recursive division and merging, achieving linearithmic time complexity. Both algorithms can be implemented recursively or iteratively, with recursive implementations often providing more elegant and intuitive solutions at the cost of additional memory usage for the call stack. These algorithms showcase how recursion can be applied to solve complex problems by breaking them down into simpler subproblems.