二次再散列法 与 二次探测再散列(Quadratic probing)
起因
透过python的Dict冲突解决源码,其使用Open Addressing方式解决冲突,而二次再散列法是在搜索的时候出现的一个词。二次再散列法这个词组首先是这么理解,第二次,再稀疏,的方法,为什么这么理解?因为平时我们常说哈希函数,很清楚的知道是指这个hashing函数,但是它同样也叫散列函数,散列同样也可以理解为稀疏散开列,所以这里的散列并不是一个名词而是一个动词。为哈希冲突解决方法的统称的中文名称。
哈希函数 or 散列函数 (Hash function)
Source: Wikipedia
A hash function is any function that can be used to map data of arbitrary size to data of fixed size.
哈希函数是一个映射(mapping)函数,很多kv数据结构基础便基于此。
哈希表(Hash Table)
Source: Wikipedia
In computing, a hash table (hash map) is a data structure which implements an associative array abstract data type, a structure that can map keys to values.
本文所关心和所述皆是哈希表结构下的算法。数组通过下标直接寻址,可以在O(1)时间复杂度内访问任意元素,哈希表是普通数组O(1)时间复杂度的推广,为动态集合数据结构,在哈希表中查找一个元素的期望时间复杂度为O(1).
理想的哈希函数是希望将键值对分布在一系列的buckets中:
1 | index = f(key, array_size) |
实际上,通常是两部实现的:
1 | hash = hashfunc(key) |
使用求模来减少存储空间,函数依赖于array_size长度,这种情况下array_size通常是2次方增长。
哈希冲突
潜在的数据通过哈希函数获得唯一index总是高于我们的期望,并且哈希冲突提高了操作的代价,哈希表的设计目标就是要尽可能的减少冲突发生。
通常有两种解决哈希冲突方法:避免和解决。
Collision Avoidance
哈希避免的方式简单来说就是选择更适合的哈希函数,所以要具体问题具体分析。
Collision Resolution
使用冲突解决方法就有很深的研究价值,目前有这些方法:
- Separate chaining
- Separate chaining with linked lists
- Separate chaining with list head cells
- Separate chaining with other structures
- Open addressing
- Coalesced hashing
- Cuckoo hashing
- Hopscotch hashing
- Robin Hood hashing
- 2-choice hashing
根据前文所提,那么这些解决冲突的方法就是标题中的二次再散列法的中文总称。
链接法(Separate Chaining)
非本文重点,简述。
链接技术(Chaining)将采用额外的数据结构来处理冲突,其将哈希表中每个位置(slot)都映射到了一个链表。当冲突发生时,冲突的元素将被添加到桶(bucket)列表中,而每个桶都包含了一个链表以存储相同哈希值的元素。
开放地址法(Open Addressing)
顾名思义,开放地址就是开放哈希表中的所有地址,不使用额外的空间。如果遇到冲突,寻找其他位置存储。
Linear Probing
线性探测,探测间隔总是固定的,通常为1.
$$H+1,H+2,H+3,H+4,…,H+k$$
也就是地址冲突,地址+1.
Quadratic probing
二次探查,这是线性探测的改进,每次的步长变为平方倍数。
$$ H+1^{2},H+2^{2},H+3^{2},H+4^{2},…,H+k^{2} $$
但同样也会有同类哈希聚集问题(Secondary Clustering)。
CPython 的 Dictionary冲突解决实现就是使用二次探寻散列,在c源码头注释可以看见:
Source: http://svn.python.org/projects/python/trunk/Objects/dictobject.c
1 | j = (5*j) + 1 + perturb; |
Double hashing or Rehashing
另一种改进的开放寻址法称为重哈希(Double hashing)或者叫二度哈希(Rehashing)
随机第,一直地,独立地选定两个哈希函数$ h_1, h_2 $, 在哈希表$T$中$k$键的第$i$位为:
$$h(i,k) = (h_1(k)+i*h_2(k)) mod |T|$$