我有一些布尔数组,它们的大小不是常数,而且我需要一个强大而快速的哈希算法来为它们提供最小的哈希冲突机会。
我自己的想法是计算每个布尔数组的整数值,但是例如这两个数组将给出3的相同散列:
[0,1,1]和[1,1]
我想在计算整数值后乘以数组的大小,但这个想法也很糟糕,因为哈希冲突的可能性很高。
有没有人有个好主意?
答案 0 :(得分:7)
在数组的开头插入一个sentinel true
元素,然后将该数组解释为二进制数。对于少于32个元素的数组,这是完美哈希(无冲突)。对于较大的数组,我建议算术模数小于2 31 。
示例:
Array | Binary | Decimal
------------+--------+---------
[ 0, 1, 1 ] | 01011 | 11
[ 1, 1 ] | 00111 | 7
这与将数组解释为二进制数并使用1 << n
进行按位OR相同,其中n
是数组的大小。
实现:
int hash(int[] array)
{
int h = (1 << array.length);
for (int i = 0; i < array.length; i++)
{
h = h | (array[i] << (array.length - i - 1));
}
return h;
}
答案 1 :(得分:3)
我的想法:
方法#1:
计算第一个2n
素数,其中n
是数组的长度。
让哈希= 1。
对于i = 0到n:如果位置i
的位为1,则将hash
乘以2i
和2i + 1
st prime。如果为0,则仅将其乘以2i
。
方法#2:
将二进制数组视为三元数组。位是0 =&gt;三位数为0;位是1 =&gt;三位数是1;位不存在=&gt;三元数字是2(这个数字是有效的,因为数组有一个最大可能的长度)。
使用此替换计算三元数 - 结果将是唯一的。
这里有一些代码显示了这些算法在C ++中的实现,以及一个测试程序,它为每个长度为0 ... 18的布尔数组生成哈希值。我使用C ++ 11类std::unordered_map
,以便每个哈希都是唯一的。因此,如果我们没有任何重复项(即哈希函数是完美的),我们应该在集合which we do中获得2 ^ 19 - 1
个元素(我必须将整数更改为unsigned long long
在IDEone上,否则哈希值并不完美 - 我怀疑这与32位与64位架构有关):
#include <unordered_set>
#include <iostream>
#define MAX_LEN 18
unsigned long prime_hash(const unsigned int *arr, size_t len)
{
/* first 2 * MAX_LEN primes */
static const unsigned long p[2 * MAX_LEN] = {
2, 3, 5, 7, 11, 13, 17, 19, 23,
29, 31, 37, 41, 43, 47, 53, 59, 61,
67, 71, 73, 79, 83, 89, 97, 101, 103,
107, 109, 113, 127, 131, 137, 139, 149, 151
};
unsigned long h = 1;
for (size_t i = 0; i < len; i++)
h *= p[2 * i] * (arr[i] ? p[2 * i + 1] : 1);
return h;
}
unsigned long ternary_hash(const unsigned int *arr, size_t len)
{
static const unsigned long p3[MAX_LEN] = {
1, 3, 9, 27,
81, 243, 729, 2187,
6561, 19683, 59049, 177147,
531441, 1594323, 4782969, 14348907,
43046721, 129140163
};
unsigned long h = 0;
for (size_t i = 0; i < len; i++)
if (arr[i])
h += p3[i];
for (size_t i = len; i < MAX_LEN; i++)
h += 2 * p3[i];
return h;
}
void int2barr(unsigned int *dst, unsigned long n, size_t len)
{
for (size_t i = 0; i < len; i++) {
dst[i] = n & 1;
n >>= 1;
}
}
int main()
{
std::unordered_set<unsigned long> phashes, thashes;
/* generate all possible bool-arrays from length 0 to length 18 */
/* first, we checksum the only 0-element array */
phashes.insert(prime_hash(NULL, 0));
thashes.insert(ternary_hash(NULL, 0));
/* then we checksum the arrays of length 1...18 */
for (size_t len = 1; len <= MAX_LEN; len++) {
unsigned int bits[len];
for (unsigned long i = 0; i < (1 << len); i++) {
int2barr(bits, i, len);
phashes.insert(prime_hash(bits, len));
thashes.insert(ternary_hash(bits, len));
}
}
std::cout << "prime hashes: " << phashes.size() << std::endl;
std::cout << "ternary hashes: " << thashes.size() << std::endl;
return 0;
}
答案 2 :(得分:2)
一个简单有效的哈希码正在用素数代替0和1并执行通常的shift-accumulator循环:
hash=0
for (bits in list):
hash = hash*31 + 2*bit + 3
return hash
这里0被视为3,1被视为5,因此不会忽略前导零。乘以31确保顺序很重要。这不是加密强大的:给定一个短序列的哈希码,它是简单的算术来反转它。