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

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

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

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

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

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

: -)

8 ответов


у меня были хорошие результаты с 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;
}

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

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

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

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

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

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


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

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


Википедии показывает хорошая строковая хэш-функция, называемая Jenkins по одному хэшу за раз. Он также цитирует улучшенные версии этого хэша.

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;
}

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

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

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


Я пробовал эти хэш-функции и получил следующий результат. У меня около 960^3 записей, каждая 64 байта длиной, 64 символа в другом порядке, хэш-значение 32bit. Коды от здесь.

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% для моих данных.


хотя djb2, as представлено на stackoverflow cnicutar, почти наверняка лучше, я думаю, что стоит показать 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 книги); NB: обязательно удалите % HASHSIZE из оператора return, если вы планируете делать размер модуля для длины вашего массива вне хэш-алгоритма. Кроме того, я рекомендую вам сделать возврат и тип "hashval"unsigned long вместо простого unsigned (int).

unsigned hash(char *s)
{
    unsigned hashval;

    for (hashval = 0; *s != ''; s++)
        hashval = *s + 31*hashval;
    return hashval % HASHSIZE;
}

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

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

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