Lecture Notes 3: Recursion. Feb. 4

Dale, Joyce, and Weems, chap. 4 except section 4.6.

A recursive function is a function that calls itself, directly or indirectly.

There is always a base case where the function computes an answer without calling itself and a recursive case, where the function computes the answer to a more "complex" problem by solving the problem on "simpler" problems and then modifying the solutions to the simpler problems.

Whenever a function is called, including when it calls itself, it makes a new copy of the variables, including the formal parameters. These disappear when the function returns. (As always, if the variables are declared as objects,they are actually just references to the object, and it is just the reference that is copied, not the object itself.)

So whether you use recursion or iteration is ultimately a matter of taste. (In fact, mathematicians often don't distinguish.) However, recursion is often much more elegant, so you need to be comfortable with it, or you will be programming with one hand tied behind your back.

Unless there is a good reason to use recursion, you probably do better to use iteration because:

In particular, in practice you would want to use iteration rather than recursion for all the examples in today's lecture. However, we'll see plenty of examples later in the semester where you really do want to use recursion, and where using iteration would be quite ugly.

Certainly if you find yourself manipulating an explicit stack, you do well to consider writing the code recursively.

Recursion over Integers

RecursiveIntFns.java

Since these are numerical functions, this is all non-object-oriented, C-style coding --- static methods.

Simple Recursion

    public static double power(double x, int k)  {
        if (k==0) return 1.0;                          // Base case
         else if (k > 0) return x * power(x,k-1);      // Recursive case
         else return 1.0/power(x,-k);                // Recursive case
      }


power(2.0,-3)
|  power(2.0,3)
|  |  power(2.0,2)
|  |  |  power(2.0,1)
|  |  |  |  power(2.0,0)
|  |  |  |  returns 1.0
|  |  |  returns 2.0
|  |  returns 4.0
|  returns 8.0
returns 0.125

Two recursive calls. Also two base cases

Compute the kth Fibonacci number in the really inefficient way

    public static int fib(int k) {
       if (k==0) return 1;              // Base case
       else if (k==1) return 1;         // Base case
       else return fib(k-2) + fib(k-1); // Recursive case
      }

fib(4)
|   fib(2)
|   |   fib(0)
|   |   returns 1
|   |   fib(1)
|   |   returns 1
|   returns 1+1=2
|   fib(3)
|   |   fib(1)
|   |   returns 1  
|   |   fib(2)
|   |   |   fib(0)
|   |   |   returns 1
|   |   |   fib(1)
|   |   |   returns 1
|   |   returns 1+1=2
|   returns 1+2 = 3
returns 2+3 = 5

Mutually recursive functions

Hailstone procedure, written with mutually recursively functions
Returns the number of steps that the hailstone procedure takes to get to 1

Iterative pseudo code

{ start with a number x;
  repeat {
      if (x is even) x = x/2;
      if (x is odd) x = 3x+1;
     }  until (x==1)
  return the number of iterations of the loop.
}
This is the simplest badly-understood numerical function; it is unknown whether it terminates for all values. Wikipedia article on the "Collatz conjecture".
     public static int hailstone(int k) {
         System.out.print(k + "  ");
         if (k==1) return 0;
         else if (k % 2 == 0) 
                 return hsEven(k);
         else return hsOdd(k);
       }

     public static int hsEven(int k) {
           return 1+hailstone(k/2);
        }

     public static int hsOdd(int k) {
           return 1+hailstone(3*k+1);
        }


hailstone(3)
|   hsOdd(3)
|   |   hailstone(10)
|   |   |   hsEven(10)
|   |   |   |   hailstone(5)
|   |   |   |   |   hsOdd(5)
|   |   |   |   |   |   hailstone(16)
|   |   |   |   |   |   |   hsEven(16)
|   |   |   |   |   |   |   |   hailstone(8)
|   |   |   |   |   |   |   |   |   hsEven(8)
|   |   |   |   |   |   |   |   |   |   hailstone(4)
|   |   |   |   |   |   |   |   |   |   |   hsEven(4)
|   |   |   |   |   |   |   |   |   |   |   |   hailstone(2)
|   |   |   |   |   |   |   |   |   |   |   |   |   hsEven(2)
|   |   |   |   |   |   |   |   |   |   |   |   |   |   hailstone(1)
|   |   |   |   |   |   |   |   |   |   |   |   |   |   return 0
|   |   |   |   |   |   |   |   |   |   |   |   |   return 1
|   |   |   |   |   |   |   |   |   |   |   |   return 1
|   |   |   |   |   |   |   |   |   |   |   return 2
|   |   |   |   |   |   |   |   |   |   return 2
|   |   |   |   |   |   |   |   |   return 3
|   |   |   |   |   |   |   |   return 3
|   |   |   |   |   |   |   return 4
|   |   |   |   |   |   return 4
|   |   |   |   |   return 5
|   |   |   |   return 5
|   |   |   return 6
|   |   return 6
|   return 7
return 7

Recursion with local variables: Factorial using addition

     public static int facAdd(int k) {
         int sum = 0;

         if (k <= 1) return 1;
         for (int i = 0; i < k; i++)
             sum = sum + facAdd(k-1);
         return sum;
      }

facAdd(3)
sum = 0
i = 0
|   facadd(2)
|   sum = 0
|   i = 0
|   |   facadd(1)
|   |   return 1;
|   sum = 1
|   i = 1
|   |   facadd(1)
|   |   return 1;
|   sum = 2
|   return 2
sum = 2
i = 1
|   facadd(2)
|      ... Same as above
|   return 2
sum = 4
i = 2
|   facadd(2)
|      ... Same as above
|   return 2
sum = 6
return 6

Double recursion (i.e. recursion on two arguments

Compute C(k,n), the number of combinations of size k out of n (Pascal's triangle) in the really inefficient way
    public static int combo(int k, int n) {
       int value;
       if (k < 0 || k > n) return -1;       // Error condition
       else if (k==0 || k==n) return 1;     // Base case
       else return
             combo(k,n-1) + combo(k-1,n-1); // Recursive case
     }


combo(2,5)
|   combo(2,4)
|   |   combo(2,3)
|   |   |   combo(2,2)
|   |   |   return 1
|   |   |   combo(1,2)
|   |   |   |   combo(1,1) 
|   |   |   |   return 1
|   |   |   |   combo(0,1) 
|   |   |   |   return 1
|   |   |   return 1+1=2
|   |   return 1+2 = 3
|   |   combo(1,3)
|   |   |   combo(1,2)
            .... same as above ...
|   |   |   return 2
|   |   |   combo(0,2)
|   |   |   return 1
|   |   return 2+1 = 3
|   return 3+3 = 6
|   combo(1,4)
|   |   combo(1,3)
|   |    ... same as above ...
|   |   return 3
|   |   combo(0,3)
|   |   return 1
|   return 3+1 = 4
return 6+14 = 10
// Main procedure
//
    public static void main(String[] args) {
         System.out.println("power(3.0,4): " + power(3.0,4));
         System.out.println("power(2.0,-2): " +  power(2.0,-2));
         System.out.println("fib(4): " + fib(4));
         System.out.println("hailstone(7) Trace: " );
         System.out.println("Value: " + hailstone(7));
         System.out.println("facAdd(4): " + facAdd(4));
         System.out.println("combo(2,5): " + combo(2,5));
      }
}  // end RecursiveIntFns

Recursive procedure on linked list of ints

IntList.java
// Linked list of int. No header, empty lists not permitted.

public class IntList {
    private int value;     
    private IntList next;
  
    public IntList(int v, IntList n) {          // Constructor
        value = v;
        next = n;
      }

    public int getValue() { return value; }       // Getters
    public IntList getNext() { return next; }
    public void setValue(int v) { value = v; }    // Setters
    public void setNext(IntList n) { next = n; }

Note: The letter labels A,B,C,D are just for reference here, not part of the data structure

Find the last node of a linked list.

    public IntList findLast() {
       if (getNext() == null) return this;
       else return getNext().findLast();
     }


A.findLast()
|   B.findLast()
|      C.findLast()
|         D.findLast()
|         returns D
|      returns D
|   returns D
returns D

Add a new node with value v at the end of l.

    public void addEnd(int v) {
        findLast().setNext(new IntList(v,null));
      }

Add up the values in a list, recurring down the owner

    public int sumList() {
       if (getNext() == null) return getValue();
       else return getValue() + getNext().sumList();
      }

A.sumList()
|    B.sumList()
|    |   C.sumList()
|    |   |   D.sumList()
|    |   |   return 7
|    |   return 5+7 = 12
|    return 3+12 = 15
return 2+15 = 17

Convert list of int to string

As often in recursive programs, there is a top-level non-recursive super-routine that sets things up, and it calls a recursive subroutine.
// Recursive method for constructing the end of the string, after the
// initial open bracket.

   public String toString1() {
       if (getNext() == null)
          return getValue() + "]";
       else return getValue() + ", " + getNext().toString1();
     }

// Top level routine that starts with the "[" and then calls toString1 to
// do the rest.

   public String toString() {
        return "[" + toString1();
     }

A.toString()
|   A.toString1()
|   |   B.toString1()
|   |   |   C.toString1()
|   |   |   |   D.toString1()
|   |   |   |   returns "7]"
|   |   |   returns "5, 7]"
|   |   returns "3, 5, 7]"
|   returns "2, 3, 5, 7]"
returns "[2, 3, 5, 7]"

Recursive method for finding the sum of a list

The difference between this and sumList above is that in sumList we recur down the owner of the method. This is a static method, and we recur down an argument.

Advantage of recurring

`
   static public int sumListArg(IntList l) {
        if (l==null) return 0;
        else return l.getValue() + sumListArg(l.getNext());
       }

sumListArg(A)
|   sumListArg(B)
|   |   sumListArg(C)
|   |   |   sumListArg(D)
|   |   |   |   sumListArg(null)
|   |   |   |   returns 0
|   |   |   returns 7+0 = 0
|   |   returns 5+7 = 12
|   returns 3+12 = 15
returns 2+15=17
The difference between this and sumList above is that in sumList we recur down the owner of the method. This is a static method, and we recur down an argument.

Advantages of recurring down the owner:

Advantage of recurring down an argument: null can be an argument but it can't be an owner. Therefore, if you recur down an argument, then the base case can be null, which is often more elegant. If you recur down the owner, then the base case has to be an object whose next field is null, which is less elegant. If your class has a number of reference fields and you are recurring down all of them, then the object-based method can be seriously clumsy.

   static public void main(String[] args) {
       IntList l = new IntList(2,null);
       l.addEnd(3);
       l.addEnd(5);
       l.addEnd(7);
       System.out.println(l.toString());
       System.out.println("Sum = " + l.sumList());
    } // end main
} // end RecursiveIntList