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

  }