字典和集合
Python中两种强大的数据结构:字典(dict)用于存储键值对,集合(set)用于存储无序唯一元素。两者都提供极快的查找速度。
为什么需要字典?
假设要存储学生的成绩信息,用两个list实现:
Python
# 用两个list存储姓名和成绩
names = ['Michael', 'Bob', 'Tracy']
scores = [95, 75, 85]
# 查找Tracy的成绩
index = names.index('Tracy') # 需要遍历整个list
print(scores[index]) # 85
这种方式的问题:list越长,查找越慢。因为需要从头遍历整个list才能找到目标元素的位置。
用字典实现,只需要一个"名字"到"成绩"的映射:
Python
# 直接通过名字查找成绩,无论字典多大,速度都一样快
d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
print(d['Tracy']) # 85
dict查找速度快的秘密
dict内部使用哈希算法(Hash):给定一个key,立即能计算出value的存储位置,实现 O(1) 时间复杂度查找。
哈希算法原理图解
┌─────────────────────────────────────────────────────────────────────┐
│ dict 哈希算法原理 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 假设要存储: d['Michael'] = 95 │
│ │
│ Step 1: 计算 key 的哈希值 │
│ ┌──────────────┐ │
│ │ hash('Michael') ──▶ 0x1a2b3c4d (内存地址) │
│ └──────────────┘ │
│ │ │
│ ▼ │
│ Step 2: 直接跳转到该地址存取 value │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ 内存布局: │ │
│ │ │ │
│ │ 0x1a2b3c4d: ┌─────────┐ │ │
│ │ │ 95 │ ◀── 直接取出! │ │
│ │ └─────────┘ │ │
│ │ │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ 关键点: │
│ • 无论字典有多大,给定key就能直接计算出value的位置 │
│ • 查找速度不会随字典大小增加而变慢 │
│ │
└─────────────────────────────────────────────────────────────────────┘
重要规则
dict的key必须是不可变对象!因为哈希算法要求相同的key必须产生相同的哈希值,如果key可变,哈希值就会变化,dict就会找不到数据。
创建字典
Python
# 方法1: 使用花括号直接创建
empty_dict = {}
person = {
"name": "张三",
"age": 25,
"city": "北京"
}
# 方法2: 使用 dict() 函数
# 从键值对列表创建
pairs = [["a", 1], ["b", 2]]
d = dict(pairs) # {'a': 1, 'b': 2}
# 使用关键字参数创建
d = dict(name="李四", age=30) # {'name': '李四', 'age': 30}
# 方法3: 使用 fromkeys() 创建空字典
d = dict.fromkeys(["a", "b", "c"], 0) # {'a': 0, 'b': 0, 'c': 0}
┌─────────────────────────────────────────────────────────────────────┐
│ dict 内存结构示意图 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ person = {"name": "张三", "age": 25, "city": "北京"} │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ dict 内部结构 │ │
│ │ │ │
│ │ ┌─────────┬─────────┬─────────┐ │ │
│ │ │ key │ ──▶ │ value │ │ │
│ │ ├─────────┼─────────┼─────────┤ │ │
│ │ │ "name" │ ──▶ │ "张三" │ │ │
│ │ ├─────────┼─────────┼─────────┤ │ │
│ │ │ "age" │ ──▶ │ 25 │ │ │
│ │ ├─────────┼─────────┼─────────┤ │ │
│ │ │ "city" │ ──▶ │ "北京" │ │ │
│ │ └─────────┴─────────┴─────────┘ │ │
│ │ │ │
│ │ 注意: key 必须是不可变类型 (str, int, tuple) │ │
│ │ value 可以是任意类型 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
访问字典
Python
d = {"name": "张三", "age": 25, "city": "北京"}
# 直接访问 (如果key不存在会报错!)
print(d["name"]) # 张三
# print(d["job"]) # KeyError: 'job'
# 使用 get() 方法 (安全访问,不会报错)
print(d.get("age")) # 25
print(d.get("job")) # None (返回None,不报错)
print(d.get("job", "未知")) # 未知 (提供默认值)
# 检查key是否存在
print("name" in d) # True
print("job" in d) # False
字典操作
Python
person = {"name": "张三", "age": 25, "city": "北京"}
# 添加/修改元素
person["job"] = "工程师" # 添加新键值对
person["age"] = 26 # 修改已存在的键
print(person)
# 删除元素
del person["city"] # 删除指定键值对
age = person.pop("age") # 删除并返回对应的value
print("删除的age:", age)
# popitem() 删除并返回最后一个键值对 (Python 3.7+)
item = person.popitem()
print("删除的项:", item)
# 清空字典
person.clear()
print(person) # {}
┌─────────────────────────────────────────────────────────────────────┐
│ 字典操作内存变化图 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 初始状态: │
│ person = {"name": "张三", "age": 25, "city": "北京"} │
│ │
│ person["job"] = "工程师" 后: │
│ person = {"name": "张三", "age": 25, "city": "北京", "job": "工程师"}│
│ │
│ del person["city"] 后: │
│ person = {"name": "张三", "age": 25, "job": "工程师"} │
│ │
│ ⚠️ 注意: 字典的"顺序"在 Python 3.7+ 是插入顺序 │
│ │
└─────────────────────────────────────────────────────────────────────┘
遍历字典
Python
d = {"name": "张三", "age": 25, "city": "北京"}
# 遍历所有key
for key in d:
print(key)
# 遍历所有key (显式方式)
for key in d.keys():
print(key)
# 遍历所有value
for value in d.values():
print(value)
# 遍历所有键值对
for key, value in d.items():
print(f"{key}: {value}")
# 字典长度
print(len(d)) # 3
┌─────────────────────────────────────────────────────────────────────┐ │ dict 遍历方法对比 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ 方法 │ 返回类型 │ 用途 │ │ ─────────────────┼────────────────┼───────────────────────────── │ │ d.keys() │ dict_keys │ 获取所有key │ │ d.values() │ dict_values │ 获取所有value │ │ d.items() │ dict_items │ 获取所有键值对 │ │ │ │ for key in d: # 最简洁,推荐使用 │ │ for key in d.keys(): # 显式表明在遍历key │ │ │ └─────────────────────────────────────────────────────────────────────┘
list 与 dict 对比
| 特性 | list | dict |
|---|---|---|
| 数据结构 | 有序序列 | 键值对映射 |
| 查找方式 | 通过索引 O(n) | 通过键 O(1) |
| 查找速度 | 随长度增加而变慢 | 始终很快 |
| 存储空间 | 节省内存 | 占用内存较多 |
| 使用场景 | 有序数据、需要索引 | 快速查找、键值映射 |
| 表示方式 | [1, 2, 3] | {'a': 1, 'b': 2} |
集合基础
集合(set)是一组无序且不重复的元素集合。可以把它看作只有key没有value的dict。
Python
# 创建集合
empty_set = set() # 注意: {} 创建的是空字典,不是空集合!
nums = {1, 2, 3, 4, 5}
# 自动去重
unique_nums = {1, 2, 2, 3, 3, 3}
print(unique_nums) # {1, 2, 3}
# 使用 set() 从其他序列创建
letters = set("hello")
print(letters) # {'h', 'e', 'l', 'o'} (l只保留一个)
# 从list创建集合 (常用去重方法)
duplicates = [1, 2, 2, 3, 3, 3]
unique = set(duplicates)
print(unique) # {1, 2, 3}
┌─────────────────────────────────────────────────────────────────────┐
│ set 特性示意图 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ s = {1, 2, 3, 4, 5} │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ set 内部结构 │ │
│ │ │ │
│ │ {1, 2, 3, 4, 5} ──▶ [位置由哈希算法决定] │ │
│ │ │ │
│ │ 特性: │ │
│ │ ✓ 无序 (不按插入顺序存储) │ │
│ │ ✓ 不重复 (自动去重) │ │
│ │ ✓ 需要可变对象作为元素 (但set本身不可变) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
集合运算
┌─────────────────────────────────────────────────────────────────────┐
│ 集合运算图解 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ A = {1, 2, 3, 4} B = {3, 4, 5, 6} │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ A │ │ B │ │
│ │ 1 2 3 4 │ │ 3 4 5 6 │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ 并集 (A | B): {1, 2, 3, 4, 5, 6} ─ 合并两个集合的所有元素 │
│ 交集 (A & B): {3, 4} ─ 两个集合都有的元素 │
│ 差集 (A - B): {1, 2} ─ 只在A中的元素 │
│ 对称差集 (A ^ B): {1, 2, 5, 6} ─ 不同时在A和B中的元素 │
│ │
└─────────────────────────────────────────────────────────────────────┘
Python
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
# 并集: 合并两个集合的所有元素
print(a | b) # {1, 2, 3, 4, 5, 6}
print(a.union(b)) # {1, 2, 3, 4, 5, 6}
# 交集: 两个集合都有的元素
print(a & b) # {3, 4}
print(a.intersection(b)) # {3, 4}
# 差集: 只在第一个集合中的元素
print(a - b) # {1, 2}
print(a.difference(b)) # {1, 2}
# 对称差集: 不同时在两个集合中的元素
print(a ^ b) # {1, 2, 5, 6}
print(a.symmetric_difference(b)) # {1, 2, 5, 6}
子集与超集
Python
a = {1, 2, 3}
b = {1, 2, 3, 4, 5}
c = {1, 2}
# 判断子集: c 是 a 的子集吗?
print(c.issubset(a)) # True
print(c <= a) # True
# 判断超集: a 是 c 的超集吗?
print(a.issuperset(c)) # True
print(a >= c) # True
# 判断真子集/真超集
print(a < b) # True (a是b的真子集)
print(a < a) # False (不是真子集)
print(a <= a) # True (是子集,但不是真子集)
# 判断是否不相交
print({1, 2}.isdisjoint({3, 4})) # True
print({`1`, `2`}.isdisjoint({2, 3})) # False (有交集2)
集合方法
Python
s = {1, 2, 3}
# 添加元素
s.add(4) # 添加单个元素
s.update([5, 6]) # 添加多个元素 (传入可迭代对象)
print(s) # {1, 2, 3, 4, 5, 6}
# 删除元素
s.remove(1) # 删除指定元素,不存在会报错 KeyError
s.discard(10) # 删除指定元素,不存在不会报错
removed = s.pop() # 随机删除并返回一个元素
print("popped:", removed)
# 清空集合
s.clear()
print(s) # set()
# 集合长度
s = {1, 2, 3}
print(len(s)) # 3
# 复制集合
s_copy = s.copy()
print(s_copy) # {1, 2, 3}
可变对象与不可变对象
在Python中,可变对象(如list、dict、set)可以在原地修改;不可变对象(如str、int、tuple)修改后会创建新对象。
┌─────────────────────────────────────────────────────────────────────┐
│ 可变对象 vs 不可变对象 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 不可变对象 (str, int, float, tuple): │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ a = 'abc' │ │
│ │ b = a.replace('a', 'A') # replace返回新字符串 │ │
│ │ │ │
│ │ ┌───┐ ┌───────┐ │ │
│ │ │ a │────▶│ 'abc' │ │ │
│ │ └───┘ └───────┘ │ │
│ │ ┌───┐ ┌───────┐ │ │
│ │ │ b │────▶│ 'Abc' │ ◀── 新创建的对象 │ │
│ │ └───┘ └───────┘ │ │
│ │ │ │
│ │ a 的值不变,b 是新对象 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 可变对象 (list, dict, set): │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ a = ['c', 'b', 'a'] │ │
│ │ a.sort() # 原地修改 │ │
│ │ │ │
│ │ ┌───────────────────┐ │ │
│ │ │ a ────────────────▶ ['a', 'b', 'c'] │ │
│ │ │ (同一个对象,内容被修改) │ │
│ │ └───────────────────┘ │ │
│ │ │ │
│ │ a 指向的对象被直接修改 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
为什么dict的key不能是可变对象?
因为dict用key的哈希值来计算存储位置。如果key是可变的,每次计算出的哈希值可能不同,dict就无法正确找到对应的value了。
Python
# 可变对象不能作为dict的key
key = [1, 2, 3]
d = {}
try:
d[key] = "value"
except TypeError as e:
print(f"错误: {e}")
# tuple可以作为dict的key (因为tuple不可变)
t = (1, 2, 3)
d2 = {t: "value"}
print(d2[t]) # value
# 但包含可变对象的tuple不能作为key
t2 = (1, [2, 3])
try:
d2[t2] = "error"
except TypeError as e:
print(f"错误: {e}")
章节测验
问题1: 以下哪个可以作为dict的key?
问题2: 下面代码的输出是什么?
d = {'a': 1, 'b': 2}
print(d.get('c', 0))
问题3: 两个set A={1,2,3}和B={2,3,4},A|B的结果是?
问题4: dict查找速度快的原理是什么?
练习题
练习1: 统计单词出现频率
给定一个字符串,统计每个字符出现的次数(忽略大小写)。
输入: "Hello World"
输出: {'h': 1, 'e': 1, 'l': 3, 'o': 2, ' ': 1, 'w': 1, 'r': 1, 'd': 1}
练习2: 找出共同好友
有两个用户的的好友列表,找出他们的共同好友。
user1_friends = {"Alice", "Bob", "Charlie", "David"}
user2_friends = {"Bob", "David", "Eve", "Frank"}
输出: {'Bob', 'David'}
练习3: 合并两个字典
合并两个字典,如果key冲突,使用第二个字典的值。
d1 = {'a': 1, 'b': 2}
d2 = {'b': 3, 'c': 4}
输出: {'a': 1, 'b': 3, 'c': 4}
🐍 Python 在线代码编辑器
📝 运行结果