Datenstrukturen III (Schlüsselbasiert)

Schlüsselbasierte Datenstrukturen, die unauffällig fast jedes System antreiben.

Grundlagen 15 min Fortgeschritten 3. Mai 2026

Arrays bieten O(1)-Zugriff über die Position. Binäre Suchbäume liefern O(log n)-Zugriff über die Reihenfolge. Aber was, wenn man Daten über einen beliebigen Schlüssel nachschlagen will — einen Benutzernamen, eine URL oder ein Session-Token? Hash Tables vervollständigen das Bild: Sie wandeln jeden Schlüssel in eine Array-Position um, und zwar in konstanter Zeit.

Dieser Artikel behandelt die drei Säulen schlüsselbasierter Datenstrukturen: den Hash-Trick selbst, das Kollisionsproblem, das jede Hash Table lösen muss, und das Caching-Prinzip, das den Hash-Trick in der Praxis ausnutzt.

Hash Tables: Der O(1)-Trick

Hash Table

AnalogieDefinition
Stell dir eine riesige Bibliothek vor. Du könntest jedes Regal durchsuchen — O(n). Oder du nutzt einen alphabetischen Katalog, der den Suchraum jedes Mal halbiert — O(log n), wie ein Binärbaum. Oder du flüsterst dem magischen Bibliothekar den Titel zu, und er nennt dir sofort die exakte Regalnummer — O(1). Der Bibliothekar ist die Hash-Funktion: eine mechanische Regel, die den Titel in eine Nummer umwandelt, ohne ihn zu verstehen.

Aber Vorsicht: Ein echter Bibliothekar erkennt Ähnlichkeiten und korrigiert Tippfehler. Eine Hash-Funktion behandelt selbst kleinste Unterschiede als völlig verschiedene Schlüssel (Avalanche-Effekt). Und was passiert, wenn zwei Titel auf dasselbe Regal zeigen? Der magische Bibliothekar hat dieses Problem nicht — Hash Tables schon.

Beispiel: Hash-Berechnung

Hash Table mit 10 Slots:

hash("cat") = (99 + 97 + 116) % 10 = 312 % 10 = 2
hash("dog") = (100 + 111 + 103) % 10 = 314 % 10 = 4

Nachschlagen von "cat":
→ Hash berechnen → 2
→ Zu Slot 2 springen
→ Fertig. Ein Schritt statt bis zu 10.

Die ASCII-Werte der Buchstaben werden addiert und mit Modulo auf die Tabellengröße abgebildet. Das ergibt eine direkte Adresse — kein Suchen, kein Sortieren, einfach berechnen und springen.

Array: O(1) per Index

Direktzugriff über Positionsnummer. Schnell, wenn du die Position kennst — aber du musst die Nummer wissen.

Hash Table: O(1) per Schlüssel

Direktzugriff über beliebigen Schlüssel (Name, URL, ID). Die Hash-Funktion berechnet die Position automatisch.

Irrtum: Hash Tables sind immer O(1)

O(1) ist der Durchschnittsfall bei einer guten Hash-Funktion und moderatem Füllgrad. Im schlimmsten Fall — viele Kollisionen, hoher Füllgrad — degradiert der Zugriff auf O(n). Deshalb existiert Rehashing: Die Tabelle wird vergrößert und alle Einträge neu verteilt.

Kollisionen: Wenn zwei Schlüssel denselben Slot wollen

Eine Kollision tritt auf, wenn zwei verschiedene Schlüssel auf denselben Index gehasht werden. Das Schubfachprinzip garantiert, dass dies unvermeidlich ist: Wenn es mehr mögliche Schlüssel als Buckets gibt, müssen sich einige teilen.

