Хэш-функция для строки

Я работаю над хэш-таблицей на языке 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 (шаблон набора хэшей) выглядит следующим образом.

  • Это является частичным ответом на вопрос о том, какие используются хэш-функции GCC С++ 11, заявляя, что GCC использует реализацию "MurmurHashUnaligned2", Остин Эпплби (http://murmurhash.googlepages.com/).
  • В файле "gcc/libstdС++ - v3/libsupС++/hash_bytes.cc" здесь (https://github.com/gcc-mirror/gcc/blob/master/libstdc++-v3/libsupc++/hash_bytes.cc), я нашел реализации. Здесь, например, для возвращаемого значения "32-разрядного size_t" (вытащили 11 августа 2017 года):

Код:

// 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]. Вы можете легко показать, что это так же важно, как ваш генератор случайных чисел, и его вычислительно очень возможно, и если вы действительно столкнулись с очень плохим экземпляром с большим количеством столкновений, вы можете просто повторить все это, используя новую порцию случайных чисел.