我正在阅读破解代码面试第六版,并且对第45页的内容有疑问。
有一个这样的示例算法:
int f(int n){
if (n <= 1)
return 1;
return f(n - 1) + f(n - 1);
}
对于算法,它给出以下注释:
此算法的空间复杂度将为O(N)。虽然我们有 树中总共有O(2 ^ N)个节点,在任何给定时间只有O(N)个存在。 因此,我们只需要有O(N)可用的内存即可。
我真的不明白为什么在任何给定时间只存在O(N)。他们不应该都在调用堆栈中吗?有人可以解释吗?
答案 0 :(得分:2)
一种更好的理解方式可能是绘制调用树,而不是调用堆栈。
调用树表示在函数生存期内进行的所有调用。在f(n)
下将有两个分支。每个都有您进行的函数调用
在调用f(n)
的下面,有两个调用来计算f(n-1)
。在每一个之下,还有另外2个f(n-2)
。依此类推。
如果单独在调用中需要固定数量的内存和固定数量的工作(在子调用中花费更多的时间和精力),则此树的大小表示您必须运行的总工作量该程序。那将是1 + 2 + 4 + ... + 2**n = (1 - 2**(n+1))/(1-2) = O(2**n)
。
但是,在任何给定时间需要的最大内存量是树的 depth 。因为一旦您从呼叫中返回,就可以完成操作并丢弃所需的内存。树的最大深度为n
,并且每次调用计算f(1)
时都会达到。这样您就可以分配,存储,计算某些东西,将其丢弃,然后在需要再次分配时可用。一遍又一遍。
尝试为n=3
绘制图片,然后逐步进行计算,您将看到要点。随着您的前进,您正在分配和释放内存。因此,您可以一次又一次地重复使用相同的内存,而不必使用大量的内存。
答案 1 :(得分:2)
它看起来像是指数空间复杂度O(2^n)
,因为要完成f()
,我们需要进行两次递归:
#4: f(4)
#3: (f(3) + f(3))
#2: (f(2) + f(2)) + (f(2) + f(2))
#1: ((f(1) + f(1)) + (f(1) + f(1))) + ((f(1) + f(1)) + (f(1) + f(1)))
我们可以看到,递归的次数呈指数增长,因此空间复杂度看起来像O(2^n)
。
另一方面,我们不会同时调用这两个函数。实际上,我们将完成第一个递归调用,获取值,然后完成第二个递归调用:
#4: f(4)
#3: f(3)
#2: f(2)
#1: f(1)
#4: f(4)
#3: f(3)
#2: f(2)
#1: (1 + f(1))
#4: f(4)
#3: f(3)
#2: f(2)
#1: (1 + 1) = 2
#4: f(4)
#3: f(3)
#2: (2 + f(2))
...
因此,在任何给定时间,我们仅需要O(n)
空间+ O(n)
作为临时值。
因此,此函数具有O(n)
个空间复杂度和O(2^n)
个计算复杂度,即递归。
我想这就是作者的意思。