Python 的 collections 模块
1. deque
(双端队列)
定义
deque
(读作 “deck”,即双端队列)是一个支持从两端高效添加和删除元素的数据结构。相比列表(list
)在头部操作的 O(n) 时间复杂度,deque
的两端操作都是 O(1)。
用途
- 队列和栈:可以用
deque
实现 FIFO(队列)或 LIFO(栈)。 - 滑动窗口:在处理固定大小的窗口问题时,
deque
适合维护窗口内的元素。 - BFS(广度优先搜索):
deque
常用于实现队列。 - 字符串或数组的双端操作:需要频繁在两端添加/删除元素时。
常用方法
append(x)
:在右端添加元素 x。appendleft(x)
:在左端添加元素 x。pop()
:移除并返回右端元素。popleft()
:移除并返回左端元素。extend(iterable)
:将可迭代对象中的元素添加到右端。extendleft(iterable)
:将可迭代对象中的元素添加到左端(注意顺序会反转)。clear()
:清空队列。rotate(n)
:将队列向右旋转 n 步(n 为负数时向左旋转)。
示例代码
from collections import deque
# 创建一个双端队列
d = deque([1, 2, 3])
# 添加元素
d.append(4) # d = deque([1, 2, 3, 4])
d.appendleft(0) # d = deque([0, 1, 2, 3, 4])
# 删除元素
right = d.pop() # right = 4, d = deque([0, 1, 2, 3])
left = d.popleft() # left = 0, d = deque([1, 2, 3])
# 旋转
d.rotate(1) # d = deque([3, 1, 2])
d.rotate(-1) # d = deque([1, 2, 3])
print(d) # 输出 deque([1, 2, 3])
竞赛场景
在 BFS 中,deque
是实现队列的首选。例如:
from collections import deque
def bfs(start, graph):
queue = deque([start])
visited = set([start])
while queue:
node = queue.popleft()
print(node, end=' ')
for neighbor in graph[node]:
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor)
# 示例图
graph = {
1: [2, 3],
2: [1, 4],
3: [1, 5],
4: [2],
5: [3]
}
bfs(1, graph) # 输出:1 2 3 4 5
2. Counter
(计数器)
定义
Counter
是一个字典子类,用于统计可哈希对象(通常是字符串、数字等)的出现次数。它非常适合需要统计元素频率的问题。
用途
- 统计频率:快速计算数组、字符串中每个元素的出现次数。
- 前 k 个高频元素:结合堆或排序解决频率相关问题。
- 字符匹配:检查两个字符串是否是异位词(anagram)。
- 多重集操作:支持集合的并、交、差等操作。
常用方法
Counter(iterable)
:从可迭代对象创建计数器。elements()
:返回一个迭代器,包含所有元素(按计数重复)。most_common(n)
:返回前 n 个频率最高的元素及其计数。subtract(iterable)
:从当前计数中减去另一个可迭代对象的计数。update(iterable)
:累加另一个可迭代对象的计数。
示例代码
from collections import Counter
# 统计字符串中字符频率
s = "abracadabra"
counter = Counter(s)
print(counter) # Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
# 获取频率最高的 3 个字符
print(counter.most_common(3)) # [('a', 5), ('b', 2), ('r', 2)]
# 统计数组
nums = [1, 1, 2, 3, 1, 2]
counter = Counter(nums)
print(counter) # Counter({1: 3, 2: 2, 3: 1})
# 集合操作
c1 = Counter(a=3, b=1)
c2 = Counter(a=1, b=2)
print(c1 + c2) # Counter({'a': 4, 'b': 3})
print(c1 - c2) # Counter({'a': 2})
竞赛场景
判断两个字符串是否是异位词:
from collections import Counter
def is_anagram(s: str, t: str) -> bool:
return Counter(s) == Counter(t)
print(is_anagram("listen", "silent")) # True
print(is_anagram("hello", "world")) # False
3. defaultdict
(默认字典)
定义
defaultdict
是一个字典子类,当访问不存在的键时,会自动为该键提供一个默认值(由指定的工厂函数生成)。这避免了手动检查键是否存在。
用途
- 分组:将元素按某种规则分组(如按值、长度等)。
- 计数:类似
Counter
,但更灵活。 - 图的邻接表:构建图结构时,自动为每个节点初始化一个空列表。
- 动态规划:需要为不存在的键提供默认值时。
常用工厂函数
list
:默认值为空列表[]
。int
:默认值为 0。set
:默认值为空集合set()
。- 自定义函数:通过
lambda
或其他函数定义默认值。
示例代码
from collections import defaultdict
# 默认值为空列表
d = defaultdict(list)
d['a'].append(1) # d = {'a': [1]}
d['a'].append(2) # d = {'a': [1, 2]}
d['b'].append(3) # d = {'a': [1, 2], 'b': [3]}
print(d)
# 默认值为 0
d = defaultdict(int)
d['a'] += 1 # d = {'a': 1}
d['b'] += 2 # d = {'a': 1, 'b': 2}
print(d)
# 自定义默认值
d = defaultdict(lambda: "unknown")
d['a'] = "known"
print(d['a']) # known
print(d['b']) # unknown
竞赛场景
构建图的邻接表:
from collections import defaultdict
def build_graph(edges):
graph = defaultdict(list)
for u, v in edges:
graph[u].append(v)
graph[v].append(u) # 无向图
return graph
edges = [(1, 2), (2, 3), (3, 1)]
graph = build_graph(edges)
print(graph) # defaultdict(<class 'list'>, {1: [2, 3], 2: [1, 3], 3: [1, 2]})
4. OrderedDict
(有序字典)
定义
OrderedDict
是一个字典子类,能够记住键的插入顺序。在 Python 3.7+ 中,普通字典也保留了插入顺序,但 OrderedDict
提供了一些额外的功能(如 move_to_end
)。
用途
- LRU 缓存:实现最近最少使用(Least Recently Used)缓存。
- 保持顺序:需要显式维护键值对的插入顺序。
- 滑动窗口:某些问题需要记录窗口内元素的顺序。
常用方法
popitem(last=True)
:移除并返回最后一项(last=False 时移除第一项)。move_to_end(key, last=True)
:将指定键移动到末尾(或开头)。- 其他与普通字典类似的操作。
示例代码
from/collections import OrderedDict
# 创建有序字典
od = OrderedDict()
od['a'] = 1
od['b'] = 2
od['c'] = 3
print(od) # OrderedDict([('a', 1), ('b', 2), ('c', 3)])
# 移动元素
od.move_to_end('a') # OrderedDict([('b', 2), ('c', 3), ('a', 1)])
od.move_to_end('b', last=False) # OrderedDict([('b', 2), ('c', 3), ('a', 1)])
# 删除元素
od.popitem() # 删除最后一项,OrderedDict([('b', 2), ('c', 3)])
od.popitem(last=False) # 删除第一项,OrderedDict([('c', 3)])
竞赛场景
实现 LRU 缓存:
from collections import OrderedDict
class LRUCache:
def __init__(self, capacity: int):
self.cache = OrderedDict()
self.capacity = capacity
def get(self, key: int) -> int:
if key not in self.cache:
return -1
self.cache.move_to_end(key)
return self.cache[key]
def put(self, key: int, value: int) -> None:
if key in self.cache:
self.cache.move_to_end(key)
self.cache[key] = value
if len(self.cache) > self.capacity:
self.cache.popitem(last=False)
# 测试
cache = LRUCache(2)
cache.put(1, 1)
cache.put(2, 2)
print(cache.get(1)) # 1
cache.put(3, 3) # 移除 2
print(cache.get(2)) # -1
5. namedtuple
(命名元组)
定义
namedtuple
是一个轻量级的类,用于创建具有命名字段的元组。它结合了元组的不可变性和字典的字段访问方式。
用途
- 结构体:表示具有固定字段的数据结构(如点坐标、记录等)。
- 清晰代码:通过字段名访问元素,代码更具可读性。
- 节省内存:相比字典或自定义类,
namedtuple
更高效。
常用方法
- 通过字段名或索引访问元素。
_replace(**kwargs)
:返回一个新的命名元组,替换指定字段。_asdict()
:将命名元组转换为字典。
示例代码
from collections import namedtuple
# 定义命名元组
Point = namedtuple('Point', ['x', 'y'])
# 创建实例
p = Point(1, 2)
print(p) # Point(x=1, y=2)
print(p.x, p.y) # 1 2
print(p[0], p[1]) # 1 2
# 替换字段
p2 = p._replace(y=3)
print(p2) # Point(x=1, y=3)
# 转换为字典
print(p._asdict()) # {'x': 1, 'y': 2}
竞赛场景
表示二维平面上的点:
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
def distance(p1, p2):
return ((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2) ** 0.5
p1 = Point(0, 0)
p2 = Point(3, 4)
print(distance(p1, p2)) # 5.0
总结与建议
以下是 collections
模块中常见工具的适用场景总结:
工具 | 主要用途 | 时间复杂度优势 |
---|---|---|
deque | 队列、栈、BFS、滑动窗口 | 两端操作 O(1) |
Counter | 频率统计、异位词、Top K 问题 | 计数 O(n),查询 O(1) |
defaultdict | 分组、计数、图的邻接表 | 访问不存在键时自动初始化 |
OrderedDict | LRU 缓存、保持插入顺序 | 移动/删除操作 O(1) |
namedtuple | 结构体、轻量级数据表示 | 内存高效,字段访问 O(1) |
学习建议
- 从简单问题入手:尝试用
Counter
解决频率统计问题,用deque
实现 BFS,用defaultdict
构建图。 - 多练习竞赛题:在 LeetCode、Codeforces 等平台上,搜索与这些数据结构相关的题目(如滑动窗口、BFS、LRU 缓存)。
- 熟悉时间复杂度:理解每个工具的性能优势,避免在竞赛中因数据结构选择不当导致超时。
- 结合其他模块:
collections
常与heapq
(堆)、bisect
(二分查找)等结合使用,逐步学习这些模块。