Recursion

you are watching: Recursion here hiddentracks.org


RECURSION

TOPICS

·      
Definitions

·      
Examples

·      
When to Use Recursion

·      
Sample Program: Hanoi Towers

OUTLINE

Definitions:
 

·      
Recursion
is a very important problem-solving approach that is an alternative to
iteration (remember that iterative solutions include loops).

·      
Math
background:

There is an accepted form of mathematical definition that uses concepts to
define themselves. Such definitions are called inductive definitions. When carefully used,
such definitions are very concise, very powerful, and very elegant.

·      
Definition:
A recursive definition of an entity
defines the entity in terms of itself. Also called self-referential
definitions. Example: The recursive definition of a data structure could be
used to build the data structure from scratch.

·      
In
programming languages, we also talk about recursive methods (functions).

·      
Definition: Recursion = A problem solving / programming technique in which a
method (function) can call itself in order to solve the problem. In the
process, the problem is solved by reducing it to smaller versions of itself.

·      
In
Java a method can call itself –> if written that way, the method is called
a recursive method.

·      
Definition: Infinite recursion = the situation in which a function calls itself over and
over endlessly. A definition with a missing or badly written base case causes infinite
recursion, similar to an infinite loop (there is no stop!)  –> A  recursive solution  to a problem must be written
carefully.

·      
The
idea:
Each
successive recursive call should bring you one step closer to a situation in
which the problem can easily be solved. Each recursive definition has two
separate parts: a base case and a general (or recursive) case.

1.     The easily solved situation
is called the  base case. The base case is a simple case of the
problem that we can answer directly; the base case does NOT use recursion. Each
recursive algorithm must have at least one base case. Without at least one base
case –> infinite recursion.

2.     The general (recursive) case
is the more complicated case of the problem that isn’t easy to answer directly,
but can be expressed elegantly with recursion. The general case is the place
where the recursive calls are made –> written so that with repeated calls
it reduces to the base case (or brings you closer to the base case). Must
eventually be reduced to a base case; if not –> infinite recursion. Must
have at least one general case –> if not, no recursion!

·      
In addition to the base and recursive case, each recursive
definition has an implicit case. The base case states one or more cases that
immediately satisfy the definition. The recursive case states how to apply the
definition again to satisfy other cases. The implicit case (which is implied, not
mentioned) states “And nothing else satisfies the definition”.

·      
General
format for recursive functions:

if(some
easily-solved problem) // base case

   
solution statement


else                       
// general case

   
recursive function call

·      
Definition: Direct recursion = the method is calling itself. A method that
calls another method and eventually results in the original method call is
called indirectly recursive.

·      
Definition: Indirect recursion = the method calls another method and eventually
results in the original method call. Indirect recursion requires the same
careful analysis as direct recursion. Tracing through indirect recursion can be
a tedious process.

