Pointers on C——7 Functions.7

来源:互联网 发布:部落冲突女皇升级数据 编辑:程序博客网 时间:2024/06/15 01:21

7.5.2 Recursion versus Iteration

递归与迭代

Recursion is a powerful technique, but like any technique it can be misused. Here is an example. The definition of a factorial is often stated recursively, like this:

递归是种强有力的技巧,但和其他技巧样,它也可能被误用这里就有个例子。阶乘的定义往往就是以递归的形式描述的,如下所示:

This definition has both of the properties we discussed earlier for recursion: there is a limiting case from which no recursion is performed, and each recursive call gets a little closer to the limiting case.

这个定义同时具备了我们开始讨论递归所需要的两个特性:存在限制条件,当符合这个条件时递归便不再继续;每次递归调用之后越来越接近这个限制条件。


Definitions like this one often lead people to use recursion to implement a factorial function, such as the one shown in Program 7.7a. This function produces the correct answers, but it is not a good use of recursion. Why? The recursive function calls involve some runtime overhead—arguments must be pushed on the stack, space allocated for local variables (in general, though not for this specific example), registers must be saved, and so forth. When each call to the function returns, all this work must be undone. For all of this overhead, however, recursion does little to simplify the solution to this problem.

用这种方式定义阶乘往往引导人们使用递归来实现阶乘函数,如程序7.7a 所示。这个函数能够产生正确的结果,但它并不是递归的良好用法。为什么?递归函数调用将涉及一些运行时开销参数必须压到堆战中,为局部变量分配内存空间(所有递归均如此,并非特指这个例子),寄存器的值必须保存等。当递归函数的每次调用返回时,上述这些操作必须还原,恢复成原来的样子。所以,基于这些开销,对于这个程序而言,它并没有简化问题的解决方案。


/*

** Compute the factorial of n, recursively

*/

long

factorial( int n )

{

if( n <= 0 )

return 1;

else

return n * factorial( n - 1 );

}

Program 7.7a Recursive factorials


Program 7.7b computes the same result with a loop. Although this program with its simple loop does not resemble the mathematical definition of factorial nearly as closely, it computes the same result more efficiently. If you look more closely at the recursive function, you will see that the recursive call is the last thing the function does. This function is an example of tail recursion. Because no work is done in the function after the recursive call returns, tail recursion can easily be converted to a simple loop that accomplishes the same task.

程序7.7b 使用循环计算相同的结果。尽管这个使用简单循环的程序不甚符合前面阶乘的数学定义,但它却能更为有效地计算出相同的结果。如果你仔细观察递归函数,你会发现递归调用是函数所执行的最后一项任务。这个函数是尾部递归(tail recursion) 的一个例子。由于函数在递归调用返回

之后不再执行任何任务,所以尾部递归可以很方便地转换成一个简单循环,完成相同的任务。


/*

** Compute the factorial of n, iteratively

*/

long

factorial( int n )

{

int result = 1;

while( n > 1 ){

result *= n;

n -= 1;

}

return result;

}

Program 7.7b Iterative factorials



Many problems are explained recursively only because it is clearer than a nonrecursive explanation. However, an iterative implementation of these problems is often more efficient than a recursive implementation, even though the resulting code is less clear. When a problem is complex enough that an iterative implementation is difficult for someone else to follow, then the clarity of the recursive implementation may justify the runtime overhead that it will incur.

许多问题是以递归的形式进行解释的,这只是因为它比非递归形式更为清晰。但是,这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性可能稍差一些。当一个问题相当复杂,难以用迭代形式实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销。


In Program 7.7a, recursion added little to the solution of the problem. Program 7.7b with its loop was just as simple. Here is a more extreme example. Fibonacci numbers are a sequence of numbers in which each value is the sum of the previous two values. This relationship is often described recursively:

在程序7.7a 中,递归在改善代码的可读性方面并无优势,因为程序7.7b 的循环方案也同样简单。这里有一个更为极端的例子,菲波那契数就是一个数列,数列中每个数的值就是它前面两个数的和。这种关系常常用递归的形式进行描述:


Again, the recursive definition suggests that the solution should be implemented using recursion, as illustrated in Program 7.8a. Here is the trap: the recursive step computes Fibonacci(n ‐ 1) and Fibonacci(n ‐ 2). But, the computation of Fibonacci(n ‐ 1) also computes Fibonacci(n ‐ 2). How much could one extra computation cost?

同样,这种递归形式的定义容易诱导人们使用递归形式来解决问题,如程序7.8a 所示。这里有一个陷阱:它使用递归步骤计算Fibonacci(n-l)和Fibonacci(n-2)。但是,在计算Fibonacci(n-l )时也将计算Fibonacci(n-2)。这个额外的计算代价有多大呢?


The answer is that there is far more than just one redundant computation: each recursive call begins two more  recursive calls, and each of those calls begins two more,and so forth. Therefore, the number of redundant computations grows very rapidly.For instance, in computing Fibonacci(10), the value of Fibonacci(3) is computed 21 times. But to compute Fibonacci(30) recursively, Fibonacci(3) is computed 317,811 times. Of course, every one of these 317,811 calculations produced exactly the same result, so all but one were wasted. This overhead is horrible!

答案是:它的代价远远不止一个冗余计算:每个递归调用都触发另外两个递归调用,而这两个调用的任何一个还将触发两个递归调用,再接下去的调用也是如此。这样,冗余计算的数量增长得非常快。例如,在递归计算Fibonacci(10) 时, Fibonacci(3)的值被计算了21 次。但是,在遂归计算

Fibonacci(30) 时, Fibonacci(3)的值被计算了317811 次。当然,这317811 次计算所产生的结果是完全一样的,除了其中之一外,其余的纯属浪费。这个额外的开销真是相当恐怖!


/*

** Compute the value of the n'th Fibonacci number, recursively.

*/

long

fibonacci( int n )

{

if( n <= 2 )

return 1;

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

}

Program 7.8b Recursive Fibonacci numbers


Consider Program 7.8b, which uses a simple loop instead of recursion. Once again, the loop does not resemble the abstract definition as closely as the recursive version, but it is hundreds of thousands of times more efficient!

现在考虑程序7.8b ,它使用一个简单循环来代替递归。同样,这个循环形式不如递归形式符合前面菲波那契数的抽象定义,但它的效率提高了几十万倍!


Before implementing a function recursively, ask yourself whether the benefit to be gained by using recursion outweights the costs. And be careful: the costs might be more than what they first appear to be.

当你使用递归方式实现一个函数之前,先问问你自己使用递归带来的好处是否抵得上它的代价。而且你必须小心:这个代价可能比初看上去要大得多。


/*

** Compute the value of the n'th Fibonacci number, iteratively.

*/

long

fibonacci( int n )

{

long result;

long previous_result;

long next_older_result;

result = previous_result = 1;

while( n > 2 ){

n -= 1;

next_older_result = previous_result;

previous_result = result;

result = previous_result + next_older_result;

}

return result;

}

Program 7.8b Iterative Fibonacci numbers

上一章 Pointers on C——7 Functions.6

原创粉丝点击