Каков оптимальный способ вычисления хэш-кода для набора точек?
Я ищу оптимальный способ вычисления хэш-кода для набора двумерных точек (чтобы я мог хранить полигоны в хэш-таблице).
есть несколько очевидных способов сделать это, например, объединить все координаты точек в строке и ее хэш-код, но это будет очень медленно.
на другом конце спектра скорости / столкновения я также могу, например, суммировать все координаты, что приведет к очень быстрому коду, но также создаст много столкновений.
каков оптимальный способ вычисления хэш-кода для набора точек?
является ли оптимальное решение другим, если координаты целочисленны (против реальных координат)?
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()
в зависимости от того, какие значения будут в любом случае. Возможно, можно просто добавить их.
Если вы хотите, чтобы полигоны, определенные по часовой стрелке и против часовой стрелки, но в противном случае равные, были равны, вам придется создать функцию канонизации. Функция, задающая точки полигонов, начиная с любой точки и в любом порядке, будет возвращать точки в равном порядке.
один алгоритм, который я могу придумать, это найти минимум всех возможных последовательностей точек:
- найти набор верхних левых точек (точек с минимальным x точек с минимальным y), это отправные точки.
- для каждой начальной точки и каждого направления итеративно добавьте связанные точки в заданном направлении и исключите все, что не является верхним левым в текущей итерации. Остановка, когда остается только одна начальная точка,пара направлений или когда N-1 итерации завершены. Если осталось более одной начальной точки и направления, выберите любое - все они изоморфны.
- изменить порядок точек, начиная с найденной точки в нашел направление.
Это 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);
}
Это не будет лучшим хэшем с точки зрения предотвращения столкновений, но должно быть очень быстро вычислить, и вы можете найти его достаточным для ваших нужд.