很久以前,我花1.25美元在便宜货桌上买了一本数据结构的书。在这篇文章中,哈希函数的解释说,由于“数学的本质”,它最终应该被一个质数mod。

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

不管怎么说,我花了很多年思考数学的本质,但还是没弄明白。

当有质数个桶时,数字的分布真的更均匀吗?

或者这是一个老程序员的故事,每个人都接受,因为其他人都接受?


当前回答

Primes are used because you have good chances of obtaining a unique value for a typical hash-function which uses polynomials modulo P. Say, you use such hash-function for strings of length <= N, and you have a collision. That means that 2 different polynomials produce the same value modulo P. The difference of those polynomials is again a polynomial of the same degree N (or less). It has no more than N roots (this is here the nature of math shows itself, since this claim is only true for a polynomial over a field => prime number). So if N is much less than P, you are likely not to have a collision. After that, experiment can probably show that 37 is big enough to avoid collisions for a hash-table of strings which have length 5-10, and is small enough to use for calculations.

其他回答

插入/从哈希表中检索时要做的第一件事是计算给定键的hashCode,然后通过执行hashCode % table_length将hashCode修剪为哈希表的大小来找到正确的bucket。这里有两个“陈述”,你很可能在某处读到过

如果对table_length使用2的幂,那么查找(hashCode(key) % 2^n)就像查找(hashCode(key) & (2^n -1))一样简单快捷。但是如果你为一个给定的键计算hashCode的函数不是很好,你肯定会在几个散列桶中聚集许多键。 但是,如果table_length使用质数,即使使用稍微愚蠢的hashCode函数,计算出来的hashCode也可以映射到不同的散列桶中。

这就是证明。

如果假设你的hashCode函数的结果是以下hashCode {x, 2x, 3x, 4x, 5x, 6x…},那么所有这些都将聚集在m个桶中,其中m = table_length/GreatestCommonFactor(table_length, x)。(验证/推导这个很简单)。现在可以执行以下操作之一来避免集群

确保你不会生成太多的hashCode,这些hashCode是另一个hashCode的倍数,比如{x, 2x, 3x, 4x, 5x, 6x…}。但如果你的hashTable应该有数百万个条目,这可能有点困难。 或者通过使GreatestCommonFactor(table_length, x)等于1使m等于table_length,即使table_length与x为coprime。如果x可以是任何数字,则确保table_length是质数。

来自- http://srinvis.blogspot.com/2006/07/hash-table-lengths-and-prime-numbers.html

关于素数幂模的“数学的本质”是它们是有限域的一个组成部分。另外两个构建块是加法运算和乘法运算。素模的特殊性质是,它们用“常规”的加法和乘法运算形成一个有限域,只是取到模。这意味着每一个乘法都映射到一个不同的整数对质数求模,每一个加法也是如此。

质模的优势在于:

它们在二次哈希中选择次乘数时给予了最大的自由,除了0之外的所有乘数最终都将访问所有元素一次 如果所有哈希值都小于模量,则根本不会发生碰撞 随机质数比两个模的幂更好地混合,并压缩所有比特的信息,而不仅仅是一个子集

然而,它们有一个很大的缺点,它们需要整数除法,这需要很多(~ 15-40)个周期,即使在现代CPU上也是如此。用大约一半的计算就可以确保散列混合得很好。两次乘法和异移运算比一个质数模更容易混合。然后,我们可以使用任何哈希表的大小,哈希约简是最快的,对于2个表大小的幂,总共给出7个操作,对于任意大小的表,大约9个操作。

我最近研究了许多最快的哈希表实现,其中大多数都不使用质数模块。

哈希表索引的分布主要依赖于所使用的哈希函数。质数模量不能修复一个坏的哈希函数,一个好的哈希函数也不能从质数模量中受益。然而,在某些情况下,它们可能是有利的。例如,它可以修复半坏的哈希函数。

通常,一个简单的哈希函数的工作原理是,取输入的“组成部分”(在字符串的情况下是字符),将它们乘以某个常数的幂,然后以某种整数类型将它们相加。例如,一个字符串的典型哈希值(虽然不是特别好)可能是:

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

