Answers:
Knuth的乘法方法:
hash(i)=i*2654435761 mod 2^32
通常,您应该选择一个按哈希大小顺序排列的乘数(2^32
在示例中),并且没有公因子。这样,哈希函数可以均匀地覆盖您的所有哈希空间。
编辑:此哈希函数的最大缺点是它保留了可除性,因此,如果您的整数都可以被2或4整除(这并不罕见),则它们的哈希也将被整除。这是哈希表中的问题-您最终只能使用1/2或1/4个存储桶。
我发现以下算法提供了很好的统计分布。每个输入位以大约50%的概率影响每个输出位。没有冲突(每个输入导致不同的输出)。该算法速度很快,除非CPU没有内置的整数乘法单元。假设int
是32位的C代码(对于Java,请替换>>
为>>>
remove unsigned
):
unsigned int hash(unsigned int x) {
x = ((x >> 16) ^ x) * 0x45d9f3b;
x = ((x >> 16) ^ x) * 0x45d9f3b;
x = (x >> 16) ^ x;
return x;
}
幻数是使用运行了多个小时的特殊多线程测试程序计算得出的,该程序计算雪崩效应(如果更改单个输入位,则输出位的数量会发生变化;平均应该接近16个),输出位发生变化(输出位不应相互依赖),以及任何输入位发生变化时每个输出位发生变化的可能性。计算出的值比MurmurHash使用的32位终结器更好,并且几乎与使用AES时一样好(不太好)。一个轻微的好处是,相同的常量被使用了两次(上次测试时确实使它稍快一些,不确定是否仍然如此)。
你可以逆转这一过程(您可以通过哈希输入值),如果更换0x45d9f3b
用0x119de1f3
(在乘法逆):
unsigned int unhash(unsigned int x) {
x = ((x >> 16) ^ x) * 0x119de1f3;
x = ((x >> 16) ^ x) * 0x119de1f3;
x = (x >> 16) ^ x;
return x;
}
对于64位数字,我建议使用以下内容,甚至认为它可能不是最快的。这是基于splitmix64的,它似乎是基于博客文章Better Bit Mixing(混合13)的。
uint64_t hash(uint64_t x) {
x = (x ^ (x >> 30)) * UINT64_C(0xbf58476d1ce4e5b9);
x = (x ^ (x >> 27)) * UINT64_C(0x94d049bb133111eb);
x = x ^ (x >> 31);
return x;
}
对于Java,请使用long
,将其添加L
到常量,替换>>
为>>>
和remove unsigned
。在这种情况下,反转更为复杂:
uint64_t unhash(uint64_t x) {
x = (x ^ (x >> 31) ^ (x >> 62)) * UINT64_C(0x319642b2d24d8ec3);
x = (x ^ (x >> 27) ^ (x >> 54)) * UINT64_C(0x96de1b173f119089);
x = x ^ (x >> 30) ^ (x >> 60);
return x;
}
更新:您可能还想查看Hash Function Prospector项目,其中列出了其他(可能更好)的常量。
x = ((x >> 32) ^ x)
,然后使用上面的32位乘法。我不确定哪个更好。您可能还想看看Murmur3的64位终结器
取决于数据的分布方式。对于一个简单的计数器,最简单的功能
f(i) = i
会很好(我怀疑是最优的,但我无法证明)。
快速和良好的哈希函数可以由质量较差的快速排列组成,例如
产生具有较高质量的哈希函数,例如用PCG演示的随机数生成方法。
实际上,这也是有意或无意地使用的食谱rrxmrrxmsx_0和杂语哈希。
我个人发现
uint64_t xorshift(const uint64_t& n,int i){
return n^(n>>i);
}
uint64_t hash(const uint64_t& n){
uint64_t p = 0x5555555555555555ull; // pattern of alternating 0 and 1
uint64_t c = 17316035218449499591ull;// random uneven integer constant;
return c*xorshift(p*xorshift(n,32),32);
}
足够好。
一个好的哈希函数应该
首先让我们看一下身份功能。它满足1.但不满足2.:
输入位n确定输出位n的相关性为100%(红色),没有其他相关性,因此它们是蓝色的,在其上给出了一条完美的红线。
xorshift(n,32)并不好,只产生一行和一半的行。仍然令人满意,因为它在第二个应用程序中是可逆的。
与无符号整数相乘会更好,级联效果更好,并以绿色的概率为0.5(这就是您想要的)翻转更多的输出位。满足1.因为每个不均匀整数都有一个乘法逆。
将这两个函数结合在一起,得到的输出仍然满足1.,因为两个双射函数的组合会产生另一个双射函数。
乘法和xorshift的第二个应用将产生以下结果:
或者,您可以使用诸如GHash之类的Galois字段乘法,它们在现代CPU上已经变得相当快,并且一步就具有卓越的质量。
uint64_t const inline gfmul(const uint64_t& i,const uint64_t& j){
__m128i I{};I[0]^=i;
__m128i J{};J[0]^=j;
__m128i M{};M[0]^=0xb000000000000000ull;
__m128i X = _mm_clmulepi64_si128(I,J,0);
__m128i A = _mm_clmulepi64_si128(X,M,0);
__m128i B = _mm_clmulepi64_si128(A,M,0);
return A[0]^A[1]^B[1]^X[0]^X[1];
}
__m128i I = i; //set the lower 64 bits
,但是我做不到,所以我正在使用^=
。0^1 = 1
因此没有参与。关于与初始化{}
我的编译器从不抱怨,它可能不是最好的解决办法,但我想这是所有INITIALISE它为0,所以我可以做^=
或|=
。我想我是根据此博客文章上的代码编写的,它也提供了反转功能,非常有用:D
32位乘法方法(非常快),请参见@rafal
#define hash32(x) ((x)*2654435761)
#define H_BITS 24 // Hashtable size
#define H_SHIFT (32-H_BITS)
unsigned hashtab[1<<H_BITS]
....
unsigned slot = hash32(x) >> H_SHIFT
32位和64位(分布良好)位于:MurmurHash
Eternally Confuzzled上有一些很好的哈希算法概述。我建议使用鲍勃·詹金斯(Bob Jenkins)的一次性哈希,该哈希可以很快达到雪崩状态,因此可用于高效的哈希表查找。
对于随机哈希值,一些工程师说黄金比率素数(2654435761)是一个不好的选择,根据我的测试结果,我发现这不是真的。相反,2654435761很好地分配了哈希值。
#define MCR_HashTableSize 2^10
unsigned int
Hash_UInt_GRPrimeNumber(unsigned int key)
{
key = key*2654435761 & (MCR_HashTableSize - 1)
return key;
}
哈希表大小必须为2的幂。
我编写了一个测试程序来评估整数的许多哈希函数,结果表明GRPrimeNumber是一个不错的选择。
我努力了:
通过测试结果,我发现黄金比率素数始终具有较少的空桶或零空桶,并且碰撞链长度最短。
一些用于整数的哈希函数被认为是好的,但是测试结果表明,当total_data_entry / total_bucket_number = 3时,最长的链长大于10(最大冲突数> 10),并且许多存储桶未映射(空存储桶) ),与黄金比例素数哈希的零空桶和最长链长3的结果相比,这是非常糟糕的。
顺便说一句,根据我的测试结果,我发现一个移位异或哈希函数的版本非常好(由mikera共享)。
unsigned int Hash_UInt_M3(unsigned int key)
{
key ^= (key << 13);
key ^= (key >> 17);
key ^= (key << 5);
return key;
}
自从找到此线程以来,我一直在使用splitmix64
(指向Thomas Mueller的答案)。但是,我最近偶然发现了Pelle Evensen的rrxmrrxmsx_0,它的统计分布比原始的MurmurHash3终结器及其后续版本(splitmix64
和其他混合版本)好得多。这是C中的代码片段:
#include <stdint.h>
static inline uint64_t ror64(uint64_t v, int r) {
return (v >> r) | (v << (64 - r));
}
uint64_t rrxmrrxmsx_0(uint64_t v) {
v ^= ror64(v, 25) ^ ror64(v, 50);
v *= 0xA24BAED4963EE407UL;
v ^= ror64(v, 24) ^ ror64(v, 49);
v *= 0x9FB21C651E98DF25UL;
return v ^ v >> 28;
}
Pelle还提供了对最新版本和最新版本中使用的64位混合器的深入分析MurmurHash3
。