评估递归函数时间复杂度

时间:2014-06-09 15:45:24

标签: python algorithm list time-complexity

我想要什么

我试图找到函数的时间复杂度。

  

评估算法每个步骤的复杂程度以及整个函数的复杂性。

1 def deep_copy(ls):
2     new=[]
3     for e in ls:
4         if type(e) == list:
5             new.append(deep_copy(e))
6         else:
7             new.append(e)
8     return new

我尝试了什么

  • 我知道第2,4,6和8行都是O(1)

  • 最好的情况是被复制的列表只有简单的元素(如果你愿意的话,列表中没有列表)。

    如果是这种情况,那么第7行的最大复杂度为O(n),使第3行for循环的复杂度为O(n·n) = O(n²)。因此,对于包含O(n²)个简单元素的列表,整个函数将为n

  • 现在,我们假设我们列出了n个列表,每个列表都包含n个元素。根据之前的结果,我知道第5行是O(n³),因为它是嵌套在O(n²)中的O(n)。第3行将是O(n⁴),因为第5行执行n次,这使得此案例的整体复杂度O(n⁴)

  • 对于nn列表的列表,每个列表都包含n元素,第5行将是O(n⁵),因此整个循环和函数将是O(n⁶)

问题是什么

我很清楚,复杂性不仅取决于列表的长度,而且还因为缺少更好的词,列表的深度。

我想说这个函数的复杂度为O(n^(2·k)),其中k是深度 对于一个简单的列表,k = 1;获取简单列表列表k = 2;等

这种分析是否正确?如果没有,它有什么问题,答案是什么?

3 个答案:

答案 0 :(得分:2)

你对n的错误(又名“无用”)定义有点误导。此外,您似乎认为append已摊销O(n)。它不是;它已摊销O(1)

考虑在列表T(l)上花费时间l的操作。

new=[]
for e in ls:
    if type(e) == list:
        new.append(deep_copy(e))
    else:
        new.append(e)
return new

O(1) +                     [O(1) assign]
O(len(l)) * (              [len(l) loops + O(1) overhead for each loop]
    (O(1) +                [O(1) if]
        O(1) + T(lᵢ)) or   [O(1) append and T(lᵢ) recursion]
    (O(1) +                [O(1) else]
        O(1))              [O(1) append]
) + O(1)                   [O(1) return]

只是

T(l) = O(len(l)) * [T(lᵢ) or O(1)] + O(1)

请注意,因为T(lᵢ) or O(1)依赖两者 lᵢ i上的,我们无法解决这种递归就像正常一样。


我们的递归适用于非正方形甚至非矩形数组。这意味着我们不能仅在n中使用n列表的长度对其进行参数化。

相反,我们可以以不同的数量对其进行参数化。

我们知道递归将总共遍历N个元素。这意味着我们会有像

这样的东西
[
    T(l₁) +
    T(l₂) +
    T(l₃) +
    T(l₄) +
    ...
] + O(1)

只是

[
    [T(l₁₁) + T(l₁₂) + T(l₁₃) + T(l₁₄) + ... + O(1)] +
    [T(l₂₁) + T(l₂₂) + T(l₂₃) + T(l₂₄) + ... + O(1)] +
    [T(l₃₁) + T(l₃₂) + T(l₃₃) + T(l₃₄) + ... + O(1)] +
    [T(l₄₁) + T(l₄₂) + T(l₄₃) + T(l₄₄) + ... + O(1)] +
    ...
] + O(1)

[
    [T(l₁₁) + T(l₁₂) + T(l₁₃) + T(l₁₄) + ...] +
    [T(l₂₁) + T(l₂₂) + T(l₂₃) + T(l₂₄) + ...] +
    [T(l₃₁) + T(l₃₂) + T(l₃₃) + T(l₃₄) + ...] +
    [T(l₄₁) + T(l₄₂) + T(l₄₃) + T(l₄₄) + ...] +
    ...
] + O(1) + T(len(l))

,递归展开,

