我在学校里很难理解递归。每当教授谈到它的时候,我似乎都懂了,但当我自己尝试的时候,它完全震撼了我的大脑。
我整晚都在尝试着解决《Towers of Hanoi》这款游戏,结果完全出乎我的意料。我的课本只有大约30页关于递归的内容,所以它不是很有用。有人知道哪些书籍或资源可以帮助澄清这个话题吗?
我在学校里很难理解递归。每当教授谈到它的时候,我似乎都懂了,但当我自己尝试的时候,它完全震撼了我的大脑。
我整晚都在尝试着解决《Towers of Hanoi》这款游戏,结果完全出乎我的意料。我的课本只有大约30页关于递归的内容,所以它不是很有用。有人知道哪些书籍或资源可以帮助澄清这个话题吗?
当前回答
你的大脑爆炸是因为它进入了无限递归。这是初学者常犯的错误。
信不信由你,你已经理解了递归,你只是被一个常见的,但错误的函数比喻拖了下来:一个小盒子,里面有东西进进出出。
而不是考虑一个任务或过程,比如“在网上找到更多关于递归的知识”。这是递归的,没有问题。要完成这个任务,你可以:
a) Read a Google's result page for "recursion" b) Once you've read it, follow the first link on it and... a.1)Read that new page about recursion b.1)Once you've read it, follow the first link on it and... a.2)Read that new page about recursion b.2)Once you've read it, follow the first link on it and...
如您所见,您已经做了很长一段时间的递归工作,没有出现任何问题。
你会坚持做这个任务多久?永远,直到你的大脑爆炸?当然不是,只要你相信你已经完成了任务,你就会停在一个给定的点上。
当要求你“在网上找到更多关于递归的知识”时,没有必要指定这一点,因为你是一个人,你可以自己推断。
计算机无法推断任何东西,所以你必须包含一个明确的结尾:“在网上找到更多关于递归的知识,直到你理解它或你阅读了最多10页”。
您还推断应该从谷歌的结果页面开始进行“递归”,这也是计算机无法做到的。递归任务的完整描述还必须包括一个显式的起点:
“在网上找到更多关于递归的知识,直到你理解它,或者你已经阅读了最多10页,并从www.google.com/search?q=recursion开始”
要想全面了解,我建议你试试下面这些书:
普通Lisp:符号计算的简单介绍。这是对递归最可爱的非数学解释。 小阴谋家。
其他回答
你在用哪本书?
关于算法的标准教科书是Cormen & Rivest。我的经验是,它很好地教授了递归。
递归是编程中较难掌握的部分之一,虽然它确实需要本能,但它是可以学习的。但它确实需要一个好的描述,好的例子和好的插图。
此外,30页通常是很多的,30页是用一种编程语言编写的。在你从一本普通的书中理解递归之前,不要尝试用C或Java学习递归。
当使用递归解时,我总是尝试:
首先建立基本情况,即。 当n = 1的阶乘 试着想出一个一般的规则 对于其他情况
还有不同类型的递归解,分而治之的方法对分形和其他方法很有用。
如果你能先解决一些简单的问题,只是为了掌握窍门,这也会有帮助。一些例子是求解阶乘和生成第n个斐波那契数。
作为参考,我强烈推荐Robert Sedgewick的算法。
希望这能有所帮助。祝你好运。
我认为这个非常简单的方法可以帮助你理解递归。该方法将调用自身,直到某个条件为真,然后返回:
function writeNumbers( aNumber ){
write(aNumber);
if( aNumber > 0 ){
writeNumbers( aNumber - 1 );
}
else{
return;
}
}
这个函数将输出从你输入的第一个数字到0的所有数字。因此:
writeNumbers( 10 );
//This wil write: 10 9 8 7 6 5 4 3 2 1 0
//and then stop because aNumber is no longer larger then 0
基本上发生的情况是writeNumbers(10)将写入10,然后调用writeNumbers(9),后者将写入9,然后调用writeNumber(8)等。直到writeNumbers(1)写入1,然后调用writeNumbers(0),这将写入0 butt将不会调用writeNumbers(-1);
这段代码本质上与以下代码相同:
for(i=10; i>0; i--){
write(i);
}
你可能会问为什么要用递归,如果for循环本质上是一样的。当你需要嵌套for循环但不知道它们嵌套的深度时,你通常会使用递归。例如,当从嵌套数组中打印项目时:
var nestedArray = Array('Im a string',
Array('Im a string nested in an array', 'me too!'),
'Im a string again',
Array('More nesting!',
Array('nested even more!')
),
'Im the last string');
function printArrayItems( stringOrArray ){
if(typeof stringOrArray === 'Array'){
for(i=0; i<stringOrArray.length; i++){
printArrayItems( stringOrArray[i] );
}
}
else{
write( stringOrArray );
}
}
printArrayItems( stringOrArray );
//this will write:
//'Im a string' 'Im a string nested in an array' 'me too' 'Im a string again'
//'More nesting' 'Nested even more' 'Im the last string'
这个函数可以接受一个可以嵌套到100层的数组,而你写一个for循环就需要你嵌套它100次:
for(i=0; i<nestedArray.length; i++){
if(typeof nestedArray[i] == 'Array'){
for(a=0; i<nestedArray[i].length; a++){
if(typeof nestedArray[i][a] == 'Array'){
for(b=0; b<nestedArray[i][a].length; b++){
//This would be enough for the nestedAaray we have now, but you would have
//to nest the for loops even more if you would nest the array another level
write( nestedArray[i][a][b] );
}//end for b
}//endif typeod nestedArray[i][a] == 'Array'
else{ write( nestedArray[i][a] ); }
}//end for a
}//endif typeod nestedArray[i] == 'Array'
else{ write( nestedArray[i] ); }
}//end for i
如你所见,递归方法要好得多。
递归函数就像弹簧,每次调用都要压缩一点。在每一步中,您将一些信息(当前上下文)放在堆栈上。当到达最后一步时,释放弹簧,立即收集所有值(上下文)!
不确定这个比喻是否有效…: -)
无论如何,除了经典的例子(阶乘是最糟糕的例子,因为它效率低,很容易被平化,Fibonacci, Hanoi…),这些都有点人为(我很少,如果有的话,在实际编程案例中使用它们),看看它真正被使用的地方是有趣的。
A very common case is to walk a tree (or a graph, but trees are more common, in general). For example, a folder hierarchy: to list the files, you iterate on them. If you find a sub-directory, the function listing the files call itself with the new folder as argument. When coming back from listing this new folder (and its sub-folders!), it resumes its context, to the next file (or folder). Another concrete case is when drawing a hierarchy of GUI components: it is common to have containers, like panes, to hold components which can be panes too, or compound components, etc. The painting routine calls recursively the paint function of each component, which calls the paint function of all the components it holds, etc.
不确定我是否很清楚,但我喜欢展示现实世界中教材的使用,因为这是我过去偶然发现的东西。
我会用一个例子来解释。
你知道n!意味着什么?如果不是:http://en.wikipedia.org/wiki/Factorial
3! Is 1 times 2 times 3, which is 6
下面是一些伪代码
function factorial(n) {
if (n==0) return 1
else return (n * factorial(n-1))
}
让我们试试吧:
factorial(3)
n是0吗?
no!
所以我们在递归中深入挖掘:
3 * factorial(3-1)
3 minus 1 is 2
2 == 0?
no!
所以我们要深入! 3 * 2 *阶乘(2-1) 2-1 = 1
1 == 0吗?
no!
所以我们要深入! 3 * 2 * 1 *阶乘(1-1) 1-1 = 0
0 == 0?
yes!
我们有一个小问题
所以我们有 3 * 2 * 1 * 1 = 6
我希望这对你有所帮助