【发布时间】:2014-02-04 09:46:10
【问题描述】:
我将许多具有地理位置的对象存储为以米为单位的 2D 点 (x,y)。为了代表世界,我使用了一个划分为 1 平方公里的单元格的网格。目前我正在为此使用HashMap<Position, Object>。任何其他地图或适当的数据结构都可以,但我的解决方案有效,所以我只对解决细节感兴趣。
我已经阅读了很多关于制作好的散列函数的文章,特别是对于 2D 点。到目前为止,还没有真正好的解决方案(被评为尽可能无碰撞)。
为了测试一些想法,我编写了一个非常简单的 java 程序来生成从任意数 (-1000,-1000) 到 (1000, 1000) (x1, y1 -> x2,y2) 的点的哈希码并存储它们在HashSet<Integer> 中,这是我的结果:
# java HashTest
4000000 number of unique positions
test1: 3936031 (63969 buckets, 1,60%) collisions using Objects.hash(x,y)
test2: 0 (4000000 buckets, 100,00%) collisions using (x << 16) + y
test3: 3998000 (2000 buckets, 0,05%) collisions using x
test4: 3924037 (75963 buckets, 1,90%) collisions using x*37 + y
test5: 3996001 (3999 buckets, 0,10%) collisions using x*37 + y*37
test6: 3924224 (75776 buckets, 1,89%) collisions using x*37 ^ y
test7: 3899671 (100329 buckets, 2,51%) collisions using x*37 ^ y*37
test8: 0 (4000000 buckets, 100,00%) collisions using PerfectlyHashThem
test9: 0 (4000000 buckets, 100,00%) collisions using x << 16 | (y & 0xFFFF)
图例:碰撞次数,桶(碰撞),perc(碰撞)
这些哈希函数中的大多数都表现得很糟糕。事实上,唯一好的解决方案是将 x 移动到整数的前 16 位。我想,限制是两个最远的点不能超过Integer.MAX_INT的平方根,即面积必须小于46 340平方公里。
这是我的测试函数(只是为每个新的哈希函数复制):
public void test1() {
HashSet<Integer> hashCodes = new HashSet<Integer>();
int collisions = 0;
for (int x = -MAX_VALUE; x < MAX_VALUE; ++x) {
for (int y = -MAX_VALUE; y < MAX_VALUE; ++y) {
final int hashCode = Objects.hash(x,y);
if (hashCodes.contains(hashCode))
collisions++;
hashCodes.add(hashCode);
}
}
System.console().format("test1: %1$s (%2$s buckets, %3$.2f%%) collisions using Objects.hash(x,y)\n", collisions, buckets(collisions), perc(collisions));
}
我在这里想错了吗?我应该微调素数以获得更好的结果吗?
编辑:
添加了更多哈希函数(test8 和 test9)。 test8 来自@nawfal 在Mapping two integers to one, in a unique and deterministic way 的回复(从short 转换为int)。
【问题讨论】:
-
我现在也尝试过使用素数 71 和 97。桶的数量从 37 个几乎翻了一番。
-
请尝试 31。我想看看结果。
-
对于您的号码,应该可以创建 0 次冲突。 -1000 到 1000 乘以 -1000 到 1000 有 40 亿个不同的坐标。我认为您在某处做错了什么。请张贴代码。
-
31 的结果比我使用 37 的示例稍微“差”一些。例如:test4: 3936031 (63969 个桶,1,60%) 使用 x*31 + y 的碰撞