[
    [[[[...[O(1) + O(1) + ...]...]]]] +
    [[[[...[...]...]]]] +
    [[[[...[...]...]]]] +
    [[[[...[...]...]]]] +
    ...
] + O(1) + O(len(l)) +
  + O(len(l₁)) + ... + O(len(l₄)) + ... +
  + O(len(l₁₁) + ... + O(len(l₄₄)) + ... +
  + O(len(l₁₁₁) + ...
  + ...

第一部分(在[] s中)添加到O(N)。第二个是列表长度的总和

显然,列表长度的总和至少与基本级别项目的总数一样大。我们不能将基础级项目的数量作为答案,但是,

[[[...[[[[1]]]...]]]

需要很长时间才能复制,但只有一个项目。


所以我们的答案是

O(sum(number of items in each list, including all sublists))

那么为什么不在nN中进行参数化呢?为什么只是一盒文字?

嗯,那是因为这就是你所需要的,而你所能提供的一切。如果您有其他定义,例如:

  

数组的尺寸为(x₁, x₂, x₃, ..., xₙ)

你可以然后解决变量(O(Σx + Πx))。但这不是问题所给出的。


请注意,正如之前所建议的那样,O(nⁱ⁺¹)对于具有(n, n, n, ..., n)维度的维度i来说并不完全正确。它应该是O(nⁱ + i)


现在,我确实做了很奇怪的事。我没有写O(nⁱ + i) = O(nⁱ)

当你有两个变量时,通常(但并非总是)值得考虑只有一个的情况。通常为O(nⁱ) > O(i),但如果您可以有ni,那么let n = 1O(nⁱ) < O(i)

基本上,O(nⁱ + i) = O(nⁱ) 当且仅当您想要时才会这样做。如果案例n = 1i large很重要,请添加+ i。否则,不要。


我们可以通过设置i和时间来快速测试功率为i+1,而不是i=1

SETUP="
def deep_copy(ls):
    new=[]
    for e in ls:
        if type(e) == list:
            new.append(deep_copy(e))
        else:
            new.append(e)
    return new
"

python -m timeit -s "$SETUP" -s "x = [0] * 10**5" "deep_copy(x)"
python -m timeit -s "$SETUP" -s "x = [0] * 10**6" "deep_copy(x)"

给出

10 loops, best of 3: 20.8 msec per loop
10 loops, best of 3: 209 msec per loop

如我所说,当长度增加10倍所花费的时间增加10倍意味着线性成本。

答案 1 :(得分:1)

查看带有行号的功能以供参考。另外,感谢@Veedrac指出append在Python中是O(1)。

1 def deep_copy(ls):
2     new=[]
3     for e in ls:
4         if type(e) == list:
5             new.append(deep_copy(e))
6         else:
7             new.append(e)
8     return new

如你所说,第2,4,6和8行都是O(1)。第7行是O(1),第3行是O(n)*(循环迭代的大哦)。所以问题是第5行的时间复杂性。

第5行大致相当于:

_ = deep_copy(e)
new.append(_)

这个时间复杂度是多少? deep_copy(e) 的时间复杂度加上附加的时间复杂度 - O(1)。如果e不包含任何列表,则第5行的时间复杂度将为O(n),从而导致整个函数为O(n ^ 2)。因此,时间复杂度不是O(n ^(2 * k)),而应该是O(n ^ k)。

但是,到目前为止,n已被用于表示列表的长度。如果我们使用n来表示所有列表中元素的总数,该怎么办?我没有经历并确定时间复杂度基于元素总数,但值得研究。

修改: 我更新了我的计算以反映附加有O(1)。另外,@ Veedrac表明我认为n表示元素总数是有用的是正确的。

答案 2 :(得分:0)

另一方面,假设你改为

def deep_copy(ls):
    new=[]
    for e in ls:
        if type(e) == list:
            new = new + [deep_copy(e)]
        else:
            new = new + [e]
    return new

new = new + [...] O(n)。那实际上是时间的复杂性呢?

从我的其他答案中得出结果,

T(l) = O(len(l)) * [T(lᵢ) or O(1)] + O(1)

我们知道[]内的每条路径的成本都有额外的O(len(l))

T(l) = O(len(l)) * [T(lᵢ) + O(len(l)) or O(1) + O(len(l))] + O(1)
     = O(len(l)) * [(T(lᵢ) or O(1)) + O(len(l))] + O(1)

但是通过展开我们刚刚拥有的括号:

T(l) = O(len(l)) * [T(lᵢ) or O(1)] + O(len(l))·O(len(l)) + O(1)
     = O(len(l)) * [T(lᵢ) or O(1)] + O(len(l)²)

这意味着我们的费用是

O(sum(square of number of items in each list, including all sublists))

因此对于具有(n, n, n, ..., n)维度的维度i,我们有

O(
    n² +            first level; one list of length n
    n · n² +        second level; n lists of length n
    n · n · n² +    third level; n² lists of length n
    ... +
    nⁱ⁻¹ · n²         "i"th level; nⁱ⁻¹ lists of length n
)

O(Σn^k from k=0 to k=i-1) · O(n²) + 
= O(nⁱ⁺¹ + i) = O(nⁱ⁺¹) as i+1 ≥ 2
不出所料,这是Rob Watts在O(n)追加时所拥有的。