链地址法(Chaining)和开放地址法(Open Addressing)是哈希表中解决哈希冲突(不同键映射到相同位置)的两种主要策略。
一、链地址法
哈希表的每个槽位(桶)不直接存储键值对本身,而是存储一个链表(或其他容器,如红黑树)的指针。当发生哈希冲突时(即多个键被哈希到同一个槽位),这些键值对会被添加到该槽位对应的链表中。
原理:
- 插入: 计算键的哈希值,找到对应的槽位。将新的键值对插入到该槽位所指向的链表中(通常插入链表头部或尾部)。
- 查找: 计算键的哈希值,找到对应的槽位。遍历该槽位指向的链表,查找目标键。
- 删除: 计算键的哈希值,找到对应的槽位。遍历该槽位指向的链表,找到目标键值对并将其从链表中移除。
二、开放寻址法
1.核心思想:
- 哈希表的每个槽位直接存储键值对(或者一个标记)。
- 当发生哈希冲突时,不会使用额外的数据结构(如链表),而是根据一个预先定义的探测序列(Probing Sequence)在哈希表中寻找下一个空闲的槽位来存放冲突的键值对。
- 所有元素都存储在数组本身中。
2.原理:
- 插入:
- 计算键的初始哈希值 h0 = hash(key)。
- 如果槽位 h0 空闲,则直接插入。
- 如果槽位 h0 已被占用(发生冲突),则根据探测序列计算下一个位置 h1, h2, h3, …,直到找到一个空闲槽位插入。如果遍历完整个表都没找到空闲槽位,则哈希表已满,需要扩容。
- 查找:
- 计算键的初始哈希值 h0 = hash(key)。
- 检查槽位 h0:
- 如果槽位为空,说明键不存在。
- 如果槽位不为空且键匹配,则查找成功。
- 如果槽位不为空但键不匹配(冲突),则根据探测序列计算下一个位置 h1, h2, h3, …,继续检查。
- 在探测过程中,如果遇到一个真正空闲的槽位(不是被删除标记占据),说明键不存在。
- 删除:
- 不能简单地将找到的槽位置为空。因为这可能会中断后续的查找探测序列(后续查找遇到空槽会误认为键不存在)。
- 常见的做法是使用一个特殊的标记(如 DELETED)来标记被删除的槽位。
- 插入操作可以将 DELETED 标记的槽位视为空闲槽位并插入新元素。
- 查找操作遇到 DELETED 标记时需要继续探测。
- 这种标记可能导致“脏”槽位积累,需要定期清理(如rehash)。
3.探测序列类型
- 线性探测: hi = (h0(key) + i) % TableSize (i = 1, 2, 3, …)。最简单,但容易产生聚集(Clustering),导致性能下降。
- 二次探测法: 当发生冲突时,根据一个二次函数来确定下一个探测位置。
- hi = (h0(key) + Ci) % TableSize (Ci是一个二次函数,去+/- i的平方)。
- 与线性探测相比,二次探测法可以避免“线性聚集”问题,即冲突的元素不会集中在连续的位置上。但是可能会出现“二次聚集”,即某些位置被频繁探测。
- 随机探测: 当发生冲突时,随机选择一个位置作为下一个探测点。
- 具体来说,假设初始哈希地址为 h(key),则探测序列可以表示为:hi = (h0(key) + i) % TableSize (i是一个随机数)。
- 再哈希法: 通过使用两个不同的哈希函数来确定探测序列,从而避免冲突聚集问题,提高哈希表的性能。
- 当发生冲突时,使用第二个哈希函数来计算偏移量,而不是固定地增加一个常量或二次函数值。
- hi = (h1(key) + i*h2(key) % TableSize。h1(key)是主哈希函数,用于计算初始地址。h2(key)是次哈希函数,用于计算偏移量。i 是探测次数,从 1 开始递增。
9158

被折叠的 条评论
为什么被折叠?