·      
Every recursion has a forward phase (a call at every level except
the last makes a call to the next level and waits for the latter call to return
control to it.

·      
Every recursion also has a backing-out phase (a call at every
level except the first passes control back to the previous level, at which
point the call waiting at the previous level resumes the work.)

·      
Definition: Tail recursion = A recursive method in which no statements are executed
after the return from the recursive call (the recursive call is the last
statement) It often indicates that the problem could be solved more easily with
iteration.

·      
To
design a recursive method, you must:

1. Understand the problem requirements.
2. Determine the limiting conditions.
3. Identify the base case(s) and provide a direct solution to each base case.
4. Identify the general case(s) and provide a solution to each general case in
terms of a smaller version of itself.

Examples of
Recursive Methods:


 

·      
Example #1 (sum): Write a recursive method to find the sum of all positive
integers between
1 and
n. The method call sum(5) should have value 15, because that is 1
+ 2 + 3 + 4 + 5.


The iterative method:

public
static int sum(int n){

    int total = 0;
    for(int i = 1; i <= n; i++)

        total += i;

    return total;
}

Recursive method analysis: For an easily
solved situation, the sum of the numbers from 1 to 1 is 1.
So, our  base case 
could be
     

if
(n == 1)

    return 1;


Now for the  general case. . .

sum(n)
= 1 + 2 + . . . + (n-1) + n =  [1 + 2 + . . . + (n-1)] + n = sum(n – 1) +
n


Notice that the recursive call  sum(n
– 1)
gets us “closer”
to the base case of 
sum(1).
The recursive method:

public
static int sum(int n){

    if (n == 1) //
base case


       return  1;
    else    // general case
       return n + sum( n – 1);//tail recursion
}

 

·      
Example #2 (factorial): Write a recursive method to
find

n
factorial (n!
= n * (n-1) * (n-2)… 3 * 2 * 1).
The method call factorial(5) should have value 120, because that is 5
* 4 * 3 * 2 * 1 .



The iterative method:

public
static int factorial(int
n){

    int fact = 1;
    for(int i = 1; i <= n; i++)

        fact *= i;

    return fact;
}

 

Recursive method analysis: For a situation in which the
answer is known, the value of  1
! is
1
.  So our  base case  could be
      if (n == 1)
         
return 1;

Now for the  general case . . .

n!
= n * (n-1) * (n-2)… 3 * 2 * 1 = n * [(n-1) * (n-2)… 3 * 2 * 1] = n *
(n-1)! OR:

factorial(n)
= n * factorial(n-1)



Notice that the recursive call  factorial(n
– 1)
gets us “closer”
to the base case of 
factorial(1).
The recursive method:

public
static int factorial(int
n){

    if (n == 1)  // base case

       return  1;
    else     // general case
       return n * factorial(n
– 1); //tail recursion

}

 

·      
Example #3 (power): Write a recursive method to find xn recursively.
      The iterative method:

public
static int power(int x, int n){


    int prod = 1;
    for(int i = 1; i <= n; i++)

        prod *= x;
    return prod;
}

 

Recursive method analysis: From mathematics, we know
that  2

= 1
    
and    
25  =
2 * 24

In general,  x
= 1 
and    
xn
= x * xn-1
   
for integer
x,
and integer
n > 0. Here we are defining xn recursively, in terms
of  xn
-1

The recursive method:

public
static int power(int x, int n ){


   if (n == 0)   // base case

      return  1;
   else      // general case
      return (x * power ( x ,
n-1)); //tail recursion

}

 

What is the value of  2-3?   Again from
mathematics, we know that it is  2
-3
= 1/23 = 1/8


In general, 
xn
= 1/ x-n
 
for non-zero
x,
and integer
n < 0. Let’s define xn  recursively, in terms of  x-n  when n
< 0 .

The new recursive method:

public
static double power(int x, int
n){

   if (n == 0)     // base case
      return  1;
   else if(n > 0)  // first general case
      return (x * power(x , n –
1));

   else        // second general case
      return (1.0 / power(x , –
n));

}

·      
Example #4 (gcd): Write a recursive method to
find the greatest common divisor of 2 integers (the largest integer that goes
evenly into 2 integers or, in other words, the largest number that is a factor
of both). We will use the Euclid’s formula, which states that: 
gcd(a, b) = gcd(b, a mod b), and gcd(a, 0) =
a.
Based
on this formula, it is easy to figure out the base case and the general case
for the recursive method.
 The iterative method:

public
static int gcd(int x, int y){

    int temp = x %
y;

    while(temp > 0) {
        x = y;
        y = temp;
        temp = x % y;
    }
    return y;
}

The recursive method:

public
static int gcd(int x, int y){

    if(y ==
0)           // base case

        return x;
    else if(x < 0 || y < 0) // first general case
        return gcd(Math.abs(x), Math.abs(y));
   
else               
// second general case

        return gcd(y, x % y);
}

·      
Example #5: (print chars) Write
a method to print a line of
n characters.
The iterative method:

public
static void printChar(char what, int
n){

    for(int i = 1; i <= n; i ++)

        System.out.print(what);
    System.out.println();
}

 

The recursive method:

public
static void printChar(char what, int
n){

    if(n == 0) //
base case


        System.out.println();
    else {     // general case
        System.out.print(what);
        printChar(what, n-1);
    }
}

 

·      
Example #6: (Fibonacci) Write a recursive method to determine
the n-th Fibonacci number. The first Fibonacci number
is 1 and the second Fibonacci number is 1. A Fibonacci number in the sequence
is the sum of the previous 2 Fibonacci numbers.

The recursive method:

public
static int fibonacci(int n) {


     if(n ==
1)      // first base case

         return 1;
     else if (n == 2)// second base case
         return 1;
    
else           // general case

         return(fibonacci(n – 1) + fibonacci(n –
2));

}

 

·      
Example #7: (Fibonacci, more general) Write a recursive method to determine the n-th Fibonacci
number in a sequence where: the first number in the sequence is a and the
second number is b. A Fibonacci number in the sequence is the sum of the
previous 2 Fibonacci numbers

The recursive method:

public
static int fibonacci(int a, int b, int
n) {

     if(n ==
1)       // base case

         return a;
     else if (n == 2) // second base case
         return b;
    
else           // general case

         return(fibonacci(a, b, n – 1) + fibonacci(a,
b, n – 2));

}

 

·      
Example #8: (recursion with arrays) Write
a method to print array elements in reverse order. If the array (a) 
is: 
74  36  87  95,  the call  printRev(a,
0, 3 )
should
produce this output:   
95  
87   36  74


The recursive method:

public
static void printRev(int []
a, int i, int j) {


     if (i
<= j) { // general case

        System.out.print(a[j] + ”  “); // print last element
        printRev(a, i, j – 1); // then process the rest
     }
    // where is the
base case???


}

 

·      
Example #9: (recursion with arrays) 
Write
a
recursive method that takes an array
a  and two subscripts, i and j as arguments, and returns
the sum of the elements
a[i] + . .
. + a[j]
.

The recursive method:

public
static int sum(int [] a, int i, int
j){

   if (i == j) // base case
       return (a[i]);

   else        // general case
       return (a[i] + sum(a, i + 1, j));
}

 

·      
Example #10: (recursion with arrays)  Write a recursive method that
takes an array
a and its size as arguments, and returns the minimum element
in the array
.
What is the base case here?
The recursive method:

public
static int findMin(int[] a, int size) {

     int min;
     if(size == 1)  // base case: one element in list
         return
a[0];

     else
{         //
general case


         min = findMin(a, size – 1);
         if(min <
a[size – 1])

             
return min;

         else
             
return a[size – 1];


     }
}

 

·      
Example #11: (recursion with arrays)  Write a similar
recursive method to allow subarray processing (from
a[i] to
a[j])

The recursive method:

public
static int findMin(int [] a, int i,
int j) {

     int min;
     if(i ==
j)  // base case: one element in list

         
return a[i];

     else
{      // general case

          min =
findMin(a, i + 1, j);

          if(a[i] <= min)
              
return a[i];

          else
              
return min;

     }
}

 

·      
Example #12: (recursion with strings) 
Write
a
recursive method to display in reverse order a string of characters. Take input
characters until the user enters a period  and
output all characters entered so far in reverse order.
The recursive method:

public
static void printBack (){

    Scanner input = new Scanner(System.in);
    char inputChar;
    System.out.print(“Enter
a character. When finished press <.>: “);

    inputChar = input.next().charAt(0);

    if(inputChar !=
‘.’){ // general case

        printBack();
        System.out.print(inputChar);
    }
}

Output:

Enter a character. When finished press <.>: r
Enter a character. When finished press <.>: e
Enter a character. When finished press <.>: c
Enter a character. When finished press <.>: u
Enter a character. When finished press <.>: r
Enter a character. When finished press <.>: s
Enter a character. When finished press <.>: i

Enter
a character. When finished press <.>: o

Enter a character. When finished press <.>: n
Enter a character. When finished press <.>: .

noisrucer
 

·      
Example #13: (recursion with linked lists) Write a recursive method to
compare 2 linked lists for equality (given references to their first node).
The recursive method:

public
static boolean equalLists(LinkedListNode<Integer> head1, LinkedListNode<Integer>
head2){

    if(head1 == null || head2 == null)//base case #1
        return (head1 ==
head2);

    if(head1.data !=
head2.data)      //base case
#2

        return false;
    return equalLists(head1.link,
head2.link);// general case

}

 

·      
Example #14:(recursion with
linked lists)

