一、哈希的概念
哈希(Hash)又称散列,是组织数据的一种方式,本质为:将键值 key 与存储位置建立映射关系,查找时通过哈希函数计算出 key 的存储位置快速查找。
1. 哈希冲突(碰撞):借助哈希函数将 N 个值映射到大小为 M 的哈希表中(M≥N),而不同的 key 可能会映射到同一个位置上,称为哈希冲突,是不可避免的。
如哈希函数:h(key) = key % M(除法散列法)中,203 % 200 = 3、3 % 200 = 3。
2. 负载因子(load factor):映射存储了 N 个值,哈希表大小为 M,则 负载因子 = N / M。
负载因子越大,哈希冲突概率越高,空间利用率越高;相反,负载因子越小,哈希冲突概率越低,空间利用率越低。所以,要保持负载因子达到一定的平衡才能保持效率。
3. 关键词 key 类型需要支持整型转换:key 为整数好做映射计算。
二、直接定址法
每个关键字的值(或其 ASCII 码)就是其存储位置的下标(计数排序)。
适用于整型(如 float、string 等非整型类型不支持)且范围比较集中的数据 => 简单高效。
三、哈希函数
尽可能将 N 个值以概率均分到哈希表 M 空间。
1. 除留余数法/除法散列法 (最常用)
h(key)= key % M
该方法不关注 key 的大小,只在乎 key 的个数。
① M 尽量避免 2^x,10^x 的值: a. M 若为 2^x,则%M 本质相当于保留 key 的后 x 位(二进制下),后 x 位相同的值哈希值相同。 如对于 01001101 (77) % 2^4 (16),结果仅与后 3 位有关,前 (32 - 3) 位均能整除 2^4,故算出的哈希值没有特征性(参与计算的位数越多,哈希值越具有特征性,哈希冲突越少发生)。 b. M 若为 10^x,则%M 本质相当于保留 key 后 x 位(十进制下),与 2^x 一致。
②建议 M 取不太接近 2^x 的一个质数。
③JAVA 中 HashMap 则采用 2^x 作为 M:不用取模,直接位运算(相对于位运算,取模的效率要低很多),且扩容直接 M*2 即可(C++ 采用设置好的质数)。
int hashi = key % pow(2, 16); // 效率低
int hashi = key & ((1 << 16) - 1); // 效率高
为防止前 32-x 位没有参与运算,将后 x 位与前 32-x 位再异或。
int n = 16;
int hashi = key & (1 << (n - 1));
hashi = hashi ^ (key >> (32 - n));
但是当初始的 x < 16 时,会出现异或后数值大于 M 的情况。
2. 乘法散列法
h(key) = floor(M * (A * key) % 1.0)
a. 用 key * 常数 A(0 < A < 1),并抽取 k * A 的小数部分。 b. 再用 M * (小数部分),并向下取整。
3. 全域散列法
该方法为给散列函数增加随机性,防止恶意攻击。
四、开放定址法
1. 线性探测
a. 以发生冲突的位置开始,依次向后线性探测,直到找到下一个没有存储数据的位置为止(如果走到表尾就绕回到表头)。


