稳定的散列函数将一组数字划分为m个桶,使m个桶的大小尽可能平衡



当前算法为:

d = {}
for doc_id in request:
md5 = hashlib.md5()
md5.update(str(doc_id) + "_" +  request_id)
digest = md5.hexdigest()
k = int(digest, 16)
a = (k%720)/(720/16)
if a not in d:
d[a]=1
else:
d[a]=d[a]+1

请求大小在(8001000(范围内

我计算了1000次算法,

最大值(d[i](-最小值(d[j](在20+中为平均值

有没有一种方法可以使16个桶的大小尽可能平衡

我不认为通过更改哈希函数可以做得更好。MD5已经不是最先进的了,但对于这样的东西来说,它仍然足够随机。

让我们看看一些概率论,看看如果我们使用一个完全一致的哈希函数,它会有多好。如果您完全随机地将1000个元素放入16个桶中,则每个桶平均会有1000/16=62.5个元素。但是这个数字的方差是多少?

要计算此值,请考虑单个bucket和完全随机的bucket分配。一个特定元素被分配给这个桶的概率是1/16,我们有1000个元素。因此,我们的分布等价于一个二项分布,其中n=1000,p=1/16。

该分布的方差由n*p*(1-p(≈59给出。标准偏差是sqrt(方差(≈8,这让你知道了与平均值的预期偏差的数量级。二项式分布不是正态分布,但对于这种大小的数字,它非常接近,所以我们预计大约68%的桶大小在平均值的8以内。根据我可靠的TI-83计算器,预计0.6%的病例会出现与平均值偏离20以上的值,因此在1000次试验中,你很可能会看到其中的一些。


如果不平衡确实导致了问题,您可以更改分区代码以使用某种类型的开放寻址。一个简单的方法是使用目标bucket加1(模bucket计数(,直到它找到一个不够满的bucket。

请注意,之后很难再次找到元素,除非您限制这种跳桶操作,例如最多3次。这使得查找成本更高,因为您需要在4个bucket中查找元素,而不是仅在1个bucket。

如果之后不需要来查找元素,您可以简单地将它们分配给最不满的bucket;那么就根本不需要散列了。

正如Thomas所解释的,即使是一个加密强度哈希函数——它有效地产生了一个伪随机值(但对每个密钥都是稳定的(——也会表现出相当大的差异。如果你想在这方面有所改进,你有几个选择:

  • 每个bucket的#元素差异有一些差异,所以你可以做一些事情,比如将i从1循环到1000;将"_" + i附加到键上,并查看哪个循环迭代产生了最小的max(d[i])- min(d[j])值;只要记住在查找时添加相同的_i

    • 我的公司在早上打包密钥以供全天使用时会这样做,以最大限度地减少最坏情况下的冲突链长度,从而减少静态分配的二维数组的内存使用;基本为CCD_ 4;我们更改哈希函数使用的随机数数据,而不是密钥,但两者都有可能
  • 利用密钥的一些知识(如果他们愿意的话(-这有点黑艺术,但例如-倾向于增加但可能偶尔有间隙的数字通常比加密强度哈希更好地映射到具有身份哈希(即h(k(==k(的桶上;当你有两个部分密钥doc_idrequest_id时,你需要了解这两个密钥中的典型值/模式,才能有机会制作一个比加密分布更均匀的哈希函数,并尝试将它们组合起来,以获得类似的密集但均匀的

正如其他答案所指出的,您看到的行为是将球随机放入垃圾箱并查看装载量最大和装载量最小的桶的行为的结果。即使是纯随机分布,你也会期望在这些桶之间看到一个不错的分布。

另一种选择是使用不同的策略将项目分配到存储桶中。假设您有两个散列函数,而不是一个。如果你对每个项目进行散列,并将其放入散列到的两个桶中负载较小的一个桶中,那么与只对每个项目散列一次相比,结果的分布更接近统一。(这有时被称为"两种选择的力量"(。添加更多的哈希函数确实会进一步减少扩散,但程度与从一个哈希函数到两个哈希函数不同。

最新更新