【问题标题】:Complexity of creating list with concat operator in python在 python 中使用 concat 运算符创建列表的复杂性
【发布时间】:2018-12-31 23:08:22
【问题描述】:

我开始学习数据结构+算法,但遇到了一个问题。这是我正在测试的功能:

def create_list_with_concat(n):
    l = []
    for i in range(n):
        l = l + [i]

这是我的思考过程: 我知道 concat 运算符是 O(k) 其中k 是添加到原始列表的列表的大小。由于在这种情况下k 的大小始终为1,因为我们一次添加一个字符列表,因此 concat 操作采用1 步骤。由于循环迭代n 次,算法将执行n 步骤——每次迭代执行1 步骤。因此,算法的时间复杂度为O(n)。该算法的实际执行时间类似于T(n) = dn,其中d 是执行连接所需的时间。对于这样一个函数,我希望以下是正确的:当您将输入大小增加 10 倍时,输出(执行时间)将增加 10 倍,因为:

(x, dx) --> (10x, 10dx) --> 10dx/dx = 10

但是,当我在实际值和执行时间上实际测试算法时,这似乎并没有发生。相反,当我将输入大小增加 10 倍时,输出(执行时间)增加 100 倍,当我将输入大小增加 100 倍时,输出增加 10000 倍。这些输出表明二次时间函数和O(n squared)

这是我的完整代码:

import timeit
def create_list_with_concat(n):
    l = []
    for i in range(n):
        l = l + [i]

t1 = timeit.Timer("create_list_with_concat(100)", "from __main__ import 
create_list_with_concat")
print("concat ",t1.timeit(number=1)*1000, "milliseconds")
t1 = timeit.Timer("create_list_with_concat(1000)", "from __main__ 
import create_list_with_concat")
print("concat ",t1.timeit(number=1)*1000, "milliseconds")
# OUTPUT
# concat  0.05283101927489042 milliseconds
# concat  2.8588240093085915 milliseconds

非常感谢您的帮助。

【问题讨论】:

  • 您意识到l = l + [i]l += [i] 之间存在差异?
  • @WillemVanOnsem 就最终结果而言,没有。如果您指的是在较低级别发生的事情,那么如果您要分享您的知识会很好。
  • @chb:但问题涉及时间复杂度,并且就时间复杂度而言,对于列表来说存在差异。
  • @WillemVanOnsem 抱歉,在审核队列中,没有看到您发布了答案。

标签: python algorithm data-structures time-complexity


【解决方案1】:

时间复杂度不是O(N)

两个列表 A 和 B 的 concat 操作的时间复杂度为 O(A + B)。这是因为您没有添加到一个列表中,而是创建了一个全新的列表并使用来自 A 和 B 的元素填充它,这需要您对两者进行迭代。

因此,执行l = l + [i] 操作是O(len(l)),给您留下执行N 操作的N 步骤,从而导致O(N^2) 的整体复杂度

您将 concat 与 appendextend 函数混淆了,它们不会创建新列表而是添加到原始列表中。如果你使用这些函数,你的时间复杂度确实是O(N)

补充说明:

l = l + [i] 表示法可能会让人感到困惑,因为从直觉上看,[i] 似乎只是被添加到现有的l 中。这不是真的!

l + [i] 构建一个全新的列表,然后让l 指向该列表。

另一方面,l += [i] 修改了原始列表并表现得像extend

【讨论】:

  • l += [i] 应该只是 l.append(x)
  • 我只是想强调l += [i]l = l + [i]之间的区别
【解决方案2】:

这是我的思考过程:我知道 concat 运算符是 O(k) 其中 k 是要添加到原始列表的列表的大小。由于 k 的大小在这种情况下始终为 1,因为我们一次添加一个字符列表,因此 concat 操作需要 1 步。

这个假设是不正确的。如果你写:

l + [i]

你构造一个新列表,这个列表将有 m+1 个元素,其中 ml 中的元素数,假设一个列表的实现方式类似于数组,我们知道构造这样的列表需要 O(m) 时间。然后我们将 new 列表分配给l

也就是说总步数是:

 n
---
\              2
/    O(m) = O(n )
---
m=0

所以时间复杂度是O(n2)

但是,您可以通过使用l += [i] 甚至更快的l.append(i) 来提高性能,其中摊销成本l += [i]l.append(i) O(1 ),所以算法是O(n),但是l.append(i) 可能会更快一些,因为我们节省了构建新列表等操作。

【讨论】:

  • @juanpa.arrivillaga:是的,没错,但重读后,我意识到措辞可能有点“误导”,将编辑。
【解决方案3】:
>>> spam = []
>>> eggs = spam
>>> spam += [1]
>>> eggs
[1]
>>> spam = []
>>> eggs = spam
>>> spam = spam + [1]
>>> eggs
[]

改变一个列表和创建一个新列表在复杂性上有所不同。

【讨论】:

    猜你喜欢
    • 2012-12-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-18
    相关资源
    最近更新 更多