理解基本递归

public static void main (String[] args) { System.out.println(factorial(5)); } public int factorial(int n) { if(n <= 1){ return 1; } else{ return n * factorial(n - 1); } } 

我直接在这里写了以上内容,所以可能无法编译,但认为它确实如此。

任何人都可以简单地解释它是如何工作的,它是如何存储的? 它首先计算5 *(5-1),然后下降到4 *(4-1)然后3 *(3-1)…..直到它变为1,它将刚刚返回1? 抱歉这么粗略我只想知道它是如何工作的

谢谢

但随着它的运作 – 它获得了各个阶段的价值

5 *(5-1)4 *(4-1)…… ……

这些如何存储然后检索回来或者我错过了什么?

想象一下,你就是电脑,有人会给你一张纸

 factorial(3) 

写在上面。 然后执行该过程,查看参数。 因为它是> 1,你写

 factorial(2) 

在另一张纸上,“把它交给你自己”,等到你得到答案之前再继续。

再次执行该过程。 因为2仍然> 1你写

 factorial(1) 

在另一张纸上把它递给自己,等到你得到这个答案后再继续。

再次,您执行该过程。 这次输入是1,所以你取第一个分支并返回1.处理factorial(2)的调用现在有一个答案,所以它将2乘以答案(1)并返回。 现在处理factorial(3)的调用得到它的答案(2)并将它乘以3,得到6.然后它将该答案返回给开始整个操作的人。

如果你想象你在工作时将纸片放在你面前的堆栈中,那就是计算机内存中“堆栈”的可视化。 每个递归调用将参数(和任何临时变量)存储在它自己的纸张(堆栈框架)上,就像纸张一样排列为下推堆栈,一个在另一个上面。

是的,你在代码中拥有它,它首先检查n的值是否小于或等于1,这就是所谓的基本情况 。 它们很重要,它们会告诉您的递归函数何时停止。

如果n的值不小于或等于,则返回n的值乘以factorial的递归调用,但值为n-1 ,直到达到它的基本情况为止: if (n <= 1)返回1

你的基础案例是由因子定义为0!1! 两者都等于1。

也许这个图可能有助于理解调用的工作原理。

 5 * fact(5-1) -> 4 * fact(4-1) -> 3 * fact(3-1) -> 2 * fact(1) 1 

哪个和5!5 x 4 x 3 x 2 x 1

希望这可以帮助。

你在问内部的递归是如何工作的吗? 一句话的答案是每个线程都有一个“调用堆栈”,每次调用一个方法时,一个新的条目被推送到这个堆栈,它有关于调用哪个方法的信息,以及参数是什么。 该方法完成后,将其返回值放回同一堆栈,调用方法将其拉出。 所以在它的高度你的堆栈看起来像

阶乘(1)称为阶乘(2)称为阶乘(3)称为阶乘(4)称为阶乘(5)

关于调用堆栈的维基百科文章乍一看似乎非常彻底。

  1. 在对factorial的初始调用中,n = 5,并被推入堆栈。
  2. 然后触发else,因此4传递给阶乘,并且也被推入堆栈。
  3. 然后触发else,因此将3传递给阶乘,并且也被推入堆栈。
  4. 然后触发else,因此2传递给阶乘,并且也被推入堆栈。
  5. 然后触发else,因此1传递给阶乘,并且也被推入堆栈。
  6. 然后触发else,因此0传递给阶乘,并且也被推入堆栈。
  7. if被触发,1返回给调用阶乘。
  8. if被触发并且2 * 1被返回到调用阶乘。
  9. if被触发并且3 * 2被返回到调用阶乘。
  10. if被触发并且4 * 3被返回到调用阶乘。
  11. if被触发并且5 * 4被返回到调用阶乘。

堆栈也会被清理干净,但输入过于繁琐。 基本上,方法调用中的所有值都被压入堆栈,并在方法返回时弹出堆栈。 这使它们在递归调用之间分开。

….然后3 *(3-1)…..直到它变为1才会返回1对吗?

是的,但是它会将“1”返回到倒数第二个调用,它将乘以2,返回“2”…到下一个到倒数第二个调用,它将乘以3。 …

值得注意的是,“递归”在java(一种过程语言)中的工作方式与Haskell或F#(函数式语言)的工作方式不同。

在Java中,当我们调用递归时,我们通过计算表达式树并解析它的每个部分来实现,直到我们确定表达式的每个部分的值。 如果我们需要调用另一个函数,我们在该点为所有中间值放置一个占位符,然后开始为新函数构建一个新的表达式树。

在递归的情况下,我们正在做的是调用相同的函数,希望使用不同的终止值,这需要在我们完成当前表达式的求值之前解决。 这些扩展被反复链接在一起,直到发生以下两件事之一:1)我们到达一个终止表达式,它将控制权返回给调用者(在这种情况下是if的第一部分),或者我们耗尽了将中间值放在存储器中的能力返回exception(堆栈溢出)。

在第一种情况下,我们然后开始从堆栈顶部解析每个表达式树,向后工作直到它们没有剩余堆栈条目,此时表达式树解析为返回的最终值。

吉姆的回答是一个很好的物理隐喻。

很难准确猜出你遇到什么困难的递归部分,但我要解决你的这部分问题:

直到它变为1才会返回1对吗?

我猜你的意思是,“如果它只返回1,为什么函数的结果不是 1?”

考虑当你从一个函数返回时(在这种情况下,是factorial),你将一个值返回给最初要求它的人。

如果我说“ 给我阶乘(5) ”那么阶乘(5)将返回一个值,但在它返回值之前它必须向因子(4)求值,因子(5)基本上说“ 给”我是阶乘(4)因此我可以将它乘以5并将其交还给要求阶乘(5)的人 。“ 现在factorial(4)将其值返回到factorial(5),它可以将它乘以n并将其值返回给我。 回想一下, 没有要求阶乘(4)的价值,我甚至不在乎,它没有回到我身边,它又回到了阶乘(5)。

当你点击阶乘(1)时,你将有阶乘(2),阶乘(3),阶乘(4)和阶乘(5)等待回答。 因子(1)将返回其值(由于你的基本情况为1)到阶乘(2),它最终可以返回到阶乘(3)等等,此时递归将完成,你将会得到factorial(5)的值。

一种实用的方法,需要一个好的IDE(eclipse,netbeans,IntelliJ):

在读取return 1的行中添加断点并调试应用程序。 停止时,查看堆栈跟踪。 你会看到阶乘方法被多次调用。

eclipse Debug视图显示了挂起的线程和一个包含六个条目的堆栈,每一行代表一行代码,其中调用另一个方法(顶部条目除外 – 这是断点)。 factorial出现五次,您可以选择每一行并在Variable视图中查看n的值(这是基本的,并且应该以类似的方式在另一个IDE上工作)。

这应该给出另一个想法,递归方法调用如何工作(以及当你没有正确定义退出标准时,为什么你收到内存不足错误;))