为什么列表元素查找在Python中是O(1)?

Tee*_*nge 28 python arrays big-o list

今天在课堂上,我们了解到从列表中检索元素是O(1)在Python中.为什么会这样?假设我有一个包含四个项目的列表,例如:

li = ["perry", 1, 23.5, "s"]
Run Code Online (Sandbox Code Playgroud)

这些项目的内存大小不同.因此,不可能获取内存位置li[0]并添加每个元素大小的三倍来获取内存位置li[3].那么解释器如何知道在何处li[3]不必遍历列表以便检索元素?

DJG*_*DJG 53

Python中的列表实现为指针数组1.那么,在创建列表时会发生什么:

["perry", 1, 23.5, "s"]
Run Code Online (Sandbox Code Playgroud)

你实际上是在创建一个像这样的指针数组:

[0xa3d25342, 0x635423fa, 0xff243546, 0x2545fade]
Run Code Online (Sandbox Code Playgroud)

每个指针"指向"存储器中的相应对象,以便将字符串"perry"存储在地址中0xa3d25342,并将数字1存储在0x635423fa等处.

由于所有指针的大小都相同,因此解释实际上可以将元素大小的3倍添li[0]加到要存储的指针的地址li[3].


1获取更多详细信息:马的嘴(GitHub上的CPython源代码).

  • 参考实现为我所知道的每种语言的指针.语义可能略有不同(例如内存管理的差异,或C++中的引用是不可变的),但最终它们仍然是指针. (8认同)
  • @TLW我以前从未见过这些.你在哪里找到它们? (6认同)
  • @DmitryVerhoturov这是正确的但对这个答案没有实际意义.参考文献是参考计数,https://docs.python.org/3/c-api/structures.html#c.PyVarObject (5认同)
  • @TLW这种简单性对于那些永远不会在关注性能的环境中运行的开发人员来说非常重要,因为工作集接近于exabytes,但O(n)和O(n log n)算法之间存在显着的性能差异关于他们简化的计算模型.简化模型很好地将注意力集中在算法的最重要方面. (2认同)

Dra*_*nis 17

当你说a = [...],a实际上是指向PyObject包含指向PyObjects 的指针数组的指针.

当你要求时a[2],解释器首先跟随指向列表的指针PyObject,然后添加2到其中的数组的地址,然后返回该指针.如果您要求a[0]或,也会发生同样的情况a[9999].

基本上,所有Python对象都是通过引用而不是值来访问的,甚至是整数文字2.指针系统中只有一些技巧可以保持这一切.指针具有已知大小,因此可以方便地存储在C风格的数组中.

  • 什么是''terp''? (5认同)

hkB*_*Bst 6

简短回答:Python列表是数组.

答案很长:计算机科学术语列表通常表示单链表(在函数式编程中使用)或双向链表(在程序编程中使用).这些数据结构支持在列表的头部(功能上)或不需要搜索的任何位置(程序上)插入O(1).Python"列表"没有这些特征.相反,它支持(分期)O(1)附加在列表的末尾(如C++ std :: vector或Java ArrayList).Python列表实际上是CS术语中可调整大小的数组.

Python文档中的以下注释解释了Python``list''的一些性能特征:

也可以使用列表作为队列,其中添加的第一个元素是检索的第一个元素("先进先出"); 但是,列表不能用于此目的.虽然列表末尾的追加和弹出很快,但是从列表的开头进行插入或弹出是很慢的(因为所有其他元素都必须移动一个).