在 Python 中从列表前面删除一个元素是不是便宜?

Posted

技术标签:

【中文标题】在 Python 中从列表前面删除一个元素是不是便宜?【英文标题】:Is removing an element from the front of a list cheap in Python?在 Python 中从列表前面删除一个元素是否便宜? 【发布时间】:2017-06-20 04:24:57 【问题描述】:

我正在编写一个程序,它在数据列表的前面或后面进行大量删除,而不是中间。

我知道删除最后一个元素很便宜,但是删除第一个元素呢?例如,假设列表A 的地址位于4000,那么元素0 位于4000,元素1 位于4001

将删除元素0,然后让编译器将列表A 的地址放在4001,或者将4001 的元素1 移动到4000 的位置,然后将所有其他元素下降了1?

【问题讨论】:

不,当您从中间或前面删除时,它会调整大小。此外,Python 列表不等同于 C 数组。虽然,在内心深处,有一个 Py_Object 指针数组。 【参考方案1】:

不,它并不便宜。从列表的前面删除一个元素(例如使用list.pop(0))是一个O(N) 操作,应该避免。同样,在开头插入元素(使用list.insert(0, <value>))同样效率低下。

这是因为,在调整列表大小后,它的元素必须移动。对于 CPython,在 l.pop(0) 的情况下,this is done with memmove 而对于 l.insert(0, <value>),the shifting is implemented with a loop through the items stored。

列表是为快速随机访问O(1)在其结束操作而构建的。


由于您经常执行此操作,因此您应该考虑使用deque from the collections module(正如@ayhan 在评论中建议的那样)。 deque 上的文档还强调了 list 对象不适合这些操作:

虽然列表对象支持类似的操作,但它们针对快速固定长度操作进行了优化,并且pop(0)insert(0, v) 操作带来了O(n) 内存移动成本这会改变底层数据表示的大小和位置。

(强调我的)

deque 数据结构为双方(开始和结束)提供O(1) 复杂性,appendleft/popleftappend/pop 方法分别用于开始和结束。

当然,对于小尺寸,这会产生一些额外的空间要求(由于 deque 的结构),这通常应该是无关紧要的(正如@juanpa 在评论中指出的那样,并不总是成立)列表的大小会增长。最后,正如@ShadowRanger 富有洞察力的评论所指出的那样,对于非常小的序列大小,从前面弹出或插入的问题会变得微不足道,以至于它变得完全无关紧要。

因此,简而言之,对于包含许多项目的列表,如果您需要从两侧快速追加/弹出,请使用deque,否则,如果您随机访问并追加到末尾,请使用lists。

【讨论】:

实际上,根据我的经验,deques 比列表。尝试一个简单的测试,for i in range(0, 10000000, 100000): l,d = list(range(i)), deque(range(i)); print(sys.getsizeof(l), sys.getsizeof(d))。列表总是稍大一些。 @juanpa.arrivillaga 您正在使用 list 初始化程序,它总是创建更大的列表,尝试使用理解(或文字,对于较小的列表)并将其提供给 deque 并计数尺寸再次。较大的大小似乎存在一些差异(可能是由于这些分配内存的方式),但总的来说,我认为在较小的大小中列表较小的断言是成立的。 我确实尝试过使用列表理解。最后阅读:81528056 82500632。本质上,在这种情况下,列表理解将优化存储,并且存在具有相同大小的列表运行。在运行开始时,它比等效的双端队列略小。在运行的中间,双端队列变小了。它可能平均大约洗一次。但基本上,我的结论是双端队列通常更小或大小相同。 实际上,列表在运行的开始更大,相对于结束时的双端队列更小。 注意:对于一个永远不会超过个位数长度的序列,使用listdeque 无关紧要;当然,前面的poping 将是O(n),但是当n6 时,Python 的固定开销超过了避免memmove 的任何好处,事实上,新的@987654359当您到达当前块的末尾时,@ 块将间歇性地为 deque 分配(其中长度范围较窄的 list 永远不需要重新分配)会使性能不太一致。在长度有意义之前,与解释器开销相比,它只是噪音。【参考方案2】:

在 Python 中从列表的前面删除元素是 O(n),而从 collections.deque 的末尾删除元素只是 O(1)。因此,双端队列非常适合您的目的,但是应该注意,从双端队列中间访问或添加/删除比列表更昂贵。

删除的 O(n) 成本是因为 CPython 中的列表简单地实现为指针数组,因此您对每个元素的移动成本的直觉是正确的。

这可以在 Wiki 上的 Python TimeComplexity page 中看到。

【讨论】:

以上是关于在 Python 中从列表前面删除一个元素是不是便宜?的主要内容,如果未能解决你的问题,请参考以下文章

在 Python 中从数组中删除空元素

python全栈闯关--7-基础数据类型汇总集合深浅拷贝

Python中的元组(Tuple)

python之列表

Python 中如何删除一个列表 List 中多个符合条件的元素

python 列表删除元素问题?