【问题标题】:How many calls will be stored in stack?堆栈中将存储多少调用?
【发布时间】:2015-04-27 00:06:05
【问题描述】:

谁能解释一下下面递归方法的堆栈中将存储多少调用以及为什么?什么时候确切地存储在堆栈中?显然是49个电话,但我不明白为什么。谢谢。

public static long fib( int n ){ // n = 50
    if( n <= 1 )
        return 1;
    else
        return fib( n - 1 ) + fib( n - 2 );
    }

【问题讨论】:

  • 可以说离题了,但如果你想了解现代计算机的堆栈和所有部分是如何工作的,我强烈建议阅读this book。可能是我读过的最重要的计算机科学书籍。

标签: java recursion stack fibonacci


【解决方案1】:

下面的递归方法将在堆栈中存储多少调用,为什么?

输入为 50 时,任何给定时刻的最大堆栈帧数,包括对 fib 的初始调用,将为 50。(因此,在任何给定的堆栈中最多存储 49 个递归调用时刻)。

最多可以有 50 个堆栈帧的原因是:

fib(50) 的初始调用是1 个堆栈帧。

然后用这个声明,return fib( n - 1 ) + fib( n - 2 ); 对左边fib 的递归调用将首先执行,将fib(49) 放入堆栈。我们将再次点击返回线,将fib(48) 放入堆栈。这将重复,直到fib(1) 在堆栈上命中return 1; 语句。这将返回到fib(2) 并允许在语句return fib( n - 1 ) + fib( n - 2 ); 中执行正确的递归调用。

长话短说,所有左递归调用都在 1 个右递归调用之前执行。因此很容易看出,在最后一次递归调用中,您将拥有 fib(50)fib(49)、...,最后是 fib(1),导致在执行 fib(50); 时最多有 50 个堆栈帧。

什么时候准确地存储在堆栈中?

调用函数时,会分配一个栈帧,函数返回后,会释放栈帧。甚至对main 的函数调用也存储在堆栈中。但是,堆栈帧可能会被优化掉,但这是一个更高级的主题。

如果您想了解更多关于堆栈帧被优化掉的信息,请参阅我小时候问的这个问题:

Does a stack frame really get pushed onto the stack when a function is called?

附加编辑: 如果您想通过视觉了解递归是如何发生的,请将您的手指放在根的顶部,然后先沿着左侧向下的树的外部进行追踪:

【讨论】:

  • 这是了解空间要求的好答案。
  • 另外值得注意的是,任何时候都可以通过触摸单个帧并追溯您的方式来可视化整个堆栈。栈是栈而不是树,树只代表栈随时间演化的所有方式。
  • 建议将“调用函数时”更改为“调用函数后,但仅在该函数返回之前”。堆栈帧代表正在执行的代码,当该方法完成时它会消失。
【解决方案2】:

将堆栈帧想象成您发送给您一些信息的人工。暂时忘掉斐波那契,想想一个更简单的递归公式,总和。

int sum(int n) {
  if (n == 1) {
    return 1;
  } else {
    return sum(n - 1) + n;
  }
}

sum(1) = 1

sum(2) = sum(1) + 2 = 1 + 2 = 3

等等……

现在想象一下,当您致电 sum(n) 时,您派人去为您查找结果。在他给你你要求的结果之前,他还没有“完成”工作。但是,他还需要致电sum(n - 1),他只能通过让另一个人工作来做到这一点。现在我们有两个人同时工作!您要问的问题是同时工作的最大人数是多少?如果您包括自己,答案是n,对于上面实施的求和算法,对于您实施的斐波那契算法.

【讨论】:

    【解决方案3】:

    堆栈中对 fib 的调用次数是需要调用 fib 以评估 fib(n) 的次数。

    fib(50) 将有超过 49 次对 fib 函数的调用。

    举个小例子。如果您调用fib(3),这将调用fib(2)fib(1)fib(2) 将调用 fib(0)fib(1)。这已经是对fib(3) 的 5 次 fib 调用。

    对于较大的 n 值,每次调用 fib(3) 将导致总共 5 次调用,因为这些值没有存储在任何地方。 fib 评估的唯一方法是调用两个较小的值。

    斐波那契数列是递归使用不当的标准示例,因为成本是指数级的。我会让你计算出究竟需要多少电话。

    【讨论】:

    • 如果问题是“最多,一次将在堆栈中存储多少调用,则答案是 49。原始问题中没有说明这一点,但如果这是“显然”的答案,那么它可能就是问题所在。
    • 对斐波那契使用递归是完全可以的,只要你记住你的结果。
    • 是的,问题可能与堆栈中的最大空间要求有关。为此,请参阅 Kacy Raye 的回答。
    • 我确实相信这个问题暗示了在运行时的任何给定时间,堆栈中存储了多少最大调用。
    猜你喜欢
    • 1970-01-01
    • 2016-06-29
    • 2018-05-16
    • 2015-05-01
    • 2018-05-02
    • 2013-12-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多