基本概念
- 负载因子
- n → 待哈希的元素个数
- b → 哈希表中值的总数
- 时碰撞概率大, 反之小
- 在 Java 中,当负载因子超过 时,系统会将哈希表扩容至原先的2倍
哈希函数
统一公式:
index = hash(key) % capacity
取余法
- , 其中M为 ≤ 基本区长度的最大质数
- 取最大质数是为了使得冲突率尽可能小
平方取中法
- 取中间部分, 取多长取决于表的大小

乘法杂凑函数
哈希冲突
- 碰撞的两个(或多个)键值称为同义词, 即
链式地址(拉链法)
- 将单个元素转换为链表,将键值对作为链表节点,将所有发生冲突的键值对都存储在同一链表中。

- 操作方式变化:
- 查询元素:输入
key
,经过哈希函数得到桶索引,即可访问链表头节点,然后遍历链表并对比key
以查找目标键值对。 - 添加元素:首先通过哈希函数访问链表头节点,然后将节点(键值对)添加到链表中。
- 删除元素:根据哈希函数的结果访问链表头部,接着遍历链表以查找目标节点并将其删除。
- 局限性
- 占用空间增大:链表包含节点指针,它相比数组更加耗费内存空间。
- 查询效率降低:因为需要线性遍历链表来查找对应元素。
- 当链表很长时查询效率很差, 接近, 可以考虑将链表转换为AVL树或红黑树, 优化至
开放寻址(占坑法)
- 所有的开放寻址方法都存在不能直接删除的问题
线性探测
- 采用固定步长的线性搜索来进行探测
- 操作变化
- 插入元素:通过哈希函数计算桶索引,若发现桶内已有元素,则从冲突位置向后线性遍历(步长通常为 ),直至找到空桶,将元素插入其中。
- 查找元素:若发现哈希冲突,则使用相同步长向后进行线性遍历,直到找到对应元素,返回
value
即可;如果遇到空桶,说明目标元素不在哈希表中,返回None
。
- 缺陷: 容易产生聚集现象, 数组中连续被占用的位置越长,这些连续位置发生哈希冲突的可能性越大,从而进一步促使该位置的聚堆生长,形成恶性循环,最终导致增删查改操作效率劣化。
- 注意: 不能在开放寻址哈希表中直接删除元素。这是因为删除元素会在数组内产生一个空桶
None
,而当查询元素时,线性探测到该空桶就会返回,因此在该空桶之下的元素都无法再被访问到,程序可能误判这些元素不存在. 在此情况下选择另开一个数组tombstone[]
来记录每个桶是否有效(懒删除机制). 然而懒删除有可能会加速哈希表的性能退化, 因为随着TOMBSTONE
的增加,搜索时间也会增加,因为线性探测可能需要跳过多个TOMBSTONE
才能找到目标元素。为此,考虑在线性探测中记录遇到的首个TOMBSTONE
的索引,并将搜索到的目标元素与该TOMBSTONE
交换位置。这样做的好处是当每次查询或添加元素时,元素会被移动至距离理想位置(探测起始点)更近的桶,从而优化查询效率。(将tombstone
向后移动, 有效桶向前移动)
- 一般使用环形数组实现, 这样线性探测可以从尾部返回头部, 充分利用空间
平方探测
- 平方探测与线性探测类似,都是开放寻址的常见策略之一。当发生冲突时,平方探测不是简单地跳过一个固定的步数,而是跳过“探测次数的平方”的步数,即1, 4, 9, …步。
- 优势:
- 平方探测通过跳过探测次数平方的距离,试图缓解线性探测的聚集效应。
- 平方探测会跳过更大的距离来寻找空位置,有助于数据分布得更加均匀。
- 劣势:
- 仍然存在聚集现象,即某些位置比其他位置更容易被占用。
- 由于平方的增长,平方探测可能不会探测整个哈希表,这意味着即使哈希表中有空桶,平方探测也可能无法访问到它。
多次哈希
- 使用多个哈希函数进行探测
- 操作变化:
- 插入: 若哈希函数出现冲突,则尝试,以此类推,直到找到空位后插入元素。
- 查找元素:在相同的哈希函数顺序下进行查找,直到找到目标元素时返回;若遇到空位或已尝试所有哈希函数,说明哈希表中不存在该元素,则返回
None
。
- 优势: 不易产生聚集
- 劣势: 带来了额外的计算量