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.
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.
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.
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)