关于语言不可知:为什么哈希函数应使用素数模数?

Why should hash functions use a prime number modulus?

很久以前,我花了1.25美元从交易桌上买了一本数据结构书。在本文中,对散列函数的解释说,由于"数学的本质",它最终应该被一个素数修改。

你对一本1.25美元的书有什么期望?

不管怎样,我已经有好几年的时间来思考数学的本质,但还是搞不清楚。

即使存在质数的桶,数字的分布是否真的更大?或者这是一个老的程序员的故事,每个人都接受,因为其他人都接受它?


通常,一个简单的哈希函数的工作原理是获取输入的"组件部分"(字符串中的字符),然后将它们乘以某个常量的幂,然后将它们以某种整数类型添加到一起。例如,字符串的典型散列(尽管不是特别好)可能是:好的。

1
(first char) + k * (second char) + k^2 * (third char) + ...

然后,如果输入的字符串都具有相同的第一个字符,那么结果将都是相同的模k,至少直到整数类型溢出为止。好的。

作为一个例子,Java的字符串哈希代码与此类似,它用K=31来做字符倒序。因此,在以相同方式结束的字符串之间得到了显著的关系模31,在除接近结尾之外相同的字符串之间得到显著的关系模2^32。这不会严重破坏哈希表行为。]好的。

哈希表的工作原理是将哈希的模取到存储桶的数量上。好的。

在哈希表中,不要在可能的情况下产生冲突是很重要的,因为冲突会降低哈希表的效率。好的。

现在,假设有人将一整串值放入一个哈希表中,该哈希表在项目之间具有某种关系,就像所有项目都具有相同的第一个字符一样。我想说,这是一个相当可预测的使用模式,所以我们不希望它产生太多的冲突。好的。

事实证明,"由于数学的本质",如果散列中使用的常量和桶的数量是互质的,那么在一些常见的情况下,碰撞会最小化。如果它们不是互质的,那么在输入之间就有一些相当简单的关系,对于这些关系,碰撞不会最小化。所有散列的模都等于公因子,这意味着它们都将落在桶的第1/n个数中,这些桶的值模等于公因子。你会得到n倍的碰撞,其中n是公因数。因为n至少是2,所以对于一个相当简单的用例来说,产生的碰撞至少是正常情况的两倍是不可接受的。如果某个用户要将我们的分发分成桶,我们希望它是一个反常的意外,而不是一些简单的可预测的用法。好的。

现在,哈希表实现显然无法控制放入其中的项。他们不能阻止他们的关系。所以要做的就是确保常量和桶数是互质的。这样,就不需要仅仅依靠"最后一个"分量就可以根据一些小的公因数来确定桶的模数。据我所知,他们不必是主要的实现这一点,只是互质。好的。

但是如果散列函数和散列表是独立编写的,那么散列表就不知道散列函数是如何工作的。它可能使用一个带有小因子的常数。如果你幸运的话,它的工作方式可能完全不同,而且是非线性的。如果散列值足够好,那么任何桶数都可以。但是偏执哈希表不能假定一个好的哈希函数,所以应该使用质数的bucket。类似地,一个偏执的散列函数应该使用一个较大的素数常量,以减少有人使用许多桶的可能性,这些桶恰好与该常量有一个公共因子。好的。

在实践中,我认为使用2的幂作为桶的数目是相当正常的。这很方便,而且省去了搜索或预先选择合适数量级的素数的麻烦。所以您依赖散列函数不使用偶数乘法器,这通常是一个安全的假设。但是,根据上面的散列函数,您仍然可以偶尔得到不好的散列行为,而主存储桶计数可以进一步帮助您。好的。

就我所知,提出"一切都必须是素数"的原则是在哈希表上良好分布的一个充分但不是必要的条件。它允许每个人进行互操作,而无需假定其他人遵循相同的规则。好的。

[编辑:还有另一个更专门的原因,就是使用质数的桶,如果你处理与线性探测的碰撞。然后从散列代码计算步幅,如果步幅是存储桶计数的一个因素,那么在回到开始的位置之前,只能进行(存储桶计数/步幅)探测。当然,您最想避免的情况是stride=0,它必须是特殊的cased,但是为了避免特殊的casing bucket_count/stride等于一个小整数,您可以将bucket_count设为prime,而不关心提供了什么stride,它不是0。]好的。好啊.


