CISC 3115
Modern Programming Techniques
Lecture #5
Recursion
Motivation
Given a file containing an arbitrary number of integers, write a program that reads in the integers and prints them in reverse …
without using an array.
import java.util.*;
import java.io.*;
public class PrintInReverse{
public static void main(String [] args) throws Exception {
String filename = "../../numbers.text";
int count = getNumIntsInFile(filename);
for (int i = count; i >t; 0; i--)
printNthInt(filename, i);
}
static int getNumIntsInFile(String filename) throws Exception {
Scanner scanner = new Scanner(new File(filename));
int count = 0;
while (scanner.hasNextInt()) {
count++;
scanner.nextInt(); // skip
}
return count;
}
static void printNthInt(String filename, int n) throws Exception {
Scanner scanner = new Scanner(new File(filename));
// skip n-1 integers
for (int i = 1; i < n; i++)
scanner.nextInt();
System.out.println(scanner.nextInt()); // Print the n'th integer
}
}
Recursion
A technique that allows one to solve a problem by solving a simpler instance of the same problem.
Example: reading a file of numbers and printing them
To read and print a file of numbers:
Read the first number and print it
Read and print the rest of the numbers
If there were n numbers originally to be read, there are now n-1 — a simple version of our task
Eventually we run out of numbers (i.e., we reach the end of the file), at which point there's nothing left to do
i.e., reading and printing numbers from a empty file involves doing nothing
Recursive Method
A method that calls itself
readAndPrintNumbers(file) {
if (the file is empty)
nothing to do
else
read the first number from the file and print it
readAndPrintNumbers(file) // positioned at next number (or end of file)
Recursive Methods Can Be Written in Java
static void readANdPrintNumbers(Scanner scanner) {
if (!scanner.hasNextInt())
return;
else {
int num = scanner.nextInt();
System.out.println(num);readAndPrintdNumbers(scanner);
}
}
The run-time call stack of call frames (or activation records) makes this possible
Fresh copies of local variables are created with each (recursive) call
In the above example, the parameter contains a different value each time the function is entered.
In this case, parameter is a Scanner object positioned at a particular location in the file (the next number
to be read in our case). Once the number is read, the recursive call is made, and the Scanner object is sent to the
next recursive call, but now it's file position has been changed to the next integer to be read in.
A stack is a data structure where the last thing added is the first removed; this corresponds to the way in which methods are called:
the last method called is the first one to return.
It's also one of the primary data structures covered in 3130.
What about this one?:
static void readANdPrintNumbers(Scanner scanner) {
if (!scanner.hasNextInt())
return;
else {
int num = scanner.nextInt();
readAndPrintdNumbers(scanner);System.out.println(num);
}
}
Anatomy of a Recursive Function
One or more escape clauses
Prevents recursive runaway (infinite recursion)
One or more recursive calls
To Write Recursively You Have to Think Recursively!
Try to think of an approach whose solution involves a simpler version of the approach.
Recursive data leads to recursive algorithms
Recursive Data
Arrays
An array is a sequence of elements; a sequence of elements is either:
empty, i.e., an array of 0 elements (the escape clause)
an element followed by a sequence of elements
Strings
An string is a sequence of characters; a sequence of characters is either:
empty, i.e., the empty string (the escape clause)
a character followed by a sequence of characters
Non-negative Integers
A non-negative integer is either:
0 (the escape clause)
one more than the previous integer
Recursive Algorithms
Arrays
Recursion is usually controlled by the size of the array; i.e., reducing the number of elements in the portion
of the array we still concerned with
Don't want to be copying to another array, so we use wrapper methods
The Need for Subarrays
Often — and especially in the context of recursion involving arrays — the algorithm calls for working with subarrays. This is
because we often process/visit one element at a time (typically the first or last) and after that, we process the rest of the array (this
is especially true with recursion). HOWEVER, while the rest of the array is also a sequence of elements, it is NOT an array in the
sense of a Java array. As an example, suppose we had a method f(int [] arr) that accepted an array. The following is clearly legal:
int [] arr = new int [] {10, 20, 30, 40};
f(arr);
i.e., we can clearly pass an array to a method expecting an array (of the same element type of course). But we CAN'T pass to f the
sequence consisting of all the elements but the first (i.e., the rest of the array.
One solution is to copy those elements to a new array (whose size is one less than that of the original array; after which we can then pass
this new array to f:
int [] temp = new int [arr.length-1];
for (int i = 0; i < temp.length; i++)
temp[i] = arr[i+1]; // copying all the elements except arr[0] to temp
f(temp);
The problem with this approach is the overhead of creating a new array and copying the elements from the original array. And this is
exacerbated in a recursive context by the fact that this is done over and over again at each level of the recursion.
To solve the above issue, we use the (original) array, together with the two indexes denoting the boundaries of the
rest of the array, i.e., a subarray, as described in the overview. These indexes are often named low/high,
first/last, etc.
The bounds usually represent the first and last elements of the subarray
Sometimes the upper bound might represent the index AFTER the last bound of the array (it makes the logic of certain algorithms
simpler or cleaner).
Sometimes a length is used instead of an upper bound; i.e., the subsrray is represented by the index of the first element and
the number of elements.
The entire array itself can be represented as a subarray with bounds 0 and arr.length-1.
We can then have our method accept a subarray (rather than an array) by having three parameters corresponding to the array and the two bounds.
The Use of a Wrapper Method
While there are algorithms that explicitly present the caller with a subarray for an argument (for example a printSubarray method),
the use of the subarray is often introduced in order to avoid the performance overhead described below. In such a case, the perspective
of the original caller is the invocation of a method on the entire array (and the subarray gets introduced as part of the
recursive call on the rest of the array). As such, we wish to present the caller with a natural (i.e., intuitive) method
signature, one involving simply the array and no bounds. This is accomplished by introducing a wrapper method that presents to the caller
THEIR desired interface, and then provides the proper arguments to the subarray method:
f(int [] array) {f(array, 0, array.length-1);}
f(int [] array, int lo, int hi) {…}
Printing an array
No elements? …; done (escape clause is empty array; no elements)
Print first element
Print rest of the array
Notes:
To avoid copying, use wrapper method which add the bounds of the array
Escape clause (no elements) becomes lower and upper bounds crossing
What about adding {}'s?
Printing an array in reverse
No elements? … done
Print last element
Print rest of the array (i.e. array consisting of 1st through next-to-last element)
or
No elementd? …; done (escape clause is empty array; no elements)
Print rest of the array
Print first element
Searching an array for a value (contains)
No elements? … not there — return false
Compare against first element
equal? … done — return true
Return result of searching rest of the array
Finding a value in an array (find)
Need to return the position
No elements? … not there — return -1
Compare against first element
equal? … done — return index
Return result of searching rest of the array;
Summing an array
Max of an array
Checking for 'Palindrome' Array
Note the two escape clause situations
Also, the simpler case is two elements smaller in this example (i.e., the array shrinks front and back)
Binary search
Assumes the array is sorted
Compare with middle element
equal? … done — return true
less? … return result of searching left half
greater? … return result of searching right half
Strings
Recursion is usually controlled by the empty string; i.e., reducing the number of characters left to look at
Printing a string
Checking for palindromes
Numerics (Integers)
Escape clause is often 0, and the recursion consists of working our way down to 0
Factorial
Fibonacci numbers
0, 1, 1, 2, 3, 5, 8, 13, 21?
f(0) = 0, f(1) = 1, f(2) = 1, f(3) = 2 ?
f(n) is nth number in the sequence
Escape clause: 0 and 1
Recursion requires previous two elements in the sequence so there must be two escape clauses
Addition using increment
Similarly for multiplication (using addition) and exponentiation (using multiplication)
Test for Evenness
x is even if x ? 2 is even
Escape clause: 0 (or 2)
Equality
Towers of Hanoi
3 towers, we'll call them A, B, C
Move from A (source) to C (dest) using B (as a temporary)
n rings-- smallest is 1, largest is n
only move one ring at a time
larger rings may not be moved onto smaller rings
To solve:
Move n-1 rings from source to temp
Move ring n from source to dest
Move n-1 rings from temp to dest
Escape clause: single ring, simply move from source to dest
Recursion Within the Context of a Class
Suppose we have a Container class:
class Container {
…
public int find(int value) {…}
public boolean contains(int value) {return find(value) != -1;}
…
private int size = 0;
private int [] arr = new int[CAPACITY];
}
Arrays are recursive, containers (as we defined them) aren't
Remember, an array is either empty or an element followed ny an array
A container, on the other hand, is an object containing an array and a size; there is no recursive structure.
The recursive structure is in one of the instance variables: the array
How can we take advantage of recursion to code the find methods
The solution is to use delegation
Have a public function that adds the recursive parameter (the aray and its bounds) and calls a private recursive counterpart:
class Container {
…
public int find(int value) {return find(arr, 0, size-1, value);}}
public boolean contains(int value) {return find(value) != -1;}
…
private int find(int [] arr, int lo, int hi, int value) {
if (lo > hi) return -1;
if (arr[lo] == value) return lo;
return find(arr, lo+1, hi, value);
}
…
private int size = 0;
private int [] arr = new int[CAPACITY];
}