Tuesday, October 20, 2009

6.19. Recursion



6.19. Recursion


The programs we have discussed are
generally structured as functions that call one another in a disciplined,
hierarchical manner. For some problems, it is useful to have functions call themselves. A recursive function is a
function that calls itself, either directly, or indirectly (through another
function). [Note:
Although many compilers allow function main to
call itself, Section 3.6.1, paragraph 3, and Section 5.2.2, paragraph 9, of the
C++ standard document indicate that main
should not be called within a program or recursively. Its sole purpose is to be
the starting point for program execution.]This section and the next present
simple examples of recursion.


We first consider recursion
conceptually, then examine two programs containing recursive functions.
Recursive problem-solving approaches have a number of elements in common. A
recursive function is called to solve a problem. The function actually knows how
to solve only the simplest case(s), or so-called base case(s). If the
function is called with a base case, the function simply returns a result. If
the function is called with a more complex problem, it typically divides the
problem into two conceptual pieces—a piece that the function knows how to do and
a piece that it does not know how to do. To make recursion feasible, the latter
piece must resemble the original problem, but be a slightly simpler or slightly
smaller version. This new problem looks like the original problem, so the
function launches (calls) a fresh copy of itself to work on the smaller
problem—this is referred to as a recursive
call
and is also called the recursion step. The recursion step often includes
the keyword return, because its result will
be combined with the portion of the problem the function knew how to solve to
form a result that will be passed back to the original caller, possibly
main.


The recursion step executes while
the original call to the function is still "open," i.e., it has not yet finished
executing. The recursion step can result in many more such recursive calls, as
the function keeps dividing each new subproblem with which the function is
called into two conceptual pieces. In order for the recursion to eventually
terminate, each time the function calls itself with a slightly simpler version
of the original problem, this sequence of smaller and smaller problems must
eventually converge on the base case. At that point, the function recognizes the
base case and returns a result to the previous copy of the function, and a
sequence of returns ensues all the way up the line until the original function
call eventually returns the final result to main. All of this sounds quite exotic compared to the kind
of "conventional" problem solving we have been using to this point. As an
example of these concepts at work, let us write a recursive program to perform a
popular mathematical calculation.


The factorial of a nonnegative
integer n, written n! (and pronounced "n
factorial"), is the product




n · (n – 1) · (n – 2) · ... ·
1



with 1! equal to 1, and 0! defined to be 1.
For example, 5! is the product 5 · 4 · 3 · 2 · 1, which is equal to 120.


The factorial of an integer,
number, greater than or equal to 0, can be calculated iteratively (nonrecursively) by using a
for statement as follows:


factorial = 1;

for ( int counter = number; counter >= 1; counter-- )
factorial *= counter;


A recursive definition of the
factorial function is arrived at by observing the following algebraic
relationship:




n! = n · (n
– 1)!



For example, 5! is clearly equal to 5 *
4! as is shown by the following:


5! = 5 ·  4 · 3 · 2 · 1
5! = 5 · (4 · 3 · 2 · 1)
5! = 5 · (4!)


The evaluation of 5! would proceed as
shown in Fig. 6.27.
Figure 6.27(a) shows how the succession of recursive calls proceeds
until 1! is evaluated to be 1, which terminates the recursion. Figure 6.27(b) shows the values returned from each recursive call to
its caller until the final value is calculated and returned.




Fig. 6.27. Recursive evaluation of 5!.






The program of Fig. 6.28
uses recursion to calculate and print the factorials of the integers 0–10. (The
choice of the data type unsigned long
is explained momentarily.) The recursive function factorial (lines 23–29) first determines whether the terminating
condition number <= 1 (line 25) is true. If number is less than or equal to 1, the factorial function returns 1 (line 26), no further recursion is necessary and
the function terminates. If number is greater than
1, line 28 expresses the problem as the product of number and a recursive call to factorial evaluating the factorial
of number - 1. Note that factorial( number-1) is a slightly
simpler problem than the original calculation factorial( number
)
.













Fig. 6.28. Demonstrating the recursive function
factorial.


 

 1   // Fig. 6.28: fig06_28.cpp
2 // Demonstrating the recursive function factorial.
3 #include <iostream>
4 using std::cout;
5 using std::endl;
6
7 #include <iomanip>
8 using std::setw;
9
10 unsigned long factorial( unsigned long ); // function prototype
11
12 int main()
13 {
14 // calculate the factorials of 0 through 10
15 for ( int counter = 0; counter <= 10; counter++ )
16 cout << setw( 2 ) << counter << "! = " << factorial( counter )
17 << endl;
18
19 return 0; // indicates successful termination
20 } // end main
21
22 // recursive definition of function factorial
23 unsigned long factorial( unsigned long number )
24 {
25 if ( number <= 1 ) // test for base case
26 return 1; // base cases: 0! = 1 and 1! = 1
27 else // recursion step
28 return number * factorial( number - 1 );
29 } // end function factorial



 0! = 1
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880
10! = 3628800



Function factorial has been declared to receive a
parameter of type unsigned long and return a result of type
unsigned long. This is shorthand notation for unsigned long int. The C++ standard requires that a variable of type
unsigned long int be at least as big as an int. Typically, an
unsigned long int is stored in at least four
bytes (32 bits); such a variable can hold a value in the range 0 to at least
4294967295. (The data type long int is also stored in at least four bytes and can hold a value at
least in the range –2147483648 to 2147483647.) As can be seen in Fig. 6.28, factorial values become large quickly. We chose the
data type unsigned long so that the program
can calculate factorials greater than 7! on computers with small (such as
two-byte) integers. Unfortunately, the function factorial produces large values so quickly that even unsigned
long
does not help us compute many factorial values
before even the size of an unsigned long variable is exceeded.


We could use
variables of data type double to calculate
factorials of larger numbers. This points to a weakness in most programming
languages, namely, that the languages are not easily extended to handle the
unique requirements of various applications. As we'll see when we discuss
object-oriented programming in more depth, C++ is an extensible language that
allows us to create classes that can represent arbitrarily large integers if we
wish. Such classes already are available in popular class libraries.[1]



[1]
Such classes can be found at shoup.net/ntl, cliodhna.cop.uop.edu/~hetrick/c-sources.html and www.trumphurst.com/cpplibs/datapage.phtml?category='intro'.



Common Programming Error 6.24








Either omitting
the base case, or writing the recursion step incorrectly so that it does not
converge on the base case, causes "infinite" recursion, eventually exhausting
memory. This is analogous to the problem of an infinite loop in an iterative
(nonrecursive)
solution.





 


No comments:

Post a Comment