【问题标题】:Hash function for 3d integer coordinates3d 整数坐标的散列函数
【发布时间】:2014-10-28 06:00:52
【问题描述】:

拥有 3D 统一网格,为了在大型模型中节省内存,不需要保存空单元格(不与任何对象重叠的单元格)。为此,我在 c# 中使用字典。尽管性能已经下降,但这仍然比创建 3D 网格时出现异常要好。现在我的问题是找到一个快速散列函数,将网格的 3d 整数坐标映射到唯一的数字。

我已经尝试过 ((x * 73856093 + y * 19349669 + z * 83492791))% n ,它并不总是生成唯一的数字。

【问题讨论】:

  • 如果数字可以是任意大小,那么您只需执行 x * MAXINT * MAXINT + y * MAXINT + z。如果数字必须与整数大小相同,那么由于pigeonhole principle,唯一性是不可能的。有 2^32 个可能的整数值和 2^96 个可能的整数三元组值。如果没有重叠,就不能将后一类归入前一类。
  • x、y 和 z 坐标的可能值是多少?
  • 哈希值根本不需要唯一。当不同输入值的 2 个哈希值相同时,就会发生冲突。您只需要将冲突保持在最低限度(以提高性能),这通常很容易,例如hash = (x * 31) + (y * 37) + (z * 41) 绰绰有余。
  • 假设您的哈希值可以高达 2^48,则更好的哈希(在您的情况下没有冲突)将是 hash = (x * 18397) + (y * 20483) + (z * 29303)
  • @Floris:只有当哈希映射本身具有相同的大小(即 2^48)时,避免冲突才会有所帮助。否则你在某处有一个 mod 步骤,所以你只推迟了碰撞。 hash=x*16777216+y*4096+z 也可以避免冲突,只使用 36 位(每个坐标 12 位)。它可以使用位移来实现。 但是如果以较小的 2 次幂为模减少,它会执行 可怕,这很可能发生在真正的哈希映射中。

标签: math voxel geohashing


【解决方案1】:

一方面,您将目标写为“节省内存”,而另一方面您要求“快速散列函数,将网格的 3d 整数坐标映射到唯一数字”。这两个不是很兼容。

您要保证 O(1) 访问。在这种情况下,您必须防止散列冲突并且必须将输入映射到 unique 数字。但在这种情况下,您的哈希图中还需要尽可能多的单元格,因为有可能的输入。所以你不会比一个简单的 N×N×N 数组节省内存。

或者——这更有可能——你只希望哈希冲突很少发生。然后你可以有一个散列图,它大约是 实际 存储对象数量的两倍。但在这种情况下,您不必完全避免哈希冲突,您只需使它们足够少即可。

选择一个好的散列函数很大程度上取决于输入数据的可能模式。如果输入是相当随机的,并且知道哈希图的大小,则应该以均匀分布为目标。如果对象更有可能位于相邻的块中,那么您需要确保坐标的微小变化不太可能导致碰撞。在这一点上,不要让你的因子成为质数,这样一个方向的微小变化就不太可能与另一个方向的另一个方向发生冲突。

如果有疑问,您可以随时进行测试:给定三个素数(例如散列 137x+149y+163z)和一些实际设置(即使用的坐标和生成的散列图大小),您可以简单地应用散列到所有坐标,调整到散列图大小并计算唯一值的数量。对各种三元组做同样的事情,然后选择一个使这个数字最大化的。但我怀疑这种优化水平是否值得付出努力。

【讨论】:

  • “您要确保坐标的微小变化不太可能导致碰撞”。我认为这仅在 x、y、z 坐标具有精度时才成立。
  • @ali:我假设整数,如您的问题所述。
【解决方案2】:

不要尝试就已经很好地涵盖的主题写一篇新文章,请参阅wikipedia 有关散列函数的文章。特别是第一张图片清楚地显示了多个输入如何散列到相同的值。

基本上,您的三元组被散列到 [0,2^64 - 1] 范围内的某个散列值(允许重复!)。然后通过方程 hash = hash % n 将范围缩小到比您的输入值数量(比如 n = 5)略大的值。输入值例如 [(1,1,1), (1,2,3), (2321, 322, 232), (3,3,3)] 的结果关系将如下所示:

    (1,1,1)          -> 2
    (1,2,3)          -> 0
    (2321, 322, 232) -> 0
    (3,3,3)          -> 3

如您所见,没有输入值(即散列)与 1 或 4 相关,并且有两个输入值散列到 0。

哈希的力量(以及平均情况为 O(1) 的原因)通过注意为了从哈希表中检索输入值(例如 (1,1,1))而变得清晰,如下步骤发生。

  1. 计算输入值的哈希并应用hash = hash % n,因此 (1,1,1) -> 2。
  2. 执行直接 O(1) 查找,即hash_function[2] = (1,1,1) + additional data stored with this particular input value
  3. 完成!

在多个输入值映射到同一个哈希值(在我们的示例中为 0)的情况下,内部算法需要对这些输入值进行搜索,这通常使用红黑树完成(最坏情况O(log n))。因此,任何查找的最坏情况也是O(log n)

当关系变成一对一的函数(双射)时,就会出现完美的散列。这提供了最佳性能,但很少见。正如我之前所说,幸运的是,在重复项很少的情况下,很容易生成几乎完美的哈希。本质上使您的哈希函数尽可能随机。

我在 cmets 中给出的例子可能已经足够了(而且是错误的做法):)但更标准的计算是:hash = ((((prime1 + value1) * prime2) + value2) * prime3) + value3) * prime4

这也回答了这个问题。请注意,素数可以是任何素数,但在实践中通常使用较小的值,例如 31,37 等。

在实践中可以使用测试来检查性能,但通常不是必需的。

无论如何重新阅读您的问题,我想知道您为什么不放弃整个哈希想法,而不仅仅是将您的点存储在一个简单的数组中??

【讨论】:

  • 你能说出一个使用红黑树进行碰撞处理的实现吗?至少Mono does not。此外,完美的散列必须是单射的,但不是满射的:您不需要为散列结果范围的每个元素提供可能的输入。
  • 你说得对 - 我在提到红黑树时说出来了。此外,我将范围作为哈希函数的图像(这在数学中很常见),但在这种情况下没有意义。
  • 大型模型在很大程度上会耗尽内存来为网格创建一定数量的单元格,用于小单元格维度。而字典之类的集合提供了仅存储与三角形重叠的单元格的机会。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-10-14
  • 1970-01-01
  • 2016-02-14
  • 2013-07-20
  • 2014-01-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多