Я работаю над хеш-таблицей на языке C и тестирую хеш-функцию для строки.
Первая функция, которую я пробовал, - это добавить код ascii и использовать по модулю (% 100), но у меня плохие результаты с первым тестом данных: 40 столкновений для 130 слов.
Итоговые входные данные будут содержать 8 000 слов (словарь хранится в файле). Хеш-таблица объявлена как int table [10000] и содержит позицию слова в текстовом файле.
Первый вопрос: какой алгоритм хеширования строки лучше? а как определить размер хеш-таблицы?
заранее спасибо !
:-)
Ответы:
У меня были хорошие результаты с
djb2
Дэном Бернштейном.источник
size_t
беззнаковое или другое подобное значение (например, беззнаковое длинное число в этом коде). Абонент несет ответственность за принятие по модулю результата , чтобы вписать его в хэш - таблице. Вызывающий контролирует слот таблицы, в который хешируется; не функция. Он просто возвращает какое-то беззнаковое число.Во-первых, вы обычно не хотите использовать криптографический хеш для хеш-таблицы. Алгоритм, который очень быстр по криптографическим стандартам, все еще мучительно медленный по стандартам хэш-таблиц.
Во-вторых, вы хотите убедиться, что каждый бит ввода может повлиять на результат. Один из простых способов сделать это - повернуть текущий результат на некоторое количество бит, а затем выполнить XOR текущего хэш-кода с текущим байтом. Повторяйте, пока не дойдете до конца струны. Обратите внимание, что обычно вы также не хотите, чтобы поворот был кратен размеру байта.
Например, предполагая общий случай 8-битных байтов, вы можете повернуть на 5 бит:
Изменить: также обратите внимание, что 10000 слотов редко являются хорошим выбором для размера хеш-таблицы. Обычно вам нужно одно из двух: вы хотите либо простое число в качестве размера (требуется для обеспечения правильности с некоторыми типами разрешения хеширования), либо степень 2 (поэтому уменьшение значения до правильного диапазона может быть выполнено простым битовая маска).
источник
Википедия показывает красивую строковую хеш-функцию под названием Jenkins One At A Time Hash. Он также цитирует улучшенные версии этого хеша.
источник
Существует ряд реализаций хэш-таблиц для C, от стандартной библиотеки C hcreate / hdestroy / hsearch до тех, что находятся в APR и glib , которые также предоставляют предварительно созданные хэш-функции. Я настоятельно рекомендую использовать их, а не изобретать свою собственную хеш-таблицу или хеш-функцию; они были сильно оптимизированы для обычных случаев использования.
Однако, если ваш набор данных статичен, лучшим решением, вероятно, будет использование идеального хеша . gperf сгенерирует для вас идеальный хэш для данного набора данных.
источник
djb2 имеет 317 коллизий для этого 466k английского словаря, в то время как MurmurHash не имеет ни одного для 64-битных хэшей и 21 для 32-битных хэшей (около 25 следует ожидать для 466k случайных 32-битных хэшей). Я рекомендую использовать MurmurHash, если он доступен, это очень быстро, потому что занимает несколько байтов за раз. Но если вам нужна простая и короткая хеш-функция для копирования и вставки в ваш проект, я бы рекомендовал использовать пошаговую версию по одному байту:
Короче говоря, оптимальный размер хэш-таблицы - это как можно больший размер, но при этом он умещается в памяти. Поскольку обычно мы не знаем или не хотим узнать, сколько памяти у нас доступно, и это может даже измениться, оптимальный размер хеш-таблицы примерно в 2 раза больше ожидаемого количества элементов, которые будут храниться в таблице. Выделение гораздо большего количества сделает вашу хеш-таблицу быстрее, но с быстро убывающей отдачей, сделав вашу хеш-таблицу меньше, чем это сделает ее экспоненциально медленнее. Это связано с тем, что существует нелинейный компромисс между пространственной и временной сложностью для хеш-таблиц с оптимальным коэффициентом загрузки 2-sqrt (2) = 0,58 ... очевидно.
источник
Во-первых, 40 коллизий для 130 слов, хешированных до 0..99, плохо? Вы не можете ожидать идеального хеширования, если не предпринимаете специально для этого шаги. Обычная хеш-функция в большинстве случаев будет иметь меньше коллизий, чем случайный генератор.
Хеш-функция с хорошей репутацией - MurmurHash3 .
Наконец, что касается размера хеш-таблицы, это действительно зависит от того, какую хеш-таблицу вы имеете в виду, особенно от того, являются ли сегменты расширяемыми или однослотовыми. Если сегменты являются расширяемыми, опять же есть выбор: вы выбираете среднюю длину сегмента для имеющихся у вас ограничений памяти / скорости.
источник
n - m * (1 - ((m-1)/m)^n) = 57.075...
. 40 столкновений лучше, чем можно было ожидать случайно (от 46 до 70 при p-балле 0,999). Рассматриваемая хеш-функция более однородна, чем если бы она была случайной или мы наблюдаем очень редкое событие.Хотя то
djb2
, что представлено на stackoverflow от cnicutar , почти наверняка лучше, я думаю, стоит также показать хеши K&R :1) По-видимому, ужасный алгоритм хеширования, представленный в 1-м издании K&R ( источник )
2) Вероятно, довольно приличный алгоритм хеширования, представленный в версии 2 K&R (проверено мной на стр. 144 книги); NB: не забудьте удалить
% HASHSIZE
из оператора return, если вы планируете выполнять изменение размера модуля до длины вашего массива вне алгоритма хеширования. Также я рекомендую вам использовать тип return и hashvalunsigned long
вместо простогоunsigned
(int).Обратите внимание, что из двух алгоритмов ясно, что одна из причин, по которой хеш 1-го издания настолько ужасен, заключается в том, что он НЕ принимает во внимание порядок строковых символов ,
hash("ab")
поэтому возвращает то же значение, что иhash("ba")
. Однако это не так с хешем 2-го издания, который (намного лучше!) Возвращает два разных значения для этих строк.Функции хеширования GCC C ++ 11, используемые для
unordered_map
(шаблона хеш-таблицы) иunordered_set
(шаблона хеш-набора), выглядят следующим образом.Код:
источник
Я пробовал эти хеш-функции и получил следующий результат. У меня около 960 ^ 3 записей, каждая длиной 64 байта, 64 символа в разном порядке, хэш-значение 32 бит. Коды отсюда .
Странно то, что почти все хеш-функции имеют 6% -ную частоту конфликтов для моих данных.
источник
Одна вещь, которую я использовал с хорошими результатами, это следующее (я не знаю, упоминалось ли оно уже, потому что я не могу вспомнить его название).
Вы предварительно вычисляете таблицу T со случайным числом для каждого символа в алфавите вашего ключа [0,255]. Вы хешируете свой ключ 'k0 k1 k2 ... kN', взяв T [k0] xor T [k1] xor ... xor T [kN]. Вы можете легко показать, что это так же случайно, как и ваш генератор случайных чисел, и его вычислительно очень выполнимо, и если вы действительно столкнетесь с очень плохим экземпляром с большим количеством столкновений, вы можете просто повторить все это, используя новую партию случайных чисел.
источник