我正在使用C语言编写哈希表,并且正在测试字符串的哈希函数。
我尝试的第一个功能是添加ascii代码并使用模(%100),但是在第一次数据测试中我得到的结果很差:130个单词40次冲突。
最终的输入数据将包含8 000个单词(这是字典存储在文件中)。哈希表声明为int table [10000],其中包含单词在txt文件中的位置。
第一个问题是散列字符串的最佳算法是什么?以及如何确定哈希表的大小?
提前致谢 !
:-)
我正在使用C语言编写哈希表,并且正在测试字符串的哈希函数。
我尝试的第一个功能是添加ascii代码并使用模(%100),但是在第一次数据测试中我得到的结果很差:130个单词40次冲突。
最终的输入数据将包含8 000个单词(这是字典存储在文件中)。哈希表声明为int table [10000],其中包含单词在txt文件中的位置。
第一个问题是散列字符串的最佳算法是什么?以及如何确定哈希表的大小?
提前致谢 !
:-)
Answers:
我与djb2
Dan Bernstein 取得了不错的成绩。
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;
}
size_t
或其他此类无符号值(例如此代码中的unsigned long)。该来电者是负责接收结果的模它适合哈希表。调用者控制要散列到的表插槽;不是功能。它只是返回一些未签名的数字。
首先,您通常不希望对哈希表使用加密哈希。这是一个算法非常快的加密标准仍是哈希表的标准速度奇慢。
其次,您要确保输入的每一位都可以/将影响结果。一种简单的方法是将当前结果旋转一定位数,然后将当前哈希码与当前字节进行异或。重复直到到达字符串的末尾。请注意,通常您也不希望轮换是字节大小的偶数倍。
例如,假设常见的情况是8位字节,则可以旋转5位:
int hash(char const *input) {
int result = 0x55555555;
while (*input) {
result ^= *input++;
result = rol(result, 5);
}
}
编辑:还请注意,对于哈希表大小,10000个插槽很少是一个不错的选择。通常,您需要以下两点之一:您想要一个素数作为大小(需要确保某种类型的哈希解析正确无误),或者想要2的幂(因此可以通过简单的方法将值减小到正确的范围)位掩码)。
Wikipedia显示了一个很好的字符串哈希函数,称为Jenkins One At A 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;
}
对于此466k英语字典,djb2有317个冲突,而MurmurHash对于64位哈希没有任何冲突,而对于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倍。分配更多的值将使您的哈希表更快,但收益迅速减少,使哈希表小于此值将使其指数级地变慢。这是因为哈希表在空间和时间复杂度之间存在非线性权衡,显然最佳负载因子为2-sqrt(2)= 0.58...。
首先,将130个单词的40次冲突散列为0..99不好吗?如果您没有采取专门的步骤来实现它,就不能指望完美的哈希。大多数情况下,普通哈希函数的冲突不会比随机生成器少。
具有良好声誉的哈希函数是MurmurHash3。
最后,关于哈希表的大小,这实际上取决于您所考虑的哈希表类型,尤其是存储分区是可扩展的还是单插槽的。如果存储桶是可扩展的,则还有一个选择:为存储/速度限制选择平均存储桶长度。
n - m * (1 - ((m-1)/m)^n) = 57.075...
。40次碰撞好于偶然的预期(46到70次,p值为0.999)。所讨论的哈希函数比它是随机的还是我们目睹了一个非常罕见的事件更为统一。
虽然djb2
,正如cnicutar在stackoverflow上提出的,虽然几乎可以肯定更好,但我认为也值得展示K&R哈希值:
1)显然是一种可怕的哈希算法,如K&R第一版(来源)所述
unsigned long hash(unsigned char *str)
{
unsigned int hash = 0;
int c;
while (c = *str++)
hash += c;
return hash;
}
2)可能是一种相当不错的哈希算法,如K&R第2版中所述(我在本书第144页上进行了验证);注意:% HASHSIZE
如果您打算在散列算法之外执行将模数调整为您的数组长度,请确保从return语句中删除。另外,我建议您使用return和“ hashval”类型unsigned long
而不是简单的unsigned
(int)类型。
unsigned hash(char *s)
{
unsigned hashval;
for (hashval = 0; *s != '\0'; s++)
hashval = *s + 31*hashval;
return hashval % HASHSIZE;
}
请注意,从这两种算法中可以明显看出,第1版哈希值如此糟糕的原因之一是因为它没有考虑字符串的字符顺序,因此hash("ab")
将返回与相同的值hash("ba")
。这是不是使之与第二版散,然而,对于这些字符串这将(好多了!)返回两个不同的值。
用于unordered_map
(哈希表模板)和unordered_set
(哈希集模板)的GCC C ++ 11哈希函数如下所示。
码:
// 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;
}
我已经尝试过这些哈希函数,并得到以下结果。我大约有960 ^ 3个条目,每个条目64个字节长,不同顺序的64个字符,哈希值32bit。从这里的代码。
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%。