Stell dir eine Garderobe mit 100 nummerierten Haken vor. Der Garderobier vergibt Haken nach dem Anfangsbuchstaben des Nachnamens — A bekommt Haken 1, B bekommt Haken 2. Wenn zwei Gäste namens "Schmidt" und "Schwarz" beide auf Haken 19 treffen, hat der Garderobier zwei Optionen: beide Mäntel am selben Haken aufhängen und später durchsuchen (Separate Chaining) oder den zweiten Mantel am nächsten freien Haken aufhängen (Open Addressing).

Ein echter Garderobier wird bei Platzmangel kreativ — er stapelt Mäntel, hängt sie an die Tür oder legt sie auf einen Stuhl. Eine Hash Table kann nicht improvisieren. Sie braucht strikte, vorprogrammierte Regeln (Chaining oder Probing) für jeden Kollisionsfall.

Kollisionsstrategien

Separate Chaining Jeder Bucket enthält eine verkettete Liste. Bei einer Kollision wird der neue Eintrag an die Liste angehängt. Suche: Hash berechnen, dann die Liste im Bucket durchlaufen.
Open Addressing Bei einer Kollision wird der nächste freie Slot gesucht (lineares Probing, quadratisches Probing oder Double Hashing). Alle Einträge bleiben direkt im Array.

Beispiel: Kollision in der Praxis

Hash Table mit 7 Slots, h(key) = len(key) % 7:

"cat" → len=3 → 3 % 7 = 3
"dog" → len=3 → 3 % 7 = 3  ← Kollision!
"elephant" → len=8 → 8 % 7 = 1
"bird" → len=4 → 4 % 7 = 4

Separate Chaining an Slot 3:
[("cat", val1) → ("dog", val2)]

"cat" und "dog" haben beide 3 Buchstaben — ihre Hashes kollidieren an Slot 3. Mit Separate Chaining hält der Slot eine verkettete Liste beider Einträge. Die Suche nach "dog" braucht 2 Vergleiche statt 1, bleibt aber schnell. Bei steigendem Füllgrad wachsen die Ketten — ab 75% wird typischerweise ein Rehashing ausgelöst.

Rehashing: Wenn die Tabelle voll wird

Vorher und Nachher: Rehashing

Vor Rehashing: 75% Füllgrad, lange Ketten, langsamere Suche
Nach Rehashing: Doppelte Größe, kurze Ketten, schnelle Suche wiederhergestellt

Irrtum: Kollisionen bedeuten, die Hash Table ist kaputt

Kollisionen sind durch das Schubfachprinzip mathematisch garantiert. Eine gut entworfene Hash Table behandelt sie effizient. Es wird erst problematisch, wenn die Hash-Funktion schlecht verteilt oder der Füllgrad ohne Rehashing zu hoch steigt.

Interaktiv: Hash-Demonstrator

Gib einen Schlüssel ein und beobachte, wie die Hash-Funktion daraus eine Slot-Nummer berechnet. Füge mehrere Schlüssel ein und erlebe Kollisionen live — wenn zwei verschiedene Schlüssel auf denselben Slot abgebildet werden.

0
1
2
3
4
5
6
7
8
9

Caching: Speicher tauscht gegen Geschwindigkeit

Caching speichert häufig oder kürzlich genutzte Daten in schnellem Speicher, um langsame Neuberechnungen oder Netzwerkaufrufe zu vermeiden. Die Kernbegriffe: Cache Hit (Daten lokal gefunden — schnell), Cache Miss (nicht im Cache — muss von der langsamen Quelle geholt werden) und Cache-Invalidierung (veraltete Einträge entfernen).

Die Pizza-im-Kühlschrank-Analogie: Pizza bestellen dauert 30-40 Minuten (Daten von der Originalquelle holen). Übrig gebliebene Pizza im Kühlschrank aufwärmen geht in Minuten (Cache Hit). Aber am nächsten Tag könnte sie nicht mehr frisch sein (veraltete Daten). Du musst entscheiden, wann du sie wegwirfst und neu bestellst (Cache-Invalidierung / TTL).

