Title: CS 1322
1CS 1322
- Introduction to
- Object Oriented Programming
- Lecture 4
2Sample Quiz Problem
Given an array of ints, return the index of the
largest element. Use the code below.
NOTE THIS!
public int getLargestIndex(int myArray)
int largest myArray0 int i for (i 1
i lt myArray.length i)
if (largest lt myArrayi)
largest myArrayi return
largest // getLargestIndex
Test taking is about READING THE DIRECTIONS
3Sample Quiz Problem
Given an array of ints, return the index of the
largest element.
public int getLargestIndex(int myArray)
int largest myArray0 int indexOfLargest
0 int i for (i 1 i lt myArray.length i)
if (largest lt
myArrayi) largest
myArrayi indexOfLargest
i return indexOfLargest //
getLargestIndex
Now it looks good!
4Sample Quiz Problem
Given an array of ints, return the index of the
largest element.
public int getLargestIndex(int myArray)
int indexOfLargest 0 for (i 1 i lt
myArray.length i) if
(myArrayindexOfLargest lt myArrayi)
indexOfLargest i return
indexOfLargest // getLargestIndex
Another good solution.
5Recursion
- Remember Recursion?
- A programming technique whereby a method calls
itself. - Still relies on the typical three things
- Terminating case
- Reduction step
- Recursive call
6Recursion
Lets say you place two rabbits in a hutch.
Whats going to happen?
7Two Months Later...
original rabbits
new rabbits
If it takes two months for rabbits to reach
maturity, in two months youll have one
productive pair and one (brand new)
non-productive pair. (This assumes all rabbits
live up to their reputation.)
8The rabbits keep at it.
1 month old
0 months old
The next month, you get another pair . . .
9Suppose
What if The rabbits always had two offspring,
always male and female Rabbits
always reached maturity in two months No
rabbit dies. How many pairs of rabbits do you
have in a year?
1
2
3
10Hare Raising Story
Start
11Pairs of Rabbits
See a pattern yet?
12Lets Take Another Example
Instead of rabbits, lets use geometry. Draw a
square of size 1. Rotating 90 degrees, add to it
a square of size 1. Rotating 90 degrees again,
add a square of size 2. Again, rotate and add a
square of size 3, and so on. Keep this up for
the sequence we noted in the table
1, 1, 2, 3, 5, 8, 13, 21, . . . , What do you
see?
131
141
152
163
175
188
1913
2021
21(No Transcript)
22Does this look familiar?
23Its not just about rabbits.
24The Truth is Out There
25See the pattern?
Its the Fibonacci Sequence.
We used brute force to find the progression 1,
1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ... , It
turns out this pattern is repeated in many
places sea shells, sun flowers, pine cones, the
stock market, bee hives, etc.
26Writing the Formula
Given 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89,
144, ... , Can we write this as a formula for
any number, n?
But lets be honest. We might not be as smart as
him. But thats OK. We can code.
27What If You Cant Find the Formula?
Which one would your rather code and debug? For
some problems, there might not exist a formula,
and recursion is your only option.
Suppose you didnt know
You could take Math 3012 or you could instead
manage with
Fib(n) Fib(n-1) Fib(n-2), Fib(0) 0 Fib(1)
1
(The value at any given place is the sum of the
two prior values.)
28Recursive Fibonacci
We have our general rule
Fib(n) Fib(n-1) Fib(n-2), Fib(0) 0 Fib(1)
1
We can say a few things about it Its defined
recursively. It has a terminal condition
(AHA!) It can be determined and calculated
(addition).
1
2
3
29Coding Fibonacci
public class FibTest public static int fib
(int num) // FibTest
What do we know to start with? We know that we
need a method that return the Fibonacci value for
a number at a given position. This suggests a
method that returns and gets an int
30Coding Fibonacci
public class FibTest public static int fib
(int num) if (num 0) return
0 // FibTest
Whats the FIRST thing we do with a recursive
method? We plan on how it will terminate! We
know one special case for the Fibonacci
sequence F(0) 0
31Coding Fibonacci
public class FibTest public static int fib
(int num) if (num 0) return 0 else
if (num 1) return 1 // FibTest
We also know a second special case that could
terminate our recursion F(1) 1.
32Coding Fibonacci
public class FibTest public static int fib
(int num) if (num 0) return 0 else
if (num 1) return 1 else return
fib(num-1) fib(num-2) // FibTest
The last part of our formula is merely F(n)
F(n-1) F(n-2)
33Coding Fibonacci
public class FibTest public static int fib
(int num) if (num 0) return 0 else
if (num 1) return 1 else return
fib(num-1) fib(num-2) // FibTest
Is this safe? What if someone passed in 0 to our
method? What happens? What if they passed in 1?
34Coding Fibonacci
public class FibTest public static int fib
(int num) if (num 0) return 0 else
if (num 1) return 1 else return
fib(num-1) fib(num-2) public static
void main(String args) for (int i0 i lt 10
i) System.out.println (fib(i)) //
FibTest
It is our responsibility to write a main to
test this method.
35Coding Fibonacci
public class FibTest public static int fib
(int num) if (num 0) return 0 else
if (num 1) return 1 else return
fib(num-1) fib(num-2) public static
void main(String args) for (int i0 i lt 10
i) System.out.println (fib(i)) //
FibTest
Are we done? What about negative numbers? More
work is needed
36Recursion Review
So far, weve seen that for recursive
behavior 1) Recursion exists in all of
nature. 2) Its easier than memorizing a
formula. Not every problem has a formula, but
every problem can be expressed as a series of
small, repeated steps. 3) Each step in a
recursive process should be small, calculable,
etc. 4) You absolutely need a terminating
condition.
37Honesty in Computer Science
1. To keep it simple, the typical examples given
for recursion are factorial and the Fibonacci
numbers. 2. Many formulas or definitions are
already recursive take advantage of that! 3.
But, why all the fuss about recursion? 4.
Recursion is absolutely great when used to write
algorithms for recursively defined data
structures like binary trees. Much easier than
iteration! 5. Recursion is excellent for any
divide conquer algorithm like...
38One More Example
Suppose we wanted to create a method that
solve Pow(x, y) xy
In other words, the method returned the value of
one number raised to the power of another
public static double pow (double
value, int exponent)
39Planning the Method
Unlike the Fibonacci example, our mathematical
formula is not the complete answer. Pow(x, y)
xy Were missing some termination
conditions. But we know
x1 x x0 1
So we could use these as our terminating
condition.
40Attempt 1
public static double pow(double value, int
exponent) if (exponent 0) return
1D
Always, always start with some sort of
terminating condition. We know any
number raised to the zero power is one.
41Attempt 1
public static double pow(double value, int
exponent) if (exponent 0) return
1D else if (exponent 1) return
value
... and any number raised to the power of one is
itself.
42Attempt 1
public static double pow(double value, int
exponent) if (exponent 0) return
1D else if (exponent 1) return
value else return value pow (value,
exponent--)
For all other values, we can return the number
times the recursive call, using our exponent as a
counter. Thus, we calculate 26 222222
43Attempt 1
public static double pow(double value, int
exponent) if (exponent 0) return
1D else if (exponent 1) return
value else return value pow (value,
exponent--)
When we run this, however, bad things happen.
The program crashes, having caused a stack
overflow. How can we solve this?
44Attempt 1
public static double pow(double value, int
exponent) if (DEBUG) System.out.println
(Entering with value , and exp
exponent) if (exponent 0) return
1D else if (exponent 1) return
value else return value pow
(value, exponent--)
We could post to the newsgroup and wait it out,
or pester our TA. Or, we can TAKE CHARGE OF
THE SITUATION. Gather information. Put in
debug statements.
45Attempt 1
public static double pow(double value, int
exponent) if (bDEBUG) System.out.println
(Entering with value , and exp
exponent) if (exponent 0)
return 1D else if (exponent 1)
return value else return value
pow (value, exponent--)
DOH!
Our debug statement tells us that the exponent
is never being decreased. Evidently, the
exponent-- line is not being evaluated before
the recursive call takes place. As it turns out,
the post-decrement operator -- is the problem.
46Attempt 1
public static double pow(double value, int
exponent) if (exponent 0) return
1D else if (exponent 1) return
value else exponent exponent -
1 return value pow (value, exponent)
We decide that typing one extra line takes less
time than debugging such a subtle error. Things
are working now.
47Do I Have to Use Recursion?
public static double pow(double value, int
exponent) if (exponent 0) return
1D else if (exponent 1) return
value else exponent exponent -
1 return value pow (value, exponent)
How many would have preferred to do this with a
for loop structure or some other iterative
solution? How many think we can make our
recursive method even faster than iteration?
48Nota Bene
Our power function works through brute force
recursion. 28 2 2 2 2 2 2 2
2 But we can rewrite this brute force solution
into two equal halves 28 24 24 and 24
22 22 and 22 21 21 and anything to the
power 1 is itself!
49And here's the cool part...
- 28 24 24
- Since these are the same we don't have to
calculate them both!
50AHA!
So only THREE multiplication operations have to
take place
28 24 24 24 22 22 22 21 21
So the trick is knowing that 28 can be solved by
dividing the problem in half and using the result
twice!
51"But wait," I hear you say!
- You picked an even power of 2. What about our
friends the odd numbers? - Okay we can do odds like this
- 2odd 2 2 (odd-1)
52"But wait," I hear you say!
- You picked a power of 2. Lets see an odd one!
- Okay, how about 221
- 221 2 220 (The odd number trick)
- 220 210 210
- 210 25 25
- 25 2 24
- 24 22 22
- 22 21 21
53"But wait," I hear you say!
- You picked a power of 2. That's a no brainer!
- Okay how about 221
- 221 2 220 (The odd number trick)
- 220 210 210
- 210 25 25
- 25 2 24
- 24 22 22
- 22 21 21
That's 6 multiplications instead of 20 and it
gets more dramatic as the exponent increases
54The Recursive Insight
If the exponent is even, we can divide and
conquer so it can be solved in halves. If the
exponent is odd, we can subtract one, remembering
to multiply the end result one last time. We
begin to develop a formula Pow(x, e) 1,
where e 0 Pow(x, e) x, where e 1
Pow(x, e) Pow(x, e/2) Pow(x,e/2), where e
is even Pow(x, e) x Pow(x, e-1), where
e gt 1, and is odd
55Solution 2
public static double pow (double value, int
exponent) if (exponent 0) return
1D else if (exponent 1) return
value
We have the same base termination conditions as
before, right?
56Solution 2
public static double pow (double value, int
exponent) if (exponent 0) return
1D else if (exponent 1) return
value else if (exponent 2 0)
57Solution 2
public static double pow (double value, int
exponent) if (exponent 0) return
1D else if (exponent 1) return
value else if (exponent 2 0)
Dont like the mod operator? Get used to it. The
alternative is something mutually recursive and
wacky, like this
public static int isOdd(int num) if (num0)
return 1 else return
isEven(num-1) public static int isEven(int
num) if (num0) return 0 else
return isOdd(num-1)
58Solution 2
public static double pow (double value, int
exponent) if (exponent 0) return
1D else if (exponent 1) return
value else if (exponent 2 0)
That mod operator is starting to look a lot
better, eh? It merely tests if the number is
evenly divided by two.
public static int isOdd(int num) if (num0)
return 1 else return
isEven(num-1) public static int isEven(int
num) if (num0) return 0 else
return isOdd(num-1)
59Solution 2
public static double pow (double value, int
exponent) if (exponent 0) return
1D else if (exponent 1) return
value else if (exponent 2 0)
exponent exponent / 2
We next divide the exponent in half.
60Solution 2
public static double pow (double value, int
exponent) if (exponent 0) return
1D else if (exponent 1) return
value else if (exponent 2 0)
exponent exponent / 2 double half pow
(value, exponent)
We recurse to find that half of the brute
force multiplication.
61Solution 2
public static double pow (double value, int
exponent) if (exponent 0) return
1D else if (exponent 1) return
value else if (exponent 2 0)
exponent exponent / 2 double half pow
(value, exponent) return half
half
And return the two halves of the
equation multiplied by themselves
62Solution 2
public static double pow (double value, int
exponent) if (exponent 0) return
1D else if (exponent 1) return
value else if (exponent 2 0)
exponent exponent / 2 double half pow
(value, exponent) return half half
else exponent exponent - 1
If the exponent is odd, we have to reduce it by
one . . .
63Solution 2
public static double pow (double value, int
exponent) if (exponent 0) return
1D else if (exponent 1) return
value else if (exponent 2 0)
exponent exponent / 2 int half pow
(value, exponent) return half half
else exponent exponent - 1
double oneless pow (value, exponent)
And now the exponent is even, so we can
just recurse to solve that portion of the
equation.
64Solution 2
public static double pow (double value, int
exponent) if (exponent 0) return
1D else if (exponent 1) return
value else if (exponent 2 0)
exponent exponent / 2 int half pow
(value, exponent) return half half
else exponent exponent - 1
double oneless pow (value, exponent)
return oneless value
We remember to multiply the value returned by
the original value, since we reduced the
exponent by one.
65Recursion vs. Iteration
Those of you who voted for an iterative solution
are likely going to produce O(N) In a
Dickensian world, you would be fired for
this. While those of you who stuck it out with
recursion are now looking at
O(log2n) For that, you deserve a raise!
66Questions?
67The Cost of Recursion
Remember when CS1 covered the activation stack?
The stack is important its no joke. Computer
hardware, ,its a fact of life. We have to live
with it.
68The Cost of Recursion
Each time you call a method, a new activation
frame is created. This frame has unique copies
of the parameters (Java has IN parameters), and
unique copies of any local variables.
69Recursion Review
From CS1, you learned that recursion is defining
a program in such a way that it may call itself.
Each recursive call creates a new stack frame
stack height
time
Central to this understanding was the notion of a
stack of successive calls.
70Example
public int fact ( int num ) if (num 0)
return 1 else return num ( fact
(num -1) )
Lets trace this for 4!
71Problems
We dont calculate anything until the final
recursive call. We must save a copy of each
call, until we reach the end and unwind the
recursive call.
This is an example of augmentative (head)
recursion. Memory usage grows at O(n). This
gets expensive!
public int fact ( int num ) if (num 0)
return 1 else return num ( fact
(num -1) )
72Analyzing Augmentative Recursion
public int fact ( int num ) if (num 0)
return 1 else return num ( fact
(num -1) )
AHA!
The culprit is a recursive call that returns what
gets returned from a successive recursive call
(which depends on what gets returned from a
recursive call, etc., etc.) We leave the
multiply hanging This requires us to save each
level of the stack. How can we rewrite this to
avoid having to save the state of each frame?
73Tail Recursion
- Eliminates the one big problem with augmentative
recursion Massive memory use. - Requires a compiler/interpreter that recognizes
when a module is finished executing. (i.e. the
last action is a recursive call and theres no
more work to do.) - Requires a slightly different style of recursive
module writing.
74Tail Recursion
public int
fact (int product, int count, int max)
if (count max) return product
else count
count 1 product product
count return fact (product,
count, max)
75Tail Recursion
public int fact(int num) if (num
0) return 1 else
return fact (1, 1, num) public int
fact (int product, int count, int max)
if (count max) return product
else count
product count
return fact (product, count, max)
Method overloading takes place of helper method
These values are all precalculated no need to
save each frame!
76Tracing Tail Recursion
public int fact(int num) if (num
0) return 1 else
return fact (1, 1, num) public int
fact (int product, int count, int max)
if (count max) return product
else count
product count
return fact (product, count, max)
Each recursive call makes calculations independent
of the prior recursive calls. Theres no need
to save the state of each call
No stack buildup!
24 4 4
6 3 4
2 2 4
fact(4)
1 1 4
77Recursion Roundup
Tail and Augmentative recursion are nothing more
than fancy terms for different structures of
recursive methods. Remember, tail recursion
means that the last thing that happens in a
module is simply the return of a recursive
call. Tail return fact(...) Head return n
fact(...)
78How To Code Tail Recursion
Each semester, students wonder how to code
a method tail recursively. Usually, they
state
Does any one here have a similar problem? Really?
79If Youre Troubled by Tail Recursion...
If you can code normal recursion, then you can
code tail recursion. Heres a hint CONSIDER A
HELPER MODULE
If you understand recursion, you can do tail
recursion.
80Whats the Problem?
For some reasons, students who have a working
augmentative recursive method seem to forget that
they can change anything about the method to make
it tail recursive. Commonly overlooked options
include
Add a helper method
Add extra parameters
81Fibonacci Tail Recursion
Lets look at how to convert our augmentative
Fibonacci example to tail recursion.
Note that TWO recursive calls must be made. This
builds a stack quickly.
return fib(num-1) fib(num-2)
Some folks might look at this and say Its
impossible to make this tail recursive!
82Hogwash
How about a helper method? How about more
parameters? Honestly, you can do it!
83The Cycle
The Fibonacci sequence works by adding two prior
sequence values. This state is saved in a
stack when coded using augmentative recursion.
0, 1, 1, 2, 3, 5, 8, 13,
21, . . . ,
added
0
1
1
2
3
5
84So?
Well, the algorithm requires TWO values be known,
yet our original method (which works) has room
for only ONE parameter.
Lets add another parameter!
85From Stack to Parameter
By adding another parameter, we could eliminate
the need to hold information in the stack.
1
1
2
3
public static int fib (int b4last, int last, . .
. )
5
What other parameters would be needed?
86A Counter, Perhaps?
In our first example, we counted DOWN to zero or
one, since we knew special cases for
F(0) 0 F(1) 1 And these made
ideal termination conditions.
But we dont have these if we avoid using the
stack to hold information.
Are we allowed to add extra parameters? YES!
87More parameters is the Trick!
Why not just add more parameters, if needed? In
effect, we flatten the stack into a list of
parameters.
public static int fib (int b4last, int last,
int count, int max)
terminus
1
1
param1
param2
param3
param4
stack height
2
3
5
"A counter!"
88public class FibTail public static int
fib(int num) if (num0) return 0 else
if (num1) return 1
// FibTail
We start with our old code. The two special cases
of 1 and 0 are taken care of.
89public class FibTail public static int
fib(int num) if (num0) return 0 else
if (num1) return 1 else return
fib(0, 1, 2, num)
// FibTail
Next, we call our special stack flattener
method that we designed to avoid building a
stack. It has enough parameters to avoid
storing information and state in the stack.
90public class FibTail public static int
fib(int num) if (num0) return 0 else
if (num1) return 1 else return
fib(0, 1, 2, num) public static
int fib (int b4last, int last, int count,
int max) // FibTail
Just like we planned it ...
91public class FibTail public static int
fib(int num) if (num0) return 0 else
if (num1) return 1 else return
fib(0, 1, 2, num) public static
int fib (int b4last, int last, int count,
int max) if (count max) return (b4last
last) // FibTail
Our helper method is itself recursive,
and therefore requires a terminating condition.
92public class FibTail public static int
fib(int num) if (num0) return 0 else
if (num1) return 1 else return
fib(0, 1, 2, num) public static
int fib (int b4last, int last, int count,
int max) if (count max) return (b4last
last) else count count 1
// FibTail
If we havent gotten to the sequence number,
we advance our counter
93public class FibTail public static int
fib(int num) if (num0) return 0 else
if (num1) return 1 else return
fib(0, 1, 2, num) public static
int fib (int b4last, int last, int count,
int max) if (count max) return (b4last
last) else count count 1
return fib(last, b4last last, count, max)
// FibTail
And recurse. We shift the former last to be the
new b4last, and the new last value is the old
value of b4last last
94public class FibTail public static int
fib(int num) if (num0) return 0 else
if (num1) return 1 else return
fib(0, 1, 2, num) public static
int fib (int b4last, int last, int count,
int max) if (count max) return (b4last
last) else count count 1
return fib(last, b4last last, count, max)
public static void main(String args)
for (int i0 ilt 10 i)
System.out.println (fib(i)) // FibTail
Handling negative numbers gracefully is left as
an exercise.
Does this test main work for every value?
95Questions?
96(No Transcript)