哈希表

哈希表(Hash Table,也叫散列表)以「键值对」形式存取数据:通过哈希函数把 key 直接换算成数组下标,使查找、插入、删除的平均时间复杂度都达到 O(1)。字典、缓存、去重与数据库索引都以它为基石,是数据结构面试的高频考点。

核心原理:数组 + 哈希函数

哈希表 = 一段数组 + 一个哈希函数。插入时先对 key 求哈希值,再对数组长度取模得到下标并存入;查找时用同样的计算直接定位,无需逐个比较。

key → 哈希函数 → hash 值 → 对长度取模 → 数组下标 → 槽位
book = {}                    # 空哈希表(Python dict)
book["apple"] = 3            # 插入:内部算 hash("apple") 定位槽位
print(book.get("apple"))     # 输出:3,O(1) 查回
print(len(book))             # 输出:1

理想的哈希函数应计算快、分布均匀,让不同 key 尽量散落到不同槽位。

哈希冲突与解决方案

不同 key 算出同一个下标就叫「哈希冲突」。数组长度有限而 key 无限,冲突无法避免,常见两种解法:

  • 链地址法(拉链法):冲突元素挂在同一槽位的链表上,Java HashMap 采用此法。
  • 开放寻址法:冲突后按规则找下一个空位,如线性探测逐个后移。
下标:     0         1         2         3
链地址:   k1 → k8   null      k3 → k9   null

查找时先定位下标,再沿链表比较 key;冲突越少越接近 O(1)。

负载因子与扩容

负载因子 = 已存元素数 ÷ 槽位总数,反映哈希表的拥挤程度。因子过高时冲突剧增、性能退化为 O(n),此时应扩容:把数组扩大(通常翻倍)并把旧元素重新哈希到新表。Java HashMap 默认负载因子 0.75,容量 16 存到 12 个就扩容;Go map、Python dict 也有各自的装载阈值与渐进式迁移策略。面试常问「为什么扩容后要重新哈希?」——因为数组长度变了,取模结果也随之改变,旧下标不再有效。

常见实现对比

实现冲突策略扩容机制特点
Java HashMap链地址,链表过长转红黑树负载因子 0.75 翻倍线程不安全
Go map桶 + 溢出桶装载因子约 6.5 触发并发写会 panic
Python dict开放寻址动态缩容/扩容保留插入顺序
Redis hash链地址渐进式 rehash扩容不阻塞服务

复杂度与面试要点

  • 平均复杂度:查找、插入、删除均为 O(1);最坏情况(全部冲突成一条链)退化为 O(n)。
  • 高频题:两数之和、字母异位词分组、LRU 缓存(哈希表 + 双向链表)。
  • 回答模板:先点出「用空间换时间」,再讲哈希函数、冲突解决、负载因子与扩容,最后补一句最坏 O(n) 的边界意识。
  • 加分点:哈希表无序,键必须可哈希——Go 中 slice 不能当 map 的 key。

小结:哈希表用数组下标换取 O(1) 存取,核心是哈希函数、冲突解决与扩容三件事;把复杂度与 HashMap/Go map 的工程细节说清,就能稳拿这题。

笔记加载中…