When confronted with a new problem there are two questions you should ask: 1. Is this problem like,...

Post on 24-Dec-2015

213 views 0 download

Transcript of When confronted with a new problem there are two questions you should ask: 1. Is this problem like,...

When confronted with a new problem there are two questions you should ask:

 

•1. Is this problem like, or a special case of, a problem that I already know how to solve?

•2.Is this a problem of size n in one of its variables that I could solve if I knew the solution to an instance of the same problem (or instances of all such problems) of size less than n?

 

Recursion

Recursion

When the answer to the second question is “yes”, you will employ recursion.

There are three steps in formulating a recursive solution to a problem.

1. Formulate the solution (to a problem of size nn) in terms of the solution to the same problem of size less than nn.

2. Determine a “base case” (at nn = 0) where the solution to the problem is trivial.

3. Terminate recursion when this “base case” value is reached.

Recursion “rewards” procrastination by formulating the solution of a problem as a sequence of solutions to similar problems, and postponing the actual solution of any of these problems until they are trivial.

Recursion – Contents of Presentation

1. Recursion as a problem solving technique

2. Towers of Hanoi – An example of a recursive approach to problem solving.

3. Recursive Algorithms – The Good, the Bad, and the Ugly!

a) The “Good” – Raising a base to a power.

b) The “Bad” – Calculating N Factorial.

c) The “Ugly” – The Fibonacci Sequence

4. Binary Search

5. Recursively Defined Grammars – Finding Palindromes

Example: Towers of Hanoi puzzle

In this puzzle, the player begins with n disks of decreasing diameter placed one on top of the other on one of three pegs of the game board. The player must move the disks from peg to peg, using each of the three pegs, until the entire tower is moved from the starting peg to one of the others. The only rule governing the movement of the disks is that in each move a disk of larger diameter must never be placed on top of one of smaller diameter

Towers of Hanoi Puzzle

Towers of Hanoi Puzzle

Towers of Hanoi Puzzle

Towers of Hanoi Puzzle

Towers of Hanoi Puzzle

Towers of Hanoi Puzzle

Towers of Hanoi Puzzle

Towers of Hanoi Puzzle

A solution to the problem of moving a tower of size n from the source peg to the destination peg using a spare peg is found by moving a tower of size n – 1 from the source peg to the spare peg, moving the bottom disk from the source peg to the destination peg, and finally moving a tower of size n – 1 from the spare peg to the destination peg.

