哈希表的大小为什么最好是质数及推导过程

哈希函数是一种将任意长度的输入通过散列算法变换成固定长度的输出的函数,当不同键的哈希值相同,就会发生Hash碰撞

解决这个问题的思路有两个,一个是发生碰撞后解决碰撞,另一个是避免碰撞

发生碰撞后解决碰撞的方法有开放寻址法和拉链法,这些网上都能搜到,就不赘述了

我下面要讨论的是通过素数来减少哈希函数的碰撞

1.表的大小为质数减少哈希碰撞

哈希函数的关键属性是它该是抗碰撞的,即为很难找到两个不同的输入产生出相同的输出,下面给出三种常用哈希函数中质数的应用

表的大小与特定输入数列的关系

当设计哈希表时,选择一个质数作为数组的大小可以减少键值散列到相同索引位置(即发生碰撞)的概率

比如说有两个哈希表一个为100(合数),一个为97(素数)

当输入的数据是一系列以100的因子作为每一个数字的差值的数列的时候

比如说:5,15,25,35,45,55,65,75,85,95,105 ...

这时候哈希映射的计算方法为取余数

f(key) = (f(key)+di) % m (di=1,2,3,......,m-1)

使用100作为哈希表的大小时候

映射的结果为:

  • 0 % 100 = 0
  • 5 % 100 = 5
  • 10 % 100 = 10
  • 15 % 100 = 15
  • ...
  • 100 % 100 = 0
  • 105 % 100 = 5
  • 110 % 100 = 10

可见,在5和105的计算结果都是5,在100(第20个数)的时候发生了第一次碰撞,后面都是碰撞

使用97作为哈希表的大小时候

  • 0 % 97 = 0
  • 5 % 97 = 5
  • 10 % 97 = 10
  • 15 % 97 = 15
  • ...
  • 485 % 97 = 0
  • 490 % 97 = 5

.到485(第97个数)的时候发生了第一次碰撞,即为 5×97=485

第一次发生碰撞的公式

发生第一次碰撞位置的公式推导:

对于任意步长 d和哈希表大小 m

①如果 d 是 m 的因数,那么第一次碰撞发生在: 

第m/d个数字位置

②如果 d 不是 m 的因数,那么第一次碰撞发生在: 

第m个数字位置

结合一下,第一次发生碰撞的位置为m/gcd(d,m)

gcd(d,m)是d和m的最大公约数

结论

当输入数列的步长为哈希表大小的因数的时候,更容易发生碰撞

因数越多,满足上述条件步长的情况越多

那么直接用个质数就能从一定程度上避免这种情况的发生

2.二次探测再散列中使用质数

使用线性探测法的时候,一旦某个区域开始变得密集,那么新元素插入时就需要更长的时间去寻找空槽,二次探测法则可以避免这种情况,因为它能够以不同的间隔跳跃式地探索散列表,从而使得插入的新元素更加均匀地分布在整个表中

f(key) = (f(key)+di) % m (di = 1^2, 2^2, 3^2,……, q^2, -q^2, q <= m/2)

例如,初始位置为h(k),如果该位置已被占用,则尝试

(f(key) + 1^2)%m

(f(key) + 2^2)%m

(f(key) + 3^2)%m

...

二次探查使用的增量是平方数的形式,比如 i^2, 2i^2, 3i^2 等等。这样的探测序列意味着在某些情况下,特别是当哈希表大小不是质数时,可能会导致某些槽位永远不会被探测到,从而降低了表的有效容量,选择合适的表大小(通常是质数)可以减轻这个问题

如果选择表大小 m 为素数,那么二次探测至少会在重复前访问表的一半条目

推导过程:

假设存在两个探测次数i和j(i ≠ j),设定 i 和 j 在[0, m-1]中,使得它们探测到的位置相同,即

(hash(key) + i²) % m = (hash(key) + j²) % m

=》 (i² - j²) % m = 0

=》 (i + j)(i - j) % m = 0

由于m为质数,m的因子只有m和1

所以 (i + j) % m = 0 或 (i - j) % m = 0

①对于 (i - j) % m = 0

0 <= i <= m-1

0 <= j <= m-1

推出

-m <= 1 - m <= i - j <= m - 1 <= m

所以 i - j 不会是 -m 和 m

(i - j) % m = 0不可能成立

②对于 (i + j) % m = 0

0 <= i <= m-1

0 <= j <= m-1

推出

0 <= i + j <= 2 * (m - 1) 

则只可能i + j = m的情况下 能推出(i + j) % m = 0

那么假设i和j在[0,(m-1)/2]之间的话,i和j不会等于m

则能保证如果在负载因子

由①和②可知,在[0, (m-1)/2],不存在两个探测次数i和j(i ≠ j),使得它们探测到的位置相同

因此,如果m为质数的话哈希表的负载因子小于(m-1)/2的时候(小于哈希表一半的长度),往哈希表中插入值不会发生碰撞

即如果选择表大小 m 为素数,那么二次探测至少会在重复前访问表的一半条目

这意味着只要负载因子小于 1/2且 m 是素数的时候,它就能成功找到一个空槽位

如果想访问更多的空间,就需要别的技术了

3.混合运算中使用质数提高分布效果

在Java的String类中使用的哈希码计算方法就用到了质数31

int hash = 0;
for (int i = 0; i < string.length(); i++) {
    hash = 31 * hash + string.charAt(i);
}

在计算机中通过位移操作可以快速实现31倍的乘法:31 * x == (x << 5) - x,jvm会帮你完成这件事,这样既提高了效率,又保证了良好的分布效果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值