【问题标题】:Space and time complexity of recursive implementation of summation function求和函数递归实现的空间和时间复杂度
【发布时间】:2020-04-08 21:43:44
【问题描述】:

谁能建议以下代码的空间和时间复杂度? 我知道时间复杂度应该是 O(n) 因为函数被调用 n 次,空间复杂度至少是 O(n) (因为堆栈空间),但是将 a[1:] 传递给函数会导致空间复杂度增加?我认为 a[1:] 会在省略第一个元素的同时创建 a 的新副本,对吗?

def sum(a):
    if len(a) == 1:
        return a[0]
    return a[0] + sum(a[1:])

【问题讨论】:

  • " 我知道时间复杂度应该是 O(n),因为函数被调用了 n 次," 不是。当然,该函数被调用 N 次,所以它至少 O(N)...但是您正在函数中执行某些操作,不是吗?这不是恒定的时间。所以它不会是 O(N)...所以是的,你是对的 a[1:]。结果是你的时间复杂度是 O(N**2)

标签: python performance recursion time-complexity space-complexity


【解决方案1】:

作为递归函数,如果没有应用tail-call 优化,考虑到它在内存堆栈上的执行,在这种情况下它肯定会有至少O(n) 的空间复杂度。但是让我们进一步分析一下:

时间复杂度

我们知道 sum 是递归的,它的停止条件是输入数组是单长度的。所以我们知道,考虑到大小为n 的输入数组,在最坏的情况下Sum 将至少被调用O(n) 次。考虑一下递归是什么,即。例如,一个循环

然而,在函数内部,我们有一个切片操作。切片操作l[a:b]O(b-a),所以这个操作第一次运行的复杂度为O(n-1),第二次运行的复杂度为O(n-2),以此类推。我们最初认为,它将执行整个数组的副本。这个函数的总体时间复杂度应该是O(n^2),因为它会在大小为n 的数组中为每个项目创建一个切片。

空间复杂度

现在谈谈内存中的空间。

len(a) == 1

这里我们有一份来自 len(a) 的返回值的副本。

return a[0]

&

    return a[0] + sum(a[1:])

在上面的两行中,我们将有一个值的另一个副本,该副本将存储到函数的返回地址中。该切片还具有O(n) 空间复杂度。

看到这一点,考虑到编译器没有应用重大优化,例如reduction,我们说这个函数的空间复杂度是O(n),因为它会产生一个常量 每个输入的副本数 AND 将在大小为 n 的数组中执行切片操作。

由于我们一开始就说过递归就像一个循环,考虑到没有尾调用优化,整个函数将在最坏的情况下执行n 次。程序将增加函数的内存堆栈,直到它达到停止条件,直到它最终可以从调用堆栈中“弹出”返回值。因此,总空间复杂度也是O(n*log n)(因为每次执行输入数组都更小)。

附言:

根据this,我还认为len(a) 的时间复杂度为O(1)

【讨论】:

  • " 第一次运行的复杂度为 O(n-1),第二次运行的复杂度为 O(n-2),依此类推。我们认为原始地,它将执行整个副本数组。由于摊销因子,此函数的时间复杂度应为 O(n*log n)。我不跟。在我看来,对于给定的大小序列N,您创建大小为N-1, N-2, ..., 3, 2, 1 的切片,您有n*(n-1)/2 所以O(N^2) 在第一次调用时说它的复杂度为O(N-1),第二个的 O(N-2) 不正确。
【解决方案2】:

时间复杂度类似于 theta(n^2) ,因为每次执行 a[i:] 时,您基本上都是从 i 复制列表到末尾,因此您必须遍历它。至于空间复杂度,应用程序堆栈将包含您将调用的所有列表,首先是一个包含 n 个元素的列表,然后是 n-1,依此类推,直到 1,您将开始清空堆栈。所以你最终也会得到一个 theta(n^2) 的复杂度。

【讨论】:

    猜你喜欢
    • 2019-04-21
    • 2018-02-10
    • 2019-03-02
    • 2018-07-23
    • 1970-01-01
    • 1970-01-01
    • 2023-03-04
    • 2017-09-04
    • 1970-01-01
    相关资源
    最近更新 更多