我正在用C语言编写哈希表,我正在测试字符串的哈希函数。
我尝试过的第一个函数是添加ascii代码并使用modulo(%100)但是我在第一次测试数据时得到的结果很差:140个单词的40次碰撞。
最终输入数据将包含8 000个单词(它是一个文件中的dictionnary存储)。哈希表声明为int table [10000],并包含txt文件中单词的位置。
第一个问题是散列字符串的最佳算法是哪一个?以及如何确定哈希表的大小?
提前感谢!
: - )
答案 0 :(得分:155)
我与Dan Bernstein的djb2
取得了不错的成绩。
unsigned long
hash(unsigned char *str)
{
unsigned long hash = 5381;
int c;
while (c = *str++)
hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
return hash;
}
答案 1 :(得分:19)
首先,您通常不想要为哈希表使用加密哈希。根据哈希表标准,加密标准非常的算法仍然非常慢。
其次,您希望确保输入的每一位都能/将影响结果。一种简单的方法是将当前结果旋转一些位,然后用当前字节对当前哈希码进行异或。重复,直到到达字符串的末尾。请注意,您通常不希望旋转成为字节大小的偶数倍。
例如,假设8位字节的常见情况,您可以旋转5位:
int hash(char const *input) {
int result = 0x55555555;
while (*input) {
result ^= *input++;
result = rol(result, 5);
}
}
编辑:另请注意,对于哈希表大小,10000个插槽很少是一个不错的选择。您通常需要以下两种方法之一:您要么使用素数作为大小(需要确保某些类型的散列分辨率的正确性),要么需要2的幂(因此可以通过简单的方法将值减小到正确的范围位掩码)。
答案 2 :(得分:8)
C有许多现有的哈希表实现,从C标准库hcreate / hdestroy / hsearch到APR和glib,它们还提供预构建的哈希函数。我强烈建议使用它们,而不是发明自己的哈希表或哈希函数;它们已针对常见用例进行了大量优化。
但是,如果您的数据集是静态的,那么您的最佳解决方案可能是使用perfect hash。对于给定的数据集,gperf将为您生成完美的哈希值。
答案 3 :(得分:7)
Wikipedia shows一个很棒的字符串哈希函数,叫做Jenkins One A Time Hash。它还引用了此哈希的改进版本。
uint32_t jenkins_one_at_a_time_hash(char *key, size_t len)
{
uint32_t hash, i;
for(hash = i = 0; i < len; ++i)
{
hash += key[i];
hash += (hash << 10);
hash ^= (hash >> 6);
}
hash += (hash << 3);
hash ^= (hash >> 11);
hash += (hash << 15);
return hash;
}
答案 4 :(得分:2)
首先,是40个冲突,130个单词哈希到0..99不好?如果你没有特别采取措施来实现它,你就不能指望完美的哈希。普通哈希函数在大多数情况下不会比随机生成器具有更少的冲突。
具有良好声誉的哈希函数是MurmurHash3。
最后,关于哈希表的大小,它实际上取决于您考虑的哈希表类型,尤其是桶是可扩展还是单槽。如果存储桶是可扩展的,那么还有一个选择:您可以选择存储器/速度限制的平均存储桶长度。
答案 5 :(得分:2)
我尝试过这些哈希函数并得到以下结果。我有大约960 ^ 3个条目,每个64字节长,64个字符以不同的顺序,哈希值32位。来自here的代码。
Hash function | collision rate | how many minutes to finish
MurmurHash3 | 6.?% | 4m15s
Jenkins One.. | 6.1% | 6m54s
Bob, 1st in link| 6.16% | 5m34s
SuperFastHash | 10% | 4m58s
bernstein | 20% | 14s only finish 1/20
one_at_a_time | 6.16% | 7m5s
crc | 6.16% | 7m56s
一个奇怪的事情是,几乎所有哈希函数对我的数据都有6%的冲突率。
答案 6 :(得分:2)
虽然djb2
,presented on stackoverflow by cnicutar几乎肯定更好,但我认为值得展示K&R哈希值:
1)显然是一个糟糕的哈希算法,如K&amp; R第1版(source)中所示
unsigned long hash(unsigned char *str)
{
unsigned int hash = 0;
int c;
while (c = *str++)
hash += c;
return hash;
}
2)可能是一个相当不错的哈希算法,如K&amp; R版本2中所示(由我在本书第144页验证);注意:如果您计划在哈希算法之外进行模数调整到数组长度,请务必从return语句中删除% HASHSIZE
。另外,我建议你做回报和&#34; hashval&#34;键入unsigned long
而不是简单unsigned
(int)。
unsigned hash(char *s)
{
unsigned hashval;
for (hashval = 0; *s != '\0'; s++)
hashval = *s + 31*hashval;
return hashval % HASHSIZE;
}
请注意,从两种算法中可以清楚地看出,第一版哈希非常糟糕的一个原因是因为它没有考虑字符串字符 order ,所以hash("ab")
因此将返回与hash("ba")
相同的值。这是不所以使用第二版哈希,这会(更好!)为这些字符串返回两个不同的值。
用于unordered_map
(哈希表模板)和unordered_set
(哈希集模板)的GCC C ++ 11哈希函数似乎如下所示。 < / p>
代码:
// Implementation of Murmur hash for 32-bit size_t.
size_t _Hash_bytes(const void* ptr, size_t len, size_t seed)
{
const size_t m = 0x5bd1e995;
size_t hash = seed ^ len;
const char* buf = static_cast<const char*>(ptr);
// Mix 4 bytes at a time into the hash.
while (len >= 4)
{
size_t k = unaligned_load(buf);
k *= m;
k ^= k >> 24;
k *= m;
hash *= m;
hash ^= k;
buf += 4;
len -= 4;
}
// Handle the last few bytes of the input array.
switch (len)
{
case 3:
hash ^= static_cast<unsigned char>(buf[2]) << 16;
[[gnu::fallthrough]];
case 2:
hash ^= static_cast<unsigned char>(buf[1]) << 8;
[[gnu::fallthrough]];
case 1:
hash ^= static_cast<unsigned char>(buf[0]);
hash *= m;
};
// Do a few final mixes of the hash.
hash ^= hash >> 13;
hash *= m;
hash ^= hash >> 15;
return hash;
}
答案 7 :(得分:1)
djb2有317个冲突,而对于64位哈希,MurmurHash没有,而对于32位哈希,则21个(对于466k随机32位哈希,大约25个)。 我的建议是使用MurmurHash(如果可用),它非常快,因为它一次占用几个字节。但是,如果您需要一个简单且简短的哈希函数来复制并粘贴到您的项目中,我建议使用杂音(一次一字节):
uint32_t inline MurmurOAAT32 ( const char * key)
{
uint32_t h(3323198485ul);
for (;*key;++key) {
h ^= *key;
h *= 0x5bd1e995;
h ^= h >> 15;
}
return h;
}
uint64_t inline MurmurOAAT64 ( const char * key)
{
uint64_t h(525201411107845655ull);
for (;*key;++key) {
h ^= *key;
h *= 0x5bd1e9955bd1e995;
h ^= h >> 47;
}
return h;
}
哈希表的最佳大小是-简而言之-尽可能大,同时仍适合内存。因为我们通常不知道或不想查询可用的内存量,甚至可能会更改,所以最佳哈希表大小大约是表中要存储的元素数量的2倍。分配更多的值将使您的哈希表更快,但收益迅速减少,使哈希表小于此值将使其指数级地变慢。这是因为哈希表存在一个非线性trade-off between space and time complexity,最佳负载因子显然是2-sqrt(2)= 0.58...。
答案 8 :(得分:0)
我使用的一件好事是以下(我不知道是否已经提到它因为我记不起它的名字了。)
您为密钥的字母[0,255]中的每个字符预先计算了一个带有随机数的表T.通过取T [k0] xor T [k1] xor ... xor T [kN]来哈希你的密钥'k0 k1 k2 ... kN'。你可以很容易地证明这与你的随机数生成器一样随机,并且计算上非常可行。如果你真的遇到了很多碰撞的非常糟糕的实例,你可以用一批新的随机数重复整个事情。