递归斐波那契算法的空间复杂度是多少?
Posted
技术标签:
【中文标题】递归斐波那契算法的空间复杂度是多少?【英文标题】:What is the space complexity of a recursive fibonacci algorithm? 【发布时间】:2015-04-29 15:10:37 【问题描述】:这是Cracking the Coding Interview(第5版)中斐波那契数列的递归实现
int fibonacci(int i)
if(i == 0) return 0;
if(i == 1) return 1;
return fibonacci(i-1) + fibonaci(i-2);
看了这个算法时间复杂度的视频Fibonacci Time Complexity,我现在明白了为什么这个算法运行在O(2n)。但是,我正在努力分析空间复杂性。
我上网查了一下,对此有疑问。
在这个 Quora 帖子中,作者指出“在你的情况下,你有 n 个堆栈帧 f(n)、f(n-1)、f(n-2)、...、f(1) 和奥(1)”。你不会有 2n 个堆栈帧吗?说 f(n-2) 一帧将用于实际调用 f(n-2) 但不会还有来自 f(n-1) 的调用 f(n-2) 吗?
【问题讨论】:
常数因素在大 O 复杂度中并不重要—— O(n) 和 O(2n) 是相同的。也就是说,在第一次调用返回后,堆栈帧将被回收并重用于第二次调用。 这是 LaTeX 数学符号,2 的 n 次方吗?你的意思是 2 倍 n 吗? @chrislott 你能对数学 jax 进行编辑吗?我的意思是 2 的幂 n @ChrisDodd 所以在计算机中,有一个 f(n-2) 的堆栈帧,并且因为有两次调用而被使用了两次? @committedandroider:您可以编辑自己的帖子。 *** 上没有 MathJax,所以你必须用 html 来做。 【参考方案1】:在我看来,这个过程一次只会下降一个递归。 第一个 (f(i-1)) 将创建 N 个堆栈帧,另一个 (f(i-2)) 将创建 N/2。 所以最大的是N。另一个递归分支不会占用更多空间。
所以我会说空间复杂度是 N。
由于 f(i-2) 小于 f(i-1) 空间,因此一次只计算一个递归,这一事实允许忽略它。
【讨论】:
哦,是不是因为另一个 f(i-2) 会等待第一个 f(i-1) 完成? @committedandroider。是的,在每个级别,所有 f(i-1) 都在 f(i-2) 开始之前执行。请注意,较低级别的 f(i-2) 被评估为下一个较高级别 f(i-1) 的一部分。【参考方案2】:这里有一个提示。使用 print 语句修改您的代码,如下例所示:
int fibonacci(int i, int stack)
printf("Fib: %d, %d\n", i, stack);
if (i == 0) return 0;
if (i == 1) return 1;
return fibonacci(i - 1, stack + 1) + fibonacci(i - 2, stack + 1);
现在在 main 中执行这一行:
Fibonacci(6,1);
打印出的“堆栈”的最高值是多少。你会看到它是“6”。尝试“i”的其他值,您会发现打印的“stack”值永远不会超过传入的原始“i”值。
由于 Fib(i-1) 在 Fib(i-2) 之前被完全评估,所以递归级别永远不会超过 i
。
因此,O(N)。
【讨论】:
那个栈变量有什么意义? @committedandroider 我通常称它为depth
而不是stack
。它跟踪递归级别的数量,以便printf
可以显示它。 (您也可以使用它来限制递归深度,但这里不需要。)
空间复杂度更多地取决于递归级别还是堆栈帧数?
@committedandroider 每一层递归都有一个栈帧,所以使用的空间等于“最深的递归层”乘以“栈的大小”每个级别的框架”。 空间复杂度只是“最深层次的递归”,因为每层的堆栈帧大小是一个常数乘数,因此在复杂度分析中被忽略。
@committedandroider 是和否。 f(3,2)
实例将设置一个堆栈帧。在f(3,2)
返回后,堆栈内存被回收。对f(2,2)
的调用将在内存中的同一位置设置不同的堆栈帧。所以他们是否使用相同的堆栈框架取决于您如何定义“相同的堆栈框架”。【参考方案3】:
递归实现的近似时间复杂度为 2 平方 n (2^n),这意味着该算法必须经过大约 64 个计算步骤才能获得第 6 个斐波那契数。这是巨大的并且不是最优的,大约需要程序 1073741824 的计算步骤才能得到 30 的斐波那契数,这是一个很小的数字,这是不可接受的。实际上,迭代方法肯定要好得多且经过优化。
知道了这个实现的时间复杂度之后,你可能会认为它的空间复杂度可能是一样的,其实不然。此实现的空间复杂度等于 O(n),并且永远不会超过它。那么,让我们揭开“为什么”的神秘面纱?
这是因为递归执行的函数调用乍一看似乎是并发执行的,但实际上它们是按顺序执行的。
顺序执行保证堆栈大小永远不会超过上述调用树的深度。程序首先执行所有最左边的调用,然后向右转,当 F0 或 F1 调用返回时,它们对应的堆栈帧被弹出。
在下图中,每个矩形代表一个函数调用,箭头代表程序到达递归结束所经过的路径:
这里我们可以注意到,当程序到达调用F4F3F2F1并返回1时,它又回到它的父调用F4F3F2执行右递归子调用F4F3F2F0,当F4F3F2F0和F4F3F2F1调用都返回时,程序返回到 F4F3。所以栈永远不会超过最长路径F4F3F2F1的大小。
程序将遵循同样的执行模式,从父节点到子节点,在执行左右调用后返回父节点,直到到达所有 F4 的最后一个根父节点,得到计算得出的斐波那契之和为 3。
【讨论】:
这个答案应该被接受恕我直言以上是关于递归斐波那契算法的空间复杂度是多少?的主要内容,如果未能解决你的问题,请参考以下文章