Sie sind auf Seite 1von 119

Recursion

Objectives
• become familiar with the idea of recursion
• learn to use recursion as a programming tool
• become familiar with the binary search
algorithm as an example of recursion
• become familiar with the merge sort algorithm
as an example of recursion
How do you look up a name in
the phone book?
One Possible Way
Search:
middle page = (first page + last page)/2
Go to middle page;
If (name is on middle page)
done; //this is the base case
else if (name is alphabetically before middle page)
last page = middle page //redefine search area to front half
Search //same process on reduced number of pages
else //name must be after middle page
first page = middle page //redefine search area to back half
Search //same process on reduced number of pages
Overview
Recursion: a definition in terms of itself.

Recursion in algorithms:
• Natural approach to some (not all) problems
• A recursive algorithm uses itself to solve one or more
smaller identical problems

Recursion in C/C++:
• Recursive functions implement recursive algorithms
• A recursive function includes a call to itself
Recursive Functions
Must Eventually Terminate
A recursive function must have
at least one base, or stopping, case.

• A base case does not execute a recursive call


– stops the recursion

• Each successive call to itself must be a "smaller


version of itself”
– an argument that describes a smaller problem
– a base case is eventually reached
Key Components of a Recursive
Algorithm Design
1. What is a smaller identical problem(s)?
 Decomposition

2. How are the answers to smaller problems combined to


form the answer to the larger problem?
 Composition

3. Which is the smallest problem that can be solved easily


(without further decomposition)?
 Base/stopping case
Examples in Recursion
• Usually quite confusing the first time

• Start with some simple examples


– recursive algorithms might not be best

• Later with inherently recursive algorithms


– harder to implement otherwise
Factorial (N!)
• N! = (N-1)! * N [for N > 1]
• 1! = 1
• 3!
= 2! * 3
= (1! * 2) * 3
=1*2*3
• Recursive design:
– Decomposition: (N-1)!
– Composition: * N
– Base case: 1!
factorial Method

int factorial(int n)
{
int fact;
if (n > 1) // recursive case (decomposition)
fact = factorial(n – 1) * n; // composition
else // base case
fact = 1;

return fact;
}
int factorial(int 3)
{
int fact;
if (n > 1)
fact = factorial(2) * 3;
else
fact = 1;
return fact;
}
public static int factorial(int 3)
{
int fact;
if (n > 1)
fact = factorial(2) * 3;
else
fact = 1;
return fact;
}

int factorial(int 2)
{
int fact;
if (n > 1)
fact = factorial(1) * 2;
else
fact = 1;
return fact;
}
public static int factorial(int 3)
{
int fact;
if (n > 1)
fact = factorial(2) * 3;
else
fact = 1;
return fact;
}

public static int factorial(int 2)


{
int fact;
if (n > 1)
fact = factorial(1) * 2;
else
fact = 1;
return fact;
}

int factorial(int 1)
{
int fact;
if (n > 1)
fact = factorial(n - 1) * n;
else
fact = 1;
return fact;
}
int factorial(int 3)
{
int fact;
if (n > 1)
fact = factorial(2) * 3;
else
fact = 1;
return fact;
}

int factorial(int 2)
{
int fact;
if (n > 1)
fact = factorial(1) * 2;
else
fact = 1;
return fact;
}

int factorial(int 1)
{
int fact;
if (n > 1)
fact = factorial(n - 1) * n;
else
fact = 1;
return 1;
}
int factorial(int 3)
{
int fact;
if (n > 1)
fact = factorial(2) * 3;
else
fact = 1;
return fact;
}

int factorial(int 2)
{
int fact;
if (n > 1)
fact = 1 * 2;
else
fact = 1;
return fact;
}

int factorial(int 1)
{
int fact;
if (n > 1)
fact = factorial(n - 1) * n;
else
fact = 1;
return 1;
}
int factorial(int 3)
{
int fact;
if (n > 1)
fact = factorial(2) * 3;
else
fact = 1;
return fact;
}

