Хэш-функция для строки
Я работаю над хэш-таблицей на языке C, и я тестирую хэш-функцию для строки.
Первая функция, которую я пробовал, - добавить код ascii и использовать modulo (% 100), но я получил плохие результаты при первом тестировании данных: 40 столкновений для 130 слов.
Конечные входные данные будут содержать 8 000 слов (это хранилище dictionnary в файле). Хэш-таблица объявляется как int table [10000] и содержит положение слова в txt файле.
Первый вопрос - это лучший алгоритм для хеширования? и как определить размер хеш-таблицы?
заблаговременно!
: -)
Ответы
Ответ 1
У меня были хорошие результаты с djb2
от Dan Bernstein.
unsigned long
hash(unsigned char *str)
{
unsigned long hash = 5381;
int c;
while (c = *str++)
hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
return hash;
}
Ответ 2
Во-первых, вы вообще не хотите использовать криптографический хеш для хеш-таблицы. Алгоритм, очень быстрый по криптографическим стандартам, по-прежнему мучительно медленный по стандартам хеш-таблицы.
Во-вторых, вы хотите, чтобы каждый бит ввода мог/повлиял на результат. Один простой способ сделать это - повернуть текущий результат на некоторое количество бит, затем XOR - текущий хэш-код с текущим байтом. Повторяйте, пока не дойдете до конца строки. Обратите внимание, что вы, как правило, не хотите, чтобы вращение было даже кратным размеру байта.
Например, если предположить общий случай 8-битных байтов, вы можете повернуть на 5 бит:
int hash(char const *input) {
int result = 0x55555555;
while (*input) {
result ^= *input++;
result = rol(result, 5);
}
}
Изменить: Также обратите внимание, что 10000 слотов редко являются хорошим выбором для размера хэш-таблицы. Обычно вам нужна одна из двух вещей: вам нужно либо простое число, сколько размер (необходимый для обеспечения правильности с некоторыми типами хеш-разрешения), или же мощность 2 (поэтому уменьшение значения до правильного диапазона может быть выполнено с помощью простого битовая маска).
Ответ 3
Существует ряд существующих реализаций хэш-таблицы для C, из стандартной библиотеки c hcreate/hdestroy/hsearch C, для тех, что указаны в APR и glib, которые также предоставляют предварительно построенные хэш-функции. Я бы настоятельно рекомендовал использовать их, а не изобретать собственную хеш-таблицу или хэш-функцию; они были оптимизированы в основном для обычных случаев использования.
Если ваш набор данных статичен, тем не менее, лучшим решением является использование идеального хэша. gperf создаст идеальный хэш для данного набора данных.
Ответ 4
Википедия показывает прекрасную хэш-функцию, называемую Jenkins One At A Hash. Он также цитирует улучшенные версии этого хэша.
uint32_t jenkins_one_at_a_time_hash(char *key, size_t len)
{
uint32_t hash, i;
for(hash = i = 0; i < len; ++i)
{
hash += key[i];
hash += (hash << 10);
hash ^= (hash >> 6);
}
hash += (hash << 3);
hash ^= (hash >> 11);
hash += (hash << 15);
return hash;
}
Ответ 5
Во-первых, 40 столкновений за 130 слов хэшированных до 0..99 плохо? Вы не можете ожидать идеального хеширования, если не предпринимаете особых шагов, чтобы это произошло. Обычная хеш-функция не будет иметь меньше коллизий, чем случайный генератор большую часть времени.
Хеш-функция с хорошей репутацией MurmurHash3.
Наконец, что касается размера хеш-таблицы, это действительно зависит от того, какую хэш-таблицу вы имеете в виду, особенно, будь то ведра растяжимые или однослотовые. Если ведра расширяемы, снова есть выбор: вы выбираете среднюю длину ведра для ограничений памяти/скорости, которые у вас есть.
Ответ 6
Я пробовал эти хэш-функции и получил следующий результат. У меня около 960 ^ 3 записей, каждый длиной 64 байта, 64 символа в другом порядке, хэш-значение 32 бит. Коды из здесь.
Hash function | collision rate | how many minutes to finish
MurmurHash3 | 6.?% | 4m15s
Jenkins One.. | 6.1% | 6m54s
Bob, 1st in link| 6.16% | 5m34s
SuperFastHash | 10% | 4m58s
bernstein | 20% | 14s only finish 1/20
one_at_a_time | 6.16% | 7m5s
crc | 6.16% | 7m56s
Одна странная вещь заключается в том, что почти все хеш-функции имеют 6% -ную скорость столкновения для моих данных.
Ответ 7
Хотя djb2
, как fooobar.com/questions/49745/..., является почти наверняка лучше, я думаю, что стоит показать K & R хеши тоже:
1) По-видимому, ужасный алгоритм хеширования, представленный в K & R 1st edition (источник)
unsigned long hash(unsigned char *str)
{
unsigned int hash = 0;
int c;
while (c = *str++)
hash += c;
return hash;
}
2) Вероятно, довольно приличный алгоритм хеширования, представленный в версии K & R версии 2 (проверен мной на стр. 144 книги); Примечание. Обязательно удалите % HASHSIZE
из оператора return, если вы планируете выполнять размер модуля по размеру вашего массива вне хеш-алгоритма. Кроме того, я рекомендую вам сделать возврат и тип "hashval" unsigned long
вместо простого unsigned
(int).
unsigned hash(char *s)
{
unsigned hashval;
for (hashval = 0; *s != '\0'; s++)
hashval = *s + 31*hashval;
return hashval % HASHSIZE;
}
Обратите внимание, что из двух алгоритмов ясно, что одна из причин, почему хеш 1-го издания настолько ужасен, состоит в том, что он не учитывает строковый порядок символов, поэтому hash("ab")
будет возвращать то же значение, что и hash("ba")
. Однако это не так с хешем второго издания, который (намного лучше!) Возвращает два разных значения для этих строк.
Функции хеширования GCC С++ 11, используемые для unordered_map
(шаблон хэш-таблицы) и unordered_set
(шаблон набора хэшей) выглядит следующим образом.
Код:
// Implementation of Murmur hash for 32-bit size_t.
size_t _Hash_bytes(const void* ptr, size_t len, size_t seed)
{
const size_t m = 0x5bd1e995;
size_t hash = seed ^ len;
const char* buf = static_cast<const char*>(ptr);
// Mix 4 bytes at a time into the hash.
while (len >= 4)
{
size_t k = unaligned_load(buf);
k *= m;
k ^= k >> 24;
k *= m;
hash *= m;
hash ^= k;
buf += 4;
len -= 4;
}
// Handle the last few bytes of the input array.
switch (len)
{
case 3:
hash ^= static_cast<unsigned char>(buf[2]) << 16;
[[gnu::fallthrough]];
case 2:
hash ^= static_cast<unsigned char>(buf[1]) << 8;
[[gnu::fallthrough]];
case 1:
hash ^= static_cast<unsigned char>(buf[0]);
hash *= m;
};
// Do a few final mixes of the hash.
hash ^= hash >> 13;
hash *= m;
hash ^= hash >> 15;
return hash;
}
Ответ 8
djb2 имеет 317 коллизий для этого 466k английского словаря, в то время как MurmurHash не имеет ни одного для 64-битных хэшей, и 21 для 32-битных хэшей (около 25 следует ожидать для 466k случайных 32-битных хэшей).
Я рекомендую использовать MurmurHash, если он доступен, это очень быстро, потому что он занимает несколько байтов за раз. Но если вам нужна простая и короткая хэш-функция для копирования и вставки в ваш проект, я бы порекомендовал использовать помехи поочередно:
uint32_t inline MurmurOAAT32 ( const char * key)
{
uint32_t h(3323198485ul);
for (;*key;++key) {
h ^= *key;
h *= 0x5bd1e995;
h ^= h >> 15;
}
return h;
}
uint64_t inline MurmurOAAT64 ( const char * key)
{
uint64_t h(525201411107845655ull);
for (;*key;++key) {
h ^= *key;
h *= 0x5bd1e9955bd1e995;
h ^= h >> 47;
}
return h;
}
Оптимальный размер хеш-таблицы - короче говоря - максимально большой, но при этом он умещается в памяти. Поскольку мы обычно не знаем или не хотим искать, сколько памяти у нас есть, и она может даже измениться, оптимальный размер хеш-таблицы примерно в 2 раза превышает ожидаемое количество элементов, которые будут храниться в таблице. Выделение намного большего, чем это, сделает вашу хэш-таблицу быстрее, но при быстром уменьшении доходности, уменьшит вашу хэш-таблицу по сравнению с ней, что сделает ее экспоненциально медленной. Это связано с тем, что существует нелинейный компромисс между пространственной и временной сложностью для хеш-таблиц с оптимальным коэффициентом загрузки 2-sqrt (2) = 0,58... очевидно.
Ответ 9
Одна вещь, которую я использовал с хорошими результатами, - это следующее (я не знаю, упоминалось ли это, потому что я не могу запомнить его имя).
Вы прекомпилируете таблицу T со случайным числом для каждого символа в вашем ключевом алфавите [0,255]. У вас есть ваш ключ "k0 k1 k2... kN", взяв T [k0] xor T [k1] xor... xor T [kN]. Вы можете легко показать, что это так же важно, как ваш генератор случайных чисел, и его вычислительно очень возможно, и если вы действительно столкнулись с очень плохим экземпляром с большим количеством столкновений, вы можете просто повторить все это, используя новую порцию случайных чисел.