【问题标题】:Time complexity of swapping elements in a python list在 python 列表中交换元素的时间复杂度
【发布时间】:2015-06-10 01:41:14
【问题描述】:

如果我执行以下操作,在 python 列表中交换元素的时间复杂度是多少

案例1:(惯用方式)交换列表中的两个元素

>>> a = [1, 2, 3, 4, 5]
>>> a[1], a[3] = a[3], a[1]  # Pythonic Swap --> x, y = y, x

>>> print(a)
[1, 4, 3, 2, 5]

问题1:交换步骤的时间复杂度是多少?而python内部是做什么的。


案例2:(非常低效的方式)交换列表中的两个元素

>>> a = [1, 2, 3, 4, 5]
>>> temp1, temp2 = a[1], a[3]
>>> del a[1]  # a = [1, 3, 4, 5]
>>> a.insert(1, temp2)  # a = [1, 4, 3, 4, 5]
>>> del a[3]  # a = [1, 4, 3, 5]
>>> a.insert(3, temp1)  # a = [1, 4, 3, 2, 5]
>>> print(a)
[1, 4, 3, 2, 5]

如果我这样做,每当我在任何索引处插入或删除时,该索引右侧的所有内存地址都需要分别向右或向左移动/复制一步。所以它需要 O(K),其中 K 是我们插入或删除的索引中存在的地址数。如果我错了,请纠正我。


一些分析

如果列表非常小,那么我使用哪种方法(Case1 或 Case2)运行时间复杂度并不重要。但是如果列表非常大,比如 a = [i for i in range(0,1000000)] 那么交换列表中两个元素的有效方法是什么?在这里,我对上述两个案例进行了一些基本的分析,一百万个记录列表交换索引 100 和 54321,这是结果。令人惊讶的是,这两种情况的性能几乎相同。

a = [i for i in range(0,1000000)]

案例 1 分析

$ time python3 case1_script.py

real    0m0.129s
user    0m0.088s
sys     0m0.041s

$ python3 -m cProfile case1_script.py

3 function calls in 0.060 seconds

Ordered by: standard name

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    1    0.047    0.047    0.060    0.060 list_ele_swap_profile.py:1(<module>)
    1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
    1    0.013    0.013    0.013    0.013 {range}

案例 2 分析

$ time python3 case2_script.py

real    0m0.132s
user    0m0.090s
sys     0m0.042s

$ python3 -m cProfile case2_script.py

5 function calls in 0.115 seconds

Ordered by: standard name

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     1    0.048    0.048    0.115    0.115 list_ele_swap_profile.py:1(<module>)
     1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
     2    0.001    0.001    0.001    0.001 {method 'insert' of 'list' objects}
     1    0.066    0.066    0.066    0.066 {range}

问题2:如果列表非常大,那么在列表中交换两个元素的有效方法是什么。

问题 3: 在考虑这个问题时,我还有一个疑问,所以如果从列表需要复制/移动内存地址,那么从列表中删除一个元素的时间是 O(1)。


PS:我不认为这是一个重复的问题,我已经完成了以下问题,但没有一个是我想要的。我有兴趣了解上述操作的时间/空间复杂度。谢谢。

【问题讨论】:

  • “从列表中删除一个元素是 O(1)” - 是什么让你想到它是 O(1)?它是 O(K),其中 K 的定义方式与您之前定义的方式相同。事实上,您在前面的帖子中已经说过这是 O(K)。
  • 好吧,在写这个问题时出于某些原因我很困惑,并认为从列表/数组中删除是 O(1),但它是访问 O(1)。对困惑感到抱歉。我编辑了问题。

标签: python list time-complexity


【解决方案1】:

答案取决于您对 python 的实现。 我假设您对默认的 CPython 实现感兴趣。 从现在开始,我说“python”而不是“cpython”。

python 中的列表或多或少类似于 C 中的数组。

删除其中的元素需要移动其上方的所有元素,插入也是如此,因此这两个操作是 O(i),其中 i 是您删除/插入的项目之后的项目数。 你的第一个答案是 O(1),这样更好。

你的分析是错误的,因为这个数组的大小实际上很小,而且你的时间也考虑了构建数组的时间。 尝试以下实验:构建一个大小为 100000 的数组一次,并尝试 100000 次交换其中的两个元素。 你会看到第一个代码比第二个代码至少快 100 倍(这是我在我的计算机上获得的数字),这当然是在 python 列表中交换元素的好方法。

【讨论】:

    【解决方案2】:

    我认为是 O(1)。这个链接Fastest way to swap elements in Python list 显示了 dis 模块的使用。执行此操作的汇编指令之一是 LOAD_FAST,它将元素插入堆栈。插入和删除操作在堆栈中占用 O(1)。实际的交换指令是 ROT_TWO ,它再次交换两个堆栈的顶部元素,基本上是插入和删除,所以我猜这也是 O(1)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-01-13
      • 2017-05-18
      • 2022-11-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多