int factorial(int 2)
{
int fact;
if (n > 1)
fact = 1 * 2;
else
fact = 1;
return 2;
}
int factorial(int 3)
{
int fact;
if (n > 1)
fact = 2 * 3;
else
fact = 1;
return fact;
}

int factorial(int 2)
{
int fact;
if (n > 1)
fact = 1 * 2;
else
fact = 1;
return 2;
}
int factorial(int 3)
{
int fact;
if (n > 1)
fact = 2 * 3;
else
fact = 1;
return 6;
}
int factorial(int n)
{
Execution Trace int fact;
if (n > 1) // recursive case (decomposition)
fact = factorial(n – 1) * n; (composition)
(decomposition) else // base case
fact = 1;
return fact;
}

factorial(4)

factorial(3) 4
int factorial(int n)
{
Execution Trace int fact;
if (n > 1) // recursive case (decomposition)
fact = factorial(n – 1) * n; (composition)
(decomposition) else // base case
fact = 1;
return fact;
}

factorial(4)

factorial(3) 4

factorial(2) 3
int factorial(int n)
{
Execution Trace int fact;
if (n > 1) // recursive case (decomposition)
fact = factorial(n – 1) * n; (composition)
(decomposition) else // base case
fact = 1;
return fact;
}

factorial(4)

factorial(3) 4

factorial(2) 3

factorial(1) 2
int factorial(int n)
{
Execution Trace int fact;
if (n > 1) // recursive case (decomposition)
fact = factorial(n – 1) * n; (composition)
(composition) else // base case
fact = 1;
return fact;
}

factorial(4)
*
factorial(3) 4
*
factorial(2) 3

*
factorial(1)->1 2
int factorial(int n)
{
Execution Trace int fact;
if (n > 1) // recursive case (decomposition)
fact = factorial(n – 1) * n; (composition)
(composition) else // base case
fact = 1;
return fact;
}

factorial(4)
*
factorial(3) 4
*
factorial(2)->2 3
int factorial(int n)
{
Execution Trace int fact;
if (n > 1) // recursive case (decomposition)
fact = factorial(n – 1) * n; (composition)
(composition) else // base case
fact = 1;
return fact;
}

factorial(4)
*
factorial(3)->6 4
int factorial(int n)
{
Execution Trace int fact;
if (n > 1) // recursive case (decomposition)
fact = factorial(n – 1) * n; (composition)
(composition) else // base case
fact = 1;
return fact;
}

factorial(4)->24
Improved factorial Method

int factorial(int n)
{
int fact=1; // base case value

if (n > 1) // recursive case (decomposition)


fact = factorial(n – 1) * n; // composition
// else do nothing; base case

return fact;
}
Fibonacci Numbers
• The Nth Fibonacci number is the sum of the previous
two Fibonacci numbers
• 0, 1, 1, 2, 3, 5, 8, 13, …
• Recursive Design:
– Decomposition & Composition
• fibonacci(n) = fibonacci(n-1) + fibonacci(n-2)
– Base case:
• fibonacci(1) = 0
• fibonacci(2) = 1
fibonacci Method
int fibonacci(int n)
{
int fib;
if (n > 2)
fib = fibonacci(n-1) + fibonacci(n-2);
else if (n == 2)
fib = 1;
else
fib = 0;
return fib;
}
Execution Trace (decomposition)
fibonacci(4)

fibonacci(3) fibonacci(2)
Execution Trace (decomposition)
fibonacci(4)

fibonacci(3) fibonacci(2)

fibonacci(2) fibonacci(1)
Execution Trace (composition)
fibonacci(4)
+

fibonacci(3) fibonacci(2)
+

fibonacci(2)->1 fibonacci(1)->0
Execution Trace (composition)
fibonacci(4)
+

