附录:其他的冲突处理方案
常见的两种哈希冲突解决方案如下:
- 链表法
- 开放寻址法
链表法
分离链表法中,每一个桶包含一个链接表。当键值对的键冲突时,键值对会被加入到这个列表中。它支持的方法如下:
插入:
计算关键字的哈希值来查找桶的下表索引。如果访问的桶没有值,把键值对插入到这个桶中。如果已经保存了键值对了,将待插入的键值对追加到链接表后面。
搜索:
计算关键字的哈希值来查找桶的下表索引。遍历链接表,用待查找的关键字跟比较每一个键值对的关键字。如果找到了关键字,返回对应的值,否则返回空。
删除:
计算关键字的哈希值来查找桶的下表索引。遍历链接表,用待查找的关键字跟比较每一个键值对的关键字。如果找到了关键字,从链接表中删除对应的键值对。如果链接表中只有一个键值对了,在桶中放一个空值以标志链接表是空的。
这个方法的优势是易于实现,但是空间效率低下。每一个键值对都保存了所在链接表中的下一个节点的指针。如果没有下一个节点保存空指针。空间浪费在了记录指针上面,这本可以用来存储更多键值对的。
开放寻址法
开放寻址法解决了链表法的空间效率低的问题。当发生冲突时,冲突的键值对被放在哈希表的其他桶中。放置键值对的桶,是依据预设好的规则来选择的。这样,查找键值对的时候可能出现重复。目前有三种常见的方法来为冲突的键值对选择选择可插入的桶。
线性探测
当发生冲突时,增加下标,然后把键值对放在数组的下一个可用的桶中。方法如下:
插入:
计算关键字的哈希值来查找桶的下标索引。如果桶是空的,把键值对插入的这里。如果桶是非空的,重复增加下表的动作,直到找到空的桶,然后把键值对插入到这个桶中。
搜索:
计算关键字的哈希值来查找桶的下表索引。重复增加下标,比较每一个键值对的关键字跟待查找的关键字,直到找到一个空的桶。如果匹配到了待查询的关键字,返回对应的值,否则返回空。
删除:
计算关键字的哈希值来查找桶的下表索引。重复增加下标,比较每一个键值对的关键字跟待删除的关键字,直到找到一个空的桶。如果匹配到了待查询的关键字,删除对应的键值对。删除一个键值对会使链表断开,我们只能把待删除的键值对后面的所有节点插入到链表的后面。
线性探查提供了很好的缓存性能,但是导致了扩展性的问题。把冲突的键值对放在下一个可用的桶中,这可能会导致以填充的桶的连续扩张。插入、搜索或删除时,这种做法都需要遍历。
二次探测
类似于线性探查,但它并没有把冲突的项放到下一个可用的桶中,而是尝试着放进下标为i, i + 1, i + 4, i + 9, i + 16, ...,
序列的桶中。此处的i
是原先的关键字哈希值。支持的方法如下:
插入:
计算关键字的哈希值来查找桶的下表索引。遍历探查序列,直到发现了空的或者已被删除的桶,随之将键值对插入找到的桶中。
搜索:
计算关键字的哈希值来查找桶的下表索引。遍历探查序列,直到发现了空的或者已被删除的桶,否则比较序列对应的桶中键值对的关键字和待查找的关键字;如果关键字匹配,返回对应的值,反之返回空。
删除:
我们无法辨别待删除的项是否处于冲突链表中,因此无法立刻删除该项。我们只能将其标记为“已删除”。
尽管二次探查法没有根除键值对的聚集,但是情况有所缓解;同时它也提高了存储的性能。
再哈希法
再哈希法是要解决键值对聚集问题的。为此,我们使用备用哈希函数来为键值对计算新的索引位置。使用哈希函数给出新的桶,桶的下标得是全局均匀分布的。这个方法解决键值对聚集问题,同时也增强了存储的性能。再哈希法是生产中常见的冲突解决方法。教程中也实现了这个方法。