假设您有两个哈希H(A)
和H(B)
,并且您希望将它们组合在一起。我已经读过将两个哈希值组合在一起的好方法是XOR
它们,例如XOR( H(A), H(B) )
。
我在这些hash function guidelines上简要介绍了我发现的最佳解释:
对具有大致随机分布的两个数字进行异或,导致另一个数字仍具有大致随机分布*,但现在取决于这两个值。
...
*在要组合的两个数字的每个位,如果两个位相等则输出0,否则为1.换句话说,在50%的组合中,输出1。因此,如果两个输入位各有大约50-50的机会为0或1,那么输出位也是如此。
你能解释为什么XOR应该是组合散列函数(而不是OR或AND等)的默认操作的直觉和/或数学吗?
答案 0 :(得分:145)
xor是散列时使用的危险默认函数。它比和和更好,但是并没有多说。
xor是对称的,因此元素的顺序会丢失。因此,"bad"
将哈希与"dab"
相同。
xor将相同的值映射到零,你应该避免映射" common"值为零:
所以(a,a)
被映射到0,而(b,b)
也被映射到0.因为这样的对比随机性更常见,你最终会得到远远多于零的碰撞
有了这两个问题,xor最终成为一个散列组合器,表面看起来不太合适,但在进一步检查后却没有。
在现代硬件上,通常以xor的速度添加(它可能会使用更多的功率来实现这一点)。添加的真值表与所讨论的位上的xor类似,但当两个值均为1时,它也会向下一位发送一个位。这会消除较少的信息。
所以hash(a) + hash(b)
更好,如果a==b
,则结果是hash(a)<<1
而不是0。
这仍然是对称的。我们可以以适度的成本打破这种对称性:
hash(a)<<1 + hash(a) + hash(b)
又名hash(a)*3 + hash(b)
。 (计算hash(a)
一次,如果您使用班次解决方案,建议存储)。任何奇数常数而不是3
都会将size_t
(或k位无符号常量)双射地映射到自身,因为对于某些2^k
,无符号常量上的映射是数学模k
,任何奇数常数都是2^k
的相对素数。
对于一个更加漂亮的版本,我们可以检查boost::hash_combine
,这是有效的:
size_t hash_combine( size_t lhs, size_t rhs ) {
lhs^= rhs + 0x9e3779b9 + (lhs << 6) + (lhs >> 2);
return lhs;
}
这里我们将seed
的一些移位版本与常量(基本上是随机0
s和1
s - 加在一起,特别是它是黄金比例的倒数具有一些加法和xor的32位定点分数。这打破了对称性,并引入了一些&#34;噪音&#34;如果传入的散列值很差(即,想象每个分量哈希值为0 - 上面的处理很好,在每个组合后产生1
和0
s的拖尾。我只是输出一个{{ 1}})。
对于那些不熟悉C / C ++的人来说,0
是一个无符号整数值,足以描述内存中任何对象的大小。在64位系统上,它通常是64位无符号整数。在32位系统上,32位无符号整数。
答案 1 :(得分:107)
假设均匀随机(1位)输入,AND函数输出概率分布为75%0
和25%1
。相反,OR为25%0
和75%1
。
XOR函数是50%0
和50%1
,因此它有利于组合均匀概率分布。
这可以通过写出真值表来看出:
a | b | a AND b
---+---+--------
0 | 0 | 0
0 | 1 | 0
1 | 0 | 0
1 | 1 | 1
a | b | a OR b
---+---+--------
0 | 0 | 0
0 | 1 | 1
1 | 0 | 1
1 | 1 | 1
a | b | a XOR b
---+---+--------
0 | 0 | 0
0 | 1 | 1
1 | 0 | 1
1 | 1 | 0
练习:两个1位输入a
和b
有多少逻辑函数具有均匀的输出分布?为什么XOR最适合您问题中所述的目的?
答案 2 :(得分:29)
尽管它具有方便的位混合特性,但由于其可交换性,XOR 不是组合哈希的好方法。考虑如果将{1,2,...,10}的排列存储在10元组的哈希表中会发生什么。
更好的选择是m * H(A) + H(B)
,其中 m 是一个很大的奇数。
信用:上述合并器是Bob Jenkins的提示。
答案 3 :(得分:16)
Xor可能是结合哈希的“默认”方式,但Greg Hewgill的回答也说明了它有其陷阱的原因: 两个相同散列值的xor为零。 在现实生活中,有相同的哈希比人们预期的更为常见。然后,您可能会发现在这些(并非如此罕见)的极端情况下,生成的组合哈希值始终相同(零)。哈希碰撞会比你预期的要频繁得多。
在一个人为的例子中,您可能会将来自您管理的不同网站的用户的哈希密码组合在一起。不幸的是,大量用户重复使用他们的密码,并且产生的哈希值的惊人比例为零!
答案 4 :(得分:8)
答案 5 :(得分:2)
如果XOR
带有偏置输入的随机输入,则输出是随机的。 AND
或OR
也是如此。例如:
00101001 XOR 00000000 = 00101001 00101001 AND 00000000 = 00000000 00101001 OR 11111111 = 11111111
正如@Greg Hewgill所提到的,即使两个输入都是随机的,使用AND
或OR
也会导致偏向输出。
我们将XOR
用于更复杂的事情的原因是,嗯,没有必要:XOR
完美无缺,而且它非常愚蠢。
答案 6 :(得分:0)
java.util.Arrays中hashCode()
的各种版本的源代码是可靠,通用的散列算法的绝佳参考。它们易于理解并翻译成其他编程语言。
粗略地说,大多数多属性hashCode()
实现都遵循以下模式:
public static int hashCode(Object a[]) {
if (a == null)
return 0;
int result = 1;
for (Object element : a)
result = 31 * result + (element == null ? 0 : element.hashCode());
return result;
}
您可以搜索其他StackOverflow Q&amp; As,了解有关31
背后的魔力的更多信息,以及Java代码经常使用它的原因。它不完美,但具有很好的一般性能特征。
答案 7 :(得分:0)
覆盖左侧2列并尝试使用输出计算出输入的内容。
a | b | a AND b
---+---+--------
0 | 0 | 0
0 | 1 | 0
1 | 0 | 0
1 | 1 | 1
当你看到1位时,你应该知道两个输入都是1。
现在为XOR做同样的事情
a | b | a XOR b
---+---+--------
0 | 0 | 0
0 | 1 | 1
1 | 0 | 1
1 | 1 | 0
XOR没有提供任何关于它的输入。
答案 8 :(得分:0)
XOR 不会忽略某些输入,例如 OR 和 AND 。
如果您以 AND(X,Y)为例,并为输入 X 输入false,则输入 Y 无关紧要...而且在组合哈希时,可能希望输入很重要。
如果您采用 XOR(X,Y),则两者输入 ALWAYS 很重要。如果Y无关紧要,那么将没有X的值。如果更改了X或Y,则输出将反映出来。