fibonacci(3)->1 fibonacci(2)->1
Execution Trace (composition)
fibonacci(4)->2
Remember:
Key to Successful Recursion
• if-else statement (or some other branching
statement)
• Some branches: recursive call
– "smaller" arguments or solve "smaller"
versions of the same task (decomposition)
– Combine the results (composition) [if
necessary]
• Other branches: no recursive calls
– stopping cases or base cases
Template
… function(…)
{
if ( … )// base case
{
}
else // decomposition & composition
{
}
return … ; // if not void method
}
Template (only one base case)
… function(…)
{
… result = … ;//base case

if ( … ) // not base case


{ //decomposition & composition
result = …
}

return result;
}
What Happens Here?

int factorial(int n)
{
int fact=1;

if (n > 1)
fact = factorial(n) * n;

return fact;
}
What Happens Here?

public static int factorial(int n)


{
return factorial(n – 1) * n;
}
Warning: Infinite Recursion May
Cause a Stack Overflow Error
• Infinite Recursion
– Problem not getting smaller (no/bad decomposition)
– Base case exists, but not reachable (bad base case
and/or decomposition)
– No base case
• Stack: keeps track of recursive calls by OS
– Function begins: add data onto the stack
– Function ends: remove data from the stack
• Recursion never stops; stack eventually runs out of space
– Stack overflow error
Mistakes in recursion
• No composition -> ?
• Bad composition -> ?
Number of Zeros in a Number
• Example: 2030 has 2 zeros
• If n has two or more digits recursive
– the number of zeros is the number of zeros in n with the
last digit removed
– plus an additional 1 if the last digit is zero
• Examples:
– number of zeros in 20030 is number of zeros in 2003
plus 1
– number of zeros in 20031 is number of zeros in 2003
plus 0
numberOfZeros Recursive Design
• numberOfZeros in the number N
• K = number of digits in N
• Decomposition:
– numberOfZeros in the first K - 1 digits
– Last digit
• Composition:
– Add:
• numberOfZeros in the first K - 1digits
• 1 if the last digit is zero
• Base case:
– N has one digit (K = 1)
numberOfZeros method
int numberOfZeros(int n) Which is (are)
{ the base
int zeroCount; case(s)?
if (n==0)
zeroCount = 1; Decompostion
else if (n < 10) // and not 0 ?
zeroCount = 0; // 0 for no zeros
else if (n%10 == 0) Composition?
zeroCount = numberOfZeros(n/10) + 1;
else // n%10 != 0
zeroCount = numberOfZeros(n/10);
return zeroCount;
}
int numberOfZeros(int n)
{
Execution Trace int zeroCount;
if (n==0)
(decomposition) zeroCount = 1;
else if (n < 10) // and not 0
zeroCount = 0; // 0 for no zeros
Each method else if (n%10 == 0)
invocation will zeroCount = numberOfZeros(n/10) + 1;
execute one of the else // n%10 != 0
zeroCount = numberOfZeros(n/10);
if-else cases return zeroCount;
shown at right. }

numberOfZeros(2005)

numberOfZeros(200) 5

numberOfZeros(20) 0

numberOfZeros(2) 0
int numberOfZeros(int n)
{
Execution Trace int zeroCount;
if (n==0)
(composition) zeroCount = 1;
else if (n < 10) // and not 0
zeroCount = 0; // 0 for no zeros
Recursive calls else if (n%10 == 0)
return zeroCount = numberOfZeros(n/10) + 1;
else // n%10 != 0
zeroCount = numberOfZeros(n/10);
return zeroCount;
}

numberOfZeros(2005)->2
+
numberOfZeros(200)->2 5->0
+
numberOfZeros(20)->1 0->1
+
numberOfZeros(2)->0 0->1
Number in English Words
• Process an integer and print out its digits
in words
– Input: 123
– Output: "one two three”
inWords Resursive Design
• inWords prints a number N in English words
• K = number of digits in N
• Decomposition:
– inWords for the first K – 1 digits
– Print the last digit
• Composition:
– Execution order of composed steps [more later]
• Base case:
– N has one digit (K = 1)
inWords method

