哈希表
哈希表:从原理到碰撞处理
哈希表(Hash Table)是编程中最常用的高效数据结构之一,不管是 Python 的字典、Java 的 HashMap,还是 C++ 的 unordered_map,底层核心都是哈希表。它能实现「平均 O (1) 时间复杂度」的查找、插入和删除,比数组、链表快得多,今天就用最通俗的话讲清哈希表的原理和核心问题。
一、哈希表到底是什么?
简单说,哈希表的本质是「数组 + 映射规则」—— 把「任意类型的键(比如字符串、数字)」通过一套规则,转换成「数组的下标」,从而实现「通过键快速找值」。
举个生活例子:
你有一堆快递(键值对),快递站有一排货架(数组),每个货架有编号(下标)。你不用挨个翻货架,只需要按「快递单号最后一位」(映射规则)找对应货架,就能快速找到自己的快递 —— 这就是哈希表的核心逻辑。
二、哈希函数:把「任意键」变成「数组下标」
要让键对应数组下标,就得靠哈希函数。它的作用只有一个:接收任意类型的键(比如 "apple"、123),返回一个整数(数组下标)。
哈希函数的基本要求
- 相同的键,必须返回相同的结果(比如 "apple" 每次算出来都是 45);
- 尽量让不同的键,返回不同的结果(减少冲突);
- 计算要快,不能比遍历数组还慢。
常见的哈希函数(以数字键为例)
最朴素的哈希函数:哈希值 = 键 % 数组长度。
比如数组长度是 10,键是 123 → 123%10=3 → 对应数组下标 3;键是 45 → 45%10=5 → 对应下标 5。
如果键是字符串(比如 "apple"),会先把字符串转成数字(比如按 ASCII 码求和),再取模得到下标。
三、哈希碰撞:躲不开的小问题
理想状态下,每个键都对应唯一的数组下标,但现实中一定会出现「不同的键,哈希函数算出相同下标」的情况 —— 这就是哈希碰撞。
比如数组长度 10,键是 13(13%10=3)、23(23%10=3)、33(33%10=3),都映射到下标 3,这就发生了碰撞。
碰撞是哈希表必须解决的核心问题,工业界最常用的有两种方法:拉链法和线性探测法。
四、碰撞处理方法 1:拉链法(最常用)
拉链法是目前工业界的主流(比如 Java HashMap、C++ unordered_map),核心思路是「数组存链表头,碰撞就往链表加」。
具体逻辑
- 哈希表的数组每个下标,不直接存值,而是存「链表的头节点」;
- 没碰撞时:键值对直接存在下标对应的链表中(只有一个节点);
- 发生碰撞时:把新的键值对追加到该下标链表的末尾(或头部)。
可视化例子
数组长度 10,键 13、23、33 都映射到下标 3:
plaintext
数组下标:0 1 2 3 4 5 ... 9
| | | | | | |
∅ ∅ ∅ 13→23→33 ∅ ∅ ∅
- 查找键 23:先算下标 3,再遍历链表找到 23;
- 插入键 43:直接加到链表末尾,变成 13→23→33→43。
优缺点
✅ 优点:处理碰撞简单,不会连锁影响其他下标,空间利用率高;
❌ 缺点:链表遍历有少量开销(可优化成红黑树,比如数据量多的时候)。
五、碰撞处理方法 2:线性探测法(纯数组实现)
线性探测法是「开放寻址法」的一种,核心思路是「纯数组存储,碰撞就往后找空位」,没有链表。
具体逻辑
- 哈希表就是一个普通数组,每个下标直接存键值对;
- 没碰撞时:键值对直接存在计算出的下标位置;
- 发生碰撞时:从目标下标开始,按步长 1 向后逐个检查,找到第一个空位置存放。
可视化例子
数组长度 10,键 13(下标 3)、23(下标 3,碰撞)、33(下标 3,碰撞):
- 插入 13:下标 3 为空,直接存 →
[∅,∅,∅,13,∅,∅,∅,∅,∅,∅]; - 插入 23:下标 3 被占,往后找下标 4,存 23 →
[∅,∅,∅,13,23,∅,∅,∅,∅,∅]; - 插入 33:下标 3、4 被占,往后找下标 5,存 33 →
[∅,∅,∅,13,23,33,∅,∅,∅,∅]。
关键注意点
- 查找时:先算目标下标,找不到就往后找,直到找到目标或空位置(空位置说明不存在);
- 必须保证「数组长度>数据量」:否则空位太少,碰撞会越来越多,效率大幅下降;
- 删除时:不能直接置空(会导致后续查找中断),需标记「已删除」。
优缺点
✅ 优点:无链表开销,数组连续内存访问快;
❌ 缺点:容易出现「堆积问题」(一个碰撞引发多个后续碰撞),删除逻辑复杂。
总结
- 哈希表核心:用哈希函数把键转成数组下标,实现快速访问;
- 哈希碰撞:不同键映射到相同下标,是必然问题;
- 拉链法:数组 + 链表,碰撞追加到链表,工业界主流;
- 线性探测:纯数组,碰撞往后找空位,适合数据量小、碰撞少的场景。
哈希表的核心优势是「快」,只要哈希函数设计合理、碰撞处理得当,就能在大部分场景下吊打其他数据结构 —— 这也是它成为编程必备工具的原因。

浙公网安备 33010602011771号