您的位置:首页 > 理论基础 > 数据结构算法

Data Structures and algorithm analysis—1.3. A Brief Introduction to Recursion(数据结构—1.3 递归的简介)(之一)

2017-06-15 15:05 363 查看

1.3. A Brief Introduction to Recursion

1.3. 递归的简介

Most mathematical functions that we are familiar with are described by a simple formula. For instance, we can convert temperatures from Fahrenheit to Celsius by applying the formula
我们熟悉的绝大多数数学函数都可以使用一个简单的数学公式来表达。举个栗子,我们可以通过下述公式将温度值从华氏度转化为是摄氏度
 C =5(F - 32)/9
摄氏度=5x(华氏度-32)/9
Given this formula, it is trivial to write a C function; with declarations and braces removed, the one-line formula translates to one line of C.
给出的这个公司,如果用C函数写出来小菜一碟;如果不考虑声明和括号,单句公式可以被翻译成一句C语言语句。
Mathematical functions are sometimes defined in a less standard form. As an example, we can define a function f,valid on nonnegative integers, that satisfies f(0) = 0 and f(x) = 2f(x - 1) +x2. From this definition we see that f(1) = 1, f(2) = 6, f(3) = 21, and f(4) =58. A function that is defined in terms of itself is called recursive. C allows functions to be recursive.
有时,数学公式是使用极简形式。举个栗子,我们可以定义一个函数f,定义域为非负整数,满足f(0)=0,且f(x)=2f(x-1)+x^2。从此定义我们可以得出,f(1) = 1, f(2) = 6, f(3)= 21, f(4) = 58。一个函数是根据自身定义的,那么这个函数就是递归的。C语言允许递归。
* It is important to remember that what C provides is merely an attempt to follow the recursive spirit. Not all mathematically recursive functions are efficiently (or correctly) implemented by C's simulation of recursion. The idea is that the recursive function f ought to be expressible in only a few lines, just like a non-recursive function.Figure 1.2 shows the recursive implementation of f.
谨记C语言仅提供一个试图遵循递归性是重要的。并非所有的数学递归函数都能用C语言有效(或合适)地实现。思想是递归函数f应当像非递归函数一行可以仅用几行代码描述出来.
图片1.2显示了递归函数f的实现

*Using recursion for numerical calculations is usually a bad idea. We have done so to illustrate the basic points. Lines 1and 2 handle what is known as the base case, that is, the value for which the function is directly known without resorting to recursion. Just as declaring f(x) = 2 f(x - 1) + x2 is meaningless, mathematically, without including the fact that f (0) = 0, the recursive C function doesn't make sense without a base case. Line 3 makes the recursive call.
使用递归计算数值通常不是什么好想法。我们先表明了基础态度。第一二行代码已经处理了元情况,也就是说,此函数的值无需递归计算,直接给出。就像声明数学函数f(x) = 2 f(x - 1) + x^2在不给定能够基础条件f(0)=0下是无意义的一样。该C语言递归函数在无元情况条件下也是无意义的。第三行代码使用了递归调用。
There are several important and possibly confusing points about recursion. A common question is: Isn't this just circular logic? The answer is that although we are defining a function in terms of itself, we are not defining a particular instance of the function in terms of itself. In other words, evaluating f(5) by computing f(5) would be circular.Evaluating f(5) by computing f(4) is not circular--unless, of course f(4) is evaluated by eventually computing f(5). The two most important issues are probably the how and why questions. In Chapter 3, the how and why issues are formally resolved. We will give an incomplete description here.
关于递归,有以下几个重难点。一个经常遇到的问题就是:递归就是一个逻辑循环吗?答案是:虽然我们使用函数本身来定义它自己,但是我们并未定义一个特定的函数实例。换句话说,使用计算机计算f(5)的值就是循环,通过f(4)的值来计算f(5)不是循环----当然,这不包括f(4)的值在计算f(5)的过程中已经计算出来的这种情况。两个最重要的点或许是问题怎样破和为什么这么破。在第三章中,这两点会正式解决。我们在这里给一个不完整的描述。
It turns out that recursive calls are handled no differently from any others. If f is called with the value of 4,then line 3 requires the computation of 2 * f(3) + 4 * 4. Thus, a call is made  to compute f(3). This requires the computation of 2 * f(2) + 3 * 3. Therefore,another call is made to compute f(2). This means that 2 * f(1) + 2 * 2 must be evaluated. To do so, f(1) is computed as 2 * f(0) + 1 * 1. Now, f(0) must be evaluated. Since this is a base case, we know a priori that f(0) = 0. This enables the completion of the calculation for f(1), which is now seen to be 1.Then f(2), f(3), and finally f(4) can be determined. All the bookkeeping needed to keep track of pending function calls (those started but waiting for are cursive call to complete), along with their variables, is done by the computer automatically. An important point, however, is that recursive calls will keep on being made until a base case is reached.For instance, an attempt to evaluate f(-1) will result in calls to f(-2), f(-3), and so on.Since this will never get to a base case, the program won't be able to compute the answer (which is undefined anyway). Occasionally, a much more subtle erroris made, which is exhibited in Figure 1.3. The error in the program in Figure1.3 is that bad(1) is defined, by line 3, to be bad(1).Obviously, this doesn't give any clue as to what bad(1) actually is. The computer will thus repeatedly make calls to bad(1) in an attempt to resolve its values.Eventually, its bookkeeping system will run out of space, and the program will crash. Generally, we would say that this function doesn't work for one special case but is correct otherwise. This isn't true here, since bad(2) calls bad(1).Thus, bad(2) cannot be evaluated either. Furthermore, bad(3), bad(4), andbad(5) all make calls to bad(2). Since bad(2) is unevaluable, none of these values are either. In fact, this program doesn't work for any value of n,except 0. With recursive programs, there is no such thing as a "special case."
执行递归调用和执行其他调用时没啥区别的!如果使用参数值为4调用函数f,那么第三行代码要求计算2*f(3)+4*4。因此,产生了一个新的调用来计算f(3)。这个新的调用要求去计算2*f(2)+3*3.因此再来一个调用来计算f(2),这就相当于会计算2*f(1)+2*2,为了完成这个计算f(1)会被计算为2f(0)+1*1。现在,f(0)一定要被计算出来了。因为这是一个源情况,我们知道原条件f(0)=0。这就是的f(1)可以被计算了,f(1)=1,以此类推,f(2),f(3),最终f(4)都可以被计算出。所有根据路径被记在账本上的待定的函数调用伴随着他们的变量值都被电脑自动计算了。然而,在抵达元情况之前,递归调用将一直在重复是个重点。举个例子,尝试计算f(-1)将导致调用计算f(-2),f(-3)等等...。程序将无法计算算出答案(它们未定义),因为它永远无法到达元情况。偶尔,展示出的图片1.3会产生一个坑人于无形的错误。图1.3中程序的错误在求bad(1)在第三行代码产生了。
明显,关于bad(1)实际上是啥,它等于没说!电脑会重复调用去尝试计算算bad(1)的值。最后,这个记账本系统将会使用完所有的空间,并且程序会崩溃。一般而言呢,我们会说这个函数在特定条件下不工作但其余情况运转正常。它在这儿这儿不对,bad(2)要调用bad(1),因此,bad(2)也是无法计算的,此外,bad(3),bad(4),bad(5)都会调用bad(2),因为bad(2)无法计算,因此他们没有一个可以倍计算出来。事实上,这个程序对于任意的非零n,都无法正常工作。对于递归程序而言,没有什么”特殊情况”!
 
