хеш-функция для строки - PullRequest
       29

хеш-функция для строки

104 голосов
/ 05 октября 2011

Я работаю над хэш-таблицей на языке C и тестирую хеш-функцию для строки.

Первая функция, которую я попробовал, - добавить код ascii и использовать модуль (% 100), но яу нас плохие результаты с первым тестом данных: 40 столкновений для 130 слов.

Конечные входные данные будут содержать 8 000 слов (это словари хранятся в файле).Хеш-таблица объявлена ​​как int table [10000] и содержит позицию слова в текстовом файле.

Первый вопрос: какой алгоритм является лучшим для хеширования строки?а как определить размер хеш-таблицы?

заранее спасибо!

: -)

Ответы [ 8 ]

158 голосов
/ 05 октября 2011

У меня были хорошие результаты с djb2 Дэном Бернштейном.

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;
}
20 голосов
/ 05 октября 2011

Во-первых, вы обычно не хотите использовать криптографический хеш для хеш-таблицы. Алгоритм, который очень быстрый по криптографическим стандартам, все еще мучительно медленен по стандартам хеш-таблиц.

Во-вторых, вы хотите убедиться, что каждый бит ввода может / будет влиять на результат. Один из простых способов сделать это - повернуть текущий результат на некоторое количество битов, а затем XOR текущего хеш-кода с текущим байтом. Повторяйте, пока не дойдете до конца строки. Обратите внимание, что вы обычно не хотите, чтобы вращение было даже кратным размеру байта.

Например, если принять общий случай 8-битных байтов, вы можете повернуть на 5 битов:

int hash(char const *input) { 
    int result = 0x55555555;

    while (*input) { 
        result ^= *input++;
        result = rol(result, 5);
    }
}

Редактировать: Также обратите внимание, что 10000 слотов редко являются хорошим выбором для размера хеш-таблицы. Обычно вам требуется одна из двух вещей: либо вы хотите использовать простое число в качестве размера (необходимого для обеспечения корректности при некоторых типах разрешения хеша), либо степень 2 (поэтому уменьшение значения до правильного диапазона можно сделать с помощью простого бит-маска).

8 голосов
/ 06 октября 2011

Существует ряд существующих реализаций хеш-таблиц для C, от стандартной библиотеки C hcreate / hdestroy / hsearch до тех, что в APR и glib , которые также предоставляют предварительно встроенный хэшфункции.Я настоятельно рекомендую использовать их, а не изобретать собственную хеш-таблицу или хеш-функцию;они были сильно оптимизированы для обычных случаев использования.

Если ваш набор данных статичен, тем не менее, вашим лучшим решением, вероятно, будет использование совершенного хэша . gperf создаст для вас идеальный хеш для данного набора данных.

7 голосов
/ 05 октября 2011

Википедия показывает красивую строковую хеш-функцию под названием Jenkins One At A Time 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;
}
2 голосов
/ 11 августа 2017

Хотя djb2, поскольку , представленный на стеке потока cnicutar , почти наверняка лучше, я думаю, что стоит показать и хеши K & R :

1) По-видимому, ужасный алгоритм хеширования, представленный в K & R 1st edition ( source )

unsigned long hash(unsigned char *str)
{
    unsigned int hash = 0;
    int c;

    while (c = *str++)
        hash += c;

    return hash;
}

2) Вероятно, довольно приличный алгоритм хеширования, представленный в K & R версии 2 (проверено мной на стр. 144 книги); NB: обязательно удалите % HASHSIZE из оператора return, если вы планируете использовать модуль sizing-to-your-array-length вне алгоритма хеширования. Кроме того, я рекомендую вам сделать возврат и тип "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-го издания настолько ужасен, состоит в том, что он НЕ учитывает строковый символ order , поэтому hash("ab") будет возвращать то же значение, что и hash("ba"). Это , а не , поэтому с хэшем 2-го издания, однако, который (намного лучше!) Вернет два разных значения для этих строк.

Функции хэширования GCC C ++ 11, используемые для unordered_map (шаблон хеш-таблицы) и unordered_set (шаблон хэш-набора) следующим образом.

  • Этот является частичным ответом на вопрос , какие используются хеш-функции GCC C ++ 11 , заявляя, что GCC использует реализацию MurmurHashUnaligned2, выполненную Остином Эпплби (http://murmurhash.googlepages.com/).
  • В файле "gcc / libstdc ++ - v3 / libsupc ++ / hash_bytes.cc", здесь (https://github.com/gcc-mirror/gcc/blob/master/libstdc++-v3/libsupc++/hash_bytes.cc), Я нашел реализации. Вот пример для возвращаемого значения "32-bit 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;
}
2 голосов
/ 29 июня 2017

Я попробовал эти хэш-функции и получил следующий результат. У меня есть около 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% частоту столкновений для моих данных.

2 голосов
/ 05 октября 2011

Во-первых, 40 коллизий для 130 слов, хэшированных до 0 ... 99, плохо?Вы не можете ожидать идеального хеширования, если не предпринимаете шагов специально для того, чтобы это произошло.Обычная хеш-функция в большинстве случаев не будет иметь меньше коллизий, чем генератор случайных чисел.

Хеш-функция с хорошей репутацией: MurmurHash3 .

Наконец, что касаетсяразмер хеш-таблицы, это действительно зависит от того, какую хеш-таблицу вы имеете в виду, особенно от того, являются ли сегменты расширяемыми или однослотовыми.Если сегменты расширяемы, снова есть выбор: вы выбираете среднюю длину сегментов для имеющихся у вас ограничений памяти / скорости.

0 голосов
/ 06 октября 2011

Одна вещь, которую я использовал с хорошими результатами, заключается в следующем (я не знаю, упоминалось ли оно уже, потому что я не могу вспомнить его имя).

Вы предварительно вычислили таблицу T со случайным числом для каждого символа в алфавите вашей клавиши [0,255]. Вы хешируете свой ключ 'k0 k1 k2 ... kN', взяв T [k0] xor T [k1] xor ... xor T [kN]. Вы можете легко показать, что это так же случайно, как ваш генератор случайных чисел, и его вычислительно очень выполнимо, и если вы действительно столкнетесь с очень плохим экземпляром с множеством коллизий, вы можете просто повторить все это, используя новую партию случайных чисел.

...