public class TowersOfHanoi {

private int num_disks;

private int source, spare, dest; //pegs

public TowersOfHanoi (int n_disks) {//see next slide}

//pre-condition: n_disks > 0

//exception: error message sent and program terminates

public void moveTower(int disks, int from_peg, int to_peg, int use_peg) {…}

public void moveDisk(int num, int from_peg, int to_peg) {…}

};

Class TowersOfHanoi -- header file

Class TowersOfHanoi -- implementation

Class Constructor

public TowersOfHanoi(int n_disks) {

if ( n_disks < 1) {

System.err.println( “error – must start with 1 or more disks in a tower”;

exit(1);

}

num_disks = n_disks;

source = 1; spare = 2; dest = 3;

moveTower(num_disks, source, dest, spare);

}

Class TowersOfHanoi -- implementation

moveTower

public void moveTower(int disks, int from_peg, int to_peg, int use_peg) {

if (disks == 1)

moveDisk(disks, from_peg, to_peg);

else {

moveTower(disks – 1, from_peg, use_peg, to_peg);

moveDisk(disks, from_peg, to_peg);

moveTower(disks –1, use_peg, to_peg, from_peg);

}

}

moveDisk

public void moveDisk(int num, int from_peg, int to_peg) {

System.out.print( “moving disk number ” + num + “ from peg”) ;

System.out.println(from_peg + “ to peg ” + to_peg );

}

Class TowersOfHanoi -- implementation

public void moveTower(int disks, int from_peg, int to_peg, int use_peg) {

if (disks == 1)

moveDisk(disks, from_peg, to_peg);

else

moveTower(disks-1, from_peg, use_peg, to_peg);

moveDisk(disks, from_peg, to_peg);

moveTower(disks-1, use_peg, to_peg, from_peg);

}

1

1

2

Disk source target

2

3

moveTower(3,1,3,2);1

1

moveTower(2,1,2,3);2

moveTower(1,1,3,2);3

3

1 1 3

2

2 1 2

2

moveTower(1,3,2,1);3

3

3

1 3 2

1

3 1 3

1

2

moveTower(2,2,3,1);2

2

moveTower(1,2,1,3);33

3

1 2 1

2

2 2 3

2

3moveTower(1,1,3,2);3

3

1 1 3Stack of act. Recs.

screen

Illustration of moveTower operation

Calls to moveTower

Recursively Defined Functions

Exponentiation --”The Good”

The Good, the bad, and the ugly

  1 if n = 0

1/ power(b, |n|) if n < 0

power (b, n) = b * power (b, n-1) if n > 0 and odd

power(b, n/2) * power(b,n/2)if n > 0 and even

public double power (double b, int n) {

//start all recursive functions with a test for the base case

//test to terminate recursion)

if ( n == 0) return 1;

if (n < 0) return 1/power(b, -n);

if ((n % 2) == 1) return b * power(b, n-1);

else { // n even and positive

double temp = power(b, n/2);

return temp * temp;

}

}

Recursive power function

The recursive algorithm completes after at most 2*lg(n) calls

Recursively Defined Functions

1 if n = 0

n! =

n * (n – 1) ! for all n > 0

 

Figure 2. The factorial function

 

The “bad”

Recursively Defined Functions

The Factorial Function

public long factorial (int n) {

//precondition: n >= 0

if (n < 0) {

System.out.println( “error – factorial function requires non-negative argument\n”;

exit(1);

}

if (n == 0)

return 1;

else

return n * factorial (n – 1);

}

Requires n time (and space) consuming function calls

Recursively Defined Functions

Tail Recursion

public void reverse_write(const char [ ] A, int size) {

if (size > 0) {

System.out.print( A[size – 1]);

reverse_write (A, size – 1);

}

}

The recursive factorial algorithm uses recursion as the last statement in the function, only to set the parameters for the next round of processing. This is better done by iteration! Consider this example.

while(size > 0)

size--;

public void iterative_reverse_write(const char A[ ], int size) {

while (size > 0) {

System.out.println( A[size – 1]);

size--;

}

}

Recursively Defined Functions

Eliminating Tail Recusion

Recursively Defined Functions

public long iterative_factorial (int n) {

//precondition: n >= 0

if (n < 0) {

System.out.println( “error – factorial function requires non-negative argument”);

exit(1);

}

long prod = n;

while ( n > 1) {

n--;

prod *= n;

}

return prod;

}

Non-recursive factorial function

n iterations, but much faster and uses less memory than n recursive calls!

Recursively Defined Functions

The “ugly” – the fibonacci function

0 if n = 0

fibon (n) = 1 if n = 1

finbon(n-1) + fibon(n-2) if n > 1

Recursive fibonacci function

public long fibon (long int n) {

//precondition n >= 0;

//exception: if n send an error message and terminate

if ( n < 0 ) {

System.out.println( “error – fibonacci numbers not defined over negative numbers”);

exit (1);

}

if (n = 0) return 0; //base cases

if (n = 1) return 1;

return fibon(n-1) + fibon(n-2);

}

The fibonacci function

The run-time behavior of this recursive algorithm becomes exponential because the algorithm is “senile”. It “forgets” the value it obtained on a previous call and has to (recursively) recalculate the same value again.

fibon(6)

fibon(5)

fibon(4)

fibon(3)

fibon(2)

fibon(1)

1

+ fibon(0)

0

1

fibon(1)+

2

1

fibon(2)+

fibon(1)1

+ fibon(0)0

1

3+ fibon(3)

fibon(2)

fibon(1)1

+ fibon((0)

0

1+ fibon(1)

1

2

5+ fibon(4)

fibon(3)

fibon(2)

fibon(1)1

+ fibon(0)

0

1+ fibon(1)

1

2+ fibon(2)

fibon(1)1

+ fibon(0)0

1

3

8

An iterative fibonacci functionpublic long iterative_fibon(long int n) {

//precondition n >= 0;

//exception: if n send an error message and terminate

if ( n < 0 ) {

System.out.println( “error – fibonacci numbers not defined over negative numbers”);

exit (1);

}

if (n = 0) return 0; //base cases

if (n = 1) return 1;

long int term1 = 1, term2 = 0, hold;

for (int i = 2; i <= n; i++) {

hold = term1;

term1 += term2;

term2 = hold;

}

return term1;

}

Requires only 2 memory locations and n iterations!

Divide and Conquer

Binary Search

public int binarySearch(int [ ] A, int first, int last, int key) {..}

//preconditions: list A is ordered in increasing order (not checked)

// last > first (sub-list is not empty)

//post conditions: list A is unchanged, if key occurs in the list the index

// of (one of) the location where it occurs is returned, else

// a dummy value of –1 is returned

//exception: if first >= last send error message and terminate

Initial State of List A

State of List A during the first call to binary_search

State of List A during the third call to

binary_search

public int binarySearch(int [ ] A, int first, int last, int key) {

//check for exception

if (first >= last) {

System.out.println( “error – list to be searched is empty”);

exit(1);

} //check for termination of recursion

if ((last – first) == 1)

if (A[first] == key)

return first;

else

return –1;

int med = (first + last) / 2; //integer division

if (key < A[med] )

binarySearch (A, first, med, key);

else

binarySearch (A, med, last, key);

}

Recursively Defined Grammars

Palindrome

L = {w | w is a string of alphabetic characters that is the same read backwards or forwards}

 

and the BNF grammar G is:

 <palindrome> ::= <empty_string> | <alphabetic> | “a” <palindrome> “a” |…| < “Z” <palindrome> “Z”

<alphabetic> ::= “a” | … | “z” | “A” | … | “Z”

<empty_string> ::= “”

public boolean isPalindrome(String s) { if ( s == NULL)

return TRUE; if (s.length == 1)

return TRUE; int len = s.length; return boolean (s.charAt(0) == s.charAt([len-1])) &&

isPalindrome(s.substring(1,len-2));}

Test for a palindrome