Python 递归函数的求值&x27;时间复杂度 我想要什么

Python 递归函数的求值&x27;时间复杂度 我想要什么,python,algorithm,list,time-complexity,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) 最好的

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

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

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²)
    。因此,对于包含
    n
    简单元素的列表,整个函数将是
    O(n²)

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

  • 对于
    n
    列表中的
    n
    列表中的每个
    n
    元素,第5行将是
    O(n⁵)O(n⁶)

问题是什么 我很清楚,复杂性不仅取决于列表的长度,而且由于没有更好的词,列表的深度也会影响列表的深度。

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
我想说这个函数的复杂性是
O(n^(2·k))
,其中
k
是深度。
对于简单列表,
k=1
;对于简单列表列表,
k=2
;等等


此分析正确吗?如果不正确,它有什么问题,正确答案是什么?

查看带有行号的函数以供参考。另外,感谢@Veedrac指出,在Python中,append是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)*(循环迭代的大Oh)。所以问题是第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意味着元素的总数将是有用的是正确的。

你被
n
的一个糟糕(又称“不有用”)定义稍微误导了。此外,你似乎认为
append
是摊销
O(n)
。它不是;它是摊销的
O(1)

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

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

这只是

T(l) = O(len(l)) * [T(lᵢ) or O(1)] + 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ᵢ) 或O(1)
取决于
l的类型ᵢi
上,我们不能像平常一样解决这个递归


我们已经知道,我们的递归可以在非正方形甚至非矩形数组上工作。这意味着我们不能只在
n
中用
n
列表的长度参数化它

相反,我们可以用不同的量来参数化它

我们知道递归将总共遍历
N
个元素

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

T(l) = O(len(l)) * [T(lᵢ) or O(1)] + 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(Σn^k from k=0 to k=i-1) · O(n²) + 
= O(nⁱ⁺¹ + i) = O(nⁱ⁺¹) as i+1 ≥ 2
也就是说,递归地展开

[
    [[[[...[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))
那么为什么不在
n
n
中进行参数化呢?为什么只是一个文本框

那是因为这就是你所需要的,也是你所能给予的。如果你有另一个定义,比如:

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

然后你可以用变量(
O(∑x+πx)
)来求解,但这不是问题给出的结果


注意
O(nⁱ⁺1)
,正如前面所建议的,对于维度
(n,n,n,…,n)
i
维度的东西不太合适。它应该是
O(nⁱ + i) 


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

当你有两个变量时,通常(但不总是)值得考虑只有一个变量较大的情况ⁱ) > O(i)
,但是如果你可以有任何
n
i
,那么
让n=1
O(nⁱ) < O(i)

基本上,
O(nⁱ + i) =O(n)ⁱ)
当且仅当您希望它时。如果案例
n=1
i large
很重要,请包含
+i
。否则,请不要


通过设置
i=1
和计时,我们可以快速测试电源是否为
i
,而不是
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,花费的时间增加了10,这意味着线性成本。

另一方面,假设你有

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

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))
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