虽然众所周知递归是“一种自称的方法”,但我倾向于想知道实际发生了什么。以经典的因子为例:
public static int fact(int n) {
if(n == 0)
return 1;
else
return n * fact(n - 1);
}
事实(5);
我知道它有点像这样:(等号表示在为该值调用函数时发生了什么)
http://postimg.org/image/4yjalakcb/
为什么递归函数会这样?计算机的哪个方面使其自身向后工作?幕后发生了什么?
作为一名学生,我觉得我们所教授的递归是浅薄而一般的。我希望这里的优秀社区帮助我在机器本身的层面上理解它。谢谢!
答案 0 :(得分:7)
以下是每当您调用方法时会发生什么的简要概述:
您可以在此处了解有关框架的更多信息 - JVM Spec - Frames。
在递归的情况下,同样的事情发生。暂时不要忘记你正在处理递归,并将每个递归调用作为对不同方法的调用。所以,在factorial
的情况下,堆栈会像这样增长:
fact(5)
5 * fact(4)
4 * fact(3)
3 * fact(2)
2 * fact(1)
1 * fact(0) // Base case reached. Stack starts unwinding.
2 * 1 * 1
3 * 2 * 1 * 1
4 * 3 * 2 * 1 * 1
5 * 4 * 3 * 2 * 1 * 1 == Final result
答案 1 :(得分:2)
如果跟踪函数调用,您将看到它是如何工作的。
E.g。
fact(3)
将返回3 * fact(2)
。所以java会调用fact(2)
。
fact(2)
将返回2 * fact(1)
。所以java会调用fact(1)
。
fact(1)
将返回1 * fact(0)
。所以java会调用fact(0)
。
fact(0)
将返回1
。
然后fact(1)
将返回1 * 1 = 1
。
然后fact(2)
将返回2 * 1 = 2
。
然后fact(3)
将返回3 * 2 = 6
。
Java像任何其他方法一样调用递归方法。
答案 2 :(得分:0)
您可能听说过一些名为“The Stack”的内容这是用于存储方法状态的内容。
我相信它也存储了调用行,因此该函数可以返回给它的调用者
假设您调用了递归函数
- int $input = 5
- stack.Push L
- GOTO FOO
- Label L
您的递归函数(没有基本情况)可能类似于以下
- Label FOO
- int in = $input
- input = in - 1
- stack.Push in
- stack.Push L2
- goto FOO
- Label L2
- in = stack.Pop in
- output *= in
- goto stack.POP
答案 3 :(得分:0)
以下可能会帮助您理解。计算机不关心他是否只调用它只是计算相同的功能。一旦你理解它是什么以及为什么它适用于很多东西,比如列表,自然数等等,它们本身就是由结构递归的,所以没有任何关于递归的神奇之处。
因此
5! = 5*4! = 5*4*3! = 5*4*3*2! = 5*4*3*2*1! = 5*4*3*2*1*0! = 5*4*3*2*1*1 = 120
所以,如果你曾经通过归纳听过证据,那就是这样的:
示例:通过归纳证明偶数的平方是4的倍数!
(2+n)*(2+n)
= 4+2n+2n+n²
。这是4的多重,因为n²是我们的假设,4是1,2n+2n = 4n
也是4的倍数,4的倍数之和是分布定律的4的倍数:4a + 4b = 4(a+b)
(更容易证明(2a)²
= 4*a*a
,这是4的倍数。)
编写递归程序与通过归纳进行证明非常相似:
n! = n * (n-1)!
,所以我们将其写下来,因为我们需要的功能是我们刚刚写的那个!678!
仍未计算出正确的答案,那么它与我们使用的数据类型int
这一事实有关,这种数据类型不适合大数字(或者,换句话来说不同) ,计算一切moulo 2 ^ 32)此外,还有一个软件坚持将一半的可用数字解释为负数。其工作原理与计算机硬件或编程语言无关:正如我之前所说,它是手头项目(列表,树,集,自然数)的递归结构的结果。
新手所犯的常见错误是忽略基本情况并迷失复杂性。我总是建议从基础案例开始,一旦你有了这个,你可以假设函数存在,并且可以在更复杂的情况下使用它。