Base case void inWords(int numeral)


executes {
when only 1 if (numeral < 10)
digit is left printf(“%s ”, digitWord(numeral));
else //numeral has two or more digits
{
Size of problem inWords(numeral/10);
is reduced for printf(“%s ”, digitWord(numeral%10));
each recursive }
call }
Execution Trace (decomposition)
inwords(987)

inwords(98) [print “seven”]

inwords(9) [print “eight”]

[print “nine”]
No output yet, why?
Execution Trace (composition)
inwords(987)

inwords(98) print “seven”

inwords(9) print “eight”

print “nine”
Output: nine eight seven
inWords(987)
if (987 < 10)
1 What Happens
// print digit here
else //two or more digits left with a Recursive
{
inWords(987/10); Call
// print digit here
}

• inWords (slightly simplified) with


argument 987
inWords(987)
if (987 < 10)
// print digit here
Execution
else //two or more digits left
{
Trace
inWords(987/10); The argument is getting
// print digit here shorter and will eventually
} inWords(98) get to the base case.
2 if (98 < 10)
// print digit here
Computation else //two or more digits left
waits here {
until recursive inWords(98/10);
// print digit here
call returns
}

• The if condition is false


• recursive call to inWords, with 987/10
or 98 as the argument
inWords(987)
if (987 < 10)
// print digit here
else //two or more digits left
Execution Trace
{
inWords(987/10);
// print digit here
} inWords(98)
if (98 < 10)
// print digit here
else //two or more digits left
{
inWords(98/10);
// print digit here
} inWords(9)
3 if (9 < 10)
// print digit here
• the if condition is false else //two or more digits left
• another recursive call is {
inWords(numeral/10);
made. // print digit here
}
inWords(987)
if (987 < 10)
// print digit here
else //two or more digits left
Execution Trace
{
inWords(987/10); Output: nine
// print digit here
} inWords(98)
if (98 < 10)
// print digit here
else //two or more digits left
{ 4
inWords(98/10);
// print 98 % 10
} inWords(9)
if (9 < 10)
• if condition is true (base // print nine
case) else //two or more digits left
{
• prints nine and returns inWords(numeral/10);
// print digit here
• no recursive call }
inWords(987)
if (987 < 10)
// print out digit here Execution Trace
else //two or more digits left
{ 5
inWords(987/10);
// print digit here
} inWords(98)
if (98 < 10)
// print out digit here
else //two or more digits left
{
inWords(98/10);
// print out 98 % 10 here
}

Output: nine eight

• executes the next statement after the recursive call


• prints eight and then returns
inWords(987)
if (987 < 10)
// print out digit here 6 Execution Trace
else //two or more digits left
{
inWords(987/10);
// print 987 % 10
6
Output: nine eight seven
}

• executes the next statement after


the recursive method call.
• prints seven and returns
Composition Matters
public static void inWords(int numeral)
{
if (numeral < 10)
printf(“%s ”, digitWord(numeral));
else //numeral has two or more digits
Print before {
making the printf(“%s ”, digitWord(numeral%10) );
recursive call inWords(numeral/10);
}
}

Recursive Design:
1. Print the last digit
2. inWords for the first K – 1 digits
Execution Trace (decomposition)
inwords(987)

print “seven” inwords(98)

Output: seven
Execution Trace (decomposition)
inwords(987)

print “seven” inwords(98)

print “eight” inwords(9)

Output: seven eight


Execution Trace (decomposition)
inwords(987)

print “seven” inwords(98)

print “eight” inwords(9)

print “nine”
Output: seven eight nine
Execution Trace (composition)
inwords(987)

print “seven” inwords(98)

print “eight” inwords(9)