Write a recursive method to print a linked list in direct order (given a
reference to its first node).
The recursive method:

public
static void printLL(LinkedListNode<Integer>
head) {

    if(head != null) {
        System.out.print(head.info + ” “);
        printLL(head.link);//tail recursion
    }
}

 

·      
Example #15:(recursion with
linked lists)
 
Write a recursive method to print a linked list in reverse order (given a
reference to its first node).
The recursive method:

public
static void revPrintLL(LinkedListNode<Integer>
head) {

    if(head != null) {
        revPrintLL(head.link); //forward phase
        System.out.print(head.info + ” “); //backing-out phase
    }
}

·      
Example #16:(recursion with
linked lists)
 
Write a recursive method to count the nodes in a linked list (given a reference
to its first node).
The recursive method:

public
static int countNodesLL(LinkedListNode<Integer> head) {

    if(head == null)
        return 0;
    else
        return 1 + countNodesLL(head.link);
}

When
to Use Recursion


 

·      
The
examples listed here could all have been written without recursion, by using
iteration instead.  The iterative solution uses a
loop, and the recursive solution
uses an if
statement. However, for
certain problems the recursive solution is the most natural solution.

·      
Recursion
is a very important tool in supporting data abstraction. Recursive definitions
are often necessary to define data and associated operations, and the recursive
functions are (in many cases) the natural solution for the implementation of
the operations on data.

