递归函数的空间复杂度
Posted
技术标签:
【中文标题】递归函数的空间复杂度【英文标题】:Space complexity of recursive function 【发布时间】:2017-09-04 01:36:32 【问题描述】:给定以下函数:
int f(int n)
if (n <= 1)
return 1;
return f(n - 1) + f(n - 1);
我知道Big O时间复杂度是O(2^N)
,因为每次调用都会调用两次函数。
我不明白为什么空间/内存复杂度是O(N)
?
【问题讨论】:
你的意思是像斐波那契数列那样 return f(n - 1) + f(n - 2) 吗? 【参考方案1】:解决这类问题的一个有用方法是考虑recursion tree。递归函数要识别的两个特征是:
-
树的深度(在基本情况之前将执行多少个返回语句)
树的宽度(总共将进行多少递归函数调用)
我们这个案例的递归关系是T(n) = 2T(n-1)
。正如您正确指出的,时间复杂度是 O(2^n)
,但让我们看看它与我们的递归树的关系。
C
/ \
/ \
T(n-1) T(n-1)
C
____/ \____
/ \
C C
/ \ / \
/ \ / \
T(n-2) T(n-2) T(n-2) T(n-2)
这种模式将一直持续到我们的基本情况,如下图所示:
随着每个连续的树级别,我们的 n 减少 1。因此,我们的树在到达基本情况之前将具有 n 的深度。由于每个节点有 2 个分支,我们总共有 n 个级别,因此我们的节点总数为2^n
,使我们的时间复杂度为O(2^n)
。
我们的内存复杂度由返回语句的数量决定,因为每个函数调用都将存储在程序堆栈中。概括地说,递归函数的内存复杂度为O(recursion depth)
。正如我们的树深度所暗示的那样,我们将有 n 个总返回语句,因此内存复杂度为 O(n)
。
【讨论】:
解释得很好。 ***.com/questions/33590205/… 这更清楚了 引用这个答案的关键外卖语句:“内存复杂度由返回语句的数量决定,因为每个函数调用都将存储在程序堆栈上。概括地说,递归函数的内存复杂度是 O (递归深度)。正如我们的树深度所暗示的那样,我们将有 n 个总返回语句,因此内存复杂度为 O(n)。但这是否意味着所有递归调用都具有 O(n) 空间复杂度? (函数总是只返回一次,对吧?)【参考方案2】:这是我的想法:
很有诱惑力的是,空间复杂度也将是 O(2^N),因为毕竟每个 O(2^N) 递归调用都必须分配内存,对吧? (不对) 实际上,这些值在每次调用时被加在一起/折叠,因此所需的 空格 将只是从基本情况开始的每次调用的结果,形成数组 [f(1 ), f(2), f(3) ... f(n)],换句话说就是 O(n) 内存【讨论】:
【参考方案3】:我在两篇文章中找到了明确的答案。
首先
在这个article,它告诉我为什么空间复杂度是O(n)
。
但我也很困惑,为什么the stack frames
一次只有f(5) -> f(4) -> f(3) -> f(2) -> f(1)
而没有f(5) -> f(4) -> f(3) -> f(2) -> f(0)
和其他人。
The Fibonacci tree
图片:
然后我终于在第二篇文章中找到了答案,它消除了我的困惑。
第二
article 这很有帮助。您可以在此处查看详细信息。
The stack frames
图片:
谢谢。
【讨论】:
【参考方案4】:考虑到不同的功能,这可以更好地解释 f(n) = f(n-1) + f(n-2) f(0) =0, f(1)=1
这将导致 f(4) 的以下计算树
f(4) f(3) f(2) f(2) f(1) f(1) f(0) f(1) f(0)
系统可以使用等于深度的重复存储堆栈(存储单元为 f(0)、f(1)、f(2)、f(3) 和 f(4))来处理计算。虽然运行时需要考虑每个节点上的所有操作(添加或返回语句) - 因此不是任何节点的一个因素。
【讨论】:
【参考方案5】:递归问题我们可以认为我们正在用堆栈实现,所以如果第一个函数调用自己,第二个函数暂停并且它遍历结束并一个一个添加到堆栈中,完成后它将返回并一个一个删除从最顶部的堆栈开始,然后第二个函数恢复并遍历结束并添加到堆栈的最顶部并在返回时删除。但它使用相同的堆栈,并且在同一个堆栈下最多占用 n 个空间,因此使用空间复杂度 O(n)。
【讨论】:
请花点时间整理一下您帖子的语法。连贯的句子非常难以理解。以上是关于递归函数的空间复杂度的主要内容,如果未能解决你的问题,请参考以下文章
尾递归 递归函数中,递归调用是整个函数体中最后的语句,且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归,空间复杂度是O