哈希概念
- 哈希(hash)又称散列,是一种组织数据的方式。本质就是通过哈希函数把关键字 Key 跟存储位置建立一个映射关系,查找时通过这个哈希函数计算出 Key 存储的位置,进行快速查找。
直接定址法
- 直接定址法本质就是用关键字计算出一个绝对位置或者相对位置。
- 直接定址法的缺点非常明显,当关键字的范围比较分散时,就很浪费内存甚至内存不够用。
哈希冲突
- 假设我们只有数据范围是 [0,9999] 的 N 个值,我们要映射到一个 M 个空间的数组中 (一般情况下 M>=N),那么就要借助哈希函数 (hashfunction) hf,关键字 key 被放到数组的 h(key) 位置,这里要注意的是 h(key) 计算出的值必须在 [0,M) 之间。
- 两个不同的 key 可能会映射到同一个位置去,这种情况叫做哈希冲突,或者哈希碰撞。
- 冲突是不可避免的,但可以设计出优秀的哈希函数,减少冲突的次数并设计出解决冲突的方案。
负载因子
- 假设哈希表中已经映射存储了 N 个值,哈希表的大小为 M,那么 负载因子=N/M。
- 负载因子越大,哈希冲突的概率越高,空间利用率越高;负载因子越小,哈希冲突的概率越低,空间利用率越低。
- 一般设置最大为 0.7。
将关键字转为整数
- 我们将关键字映射到数组中位置,一般是整数好做映射计算,如果不是整数,我们要想办法转换成整数。
哈希函数
除法散列法/除留余数法
- 假设哈希表的大小为 M,那么通过 key 除以 M 的余数作为映射位置的下标,也就是哈希函数为:h(key) = key % M。
- 当使用除法散列法时,要避免 M 为某些值,如 2 的幂,10 的幂等。如果是 2 的幂就相当于保留二进制的后多少位了;10 的幂就是十进制的后多少位。
- 当使用除法散列法时,建议 M 取不太接近 2 的整数次幂的一个质数 (素数)。
乘法散列法、全域散列法
处理哈希冲突
开放定址法
- 在开放定址法中所有的元素都放到哈希表里,当一个关键字 key 用哈希函数计算出的位置冲突了,则按照某种规则找到一个没有存储数据的位置进行存储,开放定址法中负载因子一定是小于 0.7 的。这里的规则有三种:线性探测、二次探测、双重探测。
线性探测
- 从发生冲突的位置开始,依次线性向后探测,直到寻找到下一个没有存储数据的位置为止,如果走到哈希表尾,则回绕到哈希表头的位置。
- 线性探测的问题假设,hash0 位置连续冲突,hash0,hash1,hash2 位置已经存储数据了,后续映射到 hash0,hash1,hash2,hash3 的值都会争夺 hash3 位置,这种现象叫做群集/堆积。
- h(key) = hash0 = key % M,hash0 位置冲突了,则线性探测公式为 hc(key,i) = hashi = (hash0+i) % M,i = {1,2,3,…,M −1}。
下面演示将 {19,30,5,36,13,20,21,12} 这一组按顺序映射到 M=11 的表中

二次探测
- 从发生冲突的位置开始,依次左右按二次方跳跃式探测,直到寻找到下一个没有存储数据的位置为止,如果往右走到哈希表尾,则回绕到哈希表头的位置;如果往左走到哈希表头,则回绕到哈希表尾的位置。
- h(key) = hash0 = key % M , hash0 位置冲突了,则二次探测公式为 hc(key,i) = hashi = (hash0 ± i^2 ) % M,i={ 1,2,3,…,M/2},当 hashi<0 时,需要 hashi+=M。
双重散列
- 第一个哈希函数计算出的值发生冲突,使用第二个哈希函数计算出一个跟 key 相关的偏移量值,不断往后探测,直到寻找到下一个没有存储数据的位置为止。



