在Python列表中交换元素的时间复杂度

7

如果我按照以下方式交换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)]

Case1 Profiling

$ 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}

Case2 Profiling

$ 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(K),其中K是你之前定义的。实际上,在你的帖子中早就已经说过它的时间复杂度是O(K)了。 - user2357112
写这个问题的时候,由于某些原因我感到困惑,并认为从列表/数组中删除是O(1),但实际上是访问O(1)。对于混淆造成的困扰,请见谅。我已经编辑了问题。 - ravi
2个回答

2
答案取决于您的Python实现。我假设您对默认的CPython实现感兴趣。从现在开始,我用“python”代替“cpython”。
在Python中,列表更多或少是类似于C中的数组。
删除其中一个元素需要移动其上方的所有元素,插入同样如此,因此这两个操作的时间复杂度为O(i),其中i是您删除/插入的项目之后的项目数量。您的第一个答案是O(1),所以好得多。
您的分析错误,因为该数组大小实际上相当小,并且您的计时还考虑了构建数组的时间。尝试以下实验:一次构建大小为100000的数组,并尝试100000次交换其中两个元素。您会发现第一个代码至少比第二个代码快100倍(这是我在我的电脑上得到的数字),它当然是在Python列表中交换元素的好方法。

0

我认为,这是O(1)的。这个链接Python列表中交换元素的最快方法展示了dis模块的使用。其中一个汇编指令是LOAD_FAST,它将元素插入到堆栈中。在堆栈中,插入和删除操作需要O(1)的时间。实际的交换指令是ROT_TWO,它再次交换两个堆栈的顶部元素,这基本上是插入和删除,所以我猜这也是O(1)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接