chapter 6 recursion. solving simple problems iteration can be replaced by a recursive function...

60
Chapter 6 Chapter 6 Recursion

Upload: melanie-shepherd

Post on 19-Jan-2016

254 views

Category:

Documents


0 download

TRANSCRIPT

Chapter 6Chapter 6

Recursion

Solving simple problemsSolving simple problems

Iteration can be replaced by a recursive function

Recursion is the process of a function calling itself

Computing 2^5th without recursionComputing 2^5th without recursion

#include <iostream.h>

int twoRaisedTo0() { return 1; }

int twoRaisedTo1() { return 2 * twoRaisedTo0(); }

int twoRaisedTo2() { return 2 * twoRaisedTo1(); }

int twoRaisedTo3() { return 2 * twoRaisedTo2(); }

int twoRaisedTo4() { return 2 * twoRaisedTo3(); }

int twoRaisedTo5() { return 2 * twoRaisedTo4(); }

Main programMain program

int main()

{

cout << "2 to the 5th is " << twoRaisedTo5();

cout << endl;

return 0;

}

Recursively computing powers of 2Recursively computing powers of 2

#include <iostream.h>

int twoRaisedTo(int n)

{

if (n == 0) // special case

return 1;

else

return 2 * twoRaisedTo(n-1);

}

Main programMain program

int main()

{

cout << "2 to the 5th is " << twoRaisedTo(5); cout << endl;

return 0;

}

The Nature of RecursionThe Nature of Recursion

Solving a problem by first solving a smaller version of it

Recursive solutions have two parts– A recursive call– A recursive stop

The function calls itself until the stop condition is hit, then it returns back through the recursive calls.

Powers of 2 equationPowers of 2 equation

When n >= 1

When n = 0