·      
Drawbacks
of recursion: the amount of stack space used to implement recursion and
possible redundancy. Every recursive method call produces a new instance of the
method (with a new set of local variables and parameters).  Each set of
local variables and parameters related to the current call is stored on the
stack, to be picked up on return. For example, the recursive implementation for
the factorial method is a good example for its simplicity, but not a practical
solution (huge overhead –> huge usage of stack space).

·      
There
are no absolute rules when choosing between a recursive or
an iterative solution. The most powerful benefit of recursive methods is the
fact that they are concise, which makes them easier to maintain and read. On
the other hand, recursive methods consume time and computer storage, which
means that they may not be very efficient. These are some guidelines when
considering the alternatives:

1.     Design a recursive method if
the problem is stated recursively and the recursive algorithm is less complex.
Keep in mind that in many cases, recursion is a technique that reduces the
complexity of the algorithms you want to implement.

2.     Design an iterative method if
similar complexities for the recursive and the iterative algorithms (the
iterative solution is likely to be more efficient)

In
many cases the choice is not very clear (concise vs. efficient). More
experienced designers know when efficiency really matters and when it is less
important.

Sample
Program: Hanoi Towers

The problem: We have n
disks of different sizes, and 3 pegs: A
(the source), B (the
destination), and C (the spare).
Initially, all the disks are on peg A,
ordered by size, with the smallest on the top. The problem is to move all the
disks, one by one, from peg A to
peg B, using peg C as an auxiliary/spare (it must be
empty at the beginning and at the end of the game). Restriction: a disk cannot
be placed on top of one that is smaller in size.
How could this be done?  Look at the following figure:
 

Description: C:Courses-NOWWebpage237LectureNotesHanoi(a-b).gif
 
 

Description: C:Courses-NOWWebpage237LectureNotesHanoi(c-d).gif


 
 

(a) The initial state 
 
 
 
 
 
 

(b) move (n – 1) disks from A to C
 
 
 
 
 
 

(c) move one disk from A to B 
This disk is in its final place now.
 
 
 
 
 
 
 

(d) move (n – 1) disks from C to B 

The algorithm: To free the n-th disk, we
have to move (n-1) disks from top. Each stage could be the initial state
(again) for a smaller problem (one less disk each time). To get closer to what
we discussed so far:

Base
case
: If  n = 1 (only one disk), we just move it from peg A to
peg B (figure (b) and (c): one disk from peg A moved to peg B).
General case: If n > 1:

·      
Ignore
the bottom disk and solve the problem for (n-1) disks with peg C the
destination and peg B the spare.

·      
After
step 1, peg C will hold (n-1) disks and the largest disk will be left on peg A (figure (b)). Solve the problem for n=1 and move the large
disk from peg A to peg B. (figure (c))

·      
Move
(n-1) disks from peg C to peg B (the original problem with a twist: peg C is
the source, peg B is the destination and peg A is the spare)