These considerations lead to the first two fundamental rules of recursion:
关于递归,这有两个基本原则值得考虑:
 
1. Base cases.You must always have some base cases, which can be solved without recursion.
1.元情况,你必须总有一些不适用递归就能得出答案的基本情况
 
2. Making progress. For the cases that are to be solved recursively, the recursive call must always be to a case that makes progress toward a base case.
2.编写下一步。对于那些使用递归解决的情况而言,递归调用必须总是达到一种可以逐步使用到元情况的状态。
 
Throughout this book, we will use recursion to solve problems. As an example of a nonmathematical use, consider a large dictionary. Words in dictionaries are defined in terms of other words. When we look up a word, we might not always understand the definition, so we might have to look up words in the definition. Likewise, we might not understand some of those, so we might have to continue this search for a while. As the dictionary is finite, eventually either we will come to a point where we understand all of the words in some definition (and thus understand that definition and retrace our path through the other definitions),or we will find that the definitions are circular and we are stuck, or that some word we need to understand a definition is not in the dictionary.
我们递归地解决遇到的问题将贯穿整本书。对于那些非数学的用例,将它们人做是一个超大的字典。字典中的单词使用其他单词来定义。当我们查询一个单词的时候,我们或许不总是理解其定义,因此我们可能要查询其定义中的单词。同样,我们或许不理解它们(定义中的单词),因此我们不得不继续再进行查找。因为一个字典是有限的,要么最终我们可以抵达理解定义中所有的单词的地步(理解那些定义并且回溯我们对单词的理解路径),要么我们会发现我们卡在轮回的定义上,要么我们需要理解的某些单词定义根本不在字典里。
 
                                   
图1.3  一个无尽的递归程序
 
Our recursive strategy to understand words is as follows: If we know the meaning of a word, then we are done; otherwise,we look the word up in the dictionary. If we understand all the words in the definition, we are done; otherwise, we figure out what the definition means by recursively looking up the words we don't know. This procedure will terminate if the dictionary is well defined but can loop indefinitely if a word is either not defined or circularly defined.
我们理解单词递归策略如下:如果我们知道一个单词的意思,那么就罢了,否则,我们需要在字典中查询。如果我们理解定义中的所有单词,那就完成了,否则,我们需要通过递归查询那些我们不懂得单词来搞明白定义。如果字典编写的很好,那么这个套路结束了,如果一个单词没定义或轮回定义,那么这个套路就没完没了了。
 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