03b: Recursion
ICS211, Spring 2013
Dr. Zach
(switch view)
Status
- Labs: Continue to submit where you are, but may attend other sections.
- Discussion 01 closing... to be summarized next time.
- Discussion 02 opening
- In general: Beware contrived examples (lectures and quizzes)
- so you learn to read bad code, but write good code (ie, coding stds)
Java
JVM: Runtime Stack and Heap
JVM: Three parts of memory
- (runtime) stack: methods details
- incl. parameters and local variables
- heap: objects (incl. instance variables)
- Whenever you see
new
, you're creating something in the heap
- And occasionally other times, such as with array initializers: int[] nums = {1, 3, 5};
- static/class memory*: static variables, (code)
(JVM = Java Virtual Machine)
*currently called "permanent generation" storage in JVM
Runtime Stack
- Composed of method activation records (aka: stack frames, activation frames)
- Starts with main
- One for each method call, current call on top
- parameters and local variables stored here
- primitives: contained value stored on stack
- objects: variable contains reference to that object in the heap
- See this in stack trace from runtime crash
- Example: Tracing through 02A example
JVM Conclusion
- Runtime stack very helpful to understand both methods and recursion
- Return to heap and static memory later...
Recursion
Concept: Recursion
- Conceptually: Compute a solution to a problem by first computing solutions to smaller instances of the same problem (using the same technique)
- Practically: A method that calls itself (usually directly)
Algorithm: Handing out papers (1/2)
An iterative (loop-based) approach (called on a Teacher object):
handOutPapers(pile):
student = first student in line
while pile is not empty:
paper = pile.removeTop()
give paper to student
student = next student in line
- Assuming more papers than students here.
- This method does all the work.
Algorithm: Handing out papers (2/2)
A recursive approach (called on a Student object):
handOutPapers(pile):
if pile is not empty:
paper = pile.removeTop()
keep paper
next = next student in line
next.handOutPapers(pile)
- So stack gets smaller each time (thanks to removeTop)
- But each student follows the exact same algorithm!
- Stops when a student gets empty stack (huh?)
Alternatives
More descriptive of real life:
handOutPapers(pile):
paper = pile.removeTop()
keep paper
if pile is not empty:
next = next student in line
next.handOutPapers(pile)
But in code: what if passed an empty pile?
handOutPapers(pile):
if pile is not empty:
paper = pile.removeTop()
keep paper
if pile is still not empty:
next = next student in line
next.handOutPapers(pile)
Original version was simpler. Often simplest code if you continue to 0 or empty case.
Practical example: Quiz 02, last question
public class CodeD {
public static void main(String[] args) {
lotsAndLots(0);
}
public static void lotsAndLots(int num) {
System.out.println(num);
lotsAndLots(num + 1);
}
}
- What happened? For me: printed up to 11620, then crashed with StackOverflowError
- Why? (in terms of runtime stack) Ran out of stack space due to infinite recursion
Example: Print digits n to 1
public static void printDigits(int n) {
if (n == 0) {
return; //base case: do nothing
}else {
System.out.println(n);
printDigits(n - 1); //recursive call on smaller problem
}
}
- What would printDigits(3) do? 3 2 1 (one per line)
- What would printDigits(-1) do? "infinite" recursion
- What if we swap the order of the two lines in the
else
...?
Example: Print digits 1 to n
public static void printDigits(int n) {
if (n <= 0) {
return; //base case: do nothing
}else {
printDigits(n - 1); //recursive call on smaller problem
System.out.println(n); //print after returning from recursion
}
}
- Changed test in if; swapped order of two line in else.
- Now prints...? 1 2 3 (one per line)
- Other ways to write this...
- Else is actually optional here, but reflects logic
if
doesn't always have to be for base case
Example: Print digits 1 to n (alternatives)
- Else is optional (since return if enter the if block)
public static void printDigits(int n) {
if (n <= 0) {
return; //base case: do nothing
}
printDigits(n - 1); //recursive call on smaller problem
System.out.println(n); //print after returning from recursion
}
- If doesn't need to contain the base case; base can even be implicit
public static void printDigits(int n) {
if (n > 0) {
printDigits(n - 1); //recursive call on smaller problem
System.out.println(n); //print after returning from recursion
}
//implied base case: do nothing if n <= 0.
}
- So even with a method this simple--one logical test and two method calls--lots of ways to do it.
Example: Digits n to 1 as String
What if we want to generate a String of the numbers?
public static void main(String[] args) {
String str = allDigits(4);
System.out.println(str);
}
public static String allDigits(int n) {
if (n <= 0) {
return "";
}else {
return n + " " + allDigits(n - 1);
}
}
- What does it print? 4 3 2 1
- How do we reverse the digit order? allDigits(n - 1) + " " + n
Recursion Lessons
- Recursion == method calls itself
- Needs to break the problem into smaller pieces each time
- Usually (but not always): subtract 1 or divide in half
- Each call creates another "copy"/version of the method on the runtime stack
- but remembers where it is in current method
- Need 1 (or more) base cases (stopping conditions)
- Stack of recursive calls then returns, one at a time
- Can also do some work after ("on the way back") from the recursive calls
Classic Example: Factorial
public static int factorial(int n) {
if (n <= 0) {
return 1;
}else {
return n * factorial(n - 1);
}
}
Classic Example: Fibonacci
/** Computes nth Fibonacci number: 1,1,2,3,5,8,13,... */
public static int fibonacci(int n) {
if (n <= 0) {
return 0; //error: n should not be less than 1
}else if (n <= 2) {
return 1;
}else {
return fibonacci(n - 1) + fibonacci(n - 2);
}
}
Classic Example: Fibonacci, more efficiently
public static int fibonacci(int n) {
return fib(0, 1, n);
}
private static int fib(int prev, int curr, int n) {
if (n <= 1) {
return curr;
}else {
return fib(curr, prev + curr, n - 1);
}
}
- Public method and private helper method
- Big-O now? O(n)
Summary
- How methods are managed with the runtime stack in the JVM
- Recursion
- Understanding how the same method can call itself over and over
- Because the runtime stack keeps track of each call ("copy") of the method
For next time...
- So far: recursion as alternative to loops
- Quiz 03 will give you more XP w/ recursion
- A03: write 5 methods both iteratively and recursively
- Discussion 03 to be posted
- Next week: backtracking and using recursion on problems that loops can't (easily) handle