Transcript ppt
Recursion
•
•
•
•
•
•
Recursive Thinking
Recursive Programming
Recursion versus Iteration
Direct versus Indirect Recursion
More on Project 2
Reading L&C 10.1 – 10.4
1
Recursive Thinking
• Many common problems can be stated in
terms of a “base case” and an “inferred
sequence of steps” to develop all examples
of the problem statement from the base case
• Let’s look at one possible definition of a
comma separated values (.csv) list:
– A list can contain one item (the base case)
– A list can contain one item, a comma, and a list
(the inferred sequence of steps)
2
Recursive Thinking
• The above definition of a list is recursive
because the second portion of the definition
depends on there already being a definition
for a list
• The second portion sounds like a circular
definition that is not useful, but it is useful
as long as there is a defined base case
• The base case gives us a mechanism for
ending the circular action of the second
portion of the definition
3
Recursive Thinking
• Using the recursive definition of a list:
A list is a: number
A list is a: number comma list
• Leads us to conclude 24, 88, 40, 37 is a list
number comma list
24
,
88, 40, 37
number comma list
88
,
40, 37
number comma list
40
,
37
number
37
4
Recursive Thinking
• Note that we keep applying the recursive
second portion of the definition until we
reach a situation that meets the first portion
of the definition (the base case)
• Then we apply the base case definition
• What would have happened if we did not
have a base case defined?
5
Infinite Recursion
• If there is no base case, use of a recursive
definition becomes infinitely long and any
program based on that recursive definition
will never terminate and produce a result
• This is similar to having an inappropriate
or no condition statement to end a “for”,
“while”, or “do … while” loop
6
Recursion in Math
• One of the most obvious math definitions
that can be stated in a recursive manner is
the definition of integer factorial
• The factorial of a positive integer N (N!) is
defined as the product of all integers from
1 to the integer N (inclusive)
• That definition can be restated recursively
1! = 1
N! = N * (N – 1)!
(the base case)
(the recursion)
7
Recursion in Math
• Using that recursive definition to get 5!
5! = 5 * (5-1)!
5! = 5 * 4 * (4-1)!
5! = 5 * 4 * 3
5! = 5 * 4 * 3
5! = 5 * 4 * 3
5! = 5 * 4 * 3
5! = 120
* (3-1)!
* 2 * (2-1)!
* 2 * 1! (the base case)
* 2 * 1
8
Recursive Programming
• Recursive programming is an alternative
way to program loops without using “for”,
“while”, or “do … while” statements
• A Java method can call itself
• A method that calls itself must choose to
continue using either the recursive definition
or the base case definition
• The sequence of recursive calls must make
progress toward meeting the definition of
the base case
9
Recursion versus Iteration
• We can calculate 5! using a loop
int fiveFactorial = 1;
for (int i = 1; i <= 5; i++)
fiveFactorial *= i;
• Or we can calculate 5! using recursion
int fiveFactorial = factorial(5);
. . .
private int factorial(n)
{
return n == 1? 1 : n * factorial(n – 1);
}
10
Recursion versus Iteration
main
return 120
factorial
return 24
factorial(5)
factorial
return 6
factorial(4)
factorial
return 2
factorial(3)
factorial
return 1
factorial(2)
factorial
factorial(1)
11
Recursion versus Iteration
• Note that in the “for” loop calculation, there is
only one variable containing the factorial
value in the process of being calculated
• In the recursive calculation, a new variable n
is created on the system stack each time the
method factorial calls itself
• As factorial calls itself proceeding toward the
base case, it pushes the current value of n-1
• As factorial returns after the base case, the
system pops the now irrelevant value of n-1
12
Recursion versus Iteration
• Note that in the “for” loop calculation, there
is only one addition (i++) and a comparison
(i<=5) needed to complete each loop
• In the recursive calculation, there is a
comparison (n==1) and a subtraction (n 1), but there is also a method call/return
needed to complete each loop
• Typically, a recursive solution uses both
more memory and more processing time
13
than an iterative solution
Calling main( ) Recursively
• Any Java method can call itself
• Even main() can call itself as long as
there is a base case to end the recursion
• You are restricted to using a String []
as the parameter list for main()
• The JVM requires the main method of a
class to have that specific parameter list
14
Calling main( ) Recursively
public class RecursiveMain
{
public static void main(String[] args)
{
if (args.length > 1) {
String [] newargs = new String[args.length - 1];
for (int i = 0; i < newargs.length; i++)
newargs[i] = args[i + 1];
main(newargs);
// main calls itself with a new args array
}
System.out.println(args[0]);
return;
}
}
java RecursiveMain computer science is fun
fun
is
science
computer
15
Recursion (Continued)
• Tail Recursion versus Iterative Looping
• Using Recursion
– Printing numbers in any base
– Computing Greatest Common Denominator
– Towers of Hanoi
• Analyzing Recursive Algorithms
• Misusing Recursion
– Computing Fibonacci numbers
• The Four Fundamental Rules of Recursion
• Reading L&C 10.1 – 10.4
16
Tail Recursion
• If the last action performed by a recursive
method is a recursive call, the method is
said to have tail recursion
• It is easy to transform a method with tail
recursion to an iterative method (a loop)
• Some compilers actually detect a method
that has tail recursion and generate code
for an iteration to improve the performance
17
Tail Recursion
• Here is a method with tail recursion:
public void countdown(int integer)
{
if (integer >= 1) {
System.out.println(integer);
countdown(integer – 1);
}
}
• Here is the equivalent iterative method:
public void countdown(int integer)
{
while(integer >=1) {
System.out.println(integer);
integer--;
}
}
18
Tail Recursion
• As you can see, conversion from tail
recursion to a loop is straightforward
• Change the if statement that detects
the base case to a while statement with
the same condition
• Change recursive call with a modified
parameter value to a statement that
just modifies the parameter value
• Leave the rest of the body that same
19
Printing an Integer in any Base
• Hard to produce digits in left to right order
– Must generate the rightmost digit first
– Must print the leftmost digit first
• Basis for recursion
Last digit
= n % base
Rest of digits = n / base
(base case)
(recursion)
20
Printing an Integer in any Base
• Table of digits for bases up to 16
private final String DIGIT_TABLE =
"0123456789abcdef";
• Recursive method
private void printInt(int n, int base)
{
if (n >= base)
printInt( n/base, base );
System.out.print( DIGIT_TABLE.charAt(n % base));
}
21
Computing GCD of A and B
• Basis for recursion
GCD (a, 0) = a
GCD (a, b) = GCD (b, a mod b)
(base case)
(recursion)
• Recursive method
private static int gcd(int a, int b)
{
if (b == 0)
return a;
else
return gcd(b, a % b);
}
22
Towers of Hanoi
• The Towers of Hanoi puzzle was invented
in the 1880’s by a French mathematician,
Edouard Lucas
• There are three upright pegs and a set of
disks with holes to fit over the pegs
• Each disk has a different diameter and a
disk can only be put on top of a larger disk
• Must move a pile of N disks from a starting
tower to an ending tower one at a time
23
Towers of Hanoi
• While solving the puzzle the rules are:
– We can only move one disk at a time
– We cannot place a larger disk on top of a
smaller disk
– All disks must be on some peg except for the
one in transit
• See example for three disks
24
Towers of Hanoi
Original Configuration
After First Move
After Fourth Move
After Fifth Move
After Second Move
After Sixth Move
After Third Move
After Last Move
25
Towers of Hanoi
• The recursive solution is based on:
Move one disk from start to end (base case)
Move a tower of N-1 disks out of the way (recursion)
private void moveTower
(int numDisks, int start, int end, int temp)
{
if (numDisks == 1)
moveOneDisk (start, end);
else {
moveTower(numDisks-1, start, temp, end);
moveOneDisk(start, end);
moveTower(numDisks-1, temp, end, start);
}
}
26
Misusing Recursion
• Some algorithms are stated in a recursive
manner, but they are not good candidates
for implementation as a recursive program
• Calculation of the sequence of Fibonacci
numbers Fn (which have many interesting
mathematical properties) can be stated as:
F0 = 0
F1 = 1
Fn = F(n-1) + F (n-2)
(one base case)
(another base case)
(the recursion)
27
Misusing Recursion
• We can program this calculation as follows:
public int
{
if (n <=
return
else
return
}
fib(int n)
1)
n;
fib(n – 1) + fib(n – 2);
• Why is this not a good idea?
28
Misusing Recursion
• If we trace the execution of this recursive
solution, we find that we are repeating the
calculation for many instances of the series
F5
F4
F3
F2
F1
F3
F2
F1
F1
F2
F0
F1
F1
F0
F0
29
Misusing Recursion
• Note that in calculating F5, our code is
calculating F3 twice, F2 three times, F1
five times, and F0 three times
• These duplicate calculations get worse as
the number N increases
• The order of the increase of time required
with the value of N is exponential
• For N = 40, the total number of recursive
calls is more than 300,000,000
30
Misusing Recursion
• This loop solution (for N >= 2) is O(n) :
public int fibonacci(int n)
{
int fNminus2 = 0, fNminus1 = 1;
int fN = 0;
for (int i = 2; i <= n; i++)
{
fN = fNminus1 + fNminus2;
fNminus2 = fNminus1;
fNminus1 = fN;
}
return fN;
}
31
Four Fundamental Rules of Recursion
• Base Case: Always have at least one case
that can be solved without recursion
• Make Progress: Any recursive call must
make progress toward a base case
• You gotta believe: Always assume that the
recursive call works
• Compound Interest: Never duplicate work
by solving the same instance of a problem
in separate recursive calls
Ref: Mark Allen Weiss, Data Structures & Problem Solving using Java, Chapter
7
32
Introduction to Project 4
• Evaluate LISP Expressions (Prefix Notation)
• Name LISP comes from “LISt Processor”
• Name is sometimes ridiculed as:
“Lots of Irritating and Silly Parentheses”
• In LISP, all loops are done using recursion!
• Infix Notation
Prefix Notation
2+2
2 * ( 3 + 4)
(1+2)*(3-4)
(+2 2 )
(* 2(+3 4))
( * ( + 1 2 ) (- 3 4 ) )
Introduction to Project 4
• The Evaluator class for project 4 needs a
recursive evaluate() method
• When the evaluate method calls itself:
– the current context of all local variable is left on
the system stack
– a new context for all local variables is created on
the top of the system stack
• The return from evaluate() dissolves the
current context and pops the previous level
context off the system stack
Calling Sequence / System Stack
(
main
return 4.0
+
1
3
)
evaluate(null)
evaluate(new q)
evaluate
evaluate evaluate(q)
return 4.0
return 4.0
evaluate
return 4.0
System Stack
[null]
[‘+’]
[‘+’, 1]
evaluate(q)
[‘+’, 1, 3]
[empty]
[null]
[null]
evaluate
return 4.0
[null]
[null]
evaluate(q)
[empty]
[empty]
evaluate
[empty]
Return on ‘)’
(Base Case)
Calling Sequence / System Stack
(
*
evaluate
return
4.0 * ?
(
+
1
3
)
Next Operand
OR ‘(‘ or ‘)’
evaluate(q)
evaluate(new q)
evaluate evaluate(q)
evaluate
return ?
evaluate evaluate(q)
return 4.0
return 4.0
evaluate
return 4.0
[‘*’]
[empty]
[‘*’]
[empty]
[‘*’]
[empty]
[‘*’]
[empty]
[‘*’, 4.0]
[empty]
[‘*’, 4.0]
evaluate
evaluate(q)
return 4.0
System Stack
[empty]
evaluate(q)
[‘+’]
[‘+’, 1]
[‘+’, 1, 3]
[empty]
[empty]
[empty]
[‘*’, 4.0, ?]
evaluate
[empty]
Return on ‘)’
(Base Case)