从哈希表插入/检索时,首先要计算给定键的哈希代码,然后通过执行hashcode%table_length将哈希代码剪裁为哈希表的大小,找到正确的存储桶。这里有两个"陈述",你可能在某个地方读过

  • 如果对表长度使用2的幂,查找(hashcode(key)%2^n)与(hashcode(key)&;(2^n-1))一样简单快捷。但是,如果计算给定键的哈希代码的函数不好,那么在几个哈希桶中肯定会遇到许多键的集群问题。
  • 但是,如果您使用素数作为表的长度,计算出的哈希代码可以映射到不同的哈希桶中,即使您有一个稍微愚蠢的哈希代码函数。
  • 这是证据。

    如果您的hashcode函数在其他变量中产生以下hashcodes x、2x、3x、4x、5x、6x…,那么所有这些都将聚集在m个存储桶中,其中m=表的长度/greatestcommonfactor(表的长度,x)。(验证/推导这一点很简单)。现在您可以执行以下操作之一以避免集群

    确保您不会生成太多的哈希代码,这些哈希代码是其他哈希代码的倍数,如in x、2x、3x、4x、5x、6x…。但是,如果您的哈希表应该有数百万个条目,这可能有点困难。或者简单地通过使greatestcommonfactor(table_length,x)等于1使m等于table_length,即使table_length与x互质。如果x可以是任意数字,则确保table_length是质数。

    发件人-http://srinv.blogspot.com/2006/07/hash-table-lengthes-and-prime-numbers.html


    http://computinglife.wordpress.com/2008/11/20/为什么做哈希函数使用的素数数/

    漂亮的图片和清晰的解释,太。

    编辑:作为一个总结,使用素数,因为你有最好的机会获得一个独特的价值,当multiplying素数数值的选择和添加他们的一切。例如,给定一个字符串,multiplying与素数的数值,然后添加你想要的一切都给它的哈希值。

    一个更好的问题将是完全数31,为什么?


    DR

    index[hash(input)%2]将导致所有可能散列值的一半和一系列值发生冲突。index[hash(input)%prime]导致所有可能散列的碰撞小于2。将除数固定到表大小还可以确保数字不能大于表。


    使用素数是因为您很有可能为使用多项式模p的典型哈希函数获得唯一值。比如,对于长度<=n的字符串使用这样的哈希函数,就会发生冲突。这意味着两个不同的多项式产生相同的模P值。这些多项式的差也是一个相同次数n(或更小)的多项式。它的根数不超过n个(这里是数学的本质,因为这个声明只适用于域上的多项式=>素数)。所以,如果n远小于p,你可能不会发生碰撞。在此之后,实验可能会证明37足够大,可以避免长度为5-10的字符串哈希表发生冲突,并且足够小,可以用于计算。


    提供额外的观点只是有这个网站:

    http://www.codexon.com /文章/散列函数的模素数的神话

    这contends,你应该使用桶数为最大可能的反对下一个素数对圆形桶数。它似乎是一合理的可能性。当以上的行,我可以了解大数A桶将是更好的,但我无法让一本数学论证。


    它依赖于哈希函数的选择。

    哈希函数是将各种元素的位置数据是由一些因素模量multiplying他们相应的电力两个机器字大小(这是免费的只是让模量的计算溢出)。

    你不想任何公共因子之间的数据元素和一个乘数大小的哈希表,然后它可以发生,因为不同的数据元素不传播在整个数据表。如果你选择A素表的大小是不可能的,在这样一个公共因子。

    在其他的手,这些因素通常是从制造的奇素数,所以你应该在你的安全使用两个哈希表的权力(例如Eclipse的Java使用31当它哪里hashcode()方法)。


    Primes are unique numbers. They are
    unique in that, the product of a prime
    with any other number has the best
    chance of being unique (not as unique
    as the prime itself of-course) due to
    the fact that a prime is used to
    compose it. This property is used in
    hashing functions.

    Given a string"Samuel", you can
    generate a unique hash by multiply
    each of the constituent digits or
    letters with a prime number and adding
    them up. This is why primes are used.

    However using primes is an old
    technique. The key here to understand
    that as long as you can generate a
    sufficiently unique key you can move
    to other hashing techniques too. Go
    here for more on this topic about
    http://www.azillionmonkeys.com/qed/hash.html

    http://computinglife.wordpress.com/2008/11/20/为什么做哈希函数使用的素数数/


    Copying from my other answer https://stackoverflow.com/a/43126969/917428. See it for more details and examples.

    我认为,这只与计算机在BASE2中工作的事实有关。想一想同样的东西是如何在基础10上工作的:

    • 8%10=8
    • 18%10=8
    • 87865378%10=8

    数字是什么并不重要:只要以8结尾,它的模10就是8。

    选择一个足够大的、非二次幂的值,将确保哈希函数实际上是所有输入位的函数,而不是它们的一个子集。


    假设您的表大小(或模数)是t=(b*c)。现在,如果输入的散列类似于(n*a*b),其中n可以是任何整数,那么输出将不会得到很好的分布。因为每次n变为c、2c、3c等时,输出都会开始重复。也就是说,您的输出将只分布在C位置。注意这里的c是(t/hcf(表大小,哈希))。

    这个问题可以通过制造HCF 1来解决。素数是非常好的。

    另一个有趣的事情是,当t为2^n时,输出将与输入散列的所有低n位完全相同。由于每个数都可以表示2的幂,当我们用t取任意数的模时,我们将减去2形式数的所有幂,即大于等于n,因此总是根据输入给出特定模式的数目。这也是一个糟糕的选择。

    同样地,t as 10^n也是不好的,因为类似的原因(以数字的十进制记数法代替二进制记数法)。

    因此,素数倾向于给出更好的分布结果,因此是表大小的好选择。


    我想为史蒂夫·杰索普的回答做些补充(因为我没有足够的声誉,所以我不能对此发表评论)。但我找到了一些有用的材料。他的回答很有帮助,但他犯了一个错误:桶的大小不应该是2的幂。我将引用托马斯·科尔曼、查尔斯·雷瑟森等人在第263页的《算法导论》一书:

    When using the division method, we usually avoid certain values of m. For example, m should not be a power of 2, since if m = 2^p, then h(k) is just the p lowest-order bits of k. Unless we know that all low-order p-bit patterns are equally likely, we are better off designing the hash function to depend on all the bits of the key. As Exercise 11.3-3 asks you to show, choosing m = 2^p-1 when k is a character string interpreted in radix 2^p may be a poor choice, because permuting the characters of k does not change its hash value.

    希望有帮助。


    我读过一个热门的WordPress网站,上面有一些热门的答案。根据我的理解,我想分享一下我做的一个简单的观察。

    您可以在本文中找到所有详细信息,但假定以下内容正确:

    • 使用素数给我们一个唯一值的"最佳机会"

    一个普通的hashmap实现需要两件事是唯一的。

    • 键的唯一哈希代码
    • 存储实际值的唯一索引

    如何获得唯一索引?使内部容器的初始大小也成为素数。因此,基本上,prime之所以参与进来,是因为它具有产生唯一数字的独特特性,我们最终使用这些数字来标识对象,并在内部容器中查找索引。

    例子:

    key="key"

    value="值"
    uniqueId ="k" * 31 ^ 2 +
    "e" * 31 ^ 1` +
    "y"

    映射到唯一ID

    现在我们想要一个独特的位置来体现我们的价值——所以我们

    假设internalContainerSize也是质数。

    我知道这是简化的,但我希望能把一般的想法讲清楚。


    一个哈希函数,它的重要,不仅减少,但不可能让它colisions通常呆在相同的哈希值而变化的几个字节。

    说你有一个方程:一个(x + y*z) % key = x00。如果密钥是一个素数N×y =关键是真和假在每N N为另一个号码。

    F在A密钥不是素数的例子:x=1,z = 2 = 8和密钥因为密钥/ Z = 4仍然是一个自然数,4变的解决方案的方程和我们在这个案例(N / 2)×Y键是N的N为每一个低的解决方案的方程有实用的8倍,因为A不是素数。

    如果我们已经知道attacker 8是可能的解决方案的方程可以从变更文件产生的氦4和8个安静变得相同的哈希。