print “nine”
No additional output
"Name in the Phone Book" Revisited
Search:
middle page = (first page + last page)/2
Go to middle page;
If (name is on middle page)
done;//this is the base case
else if (name is alphabetically before middle page)
last page = middle page//redefine to front half
Search//recursive call
else //name must be after middle page
first page = middle page//redefine to back half
Search//recursive call
Binary Search Algorithm
• Searching a list for a particular value
– sequential and binary are two common
algorithms
• Sequential search (aka linear search):
– Not very efficient
– Easy to understand and program
• Binary search:
– more efficient than sequential
– but the list must be sorted first!
Why Is It Called "Binary" Search?
Compare sequential and binary search algorithms:
How many elements are eliminated from the list each
time a value is read from the list and it is not the
"target" value?

Sequential search: only one item


Binary search: half the list!

That is why it is called binary -


each unsuccessful test for the target value
reduces the remaining search list by 1/2.
int search(int target, int first, int last)
Binary Search {
int location = -1; // not found
Method
if (first <= last) // range is not empty
{
• returns the index of the int mid = (first + last)/2;
entry if the target value is
found or -1 if it is not if (target == a[mid])
found location = mid;
• Compare it to the else if (target < a[mid]) // first half
pseudocode for the location = search(target, first, mid - 1);
"name in the phone else //(target > a[mid]) second half
book" problem location = search(target, mid + 1, last);
}

return location;
}
Where is the composition?
• If no items
– not found (-1)
• Else if target is in the middle
– middle location
• Else
– location found by search(first half) or
search(second half)
Binary Search Example

target is 33
The array a looks like this:
Indices 0 1 2 3 4 5 6 7 8 9
Contents 5 7 9 13 32 33 42 54 56 88

mid = (0 + 9) / 2 (which is 4)
33 > a[mid] (that is, 33 > a[4])
So, if 33 is in the array, then 33 is one of:
5 6 7 8 9
33 42 54 56 88

Eliminated half of the remaining elements from


consideration because array elements are sorted.
target is 33
The array a looks like this:
Binary Search Example
Indexes 0 1 2 3 4 5 6 7 8 9
Contents 5 7 9 13 32 33 42 54 56 88

mid = (5 + 9) / 2 (which is 7)
33 < a[mid] (that is, 33 < a[7]) Eliminate
So, if 33 is in the array, then 33 is one of: half of the
5 6 remaining
33 42 elements

mid = (5 + 6) / 2 (which is 5)
33 == a[mid]
So we found 33 at index 5:
5
33
Tips
• Don’t throw away answers (return values)--
need to compose the answers
– Common programming mistake: not capturing
and composing answers (return values)
• Only one return statement at the end
– Easier to keep track of and debug return values
– “One entry, one exit”
Recursive Versus Iterative
Functions
All recursive algorithms/functions
can be rewritten without recursion.

• Iterative functions use loops instead of recursion

• Iterative functions generally run faster and use less


memory--less overhead in keeping track of function
calls
So When Should You Use
Recursion?
• Solutions/algorithms for some problems are
inherently recursive
– iterative implementation could be more
complicated
• When efficiency is less important
– it might make the code easier to understand
• Bottom line is about:
– Algorithm design
– Tradeoff between readability and efficiency
Sometimes recursion has much higher complexity than
iterative methods – Example: Fibonacci numbers.
Recursive Fibonacci method
• This is elegant code, no?

long fib(int n) {
if ( n == 0 ) return 1;
if ( n == 1 ) return 1;
return fib(n-1) + fib(n-2);
}
• Let’s start to trace it for fib(6)
72
Trace of fib(5)
fib(5)

fib(4) fib(3)

fib(3) fib(2) fib(2) fib(1)