More
refined, if we write the solution as a recursive function that will take
parameters n disks and 3 pegs (hanoiTowers(n, A, B,
C)) and if we start with n disks on peg A and 0 disks on both pegs B and C, the
algorithm is:

  1. In the initial state (all disks on peg A), solve the
    problem hanoiTowers(n-1, A, C, B). In other words, you have here step 1
    from the previous variant. When finished, the largest disk will be on peg
    A and (n-1) disks will be on peg C.
  2. With the largest disk on peg A and (n-1) disks on peg
    C, solve the problem hanoiTowers(1, A, B, C). In other words, you have here step 2 from
    the previous variant. When finished, the largest disk will be on peg B (in
    place) and (n-1) disks will be on peg C.
  3. With the largest disk on peg B and all the others on
    peg C, solve the problem hanoiTowers(n-1, C, B, A).

Java
recursive method:

public static void hanoiTowers(int how_many, char source, char destination, char spare){

    
if (how_many == 1) { //base
case

        
System.out.print(“Move top disk from peg ”
+ source);

        
System.out.println(” to peg ” +
destination);

    
}

    
else { //general case

        
hanoiTowers(how_many-1, source, spare, destination);


        
hanoiTowers(1, source, destination, spare);

        
hanoiTowers(how_many-1, spare, destination, source);


    
}

 }
The Java program:

import java.util.Scanner;
public class HanoiTowers {
   
public static void main(String[] args) {

       
int diskCount;// Number of disks on starting peg(A)

       
Scanner input = new Scanner(System.in);

       
System.out.print(“Input number of disks:
“);

       
diskCount = input.nextInt();


       
while(diskCount <= 0) {

            
System.out.print(“nERROR!
Should be positive. Reenter: “);

            
diskCount = input.nextInt();

       
}

       
System.out.println(“Hanoi Towers: Output with
” + diskCount + ” disks”);

       
System.out.println(“INSTRUCTIONS === peg A =
source, peg B = destination, peg C = spare ===n”);

       
hanoiTowers(diskCount, ‘A’,
‘B’, ‘C’);//method call

    
}

    
public static void hanoiTowers(int
how_many, char source, char destination, char spare){


        
if (how_many == 1) { //base
case

         
System.out.print(“Move top disk from peg ”
+ source);

         
System.out.println(” to peg ” +
destination);

       
}

        
else { //general case

            
hanoiTowers(how_many-1, source, spare, destination);


            
hanoiTowers(1, source, destination, spare);

            
hanoiTowers(how_many-1, spare, destination, source);


        
}

    
}

}
OUTPUT:

Input number of disks:
4

Hanoi Towers: Output
with 4 disks

INSTRUCTIONS === peg A
= source, peg B = destination, peg C = spare ===

Move top disk from
peg A to peg C

Move top disk from peg
A to peg B

Move top disk from peg
C to peg B

Move top disk from peg
A to peg C

Move top disk from peg
B to peg A

Move top disk from peg
B to peg C

Move top disk from peg
A to peg C

Move top disk from peg
A to peg B

Move top disk from peg
C to peg B

Move top disk from peg
C to peg A

Move top disk from peg
B to peg A

Move top disk from peg
C to peg B

Move top disk from peg
A to peg C

Move top disk from peg
A to peg B

Move top disk from peg
C to peg B

Key
Terms

Base case: the case in a recursive
definition in which the solution is obtained directly.
Directly recursive method: a method
that calls itself.
General case (Recursive case): the
case in a recursive definition in which the method is calling itself
Indirectly recursive: a method that
calls another method and eventually results in the original method call.
Recursive definition: a definition in
which an entity is defined in terms of a smaller version of itself.
Recursive method: a method that calls
itself.
Tail recursive method: a recursive
method in which no statements are executed after the return from the recursive
call
Infinite recursion: the situation in
which a function calls itself over and over endlessly.

References:
[1] Java Programming: From Problem Analysis to Program Design, by D.S. Malik,
Thomson Course Technology, 2008.
[2] Building Java Programs: A Back to Basics Approach, by Stuart Reges, and Marty Stepp, Addison Wesley,
2008.
[3] Data Abstraction and Problem Solving with C++: Walls and Mirrors, third
edition, by Frank M. Corrano, and Janet J. Prichard,
Addison Wesley, 2002.


View more information: http://orion.towson.edu/~izimand/237/LectureNotes/7-Lecture-Recursion.htm

See more articles in category: Grammar
READ:  Joseph Capista III | Towson University

Leave a Reply

Your email address will not be published. Required fields are marked *

Check Also
Close
Back to top button