我在学校里很难理解递归。每当教授谈到它的时候,我似乎都懂了,但当我自己尝试的时候,它完全震撼了我的大脑。
我整晚都在尝试着解决《Towers of Hanoi》这款游戏,结果完全出乎我的意料。我的课本只有大约30页关于递归的内容,所以它不是很有用。有人知道哪些书籍或资源可以帮助澄清这个话题吗?
我在学校里很难理解递归。每当教授谈到它的时候,我似乎都懂了,但当我自己尝试的时候,它完全震撼了我的大脑。
我整晚都在尝试着解决《Towers of Hanoi》这款游戏,结果完全出乎我的意料。我的课本只有大约30页关于递归的内容,所以它不是很有用。有人知道哪些书籍或资源可以帮助澄清这个话题吗?
当前回答
构建递归函数的真正数学方法如下:
1:假设你有一个函数对f(n-1)是正确的,构造f使f(n)是正确的。 2:构造f,使得f(1)是正确的。
This is how you can prove that the function is correct, mathematically, and it's called Induction. It is equivalent to have different base cases, or more complicated functions on multiple variables). It is also equivalent to imagine that f(x) is correct for all x Now for a "simple" example. Build a function that can determine if it is possible to have a coin combination of 5 cents and 7 cents to make x cents. For example, it's possible to have 17 cents by 2x5 + 1x7, but impossible to have 16 cents. Now imagine you have a function that tells you if it's possible to create x cents, as long as x < n. Call this function can_create_coins_small. It should be fairly simple to imagine how to make the function for n. Now build your function: bool can_create_coins(int n) { if (n >= 7 && can_create_coins_small(n-7)) return true; else if (n >= 5 && can_create_coins_small(n-5)) return true; else return false; } The trick here is to realize that the fact that can_create_coins works for n, means that you can substitute can_create_coins for can_create_coins_small, giving: bool can_create_coins(int n) { if (n >= 7 && can_create_coins(n-7)) return true; else if (n >= 5 && can_create_coins(n-5)) return true; else return false; } One last thing to do is to have a base case to stop infinite recursion. Note that if you are trying to create 0 cents, then that is possible by having no coins. Adding this condition gives: bool can_create_coins(int n) { if (n == 0) return true; else if (n >= 7 && can_create_coins(n-7)) return true; else if (n >= 5 && can_create_coins(n-5)) return true; else return false; } It can be proven that this function will always return, using a method called infinite descent, but that isn't necessary here. You can imagine that f(n) only calls lower values of n, and will always eventually reach 0. To use this information to solve your Tower of Hanoi problem, I think the trick is to assume you have a function to move n-1 tablets from a to b (for any a/b), trying to move n tables from a to b.
其他回答
要向一个六岁的孩子解释递归,首先要向一个五岁的孩子解释,然后再等一年。
实际上,这是一个有用的反例,因为递归调用应该更简单,而不是更难。向一个5岁的孩子解释递归就更难了,尽管你可以在0点停止递归,但你没有简单的解决方案来向一个0岁的孩子解释递归。
要使用递归解决一个问题,首先将其细分为一个或多个可以用相同方法解决的更简单的问题,然后当问题简单到无需进一步递归就可以解决时,您可以返回到更高的级别。
事实上,这是用递归方法来解决问题的递归定义。
http://javabat.com是一个有趣而令人兴奋的练习递归的地方。他们的例子开始时相当简单,然后逐步扩展(如果你想这么做的话)。注意:他们的方法是在实践中学习。这是我写的一个递归函数,用来替换for循环。
for循环:
public printBar(length)
{
String holder = "";
for (int index = 0; i < length; i++)
{
holder += "*"
}
return holder;
}
这是做同样事情的递归。(请注意,我们重载了第一个方法,以确保它像上面那样使用)。我们还有另一种方法来维护索引(类似于上面的for语句)。递归函数必须维护自己的索引。
public String printBar(int Length) // Method, to call the recursive function
{
printBar(length, 0);
}
public String printBar(int length, int index) //Overloaded recursive method
{
// To get a better idea of how this works without a for loop
// you can also replace this if/else with the for loop and
// operationally, it should do the same thing.
if (index >= length)
return "";
else
return "*" + printBar(length, index + 1); // Make recursive call
}
简而言之,递归是一种编写更少代码的好方法。在后面的printBar中,请注意我们有一个if语句。如果我们的条件已经达到,我们将退出递归并返回到前一个方法,该方法返回到前一个方法,等等。如果我发送一个printBar(8),我得到********。我希望通过一个简单函数的例子,它做的事情与for循环相同,这可能会有所帮助。不过,您可以在Java Bat中进行更多的练习。
想想工蜂。它试着酿蜂蜜。它完成了自己的工作,并期待其他工蜂来酿造剩下的蜂蜜。蜂房满了,蜂房就停了。
把它想象成魔法。你有一个与你要实现的函数同名的函数,当你给它子问题时,它就会帮你解决它,你唯一需要做的就是把你的部分的解与它给你的解集成起来。
例如,我们想计算一个列表的长度。让我们用magical_length来调用我们的函数,用magical_length来调用神奇的助手 我们知道,如果我们给出没有第一个元素的子列表,它会神奇地给我们子列表的长度。那么我们唯一需要考虑的就是如何将这些信息与我们的工作结合起来。第一个元素的长度是1,而magic_counter给出了子列表的长度n-1,因此总长度是(n-1) + 1 -> n
int magical_length( list )
sublist = rest_of_the_list( list )
sublist_length = magical_length( sublist ) // you can think this function as magical and given to you
return 1 + sublist_length
然而,这个答案是不完整的,因为我们没有考虑如果我们给出一个空列表会发生什么。我们认为我们的列表总是至少有一个元素。因此,我们需要思考,如果给我们一个空列表,答案显然是0,那么答案应该是什么。所以把这些信息加到我们的函数中,这被称为基础/边缘条件。
int magical_length( list )
if ( list is empty) then
return 0
else
sublist_length = magical_length( sublist ) // you can think this function as magical and given to you
return 1 + sublist_length
递归
方法A调用方法A调用方法A,最终这些方法A中的一个不会调用并退出,但这是递归,因为有东西调用了它自己。
递归的例子,我想打印出硬盘驱动器上的每个文件夹名称:(在c#中)
public void PrintFolderNames(DirectoryInfo directory)
{
Console.WriteLine(directory.Name);
DirectoryInfo[] children = directory.GetDirectories();
foreach(var child in children)
{
PrintFolderNames(child); // See we call ourself here...
}
}
我认为这个非常简单的方法可以帮助你理解递归。该方法将调用自身,直到某个条件为真,然后返回:
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
如你所见,递归方法要好得多。