递归调用的空间复杂度

时间:2018-10-31 21:26:05

标签: algorithm space-complexity

我正在阅读破解代码面试第六版,并且对第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)。他们不应该都在调用堆栈中吗?有人可以解释吗?

2 个答案:

答案 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)个计算复杂度,即递归。

我想这就是作者的意思。