理解基本递归

时间:2009-12-22 21:56:25

标签: java recursion

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) ... ... ...

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

8 个答案:

答案 0 :(得分:27)

想象一下,你是计算机,有人用

给你一张纸
factorial(3)

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

factorial(2) 

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

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

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

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

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

答案 1 :(得分:9)

是的,你在代码中拥有它,它首先检查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

相同

希望这有帮助。

答案 2 :(得分:7)

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

阶乘(1) 被阶乘所称(2) 被阶乘调用(3) 被阶乘调用(4) 由factorial(5)调用

乍一看,Wikipedia article来电堆栈看起来非常彻底。

答案 3 :(得分:5)

  1. 在对阶乘的初步调用中, 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被触发  回到了呼唤的阶乘。
  12. 堆栈也会被清理干净,但输入过于繁琐。基本上,方法调用中的所有值都被压入堆栈,并在方法返回时弹出堆栈。这使它们在递归调用之间分开。

答案 4 :(得分:1)

  

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

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

答案 5 :(得分:1)

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

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

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

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

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

答案 6 :(得分:1)

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

  

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

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

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

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

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

答案 7 :(得分:0)

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

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

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

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