文章目录
资料:Common Python Data Structures(Guide)
背景:数据结构是构建程序的基本结构,每个数据结构都提供了一种组织数据的特定方式,以便能更高效的访问数据,Python 在其标准库中附带了一组广泛的数据结构,本文主要介绍以下内容:
(1)Python 标准库中内置了哪些常见的抽象数据类型
(2)最常见的抽象数据类型在Python中如何映射到具体的命名
(3)如何在各种算法中将抽象数据类型付诸实际使用
本文为Python数据结构指引,对于每类具体的数据结构细节没有详细阐述,可根据功能需求自行查找相关内容
Dictionaries, Maps, and Hash Tables
python中字典是一个中心数据结构,可以存储任意数量的对象,每个对象由唯一的字典键标识。字典也被称为映射(maps),哈希表(hashmaps),查找表(lookup tables)或关联数组(associative arrays),允许基于给定的键来有效查找,插入和删除与之关联的任何对象。
dict:标准字典
由于字典非常重要,Python提供了一个健壮的字典实现,它直接内置于核心语言中:dict数据类型。
- 定义字典:
{}
- 读取字典:
[]
>>> numbers = {x: x * x for x in range(5)}
>>> phonebook['Rose']
2345
>>> numbers
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
字典是通过键来索引的,键可以是任何可哈希类型,不可变类型(字符串,数字,元组)都可以成为字典的键。字典的查找、插入、更新和删除操作的平均时间复杂度为O(1)。除了普通的字典对象,python的标准库还包含了许多专门化的字典实现,这些都是基于python内置的dict构建,同时又添加一些额外的便利特征。
collections.OrderedDict: 记住键的插入顺序
字典是无序的,python的collections模块提供了一个dict子类OrderedDict,能记住键的插入顺序,在需要维护元素插入顺序的场景下非常有用。
OrderedDict有两个主要功能:
popitem(last=True)
:移除并返回一个键值对,如果last
为真,按照LIFO,否则按照FIFOmove_to_end(key, last=True)
:将一个现有的键移到序字典的任一端,如果last
为真,移到最右端,否则移到开头
# 记住键的插入顺序
>>> import collections
>>> test = collections.OrderedDict()
>>> test['d'] = 1
>>> test['a'] = 2
>>> test['b'] = 3
>>> test
OrderedDict({'d': 1, 'a': 2, 'b': 3})
# LIFO移除
>>> test.popitem(False)
('d', 1)
>>> test
OrderedDict({'a': 2, 'b': 3})
# 键移动
>>> test.move_to_end('a')
>>> test
OrderedDict({'b': 3, 'a': 2})
collections.defaultdict: 返回缺失键的默认值
defaultdict类是另一个字典子类,它在其构造函数中接受一个可调用对象,如果找不到请求的键,则使用该可调用对象的返回值。比如下面例子,在dict中,如果键['a']
不存在,直接修改会报错,但是在defaultdict中,没有键['a']
也能直接添加数据,即test['a'].append(1)
,不存在的键['b']
值为[]
,适合处理键缺失的情况,与字典的get()方法类似
>>> a = {}
>>> a['a'].append(1)
KeyError: 'a'
>>> test = collections.defaultdict(list)
# 键'a'不存在,默认先创建键,再添加元素
>>> test['a'].append(1)
>>> test
defaultdict(<class 'list'>, {'a': [1]})
>>> test['b']
[]
collections.ChainMap:将多个词典作为单个映射进行搜索
多个字典分组为一个映射,查找会逐渐搜索每个字典直到找到唯一的键,插入、更新和删除仅影响添加到链中的第一个字典。比如在环境配置中,个人配置优先与系统配置,个人配置可以修改而系统配置不能修改,使用ChainMap能获取到最终配置
>>> from collections import ChainMap
>>> h1 = {'a':1, 'b':2, 'c':3}
>>> h2 = {'a':4, 'b':5, 'c':6, 'd':7}
>>> h = ChainMap(h1, h2
# 查找出现的第一个键
>>> h['d']
7
# 修改只会修改第一个字典中的键值
>>> h['a']=10
>>> h
ChainMap({'a': 10, 'b': 2, 'c': 3}, {'a': 4, 'b': 5, 'c': 6, 'd': 7})
types.MappingProxyType: 制作只读字典的封装器wrapper
MappingProxyType 是标准字典的封装器,提供字典的只读视图,创建字典的不可变版本,比如想要返回一个包含类或模块内部状态的字典,同时阻止对该对象的写访问,可以使用MappingProxyType
>>> from types import MappingProxyType
>>> dict1 = {'a':1, 'b':2}
>>> dict_readonly = MappingProxyType(dict1)
>>> dict_readonly['a']
1
# 不能修改MappingProxyType中的内容
>>> dict_readonly['a'] = 2
TypeError: 'mappingproxy' object does not support item assignment
上述python字典都是内置于Python标准库的实现,大部分情况下使用dict
就可以满足要求,如果有一些特殊要求就可以使用其它扩展的字典
Array Data Structures
array数组是大多数编程语言中可用的基本数据结构,很多算法实现都会用到数组。数组由固定大小的数据记录组成,允许根据其索引有效地定位每个元素。数组将信息存储在相邻的内存块中,称为连续数据结构(contiguous data structures),另一种是链式数据结构(linked data structures),比如链表。
数组数据结构在现实世界中的类比是停车场,停车场内有由唯一编号索引的停车位,每个停车位可以为空,也可以停放汽车、摩托车或其他车辆。如果停车场只限于某种类型的车辆,称为类型化数据结构(typed array data structure)。
给定数组索引,可以保证O(1)的查找时间,Python 的标准库中包含几个类似数组的数据结构,每个数据结构的特征略有不同。
list: 可变动态数组
list列表是python核心语言的一部分
list
列表是可变的:列表在后台通过动态数组的方式实现,可以删除和添加元素,通过分配和释放内存自动调整保存这些元素的后备存储。list
列表可以存储任意类型的元素:列表可以混合搭配不同类型的数据类型(包括函数)并将它们全部存储在单个列表中,但是缺点在于同时支持多种数据类型意味着数据通常不太紧密,整个结构会占用更多空间
>>> list1 = ['one', 'two', 'three']
>>> list1
['one', 'two', 'three']
>>> list1[0]
'one'
# 可以修改和删除
>>> list1[1] = 'hello'
>>> list1
['one', 'hello', 'three']
>>> del list1[0]
>>> list1
['hello', 'three']
# 数据类型可不同
>>> list1.append(20)
>>> list1
['hello', 'three', 20]
tuple:不可变容器
和列表一样,元组也是 Python 核心语言的一部分。然而,与列表不同的是
tuple
元组是不可变的:不能动态添加或删除元素,元组中的所有元素都必须在创建时定义。tuple
元组可以存储任意类型的元素:拥有这种灵活性的同时也意味着占据更大的内存空间
>>> tuple1 = ('one', 'two', 'three')
>>> tuple1
('one', 'two', 'three')
# 读取
>>> tuple1[0]
'one'
# 不能修改
>>> tuple1[0] = 'oneone'
TypeError: 'tuple' object does not support item assignment
# 不能删除
>>> del tuple1[0]
TypeError: 'tuple' object doesn't support item deletion
# 元组和元组连接
# 元组只有一个元素时需要加逗号
>>> tuple1 + (23, )
('one', 'two', 'three', 23)
array.array: 基本类型化数组
python的array模块提供C-风格数据类型(字节,整型,浮点型)的空间有效存储
array.array
数组是可变的:使用array.array
类创建的数组是可变的,其行为与列表类似array.array
数组只能存储类型一致的元素:与列表不同的是,其限制为单一数据类型的类型化数组。由于这一限制,具有许多元素的array.array
对象比列表和元组更节省空间。存储在其中的元素紧密排列,非常适合存储许多相同类型的元素。
>>> import array
>>> arr = array.array('f', (1.0, 1.5, 2.0, 2.5))
>>> arr
array('f', [1.0, 1.5, 2.0, 2.5])
# 读取修改
>>> arr[0] = 3.5
>>> arr
array('f', [3.5, 1.5, 2.0, 2.5])
# 删除
>>> del arr[1]
>>> arr
array('f', [3.5, 2.0, 2.5])
# 添加
>>> arr.append(10)
>>> arr
array('f', [3.5, 2.0, 2.5, 10.0])
# 修改
# 要求数据类型一致
>>> arr[0] = 'hello'
TypeError: must be real number, not str
str: Unicode 字符的不可变数组
str
对象是不可变的:python3使用str
对象将文本数据存储为不可变的Unicode字符序列,因此str
是一个不可变的字符数组,如果要修改字符串要先创建副本,通常是将单个字符存储在列表中。str
对象只能存储类型一致的元素:str
对象紧密排列并且专门用于单一数据类型,因此比较节省空间,如果要存储Unicode 文本,适合使用字符串。
>>> string1 = 'hello'
>>> string1
'hello'
# 不能修改
>>> string1[0] = g
TypeError: 'str' object does not support item assignment
# 不能删除
>>> del string1[0]
TypeError: 'str' object doesn't support item deletion
# 支持转变为list,变成可变对象
>>> list(string1)
['h', 'e', 'l', 'l', 'o']
数组总结
python中有很多可以实现数组的内置数据结构:
- 存储任意对象(混合数据类型):使用列表和元组,包括可变和不可变
- 存储数字(整型或浮点)并且内存性能有要求:使用
array.array
- 存粗Unicode 字符表示的文本数据:使用
str
数据类型 | 可变/不可变 | 元素类型 |
---|---|---|
list | 可变 | 数据类型任意 |
tuple | 不可变 | 数据类型任意 |
array | 可变 | 数据类型一致 |
str | 不可变 | 数据类型一致 |
除了python内置的标准库,一些第三方库numpy
和pandas
也为科学计算和数据科学提供了广泛的快速数组实现
Records, Structs, and Data Transfer Objects
与数组相比,记录数据结构(record data structures)提供固定数量的字段,每个字段可以有一个名称,也可以有不同的类型。Python 提供了多种数据类型,可用于实现记录、结构和数据传输对象。
dict: Simple Data Objects
正如前面介绍的,Python字典可以存储任意数量的对象,每个对象都由一个唯一键标识,字典也被称为映射(maps)或关联数组(associative arrays),可以高效的查找、插入和删除与给定键关联的任何对象。
使用字典来记录数据来比较方便的方法,但是由于字典创建的数据对象是可变的,几乎没有针对字段名称拼写错误的保护措施,可以随时自由添加和删除字段,这些属性可能会导致错误,因此需要在便利性和错误恢复间作出平衡。
# 无法防止字段名称错误
# 或缺失/多余的字段
>>> cars = {'colo':'red', 'wind':'broken'}
>>> cars['color']
KeyError: 'color'
tuple: Immutable Groups of Objects
Python 的元组是一种用于对任意对象进行分组的简单数据结构,元组是不可变的,一旦创建就无法修改。
用元组存储数据的一个潜在缺点是:数据只能通过整数索引访问才能提取出来,存储在元组中的各个属性无法命名,这会影响代码的可行性,此外元组是一种临时结构,难以保证两个元组具有相同数量的字段和相同属性,比如字段顺序混淆。
# 字段:颜色,价格,自动
>>> car1 = ("red", 3812.4, True)
>>> car2 = ("blue", 40231.0, False)
>>> car3 = (3431.45, 'green', True, 'silver')
Write a Custom Class: More Work, More Control
类能用来为数据对象构建可重用版本,并且确保每个对象有相同的字段集。使用常规的Python类来记录数据类型是可行的,但是需要手动操作才能获得其它便利功能
- 添加字段:使用
__init__
构造函数添加新字段比较冗长且耗时 - 字符串表示:使用
__repr__
获得实例化对象的字符串表示冗长,并且每次添加新字段时都必须更新
存储在类上的字段是可变的,可以使用@property
装饰器提供更多访问控制并创建只读字段。如果需要使用方法向记录对象添加业务逻辑和行为时,编写自定义类比较适合,但是这就意味着这些对象不仅仅是一个普通的数据对象。
>>> class Car():
def __init__(self, color, mileage, automatic):
self.color = color
self.mileage = mileage
self.automatic = automatic
def __repr__(self):
return f'color:{self.color}, mileage:{self.mileage},automatic:{self.automatic}'
>>> car1 = Car('red', 3812, True)
# 使用__repr__返回对象具体信息
>>> car1
color:red, mileage:3812,automatic:True
# 类字段值可以修改
>>> car1.color = 'green'
>>> car1
color:green, mileage:3812,automatic:True
dataclasses.dataclass: Python 3.7+ Data Classes
在python3.7及更高版本提供了数据类(Data classes),非常适合定义个性话的数据存储类,比起Python类,数据类有一些方便的实现功能
- 定义数据对象的字段更方便,不需要实现
__init__
- 可以自动生成实例的字符串表示,不需要使用
__repr__
- 接受变量类型注释,使得数据类在一定程度上具有自文档性
定义数据类通常使用@dataclass 装饰器创建
>>> from dataclasses import dataclass
>>> @dataclass
class Car:
color:str
mileage:float
automatic:bool
>>> car1 = Car('red', 3812, True)
# 直接的字符串表示
>>> car1
Car(color='red', mileage=3812, automatic=True)
# 可以读取和修改属性
>>> car1.color = 'green'
>>> car1
Car(color='green', mileage=3812, automatic=True)
collections.namedtuple: Convenient Data Objects
Python 2.6+中可用的namedtuple类提供了内置tuple数据类型的扩展。
- namedtuple对象不可变:Namedtuple对象是不可变的,就像普通的元组一样,因此在namedtuple实例创建之后,不能添加新字段或修改现有字段。
- namedtuple对象比元组更方便访问:存储在namedtuple中的每个对象都能通过唯一标识符进行访问。
- namedtuple对象内存效率高:namedtuple对象在内部实现为普通的Python类,在内存使用方面常规Python类更好,并且与常规tuple一样具有内存效率。和具有固定格式的字典相比,使用namedtupl命名元组能更清楚的记录和传递数据。
>>> from collections import namedtuple
>>> from sys import getsizeof
>>> Car = namedtuple('Car', ['color', 'mileage', 'automatic'])
>>> car1 = Car('red', 3812, True)
# 访问字段
>>> car1.color
'red'
# 不能修改已有字段
>>> car1.mileage = 2000
AttributeError: can't set attribute
# 与tuple内存效率一致
>>> getsizeof(car1)
64
>>> car2 = ('red', 3812, True)
>>> getsizeof(car2)
64
typing.NamedTuple: Improved Namedtuples
Python 3.6中添加的typing模块的NamedTuple类是collections模块中namedtuple的改进版本,主要区别在于更新了定义新记录类型的语法,并增加了对类型提示的支持,既支持键值访问,也支持迭代访问。由于继承元组,因此是不可变对象
>>> from typing import NamedTuple
>>> class Car(NamedTuple):
color:str
mileage:float
where:list
>>> car = Car('green', 3814, ['a', 'b'])
# 同时支持迭代和键访问
>>> car.where
['a', 'b']
>>> car[0]
'green'
>>> car.color = 'red'
AttributeError: can't set attribute
types.SimpleNamespace: Fancy Attribute Access
types.SimpleNamespace是在Python 3.3中添加的,并提供对其命名空间的属性访问,是一个允许属性访问的字典,通过obj.
而不是[]
访问,可以添加修改和删除属性,默认情况下,所有实例还包含一个有意义的__repr__
from types import SimpleNamespace
>>> car1 = SimpleNamespace(color="red", mileage=3812.4, automatic=True)
# 修改属性
>>> car1.mileage = 2
>>> car1
namespace(color='red', mileage=2, automatic=True)
# 添加属性
>>> car1.windshield = 'broken'
# 字符串表示
>>> car1
namespace(color='red', mileage=2, automatic=True, windshield='broken')
记录总结
数据类型 | 访问 | 添加 | 修改 | 删除 | 字符串表示 |
---|---|---|---|---|---|
dict | 键 | ✅ | ✅ | ✅ | ✅ |
class | 属性 | ✅ | ✅ | ✅ | ❌ |
dataclass | 属性 | ✅ | ✅ | ✅ | ✅ |
tuple | 索引 | ❌ | ❌ | ❌ | ✅ |
namedtuple | 属性 | ❌ | ❌ | ❌ | ✅ |
NamedTuple | 属性 | ❌ | ❌ | ❌ | ✅ |
SimpleNamespace | 属性 | ✅ | ✅ | ✅ | ✅ |
有很多不同的数据类型可以选择来记录数据,具体使用要结合场景:
- 字段较少并且字段顺序容易记住,可以使用
tuple
,比如三维空间中的(x,y,z)点 - 需要不可变字段或锁定字段避免输入错误,使用
namedtuple
或者NamedTuple
- 简单的数据记录可以使用
dict
- 需要完全控制数据结构,实时修改数据可以编写数据类
dataclass
- 向数据对象添加行为(方法),编写普通类
class
比较常用的记录数据类型是NamedTuple
和dataclass
,两者都有很好的记录语法,支持定义属性类型,支持实例字符串表示等,区别在于NamedTuple
不可变,dataclass
可变,因此记录历史已产生数据可以使用NamedTuple
,流程中不断变化的数据可以使用dataclass
,dataclass
也可以设置属性不可变。
# dataclass
>>> from dataclasses import dataclass
>>> @dataclass
class Car:
color: str
number: int
location: list
>>> car = Car('red', 20, ['a', 'b'])
>>> car.location
['a', 'b']
>>> car.color = 'green'
>>> car
Car(color='green', number=20, location=['a', 'b'])
# NamedTuple
>>> from typing import NamedTuple
>>> class Book(NamedTuple):
index: int
number: int
borrow: list
>>> book = Book(1, 20, ['c', 'd'])
>>> book
Book(index=1, number=20, borrow=['c', 'd'])
>>> book.index = 2
AttributeError: can't set attribute
Sets and Multisets
set: Your Go-to Set
set是Python中的内置集合实现,是可变的,允许动态插入和删除元素。集合有一个关键特征:元素不可重复
>>> list1 = [1, 1, 2, 3]
# 集合去重
>>> set1 = set(list1)
>>> set1
{1, 2, 3}
>>> set2 = (3,4,5)
# 两个集合的交集
>>> set1.intersection(set2)
{3}
# 集合可以添加元素
>>> set1.add(8)
>>> set1
{8, 1, 2, 3}
frozenset: Immutable Sets
frozenset集合是set的不可变版本,构造后无法更改,只允许对其元素进行查询操作,而不允许插入或删除。由于frozenset对象是静态且可哈希的,因此它们可以用作字典键或另一个集合的元素
>>> vowels = frozenset({'a', 'e', 'o', 'i', 'u'})
>>> vowels
frozenset({'a', 'u', 'e', 'o', 'i'})
>>> vowels.add('f')
AttributeError: 'frozenset' object has no attribute 'add'
>>> d = {frozenset({1,2,3}):'hello'}
>>> d[frozenset({1,2,3})]
'hello'
collections.Counter: Multisets
Python 标准库中的 collections.Counter 类实现了多集或包类型,允许集合中的元素出现多次,如果需要跟踪元素是否属于集合,还需要跟踪元素被包含在集合中的次数,collections.Counter 非常有用
>>> from collections import Counter
>>> inventory = Counter()
>>> loot = {'sword':1, 'bread':1}
>>> inventory.update(loot)
>>> inventory
Counter({'sword': 1, 'bread': 1})
# 增加新sku和新库存
>>> more_loot = {'sword':1, 'apple':1}
>>> inventory.update(more_loot)
>>> inventory
Counter({'sword': 2, 'bread': 1, 'apple': 1})
集合总结
- 如果想要可变集合,使用
set
- 如果想要哈希对象作为字典键或者集合键,使用
frozenset
- 如果需要多集,包或者数据结构,使用
collections.Counter
Stacks (LIFOs)
栈支持后进先出(LIFO),可以插入和删除元素,与列表和数组不同的是,栈不能随机访问其中的元素,插入和删除元素称为push
和pop
。栈在现实世界的类比是一叠盘子,新盘子只能放在最上面,也只能从最上面拿走,因此是LIFO。栈实现插入和删除操作的时间复杂度为O(1)
list: Simple, Built-in Stacks
python的内置列表是一个不错的栈数据结构,支持在O(1)时间内推进和弹出元素。列表在内部通过动态数组实现,当添加或删除元素时,python偶尔需要调整存储在其中的元素的存储空间大小。但是列表会过度分配后备空间,所以不是每次推进或弹出都需要重新调整空间大小,这些操作也能在O(1)时间内获得。
但是动态数组的这种实现也意味着列表不能像链表一样提供非常稳定的O(1)插入和删除。列表读取元素时也是O(1)。列表增加元素为append()
,弹出元素为pop()
,都是从列表最后面改变,这样比前面改变能实现更好的内存分配效率。
>>> s = []
>>> s.append('apple')
>>> s.pop()
'apple'
>>> s
[]
collections.deque: Fast and Robust Stacks
collections.deque
是基于双链表实现的,因此可以从两端插入和删除元素,能同时作为栈和队列。左边操作为appendleft()
和popleft()
,右边操作为append()
和pop()
。collections.deque
插入和删除元素比较快,但是读取元素需要O(n)
>>> d = deque(range(1,4))
d
>>> deque([1, 2, 3])
>>> d.appendleft(0)
>>> d.append(5)
>>> d
deque([0, 1, 2, 3, 5])
>>> d.popleft()
0
>>> d.pop()
5
>>> d
deque([1, 2, 3])
如果想要在Python标准库中寻找具有链表实现性能特征的堆栈数据结构,collections.deque
是一个不错的选择。
queue.LifoQueue:并行计算的锁语义
queue.LifoQueue
是后进先出队列的构造函数,因此可以看一个栈,LifoQueue堆栈实现是同步的,并提供锁定语义以支持多个并发的生产者和消费者。除了LifoQueue, queue模块还包含其他几个类,这些类实现了多生产者、多消费者队列,这对并行计算很有用。LifoQueue
在多线程编程时有用,但是也会产生不必要的开销,因此通常情况下还是使用列表和deque
作为栈的实现。
>>> from queue import LifoQueue
>>> a = LifoQueue()
>>> a.put('eat')
>>> a
<queue.LifoQueue object at 0x104166720>
>>> a.get()
'eat'
栈总结
queue.LifoQueue
:要求并行处理时可以使用,否则考虑列表或者collections.deque
- 列表:基于动态数组实现,内存空间动态分配,因此插入和删除不稳定O(1),读取O(1)
collections.deque
:基于双链表实现,插入和删除稳定O(1),读取O(n),可同时作为栈和队列
collections.deque
是在Python中实现堆栈(后进先出队列)的绝佳选择。
Queues (FIFOs)
队列是支持插入和删除的快速FIFO(先进先出)语义的对象集合。插入和删除操作有时被称为enqueue和dequeue。与列表或数组不同,队列通常不允许对其包含的对象进行随机访问。
一个现实的队列系统就是排队系统,新到达的人只能排到队伍最后面,队伍最前面的人买到票就可以离开。队列与栈类似,区别是栈是先进后出,队列是先进先出。
队列在解决调度和平行编程问题上经常使用,比如广度优先搜索使用队列作为树的数据结构。调度问题通常使用优先队列,元素按照键排序,但是普通的队列只是简单的按照先后排序。
list: Terribly Sloooow Queues
列表也可以当成队列使用,也就是pop()
时指定索引位置,但是这样会非常慢,因此需要移动所有其它元素,不推荐使用
>>> a = ['a', 'b', 'c']
>>> a.pop(0)
'a'
collections.deque: Fast and Robust Queues
collections.deque
是基于双链表实现的,因此可以从两端插入和删除元素,能同时作为栈和队列。两端插入和删除时间复杂度为O(1),缺点是不能随便读取中间的元素
>>> from collections import deque
>>> a = deque(range(1,3))
>>> a
deque([1, 2])
>>> a.append(3)
>>> a.popleft()
1
>>> a
deque([2, 3])
如果要在Python的标准库中寻找队列数据结构,collections.deque是一个很好的默认选择
multiprocessing.Queue: Shared Job Queues
multiprocessing.Queue是一种共享作业队列实现,它允许多个并发工作者并行处理排队项,专门用于在进程之间共享数据的队列实现。
>>> from multiprocessing import Queue
>>> a = Queue()
>>> a.put('apple')
>>> a
<multiprocessing.queues.Queue object at 0x104166d80>
>>> a.get()
'apple'
>>> a.get() # Blocks/waits forever...
队列总结
- 列表:列表可以作为队列,但是不推荐,性能不好
multiprocessing.Queue
:并行化处理中使用collections.deque
:比较适合作为FIFO队列数据结构的实现
Priority Queues
list: Manually Sorted Queues
可以给列表排序来实现优先队列,但是这样每次插入新元素后都要重新排序,不仅时间复杂度高,而且还增加开发负担,因此不太推荐使用列表作为优先级队列
>>> a = []
>>> a.append((1, 'apple'))
>>> a.append((3, 'dog'))
>>> a.append((2, 'sleep'))
>>> a
[(1, 'apple'), (3, 'dog'), (2, 'sleep')]
>>> a.sort()
>>> a
[(1, 'apple'), (2, 'sleep'), (3, 'dog')]
heapq: List-Based Binary Heaps
heapq是一个二进制堆实现,通常由一个普通列表支持,它支持在O(log n)时间内插入和提取最小元素。由于heapq在技术上只提供最小堆实现,因此必须采取额外的步骤来确保排序稳定性和实际优先级队列通常期望的其他特性。
>>> import heapq
>>> a = []
>>> heapq.heappush(a, (1, 'apple'))
>>> heapq.heappush(a, (3, 'dog'))
>>> heapq.heappush(a, (2, 'sleep'))
>>> a
[(1, 'apple'), (3, 'dog'), (2, 'sleep')]
>>> heapq.heappop(a)
(1, 'apple')
>>> heapq.heappop(a)
(2, 'sleep')
queue.PriorityQueue: Beautiful Priority Queues
queue.PriorityQueue
在内部使用heapq,并共享相同的时间和空间复杂性。不同之处在于,PriorityQueue是同步的,并提供锁定语义来支持多个并发生产者和消费者。PriorityQueue提供的基于类的接口,而不是heapq提供的基于函数的接口
>>> from queue import PriorityQueue
>>> q = PriorityQueue()
>>> q.put((2, 'apple'))
>>> q.put((1, 'dog'))
>>> q.put((3, 'sleep'))
>>> while not q.empty():
next_item = q.get()
print(next_item)
(1, 'dog')
(2, 'apple')
(3, 'sleep')
优先队列总结
queue.PriorityQueue
在通常情况下是比较好的选择,方便提供面向对象接口。
以上内容总结了python中常用的一些数据结构,在不同的场景下要选择合适的数据结构实现,以下是比较常用且有效的选择
功能 | 常用 |
---|---|
哈希表 | dict,collections.defaultdict |
数组 | list,tuple |
数据记录 | dataclass,namedtuple |
集合 | set |
栈 | collections.deque |
队列 | heapq,queue.PriorityQueue |