1

)1(*2)(

nfnf

Recursive substitutionRecursive substitution

It is easy to turn a recursive definition into a recursive program

Keep substituting values until you get an expression that can be evaluated without recursion

Recursion back to the stop caseRecursion back to the stop case

1)0(

)0(*2)1(

)1(*2)2(

)2(*2)3(

)3(*2)4(

)4(*2)5(

f

ff

ff

ff

ff

ff

Back substitutionBack substitution

Once the recursive stop has been located you can use the value obtained and return to the previous definition.

You then repeatedly work your way back to the place where you started

This is called ‘back substitution’

Substitution detailsSubstitution details

1)0(

21*2)0(*2)1(

42*2)1(*2)2(

84*2)2(*2)3(

168*2)3(*2)4(

3216*2)4(*2)5(

f

ff

ff

ff

ff

ff

Factorial problemFactorial problem

This is a classic problem demonstrating the use of recursion

The only problem comes when you try to implement it with ints or long integers (a small integer may generate a factorial that exceeds the representational capacity of the machine.

Equation 6-5Equation 6-5

Equation 6-6

1*...*)2(*)1(*! nnnn

1

)1(*)(

nfnnf

When n >= 1

When n = 0

Factorial functionFactorial function

int factorial(int n){ // precondition: n is not negative

if (n == 0)return 1;

elsereturn (n*factorial(n-1);

// postcondition: n is not changed // returns: n! // limitations: INT_MAX}

Combinatorial problemCombinatorial problem

How many different one hour shows can be produced from 40 records if you can only play 10 songs in an hour?

How many combinations of 40 can be arrived at choosing 10 at a time?

“n choose k” problem

Factorial solutionFactorial solution

n choose k = n!/(k!(n-k)!)This problem has a recursive

solution that employs our recursive factorial function (nested recursion)

This function has two recursive stops

Another solutionAnother solution

We can also come up with a recursive version of the ‘n choose k’ problem on our own, without factorial.

Equation 6-11Equation 6-11

when k = 1

when n = k

when n > k and k > 1

),1()1,1(),( knckncknc

Equation 6-12Equation 6-12

),1()1,1(

1),(

kncknc

n

knc

n choose k functionn choose k function

int choose(int n, int k)

{

if (k == 1)

return n;

else if (n == k)

return 1;

else // recursive case: n>k and k>1

return choose(n - 1, k - 1) + choose(n - 1, k);

}

Recursion in searching and Recursion in searching and sortingsortingAny iterative process (a loop) that

terminates can be redefined recursively

Iterative processes are used in both searching and sorting routines

A recursive linear searchA recursive linear search

Given an array ‘a’, indexed from 0 to n-1Function search is called like this:search(a,n-1,target) where the value n-1

becomes the value for n in the functionHere is it’s recursive algorithm

– If a[n-1] == target return n-1– else– return the result of search(a,n-1,target)

a[0] through a[n-2]

a[n-1]

use recursive call linearSearch(a, n-1, target) for this part

Visualization of recursive Linear SearchVisualization of recursive Linear Search

Recursive Linear SearchRecursive Linear Search

int linearSearch(int a[], int n, int target)

{ // Recursive version of linear search

// Precondition: a is an indexed from 0 to n-1

if (n < 0) // an empty list is specified

return -1;

else {

Recursive linear search (con’t)Recursive linear search (con’t)

if (a[n-1] == target) // test final position

return n-1;

else // search the rest of the list recursively

return linearSearch(a, n-1, target);

}

// Postcondition: If a value between 0 and n-1 is returned,

// a[returnValue] == target;

// Otherwise, if -1 is returned, target is not in a

}

Recursive binary searchRecursive binary search

Preconditions:

a is an array sorted in ascending order,

first is the index of the first element to search,

last is the index of the last element to search,

target is the item to search for.

Recursive binary searchRecursive binary search

The repetitive process involves dividing the search domain in half

The recursive stop happens when the target value equals the middle value of the current list, or when you run out of places to look

Algorithm for binary searchAlgorithm for binary search

If first > last return -1 (target not found)If a[mid] == target return midelse if target < a[mid] bsearch(a,first,mid-1, target)else bsearch(a, mid+1, last, target)

Binary search (con’t)Binary search (con’t)

If first > last

return failure

mid = (first+last)/2

if a[mid] is equal to target

return mid

else if target < a[mid]

return result of recursive search of a from first up to mid-1

else

return result of recursive search of a from mid+1 up to last

Recursive binary search codeRecursive binary search code

int binarySearch(int a[], int first, int last, int target)

{

// Preconditions: a is an array sorted in ascending order,

// first is the index of the first element to search,

// last is the index of the last element to search, // target is the item to search for.

Binary search codeBinary search code

if (first > last)

return -1; // -1 indicates failure of search

int mid = (first+last)/2;

if (a[mid] == target)

return mid;

else if (target < a[mid])

return binarySearch(a, first, mid-1, target);

else // target must be > a[mid]

return binarySearch(a, mid+1, last, target);

// Postcondition: Value returned is position of target in a,

// otherwise -1 is returned

}

CorrectnessCorrectness

Proof of correctness was done with loop invariants for non-recursive loops

Recursive algorithms are proved by induction– Does the program work for the base case?– Does the program work for each case smaller

than the current n?If both are true we assume it has been

proved correct.

Recursive quicksortRecursive quicksort

Quicksort was published in 1962 by British mathematician C.A.R. Hoare

Basic idea:– Pick one item from an array (the pivot)– Reorganize the array so that smaller things are

on one side and larger things on the other (partitioning).

– Recursively select pivots for each half of the list, partition, select pivots, partition, etc.

Quicksort: The array after one partitionQuicksort: The array after one partition

partition 1: all items <= pivot partition 2: all items > pivotpivot

Quicksort codeQuicksort code

int partition(int a[], int first, int last);

void quicksort(int a[], int first, int last)

{

// precondition: a is an array;

// The portion to be sorted runs from // index first to index last inclusive.

if (first >= last) // Base Case -- nothing to sort, so return

return;

Code ExampleCode Example

// Otherwise, we’re in the recursive case.

// The partition function uses the item in a[first] as the pivot

// and returns the position of the pivot -- split -- after the partition.

int split(partition(a, first, last));

// Recursively, sort the two partitions.

quicksort(a, first, split-1);

quicksort(a, split+1, last);

// postcondition: a is sorted in ascending order

// between first and last inclusive.

}

First attempt at a loop invariant for partitionFirst attempt at a loop invariant for partition

partition 1: all items <= pivot partition 2: all items > pivot

first split last

items yet to be processed

14 9 22 11 4 8 27 41 56 31 33 101 66 14 53 99 11 2 24 87 33 47 22

The initial value ‘27’ is moving right as array elements areprocessed. This is unnecessarily complicated.A better way would be to keep it in position 0 until all elementshave been moved, then swap it with the last small one.

Better loop invariant for partitionBetter loop invariant for partition

partition 1: all items <= pivot partition 2: all items > pivot

first lastSmall i

items yet to be processed

27 14 9 22 11 4 8 41 56 31 33 101 66 53 99 11 2 24 87 33 47 2214

14 is less than 27, so lastSmall increments by one to point to 41and then 14 and 41 are switched. Then i moves on to the value 53.

27 14 9 22 11 4 8 56 31 33 101 66 41 99 11 2 24 87 33 47 225314

Getting the pivot into its proper locationGetting the pivot into its proper location

first lastSmall

Exchange a[first] with a[lastSmall]

Partition functionPartition function

void swapElements(int a[], int first, int last);

int partition(int a[], int first, int last)

{ int lastSmall(first), i;

for (i=first+1; i <= last; i++)

// loop invariant: a[first+1]...a[lastSmall] <= a[first] &&

// a[lastSmall+1]...a[i-1] > a[first]

if (a[i] <= a[first]) { // key comparison

++lastSmall;

swapElements(a, lastSmall, i);

}

Partition continuedPartition continued

// put pivot into correct position

swapElements(a, first, lastSmall);

// postcondition: a[first]...a[lastSmall-1] <= a[lastSmall] &&

// a[lastSmall+1]...a[last] > a[lastSmall]

return lastSmall; // this is the final position of the pivot -- the split index

}

Example recursion tree for QuicksortExample recursion tree for Quicksort[67, 58, 38, 81, 90, 57, 54]

[54, 58, 38, 57, 67, 81, 90]

[54, 58, 38, 57] [81, 90]

[38, 54, 58, 57] [81, 90]

[58, 57][38] [] [90]

[57, 58]

[] [58]

A worst case recursion tree for QuicksortA worst case recursion tree for Quicksort

[1,2,3,4,5,6,7]

[] [2,3,4,5,6,7]

[] [3,4,5,6,7]

[] [4,5,6,7]

[5,6,7]

[] [6,7]

[]

[] [7]

Efficiency of quicksort’s worst caseEfficiency of quicksort’s worst case

)(2

)1(1...)2()1( 2

1

1nO

nninn

n

n

Quicksort versus bubble sortQuicksort versus bubble sort

23 8 9 2824 16 18 12025 32 53 49626 64 148 2,01627 128 448 8,12828 256 853 32,64029 512 2,200 130,816210 1,024 4,942 523,776211 2,048 13,656 2,096,128212 4,096 26,854 8,386,560213 8,192 68,957 33,550,336214 16,384 123,080 134,209,536

n(as power of 2) n Quicksort Bubble Sort

Comparisionsa Comparisons

A best case recursion tree for QuicksortA best case recursion tree for Quicksort

[4, 1, 3, 2, 6, 5, 7]

[2, 1, 3, 4, 6, 5, 7]

[2, 1, 3] [6, 5, 7]

[1, 2, 3] [5, 6, 7]

[3][1] [7][5]

the number of items to sort at each levelthe number of items to sort at each level

?,...8

,4

,2

,nnn

n

The number of levels (I) is no more than log(base 2)nThe number of levels (I) is no more than log(base 2)n

in

in

n

ni

n

1log

1log

2loglog

2

2

2

122

1

A uniformly distributed pivot splits near the middle A uniformly distributed pivot splits near the middle half the timehalf the time

about half the time, the pivot falls in the shaded area around the middle

n3n 4

n 2

n 4

0

How is recursion implemented?How is recursion implemented?

When a function calls itself

– The original stops executing and it’s values are stored on the ‘data stack’

– Eventually, the recursive call will return and at that time the values in storage are popped off the stack and the function picks up processing

ExampleExample

Given a function b(int x) that contains a call to another function a(int z) within it...

Function b must be temporarily suspended until a finishes.

This means that b, and the values of its variables must be stored on the stack temporarily.

Example, (continued)Example, (continued)

When b goes on the stack, then the execution of a can begin with any parameters from b bound to it.

When a is finished it returns a value to b

At the point of return, b and its variables are popped off of the stack and execution of b continues.

Example continuedExample continued

The next slide is an example of a function call of this nature.

Nested function callsNested function calls

int a(int x)

{ return x+x; }

int b(int x)

{ int z(x + 3);

int y(a(z)); // ***

z = x * y;

return z;

}

Recursion?Recursion?

Recursive calls are like any other function call.

They require the same binding of data values and the same suspension of the parent function through storage of its parameters on the stack

Recursion therefore comes at a cost.

Recursive function callsRecursive function calls

int f(int x)

{ int y;

if (x == 0)

return 1;

else {

y = 2 * f(x-1);

return y + 1;

}

}

Execution of Code Example for Execution of Code Example for f(3)f(3)

x = 3 y = ? call f(2)

y = 2 * 7 = 14 return y + 1 = 15

x = 2 y = ? call f(1)

y = 2 * 3 = 6 return y + 1 = 7

x = 1 y = ? call f(0)

y = 2 * 1 = 2 return y + 1 = 3

x = 0 y = ? return 1

copy of f

copy of f

copy of f

copy of f

value returned by call is 15

Recursion and RAMRecursion and RAM

Recursive functions may require more space in memory than non-recursive calls.

With some deeply recursive algorithms this can be a problem.

It can also be a problem with poorly written recursive functions with bad base case definitions (result is like an infinite loop only on the stack)

Advantages of recursionAdvantages of recursion

Recursion’s big advantage is the elegance with which it embodies the solution to many problems.

It closely follows the mathematical definition of many functions

This makes it easier to prove correctness.

Advantages of recursion (con’t)Advantages of recursion (con’t)

Some problems are much easier to solve recursively than iteratively

(Towers of Hanoi)

Disadvantages of recursionDisadvantages of recursion

It’s use of system resources slows the process down compared to iterative solutions– This may not be a big factor on modern

machines however (author’s note: 2% difference in speed)

Difficult to ‘think recursively’