Hashing - Informatik 2

Hashing I
Datenstrukturen und Algorithmen
Vorlesung 12: Hashing I
Joost-Pieter Katoen
Lehrstuhl für Informatik 2
Software Modeling and Verification Group
http://moves.rwth-aachen.de/teaching/ss-15/dsal/
20. Mai 2015
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
1/37
Hashing I
Übersicht
1
Direkte Adressierung
Counting Sort
2
Grundlagen des Hashings
3
Verkettung
4
Hashfunktionen
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
2/37
Hashing I
Einführung (I)
Dictionary (Wörterbuch)
Das Dictionary (auch: Map, assoziatives Array) speichert Informationen,
die jederzeit anhand ihres Schlüssels abgerufen werden können.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
3/37
Hashing I
Einführung (I)
Dictionary (Wörterbuch)
Das Dictionary (auch: Map, assoziatives Array) speichert Informationen,
die jederzeit anhand ihres Schlüssels abgerufen werden können. Weiterhin:
I
Die Daten sind dynamisch gespeichert.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
3/37
Hashing I
Einführung (I)
Dictionary (Wörterbuch)
Das Dictionary (auch: Map, assoziatives Array) speichert Informationen,
die jederzeit anhand ihres Schlüssels abgerufen werden können. Weiterhin:
I
Die Daten sind dynamisch gespeichert.
I
Element dictSearch(Dict d, int k) gibt die in d zum Schlüssel k
gespeicherten Informationen zurück.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
3/37
Hashing I
Einführung (I)
Dictionary (Wörterbuch)
Das Dictionary (auch: Map, assoziatives Array) speichert Informationen,
die jederzeit anhand ihres Schlüssels abgerufen werden können. Weiterhin:
I
Die Daten sind dynamisch gespeichert.
I
Element dictSearch(Dict d, int k) gibt die in d zum Schlüssel k
gespeicherten Informationen zurück.
I
void dictInsert(Dict d, Element e) speichert Element e unter
seinem Schlüssel e.key in d.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
3/37
Hashing I
Einführung (I)
Dictionary (Wörterbuch)
Das Dictionary (auch: Map, assoziatives Array) speichert Informationen,
die jederzeit anhand ihres Schlüssels abgerufen werden können. Weiterhin:
I
Die Daten sind dynamisch gespeichert.
I
Element dictSearch(Dict d, int k) gibt die in d zum Schlüssel k
gespeicherten Informationen zurück.
I
void dictInsert(Dict d, Element e) speichert Element e unter
seinem Schlüssel e.key in d.
I
void dictDelete(Dict d, Element e) löscht das Element e aus d,
wobei e in d enthalten sein muss.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
3/37
Hashing I
Einführung (I)
Dictionary (Wörterbuch)
Das Dictionary (auch: Map, assoziatives Array) speichert Informationen,
die jederzeit anhand ihres Schlüssels abgerufen werden können. Weiterhin:
I
Die Daten sind dynamisch gespeichert.
I
Element dictSearch(Dict d, int k) gibt die in d zum Schlüssel k
gespeicherten Informationen zurück.
I
void dictInsert(Dict d, Element e) speichert Element e unter
seinem Schlüssel e.key in d.
I
void dictDelete(Dict d, Element e) löscht das Element e aus d,
wobei e in d enthalten sein muss.
Beispiel
Symboltabelle eines Compilers, wobei die Schlüssel Strings (etwa
Bezeichner) sind.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
3/37
Hashing I
Einführung (II)
Problem
Welche Datenstrukturen sind geeignet, um ein Dictionary zu
implementieren?
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
4/37
Hashing I
Einführung (II)
Problem
Welche Datenstrukturen sind geeignet, um ein Dictionary zu
implementieren?
I
Heap: Einfügen und Löschen sind effizient. Aber was ist mit Suche?
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
4/37
Hashing I
Einführung (II)
Problem
Welche Datenstrukturen sind geeignet, um ein Dictionary zu
implementieren?
I
Heap: Einfügen und Löschen sind effizient. Aber was ist mit Suche?
I
Sortiertes Array/Liste: Einfügen ist im Worst-Case linear.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
4/37
Hashing I
Einführung (II)
Problem
Welche Datenstrukturen sind geeignet, um ein Dictionary zu
implementieren?
I
Heap: Einfügen und Löschen sind effizient. Aber was ist mit Suche?
I
Sortiertes Array/Liste: Einfügen ist im Worst-Case linear.
I
Rot-Schwarz-Baum: Alle Operationen sind im Worst-Case
logarithmisch.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
4/37
Hashing I
Einführung (II)
Problem
Welche Datenstrukturen sind geeignet, um ein Dictionary zu
implementieren?
I
Heap: Einfügen und Löschen sind effizient. Aber was ist mit Suche?
I
Sortiertes Array/Liste: Einfügen ist im Worst-Case linear.
I
Rot-Schwarz-Baum: Alle Operationen sind im Worst-Case
logarithmisch.
Lösung
Unter realistischen Annahmen benötigt eine Hash-Tabelle im Durchschnitt
O(1) für alle Operationen.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
4/37
Hashing I
Direkte Adressierung
Übersicht
1
Direkte Adressierung
Counting Sort
2
Grundlagen des Hashings
3
Verkettung
4
Hashfunktionen
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
5/37
Hashing I
Direkte Adressierung
Direkte Adressierung (I)
Direkte Adressierung
I
Alloziere ein Array (die Direkte-Adressierungs-Tabelle), so dass es für
jeden möglichen Schlüssel eine(1) Position gibt.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
6/37
Hashing I
Direkte Adressierung
Direkte Adressierung (I)
Direkte Adressierung
I
I
Alloziere ein Array (die Direkte-Adressierungs-Tabelle), so dass es für
jeden möglichen Schlüssel eine(1) Position gibt.
Jedes Array-Element enthält einen Pointer auf die gespeicherte
Information.
I
Der Einfachheit halber vernachlässigen wir in der Vorlesung die zu den
Schlüsseln gehörenden Informationen.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
6/37
Hashing I
Direkte Adressierung
Direkte Adressierung (I)
Direkte Adressierung
I
I
Alloziere ein Array (die Direkte-Adressierungs-Tabelle), so dass es für
jeden möglichen Schlüssel eine(1) Position gibt.
Jedes Array-Element enthält einen Pointer auf die gespeicherte
Information.
I
I
Der Einfachheit halber vernachlässigen wir in der Vorlesung die zu den
Schlüsseln gehörenden Informationen.
Mit Schlüsselmenge U = {0, 1, . . . , n − 1} ergibt sich:
I
Eine Direkte-Adressierungs-Tabelle T[0..n-1], wobei T[k] zu
Schlüssel k gehört.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
6/37
Hashing I
Direkte Adressierung
Direkte Adressierung (I)
Direkte Adressierung
I
I
Alloziere ein Array (die Direkte-Adressierungs-Tabelle), so dass es für
jeden möglichen Schlüssel eine(1) Position gibt.
Jedes Array-Element enthält einen Pointer auf die gespeicherte
Information.
I
I
Der Einfachheit halber vernachlässigen wir in der Vorlesung die zu den
Schlüsseln gehörenden Informationen.
Mit Schlüsselmenge U = {0, 1, . . . , n − 1} ergibt sich:
I
I
Eine Direkte-Adressierungs-Tabelle T[0..n-1], wobei T[k] zu
Schlüssel k gehört.
datSearch(T, int k): return T[k];
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
6/37
Hashing I
Direkte Adressierung
Direkte Adressierung (I)
Direkte Adressierung
I
I
Alloziere ein Array (die Direkte-Adressierungs-Tabelle), so dass es für
jeden möglichen Schlüssel eine(1) Position gibt.
Jedes Array-Element enthält einen Pointer auf die gespeicherte
Information.
I
I
Der Einfachheit halber vernachlässigen wir in der Vorlesung die zu den
Schlüsseln gehörenden Informationen.
Mit Schlüsselmenge U = {0, 1, . . . , n − 1} ergibt sich:
I
I
I
Eine Direkte-Adressierungs-Tabelle T[0..n-1], wobei T[k] zu
Schlüssel k gehört.
datSearch(T, int k): return T[k];
datInsert(T, Element e): T[e.key] = e;
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
6/37
Hashing I
Direkte Adressierung
Direkte Adressierung (I)
Direkte Adressierung
I
I
Alloziere ein Array (die Direkte-Adressierungs-Tabelle), so dass es für
jeden möglichen Schlüssel eine(1) Position gibt.
Jedes Array-Element enthält einen Pointer auf die gespeicherte
Information.
I
I
Der Einfachheit halber vernachlässigen wir in der Vorlesung die zu den
Schlüsseln gehörenden Informationen.
Mit Schlüsselmenge U = {0, 1, . . . , n − 1} ergibt sich:
I
I
I
I
Eine Direkte-Adressierungs-Tabelle T[0..n-1], wobei T[k] zu
Schlüssel k gehört.
datSearch(T, int k): return T[k];
datInsert(T, Element e): T[e.key] = e;
datDelete(T, Element e): T[e.key] = null;
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
6/37
Hashing I
Direkte Adressierung
Direkte Adressierung (I)
Direkte Adressierung
I
I
Alloziere ein Array (die Direkte-Adressierungs-Tabelle), so dass es für
jeden möglichen Schlüssel eine(1) Position gibt.
Jedes Array-Element enthält einen Pointer auf die gespeicherte
Information.
I
I
Mit Schlüsselmenge U = {0, 1, . . . , n − 1} ergibt sich:
I
I
I
I
I
Der Einfachheit halber vernachlässigen wir in der Vorlesung die zu den
Schlüsseln gehörenden Informationen.
Eine Direkte-Adressierungs-Tabelle T[0..n-1], wobei T[k] zu
Schlüssel k gehört.
datSearch(T, int k): return T[k];
datInsert(T, Element e): T[e.key] = e;
datDelete(T, Element e): T[e.key] = null;
Die Laufzeit jeder Operation ist im Worst-Case Θ(1).
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
6/37
Hashing I
Direkte Adressierung
Direkte Adressierung (II)
Direkte-Adressierungs-Tabelle T
Schlüssel
0
U
1
0
2
4
1
6
3
K 2
4
7
5
6
3
9
5
7
8
8
9
benutzte Schlüssel
n = 10
Schlüsselmenge
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
7/37
Hashing I
Direkte Adressierung
Duplikate in Linearzeit erkennen
Alle Elemente seien ganze Zahlen zwischen 0 und k, wobei k ∈ Θ(n).
1
2
3
4
5
6
7
8
9
10
11
bool checkDuplicates(int E[n], int n, int k) {
int histogram[k] = 0; // "Direkte-Adressierungs-Tabelle"
for (int i = 0; i < n; i++) {
if (histogram[E[i]] > 0) {
return true; // Duplikat gefunden
} else {
histogram[E[i]]++; // Zähle Häufigkeit
}
}
return false; // keine Duplikate
}
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
8/37
Hashing I
Direkte Adressierung
Counting Sort – Idee
1. Berechne Häufigkeit
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
9/37
Hashing I
Direkte Adressierung
Counting Sort – Idee
1. Berechne Häufigkeit
2. Berechne „Position von x “ = „Anzahl der Elemente 6 x “
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
9/37
Hashing I
Direkte Adressierung
Counting Sort – Idee
1. Berechne Häufigkeit
2. Berechne „Position von x “ = „Anzahl der Elemente 6 x “
3. Erzeuge Ausgabearray anhand dieser neuen Positionen
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
9/37
Hashing I
Direkte Adressierung
Counting Sort
Alle Elemente seien ganze Zahlen zwischen 0 und k, wobei k ∈ Θ(n).
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
10/37
Hashing I
Direkte Adressierung
Counting Sort
Alle Elemente seien ganze Zahlen zwischen 0 und k, wobei k ∈ Θ(n).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int[n] countSort(int E[n], int n, int k) {
int histogram[k] = 0; // "Direkte-Adressierungs-Tabelle"
for (int i = 0; i < n; i++) {
histogram[E[i]]++; // Zähle Häufigkeit
}
for (int i = 1; i < k; i++) { // Berechne Position
histogram[i] = histogram[i] + histogram[i - 1];
}
// Erzeuge Ausgabe
int result[n];
for (int i = n - 1; i >= 0; i--) { // stabil: rückwärts
histogram[E[i]]--;
result[histogram[E[i]]] = E[i];
}
return result;
}
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
10/37
Hashing I
Direkte Adressierung
Counting Sort
Alle Elemente seien ganze Zahlen zwischen 0 und k, wobei k ∈ Θ(n).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int[n] countSort(int E[n], int n, int k) {
int histogram[k] = 0; // "Direkte-Adressierungs-Tabelle"
for (int i = 0; i < n; i++) {
histogram[E[i]]++; // Zähle Häufigkeit
}
for (int i = 1; i < k; i++) { // Berechne Position
histogram[i] = histogram[i] + histogram[i - 1];
}
// Erzeuge Ausgabe
int result[n];
for (int i = n - 1; i >= 0; i--) { // stabil: rückwärts
histogram[E[i]]--;
result[histogram[E[i]]] = E[i];
}
return result;
}
I
Worst-Case Zeitkomplexität: Θ(n)
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
10/37
Hashing I
Direkte Adressierung
Counting Sort: Beispiel
Eingabe 6 8 0 3 5 4 0 4
0
Joost-Pieter Katoen
1
2
3
4
5
6
7
Datenstrukturen und Algorithmen
11/37
Hashing I
Direkte Adressierung
Counting Sort: Beispiel
Eingabe 6 8 0 3 5 4 0 4
0
1
2
3
4
5
6
7
Histogramm 2 0 0 1 2 1 1 0 1
0
1
2
3
4
5
6
7
8
Ausgabe 0 0 3 4 4 5 6 8
0
Joost-Pieter Katoen
1
2
3
4
5
6
7
Datenstrukturen und Algorithmen
11/37
Hashing I
Direkte Adressierung
Counting Sort: Beispiel
Eingabe 6 8 0 3 5 4 0 4
0
1
2
3
4
5
6
7
Histogramm 2 0 0 1 2 1 1 0 1
0
1
2
3
4
5
6
7
8
Positionen 2 2 2 3 5 6 7 7 8
0
1
2
3
4
5
6
7
8
Ausgabe 0 0 3 4 4 5 6 8
0
Joost-Pieter Katoen
1
2
3
4
5
6
7
Datenstrukturen und Algorithmen
11/37
Hashing I
Direkte Adressierung
Counting Sort: Beispiel
Eingabe 6 8 0 3 5 4 0 4
0
1
2
3
4
5
6
7
Positionen 2 2 2 3 5 6 7 7 8
0
1
2
3
4
5
6
7
8
Ausgabe 0 0 3 4 4 5 6 8
0
Joost-Pieter Katoen
1
2
3
4
5
6
7
Datenstrukturen und Algorithmen
11/37
Hashing I
Direkte Adressierung
Counting Sort: Beispiel
Eingabe 6 8 0 3 5 4 0 4
0
1
2
3
4
5
6
7
Positionen 2 2 2 3 4 6 7 7 8
0
1
2
3
4
5
6
7
8
Ausgabe 0 0 3 4 4 5 6 8
0
Joost-Pieter Katoen
1
2
3
4
5
6
7
Datenstrukturen und Algorithmen
11/37
Hashing I
Direkte Adressierung
Counting Sort: Beispiel
Eingabe 6 8 0 3 5 4 0 4
0
1
2
3
4
5
6
7
Positionen 1 2 2 3 4 6 7 7 8
0
1
2
3
4
5
6
7
8
Ausgabe 0 0 3 4 4 5 6 8
0
Joost-Pieter Katoen
1
2
3
4
5
6
7
Datenstrukturen und Algorithmen
11/37
Hashing I
Direkte Adressierung
Counting Sort: Beispiel
Eingabe 6 8 0 3 5 4 0 4
0
1
2
3
4
5
6
7
Positionen 1 2 2 3 3 6 7 7 8
0
1
2
3
4
5
6
7
8
Ausgabe 0 0 3 4 4 5 6 8
0
Joost-Pieter Katoen
1
2
3
4
5
6
7
Datenstrukturen und Algorithmen
11/37
Hashing I
Direkte Adressierung
Counting Sort: Beispiel
Eingabe 6 8 0 3 5 4 0 4
0
1
2
3
4
5
6
7
Positionen 1 2 2 3 3 5 7 7 8
0
1
2
3
4
5
6
7
8
Ausgabe 0 0 3 4 4 5 6 8
0
Joost-Pieter Katoen
1
2
3
4
5
6
7
Datenstrukturen und Algorithmen
11/37
Hashing I
Direkte Adressierung
Counting Sort: Beispiel
Eingabe 6 8 0 3 5 4 0 4
0
1
2
3
4
5
6
7
Positionen 1 2 2 2 3 5 7 7 8
0
1
2
3
4
5
6
7
8
Ausgabe 0 0 3 4 4 5 6 8
0
Joost-Pieter Katoen
1
2
3
4
5
6
7
Datenstrukturen und Algorithmen
11/37
Hashing I
Direkte Adressierung
Counting Sort: Beispiel
Eingabe 6 8 0 3 5 4 0 4
0
1
2
3
4
5
6
7
Positionen 0 2 2 2 3 5 7 7 8
0
1
2
3
4
5
6
7
8
Ausgabe 0 0 3 4 4 5 6 8
0
Joost-Pieter Katoen
1
2
3
4
5
6
7
Datenstrukturen und Algorithmen
11/37
Hashing I
Direkte Adressierung
Counting Sort: Beispiel
Eingabe 6 8 0 3 5 4 0 4
0
1
2
3
4
5
6
7
Positionen 0 2 2 2 3 5 7 7 7
0
1
2
3
4
5
6
7
8
Ausgabe 0 0 3 4 4 5 6 8
0
Joost-Pieter Katoen
1
2
3
4
5
6
7
Datenstrukturen und Algorithmen
11/37
Hashing I
Direkte Adressierung
Counting Sort: Beispiel
Eingabe 6 8 0 3 5 4 0 4
0
1
2
3
4
5
6
7
Positionen 0 2 2 2 3 5 6 7 7
0
1
2
3
4
5
6
7
8
Ausgabe 0 0 3 4 4 5 6 8
0
Joost-Pieter Katoen
1
2
3
4
5
6
7
Datenstrukturen und Algorithmen
11/37
Hashing I
Direkte Adressierung
Counting Sort: Beispiel
Eingabe 6 8 0 3 5 4 0 4
0
1
2
3
4
5
6
7
Positionen 0 2 2 2 3 5 6 7 7
0
1
2
3
4
5
6
7
8
Ausgabe 0 0 3 4 4 5 6 8
0
Joost-Pieter Katoen
1
2
3
4
5
6
7
Datenstrukturen und Algorithmen
11/37
Hashing I
Direkte Adressierung
Counting Sort (II)
Problem
Wir sortieren also mit Worst-Case Komplexität Θ(n), obwohl wir als
untere Schranke Θ(n · log n) bewiesen hatten?
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
12/37
Hashing I
Direkte Adressierung
Counting Sort (II)
Problem
Wir sortieren also mit Worst-Case Komplexität Θ(n), obwohl wir als
untere Schranke Θ(n · log n) bewiesen hatten?
Lösung
Dieser Algorithmus ist nicht mit Quicksort, Heapsort, usw. vergleichbar
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
12/37
Hashing I
Direkte Adressierung
Counting Sort (II)
Problem
Wir sortieren also mit Worst-Case Komplexität Θ(n), obwohl wir als
untere Schranke Θ(n · log n) bewiesen hatten?
Lösung
Dieser Algorithmus ist nicht mit Quicksort, Heapsort, usw. vergleichbar
I
denn er basiert nicht auf Vergleich von Elementen, sondern auf
Häufigkeiten.
I
Das funktioniert, indem wir Direkte-Adressierung (Einfügen, Suchen,
Löschen in Θ(1)) ausnutzen.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
12/37
Hashing I
Direkte Adressierung
Counting Sort (II)
Problem
Wir sortieren also mit Worst-Case Komplexität Θ(n), obwohl wir als
untere Schranke Θ(n · log n) bewiesen hatten?
Lösung
Dieser Algorithmus ist nicht mit Quicksort, Heapsort, usw. vergleichbar
I
denn er basiert nicht auf Vergleich von Elementen, sondern auf
Häufigkeiten.
I
Das funktioniert, indem wir Direkte-Adressierung (Einfügen, Suchen,
Löschen in Θ(1)) ausnutzen.
Hauptproblem: Übermäßiger Speicherbedarf für das Array.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
12/37
Hashing I
Direkte Adressierung
Counting Sort (II)
Problem
Wir sortieren also mit Worst-Case Komplexität Θ(n), obwohl wir als
untere Schranke Θ(n · log n) bewiesen hatten?
Lösung
Dieser Algorithmus ist nicht mit Quicksort, Heapsort, usw. vergleichbar
I
denn er basiert nicht auf Vergleich von Elementen, sondern auf
Häufigkeiten.
I
Das funktioniert, indem wir Direkte-Adressierung (Einfügen, Suchen,
Löschen in Θ(1)) ausnutzen.
Hauptproblem: Übermäßiger Speicherbedarf für das Array.
I Zum Beispiel bei Strings mit 20 Zeichen (5 bit/Zeichen) als Schlüssel
benötigt man 25·20 = 2100 Arrayeinträge.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
12/37
Hashing I
Direkte Adressierung
Counting Sort (II)
Problem
Wir sortieren also mit Worst-Case Komplexität Θ(n), obwohl wir als
untere Schranke Θ(n · log n) bewiesen hatten?
Lösung
Dieser Algorithmus ist nicht mit Quicksort, Heapsort, usw. vergleichbar
I
denn er basiert nicht auf Vergleich von Elementen, sondern auf
Häufigkeiten.
I
Das funktioniert, indem wir Direkte-Adressierung (Einfügen, Suchen,
Löschen in Θ(1)) ausnutzen.
Hauptproblem: Übermäßiger Speicherbedarf für das Array.
I Zum Beispiel bei Strings mit 20 Zeichen (5 bit/Zeichen) als Schlüssel
benötigt man 25·20 = 2100 Arrayeinträge.
I Können wir diesen riesigen Speicherbedarf vermeiden und effizient
bleiben?
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
12/37
Hashing I
Direkte Adressierung
Counting Sort (II)
Problem
Wir sortieren also mit Worst-Case Komplexität Θ(n), obwohl wir als
untere Schranke Θ(n · log n) bewiesen hatten?
Lösung
Dieser Algorithmus ist nicht mit Quicksort, Heapsort, usw. vergleichbar
I
denn er basiert nicht auf Vergleich von Elementen, sondern auf
Häufigkeiten.
I
Das funktioniert, indem wir Direkte-Adressierung (Einfügen, Suchen,
Löschen in Θ(1)) ausnutzen.
Hauptproblem: Übermäßiger Speicherbedarf für das Array.
I Zum Beispiel bei Strings mit 20 Zeichen (5 bit/Zeichen) als Schlüssel
benötigt man 25·20 = 2100 Arrayeinträge.
I Können wir diesen riesigen Speicherbedarf vermeiden und effizient
bleiben? Ja! – mit Hashing.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
12/37
Hashing I
Grundlagen des Hashings
Übersicht
1
Direkte Adressierung
Counting Sort
2
Grundlagen des Hashings
3
Verkettung
4
Hashfunktionen
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
13/37
Hashing I
Grundlagen des Hashings
Hashing (I)
Praktisch wird nur ein kleiner Teil der Schlüssel verwendet, d. h. |K | |U|.
⇒ Bei Direkter-Adressierung ist der größte Teil von T verschwendet.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
14/37
Hashing I
Grundlagen des Hashings
Hashing (I)
Praktisch wird nur ein kleiner Teil der Schlüssel verwendet, d. h. |K | |U|.
⇒ Bei Direkter-Adressierung ist der größte Teil von T verschwendet.
Das Ziel von Hashing ist:
I
Einen extrem großen Schlüsselraum auf einen vernünftig kleinen
Bereich von ganzen Zahlen abzubilden.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
14/37
Hashing I
Grundlagen des Hashings
Hashing (I)
Praktisch wird nur ein kleiner Teil der Schlüssel verwendet, d. h. |K | |U|.
⇒ Bei Direkter-Adressierung ist der größte Teil von T verschwendet.
Das Ziel von Hashing ist:
I
Einen extrem großen Schlüsselraum auf einen vernünftig kleinen
Bereich von ganzen Zahlen abzubilden.
I
Dass zwei Schlüssel auf die selbe Zahl abgebildet werden, soll
möglichst unwahrscheinlich sein.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
14/37
Hashing I
Grundlagen des Hashings
Hashing (I)
Praktisch wird nur ein kleiner Teil der Schlüssel verwendet, d. h. |K | |U|.
⇒ Bei Direkter-Adressierung ist der größte Teil von T verschwendet.
Das Ziel von Hashing ist:
I
Einen extrem großen Schlüsselraum auf einen vernünftig kleinen
Bereich von ganzen Zahlen abzubilden.
I
Dass zwei Schlüssel auf die selbe Zahl abgebildet werden, soll
möglichst unwahrscheinlich sein.
Hashfunktion, Hashtabelle, Hashkollision
Eine Hashfunktion bildet einen Schlüssel auf einen Index der Hashtabelle T
ab:
h : U −→ { 0, 1, . . . , m−1 } für Tabellengröße m und |U| = n.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
14/37
Hashing I
Grundlagen des Hashings
Hashing (I)
Praktisch wird nur ein kleiner Teil der Schlüssel verwendet, d. h. |K | |U|.
⇒ Bei Direkter-Adressierung ist der größte Teil von T verschwendet.
Das Ziel von Hashing ist:
I
Einen extrem großen Schlüsselraum auf einen vernünftig kleinen
Bereich von ganzen Zahlen abzubilden.
I
Dass zwei Schlüssel auf die selbe Zahl abgebildet werden, soll
möglichst unwahrscheinlich sein.
Hashfunktion, Hashtabelle, Hashkollision
Eine Hashfunktion bildet einen Schlüssel auf einen Index der Hashtabelle T
ab:
h : U −→ { 0, 1, . . . , m−1 } für Tabellengröße m und |U| = n.
Wir sagen, dass h(k) der Hashwert des Schlüssels k ist.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
14/37
Hashing I
Grundlagen des Hashings
Hashing (I)
Praktisch wird nur ein kleiner Teil der Schlüssel verwendet, d. h. |K | |U|.
⇒ Bei Direkter-Adressierung ist der größte Teil von T verschwendet.
Das Ziel von Hashing ist:
I
Einen extrem großen Schlüsselraum auf einen vernünftig kleinen
Bereich von ganzen Zahlen abzubilden.
I
Dass zwei Schlüssel auf die selbe Zahl abgebildet werden, soll
möglichst unwahrscheinlich sein.
Hashfunktion, Hashtabelle, Hashkollision
Eine Hashfunktion bildet einen Schlüssel auf einen Index der Hashtabelle T
ab:
h : U −→ { 0, 1, . . . , m−1 } für Tabellengröße m und |U| = n.
Wir sagen, dass h(k) der Hashwert des Schlüssels k ist.
Das Auftreten von h(k) = h(k 0 ) für k 6= k 0 nennt man eine Kollision.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
14/37
Hashing I
Grundlagen des Hashings
Hashing (II)
Schlüsselmenge
Hashfunktion
0 Hashtabelle
U
K
k2
k4
h(k1 )
h(k2 ) = h(k3 )
k1
h(k5 )
k3
k5
Kollision
h(k4 )
m−1
benutzte Schlüssel
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
15/37
Hashing I
Grundlagen des Hashings
Hashing (II)
Schlüsselmenge
Hashfunktion
0 Hashtabelle
U
K
k2
k4
h(k1 )
h(k2 ) = h(k3 )
k1
h(k5 )
k3
k5
Kollision
h(k4 )
m−1
benutzte Schlüssel
I
Wie finden wir Hashfunktionen, die einfach auszurechnen sind und
Kollisionen minimieren?
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
15/37
Hashing I
Grundlagen des Hashings
Hashing (II)
Schlüsselmenge
Hashfunktion
0 Hashtabelle
U
K
k2
k4
h(k1 )
h(k2 ) = h(k3 )
k1
h(k5 )
k3
k5
Kollision
h(k4 )
m−1
benutzte Schlüssel
I
I
Wie finden wir Hashfunktionen, die einfach auszurechnen sind und
Kollisionen minimieren?
Wie behandeln wir dennoch auftretende Kollisionen?
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
15/37
Hashing I
Grundlagen des Hashings
Kollisionen: Das Geburtstagsparadoxon (I)
Unsere Hashfunktion mag noch so gut sein,
wir sollten auf Kollisionen vorbereitet sein!
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
16/37
Hashing I
Grundlagen des Hashings
Kollisionen: Das Geburtstagsparadoxon (I)
Unsere Hashfunktion mag noch so gut sein,
wir sollten auf Kollisionen vorbereitet sein!
Das liegt am
Geburtstagsparadoxon
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
16/37
Hashing I
Grundlagen des Hashings
Kollisionen: Das Geburtstagsparadoxon (I)
Unsere Hashfunktion mag noch so gut sein,
wir sollten auf Kollisionen vorbereitet sein!
Das liegt am
Geburtstagsparadoxon
I
Die Wahrscheinlichkeit, dass dein Nachbar am selben Tag wie du
1
Geburtstag hat ist 365
≈ 0,027.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
16/37
Hashing I
Grundlagen des Hashings
Kollisionen: Das Geburtstagsparadoxon (I)
Unsere Hashfunktion mag noch so gut sein,
wir sollten auf Kollisionen vorbereitet sein!
Das liegt am
Geburtstagsparadoxon
I
Die Wahrscheinlichkeit, dass dein Nachbar am selben Tag wie du
1
Geburtstag hat ist 365
≈ 0,027.
I
Fragt man 23 Personen, wächst die Wahrscheinlichkeit auf
23
365 ≈ 0,063.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
16/37
Hashing I
Grundlagen des Hashings
Kollisionen: Das Geburtstagsparadoxon (I)
Unsere Hashfunktion mag noch so gut sein,
wir sollten auf Kollisionen vorbereitet sein!
Das liegt am
Geburtstagsparadoxon
I
Die Wahrscheinlichkeit, dass dein Nachbar am selben Tag wie du
1
Geburtstag hat ist 365
≈ 0,027.
I
Fragt man 23 Personen, wächst die Wahrscheinlichkeit auf
23
365 ≈ 0,063.
I
Sind aber 23 Personen in einem Raum, dann haben zwei von ihnen
den selben Geburtstag mit Wahrscheinlichkeit
1−
Joost-Pieter Katoen
365 364 363
343
·
·
· ... ·
365 365 365
365
≈ 0,5
Datenstrukturen und Algorithmen
16/37
Hashing I
Grundlagen des Hashings
Kollisionen: Das Geburtstagsparadoxon (II)
Auf Hashing angewendet bedeutet das:
I
Die Wahrscheinlichkeit keiner Kollision nach k Einfügevorgängen in
einer m-elementigen Tabelle ist:
Y m−i
m m−1
m − k + 1 k−1
·
· ... ·
=
m
m
m
m
i=0
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
17/37
Hashing I
Grundlagen des Hashings
Kollisionen: Das Geburtstagsparadoxon (II)
Auf Hashing angewendet bedeutet das:
I
Die Wahrscheinlichkeit keiner Kollision nach k Einfügevorgängen in
einer m-elementigen Tabelle ist:
Y m−i
m m−1
m − k + 1 k−1
·
· ... ·
=
m
m
m
m
i=0
I
Dieses Produkt geht gegen 0.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
17/37
Hashing I
Grundlagen des Hashings
Kollisionen: Das Geburtstagsparadoxon (II)
Auf Hashing angewendet bedeutet das:
I
Die Wahrscheinlichkeit keiner Kollision nach k Einfügevorgängen in
einer m-elementigen Tabelle ist:
Y m−i
m m−1
m − k + 1 k−1
·
· ... ·
=
m
m
m
m
i=0
I
Dieses Produkt geht gegen 0.
I
Etwa bei m = 365 ist die Wahrscheinlichkeit für k > 50 praktisch 0.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
17/37
Hashing I
Grundlagen des Hashings
Kollisionen: Das Geburtstagsparadoxon (III)
Wahrscheinlichkeit
keiner Kollision
1.0
0.8
0.6
0.4
0.2
20
40
60
80
100
Anzahl Einfügungen n
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
18/37
Hashing I
Verkettung
Übersicht
1
Direkte Adressierung
Counting Sort
2
Grundlagen des Hashings
3
Verkettung
4
Hashfunktionen
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
19/37
Hashing I
Verkettung
Kollisionsauflösung durch Verkettung (I)
Idee
Alle Schlüssel, die zum gleichen Hash führen, werden in einer
verketteten Liste gespeichert.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
[Luhn 1953]
20/37
Hashing I
Verkettung
Kollisionsauflösung durch Verkettung (I)
Idee
Alle Schlüssel, die zum gleichen Hash führen, werden in einer
verketteten Liste gespeichert.
[Luhn 1953]
0
U
K
k2
k4
k1
k7
k3 k
6
k5
k1
k2
k3
k6
k7
k5
k4
m−1
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
20/37
Hashing I
Verkettung
Kollisionsauflösung durch Verkettung (II)
Dictionary-Operationen bei Verkettung (informell)
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
21/37
Hashing I
Verkettung
Kollisionsauflösung durch Verkettung (II)
Dictionary-Operationen bei Verkettung (informell)
I
hcSearch(int k): Suche nach einem Element mit Schlüssel k in der
Liste T[h(k)].
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
21/37
Hashing I
Verkettung
Kollisionsauflösung durch Verkettung (II)
Dictionary-Operationen bei Verkettung (informell)
I
hcSearch(int k): Suche nach einem Element mit Schlüssel k in der
Liste T[h(k)].
I
hcInsert(Element e): Setze Element e an den Anfang der Liste
T[h(e.key)].
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
21/37
Hashing I
Verkettung
Kollisionsauflösung durch Verkettung (II)
Dictionary-Operationen bei Verkettung (informell)
I
hcSearch(int k): Suche nach einem Element mit Schlüssel k in der
Liste T[h(k)].
I
hcInsert(Element e): Setze Element e an den Anfang der Liste
T[h(e.key)].
I
hcDelete(Element e): Lösche Element e aus der Liste T[h(e.key)].
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
21/37
Hashing I
Verkettung
Kollisionsauflösung durch Verkettung (III)
Worst-Case Komplexität
Angenommen, die Berechnung von h(k) ist recht effizient, etwa Θ(1).
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
22/37
Hashing I
Verkettung
Kollisionsauflösung durch Verkettung (III)
Worst-Case Komplexität
Angenommen, die Berechnung von h(k) ist recht effizient, etwa Θ(1).
Die Komplexität ist:
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
22/37
Hashing I
Verkettung
Kollisionsauflösung durch Verkettung (III)
Worst-Case Komplexität
Angenommen, die Berechnung von h(k) ist recht effizient, etwa Θ(1).
Die Komplexität ist:
Suche: Proportional zur Länge der Liste T [h(k)].
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
22/37
Hashing I
Verkettung
Kollisionsauflösung durch Verkettung (III)
Worst-Case Komplexität
Angenommen, die Berechnung von h(k) ist recht effizient, etwa Θ(1).
Die Komplexität ist:
Suche: Proportional zur Länge der Liste T [h(k)].
Einfügen: Konstant (ohne Überprüfung, ob das Element schon
vorhanden ist).
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
22/37
Hashing I
Verkettung
Kollisionsauflösung durch Verkettung (III)
Worst-Case Komplexität
Angenommen, die Berechnung von h(k) ist recht effizient, etwa Θ(1).
Die Komplexität ist:
Suche: Proportional zur Länge der Liste T [h(k)].
Einfügen: Konstant (ohne Überprüfung, ob das Element schon
vorhanden ist).
Löschen: Proportional zur Länge der Liste T [h(k)].
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
22/37
Hashing I
Verkettung
Kollisionsauflösung durch Verkettung (III)
Worst-Case Komplexität
Angenommen, die Berechnung von h(k) ist recht effizient, etwa Θ(1).
Die Komplexität ist:
Suche: Proportional zur Länge der Liste T [h(k)].
Einfügen: Konstant (ohne Überprüfung, ob das Element schon
vorhanden ist).
Löschen: Proportional zur Länge der Liste T [h(k)].
I
Im Worst-Case haben alle Schüssel den selben Hashwert.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
22/37
Hashing I
Verkettung
Kollisionsauflösung durch Verkettung (III)
Worst-Case Komplexität
Angenommen, die Berechnung von h(k) ist recht effizient, etwa Θ(1).
Die Komplexität ist:
Suche: Proportional zur Länge der Liste T [h(k)].
Einfügen: Konstant (ohne Überprüfung, ob das Element schon
vorhanden ist).
Löschen: Proportional zur Länge der Liste T [h(k)].
I
Im Worst-Case haben alle Schüssel den selben Hashwert.
I
Suche und Löschen hat dann die selbe Worst-Case Komplexität wie
Listen: Θ(n).
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
22/37
Hashing I
Verkettung
Kollisionsauflösung durch Verkettung (III)
Worst-Case Komplexität
Angenommen, die Berechnung von h(k) ist recht effizient, etwa Θ(1).
Die Komplexität ist:
Suche: Proportional zur Länge der Liste T [h(k)].
Einfügen: Konstant (ohne Überprüfung, ob das Element schon
vorhanden ist).
Löschen: Proportional zur Länge der Liste T [h(k)].
I
Im Worst-Case haben alle Schüssel den selben Hashwert.
I
Suche und Löschen hat dann die selbe Worst-Case Komplexität wie
Listen: Θ(n).
I
Im Average-Case ist Hashing mit Verkettung aber dennoch effizient!
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
22/37
Hashing I
Verkettung
Average-Case-Analyse von Verkettung (I)
Annahmen:
I
Es gebe n mögliche Schlüssel und m Hashtabellenpositionen, n m.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
23/37
Hashing I
Verkettung
Average-Case-Analyse von Verkettung (I)
Annahmen:
I
Es gebe n mögliche Schlüssel und m Hashtabellenpositionen, n m.
I
Gleichverteiltes Hashing: Jeder Schlüssel wird mit gleicher
Wahrscheinlichkeit und unabhängig von den anderen Schlüssel auf
jedes der m Slots abgebildet.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
23/37
Hashing I
Verkettung
Average-Case-Analyse von Verkettung (I)
Annahmen:
I
Es gebe n mögliche Schlüssel und m Hashtabellenpositionen, n m.
I
Gleichverteiltes Hashing: Jeder Schlüssel wird mit gleicher
Wahrscheinlichkeit und unabhängig von den anderen Schlüssel auf
jedes der m Slots abgebildet.
I
Der Hashwert h(k) kann in konstanter Zeit berechnet werden.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
23/37
Hashing I
Verkettung
Average-Case-Analyse von Verkettung (I)
Annahmen:
I
Es gebe n mögliche Schlüssel und m Hashtabellenpositionen, n m.
I
Gleichverteiltes Hashing: Jeder Schlüssel wird mit gleicher
Wahrscheinlichkeit und unabhängig von den anderen Schlüssel auf
jedes der m Slots abgebildet.
I
Der Hashwert h(k) kann in konstanter Zeit berechnet werden.
O, Θ, Ω erweitert
Aus technischen Gründen erweitern wir die Definition von O, Θ und Ω auf
Funktionen mit zwei Parametern.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
23/37
Hashing I
Verkettung
Average-Case-Analyse von Verkettung (I)
Annahmen:
I
Es gebe n mögliche Schlüssel und m Hashtabellenpositionen, n m.
I
Gleichverteiltes Hashing: Jeder Schlüssel wird mit gleicher
Wahrscheinlichkeit und unabhängig von den anderen Schlüssel auf
jedes der m Slots abgebildet.
I
Der Hashwert h(k) kann in konstanter Zeit berechnet werden.
O, Θ, Ω erweitert
Aus technischen Gründen erweitern wir die Definition von O, Θ und Ω auf
Funktionen mit zwei Parametern.
I
Beispielsweise ist g ∈ O(f ) gdw.
∃c > 0, n0 , m0 mit ∀n > n0 , m > m0 : 0 6 g(n, m) 6 c · f (n, m)
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
23/37
Hashing I
Verkettung
Average-Case-Analyse von Verkettung (II)
I
Der Füllgrad der Hashtabelle T ist α(n, m) =
Joost-Pieter Katoen
n
m.
Datenstrukturen und Algorithmen
24/37
Hashing I
Verkettung
Average-Case-Analyse von Verkettung (II)
I
Der Füllgrad der Hashtabelle T ist α(n, m) =
n
m.
⇒ Auch die durchschnittliche Länge der Liste T[h(k)] ist α!
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
24/37
Hashing I
Verkettung
Average-Case-Analyse von Verkettung (II)
I
Der Füllgrad der Hashtabelle T ist α(n, m) =
n
m.
⇒ Auch die durchschnittliche Länge der Liste T[h(k)] ist α!
I
Wieviele Elemente aus T[h(k)] müssen nun im Schnitt untersucht
werden, um den Schlüssel k zu finden?
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
24/37
Hashing I
Verkettung
Average-Case-Analyse von Verkettung (II)
I
Der Füllgrad der Hashtabelle T ist α(n, m) =
n
m.
⇒ Auch die durchschnittliche Länge der Liste T[h(k)] ist α!
I
Wieviele Elemente aus T[h(k)] müssen nun im Schnitt untersucht
werden, um den Schlüssel k zu finden?
⇒ Unterscheide erfolgreiche von erfolgloser Suche (wie in Vorlesung 1).
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
24/37
Hashing I
Verkettung
Average-Case-Analyse von Verkettung (III)
Erfolglose Suche
Die erfolglose Suche benötigt Θ(1 + α) Zeit im Average-Case.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
25/37
Hashing I
Verkettung
Average-Case-Analyse von Verkettung (III)
Erfolglose Suche
Die erfolglose Suche benötigt Θ(1 + α) Zeit im Average-Case.
I
Die erwartete Zeit, um Schlüssel k zu finden ist gerade die Zeit, um
die Liste T[h(k)] zu durchsuchen.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
25/37
Hashing I
Verkettung
Average-Case-Analyse von Verkettung (III)
Erfolglose Suche
Die erfolglose Suche benötigt Θ(1 + α) Zeit im Average-Case.
I
Die erwartete Zeit, um Schlüssel k zu finden ist gerade die Zeit, um
die Liste T[h(k)] zu durchsuchen.
I
Die erwartete Länge dieser Liste ist α.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
25/37
Hashing I
Verkettung
Average-Case-Analyse von Verkettung (III)
Erfolglose Suche
Die erfolglose Suche benötigt Θ(1 + α) Zeit im Average-Case.
I
Die erwartete Zeit, um Schlüssel k zu finden ist gerade die Zeit, um
die Liste T[h(k)] zu durchsuchen.
I
Die erwartete Länge dieser Liste ist α.
I
Das Berechnen von h(k) benötige nur eine Zeiteinheit.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
25/37
Hashing I
Verkettung
Average-Case-Analyse von Verkettung (III)
Erfolglose Suche
Die erfolglose Suche benötigt Θ(1 + α) Zeit im Average-Case.
I
Die erwartete Zeit, um Schlüssel k zu finden ist gerade die Zeit, um
die Liste T[h(k)] zu durchsuchen.
I
Die erwartete Länge dieser Liste ist α.
I
Das Berechnen von h(k) benötige nur eine Zeiteinheit.
⇒ Insgesamt erhält man 1 + α Zeiteinheiten im Durchschnitt.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
25/37
Hashing I
Verkettung
Average-Case-Analyse von Verkettung (IV)
Erfolgreiche Suche
Die erfolgreiche Suche benötigt im Average-Case auch Θ(1 + α).
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
26/37
Hashing I
Verkettung
Average-Case-Analyse von Verkettung (IV)
Erfolgreiche Suche
Die erfolgreiche Suche benötigt im Average-Case auch Θ(1 + α).
I
Sei ki der i-te eingefügte Schlüssel und A(ki ) die erwartete Zeit, um
ki zu finden:
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
26/37
Hashing I
Verkettung
Average-Case-Analyse von Verkettung (IV)
Erfolgreiche Suche
Die erfolgreiche Suche benötigt im Average-Case auch Θ(1 + α).
I
Sei ki der i-te eingefügte Schlüssel und A(ki ) die erwartete Zeit, um
ki zu finden:
A(ki ) = 1 +
Joost-Pieter Katoen
Durchschnittliche Anzahl Schlüssel,
die in T[h(k_i)] erst nach ki eingefügt wurden
Datenstrukturen und Algorithmen
26/37
Hashing I
Verkettung
Average-Case-Analyse von Verkettung (IV)
Erfolgreiche Suche
Die erfolgreiche Suche benötigt im Average-Case auch Θ(1 + α).
I
Sei ki der i-te eingefügte Schlüssel und A(ki ) die erwartete Zeit, um
ki zu finden:
A(ki ) = 1 +
I
Durchschnittliche Anzahl Schlüssel,
die in T[h(k_i)] erst nach ki eingefügt wurden
Annahme von gleichverteiltem Hashing ergibt: A(ki ) = 1 +
n
X
1
j=i+1
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
m
26/37
Hashing I
Verkettung
Average-Case-Analyse von Verkettung (IV)
Erfolgreiche Suche
Die erfolgreiche Suche benötigt im Average-Case auch Θ(1 + α).
I
Sei ki der i-te eingefügte Schlüssel und A(ki ) die erwartete Zeit, um
ki zu finden:
A(ki ) = 1 +
I
I
Durchschnittliche Anzahl Schlüssel,
die in T[h(k_i)] erst nach ki eingefügt wurden
Annahme von gleichverteiltem Hashing ergibt: A(ki ) = 1 +
Durchschnitt über alle n Einfügungen in die Hashtabelle:
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
1
n
n
X
1
j=i+1
n
X
m
A(ki )
i=1
26/37
Hashing I
Verkettung
Average-Case-Analyse von Verkettung (IV)
Die erwartete Anzahl an untersuchten Elementen bei einer erfolgreichen
Suche ist:
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
27/37
Hashing I
Verkettung
Average-Case-Analyse von Verkettung (IV)
Die erwartete Anzahl an untersuchten Elementen bei einer erfolgreichen
Suche ist:
n
1X
n