Im echten Leben verdirbt Pizza langsam — man merkt es am Geruch oder Geschmack. Im digitalen Cache gibt es keinen graduellen Verfall: Daten sind entweder gültig oder veraltet, ohne Zwischenstufe. Deshalb muss die Invalidierung durch exakte Metadaten wie max-age gesteuert werden, nicht durch Intuition.

Speicherhierarchie: Latenzen im Vergleich

1
L1-Cache: ~1 ns (schnellster, kleinster Speicher)
2
L2-Cache: ~5 ns
3
L3-Cache: ~20 ns
4
RAM: ~100 ns
5
SSD: ~100 μs (1.000× langsamer als RAM)
6
Netzwerk: ~100 ms (1.000.000× langsamer als RAM)

Praxisbeispiel: Browser-Caching

Erster Besuch: Browser lädt HTML, CSS und Bilder vom Server (~2 Sekunden). Der Server sendet Cache-Control: max-age=3600. Zweiter Besuch innerhalb einer Stunde: Browser liefert aus dem lokalen Cache (~50 ms). Nach einer Stunde: Cache abgelaufen, Browser holt die Daten erneut. Ergebnis: Bis zu 50% schnellere wahrgenommene Ladezeit für wiederkehrende Besucher.

Irrtum: Caching macht immer alles schneller

Caching hilft nur, wenn Daten wiederholt abgerufen werden. Bei einmaligen Abfragen dominieren Cache Misses, und der Cache verschwendet nur Speicher. Außerdem liefert aggressives Caching ohne saubere Invalidierung veraltete Daten. Daher der berühmte Spruch: "Die zwei schwierigen Dinge in der Informatik: Cache-Invalidierung und Namensgebung."

In großen Sprachmodellen (LLMs) werden Hash Tables für die Token-zu-ID-Zuordnung verwendet: Jedes Wort oder Teilwort wird auf eine Ganzzahl abgebildet, die als Index in die Embedding-Matrix dient. Auch bei der Datendeduplizierung spielt Hashing eine zentrale Rolle — identische Trainingsdaten werden über ihren Hash erkannt und gefiltert. Der KV-Cache (Key-Value-Cache) in Transformer-Modellen ist ein weiteres Beispiel: Bereits berechnete Attention-Werte werden gecacht, um bei der Token-für-Token-Generierung keine Neuberechnung durchführen zu müssen.

Kernaussagen

  1. Eine Hash-Funktion wandelt jeden Schlüssel in einen Array-Index um — das ist der "Zaubertrick" hinter dem durchschnittlichen O(1)-Nachschlagen in Dictionaries, Maps und Caches.
  2. Caching tauscht Speicher gegen Geschwindigkeit: Speichere die Antwort einmal, überspringe die teure Berechnung oder den Netzwerkaufruf beim nächsten Mal — aber veraltete Daten sind der Preis ohne saubere Invalidierung.
  3. Kollisionen sind keine Fehler — sie sind mathematisch unvermeidlich. Gute Hash Tables behandeln sie elegant durch Chaining oder Probing und halten die Performance durch Rehashing stabil.

Quiz: Schlüsselbasierte Datenstrukturen

Frage 1 / 4
Noch offen

Was macht eine Hash-Funktion?

Wählen Sie eine Antwort
Auflösung: 1) B · 2) C · 3) B · 4) B

Checkpoint: Schlüsselbasierte Datenstrukturen

  • Du speicherst das Token 'A1B2' in einer Hash Table mit 10 Slots. Die Hash-Funktion berechnet den Wert 487. In welchem Slot landet das Token — und warum?
  • Zwei User 'Müller' und 'Meier' landen durch die Hash-Funktion beide in Slot 13. Wie nennt man dieses Ereignis und welche zwei Strategien gibt es?
  • Dein Wetter-Widget lädt Temperaturen vom Server und nutzt aggressives Caching ohne Invalidierung. Welches Problem entsteht für die Nutzer?