哈希表,又称为散列表,是一种数据结构,大体概念估计大家都清楚,我这里不在赘述。
目的:用来查询。 通过给定关键字—> 直接找到数据的内存位置(也就是说直接此数据)。 (是不是和 key-value 很像)
方式: 通过计算一个键值的函数,将所需要查询的 数据映射到表中的一个位置来访问记录,这样子加快了查找速度。 这个函数称为散列函数,存放记录的数组称为散列表。
有两点很重要: 先撇开哈希算法,我们应该思考一下,如何根据一个key,直接定位数据在内存的位置呢? 可以使用一张表,第一列存储key,第二列存储这个key在内存中的位置。
如果这个表采用数组进行存储,那么查询这个表的时间复杂度是O(n)。
如果这个表采用二叉树进行存储(假设关键字可以比较大小),那么查询这个表需要O(logn)的时间复杂度这里我们需要的真的数据不是在一起的(通过key->内存位置->访问数据)。
现在呢?我们让所有的数据都存放在一个线性的数组中,那么内存位置即为数组下标。 我们只需要关系如何通过 key——>数组下标。
哈希算法采用 直接使用数学函数计算的方式 f(key)=下标。
最大的问题:如果不同的关键字 被 哈希之后,得到了同样的 下标 怎么办? 这个称谓冲突
第二个问题:哈希函数 怎么设定呢?
以下全篇,我们之讲解这两个问题!
处理冲突
冲突不可避免,必须处理(不产生冲突的可能性非常小)。 那么应该如何处理呢?
开放定址法
hash = (hask(key) + d(i)) mod m , i = 1, 2, …, k (k <= m - 1)。其中 d(i) 为增量序列,i是已经发生的碰撞的次数。 增量序列有如下取法
1. 线性探测 d = 1, 2, 3, … (m-1)
2. 平方探测。相对于线性探测,相当于发生碰撞的时候,探测时间间隔 平方个单位的位置是否为空。如果为空,将地址存放进去。
3. 伪随机数 探测。
这里表的大小很重要。 同时,模数p的取值很重要,越是质数,那么 mod 取余就越可能分布在表的各处。
这个有可能形成聚集(cluster): 在函数地址的表中,散列函数的结果不均匀占据表的单元,形成区块。插入到区块中的关键字需要多次试选单元才能够插入到表中,这样造成时间浪费。对于开放定址法,聚集是灾难性的,必须避免。
单独链表法
将散列到同一个存储位置的所有元素保存在同一个链表中。
再次散列
可以用多个散列函数,进行散列,直到碰撞不再发生。这种方法不容易产生聚集。但是增加了计算时间
建立一个公共溢出区
将所有产生冲突的都放在这个溢出区中。
哈希函数
如果对于关键字集合中的任意一个关键字,经过散列函数 映射到 地址集合中任意一个 地址上的概率是相等的话, 那么称这个散列函数为”均匀散列函数(Uniform Hash Function)“
均匀散列函数:这样可使使得关键字经过散列函数得到一个”随机的地址“,从而减少了碰撞。
所以一个好的散列函数,可以使得数据序列的访问过程更加有效,可以减少冲突。
- 直接定址法:取关键字或者关键字的某个线性函数值为散列地址: hash(k) = k 或者 hash(k) = a * k + b
- 数字分析法: 假设关键字是 以 r 为基数的数字,并且哈希表中可能出现的关键字都是事先知道的,那么可以取关键字的若干树位组成哈希地址。:
- 举例子:一个班同学的生日,有年月日,经过分析,年月重复的可能性比较大,那么就选取日来作为哈希。
- 平方取中法: 取关键字的平方后的中间几位为哈希地址。 (通常在选取哈希函数的时候,不一定可以知道关键字的全部情况,取其中的哪几位也不一定合适,而一个数的平方后的中间几位数字和数的每一位都相关,由此使随机分布的关键字得到的哈希地址也是随机的。取的位数由表长决定)
- 折叠法: 将关键字分割成位数相同的几个部分(最后一个部分的位数可以不相同),然后取这几个部分的叠加和(舍去进位) 作为哈希地址。
除留余数法: 取关键字被某个不大于散列表表长m的数p除后所得到的余数为散列地址。 hash(k) = k mod p , p <= m。 不仅可以对关键字直接取模数,也可以在折叠法,平方取中法等运算之后取模数。对p的选择很重要,一般来说取素数或者 m,若p选择不好,容易产生碰撞。
坏消息:一个好的哈希函数比较困难,需要一定的技术含量。
好消息:现在又很多现成的不错的哈希函数,可以直接使用。
查找效率
可以参看:维基百科
这里有一个载荷因子的定义很关键。
散列表的载荷因子:a = (填入表中的元素的个数) / (散列表的长度)
a是散列表装满程度的标志因子。 由于表长是一个定值,a 与“填入表中的元素个数”成正比,所以a越大,表明填入的元素越多,产生冲突的可能性越大。实际上散列表的平均查找长度即为 载荷因子的a的函数,知识不同处理冲突的方法有不同的函数。
对于开放定址法,载荷因子是特别重要的因素,应该严格限制在 0.7~0.8一下。超过0.8,查表的时候CPU的缓存不命中,按照指数曲线上升。因此一些开放定址法的hash库,如JAVa的系统库,限制了载荷因子为0.75。超过这个值,将resize 散列表。
全域哈希 和 完美哈希
http://www.guokr.com/blog/483599/
http://www.cnblogs.com/a180285/archive/2012/07/22/opmphf.html
http://www.yankay.com/introduction-to-opmphf/
http://blog.csdn.net/chixinmuzi/article/details/1727195