当前位置:首页>python>Python 哈希集合(set)与 字典(dict)超详细讲解

Python 哈希集合(set)与 字典(dict)超详细讲解

  • 2026-10-11 07:06:19
Python 哈希集合(set)与 字典(dict)超详细讲解

Python 哈希集合(set)与 字典(dict)超详细讲解


一、先搞懂底层:什么是「哈希」?

想象你去超市存包:

  • 普通列表(list):像一排没有号码的柜子。你要找一个包,只能逐个打开看,最坏情况下要翻遍所有柜子。查找速度 O(n)。
  • 哈希表(Hash Table):像智能存包柜。你输入手机号,系统立刻算出「3号柜」,你直接去3号柜取包。查找速度接近 O(1)。

核心原理:通过一个「哈希函数」,把任意数据(字符串、数字、元组等)转换成固定长度的数字(哈希值),这个数字就是数据的「门牌号」。Python 的 dict 和 set 底层都是基于哈希表实现的。


二、字典(dict)—— 带「门牌号」的储物柜

1. 一句话定义

字典是键值对(key-value)的集合。每个 key 必须唯一且不可变,通过 key 可以在常数时间内找到对应的 value。

2. 生活中的类比

就像一本通讯录:

  • key = 人名(唯一的标识)
  • value = 电话号码(实际存储的数据)
# 创建字典的多种方式
phone_book = {"Alice": "13800138000", "Bob": "13900139000"}
phone_book = dict(Alice="13800138000", Bob="13900139000")
phone_book = dict([("Alice", "13800138000"), ("Bob", "13900139000")])

# 空字典
empty = {}
empty = dict()

3. 核心操作详解

增 & 改

phone_book = {"Alice": "13800138000"}

# 新增:key 不存在就添加
phone_book["Charlie"] = "13700137000"

# 修改:key 存在就覆盖
phone_book["Alice"] = "15000150000"

# 批量更新
phone_book.update({"David": "13600136000", "Alice": "99999999999"})

查

# 方式1:直接索引(key 不存在会报错 KeyError)
num = phone_book["Alice"]

# 方式2:get()(key 不存在返回 None 或默认值,不会报错)
num = phone_book.get("Eve")           # None
num = phone_book.get("Eve", "未知")    # "未知"

# 方式3:判断是否存在
if"Alice"in phone_book:   # 这里底层就是哈希查找,速度极快
    print("找到了!")

删

# 删除并返回值
num = phone_book.pop("Alice")

# 删除不报错
phone_book.pop("Eve", None)

# 清空
phone_book.clear()

4. 遍历字典

info = {"name": "小明", "age": 18, "city": "北京"}

# 遍历键(默认)
for key in info:
    print(key)

# 遍历值
for value in info.values():
    print(value)

# 遍历键值对
for key, value in info.items():
    print(f"{key}: {value}")

5. 对 key 的严格要求

key 必须是不可变类型(hashable):

  • ✅ 可用:字符串、数字、元组(元组内部元素也必须是不可变的)
  • ❌ 不可用:列表、字典、集合
# 正确
d = {(1, 2): "坐标点"}      # 元组可以
d = {3.14: "圆周率"}        # 浮点数可以

# 错误!会报错
# d = {[1, 2]: "列表"}      # TypeError: unhashable type: 'list'

为什么列表不能当 key?因为列表是可变的——如果允许列表当 key,你后面改了列表里的元素,它的哈希值就变了,字典就找不到它了,整个哈希表会乱套。


三、集合(set)—— 只存「门牌号」的储物柜

1. 一句话定义

集合是无序、不重复的元素集合。它只关心「某个元素在不在集合里」,不存储额外的 value。

2. 生活中的类比

  • 黑名单系统:手机号要么在黑名单里,要么不在。不关心这个手机号对应什么信息。
  • 去重工具:把一堆重复的数据扔进去,出来就是唯一值。
# 创建集合
s = {1, 2, 3, 3, 3}        # 自动去重,结果是 {1, 2, 3}
s = set([1, 2, 2, 3, 3])   # 从列表转换,结果也是 {1, 2, 3}

# 空集合(注意:{} 是空字典!)
empty = set()                # 正确
# empty = {}                 # 错误!这是字典

3. 核心操作详解

增删查

s = {1, 2, 3}