1
fib(2) fib(1) fib(1) fib(0) fib(1) fib(0)
1 1 1 1 1
fib(1) fib(0)
1 1
73
Trace of fib(5)
• For fib(5), we call fib(4) and fib(3)
– For fib(4), we call fib(3) and fib(2)
• For fib(3), we call fib(2) and fib(1)
– For fib(2), we call fib(1) and fib(0). Base cases!
– fib(1). Base case!
• For fib(2), we call fib(1) and fib(0). Base cases!
– For fib(3), we call fib(2) and fib(1)
• For fib(2), we call fib(1) and fib(0). Base cases!
• fib(1). Base case!
74
Fibonacci: recursion is a bad choice

• Note that subproblems (like fib(2) ) repeat,


and solved again and again
– We don’t remember that we’ve solved one of our
subproblems before
– For this problem, better to store partial solutions
instead of recalculating values repeatedly
(Dynamic programming)
– Turns out to have exponential time-complexity!

75
Non-recursive Fibonacci
• Two bottom-up iterative solutions:
– Create an array of size n, and fill with values
starting from 1 and going up to n
– Or, have a loop from small values going up, but
• only remember two previous Fibonacci values
• use them to compute the next one
• (See next slide)

76
Iterative Fibonacci
long fib(int n) {
if ( n < 2 ) return 1;
long answer;
long prevFib=1, prev2Fib=1; // fib(0) & fib(1)
for (int k = 2; k <= n; ++k) {
answer = prevFib + prev2Fib;
prev2Fib = prevFib;
prevFib = answer;
}
return answer;
}
77
Merge Sort—
A Recursive Sorting Algorithm
• Example of divide and conquer algorithm
• Recursive design:
– Divides array in half and merge sorts the
halves (decomposition)
– Combines two sorted halves
(composition)
– Array has only one element (base case)
• Harder to implement iteratively
Divide and Conquer
• It is often easier to solve several small instances of
a problem than one large one.
– divide the problem into smaller instances of the same
problem
– solve (conquer) the smaller instances recursively
– combine the solutions to obtain the solution for original
input
– Must be able to solve one or more small inputs directly
• This is an algorithm design strategy
– Computer scientists learn many more of these

79
General Strategy for Div. & Conq.

Solve (an input I)


n = size(I)
if (n <= smallsize)
solution = directlySolve(I);
else
divide I into I1, …, Ik.
for each i in {1, …, k}
Si = solve(Ii);
solution = combine(S1, …, Sk);
return solution;
80
Why Divide and Conquer?
• Sometimes it’s the simplest approach
• Divide and Conquer is often more efficient than
“obvious” approaches
– E.g. BinarySearch vs. Sequential Search
– E.g. Mergesort, Quicksort vs. SelectionSort
• But, not necessarily efficient
– Might be the same or worse than another approach
• We must analyze each algorithm's time
complexity
• Note: divide and conquer may or may not be
implemented recursively

81
Top-Down Strategy
• Divide and Conquer algorithms illustrate a top-
down strategy
– Given a large problem, identify and break into smaller
subproblems
– Solve those, and combine results
• Most recursive algorithms work this way
• The alternative? Bottom-up
– Identify and solve smallest subproblems first
– Combine to get larger solutions until solve entire
problem
82
Execution Trace (decomposition)
3 6 8 2 5 4 7 1

3 6 8 2 5 4 7 1

3 6 8 2 5 4 7 1
3 6 8 2 5 4 7 1
Execution Trace (composition)
1 2 3 4 5 6 7 8

2 3 6 8 1 4 5 7

3 6 2 8 4 5 1 7

3 6 8 2 5 4 7 1
Merging Two Sorted Arrays
2

3 6 2 8
Merging Two Sorted Arrays
2 3

3 6 2 8
Merging Two Sorted Arrays
2 3 6

3 6 2 8
Merging Two Sorted Arrays
2 3 6 8

