A recursive method is a method that is defined in terms of itself. The
general idea behind recursion is that a problem lends itself to a recursive
solution if the problem can be broken down into a smaller version of the same
problem. A nonCS example of this is the
Matroyshka doll
. A
mathematical example of this is the computation of the factorial of a number.
factorial(N) is N*factorial(N1).
Iterative Factorialpublic int factorial(int n) { Recursive Factorial(Write the previous method without using a loop!) public int factorial(int n) { Try running this for large values of n! Comparison
If you can solve it iteratively, you usually should! The Base CaseThe number 1 rule of recursion:
At some point, your recursion must end. There must be some trivial version of the problem that is easily solved without using recursion. In the factorial example, the factorial of 1 is 1. If you fail to specify a base case, you will end up with infinite recursion . In fact such a program will not execute infinitely as an infinite loop might, but will execute until you get a StackOverflowException. The Recursive CaseThe number 2 rule of recursion:
Consider what would happen if we mistakenly called the recursive factorial passing n instead of n1 on the last line of the method. Because we would not be making progress toward the base case, the program would continue calling itself with the same input until we encountered a StackOverflow Exception. Other Recursion RulesThe 3rd and 4th rules of recursion:
Linear RecursionA linear recursive algorithm contains at most 1 recursive call at each iteration. Factorial is an example of this. The general algorithm followed by factorial is (1) test for the base case; (2) recurse. Factorial is also an example of tail recursion . In tail recursion, the recursive call is the last operation in the method. Tail recursive methods can easily be converted to iterative algorithms. Exercise: Implement a recursive method that takes as input a String and prints the characters of the String in reverse order. Consider how you would implement the method if you were only able to either access the first character of the String, or extract a multicharacter substring. Another good example of linear recursion is Binary Search. Suppose you have a sorted array of numbers and you want to determine whether a particular number appears in the list. The linear algorithm would start at the beginning of the array and look at each element until it came to the desired element or the end of the list. At maximum, this would require n comparisons where n is the number of items in the list. To reduce the number of comparisons, we can take advantage of the fact that the array is sorted. Rather than start at the beginning, we can start in the middle. If the middle element is larger than the element we are looking for, we know that the element will be in the left side of the array if it exists in the array. Similarly, if the middle element is smaller than the element we are looking for, we know that the element will be in the right side of the array if it exists. At each step, we can ignore half of the remaining elements. It turns out that this algorithm will result in a maximum of log(N) comparisons. A general search method would take as input the element you are looking for and the array in which you want to search. However, to implement binary search, your recursive method should take as input the element you are looking for, the array you want to search, and the starting and ending indices of the subarray where you want to search. To alleviate this inconsistency, it is typical to implement a driver method that supports the interface you would expect (input and element and an array). The driver method simply calls the recursive helper method passing the appropriate data. In this case, the driver method would call the recursive method passing the element, the array, the starting index 0, and the length of the array minus 1 as the ending index. Exercise: Implement a method to perform a binary search. Think about the base case and how you will make progress toward the base case at each iteration. Higher Order RecursionAn algorithm certainly can make more than 1 recursive call. A really bad example of this is the calculation of Fibonacci numbers. The nth Fibonacci number is the (n1)st Fibonacci number plus the (n2)nd Fibonacci number. The 0th Fibonacci number is 0 and the 1st Fibonacci number is 1. This problem lends itself well to a recursive solution, but violates rule number 4, never duplicate work. The following recursive algorithm to solve for the nth Fibonacci number duplicates lots of work: fib(n) A better example of higher order recursion is mergesort. The mergesort algorithm is also an example of binary recursion , a recursive algorithm that splits a problem in half and recursively solves each half. Mergesort is similar to binary search, except it makes 2 recursive calls and then must merge the results. The general algorithm for mergesort is as follows: mergesort(elt, array, start, end) Exercise: Implement a recursive method to print the following output. Assume your method takes as input the length of the longest row (in this case 5), and another integer that will represent the progress toward the base case. 0 Exercise: Implement a recursive method to solve the towers of hanoi. The idea of the puzzle is that you have three pegs and a set of disks where each disk is a different size. The goal is to move all of the disks from peg 1 to peg 3 while not violating the following rules:
