web-dev-qa-db-de.com

Haben Sie eine gute Hash-Funktion für eine C++ - Hash-Tabelle?

Ich benötige eine leistungsorientierte Implementierung von Hash-Funktionen in C++ für eine Hash-Tabelle, die ich codieren werde. Ich habe mich bereits umgesehen und nur Fragen gefunden, die mich fragten, was eine gute Hash-Funktion "im Allgemeinen" ist. Ich habe über CRC32 (aber wo finde ich eine gute Implementierung?) Und ein paar Kryptografiealgorithmen nachgedacht. Mein Tisch hat jedoch sehr spezifische Anforderungen.

So wird der Tisch aussehen:

100,000 items max
200,000 capacity (so the load is 0.5)
hashing a 6-character string which is a part of English sentence
     examples: "become"    "and he"    ", not "

Die Priorität Nummer eins meiner Hash-Tabelle ist die schnelle Suche (Abruf). Das schnelle Einfügen ist nicht wichtig, geht aber mit einer schnellen Suche einher. Das Löschen ist nicht wichtig und das erneute Hashing ist nichts, worüber ich nachdenken werde. Zur Behandlung von Kollisionen verwende ich wahrscheinlich separate Verkettung wie beschrieben hier . Ich habe mir diesen Artikel bereits angeschaut, hätte aber gerne eine Meinung von denen, die diese Aufgabe schon einmal erledigt haben.

33
DV.

Nun, Sie wollen einen Hash und wollen etwas flink schnell das würde in Ihrem Fall funktionieren, weil Ihre Zeichenketten nur 6 Zeichen lang sind und Sie diese Magie verwenden könnten:

size_t precision = 2; //change the precision with this
size_t hash(const char* str)
{
   return (*(size_t*)str)>> precision;
}

CRC ist für Slowpokes;)

Erläuterung: Dies funktioniert, indem der Inhalt des Zeichenfolgenzeigers in "size_t" (int32 oder int64 basierend auf der optimalen Übereinstimmung für Ihre Hardware) umgewandelt wird. Der Inhalt des Strings wird also als reine Zahl interpretiert, keine Sorge mehr über Zeichen und Sie verschieben dann die Genauigkeit, die erforderlich ist (Sie ändern diese Zahl auf die beste Leistung. Ich habe festgestellt, dass 2 für das Hashing von Strings gut geeignet ist Set von einigen Tausenden). 

Der wirklich ordentliche Teil ist, dass jeder anständige Compiler auf moderner Hardware eine Zeichenfolge wie diese in einer Assembly-Anweisung hashhhhhhhhhh.

24
Robert Gould

Dieses einfache Polynom funktioniert überraschend gut. Ich habe es von Paul Larson von Microsoft Research erhalten, der eine Vielzahl von Hash-Funktionen und Hash-Multiplikatoren studierte.

unsigned hash(const char* s, unsigned salt)
{
    unsigned h = salt;
    while (*s)
        h = h * 101 + (unsigned) *s++;
    return h;
}

salt sollte auf einen bestimmten zufälligen gewählten Wert initialisiert werden, bevor die Hashtabelle erstellt wird, um gegen Angriffe gegen Hashtabellen zu schützen . Wenn dies kein Problem für Sie ist, verwenden Sie einfach 0.

Die Größe der Tabelle ist auch wichtig, um Kollisionen zu minimieren. Klingt wie deine, ist gut.

13

Boost.Functional/Hash könnte für Sie von Nutzen sein. Ich habe es nicht ausprobiert, daher kann ich nicht für seine Leistung bürgen.

Boost hat auch eine CRC-Bibliothek .

Ich würde zuerst einen Boost.Unordered suchen (d. H. Boost :: unordered_map <>). Es verwendet Hash-Maps anstelle von binären Bäumen für Container.

Ich glaube, einige STL-Implementierungen haben einen Hash_map-Container im stdext-Namespace.

6
Ferruccio

Die Größe Ihrer Tabelle bestimmt, welche Größe der Hash verwenden soll. Sie möchten natürlich Kollisionen minimieren. Ich bin nicht sicher, was Sie für die maximale Anzahl von Elementen und die Kapazität angeben (sie scheinen mir das Gleiche zu sein). In jedem Fall deutet eine dieser Zahlen darauf hin, dass ein 32-Bit-Hash ausreichend wäre. Sie könnten mit CRC16 (~ 65.000 Möglichkeiten) davonkommen, aber Sie hätten wahrscheinlich eine Menge Kollisionen zu bewältigen. Andererseits ist eine Kollision möglicherweise schneller zu bewältigen als ein CRC32-Hash.

Ich würde sagen, gehe mit CRC32. Sie werden keinen Mangel an Dokumentation und Beispielcode finden. Da Sie Ihr Maximum herausgefunden haben und Geschwindigkeit eine Priorität hat, gehen Sie mit einer Reihe von Zeigern. Verwenden Sie den Hash, um einen Index zu generieren. Erhöhen Sie bei einer Kollision den Index, bis Sie einen leeren Behälter treffen. Schnell und einfach.

4
Arnold Spence

Da Sie englische Wörter speichern, bestehen die meisten Ihrer Zeichen aus Buchstaben, und die höchstwertigen zwei Bits Ihrer Daten werden nicht viel variieren. Abgesehen davon würde ich es sehr einfach halten, nur mit XOR. Schließlich suchen Sie nicht nach kryptografischer Stärke, sondern nur nach einer relativ gleichmäßigen Verteilung. Etwas in dieser Richtung:

size_t hash(const std::string &data) {
  size_t h(0);
  for (int i=0; i<data.length(); i++)
    h = (h << 6) ^ (h >> 26) ^ data[i];
  }
  return h;
}

Haben Sie außerdem std :: tr1 :: hash als Hashfunktion und/oder std :: tr1 :: unordered_map als Implementierung einer Hashtabelle betrachtet? Die Verwendung dieser Elemente würde wahrscheinlich viel Arbeit abwenden, anstatt eigene Klassen zu implementieren.

4
sth

Wenn Sie kurze Zeichenfolgen suchen müssen und das Einfügen kein Problem darstellt, können Sie möglicherweise einen B-Baum oder einen 2-3-Baum verwenden. In diesem Fall können Sie durch das Hashing nicht viel gewinnen.

Die Art und Weise, wie Sie dies tun würden, ist, einen Buchstaben in jeden Knoten einzufügen, so dass Sie zuerst nach dem Knoten "a" suchen und dann "a" s Kinder für "p" und ihre Kinder für "p" und dann " l "und dann" e ". In Situationen, in denen Sie "Apple" und "Anwenden" haben, müssen Sie bis zum letzten Knoten suchen (da der einzige Unterschied in den letzten "e" und "y" besteht).

Aber in den meisten Fällen können Sie das Wort bereits nach wenigen Schritten erhalten ("Xylophon" => "x" -> "Ylophon"), so dass Sie dies optimieren können. Dies kann schneller als Hashing sein

2
Robert Gould

Die oberste Priorität meiner Hashtabelle ist die Schnellsuche (Abruf).

Dann verwenden Sie die richtige Datenstruktur, da die Suche in einer Hash-Tabelle O (1) ist! :)

Der CRC32 sollte gut funktionieren. Die Implementierung ist nicht so komplex, sie basiert hauptsächlich auf XORs. Stellen Sie nur sicher, dass ein gutes Polynom verwendet wird.

2
Bob Somers

Wie wäre es mit etwas Einfachem:

// Initialize hash lookup so that it maps the characters
// in your string to integers between 0 and 31
int hashLookup[256];

// Hash function for six character strings.
int hash(const char *str)
{
    int ret = 0, mult = 1;
    for (const char *p = str; *p; *p++, mult *= 32) {
        assert(*p >= 0 && *p < 256);
        ret += mult * hashLookup[*p];
    }

    return ret;
}

Dies setzt 32-Bit-Werte voraus. Es werden 5 Bits pro Zeichen verwendet, sodass der Hashwert nur 30 Bits enthält. Sie könnten dies vielleicht beheben, indem Sie sechs Bits für das erste oder zwei Zeichen generieren. Wenn Ihr Zeichensatz klein genug ist, benötigen Sie möglicherweise nicht mehr als 30 Bits.

2
David Norman

Seit C++ 11 stellt C++ eine std::hash< string >( string ) bereit. Dies ist wahrscheinlich eine effiziente Hash-Funktion, die eine gute Verteilung von Hash-Codes für die meisten Zeichenketten bereitstellt.

Wenn Sie darüber nachdenken, eine Hash-Tabelle zu implementieren, sollten Sie jetzt auch die Verwendung von C++ std::unordered_map in Betracht ziehen.

0
Raedwald