然后,如果输入了一堆具有相同首字符的字符串,那么结果将都是相同的k模,至少在整数类型溢出之前是这样。

[举个例子,Java的字符串hashCode与此惊人地相似——它将字符的顺序颠倒,k=31。所以你会得到以31为模的惊人的关系在以相同方式结束的字符串之间,以及以2^32为模的惊人的关系在除了接近结尾的字符串之间都是相同的。这并没有严重扰乱哈希表行为。]

哈希表的工作原理是将哈希的模数除以桶的数量。

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

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

It turns out that "because of the nature of maths", if the constant used in the hash, and the number of buckets, are coprime, then collisions are minimised in some common cases. If they are not coprime, then there are some fairly simple relationships between inputs for which collisions are not minimised. All the hashes come out equal modulo the common factor, which means they'll all fall into the 1/n th of the buckets which have that value modulo the common factor. You get n times as many collisions, where n is the common factor. Since n is at least 2, I'd say it's unacceptable for a fairly simple use case to generate at least twice as many collisions as normal. If some user is going to break our distribution into buckets, we want it to be a freak accident, not some simple predictable usage.

现在,哈希表实现显然无法控制放入其中的项。他们不能阻止他们之间的联系。所以要做的就是确保常量和桶数都是互质。这样你就不需要单独依靠“最后一个”分量来确定桶的模数相对于某个小的公共因子。据我所知,它们不一定是质数,只要是质素就可以了。

But if the hash function and the hashtable are written independently, then the hashtable doesn't know how the hash function works. It might be using a constant with small factors. If you're lucky it might work completely differently and be nonlinear. If the hash is good enough, then any bucket count is just fine. But a paranoid hashtable can't assume a good hash function, so should use a prime number of buckets. Similarly a paranoid hash function should use a largeish prime constant, to reduce the chance that someone uses a number of buckets which happens to have a common factor with the constant.

在实践中,我认为使用2的幂作为桶的数量是相当正常的。这很方便,并且省去了四处搜索或预先选择正确大小的质数的麻烦。所以你依赖于哈希函数而不是使用偶数乘数,这通常是一个安全的假设。但是,基于上面的哈希函数,您仍然会偶尔遇到糟糕的哈希行为,而素数桶计数可能会有进一步的帮助。

就我所知,提出“所有东西都必须是质数”的原则是在哈希表上进行良好分布的充分条件,而不是必要条件。它允许每个人进行互操作,而不需要假设其他人遵循相同的规则。

[Edit: there's another, more specialized reason to use a prime number of buckets, which is if you handle collisions with linear probing. Then you calculate a stride from the hashcode, and if that stride comes out to be a factor of the bucket count then you can only do (bucket_count / stride) probes before you're back where you started. The case you most want to avoid is stride = 0, of course, which must be special-cased, but to avoid also special-casing bucket_count / stride equal to a small integer, you can just make the bucket_count prime and not care what the stride is provided it isn't 0.]

为了提供另一种观点,这里有一个网站:

http://www.codexon.com/posts/hash-functions-the-modulo-prime-myth

它认为你应该使用尽可能多的桶而不是四舍五入到质数桶。这似乎是个合理的可能性。直观地说,我当然可以看到桶的数量越多越好,但我无法对此进行数学论证。

我读过一个流行的wordpress网站,上面有一些流行的答案。根据我的理解,我想分享一个简单的观察。

你可以在这篇文章中找到所有的细节,但假设以下是正确的:

使用质数给我们提供了一个唯一值的“最佳机会”

一个通用的hashmap实现需要有两个东西是唯一的。

键的唯一哈希码 用于存储实际值的唯一索引

我们如何得到唯一索引?通过使内部容器的初始大小也是质数。基本上,质数的存在是因为它具有产生唯一数字的独特特性,我们最终用它来标识对象并在内部容器中查找索引。

例子:

Key = " Key "

Value = " Value " uniqueId = "k" * 31 ^ 2 + "e" * 31 ^ 1 ' + “y”

映射到唯一id

现在我们想要一个独特的位置来存放我们的价值,所以我们

uniqueId % internalContainerSize == uniqueLocationForValue,假设internalContainerSize也是质数。

我知道这是简化的,但我希望你能理解我的大意。