Warum ist eine (kollisionsfreie) Hashtabellen-Suche wirklich O (1)?


10

Haftungsausschluss: Ich weiß, dass es hier und bei Stackoverflow bereits ähnlich klingende Fragen gibt. Aber es geht nur um Kollisionen, was ich nicht verlange.

Meine Frage ist: Warum ist kollisions weniger Nachschlagen O(1)in erster Linie?

Nehmen wir an, ich habe diese Hashtabelle:

Hash  Content
-------------
ghdjg Data1
hgdzs Data2
eruit Data3
xcnvb Data4
mkwer Data5
rtzww Data6

Jetzt suche ich nach dem Schlüssel, kden die Hash-Funktion h(k)gibt h(k) = mkwer. Aber woher "weiß" die Suche, dass sich der Hash mkwerauf Position 5 befindet? Warum muss es nicht durch alle Tasten scrollen O(n), um es zu finden? Die Hashes können keine echten Hardwareadressen sein, da ich die Fähigkeit verlieren würde, die Daten zu verschieben. Und soweit ich weiß, ist die Hashtabelle nicht nach den Hashes sortiert (selbst wenn dies der Fall wäre, würde die Suche auch dauern O(log n))?

Wie hilft es, einen Hash zu kennen, um den richtigen Platz in der Tabelle zu finden?

Antworten:


24

Die Hash-Funktion gibt keine Zeichenfolge wie z mkwer. Es gibt direkt die Position des Elements im Array zurück. Wenn Ihre Hash-Tabelle beispielsweise zehn Einträge enthält, gibt die Hash-Funktion eine Ganzzahl im Bereich von 0 bis 9 zurück.


1
Vielen Dank. :) Mein Fehler war, an eine Hashtable-Hash-Funktion wie MD5 oder SHA zu denken. Aber ein Hash kann natürlich eine ganzzahlige Position sein, über die ich nicht nachgedacht habe. Jetzt, da ich weiß, wonach ich suchen muss, habe ich sogar schnell ein gutes Beispiel gefunden: die Hash-Funktion von PHP: github.com/php/php-src/blob/PHP-5.6.10/Zend/zend_hash.h#L237
Foo Bar

13
@FooBar: MD5 und SHA berechnen auch einzelne Zahlen aus der Eingabe. Es ist nur so üblich, über die Hashes in Hex-Form zu sprechen. Genau wie Speicheradressen werden sie selten dezimal betrachtet.
nperson325681

4
Außerdem sind MD5 usw. zu lang, um direkt als Array-Index verwendet zu werden. Es wäre möglich, einen Teil des Hashs wie die unteren n Bits zu verwenden.
Chirlu

6

Die Hash-Funktion berechnet die Array-Position aus der angegebenen Zeichenfolge . Wenn dies ein perfekter Hash ist, bedeutet dies, dass es mit Sicherheit keine Kollisionen gibt. Das Array ist höchstwahrscheinlich mindestens doppelt so groß wie die Anzahl der Elemente.


x=0;
x=xmod52

Dieser sehr einfache Hash (begrenzt und anfällig für Kollisionen) unterscheidet sich von anderen Hashes im Hashing-Mechanismus und berücksichtigt keine gegebenen Eingaben. In einem fortgeschritteneren Schema ist der Hash eine größere Anzahl, angepasst an die Anzahl der Elemente. Für alle Eingaben wird ein perfekter Hash generiert, um keine Kollisionen zu gewährleisten.

O(1)

h(k)

nthn(sizeofelement)


1
Und woher weiß die Suche, wo in der Tabelle der Hash ist? Es ist weder bestellt noch Hardware-Adressen.
Foo Bar

h("xcnvb")=8

Es wird jedoch nicht jeder Index gefüllt. Wenn ich Hash 1, 4, 8, 90 und 223 mit Daten gefüllt habe, wie findet eine Suche den richtigen Ort? In diesem Fall befindet sich der Index "90" an Position 4, da die meisten anderen Indizes nicht existieren. Und eine leere Hashtabelle ist nicht unendlich groß und hat alle möglichen Positionen!?
Foo Bar

HaHa(h("xcnvb"))=Ha[90]

Die Hash-Funktion gibt keinen Index in das Array zurück. Stattdessen wird eine vorhersagbare Zahl zurückgegeben, die dem Array zugeordnet werden kann. Dies geschieht normalerweise mit dem Moduloperator mit der Anzahl der Hash-Tabellen-Buckets als anderem Operanden.
Christopher Schultz

3

Um die Antwort von David Richerby zu erweitern, ist der Begriff " Hash-Funktion " etwas überladen. Wenn wir über eine Hash-Funktion sprechen, denken wir oft an MD5, SHA-1 oder etwas wie Javas .hashCode()Methode, die einige Eingaben in eine einzelne Zahl umwandelt. Es ist jedoch sehr unwahrscheinlich, dass die Domäne dieser Nummer (dh der Maximalwert) dieselbe Größe hat wie die Hashtabelle, in der Sie Daten speichern möchten (MD5 ist 16 Byte, SHA-1 ist 20 Byte und .hashCode()ist int-4 Bytes).

Ihre Frage bezieht sich also auf diesen nächsten Schritt: Wenn wir eine Hash-Funktion haben, die beliebige Eingaben auf Zahlen abbilden kann, wie fügen wir sie in eine Datenstruktur einer bestimmten Größe ein? Mit einer anderen Funktion, auch "Hash-Funktion" genannt!

Ein triviales Beispiel für eine solche Funktion ist Modulo ; Mit Modulo können Sie einem bestimmten Index in einem Array auf einfache Weise eine beliebige Anzahl beliebiger Größen zuordnen. Dies wird in CLRS als "Teilungsmethode" eingeführt:

kmkm

h(k)=km

...

mmm=2ph(k)pk

~ Einführung in Algorithmen, §11.3.1 - CLRS

m

Java HashMapverwendet eine modifizierte Version der Divisionsmethode, die einen Vorverarbeitungsschritt ausführt, um schwache .hashCode()Implementierungen zu berücksichtigen , sodass Arrays mit einer Potenz von zwei verwendet werden können. Sie können genau sehen, was in der .getEntry()Methode passiert (Kommentare sind meine):

 // hash() transforms key.hashCode() to protect against bad hash functions
 int hash = (key == null) ? 0 : hash(key.hashCode());
 // indexOf() converts the resulting hash to a value between 0 and table.length-1
 for (Entry<K,V> e = table[indexFor(hash, table.length)];
     ...

Java 8 brachte eine Umschreibung mit sich, HashMapdie noch schneller, aber etwas schwerer zu lesen ist. Es verwendet jedoch das gleiche allgemeine Prinzip für die Indexsuche.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.