Python 提供了许多内置的数据集合类型,如果选择明智的话,可以高效解决许多问题。 你可能已经学过下面这些集合类型,它们都有专门的字面值,如下所示。

• 列表(list)。

• 元组(tuple)。

• 字典(dictionary)。

• 集合(set)

Python 的集合类型当然不止这 4 种,它的标准库扩展了其可选列表。在许多情况下,

问题的答案可能正如选择正确的数据结构一样简单。本书的这一部分将深入介绍各种集合 类型,以帮你做出更好的选择。

1.列表与元组

Python 最基本的两个集合类型就是列表与元组,它们都表示对象序列。只要是花几小 时学过 Python 的人,应该都很容易发现二者之间的根本区别:列表是动态的,其大小可以 改变;而元组是不可变的,一旦创建就不能修改。

虽然快速分配/释放小型对象的优化方法有很多,但对于元素位置本身也是信息的数据 结构来说,推荐使用元组这一数据类型。举个例子,想要保存(x, y)坐标对,元组可能是一 个很好的选择。反正关于元组的细节相当无趣。本章关于元组唯一重要的内容就是,tuple 是不可变的(immutable),因此也是可哈希的(hashable)。其具体含义将会在后面“字典” 一节介绍。比元组更有趣的是另一种动态的数据结构 list,以及它的工作原理和高效处 理理方式。

(1)实现细节

许多程序员容易将 Python 的 list 类型与其他语言(如 C、C++或 Java)标准库中常

见的链表的概念相混淆。事实上,CPython 的列表根本不是列表。在 CPython 中,列表被 实现为长度可变的数组。对于其他 Python 实现(如 Jython 和 IronPython)而言,这种说法 应该也是正确的,虽然这些项目的文档中没有记录其实现细节。造成这种混淆的原因很清 楚。这种数据类型被命名为列表,还和链表实现有相似的接口。

为什么这一点很重要,这又意味着什么呢?列表是最常见的数据结构之一,其使用方 式会对所有应用的性能带来极大影响。此外,CPython 又是最常见也最常用的 Python 实现, 所以了解其内部实现细节至关重要。

从细节上来看,Python 中的列表是由对其他对象的引用组成的的连续数组。指向这个 数组的指针及其长度被保存在一个列表头结构中。这意味着,每次添加或删除一个元素时, 由引用组成的数组需要改变大小(重新分配)。幸运的是,Python 在创建这些数组时采用了 指数过分配(exponential over-allocation),所以并不是每次操作都需要改变数组大小。这也 是添加或取出元素的平摊复杂度较低的原因。不幸的是,在普通链表中“代价很小”的其 他一些操作在 Python 中的计算复杂度却相对较高:

• 利用 list.insert 方法在任意位置插入一个元素— 复杂度为 O(n)。

• 利用 list.delete 或 del 删除一个元素— 复杂度为 O(n)。

这里 n 是列表的长度。至少利用索引来查找或修改元素的时间开销与列表大小无关。

表 2-1 是一张完整的表格,列出了大多数列表操作的平均时间复杂度。

对于需要真正的链表(或者简单来说,双端 append 和 pop 操作的复杂度都是 O(1) 的数据结构)的场景,Python 在内置的 collections 模块中提供了 deque(双端队列)。 它是栈和队列的一般化,在需要用到双向链表的地方都可以使用这种数据结构。