发布于2026-06-01 阅读(0)
扫一扫,手机访问
假设你要根据同学的名字查找对应的成绩。最朴素的做法是用两个列表,下标一一对应:

names = ["A", "B", "C"]
scores = [59, 89, 90]
# 查找 B 的成绩
idx = names.index("B") # O(n) 遍历
print(scores[idx]) # 89
这样做的坏处显而易见:每次查找都要把整个列表从上到下捋一遍。如果有 100 万个名字,最坏情况下就得比对着看 100 万次——这就是 O(n) 的代价。
但 Python 里有个神器叫 dict(字典),它能把这个操作直接变成 O(1):
d = {"D": 100, "E": 99, "F": 99}
print(d["D"]) # 100 —— 一步到位!
这就像字典前面的偏旁索引——你不用从第一页翻到最后一页,而是直接定位到目标。这个“一步到位”的能力,背后站着的就是哈希表(Hash Table)。
哈希表是一种基于键值对(key-value)存储数据的结构。它的工作流可以想象成一条流水线:
key ──→ 哈希函数 ──→ 索引 ──→ 存储位置(value)
O(1) 取出 value不同的 key 有可能算出相同的索引——冲突就这样产生了。比如 "abc" 和 "cba" 经过哈希后,恰好定位到同一个槽位。
对于这个难题,业内主要有两种解法:
| 方式 | 做法 | Python 采用? |
|---|---|---|
| 链地址法 | 每个槽位存一个链表,冲突的元素串起来 | |
| 开放寻址法 | 当前槽位被占,就去下一个空位(探测) |
CPython 选的是开放寻址法 + 伪随机探测。这也是为什么 dict 总要预留大量空位——表一旦塞得太满,探测路径就会变长,O(1) 的承诺就会打折扣。
这就揭示了一个核心特征:哈希表天生就是“空间换时间”的——占据内存多,但查找极快。
d = {"D": 100, "E": 99, "F": 99}
# 取值
d["D"] # 100(key 不存在则 KeyError)
# 安全取值
d.get("Thomas") # None(不报错)
d.get("Thomas", -1) # -1(自定义默认值)
# 新增 / 修改
d["Adam"] = 67 # 新增
d["Adam"] = 90 # 修改(key 相同,覆盖原值)
# 判断 key 是否存在
"Thomas" in d # False
# 删除
d.pop("Adam") # 返回 90,key 不存在则 KeyError
| 方法 | 说明 | 示例 |
|---|---|---|
d.keys() | 返回所有 key 的视图 | d.keys() |
d.values() | 返回所有 value 的视图 | d.values() |
d.items() | 返回 (key, value) 对的视图 | for k, v in d.items() |
d.update(d2) | 将 d2 的键值对合并进来 | d.update({"x": 1}) |
d.setdefault(k, v) | key 存在则返回其值,不存在则设为 v | d.setdefault("a", 0) |
d.popitem() | 移除并返回最后插入的键值对(Python 3.7+ 起保证 LIFO 行为) | d.popitem() |
d.clear() | 清空所有元素 | d.clear() |
从 Python 3.7 开始,dict 保证按插入顺序遍历(3.6 只是 CPython 的实现细节,3.7 变成了语言规范)。
d = {}
d["a"] = 1
d["c"] = 3
d["b"] = 2
print(list(d.keys())) # ['a', 'c', 'b'] —— 严格按插入顺序
# 将列表转为 {元素: 索引} 的映射
items = ["apple", "banana", "cherry"]
d = {v: i for i, v in enumerate(items)}
# {"apple": 0, "banana": 1, "cherry": 2}
# 基于条件过滤
squares = {x: x**2 for x in range(10) if x % 2 == 0}
# {0: 0, 2: 4, 4: 16, 6: 36, 8: 64}
Python 标准库 collections 提供了几个常用的 dict 变体,可以说是日常开发的利器:
from collections import defaultdict, Counter, OrderedDict
# defaultdict —— 访问不存在的 key 时自动生成默认值
dd = defaultdict(list)
dd["fruits"].append("apple") # 无需先初始化 []
print(dd["fruits"]) # ['apple']
dd = defaultdict(int)
words = ["a", "b", "a", "c", "b", "a"]
for w in words:
dd[w] += 1 # 无需判断 key 是否存在
print(dd) # {'a': 3, 'b': 2, 'c': 1}
# Counter —— 专门用于计数的字典
c = Counter(words)
print(c.most_common(2)) # [('a', 3), ('b', 2)]
| 操作 | 平均 | 最坏 |
|---|---|---|
查找 d[k] | O(1) | O(n) |
插入 d[k] = v | O(1) | O(n) |
删除 del d[k] | O(1) | O(n) |
| 遍历 | O(n) | O(n) |
k in d | O(1) | O(n) |
| 维度 | dict(哈希表) | list(动态数组) |
|---|---|---|
| 查找 | O(1) | O(n) |
| 插入 | O(1) | O(n)(中间插入) |
| 内存占用 | 大(需预留空位) | 小 |
| 有序性 | 插入顺序(3.7+) | 索引顺序 |
| 设计哲学 | 空间换时间 | 时间换空间 |
选择建议:
dictlistset# ✅ 不可变类型 — 可作 key
d = {}
d["name"] = "Alice" # str 可哈希
d[42] = "answer" # int 可哈希
d[(1, 2)] = "point" # tuple(元素全不可变)可哈希
# ❌ 可变类型 — 不可作 key
d[[1, 2, 3]] = "list" # TypeError: unhashable type: 'list'
为什么? dict 靠 key 的哈希值来决定 value 的存放位置。如果 key 是可变的(比如 list 能 append),它的哈希值就会跟着变,dict 就再也找不到原来的数据了——整个结构会陷入混乱。
# list 是可变对象 — 方法修改对象本身
a = ['c', 'b', 'a']
a.sort() # sort() 原地修改,返回 None
print(a) # ['a', 'b', 'c'] — 同一个对象,内容变了
# str 是不可变对象 — 方法返回新对象
s = "abc"
print(s.replace("a", "A")) # "Abc" — 新字符串
print(s) # "abc" — 原字符串未变!
set 和 dict 本质上是同一类东西——都是一组 key 的集合。唯一的区别是 set 不存储对应的 value。因为 key 不能重复,set 天生就保证了元素唯一。
# 两种创建方式
s = {1, 2, 3} # 字面量
s = set([1, 2, 3, 4, 2]) # 从可迭代对象构建,自动去重 → {1, 2, 3, 4}
# 基本操作
s.add(5) # 添加元素
s.remove(1) # 删除元素(元素不存在则 KeyError)
s.discard(1) # 删除元素(元素不存在也不报错)
s1 = {1, 2, 3}
s2 = {2, 3, 4}
# 交集
s1 & s2 # {2, 3}
s1.intersection(s2)
# 并集
s1 | s2 # {1, 2, 3, 4}
s1.union(s2)
# 差集
s1 - s2 # {1} —— 在 s1 但不在 s2
s1.difference(s2)
# 对称差集(并集 - 交集)
s1 ^ s2 # {1, 4} —— 在 s1 或 s2 但不同时在两者
s1.symmetric_difference(s2)
# 找出列表中所有出现过的字符
words = ["hello", "world", "python"]
unique_chars = {c for w in words for c in w}
# {'h', 'e', 'l', 'o', 'w', 'r', 'd', 'p', 'y', 't', 'n'}
a = {1, 2}
b = {1, 2, 3, 4}
a.issubset(b) # True — a 是 b 的子集
b.issuperset(a) # True — b 是 a 的超集
a.isdisjoint({5}) # True — 没有交集
fs = frozenset([1, 2, 3])
# fs.add(4) # AttributeError —— 不可修改
d = {fs: "valid"} # ✅ frozenset 可作 dict 的 key(因为不可变)
| 操作 | 平均 |
|---|---|
添加 add | O(1) |
删除 remove | O(1) |
成员判断 x in s | O(1) |
并集 | | O(len(s1) + len(s2)) |
交集 & | O(min(len(s1), len(s2))) |
差集 - | O(len(s1)) |
| 场景 | 使用 | 理由 |
|---|---|---|
| 快速查找 / 映射关系 | dict | O(1) 查找 |
| 去重 | set | 元素自动唯一 |
| 成员判断(是否存在) | set | O(1) vs list 的 O(n) |
| 交集 / 并集 / 差集运算 | set | 原生支持集合运算 |
| 计数统计 | Counter | 专为此场景优化 |
| 缓存 / 记忆化 | dict | 快速存取 |
| 需要按索引顺序访问 | list | dict 插入顺序不替代索引 |
| 不可变的常量集合 | frozenset | 可哈希,可作 key |
collections 提供 defaultdict、Counter 等实用扩展
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8