今天在课上,我们学习了在Python中从列表中检索一个元素的时间复杂度是O(1)
。为什么会这样呢?假设我有一个包含四个元素的列表,例如:
li = ["perry", 1, 23.5, "s"]
这些项目在内存中具有不同的大小。因此,无法通过获取li[0]
的内存位置并加上每个元素大小的三倍来获取li[3]
的内存位置。那么解释器如何知道li[3]
在哪里,而无需遍历列表以检索元素?
今天在课上,我们学习了在Python中从列表中检索一个元素的时间复杂度是O(1)
。为什么会这样呢?假设我有一个包含四个元素的列表,例如:
li = ["perry", 1, 23.5, "s"]
这些项目在内存中具有不同的大小。因此,无法通过获取li[0]
的内存位置并加上每个元素大小的三倍来获取li[3]
的内存位置。那么解释器如何知道li[3]
在哪里,而无需遍历列表以检索元素?
["perry", 1, 23.5, "s"]
这意味着您实际上正在创建一个指针数组,如下所示:
[0xa3d25342, 0x635423fa, 0xff243546, 0x2545fade]
每个指针“指向”内存中的相应对象,因此字符串"perry"
将存储在地址0xa3d25342
处,而数字1
将存储在0x635423fa
等位置。
由于所有指针都具有相同的大小,因此解释器实际上可以将一个元素的大小加三次到li[0]
的地址上,以到达存储在li[3]
中的指针。
1 从CPython源代码(GitHub)获取更多详细信息。
当你使用a = [...]
时,a
实际上是一个指向包含指向PyObject
的指针数组的PyObject
的指针。
当你要求a[2]
时,解释器首先跟随指向列表的PyObject
的指针,然后将2
添加到其中的数组地址,然后返回该指针。如果你请求a[0]
或a[9999]
也会发生同样的情况。
基本上,所有Python对象都是通过引用而不是值访问的,即使是像2
这样的整数字面量也是如此。只是在指针系统中有一些技巧,以保持效率。并且指针具有已知的大小,因此可以方便地存储在C风格的数组中。
简短回答:Python列表是数组。
长篇回答:计算机科学中的术语“列表”通常指单向链表(用于函数式编程)或双向链表(用于过程式编程)。这些数据结构支持在列表头部(函数式地)或不需要搜索的任何位置(过程性地)进行O(1)插入。 Python“list”没有这些特征。相反,它支持在列表末尾进行(平摊后的)O(1)添加(类似于C++ std::vector或Java ArrayList)。从计算机科学的角度来看,Python列表实际上是可调整大小的数组。
下面的评论来自Python文档解释了Python“lists”的一些性能特点:
还可以将列表用作队列,其中添加的第一个元素是检索到的第一个元素(“先进先出”);但是,列表对于此目的并不高效。虽然从列表末尾进行的附加和弹出很快,但从列表开头进行插入或弹出很慢(因为所有其他元素都必须向前移动一个)。
append
和extend
,但没有prepend
/cons
。 - hkBst