Transcript Lecture 20
CS 2010
Lecture 20
Recursion
• Recursion is the technique of breaking down a problem
into smaller instances of the same problem.
• In programming, a recursive algorithm is one that directly
or indirectly invokes itself
– direct recursion: methodA calls methodA
– indirect recursion: methodA calls methodB and methodB calls
methodA
Recursion
• Recursion is used in many common algorithms. It is also
extremely important in functional programming, a longestablished paradigm (way of thinking about and
practicing programming) which is increasingly important
today.
• As you will learn in CS312, recursion is equivalent to
iteration. In other words, any problem you can solve
with either of these two techniques, you can also solve
with the other. However:
– a) many problems can be solved with simpler algorithms using
recursion, but
– b) most programmers find iteration easier to think about
4
King of Hearts’ Algorithm
"Where shall I begin, please your majesty?" he asked.
"Begin at the beginning," the King said gravely, "and go on till you come to the end; then
stop.
5
Recursive Algorithm
doSomething()
1. If you are finished, stop.
2. Otherwise
1.
2.
Solve part of the problem
Run this algorithm
Recursion
6
For example, we can define the operation
goHome() this way:
– Look around to see whether you are at home.
If you are, stop.
– Else
• Take one step toward home.
• goHome()
Recursion
• Avoid infinite regression (and stack overflows) by
defining a condition that indicates that the recursion
has extended as far as it can, and that therefore causes
termination of the recursion. The instance of the
problem in which the condition is true is called the base
case.
8
Iterative (not Recursive) Factorial
function factorial is:
input: integer n such that n >= 0
output: [n × (n-1) × (n-2) × … × 1]
Iterative algorithm
1. create new variable called running_total with a value of 1
2. begin loop
1.
2.
3.
4.
if n is 0, exit loop
set running_total to (running_total × n)
decrement n
repeat loop
3. return running_total
end
9
Recursive Factorial
Calculating the factorial of (a positive integer) n
can be reduced to multiplying n times the factorial
of n-1.
function factorial:
input: integer n such that n >= 0
output: [n × (n-1) × (n-2) × … × 1]
1. if n == 0, return 1
2. otherwise, return [ n × factorial(n-1) ]
• Note that this recursive solution has a simpler
10
Recursive Factorial
public class Demo {
public static int factorialRecursive(int intIn){
if(intIn ==0) return 1;
return intIn * factorialRecursive(intIn -1);
}
public static void main(String[] args) {
int num = Integer.parseInt(JOptionPane.showInputDialog(null, "Please "
+ "enter the number whose factorial you would like to compute"));
JOptionPane.showMessageDialog(null, "The factorial of " + num + " is " +
factorialRecursive(num));
}
}
Stack Overflow
• Each method call requires additional space on
the call stack to track the data values for the
current instance of the method
• If the stack exceeds the memory available to it,
a stack overflow occurs
public class StackOverflowDemo {
int x;
public static void main(String[] args){
int x = 1;
StackOverflowDemo s = new StackOverflowDemo();
s.recurseToOverflow(x);
}
public void recurseToOverflow(int x){
System.out.println("instance " + x);
x = x + 1;
recurseToOverflow(x);
}
}
Stack Overflow
• If you get a stack overflow error while doing
your work in this class, there is almost certainly
a bug in your code that involves an incorrect
termination condition. Think until you figure it
out and fix it.
• However, note that we run our programs inside
Eclipse, which decides how much space to
allocate to the call stack.
Euclid's Algorithm
• Find the GCD of two positive integers this way:
Input Two positive integers, a and b.
Output The greatest common divisor, g, of a and b.
If a<b, exchange a and b.
Divide a by b and get the remainder, r. If r=0, report b as the
GCD of a and b.
Replace a by b and replace b by r. Return to the previous step.
http://www.math.rutgers.edu/~greenfie/gs2004/euclid.
html
Euclid's Algorithm
// by Robert Sedgewick and Levin Wayne : http://introcs.cs.princeton.edu/java/23recursion/Euclid.java.html
// assumes p > q for simplicity
public class Euclid {
public static int gcd(int p, int q) {// recursive implementation
if (q == 0) return p;
else return gcd(q, p % q);
}
public static int gcd2(int p, int q) {// non-recursive implementation
while (q != 0) {
int temp = q;
q = p % q;
p = temp;
}
return p;
}
public static void main(String[] args) {
int p = Integer.parseInt(JOptionPane.showInputDialog(null, "Please enter the first integer"));
int q = Integer.parseInt(JOptionPane.showInputDialog(null, "Please enter the second integer"));
int d = gcd(p, q);
int d2 = gcd2(p, q);
JOptionPane.showMessageDialog(null, "gcd(" + p + ", " + q + ") = " + d);
JOptionPane.showMessageDialog(null, "gcd(" + p + ", " + q + ") = " + d2);
}
}
Multiple Recursion
A recursive problem may have more than one base
case and/or more than one recursive call.
The definition of the Fibonacci numbers is recursive,
and the nth Fibonacci number can be found using a
recursive function.
Fibonacci numbers: f(n) =
Multiple Recursion
public class FibonacciCalculator {
public static void main(String[] args){
FibonacciCalculator f = new FibonacciCalculator();
for(int counter = 0; counter < 10; counter++){
long fib = f.fibonacci(counter);
System.out.println("Fibonacci number No. " + counter + " = " + fib);
}
}
public static long fibonacci(long n) {
// https://www.inf.unibz.it/~calvanese/teaching/04-05-ip/lecture-notes/uni10/node23.html
if (n < 0) return -1; // F(n) is not defined when n is negative
if (n == 0)
return 0;
else if (n == 1)
return 1;
else
return fibonacci(n-1) + fibonacci(n-2);
}
}
17
Recursion
• Recursion can be used to process a list (or array of)
values.
• The termination condition occurs when there are no
values left to process; another way to put this is that the
base case is the one in which the number of values left to
process is one.
Recursion
package demos;
import java.util.ArrayList;
import java.util.List;
//https:www.stackoverflow.com/questions/126756/examples-of-recursive-functions
public class RecursionExample {
public static void main(String[] args) {
String[] sleeplessArray = { "ant", "frog", "goose", "weasel", "child" };
List<String> sleeplessList = new ArrayList<String>();
for (String s : sleeplessArray)
sleeplessList.add(s);
RecursionExample r = new RecursionExample(sleeplessList);
}
public RecursionExample(List<String> animals){
System.out.print("There was a ");
tellStory(animals);
}
private void tellStory(List<String> sleeplessList) {
int last=sleeplessList.size() -1;
String animal = sleeplessList.get(last);
if(sleeplessList.size() == 1) System.out.println("little " + animal +" who went to sleep");
else {
System.out.println("little " + animal +
" who couldn't go to sleep, so his mother read him a story about a ");
sleeplessList.remove(last);
tellStory(sleeplessList);
}
System.out.println("so the little " + animal + " went to sleep" );
}
}
18
19
Recursion
• Recursion can also be used to process a list of
values and produce a new list of processed values.
– This is at the heart of functional programming, as you
will learn in CS332F. In the FP paradigm, neither the
old list nor the objects in the list change. We produce
a new list containing new values.
– Functional languages like LISP, Haskell, and Scala are
designed to make this easy.
– It is less straightforward in OOP, but the new version of
Java provides more support for this.
package demos;
Recursion
import java.util.ArrayList;
import java.util.List;
public class ListEx {
public static void main(String[] args) {
List<Integer> origList = new ArrayList<Integer>();
for (int counter = 1; counter <= 10; counter++) origList.add(counter);
ListEx r = new ListEx();
List<Integer> newList = r.squareList(origList, null);
for(Integer i: newList) System.out.print(i + " ");
}
private List<Integer> squareList(List<Integer> oldList, List<Integer> newList) {
int lastIndex = oldList.size() -1;
int base = oldList.get(lastIndex);
oldList.remove(lastIndex);
if(oldList.size() == 0) {
newList = new ArrayList<Integer>();
}
else newList = squareList(oldList, newList);
newList.add(base * base);
return newList;
}
}
20
21
Recursion
• Here is the last example in Haskell:
Function definition:
squarelist :: Num a => [a] -> [a]
squarelist [] = []
squarelist (x:xs) = (x*x): squarelist xs
Function call:
squarelist[1..9]
22
Backtracking
• Some algorithms involve following one path as far as it
can go, then backing up to the last point at which a
different path could have been chosen and then
following that path
• Depth-First Search (DFS) is a classic example. Start at the
root of a tree or graph and explore as far as possible
along each branch before backtracking.
23
Depth-First Search
Source of picture: https://upload.wikimedia.org/wikipedia/commons/thumb/1/1f/Depth-first-tree.svg/450px-Depth-first-tree.svg.png
File Listing
• Consider the problem of listing all the files in a
directory, including those in all its
subdirectories, etc. until every path has been
exhausted.
• The natural way to approach this problem is to
do it recursively using Depth First Search:
– List files in the current directory
– Run this algorithm on each subdirectory,
continuing as far as we can go
File Listing
public class FileLister {
private static StringBuilder indentation = new StringBuilder();
public static void main(String args[]) {
String start = JOptionPane.showInputDialog(null,
"Please enter the starting directory");
getDirectoryContent(start);
}
private static void getDirectoryContent(String filePath) {
File currentDirOrFile = new File(filePath);
if (!currentDirOrFile.exists()) {
return;
} else if (currentDirOrFile.isFile()) {
System.out.println(indentation + currentDirOrFile.getName());
return;
} else {
System.out.println("\n" + indentation + "|_"
+ currentDirOrFile.getName());
indentation.append(" ");
String[] s = currentDirOrFile.list();
if (s != null) {
for (String currentFileOrDirName : currentDirOrFile.list()) {
getDirectoryContent(currentDirOrFile + "\\"
+ currentFileOrDirName);
}
}
if (indentation.length() - 3 > 3) {
indentation.delete(indentation.length() - 3,
indentation.length());
}
}
}
}
26
Towers Of Hanoi
• The Towers of Hanoi problem supposes that, at the
beginning of time, a group of monks in Hanoi were tasked
with moving a set of 64 disks of different sizes between
three pegs, according to these rules:
– No disk may ever be placed above a smaller disk
– The starting position has all the disks, in descending order of
size, stacked on the first peg
– The ending position has all the disks in the same order, stacked
on the third peg.
• An optimal solution for n disks requires 2n-1 moves
264 = 18,446,744,073,709,551,616
• There is a nice animation of a 4-disk version of the problem
at
https://en.wikipedia.org/wiki/File:Tower_of_Hanoi_4.gif
27
Towers Of Hanoi
• Recursive solution:
– For any n > 1, the problem can be solved optimally in this way:
• Solve the problem for n -1 disks, starting at the start post
and ending at the "extra" post.
• The remaining disk will be the largest one. Move it to the
finish post.
• Then solve the problem for the n-1 disks, moving from the
"extra" post to the finish post
– The above procedure is applied recursively until n = 0
– Before you try to understand the code from the book, try out
the puzzle online at
http://www.softschools.com/games/logic_games/to
wer_of_hanoi/
28
Towers Of Hanoi
The key to a Towers Of Hanoi solution is a recursive
method like this:
public void move(int disks, int from, int to) {
if (disks > 0) {
int other = 3 - from - to;
move(disks - 1, from, other);
towers[to].add(towers[from].remove());
move(disks - 1, other, to);
}
}
29
Tail Recursion
• A tail-recursive method is one in which there is one
recursive call at the very end of the method, e.g. the
examples above of recursive factorial calculations and
Euclid's algorithm.
• In many programming languages, compilers can convert
tail recursion into iterative code, which is more efficient
since it does not create new instances on the call stack
and since, in many cases, it can avoid redundant
calculations. This is called Tail-Call Optimization, or TCO.
• Java compilers may have this feature in the future, but
not at the current time.
30
Recursive Jokes
To understand recursion, read this sentence.
A truly greedy man is one who writes his will and names
himself as heir -- from Philogelos, the oldest known book of jokes, compiled
about 400 AD.
Function recurse()
1. If you are done with your program, stop
2. Otherwise,
A. Try to find the problem
B. Utter an obscenity
3. recurse()