Vom Hashing spricht man, wenn Daten in
einem Speicherbereich mit direktem Zugriff (der Hash-Tabelle) gespeichert
werden und auf jedes gespeicherte Datenelement direkt zugegriffen wird.
Dieser direkte Zugriff ist nur über einen Index möglich. Doch man verwaltet die
Indizes nicht, wie üblich, in einer eigenen Tabelle, sondern errechnet den
Index eines Datensatzes direkt aus seinem Schlüssel.
Die Hash-Tabelle ist jener Speicherbereich, über den die Datensätze verteilt werden sollen. Dabei stellt sich die Frage, wie groß die Tabelle denn sein soll.
In diesem Zusammenhang ist der Begriff
des Belegungsfaktors zu erwähnen:
Belegungsfaktor = Anzahl der Datensätze / Anzahl der Speicherplätze
Der Belegungsfaktor sollte zwischen 0,5 und 0,8 liegen.
Die
Hash-Funktion ist eine mathematische Funktion, die aus dem Schlüssel eines
Datensatzes einen Index für die Hash-Tabelle errechnet.
Diese Funktion soll so gewählt werden, daß
Wenn der Schlüssel eines Datensatzes eine
Zahl ist, so wird als Hash-Funktion meist das Divisionsrestverfahren bzw. eine
etwas modifizierte Variante davon angewendet:
H(K) = K mod M
H(K) ist die Hash-Funktion, K der Schlüsselwert und M die Größe des Arrays.
Um die Kollisionsbearbeitung (siehe weiter unten) zu erleichtern, sollte für M
nach Möglichkeit eine Primzahl gewählt werden.
Die naheliegendste Lösung für
Zeichenketten ist wohl, die Quersumme über die ASCII-Codes der einzelnen
Zeichen zu bilden und auf das Ergebnis das Divisionsrestverfahren anzuwenden.
Weiters könnte man die ganze Zeichenkette als Zahl interpretieren und auf diese
Zahl wiederum das Divisionsrestverfahren anwenden. Der Nachteil dabei ist
allerdings, daß dabei relativ hohe Zahlen entstehen (z. B.: KARLI =
0x4B41524C49).
Um das zu verhindern, sollte man nach jedem Buchstaben die Modulu-Operation
anwenden:
A = 65, I = 73, K = 75, L = 76, R = 82
Größe der Hash-Tabelle = 499
K: 75 mod 499 = 75
A: (75 * 256 + 65) mod 499 = 303
R: (303 * 256 + 82) mod 499 = 305
L: (305 * 256 + 76) mod 499 = 312
I: (312 * 256 + 73) mod 499 = 105
Der Datensatz mit dem Schlüsselwert 'KARLI' wird somit in der
Hash-Tabelle am Speicherplatz 105 gespeichert.
Beispiel für Implementierung:
Hinweis: 'key' ist der
Schlüsselbegriff, 'm' die Größe der Hash-Tabelle.
Beispiel:
Schlüsselwerte sind Ganzzahlen, die Hash-Tabelle umfaßt 499 Speicherplätze.
Es soll das Divisionsrestverfahren angewendet werden.
Zu speichernde Werte: 3598, 6093.
3598 mod 499 = 105
6093 mod 499 = 105
Der Datensatz 3598 kann zwar am Speicherplatz 105 abgelegt werden, beim
Speichern des Satzes 6093 ergibt sich allerdings das Problem, daß der errechnete
Speicherplatz nicht mehr frei ist.
Es ist ein Kollisionsverfahren anzuwenden. Das ist eine Methode, um Schlüssel
mit gleichem Ergebnis der Hash-Funktion an anderen Stellen abzulegen und wieder
zu finden.
Für das Element wird ein Platz innerhalb
der Tabelle gesucht, z. B. wird der nächste bzw. übernächste Platz genommen.
Der Nachteil dabei ist die Clusterbildung.
Die Tabelle enthält neben dem Datensatz selbst auch die Anzahl der Datensätze,
deren berechneter Hashcode dem tatsächlichen Index an dieser Position
entspricht (z. B. an Position 4 wird die Anzahl der Sätze gespeichert, für die
als Hashcode 4 errechnet wurde).
Man benötigt außerdem einen speziellen Wert, der anzeigt, daü eine Position in
der Hash-Tabelle 'frei' ist, z. B. bei Strings einen Leerstring oder
-1 bei Zahlen (Voraussetzung ist natürlich, daß diese Werte nicht als Schlüssel
auftreten können).
Der Vorteil gegenüber dem externen Verfahren ist die schnelle Positionierung.
Allerdings wird die Kapazität der Hash-Tabelle bei geringer Auslastung schlecht
genutzt.
Durch oftmaliges Löschen und Einfügen von Datensätzen kann sich beim internen
Verfahren die Anzahl der Zugriffe stark erhöhen.
Beispiel:
Schlüsselwerte sind Zeichenketten.
H('Carmen') = 2, H('Renate') = 6, H('Tina') = 4,
H('Veronika') = 2
Kollisionsverfahren: Neuer Hashcode = Alter Hashcode + 3
Wenn Plätze nicht belegt sind, werden die Personen-ID und der berechnete Index
auf -1 gesetzt.
tatsächlicher Index |
berechneter Index |
Datensatz-ID |
Anz. Elemente mit diesem Hashcode |
|
|
|
|
|
|
Carmen |
|
|
|
|
|
|
|
Tina |
|
|
|
Veronika |
|
|
|
Renate |
|
Suche nach Datensatz 'Veronika':
Beim Löschen des Datensatzes
'Veronika' ist zu berücksichtigen, daß die Anzahl der Elemente mit
Hashcode 2 (nicht 5) um 1 vermindert werden muß (im Beispiel: Anzahl bei
'Carmen' auf 1 setzen).
Die durchschnittliche Anzahl der Zugriffe beim Suchen nach einem Datensatz, der
tatsächlich in der Tabelle existiert, berechnet sich nach folgender Formel:
0,5 + 1 / (2 * (1 - Auslastung))
Beim erfolglosen Suchen:
0,5 + 1 / (2 * (1 - Auslastung) hoch 2)
Beispiele:
Auslastung |
|
|
|
durchschn. Zugriffe bei erfolgloser Suche |
|
|
|
durchschn. Zugriffe bei erfolgreicher Suche |
|
|
|
Anstelle
der Daten enthält die Hash-Tabelle nur Zeiger auf die eigentlichen Daten. Bei
einer Kollision wird eine verkettete Liste aufgebaut.
Vorteile gegenüber dem internen Verfahren:
Nachteile gegenüber dem internen Verfahren:
Beispiel: Angabe wie oben
Index |
Anzahl |
Liste |
|
|
|
|
|
Carmen, Veronika |
|
|
|
|
|
Tina |
|
|
|
|
|
Renate |
Das
doppelte Hashing verwendet die Methode der internen Kollisionsauflösung mit dem
Ziel, Clusterbildung zu vermeiden. Das wird erreicht, indem nicht, so wie beim
linearen Austesten, bei einer Kollision immer ein konstanter Betrag addiert
wird, sondern ein vom Schlüssel abhängiger Wert.
Beispiel:
H2(K) = 1 + (K / M) mod (M - 1)
K ist der Schlüsselwert, M die Größe der Hash-Tabelle.
Bei der Division (K / M) handelt es sich um eine Ganzzahldivision.
Beispiel:
Die Hash-Tabelle bietet Platz für 11 Datensätze.
Schlüsselwerte sind Ganzzahlen ('Personen-ID').
Wenn Plätze nicht belegt sind, werden die Personen-ID und der berechnete Index
auf -1 gesetzt.
1. Hash-Funktion: H(K) = K mod M
2. Hash-Funktion: H2(K) = 1 + (K / M) mod (M - 1)
tatsächlicher Index |
berechneter Index |
Personen-ID |
Name |
Anz. Elemente mit diesem Hashcode |
|
|
|
|
|
|
|
|
|
|
|
|
|
Stangl |
|
|
|
|
Meier |
|
|
|
|
Huber |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Schmidt |
|
|
|
|
Gruber |
|
tatsächlicher Index |
berechneter Index |
Personen-ID |
Name |
Anz. Elemente mit diesem Hashcode |
|
|
|
Böck |
|
|
|
|
Wagner |
|
|
|
|
Stangl |
|
|
|
|
Meier |
|
|
|
|
Huber |
|
|
|
|
Steiner |
|
|
|
|
Richter |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Schmidt |
|
|
|
|
Gruber |
|
tatsächlicher Index |
berechneter Index |
Personen-ID |
Name |
Anz. Elemente mit diesem Hashcode |
|
|
|
Böck |
|
|
|
|
Wagner |
|
|
|
|
Stangl |
|
|
|
|
Meier |
|
|
|
|
Huber |
|
|
|
|
Steiner |
|
|
|
|
Richter |
|
|
|
|
Bauer |
|
|
|
|
|
|
|
|
|
Schmidt |
|
|
|
|
Gruber |
|
Die durchschnittliche Anzahl der Zugriffe beim Suchen nach einem Datensatz, der
tatsächlich in der Tabelle existiert, berechnet sich bei diesem Verfahren nach
folgender Formel:
-ln (1 - Auslastung) / Auslastung
Beim erfolglosen Suchen:
1 / (1 - Auslastung)
Beispiele:
Auslastung |
|
|
|
durchschn. Zugriffe bei erfolgloser Suche |
|
|
|
durchschn. Zugriffe bei erfolgreicher Suche |
|
|
|
Haupt | Fügen Sie Referat | Kontakt | Impressum | Nutzungsbedingungen