Lecture 14:
Recursion
Announcements
new textbook recommendation:
Goodrich & Tamassio
P3
– questions?
H5
– posted, test using it with the posted test code
·
a
method is recursive if it can call itself
either directly
void p1() { … p1() … }
or indirectly
void p1() {
… p2() … }
void p2() { … p1() … }
Does
it make the code faster? - No!
use less memory? - No
Then why do we use it?
·
use recursion for clarity
(i.e., use it when it makes the code much simpler)
A way to think about
recursion:
when a method makes a recursive
call, the method
·
marks
its current place in the code (just after the call)
·
clones itself: makes a
copy of
o
the
instruction pointer
o
the
parameters
o
local
variables
and the clone starts
executing
·
when
the clone finishes
o
the
clone goes away
o
the
previous version starts up again at the marked position (just after the call)
Example:
void doClap (int k) {
if
(k==0) return;
clap k
times
doClap (k-1);
return;
}
Act out: doClap(2)
doClap(1)
doClap(0)
Questions?
Another example:
void doClap1 (int k)
{
clap k times
doClap1 (k+1);
return;
}
What happens if
you do doClap1(3)?
you get: 3
claps, 4claps, 5 claps, …
eventually
what happens?
you get a
runtime error (java.lang.StackOverflow)
problem: no code prevents recursive calls forever
How to make recursive methods work correctly
Recursion
Rule #1:
every
recursive method must have a base case
i.e., a condition under which no recursive
call is made
void doClap2 (int k)
{
if (k==0) return;
clap k times
doClap2 (k+1);
return;
}
Does
it have a base case? - yes
Can
we still have infinite recursion? - yes
Recursion Rule #2
Every
recursive method must make progress toward its base case for
every possible initial input
void doClap3 (int k) {
if (k==0) return;
clap k times
doClap3 (k-1);
return;
}
Does
doClap3 obey rules 1 and 2?
#1 – yes
#2 – sometimes (i.e., if k ³ 0)
Can
we change code to guarantee progress toward base case?
modify
the base case: if (k <= 0) return;
Recursive data structures
·
can do recursion with int
parameters
(count up or down to base case)
·
or with recursive data structures
e.g., a String S is
either empty ("")
or is a
character followed by a String
or, a linked list L
is either empty
or is a node followed by a linked list
Example:
//print letters in String S separated by
spaces
printStr(String S) {
if (S.length()==0)
return; // base case
System.out.print(S.substring(0,1)+" ");
printStr(S.substring(1));
}
·
about as simple as code that uses an
iterative loop
·
only need a return statement if a value
must be returned
·
S.substring(1) is ""
if S.length==1
simulate: printStr("HI") output: H I
printStr("I")
printStr("")
·
does
the base case satisfy rule #2?
no,
need to add if (S == null
|| S.length()==0)
Another example:
void printStr1
(String S) {
if (S == null || S.length()==0)
return;
System.out.print(S.substring(0,1)+" ");
printStr1(S.substring(1));
System.out.print(S.substring(0,1)+" ");
}
What is printed for printStr1("TOP");? - simulate
printStr1("TOP") output: T O P P O T
printStr1("OP")
printStr1("P")
printStr1("")
lesson:
things that are done after the recursive call are done only when
the call (and all calls it makes) have finished
How can we change the code to get a newline in the middle?
T O P
P O T
You try: modify the code to get the newline
// insert in front of printStr1 call
if (S.length()==1) System.out.println()
else
You try: given
a list L:
write
the recursive code to print the list backwards
(write the recursive
code to print it forwards first)
Solution:
print
it forward
printList(Listnode L) { // prints list L
if (L == null) return; // base case
System.out.print(L.getData());
printList(L.getNext()); // assume works on rest
}
print
it backward: move print statement after
recursive call
simulate
code to print it backwards for above list L
L
printList
“cat” “bat” “ant”
printList
“cat” L “cat” “bat” L
printList
L
printList
non-recursive
code: use a Stack
·
recursive code is simpler
Steps to create a recursive algorithm for a recusive
data structure
·
first or last: choose a base case -- make it robust
·
assume
the recursive call works correctly on the "rest" of the structure
·
think about how to combine that with the
operations on the "current piece" to get the correct result
illustrate
these steps on the above code that prints the list
You try:
write
a recursive method to count the number of nodes in a linked list L
int
count (Listnode L) {
if (L == null) return 0;
return 1 + count (L.getNext()); // assume it gives
// correct #
nodes
// in rest of
list
}