【问题标题】:Recursion - Two calls in one statement递归 - 一个语句中的两个调用
【发布时间】:2012-10-06 20:46:53
【问题描述】:

我试图理解下面代码 sn-p 中的递归调用。

static long fib(int n) {
    return n <= 1 ? n : fib(n-1) + fib(n-2);
}

首先调用哪个函数调用?调用后等式如何运作?

这两个都被调用一次然后应用方程还是第一个调用然后第二个?

也许是一个非常简单的问题!

【问题讨论】:

  • 你可以尝试打印n的值,自己找出来。
  • @DavidBejar “试试看”并不总是好的建议,尤其是在行为可能是实现定义或未定义的情况下 (i = i++)。
  • 如果你只想知道函数被调用的顺序是没有用的,但是一本非常流行的教科书《计算机程序结构与解释》在Section 1.2.2.
  • @JohnKugelman “试试看”在处理递归时是特别危险的建议,如果你弄错了函数可能永远不会返回;)

标签: methods recursion call


【解决方案1】:

Java 和 C&sharp;

子表达式按从左到右的顺序计算。 fib(n-1)fib(n-2) 之前评估。见What are the rules for evaluation order in Java?

请务必注意,此处的评估顺序无关紧要,因为fib() 没有任何副作用。

C 和 C++

这两个函数以不确定的顺序被调用,一旦它们都被调用,它们的返回值被加在一起并返回。可以先调用左边的函数,也可以先调用右边的,你不知道。

这似乎有问题,但事实并非如此,因为调用它们的顺序无关紧要。调用fib(i)没有任何副作用(例如修改其他变量、打印消息等),所以两个函数调用是完全独立的。

一个编译器可能决定先评估左侧,再评估右侧:

 1. f(3)
 2.   f(2)
 3.     f(1)
 4.       return 1
 5.     f(0)
 6.       return 0
 7.     return 1 + 0
 8.   f(1)
 9.     return 1
10.  return 1 + 1

另一个人可能决定先评估右侧,再评估左侧:

 1. f(3)
 2.   f(1)
 3.     return 1
 4.   f(2)
 5.     f(0)
 6.       return 0
 7.     f(1)
 8.       return 1
 9.     return 1 + 0
10.  return 1 + 1

【讨论】:

  • 嗯..如果是`不确定顺序`,我们如何预测答案?
  • 我猜,如果它完全独立,那么我们就不会得到想要的答案。假设 n 为 10,那么我们期望等式为 9 + 8。如果首先调用第一个,则结果为 9,假设再次调用第一个,则 n 为 8,然后调用第二个,结果是8,所以我们得到8+8,这不是我们想要的。也许我没有正确理解你的答案?
  • 你不明白,凯文。 fib(n-1)fib(n-2) 的生成顺序无关紧要,因为 a) 都必须在表达式 @ 之前进行计算987654328@ 可以被评估,并且 b) 这两个调用是完全独立的,并且它们的结果不相互依赖。 fib(n-1) 将始终可靠地为 n 的任何一个值返回一个值,fib(n-2) 将始终返回另一个特定值并将这两个值相加对于 n 的任何一个值,值加在一起总是会给出相同的结果。
  • Kevin,暂时假设 fib 是一个可靠的函数,它可以正常工作并且总是为 fib(n) 返回正确的值。所以 fib(6) 返回 5(这是系列中的第 6 个元素),而 fib(7) 返回 8(这是第 7 个元素)。现在,假设您调用 fib(8)fib 函数尝试通过添加 fib(6)fib(7) 来计算它。它是先计算 fib(6) 还是第二个有什么关系?它仍然将答案添加到 8 以得到 13。
【解决方案2】:

GCC -S fib.c:

    subl    $1, %eax
    movl    %eax, (%esp)
    call    _fib
    movl    %eax, %ebx
    movl    8(%ebp), %eax
    subl    $2, %eax
    movl    %eax, (%esp)
    call    _fib

所以,首先调用左边的。然后怎样呢?好吧,它也为 (n-2) 调用 fib 而没有 知道正确的分支也在计算同样的事情。

这个众所周知的例子的复杂度为 O(n^2),这意味着如果 n=10,它会用不同的参数调用它自身 ~100 次,即使 10 次就足够了。

【讨论】:

    【解决方案3】:

    首先调用哪个函数并不重要。该函数返回斐波那契数列中的第第n个数,总是可以通过将前两个数相加来找到(特殊情况下,数列中的前两个数是0和1)。 p>

    所以这个函数计算 fib(n) 的作用是求 fib(n-1) 和 fib*(n-2) 并将它们相加得到 fib(n)。当然,fib(n-1) 通过请求 fib(n-2) 和 fib(n-3) 来工作,而 fib(n-2) 通过请求 fib(n-3) 和 fib(n-4) 来工作) 依此类推,直到到达序列的最开始(0 和 1)。由于这些可以在没有任何进一步递归的情况下返回,因此递归结束并且每个打开的函数返回到调用它的函数,一直返回到链。

    有一种更有效的方法可以做到这一点,它不需要两个单独的递归,但看起来不会那么优雅。

    【讨论】:

      【解决方案4】:

      + 运算符的求值顺序可能未定义(它取决于实现)意味着:fib(n-1)fib(n-2) 可以先执行。无论哪种方式,结果都是相同的,在这种特殊情况下,这无关紧要:两个递归调用将在返回之前计算并相加,从调用位置您只会看到总和的最终结果。

      【讨论】:

        【解决方案5】:

        为了理解这一点,我使用了下面的代码,输出清除了所有疑问:(C#)

                static void Main(string[] args)
            {
                var data = series(5);
                Console.WriteLine(data);
            }
        
            public static int series(int n)
            {
                Console.WriteLine(n);
                if (n ==2)
                {
                    return 1;
                }
                if (n == 50)
                {
                    return 3;
                }
                else
                {
                    return series(2) + series(50);
                }
            }
        

        输出: 5 2 50 4

        简单来说就是完成左表达式的递归然后向右移动。

        【讨论】:

          猜你喜欢
          • 2018-11-07
          • 1970-01-01
          • 2020-01-04
          • 1970-01-01
          • 2021-04-28
          • 2018-12-27
          • 2010-09-28
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多