3 6 2 8
Merge Sort Algorithm
1. If array a has more than one element:
a. Copy the first half of the elements in a to array
front
b. Copy the rest of the elements in a to array tail
c. Merge Sort front
d. Merge Sort tail
e. Merge the elements in front and tail into a
2. Otherwise, do nothing
Merge Sort
void sort(int[] a)
{
do recursive case if
if (a.length >= 2)
{
true, base case if false
int halfLength = a.length / 2;
int[] front = new int[halfLength];
int[] tail = new int[a.length – halfLength];
recursive divide(a, front, tail); make "smaller"
calls sort(front); problems by
sort(tail);
dividing array
merge(a, front, tail);
} Combine the
// else do nothing. two sorted
} arrays
base case: a.length == 1 so
a is sorted and no recursive
call is necessary.
Types of Recursion
• Direct
• Indirect
• Tail
• Linear
• Tree
Direct Recursion
A C function is directly recursive if it contains an
explicit call to itself

Int foo (int x)


{
if ( x <= 0 ) return x;
return foo ( x – 1);
}
Indirect Recursion
A C function foo is indirectly recursive if it contains a
call to another function that ultimately calls foo.

int foo (int x){ int bar (int y)

if (x <= 0) return {
x; return foo (y – 1);
return bar(x); }
}
Tail Recursion
A recursive function is said to be tail
recursive if there is no pending operations
performed on return from a recursive call

Tail recursive functions are often said to


“return the value of the last recursive call as
the value of the function”
factorial is non-tail recursive
int factorial (int n)
{
if (n == 0) return 1;
return n * factorial(n – 1);
}
Note the “pending operation” (namely the
multiplication) to be performed on return
from each recursive call
Tail recursive factorial
int fact_aux (int n, int result) {
if (n == 1) return result;
return fact_aux (n – 1, n * result);
}

factorial (int n) {
return fact_aux (n, 1);
}
Linear Recursion
A recursive function is said to be linearly recursive when no
pending operation invokes another recursive call to the function.

int factorial (int n)


{
if (n == 0) return 1;
return n * factorial ( n – 1);
}
Linear Recursive Count_42s
int count_42s(int array[ ], int n)
{
if (n == 0)
return 0;

if (array[ n-1 ] != 42) {


return (count_42s( array, n-1 ) );
}

return (1 + count_42s( array, n-1) );


}
Tree Recursion
A recursive function is said to be tree recursive (or non-linearly
recursive when the pending operation does invoke another recursive
call to the function.

The Fibonacci function fib provides a classic example of tree


recursion.
Fibonacci Numbers
The Fibonacci numbers can be recursively
defined by the rule:
fib(n) = 0 if n is 0,
= 1 if n is 1,
= fib(n-1) + fib(n-2) otherwise

For example, the first seven Fibonacci numbers are


Fib(0) = 0
Fib(1) = 1
Fib(2) = Fib(1) + Fib(0) = 1
Fib(3) = Fib(2) + Fib(1) = 2
Fib(4) = Fib(3) + Fib(2) = 3
Fib(5) = Fib(4) + Fib(3) = 5
Fib(6) = Fib(5) + Fib(4) = 8
Tree Recursive Fibonacci
// Note how the pending operation (the +)
// needs a 2nd recursive call to fib

int fib(int n)
{
if (n == 0) return 0;

if (n == 1) return 1;

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


}
Tree Recursive Count_42s
int count_42s(int array[ ], int low, int high)
{
if ((low > high)
|| (low == high && array[low] != 42)) {
return 0 ;
}

if (low == high && array[low] == 42) {


return 1;
}

return (count_42s(array, low, (low + high)/2) +


count_42s(array, 1 + (low + high)/2, high)) );
}
Converting Recursive Functions
to Tail Recursion
A non-tail recursive function can often be converted to
a tail recursive function by means of an “auxiliary”
parameter that is used to form the result.
The idea is to incorporate the pending operation into
the auxiliary parameter in such a way that the recursive
call no longer has a pending operations (is tail
recursive)
Tail Recursive Fibonacci
int fib_aux ( int n, int next, int result )
{
if ( n == 0 ) return result;
return ( fib_aux ( n - 1, next + result, next ));
}