# 添加元素
s.add(4)           # {1, 2, 3, 4}
s.add(3)           # 重复添加无效,仍然是 {1, 2, 3, 4}

# 删除
s.remove(3)        # 元素不存在会报错 KeyError
s.discard(3)       # 元素不存在也不报错(更安全)

# 随机删除并返回一个元素
elem = s.pop()

# 判断是否存在
if2in s:         # O(1) 速度
    print("存在")

集合运算(set 的杀手锏)

a = {1, 2, 3, 4}
b = {3, 4, 5, 6}

# 并集:所有出现的元素(A ∪ B)
a | b              # {1, 2, 3, 4, 5, 6}
a.union(b)

# 交集:共同拥有的元素(A ∩ B)
a & b              # {3, 4}
a.intersection(b)

# 差集:在 A 但不在 B(A - B)
a - b              # {1, 2}
a.difference(b)

# 对称差集:只在 A 或只在 B,不同时在两边(A △ B)
a ^ b              # {1, 2, 5, 6}
a.symmetric_difference(b)

# 子集判断
{1, 2}.issubset(a)        # True
a.issuperset({1, 2})      # True

4. 经典应用场景

场景1:去重(最常用)

nums = [1, 2, 2, 3, 3, 3, 4]
unique_nums = list(set(nums))   # [1, 2, 3, 4]

场景2:快速判断「是否已存在」

# 检查用户是否重复投票
voted = set()

defvote(user_id):
if user_id in voted:
return"您已经投过票了"
    voted.add(user_id)
return"投票成功"

场景3:找两个列表的共同元素

list_a = ["apple", "banana", "cherry"]
list_b = ["banana", "date", "fig"]

common = set(list_a) & set(list_b)   # {'banana'}

四、dict 和 set 的底层原理

1. 哈希函数的工作流程

以 d = {"apple": 100} 为例:

  1. 计算 "apple" 的哈希值:hash("apple") → 得到一个很大的整数
  2. 对这个整数取模,映射到数组的某个索引位置:index = hash("apple") % array_size
  3. 把 "apple" 和 100 打包成一个条目,放到数组的 index 位置
  4. 下次查找 "apple" 时,重复步骤 1-2,直接定位到同一个位置,取出 100

2. 哈希冲突(Collision)

不同 key 的哈希值可能相同(就像两个人手机号不同但存包柜算出来都是3号)。Python 解决冲突的方法是开放寻址法(Open Addressing):

  • 如果3号柜被占了,就按固定规则试4号、5号……直到找到空位。
  • 查找时也一样:先算3号,不对就继续试下一个,直到找到或遇到空位。

3. 动态扩容

当字典/集合里的元素越来越多,冲突概率上升,Python 会自动扩容(把数组变大,重新分配所有元素)。扩容时所有元素要重新计算位置,所以偶尔某次插入会变慢,但平均下来仍然是 O(1)。


五、dict vs set 对比总结

特性
字典(dict)
集合(set)
存储内容
键值对(key → value)
只有元素值
key/元素要求
不可变、唯一
不可变、唯一
查找速度
O(1)
O(1)
有序性
Python 3.7+ 保持插入顺序
无序
重复处理
key 重复会覆盖 value
重复元素自动忽略
主要用途
映射关系、配置项、计数
去重、成员判断、集合运算

六、常见坑与注意事项

坑1:用可变对象当 key

# 错误
d = {["a"]: 1}     # TypeError: unhashable type: 'list'

# 解决:把列表转成元组
d = {tuple(["a"]): 1}   # 正确

坑2:自定义对象作为 key 的问题

classPerson:
def__init__(self, name):
        self.name = name

p = Person("Alice")
d = {p: "员工"}

# 如果修改了 p 的属性,可能导致字典找不到它
# 因为哈希值是基于对象创建时的状态计算的

坑3:集合不能存列表、字典

s = {[1, 2], [3, 4]}   # TypeError
s = {(1, 2), (3, 4)}   # 正确,元组可以

坑4:set() 和 {} 的区别

a = {}        # 这是空字典!
b = set()     # 这才是空集合

七、一句话记忆口诀

字典是「查字典」:给一个词(key),找到释义(value)。
集合是「查名单」:只看某个人在不在名单里,不关心他具体是干嘛的。
两者底层都是哈希表,所以查找都飞快,但key/元素必须不可变。

最新文章

随机文章