Каков оптимальный способ вычисления хэш-кода для набора точек?

Я ищу оптимальный способ вычисления хэш-кода для набора двумерных точек (чтобы я мог хранить полигоны в хэш-таблице).

есть несколько очевидных способов сделать это, например, объединить все координаты точек в строке и ее хэш-код, но это будет очень медленно.

на другом конце спектра скорости / столкновения я также могу, например, суммировать все координаты, что приведет к очень быстрому коду, но также создаст много столкновений.

каков оптимальный способ вычисления хэш-кода для набора точек?

является ли оптимальное решение другим, если координаты целочисленны (против реальных координат)?

Edit: я использую .net, поэтому хэш-код должен быть длиной 32 бита.

7 ответов


для этой работы нет оптимального способа. Все зависит от того, насколько большой гашиш вы можете себе позволить. Вы должны сделать tradoffs между скоростью и диффузии. Имейте в виду, что нет такого понятия, как оптимальное решение (если вы точно не знаете, что вы собираетесь хэш) в некоторых случаях XOR может быть достаточно хорошим.

возьмите, например, этот код

unsigned int JSHash(char* str, unsigned int len)
{
    unsigned int hash = 1315423911;
    unsigned int i    = 0;

    for(i = 0; i < len; str++, i++)
    {
        hash ^= ((hash << 5) + (*str) + (hash >> 2));
    }

    return hash;
}
/* End Of JS Hash Function */

Вы сказали, что agregating очки и медленно. Если вы исправите верхний код, ему не нужно какое-либо согласование передайте trought (не сильно отличается от сумм), и если вы используете целые числа и поплавки, вы, вероятно, исправите сдвиги ( > - это операции сдвига, которые вместе работают как побитовое вращение), чтобы соответствовать вашему типу данных.

проверьте наличие других хэш-функций здесь: http://www.partow.net/programming/hashfunctions/


оптимальный - это зависит от ваших требований от вычисления хэша.

производительность будет стоить больше хэш-коллизий.

У вас есть жесткая привязка к любому из них? Это сводится к математическому анализу того, сколько каждый процент хэш-коллизий будет стоить вам с точки зрения производительности.


Если ваш набор данных случайно является одним из полигонов, которые могут иметь общие ребра, но не перекрываться в противном случае, вам нужно только хэшировать три точки в каждом полигоне, чтобы избежать столкновений.

Edit: переосмысливая это, представляя возможные столкновения с вогнутыми / выпуклыми границами, так же хорошо, что ваши полигоны перекрываются. - вздох

увы: когда выпуклое и вогнутое встречаются, у меня всегда возникают проблемы. :- P



кроме того, вы можете просто XOR хэши отдельных точек.

return p1.GetHashCode() ^ p2.GetHashCode()

в зависимости от того, какие значения будут в любом случае. Возможно, можно просто добавить их.


Если вы хотите, чтобы полигоны, определенные по часовой стрелке и против часовой стрелки, но в противном случае равные, были равны, вам придется создать функцию канонизации. Функция, задающая точки полигонов, начиная с любой точки и в любом порядке, будет возвращать точки в равном порядке.

один алгоритм, который я могу придумать, это найти минимум всех возможных последовательностей точек:

  1. найти набор верхних левых точек (точек с минимальным x точек с минимальным y), это отправные точки.
  2. для каждой начальной точки и каждого направления итеративно добавьте связанные точки в заданном направлении и исключите все, что не является верхним левым в текущей итерации. Остановка, когда остается только одна начальная точка,пара направлений или когда N-1 итерации завершены. Если осталось более одной начальной точки и направления, выберите любое - все они изоморфны.
  3. изменить порядок точек, начиная с найденной точки в нашел направление.

Это O(n^2) Худший случай для полностью вырожденных полигонов, но если ваши полигоны не имеют перекрывающихся точек, это O (n), с довольно небольшим постоянным коэффициентом.

с каноническим порядком вы можете легко сравнить два полигона для равенства, просто итеративно сравнить точки для равенства. Расчет хэш-кода также тривиален, используйте любой разумно надежный метод комбинации хэша. Например:

int result = 0;
foreach (var point in this.points) {
    result = (result * 31 + point.X.GetHashCode()) * 31 + point.Y.GetHashCode();
}

для очень быстрого (для вычисления) хэша с желаемыми свойствами по часовой стрелке/против часовой стрелки вы не хотели бы зависеть от нахождения четко определенного порядка точек.

это ограничивает ваши хэш-операции объединения до тех, которые коммутируют. Поэтому мы хотим сохранить все данные, которые не зависят от ориентации, отдельно во время операций объединения.

вот простое решение:

предполагая, что функция объединить int - > int - > int, который является ассоциативным для начала сделаем любое из следующих действий:

public static int combine(int h, int x)
{
    return h * 31 + x;
} 

public static int combine(int h, int x)
{
    return h ^ x;
} 

тогда мы можем сделать следующее:

public override int GetHashCode()
{
    int x = 0;
    int y = 0;
    uint h = 0;    
    foreach (var point p in polgon)
    {
        x = combine(x, p.X);
        y = combine(y, p.Y);
        h++;
    }
    // simplified, unrolled Murmur2 hash for end stage
    const uint m = 0x5bd1e995;
    const int r = 24;
    uint h = count;
    uint k = ReinterpretInt32ToUInt32(x);
    k *= m;
    k ^= k >> r;
    k *= m;
    h *= m;
    h ^= k;
    k = ReinterpretInt32ToUInt32(y);
    k *= m;
    k ^= k >> r;
    k *= m;
    h *= m;
    h ^= k;
    // avalanche
    h ^= h >> 13;
    h *= m;
    h ^= h >> 15;
    return ReinterpretUInt32ToInt32(h);
}

опираясь на это, чтобы сделать код выше просто

public unsafe uint ReinterpretInt32ToUInt32(int i)
{
    return *((uint*) (void*) &i);
}

public unsafe int ReinterpretUInt32ToInt32(uint u)
{
    return *((int*) (void*) &u);
}

Это не будет лучшим хэшем с точки зрения предотвращения столкновений, но должно быть очень быстро вычислить, и вы можете найти его достаточным для ваших нужд.