int fib ( int n )


{
return fib_aux ( n, 1, 0 );
}
Converting Tail Recursive
Functions to Iterative
Let’s assume that any tail recursive function
can be expressed in the general form

F(x){
if ( P( x ) ) return G( x );
return F ( H( x ) );
}
Tail Recursive to Iterative
(cont’d)
P( x ) is the base case of F(x)
If P(x) is true, then the value of F(x) is
the value of some other function, G(x).

If P(x) is false, then the value of F(x) is


the value of the function F on some other
value H(x).
Tail Recursive to Iterative
(cont’d)
F( x ) {
int temp_x;
while ( P(x) is not true ) {
temp_x = x;
x = H (temp_x);
}
return G ( x );
Example – tail recursive factorial
int fact_aux (int n, int result)
{
if (n == 1) return result;
return fact_aux (n – 1, n * result)
}
F = fact_aux
x is really two params – n and result
value of P(n, result) is value of (n == 1)
value of G(n, result) is result
value of H(n, result) is (n – 1, n * result)
Iterative factorial
int fact_aux (int n, int result) {
int temp_n, temp_result;

while ( n != 1 ) { // while P(x) not true


temp_n = n; // temp_x = x (1)
temp_result = result; // temp_x = x (2)
n = temp_n – 1; // H (temp_x) (1)
result = temp_n * temp_result; // H (temp_x) (2)
}
return result; // G (x)
}
Converting Iteration to Tail
Recursion

Just like it’s possible to convert tail-


recursion into iteration, it’s possible to
convert any iterative loop into the
combination of a recursive function and a
call to that function
A while-loop example
The following code fragment uses a while loop to
read values into an integer array until EOF occurs,
counting the number of integers read.

nt nums [MAX];
int count = 0;

while (scanf (“%d”, &nums[count]) != EOF)


count++;
While loop example (cont’d)
The key to implementing the loop using tail recursion is to
determine which variables capture the “state” of the
computation and use these variables as parameters to the tail
recursive subprogram.

count – the number of integers read so far


nums -- the array that stores the integers
While loop example (cont’d)
Since we are interested in getting the final value for
count, we will write a function that returns count as its
result.
If the termination condition (EOF) is not true, the
function increments count, reads a number into nums
and calls itself recursively to to input the remainder of
the numbers.
If the termination condition is true, the function simply
returns the final value of count.
While loop example (cont’d)
int read_nums (int count, int nums[ ] )
{
if (scanf (“%d”, &nums[count] != EOF))
return read_nums (count + 1, nums);

return count;
}
While loop example (cont’d)
To call the function, we must pass in the appropriate
initial values

count = read_nums (0, nums);


Converting Iteration to
Tail recursion
• Determining the variables used by the
loop
• Writing tail-recursive function with one
parameter for each variable
• Using the loop-termination condition as
the base case
Converting Iteration to
Tail recursion (cont’d)
•Determining how each variable changes
from one iteration to the next and handing
the sequence of expressions that represent
those changes to the recursive call
• Replacing the iterative loop with a call to
the tail recursive function with appropriate
initial values
An exercise for the Student
Convert the following for loop that finds the maximum value in
an array to be tail recursive. Assume that the function max( )
returns the larger of the two parameters returned to it.

int nums[MAX]; // filled by some other function


int max_num = nums[ 0 ];
for (j = 0; j < MAX; j++)
max_num = max (max_num, nums[ j ] );
Summary
• Recursive call: a method that calls itself
• Powerful for algorithm design at times
• Recursive algorithm design:
• Decomposition (smaller identical problems)
• Composition (combine results)
• Base case(s) (smallest problem, no recursive calls)
• Implementation
– Conditional (e.g. if) statements to separate different cases
– Avoid infinite recursion
• Problem is getting smaller (decomposition)
• Base case exists and reachable
– Composition could be tricky
• Different types of recursion.

Das könnte Ihnen auch gefallen