Вопрос: Что мы знаем о расстоянии Хэмминга d (x, y)?
Ответ:
- Неотрицательно: d (x, y) ≥ 0
- Он равен нулю только для одинаковых входов: d (x, y) = 0 ⇔ x = y
- Он симметричен: d (x, y) = d (y, x)
- Он подчиняется неравенству треугольника , d (x, z) ≤ d (x, y) + d (y, z)
Вопрос: Почему нас это волнует?
Ответ: Потому что это означает, что расстояние Хэмминга является метрикой для метрического пространства . Есть алгоритмы индексации метрических пространств.
Вы также можете найти алгоритмы «пространственного индексирования» в целом, зная, что ваше пространство не евклидово, а является метрическим. Многие книги по этой теме посвящены индексации строк с использованием такой метрики, как расстояние Хэмминга.
Сноска: если вы сравниваете расстояние Хэмминга для строк фиксированной ширины, вы можете получить значительное улучшение производительности, используя встроенные функции сборки или процессора. Например, с помощью GCC ( manual ) вы делаете это:
static inline int distance(unsigned x, unsigned y)
{
return __builtin_popcount(x^y);
}
Если вы затем сообщите GCC, что компилируете для компьютера с SSE4a, то я считаю, что это должно сократиться до пары кодов операций.
Изменить: согласно ряду источников, это иногда / часто медленнее, чем обычный код маски / сдвига / добавления. Сравнительный анализ показывает, что в моей системе версия C превосходит GCC __builtin_popcount
примерно на 160%.
Приложение: Мне самому была интересна проблема, поэтому я профилировал три реализации: линейный поиск, дерево BK и дерево VP. Обратите внимание, что деревья VP и BK очень похожи. Дочерние элементы узла в BK-дереве - это «оболочки» деревьев, содержащие точки, каждая из которых находится на фиксированном расстоянии от центра дерева. Узел в дереве VP имеет двух дочерних элементов, один из которых содержит все точки в сфере с центром в центре узла, а другой дочерний элемент, содержащий все точки вне. Таким образом, вы можете думать об узле VP как о узле BK с двумя очень толстыми «оболочками» вместо множества более тонких.
Результаты были получены на моем ПК с тактовой частотой 3,2 ГГц, и алгоритмы не пытаются использовать несколько ядер (что должно быть легко). Я выбрал размер базы данных из 100 миллионов псевдослучайных чисел. Результаты представляют собой среднее значение 1000 запросов для расстояния 1..5 и 100 запросов для 6..10 и линейного поиска.
- База данных: 100 млн псевдослучайных чисел
- Количество испытаний: 1000 для расстояний 1..5, 100 для расстояний 6..10 и линейных
- Результаты: среднее количество совпадений по запросу (очень приблизительное).
- Скорость: количество запросов в секунду.
- Охват: средний процент базы данных, проверенной на запрос.
- Дерево BK - - Дерево VP - - Линейное -
Dist Результаты Speed Cov Speed Cov Speed Cov
1 0,90 3800 0,048% 4200 0,048%
2 11 300 0,68% 330 0,65%
3130 56 3,8% 63 3,4%
4 970 18 12% 22 10%
5 5700 8,5 26% 10 22%
6 2,6e4 5,2 42% 6,0 37%
7 1,1e5 3,7 60% 4,1 54%
8 3,5e5 3,0 74% 3,2 70%
9 1,0e6 2,6 85% 2,7 82%
10 2,5e6 2,3 91% 2,4 90%
любая 2.2 100%
В своем комментарии вы упомянули:
Я думаю, что BK-деревья можно улучшить, сгенерировав группу BK-деревьев с разными корневыми узлами и расправив их.
Я думаю, что именно по этой причине дерево VP работает (немного) лучше, чем дерево BK. Будучи «более глубоким», а не «мелким», он сравнивает с большим количеством точек, а не использует более мелкие сравнения с меньшим количеством точек. Я подозреваю, что различия более значительны в пространствах более высоких измерений.
Последний совет: конечные узлы в дереве должны быть просто плоскими массивами целых чисел для линейного сканирования. Для небольших наборов (возможно, 1000 точек или меньше) это будет быстрее и эффективнее с точки зрения памяти.