Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

开放寻址处理冲突

复习

  • 哈希函数与哈希表:根据键直接算出位置
  • 拉链法:用链表格外串联冲突元素
  • 数组:连续存储,可按位置访问

TL;DR

  • 开放寻址不额外用链表,而是在表内继续找空位
  • 常见探测方式:线性探测、二次探测、双重哈希
  • 它省去指针开销,对缓存更友好
  • 但删除麻烦,还容易出现“聚堆”

正文

  拉链法靠额外的链表来安置冲突元素。另一派思路是:所有元素都放在表本身里,位置被占了,就按规则继续找下一个空位。 这叫开放寻址(open addressing)。

位置被占,就另找一个

  开放寻址的基本动作是:

  1. 用哈希函数算出理想位置
  2. 如果那个位置已经有人,就按某种探测规则,依次试下一个位置
  3. 直到找到空位放进去

  查找时同理:先算位置,不对就按同样的规则一路找下去,直到找到目标或遇到空位。

  常见的探测规则有:

  • 线性探测:位置被占,就试下一个、再下一个
  • 二次探测:按平方的间隔跳着试,避免挤在一起
  • 双重哈希:再用第二个哈希函数决定“每次跳多远”

好处与麻烦

  开放寻址的最大好处,是不需要额外的指针。所有数据都挨在同一个数组里,内存更紧凑,对 CPU 缓存也更友好——这在追求性能的场合很关键。

  但它也有两个麻烦:

  • 聚堆:线性探测尤其容易出现“越挤越挤”的现象。一旦某个区域被占满,后面的元素更容易连成一片,查找时就要一路走过长长的占用区
  • 删除难:不能简单地把元素清空,否则会打断后面元素的探测链。通常要打一个特殊的“墓碑”标记,表示“这里曾经有元素,但已删除,请继续往后找”

  所以,开放寻址在实现上,比拉链法要更小心一些。

两种办法的取舍

  把两者放在一起看:

  • 拉链法:额外链表,删除方便,但多花指针空间
  • 开放寻址:无额外结构,缓存友好,但删除麻烦、易聚堆

  又一次熟悉的取舍:没有白来的好处,选择取决于你更在意空间、删除的便利,还是缓存性能。

思考题 1

  开放寻址与拉链法在“冲突元素放在哪”这件事上,有什么根本不同?

思考题 2

  为什么开放寻址在删除元素时,通常要打一个“墓碑”标记?

小结

知识点

  • 开放寻址在表内按探测规则寻找空位
  • 线性探测、二次探测、双重哈希是常见方式
  • 它无指针开销、缓存友好
  • 但存在聚堆问题,删除需借助墓碑标记

参考资料

  1. Wikipedia(zh):开放寻址法:在表内寻找空位处理冲突
  2. Wikipedia(zh):哈希表:比较不同冲突处理策略

思考题答案(仅供参考)

思考题 1

  拉链法把冲突元素放到原位置之外的链表节点里,表只存“桶”;开放寻址则把冲突元素继续放在表本身中的其他空位,不引入额外结构。一个是“外挂”,一个是“就地另找”。

思考题 2

  因为查找依赖探测链:从理想位置一路往后找。如果直接清空被删元素,探测链就断了,后面本应能通过继续探测找到的元素可能找不到。留一个“墓碑”标记,表示这里已删但仍需继续探测,从而不破坏查找的正确性。

协议

  本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

封面图

设计师 | 南国微雪