虽然我看了很多教程和课本读物,但在计算递归空间复杂性方面,我无法得到一个明确的答案.

大多数解释利用非常简单的递归函数,例如下面的阶乘函数:

def factorial(n):
  if n == 1:
    return 1
  return n * factorial(n - 1)

在上面的例子中,实际函数本身占用O(1)个辅助空间.然而,由于这是一个递归函数,我们还必须考虑这样一个事实,即调用将同时占用调用堆栈上的堆栈框架.当我们处于最大递归深度时,执行此函数所需的最大堆栈帧数量就达到了.在这种情况下,如果我们认为n是第0级的初始函数调用,那么我们的最大深度将在第n-1级达到,这意味着总共有n个级别.因此,所需堆栈帧的数量与n成正比;空间复杂度为O(N).

虽然这些例子既有教育意义又有帮助,但我找不到实际函数的空间复杂度不是O(1)的例子.例如,假设我们有下面的伪递归函数:

def factorial(n):
  cool = [i for i in range(n)]
  if n == 1:
    return 1
  return n * factorial(n - 1)

唯一的区别是,我们的函数现在在每次函数调用时创建一个长度等于n的列表;因此,(非递归的)函数的空间复杂性为O(N).那么,在这样的情况下,我们如何最终计算递归函数的总空间复杂性,并将调用堆栈也计算在内呢?

我们是否将每个级别(从级别0到级别n-1)上的单个递归函数占用的空间相加,并对其应用大O记法?

在这种情况下,第0级上的单个函数调用需要n个空间,第1级上的单个函数调用需要n-1个空间,第2级上的单个函数调用需要n-2个空间,依此类推.

因此,总空间可以用以下n个术语总和来表示:

N+n-1+n-2+....+2+1=n(n+1)/2

因此,使用大O记数法,空间复杂度将简化为O(n^2).这样对吗?更广泛地说,我想知道如何计算递归空间复杂性,特别是当我们的函数本身没有O(1)辅助空间时.

推荐答案

空间复杂性指的是任何时候使用的最大内存量.

调用堆栈上的执行函数所需的空间通常受一个常量的限制,因此您只需将堆栈深度添加到任何时候可能正在使用的所有其他内存.

阶乘函数占用O(N)个堆栈空间,因此需要将其添加到其他所有元素中.

如果我们假设所有这cool个列表都在"使用",直到它们的封闭函数结束,那么您的推理是正确的,factorial按照您给出的原因占用了O(n^2)个空间.

然而,它实际上并不是这样运作的.因为在递归调用返回之后实际上并不需要use cool,所以编译器可以通过递归调用来判断没有必要保留它,并可以首先释放它.在这种情况下,任何时候都只有one cool个列表被"使用",并且您的函数将只占用O(N)空间.不过,我测试了我的python,它不会进行这种优化.

如果您仔细考虑一下,您可能会担心,即使当其中一个列表被提前删除并且没有使用时,垃圾收集过程也需要一些时间来识别这一点并实际释放内存,所以可能您的函数毕竟占用了O(n^2)空间……但事实并非如此.垃圾收集算法通常被设计成确保程序的总体空间复杂性是最优的,即不被垃圾收集过程改变.

Python相关问答推荐

为什么图像结果翻转了90度?

正在设置字段.需要为假,因为错误列表索引必须是整数或切片,而不是字符串

每个组每第n行就有Pandas

在Transformer中使用LabelEncoding的ML模型管道

自定义新元未更新参数

如何将Matplotlib的fig.add_axes本地坐标与我的坐标关联起来?

如何在Python中使用时区夏令时获取任何给定本地时间的纪元值?

使用plotnine和Python构建地块

如何比较numPy数组中的两个图像以获取它们不同的像素

试图找到Python方法来部分填充numpy数组

numba jitClass,记录类型为字符串

PywinAuto在Windows 11上引发了Memory错误,但在Windows 10上未引发

Python上的Instagram API:缺少client_id参数"

Python 约束无法解决n皇后之谜

如何使用根据其他值相似的列从列表中获取的中间值填充空NaN数据

加速Python循环

如何在python xsModel库中定义一个可选[December]字段,以产生受约束的SON模式

我想一列Panadas的Rashrame,这是一个URL,我保存为CSV,可以直接点击

将输入聚合到统一词典中

如何在PySide/Qt QColumbnView中删除列