【问题标题】:understanding basic recursion理解基本递归
【发布时间】:2010-12-29 07:10:23
【问题描述】:
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) ... ... ...

这些是如何存储的,然后再取回或者我错过了什么?

【问题讨论】:

标签: java recursion


【解决方案1】:

....然后 3 * (3-1)..... 直到它达到 1,它只会返回 1 对吗?

正确,但它会将“1”返回到倒数第二个调用,该调用将乘以 2,将“2”返回到倒数第二个调用,该调用将乘以三……

【讨论】:

    【解决方案2】:

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

    如果n 的值不小于或等于,则返回n 的值乘以factorial 的递归调用,但值n-1 直到达到它的基本情况:@ 987654326@ 返回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 相同

    希望这会有所帮助。

    【讨论】:

      【解决方案3】:

      想象你是一台电脑,有人递给你一张纸

      factorial(3)
      

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

      factorial(2) 
      

      在另一张纸上“交给你自己”,等到你得到那张纸的答案后再继续。

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

      factorial(1)
      

      在另一张纸上交给自己,等到你得到这张纸的答案后再继续。

      再次,您执行该过程。这次输入为 1,因此您采用第一个分支并返回 1。正在处理 factorial(2) 的调用现在有一个答案,因此它将 2 乘以该答案 (1) 并返回。现在处理 factorial(3) 的调用得到它的答案 (2) 并将其乘以 3,得到 6。然后它将答案返回给启动整个操作的人。

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

      【讨论】:

      • 所以它会一直工作到最底部,然后再将值传递回顶部,然后将其存储在堆栈中?
      • 到目前为止,这是最接近实际解释递归的“递归”部分的方法。 :)
      • 不在“堆栈”中的“堆栈”中。每个可以动态分配内存(几乎与语言无关)的应用程序都有一个堆和一个堆栈。有时堆栈大小是有限制的,而堆总是有限制的。
      • @sonny - 是的,就是这样。这就是为什么阶乘的递归解决方案确实不是最优的。 Factorial(50) 将分配 50 个堆栈帧,而迭代解决方案仅使用一个。这似乎不是问题,但在另一个经典的递归示例中,斐波那契数。究竟为什么留作练习:-)
      【解决方案4】:

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

      阶乘 (1) 由阶乘调用 (2) 由阶乘调用 (3) 由阶乘调用 (4) 由阶乘调用 (5)

      乍一看,调用堆栈上的Wikipedia article 似乎相当彻底。

      【讨论】:

        【解决方案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 是 返回到调用阶乘。

        堆栈也会被清理,但是输入起来太乏味了。本质上,方法调用中的所有值都被压入堆栈,并在方法返回时从堆栈中弹出。这使它们在递归调用之间保持分离。

        【讨论】:

        • 请解释第 5 行和第 6 行,因为当 1 被传递给阶乘时,“if”被触发并返回 1,而“else”未被触发,而您在第 6 行中提到 0 被传递给在我看来根本没有通过的阶乘。
        • 在代码中,最终返回直到 n
        【解决方案6】:

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

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

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

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

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

        【讨论】:

          【解决方案7】:

          很难确切地猜出你在递归的哪一部分遇到了困难,但我要回答你的这部分问题:

          直到它达到 1 才会返回 1 对吗?

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

          考虑一下,当您从函数(在本例中为阶乘)返回时,您将返回一个值给最初请求它的人。

          如果我说“give me factorial(5)”,那么 factorial(5) 会返回一个值,但在返回值之前,它必须询问 factorial(4)值,阶乘 (5) 本质上是说“给我阶乘 (4),这样我就可以将它乘以 5 并把它还给要求阶乘 (5) 的人。”现在 factorial(4) 将把它的值返回给 factorial(5) ,它可以将它乘以 n 并将它的值返回给我。回想一下,没有询问阶乘(4)的值,我什至不在乎,它没有回到我身边,它回到了阶乘(5)。

          当您点击阶乘 (1) 时,您将有阶乘 (2)、阶乘 (3)、阶乘 (4) 和阶乘 (5) 都在等待回复。 Factorial(1) 将返回它的值(因为你的基本情况是 1)到 factorial(2),它最终可以返回到 factorial(3) 等等,此时递归将完成,你会取回 factorial(5) 的值。

          【讨论】:

            【解决方案8】:

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

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

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

            这应该可以让您了解递归方法调用的工作原理(以及当您没有正确定义退出条件时为什么会收到内存不足错误;))

            【讨论】:

              猜你喜欢
              • 2021-11-29
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2016-09-01
              • 1970-01-01
              • 1970-01-01
              • 2013-10-13
              相关资源
              最近更新 更多