一、核心数据结构概览
1. 列表 (List)
my_list = [1, 2, 3, 'a', 'b']
特点:
- 有序集合
- 可变(可修改)
- 允许重复元素
- 可包含不同类型的元素
优点:
- 操作灵活,支持增删改查
- 支持索引和切片
- 丰富的内置方法(append、insert、pop等)
- 内存连续,迭代效率高
缺点:
- 插入/删除非末尾元素时效率低(O(n))
- 搜索未排序列表效率低(O(n))
- 内存占用相对元组稍大
适用场景:
- 需要频繁修改的集合
- 需要保持元素顺序
- 栈或队列的实现(虽然collections.deque更适合队列)
- 数据预处理和转换
# 列表适用场景示例
# 1. 动态数据收集
data_points = []
for i in range(10):
data_points.append(i * 2)
# 2. 排序和过滤
scores = [85, 92, 78, 90]
sorted_scores = sorted(scores)
# 3. 作为栈使用
stack = []
stack.append(1) # 入栈
stack.pop() # 出栈
2. 元组 (Tuple)
my_tuple = (1, 2, 3, 'a')
single_tuple = (1,) # 注意逗号
特点:
- 有序集合
- 不可变(创建后不能修改)
- 允许重复元素
- 通常存储异质数据
优点:
- 不可变性保证数据安全
- 比列表内存占用小
- 可哈希,可作为字典键
- 访问速度比列表快
缺点:
- 无法修改、添加或删除元素
- 灵活性差
适用场景:
- 存储不应该被修改的数据(配置、常量)
- 函数返回多个值
- 字典的键(需要可哈希)
- 保证数据完整性
# 元组适用场景示例
# 1. 函数返回多个值
def get_user_info():
return ("Alice", 30, "Engineer")
name, age, job = get_user_info()
# 2. 作为字典键
locations = {
(40.7128, -74.0060): "New York",
(51.5074, -0.1278): "London"
}
# 3. 常量定义
COLORS = ("RED", "GREEN", "BLUE")
3. 字典 (Dictionary)
my_dict = {'name': 'Alice', 'age': 30}
特点:
- 键值对集合
- 无序(Python 3.7+ 保持插入顺序)
- 键必须是不可变类型(可哈希)
- 值可以是任意类型
优点:
- O(1)平均时间复杂度的查找
- 灵活的键值映射
- 支持快速添加和删除
- 内存高效(哈希表实现)
缺点:
- 无序(在3.6及之前版本)
- 内存占用相对较大
- 键必须是可哈希的
适用场景:
- 快速查找表
- 映射关系存储
- 统计计数
- 缓存实现
# 字典适用场景示例
# 1. 快速查找
student_grades = {
"Alice": 85,
"Bob": 92,
"Charlie": 78
}
grade = student_grades["Alice"] # O(1)查找
# 2. 统计频率
text = "apple banana apple orange"
words = text.split()
frequency = {}
for word in words:
frequency[word] = frequency.get(word, 0) + 1
# 3. 配置存储
config = {
"host": "localhost",
"port": 8080,
"debug": True
}
4. 集合 (Set)
my_set = {1, 2, 3, 3} # 结果:{1, 2, 3}
特点:
- 无序集合
- 元素唯一(自动去重)
- 可变的(frozenset是不可变版本)
- 元素必须是可哈希的
优点:
- O(1)平均时间复杂度的成员测试
- 自动去重
- 支持集合运算(并集、交集等)
- 比列表查找快得多
缺点:
- 无序,不能通过索引访问
- 不能存储不可哈希的元素
- 内存占用较大
适用场景:
- 去重
- 成员关系测试
- 数学集合运算
- 过滤重复项
# 集合适用场景示例
# 1. 去重
numbers = [1, 2, 2, 3, 3, 4]
unique_numbers = set(numbers) # {1, 2, 3, 4}
# 2. 快速成员测试
valid_users = {"alice", "bob", "charlie"}
if "alice" in valid_users: # O(1)查找
print("User exists")
# 3. 集合运算
set1 = {1, 2, 3, 4}
set2 = {3, 4, 5, 6}
intersection = set1 & set2 # 交集: {3, 4}
union = set1 | set2 # 并集: {1, 2, 3, 4, 5, 6}
二、高级数据结构(collections模块)
1. defaultdict
from collections import defaultdict
dd = defaultdict(list) # 默认值为空列表
特点:
- 访问不存在的键时返回默认值
- 避免KeyError
适用场景:
- 分组和分类
- 构建图或邻接表
- 累积统计
2. Counter
from collections import Counter
counter = Counter(['a', 'b', 'a', 'c'])
特点:
- 专门用于计数
- 支持算术运算
适用场景:
- 频率统计
- 词频分析
- 投票计数
3. deque(双端队列)
from collections import deque
d = deque([1, 2, 3])
特点:
- 两端都能高效添加/删除(O(1))
- 线程安全
适用场景:
- 队列和栈的实现
- 滑动窗口
- 最近使用缓存(LRU)
4. OrderedDict
from collections import OrderedDict
od = OrderedDict()
特点:
- 保持插入顺序(Python 3.7+普通dict也保持顺序)
- 支持按顺序操作
适用场景:
- 需要保持插入顺序的映射
- LRU缓存实现
5. namedtuple
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(10, 20)
特点:
- 轻量级的对象
- 不可变
- 可通过属性访问
适用场景:
- 替代简单类
- 数据库记录
- 坐标点等简单数据结构
三、数据结构选择指南
决策流程图
开始选择数据结构
│
├─ 需要键值映射?
│ ├─ 需要默认值? → defaultdict
│ ├─ 需要计数? → Counter
│ ├─ 需要保持插入顺序? → OrderedDict (Python<3.7) / dict (Python≥3.7)
│ └─ 普通键值对 → dict
│
├─ 需要唯一元素?
│ ├─ 需要集合运算? → set
│ └─ 只需要去重 → set
│
├─ 数据是否需要修改?
│ ├─ 是 → list
│ └─ 否 → tuple 或 namedtuple
│
├─ 需要队列/栈操作?
│ └─ 是 → deque
│
└─ 考虑性能需求:
├─ 频繁查找 → dict, set
├─ 频繁在两端操作 → deque
├─ 只需要遍历 → list, tuple
└─ 内存敏感 → tuple
具体场景推荐
| 场景 | 推荐结构 | 理由 |
|---|---|---|
| 存储用户配置 | dict | 键值对,方便按名称访问 |
| 数据库查询结果 | list of dicts | 每行是dict,多行用list |
| 坐标点 | tuple 或 namedtuple | 不可变,轻量 |
| 社交网络好友 | set | 去重,快速查找 |
| 最近访问记录 | deque | 限制长度,两端操作 |
| 词频统计 | Counter | 专门的计数工具 |
| 图结构 | defaultdict(list) | 邻接表表示 |
| 常量枚举 | tuple | 不可变,安全 |
| 任务队列 | deque | 高效的入队出队 |
| 缓存系统 | dict 或 OrderedDict | 快速查找,可能需顺序 |
性能对比表
| 操作 | List | Tuple | Dict | Set | Deque |
|---|---|---|---|---|---|
| 索引访问 | O(1) | O(1) | N/A | N/A | O(1) |
| 追加 | O(1)* | 不可变 | O(1) | O(1) | O(1) |
| 插入开头 | O(n) | 不可变 | N/A | N/A | O(1) |
| 删除元素 | O(n) | 不可变 | O(1) | O(1) | O(1) |
| 查找元素 | O(n) | O(n) | O(1) | O(1) | O(n) |
| 内存占用 | 中 | 小 | 大 | 大 | 中 |
*: 列表追加平均O(1),但可能触发扩容
四、最佳实践建议
- 数据不可变性原则:如果不需修改数据,使用tuple而不是list
- 选择合适的数据结构:不要总是用list,根据操作类型选择
- 考虑时间复杂度:大数据集时选择O(1)或O(log n)操作的结构
- 使用内置工具:善用collections模块中的专用数据结构
- 命名元组替代简单类:当只需要存储数据时,namedtuple更轻量
# 综合示例:选择合适的数据结构
# 场景:学生成绩管理系统
# 1. 学生信息(不变)→ namedtuple
from collections import namedtuple
Student = namedtuple('Student', ['id', 'name', 'grade'])
# 2. 所有学生 → list(需要遍历和修改)
students = [
Student(1, "Alice", "A"),
Student(2, "Bob", "B")
]
# 3. 快速按ID查找 → dict
student_dict = {s.id: s for s in students}
# 4. 各等级学生 → defaultdict
from collections import defaultdict
grade_groups = defaultdict(list)
for student in students:
grade_groups[student.grade].append(student.name)
# 5. 唯一课程 → set
courses = {"Math", "Physics", "Chemistry"}
记住:没有"最好"的数据结构,只有"最适合"的数据结构。根据具体需求选择,并在性能和可读性之间取得平衡。