我正在寻找一个将整数的多重集映射到整数的函数,希望具有一些像成对独立性这样的保证。
理想情况下,内存使用应该是恒定的,并且在插入/删除后哈希值可以在O(1)时间内更新。(这禁止了执行诸如排序整数并使用像h(x) = h_1(x_1, h_2(x_2, h_3(x_3, x_4)))这样的哈希函数的操作。)
XOR哈希在这里无效,因为h({1,1,2}) = h({2})
如果底层哈希函数具有像n-独立性这样强的保证,那么将哈希相乘取模素数可能会奏效。
我在cstheory.stackexchange.com上提出了同样的问题,并得到了一个好答案:
SUM和SUB是安全的操作,因为它们在模算术(modular arithmetic)中是可逆的,在Java中,模数为2^32或2^64。
例如,00001011 变成 11010000。然后,只需对所有反转的元素求和。
如果我们需要在插入/删除操作上达到O(1)的时间复杂度,通常的SUM函数就可以实现这一点(这也是Java中集合Set的实现方式),尽管对于小整数集合而言分布不够均匀。
假设我们的集合不是均匀分布的(通常不会如此),我们需要一个映射N->f(N),使得f(N)在预期数据样本中能够均匀分布。通常情况下,数据样本中接近零的数字比最大值附近的数字要多得多。在这种情况下,通过反转比特位进行哈希运算可以实现均匀分布。
Scala示例:
def hash(v: Int): Int = {
var h = v & 1
for (i <- 1 to 31) {
h <<= 1;
h |= ((v >>> i) & 1)
}
h
}
def hash(a: Set[Int]): Int = {
var h = 0
for (e: Int <- a) {
h += hash(e);
}
h
}
但是我们的多集合哈希值不会均匀,尽管比简单的SUM好得多。
我曾经问过一个类似的问题, "排列的好哈希函数是什么?",并得到了一个对我的用例非常有效的哈希函数,我的工作代码中很少有碰撞。它可能也适合你。计算类似于这样的东西:
// initialize this->hash with 1
unsigned int hash = 1;
void add(int x) {
this->hash *= (1779033703 + 2*x);
}
x
,就使用上述公式更新哈希码。值的顺序并不重要,你总是会得到相同的哈希值。[a,b,c]
和 [b,c,a]
产生不同的哈希值? - jeffreyveonthis->hash *= (1779033703 + 2 * x * i);
,其中 i
是元素 x
在列表中的索引。 - jeffreyveon这里应该使用Min-hashing。应用置换,维护一个小型多重集合包含n个最小元素,然后选择最大的元素。
具体地说,这是一种在O(1)时间和空间内工作的简单方法。您需要类似于优先队列的东西,但不要使其与初始值之间的链接太明显。因此,根据一些精心制定的密钥对优先队列进行排序,这等效于在正常排序顺序的排列上运行优先队列。使队列跟踪多重性,以便所选元素也形成一个多重集合。
话虽如此,我不确定这是否分散得足够好(并且运行多个置换可能会变得昂贵),因此最好建立在Bradley的答案基础上。这是一个调整,以便重复的元素不会被取消:
xor(int_hash(x_n, multiplicity_n) foreach n)
Knuth在TAoCP中提到了这一点,这与哪些整数哈希函数接受整数哈希键是好的?几乎相同。
对于您的情况,将您的多集合转换为单个整数,然后执行链接帖子中描述的哈希可能是您想要做的。将集合转换为数字很简单;数字的连接将会做到。
有关Knuth方法的更多信息,请搜索“Knuth的乘法方法”
-tjw