这是Cracking the Coding Interview(第5版)中Fibonacci序列的递归实现
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(2 n )中运行。然而,我正在努力分析空间复杂性。
我在线查看并对此有疑问。
在这个Quora主题中,作者声明“在你的情况下,你有n个堆栈帧f(n),f(n-1),f(n-2),...,f(1)和O(1)“。你不会有2n堆栈帧吗?假设为f(n-2)一帧将用于实际呼叫f(n-2)但是不会有来自f(n-1)的呼叫f(n-2)?
答案 0 :(得分:20)
这是一个提示。使用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”的其他值,您将看到打印的“堆栈”值永远不会超过传入的原始“i”值。
由于Fib(i-1)在Fib(i-2)之前被完全评估,因此递归的永久性不会超过i
。
因此,O(N)。
答案 1 :(得分:11)
如果其他人仍然感到困惑,请务必查看此Youtube视频,该视频讨论了生成Fibonacci序列的空间复杂性。 Fibonacci Space Complexity
演示者非常清楚为什么空间复杂度为O(n),递归树的高度为n。
答案 2 :(得分:2)
正如我所看到的,该过程一次只会下降一次递归。 第一个(f(i-1))将创建N个堆栈帧,另一个(f(i-2))将创建N / 2。 所以最大的是N.另一个递归分支不会占用更多的空间。
所以我说空间复杂度是N.
事实上,在允许f(i-2)被忽略的情况下,只有一次递归被评估,因为它小于f(i-1)空间。
答案 3 :(得分:2)
递归实现的时间复杂度近似为 2 平方 n (2^n),这意味着该算法将必须经过大约 64 个计算步骤才能获得第 6 个斐波那契数。这是巨大的而不是最优的,程序需要大约 1073741824 个计算步骤才能得到 30 的斐波那契数,这是一个很小的数字,这是不可接受的。实际上,迭代方法绝对是更好和优化的方式。
知道了这个实现的时间复杂度后,可能会认为它的空间复杂度可能是一样的,其实不然。此实现的空间复杂度等于 O(n),并且永远不会超过它。那么,让我们揭开“为什么”的神秘面纱。
这是因为递归执行的函数调用乍一看似乎是并发执行的,但实际上它们是顺序执行的。
顺序执行保证堆栈大小永远不会超过上面说明的调用树的深度。程序首先执行所有最左边的调用,然后转到右侧,当 F0 或 F1 调用返回时,它们对应的堆栈帧会被弹出。
在下图中,每个矩形代表一个函数调用,箭头代表程序到达递归结束的路径:
这里我们可以注意到,当程序到达调用 F4F3F2F1 并返回 1 时,它会回到其父调用 F4F3F2 执行右递归子调用 F4F3F2F0,当 F4F3F2F0 和 F4F3F2F1 调用都返回时,程序返回到 F4F3。所以栈永远不会超过最长路径F4F3F2F1的大小。
程序将遵循相同的执行模式,从父级到子级,执行左右调用后返回父级,直到到达所有 F4 的最后一个根父级,得出斐波那契数的和为 3。
>