【问题标题】:Good hash function for list of 2-d positions?二维位置列表的良好哈希函数?
【发布时间】:2010-10-14 14:17:31
【问题描述】:

我有一系列对象,它们唯一不同的内部状态是二维位置(2 个整数)的固定长度列表(或其他)。也就是说,它们都具有相同数量的元素,具有(可能)不同的二维值。

我将不断将新实例与所有以前存在的实例进行比较,因此编写一个好的散列函数以最大限度地减少比较次数非常重要。

你会建议我如何散列它们?

【问题讨论】:

  • 值是否有界? IE。你知道 x 和 y 的最小值/最大值吗?
  • 我只想说这是一个很好的例子,说明了一个简单的方法来制作一个非常糟糕的哈希函数。我已经看到x ^ y 被用作此类事情的哈希函数,这会导致大约 99.9% 的冲突率(假设像 1024x1024 这样的方形域)
  • 这些值受已知限制的约束,是的。它们是 x, y > 0 和 x
  • 很容易编写一个看起来合理但性能不佳的哈希函数。

标签: java hash hashcode


【解决方案1】:

选择 31 作为素数的关键在于能够使用位移位和减法进行乘法运算。

假设这是一个 Point 类:

class Point {
    public final int x;
    public final int y;

    public Point(int x, int y)
    {
        this.x = x;
        this.y = y;
    }

    @Override
    public int hashCode()
    {
        int hash = 17;
        hash = ((hash + x) << 5) - (hash + x);
        hash = ((hash + y) << 5) - (hash + y);
        return hash;
    }
}

选择 31 作为素数的关键在于能够使用位移位和单次减法运算进行乘法运算。请注意,位移 5 相当于乘以 32,而减法使这相当于乘以 31。这两个操作比单个真正的乘法更有效。

然后你的对象是:

class TheObject
{
    private final java.util.List<Point> points;

    public TheObject(List<Point> points)
    {
        this.points = points;
    }

    @Override
    public int hashCode()
    {
        int hash = 17;int tmp = 0;
        for (Point p : points)
        {
            tmp = (hash + p.hashCode());
            hash = (tmp << 5) - tmp;
        }
        return hash;
    }
}

【讨论】:

  • 您可以只使用points.hashCode() 代替TheObject,因为List 根据其元素定义其哈希码。
  • 可以简化一点,因为Point::hashCode 中的前两行只是将 31*17 添加到 x (这不是必需的,因为它实际上只是将所有点向右移动) - 所以你可以做return 31*x + y;
  • +1 - 这是正确的方法,但也许应该使用更大的素数。
  • 当心这个解决方案的整数溢出 - 它会在大约 10 点后发生。我会用hash += p.hashCode() 替换hash = 31 * hash...,这应该足够了。将每个类中的散列变量初始化为 17 可能不会做很多事情。如果点列表是静态的,那么在加载点时计算哈希值会更好一些。当然,您会使用与上面看到的类似的代码,只是在不同的时间。
  • @Tony Ennis - 这里的溢出有什么问题? stackoverflow.com/questions/892618/…
【解决方案2】:

嗯,像二叉搜索树这样的东西怎么样?

用伪代码进行比较:

position1 > position2 := 
   (position1.x > position2.x) || 
   ((position1.x == position2.x) && (position1.y > position2.y))

list1.x > list2.x := {
    for (i in 0...n) 
        if (list1[i] > list2[i]) return true;
        else if (list1[i] > list2[i]) return false;
    return false;
}

n 当然是列表的长度。

我不是 java 专业人士,我真的不知道标准库,但我想,你可以自己编写树。实现一个 getID 方法,它将尝试查找此列表或以其他方式将其插入,并附带一个唯一 id,您可以通过简单地增加一个计数器来获得它。

这样,您将获得一个没有任何冲突的 ID(而不是哈希)。在最坏的情况下比较两个列表是O(n),因此查找/插入是O(n) * O(log(m))(假设树是平衡的),其中m 是所有列表的总数。

因此,在最坏的情况下,确定 ID 比散列更昂贵,但如上所述,结果保证是唯一的。

我不能说平均,因为你没有给出数字。实际上,我很惊讶您不想进行直接比较,因为我希望 2 个位置相等的概率小于 1%,因此列表比较大约为 O(1),因为您需要的概率比较 5 个条目真的很小。

此外,尚不清楚列表是否可变,因为如果它们是不可变的,则成本应该无关紧要。

【讨论】:

  • 无论如何都必须进行逐点比较 - 如果两个哈希值相同,并不意味着列表相同。所以 back2dos 并没有添加更多代码,而是利用了无论如何都必须存在的东西。 因为您需要比较 5 个条目的概率非常小。 这就是问题的关键,除非数据集往往有很多重复点。
【解决方案3】:

根据整数的大小,您可能需要将第一个坐标乘以最大可能坐标,然后再加上第二个。例如,如果 X 和 Y 是正数并且限制为 256,您可以尝试 X*256+Y 作为您的哈希函数。如果 X 和 Y 也可以是负数,您可能需要先抵消它们以使它们成为非负数。此外,如果将 X 乘以最大值会溢出整数,您可能需要一个多整数散列值,或者可能需要 mod 或按位 - 并且结果为 UINT_MAX。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-05-14
    • 1970-01-01
    • 2016-12-07
    • 2010-11-22
    • 2011-10-27
    • 2017-11-15
    • 2023-02-02
    • 2010-09-28
    相关资源
    最近更新 更多