[python]数据结构(介绍)

198 阅读6分钟

一、核心数据结构概览

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快速查找,可能需顺序

性能对比表

操作ListTupleDictSetDeque
索引访问O(1)O(1)N/AN/AO(1)
追加O(1)*不可变O(1)O(1)O(1)
插入开头O(n)不可变N/AN/AO(1)
删除元素O(n)不可变O(1)O(1)O(1)
查找元素O(n)O(n)O(1)O(1)O(n)
内存占用

*: 列表追加平均O(1),但可能触发扩容

四、最佳实践建议

  1. 数据不可变性原则:如果不需修改数据,使用tuple而不是list
  2. 选择合适的数据结构:不要总是用list,根据操作类型选择
  3. 考虑时间复杂度:大数据集时选择O(1)或O(log n)操作的结构
  4. 使用内置工具:善用collections模块中的专用数据结构
  5. 命名元组替代简单类:当只需要存储数据时,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"}

记住:没有"最好"的数据结构,只有"最适合"的数据结构。根据具体需求选择,并在性能和可读性之间取得平衡。