1 +
i=1
n
X
1

m
j=i+1

Summe aufteilen
n
n X
n
1X
1 X
=
1+
1
n i=1
nm i=1 j=i+1
Vereinfachen
n
1 X
=1+
(n − i)
nm i=1
Summe 1 . . . n − 1
1 n(n − 1)
·
nm
2
n−1
α
α
=1+
=1+ −
2m
2
2n
=1+
Joost-Pieter Katoen
Vereinfachen
und damit in Θ(1 + α)
Datenstrukturen und Algorithmen
27/37
Hashing I
Verkettung
Komplexität der Dictionary-Operationen mit
Verkettung
I
Vorausgesetzt die Anzahl der Einträge m ist (wenigstens) proportional
zu n,
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
28/37
Hashing I
Verkettung
Komplexität der Dictionary-Operationen mit
Verkettung
I
Vorausgesetzt die Anzahl der Einträge m ist (wenigstens) proportional
zu n,
I
dann ist der Füllgrad α(n, m) =
Joost-Pieter Katoen
n
m
∈
O(m)
m
= O(1).
Datenstrukturen und Algorithmen
28/37
Hashing I
Verkettung
Komplexität der Dictionary-Operationen mit
Verkettung
I
Vorausgesetzt die Anzahl der Einträge m ist (wenigstens) proportional
zu n,
I
dann ist der Füllgrad α(n, m) =
I
Damit benötigen alle Operationen im Durchschnitt O(1).
Joost-Pieter Katoen
n
m
∈
O(m)
m
= O(1).
Datenstrukturen und Algorithmen
28/37
Hashing I
Verkettung
Komplexität der Dictionary-Operationen mit
Verkettung
I
Vorausgesetzt die Anzahl der Einträge m ist (wenigstens) proportional
zu n,
I
dann ist der Füllgrad α(n, m) =
I
Damit benötigen alle Operationen im Durchschnitt O(1).
I
Weil das auch Suche mit einschließt, können wir im Average-Case mit
O(n) sortieren.
Joost-Pieter Katoen
n
m
∈
O(m)
m
= O(1).
Datenstrukturen und Algorithmen
28/37
Hashing I
Hashfunktionen
Übersicht
1
Direkte Adressierung
Counting Sort
2
Grundlagen des Hashings
3
Verkettung
4
Hashfunktionen
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
29/37
Hashing I
Hashfunktionen
Anwendungen von Hashfunktionen
I
I
Digitale Zertifikate (SSL: sha1 bzw. md5)
Digitale Signaturen
I
I
Passwortverschlüsselung
I
I
I
in der Praxis unterschreibt man meist nicht die Nachricht, sondern
ihren Hashwert
z.B. htpasswd (Apache): md5, sha1 und Mediawiki: md5
Verifikation von Downloads (üblich: md5)
Versionskontrollsysteme
I
z.B. subversion: md5, git: sha1
I
Datenblockverifikation bei Filesharingprogrammen (wie Bittorrent)
I
......
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
30/37
Hashing I
Hashfunktionen
Gängige (kryptographische) Hashfunktionen
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
31/37
Hashing I
Hashfunktionen
Hashfunktionen
Hashfunktion
I
Eine Hashfunktion bildet einen Schlüssel auf eine ganze Zahl
(d. h. einen Index) ab.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
32/37
Hashing I
Hashfunktionen
Hashfunktionen
Hashfunktion
I
Eine Hashfunktion bildet einen Schlüssel auf eine ganze Zahl
(d. h. einen Index) ab.
I
Was macht eine „gute“ Hashfunktion aus?
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
32/37
Hashing I
Hashfunktionen
Hashfunktionen
Hashfunktion
I
Eine Hashfunktion bildet einen Schlüssel auf eine ganze Zahl
(d. h. einen Index) ab.
I
Was macht eine „gute“ Hashfunktion aus?
I
Die Hashfunktion h(k) sollte einfach zu berechnen sein,
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
32/37
Hashing I
Hashfunktionen
Hashfunktionen
Hashfunktion
I
Eine Hashfunktion bildet einen Schlüssel auf eine ganze Zahl
(d. h. einen Index) ab.
I
Was macht eine „gute“ Hashfunktion aus?
I
I
Die Hashfunktion h(k) sollte einfach zu berechnen sein,
sie sollte surjektiv auf der Menge 0 . . . m−1 sein,
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
32/37
Hashing I
Hashfunktionen
Hashfunktionen
Hashfunktion
I
Eine Hashfunktion bildet einen Schlüssel auf eine ganze Zahl
(d. h. einen Index) ab.
I
Was macht eine „gute“ Hashfunktion aus?
I
I
I
Die Hashfunktion h(k) sollte einfach zu berechnen sein,
sie sollte surjektiv auf der Menge 0 . . . m−1 sein,
sie sollte alle Indizes mit möglichst gleicher Häufigkeit verwenden, und
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
32/37
Hashing I
Hashfunktionen
Hashfunktionen
Hashfunktion
I
Eine Hashfunktion bildet einen Schlüssel auf eine ganze Zahl
(d. h. einen Index) ab.
I
Was macht eine „gute“ Hashfunktion aus?
I
I
I
I
Die Hashfunktion h(k) sollte einfach zu berechnen sein,
sie sollte surjektiv auf der Menge 0 . . . m−1 sein,
sie sollte alle Indizes mit möglichst gleicher Häufigkeit verwenden, und
ähnliche Schlüssel möglichst breit auf die Hashtabelle verteilen.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
32/37
Hashing I
Hashfunktionen
Hashfunktionen
Hashfunktion
I
Eine Hashfunktion bildet einen Schlüssel auf eine ganze Zahl
(d. h. einen Index) ab.
I
Was macht eine „gute“ Hashfunktion aus?
I
I
I
I
I
Die Hashfunktion h(k) sollte einfach zu berechnen sein,
sie sollte surjektiv auf der Menge 0 . . . m−1 sein,
sie sollte alle Indizes mit möglichst gleicher Häufigkeit verwenden, und
ähnliche Schlüssel möglichst breit auf die Hashtabelle verteilen.
Drei Basistechniken, eine „gute“ Hashfunktion zu erhalten:
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
32/37
Hashing I
Hashfunktionen
Hashfunktionen
Hashfunktion
I
Eine Hashfunktion bildet einen Schlüssel auf eine ganze Zahl
(d. h. einen Index) ab.
I
Was macht eine „gute“ Hashfunktion aus?
I
I
I
I
I
Die Hashfunktion h(k) sollte einfach zu berechnen sein,
sie sollte surjektiv auf der Menge 0 . . . m−1 sein,
sie sollte alle Indizes mit möglichst gleicher Häufigkeit verwenden, und
ähnliche Schlüssel möglichst breit auf die Hashtabelle verteilen.
Drei Basistechniken, eine „gute“ Hashfunktion zu erhalten:
I
Die Divisionsmethode,
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
32/37
Hashing I
Hashfunktionen
Hashfunktionen
Hashfunktion
I
Eine Hashfunktion bildet einen Schlüssel auf eine ganze Zahl
(d. h. einen Index) ab.
I
Was macht eine „gute“ Hashfunktion aus?
I
I
I
I
I
Die Hashfunktion h(k) sollte einfach zu berechnen sein,
sie sollte surjektiv auf der Menge 0 . . . m−1 sein,
sie sollte alle Indizes mit möglichst gleicher Häufigkeit verwenden, und
ähnliche Schlüssel möglichst breit auf die Hashtabelle verteilen.
Drei Basistechniken, eine „gute“ Hashfunktion zu erhalten:
I
I
Die Divisionsmethode,
die Multiplikationsmethode, und
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
32/37
Hashing I
Hashfunktionen
Hashfunktionen
Hashfunktion
I
Eine Hashfunktion bildet einen Schlüssel auf eine ganze Zahl
(d. h. einen Index) ab.
I
Was macht eine „gute“ Hashfunktion aus?
I
I
I
I
I
Die Hashfunktion h(k) sollte einfach zu berechnen sein,
sie sollte surjektiv auf der Menge 0 . . . m−1 sein,
sie sollte alle Indizes mit möglichst gleicher Häufigkeit verwenden, und
ähnliche Schlüssel möglichst breit auf die Hashtabelle verteilen.
Drei Basistechniken, eine „gute“ Hashfunktion zu erhalten:
I
I
I
Die Divisionsmethode,
die Multiplikationsmethode, und
universelles Hashing.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
32/37
Hashing I
Hashfunktionen
Divisionsmethode
Divisionsmethode
Hashfunktion: h(k) = k mod m
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
33/37
Hashing I
Hashfunktionen
Divisionsmethode
Divisionsmethode
Hashfunktion: h(k) = k mod m
I
Bei dieser Methode muss der Wert von m sorgfältig gewählt werden.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
33/37
Hashing I
Hashfunktionen
Divisionsmethode
Divisionsmethode
Hashfunktion: h(k) = k mod m
I
Bei dieser Methode muss der Wert von m sorgfältig gewählt werden.
I
Für m = 2p ist h(k) einfach die letzte p Bits.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
33/37
Hashing I
Hashfunktionen
Divisionsmethode
Divisionsmethode
Hashfunktion: h(k) = k mod m
I
Bei dieser Methode muss der Wert von m sorgfältig gewählt werden.
I
I
Für m = 2p ist h(k) einfach die letzte p Bits.
Besser ist es, h(k) abhängig von mehreren Bits zu machen.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
33/37
Hashing I
Hashfunktionen
Divisionsmethode
Divisionsmethode
Hashfunktion: h(k) = k mod m
I
Bei dieser Methode muss der Wert von m sorgfältig gewählt werden.
I
I
I
Für m = 2p ist h(k) einfach die letzte p Bits.
Besser ist es, h(k) abhängig von mehreren Bits zu machen.
Gute Wahl ist m prim und nicht zu nah an einer Zweierpotenz.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
33/37
Hashing I
Hashfunktionen
Divisionsmethode
Divisionsmethode
Hashfunktion: h(k) = k mod m
I
Bei dieser Methode muss der Wert von m sorgfältig gewählt werden.
I
I
I
Für m = 2p ist h(k) einfach die letzte p Bits.
Besser ist es, h(k) abhängig von mehreren Bits zu machen.
Gute Wahl ist m prim und nicht zu nah an einer Zweierpotenz.
Beispiel
I
Strings mit 2000 Zeichen als Schlüssel.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
33/37
Hashing I
Hashfunktionen
Divisionsmethode
Divisionsmethode
Hashfunktion: h(k) = k mod m
I
Bei dieser Methode muss der Wert von m sorgfältig gewählt werden.
I
I
I
Für m = 2p ist h(k) einfach die letzte p Bits.
Besser ist es, h(k) abhängig von mehreren Bits zu machen.
Gute Wahl ist m prim und nicht zu nah an einer Zweierpotenz.
Beispiel
I
Strings mit 2000 Zeichen als Schlüssel.
I
Wir erlauben durchschnittlich 3 Sondierungen für die erfolglose Suche.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
33/37
Hashing I
Hashfunktionen
Divisionsmethode
Divisionsmethode
Hashfunktion: h(k) = k mod m
I
Bei dieser Methode muss der Wert von m sorgfältig gewählt werden.
I
I
I
Für m = 2p ist h(k) einfach die letzte p Bits.
Besser ist es, h(k) abhängig von mehreren Bits zu machen.
Gute Wahl ist m prim und nicht zu nah an einer Zweierpotenz.
Beispiel
I
Strings mit 2000 Zeichen als Schlüssel.
I
Wir erlauben durchschnittlich 3 Sondierungen für die erfolglose Suche.
⇒ Wähle m ≈ 2000/3 −→ 701.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
33/37
Hashing I
Hashfunktionen
Multiplikationsmethode (I)
Multiplikationsmethode
Hashfunktion: h(k) = bm·(k·c mod 1)c für 0 < c < 1
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
34/37
Hashing I
Hashfunktionen
Multiplikationsmethode (I)
Multiplikationsmethode
Hashfunktion: h(k) = bm·(k·c mod 1)c für 0 < c < 1
I
k·c mod 1 ist der Nachkommateil von k·c, d. h. k·c − bk·cc.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
34/37
Hashing I
Hashfunktionen
Multiplikationsmethode (I)
Multiplikationsmethode
Hashfunktion: h(k) = bm·(k·c mod 1)c für 0 < c < 1
I
I
k·c mod 1 ist der Nachkommateil von k·c, d. h. k·c − bk·cc.
√
Knuth empfiehlt c ≈ ( 5 − 1)/2 ≈ 0,62.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
34/37
Hashing I
Hashfunktionen
Multiplikationsmethode (I)
Multiplikationsmethode
Hashfunktion: h(k) = bm·(k·c mod 1)c für 0 < c < 1
I
I
k·c mod 1 ist der Nachkommateil von k·c, d. h. k·c − bk·cc.
√
Knuth empfiehlt c ≈ ( 5 − 1)/2 ≈ 0,62.
⇒ Der Wert von m ist hier nicht kritisch.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
34/37
Hashing I
Hashfunktionen
Multiplikationsmethode (II)
Hashfunktion: h(k) = bm·(k·c mod 1)c.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
35/37
Hashing I
Hashfunktionen
Multiplikationsmethode (II)
Hashfunktion: h(k) = bm·(k·c mod 1)c.
I
Das übliche Vorgehen nimmt m = 2p und c =
Joost-Pieter Katoen
s
2w ,
wobei 0 < s < 2w .
Datenstrukturen und Algorithmen
35/37
Hashing I
Hashfunktionen
Multiplikationsmethode (II)
Hashfunktion: h(k) = bm·(k·c mod 1)c.
I
Das übliche Vorgehen nimmt m = 2p und c =
Dann:
I
s
2w ,
wobei 0 < s < 2w .
Berechne zunächst k·s (= k·c·2w ).
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
35/37
Hashing I
Hashfunktionen
Multiplikationsmethode (II)
Hashfunktion: h(k) = bm·(k·c mod 1)c.
I
Das übliche Vorgehen nimmt m = 2p und c =
Dann:
I
I
s
2w ,
wobei 0 < s < 2w .
Berechne zunächst k·s (= k·c·2w ).
Teile durch 2w , verwende nur die Nachkommastellen.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
35/37
Hashing I
Hashfunktionen
Multiplikationsmethode (II)
Hashfunktion: h(k) = bm·(k·c mod 1)c.
I
Das übliche Vorgehen nimmt m = 2p und c =
Dann:
I
I
I
s
2w ,
wobei 0 < s < 2w .
Berechne zunächst k·s (= k·c·2w ).
Teile durch 2w , verwende nur die Nachkommastellen.
Multipliziere mit 2p und verwende nur den ganzzahligen Anteil.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
35/37
Hashing I
Hashfunktionen
Multiplikationsmethode (II)
Hashfunktion: h(k) = bm·(k·c mod 1)c.
I
Das übliche Vorgehen nimmt m = 2p und c =
Dann:
I
I
I
s
2w ,
wobei 0 < s < 2w .
Berechne zunächst k·s (= k·c·2w ).
Teile durch 2w , verwende nur die Nachkommastellen.
Multipliziere mit 2p und verwende nur den ganzzahligen Anteil.
w Bits
Schlüssel k
∗
c · 2w
p Bits extrahieren
h(k)
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
35/37
Hashing I
Hashfunktionen
Universelles Hashing (I)
Das größte Problem beim Hashing ist,
I dass es immer eine ungünstige Sequenz von Schlüsseln gibt, die auf
den selben Slot abgebildet werden.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
36/37
Hashing I
Hashfunktionen
Universelles Hashing (I)
Das größte Problem beim Hashing ist,
I dass es immer eine ungünstige Sequenz von Schlüsseln gibt, die auf
den selben Slot abgebildet werden.
Idee
Wähle zufällig eine Hashfunktion aus einer gegebenen kleinen Menge H,
unabhängig von den verwendeten Schlüsseln.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
36/37
Hashing I
Hashfunktionen
Universelles Hashing (I)
Das größte Problem beim Hashing ist,
I dass es immer eine ungünstige Sequenz von Schlüsseln gibt, die auf
den selben Slot abgebildet werden.
Idee
Wähle zufällig eine Hashfunktion aus einer gegebenen kleinen Menge H,
unabhängig von den verwendeten Schlüsseln.
Eine Menge Hashfunktionen ist universell, wenn
I der Anteil der Funktionen aus H, so dass k und k 0 kollidieren ist
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
|H|
m .
36/37
Hashing I
Hashfunktionen
Universelles Hashing (I)
Das größte Problem beim Hashing ist,
I dass es immer eine ungünstige Sequenz von Schlüsseln gibt, die auf
den selben Slot abgebildet werden.
Idee
Wähle zufällig eine Hashfunktion aus einer gegebenen kleinen Menge H,
unabhängig von den verwendeten Schlüsseln.
Eine Menge Hashfunktionen ist universell, wenn
I der Anteil der Funktionen aus H, so dass k und k 0 kollidieren ist
I
d. h., die W’lichkeit einer Kollision von k und k 0 ist
1
|H|
·
|H|
m
=
|H|
m .
1
m.
Für universelles Hashing ist die erwartete Länge der Liste T[k]
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
36/37
Hashing I
Hashfunktionen
Universelles Hashing (I)
Das größte Problem beim Hashing ist,
I dass es immer eine ungünstige Sequenz von Schlüsseln gibt, die auf
den selben Slot abgebildet werden.
Idee
Wähle zufällig eine Hashfunktion aus einer gegebenen kleinen Menge H,
unabhängig von den verwendeten Schlüsseln.
Eine Menge Hashfunktionen ist universell, wenn
I der Anteil der Funktionen aus H, so dass k und k 0 kollidieren ist
I
d. h., die W’lichkeit einer Kollision von k und k 0 ist
1
|H|
·
|H|
m
=
|H|
m .
1
m.
Für universelles Hashing ist die erwartete Länge der Liste T[k]
1. Gleich α, wenn k nicht in T enthalten ist.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
36/37
Hashing I
Hashfunktionen
Universelles Hashing (I)
Das größte Problem beim Hashing ist,
I dass es immer eine ungünstige Sequenz von Schlüsseln gibt, die auf
den selben Slot abgebildet werden.
Idee
Wähle zufällig eine Hashfunktion aus einer gegebenen kleinen Menge H,
unabhängig von den verwendeten Schlüsseln.
Eine Menge Hashfunktionen ist universell, wenn
I der Anteil der Funktionen aus H, so dass k und k 0 kollidieren ist
I
d. h., die W’lichkeit einer Kollision von k und k 0 ist
1
|H|
·
|H|
m
=
|H|
m .
1
m.
Für universelles Hashing ist die erwartete Länge der Liste T[k]
1. Gleich α, wenn k nicht in T enthalten ist.
2. Gleich 1+α, wenn k in T enthalten ist.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
36/37
Hashing I
Hashfunktionen
Universelles Hashing (II)
Beispiel
Definiere die Elemente der Klasse H von Hashfunktionen durch:
ha,b (k) = ((a · k + b) mod p) mod m
I
p sei Primzahl mit p > m und p > größter Schlüssel.
I
Die ganzen Zahlen a (1 6 a < p) und b (0 6 b < p) werden erst bei
der Ausführung gewählt.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
37/37
Hashing I
Hashfunktionen
Universelles Hashing (II)
Beispiel
Definiere die Elemente der Klasse H von Hashfunktionen durch:
ha,b (k) = ((a · k + b) mod p) mod m
I
p sei Primzahl mit p > m und p > größter Schlüssel.
I
Die ganzen Zahlen a (1 6 a < p) und b (0 6 b < p) werden erst bei
der Ausführung gewählt.
Die Klasse der obigen Hashfunktionen ha,b ist universell.
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
37/37
Hashing I
Hashfunktionen
Nächste Vorlesung
Nächste Vorlesung
Donnerstag 21. Mai, 10:15 (Aula Hauptgebäude). Bis dann!
Joost-Pieter Katoen
Datenstrukturen und Algorithmen
38/37