it-swarm.com.de

Hash-Funktion für String

Ich arbeite an einer Hash-Tabelle in der Sprache C und teste die Hash-Funktion für Zeichenfolgen.

Die erste Funktion, die ich ausprobiert habe, ist das Hinzufügen von ASCII-Code und die Verwendung von Modulo (% 100), aber ich habe beim ersten Datentest schlechte Ergebnisse erzielt: 40 Kollisionen für 130 Wörter.

Die endgültigen Eingabedaten enthalten 8 000 Wörter (es handelt sich um ein Wörterbuch, das in einer Datei gespeichert wird). Die Hash-Tabelle wird als int-Tabelle [10000] deklariert und enthält die Position des Wortes in einer txt-Datei.

Die erste Frage ist, welcher Algorithmus am besten für das Hashing von Strings geeignet ist. und wie kann man die Größe der Hash-Tabelle bestimmen?

danke im Voraus !

:-)

105
lilawood

Ich hatte gute Ergebnisse mit djb2 von 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;
}
160
cnicutar

Zunächst möchten Sie im Allgemeinen nicht einen kryptografischen Hash für eine Hash-Tabelle verwenden. Ein Algorithmus, der nach kryptografischen Maßstäben sehr schnell ist, ist nach Hash-Tabellen-Maßstäben immer noch unerträglich langsam.

Zweitens möchten Sie sicherstellen, dass jedes Bit der Eingabe das Ergebnis beeinflussen kann/wird. Eine einfache Möglichkeit besteht darin, das aktuelle Ergebnis um eine bestimmte Anzahl von Bits zu drehen und dann XOR den aktuellen Hash-Code mit dem aktuellen Byte zu kopieren Sie wollen im Allgemeinen nicht, dass die Rotation ein gerades Vielfaches der Bytegröße ist.

Wenn Sie beispielsweise den üblichen Fall von 8-Bit-Bytes annehmen, können Sie sich um 5 Bits drehen:

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

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

Bearbeiten: Beachten Sie auch, dass 10000 Slots selten eine gute Wahl für eine Hash-Tabellengröße sind. In der Regel möchten Sie eines von zwei Dingen: Sie möchten entweder eine Primzahl als Größe (erforderlich, um die Korrektheit bei bestimmten Arten von Hash-Auflösungen sicherzustellen) oder eine Potenz von 2 (sodass der Wert mit einem einfachen Verfahren auf den richtigen Bereich reduziert werden kann Bitmaske).

20
Jerry Coffin

Es gibt eine Reihe vorhandener Hashtable-Implementierungen für C, von der C-Standardbibliothek hcreate/hdestroy/hsearch bis zu denen in APR und glib , die auch vorgefertigte Hash-Funktionen bieten. Ich würde dringend empfehlen, diese zu verwenden, anstatt Ihre eigene Hash-Tabelle oder Hash-Funktion zu erfinden. Sie wurden stark für gängige Anwendungsfälle optimiert.

Wenn Ihr Datensatz jedoch statisch ist, ist es wahrscheinlich die beste Lösung, einen perfekten Hash zu verwenden. gperf generiert einen perfekten Hash für einen bestimmten Datensatz.

8
Nick Johnson

Wikipedia zeigt eine nette String-Hash-Funktion namens Jenkins One At A Time Hash. Es werden auch verbesserte Versionen dieses Hashes zitiert.

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

Obwohl [djb2 , wie von cnicutar auf stackoverflow vorgestellt , mit ziemlicher Sicherheit besser ist, denke ich, dass es sich lohnt, auch die K & R -Hashes zu zeigen :

1) Anscheinend ein schrecklicher Hash-Algorithmus, wie er in K & R 1st Edition vorgestellt wird ( source )

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

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

    return hash;
}

2) Wahrscheinlich ein ziemlich anständiger Hash-Algorithmus, wie er in K & R Version 2 vorgestellt wird (von mir auf S. 144 des Buches verifiziert); Hinweis: Entfernen Sie unbedingt % HASHSIZE Aus der return-Anweisung, wenn Sie den Modul an Ihre Array-Länge anpassen möchten, der außerhalb des Hash-Algorithmus liegt. Außerdem empfehle ich Ihnen, die Rückgabe und den "Hashval" -Typ unsigned long Anstelle des einfachen unsigned (int) einzugeben.

unsigned hash(char *s)
{
    unsigned hashval;

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

Beachten Sie, dass aus den beiden Algorithmen klar hervorgeht, dass der Hash der 1. Ausgabe unter anderem deshalb so schrecklich ist, weil er NICHT das Zeichen order berücksichtigt, also hash("ab") würde daher den gleichen Wert wie hash("ba") zurückgeben. Dies ist jedoch nicht mit dem Hash der 2. Edition, der (viel besser!) Zwei verschiedene Werte für diese Zeichenfolgen zurückgeben würde.

Die GCC C++ 11-Hashing-Funktionen für unordered_map (eine Hash-Tabellenvorlage) und unordered_set (eine Hash-Set-Vorlage) sieht folgendermaßen aus:

Code:

// 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
Gabriel Staples

Ich habe diese Hash-Funktionen ausprobiert und das folgende Ergebnis erhalten. Ich habe ungefähr 960 ^ 3 Einträge, jeweils 64 Bytes lang, 64 Zeichen in unterschiedlicher Reihenfolge, Hash-Wert 32bit. Codes von hier .

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

Eine seltsame Sache ist, dass fast alle Hash-Funktionen eine Kollisionsrate von 6% für meine Daten haben.

2
Xiaoning Bian

Erstens sind 40 Kollisionen für 130 Wörter auf 0..99 schlecht gehasht? Sie können nicht mit perfektem Hashing rechnen, wenn Sie keine konkreten Schritte unternehmen, um dies zu erreichen. Eine gewöhnliche Hash-Funktion hat die meiste Zeit nicht weniger Kollisionen als ein Zufallsgenerator.

Eine Hash-Funktion mit gutem Ruf ist MurmurHash .

Was schließlich die Größe der Hash-Tabelle anbelangt, hängt es wirklich davon ab, welche Art von Hash-Tabelle Sie im Auge haben, insbesondere, ob die Buckets erweiterbar sind oder nur einen Steckplatz haben. Wenn Buckets erweiterbar sind, haben Sie erneut die Wahl: Sie wählen die durchschnittliche Bucket-Länge für die von Ihnen festgelegten Speicher-/Geschwindigkeitsbeschränkungen.

2
Pascal Cuoq

Eine Sache, die ich mit guten Ergebnissen verwendet habe, ist die folgende (ich weiß nicht, ob es bereits erwähnt wurde, weil ich mich nicht an seinen Namen erinnern kann).

Sie berechnen vorab eine Tabelle T mit einer Zufallszahl für jedes Zeichen im Alphabet Ihres Schlüssels [0,255]. Sie kreuzen Ihren Schlüssel 'k0 k1 k2 ... kN', indem Sie T [k0] xoder T [k1] xoder ... xoder T [kN] nehmen. Sie können leicht zeigen, dass dies so zufällig ist wie Ihr Zufallszahlengenerator und dass es rechnerisch sehr machbar ist. Wenn Sie wirklich auf eine sehr schlechte Instanz mit vielen Kollisionen stoßen, können Sie das Ganze einfach mit einer neuen Charge von Zufallszahlen wiederholen.

0
Michael Nett