Rabin-Karp (Algorithmus 5.8)
Die Rabin-Karp-Fingerprint-Suche aus Sedgewick/Wayne, Algorithmus 5.8 (Listing 5.21 und 5.22) – Hash des Musters nach dem Horner-Schema, dann ein rollender Hash über den Text, am Zahlenbeispiel 26535 in 3141592653589793 wie in Abbildung 5.47.
So geht’s: Beim Öffnen läuft das Zahlenbeispiel aus dem Buch: Muster 26535 im Text 3141592653589793, gerechnet wie in Abbildung 5.47, also jede Ziffer als Zahl, R = 10 und Q = 997. Zuerst berechnet der Konstruktor RM = 10⁴ % 997 = 30 und den Hash des Musters nach dem Horner-Schema (Abbildung 5.45). Dann bekommen die ersten fünf Textzeichen ihren Hash. Danach rückt das Fenster Zeichen für Zeichen weiter: Die führende Ziffer fällt heraus (orange), die neue kommt dazu (blau), und rechts steht die Rechnung mit echten Zahlen. Sind die Hashwerte gleich, wird das Fenster grün. „Kollision“ zeigt, warum Monte Carlo irren kann. Im Text steht 01610, das hat ebenfalls den Hash 613, und die Suche meldet dort einen falschen Treffer, rot markiert. „Text mit R = 256“ rechnet wie der Java-Code mit Zeichencodes. Eigene Muster und Texte gibst du oben ein, Ziffern für die Rechenart wie im Buch, beliebige Zeichen für die Rechenart wie im Code.
Erklärung
Rabin-Karp-Fingerprint-Suche (Algorithmus 5.8), Klasse RabinKarp. Teilstringsuche über Hashing statt über Zeichenvergleiche.
Idee:
- Aus dem Muster einen Hashwert berechnen (den „Fingerabdruck“).
- Im Text für jedes Fenster der Länge M ebenfalls den Hashwert berechnen und vergleichen.
- Gleicher Hash → (fast sicher) Treffer.
- Der Trick: Der Hash des nächsten Fensters lässt sich aus dem vorigen in konstanter Zeit ausrechnen, ohne alle M Zeichen neu anzuschauen.
Der Hash:
- Die M Zeichen werden als Zahl zur Basis R = 256 aufgefasst, modulo einer großen Primzahl Q. Berechnet mit dem Horner-Schema (Listing 5.21, hier nicht abgedruckt).
- Q wird zufällig gewählt (
longRandomPrime()), damit kein böswilliger Text gezielt Kollisionen erzeugen kann. RM= R^(M−1) mod Q, vorab berechnet. Das ist der Stellenwert der vordersten „Ziffer“.
Rollender Hash, zuerst im Dezimalsystem ohne Modulo zur Anschauung: Text 31415926, Fensterlänge 3.
- Erstes Fenster 314.
- Nächstes: führende Ziffer weg, eine Stelle nach links, neue Ziffer dran: (314 − 3 · 100) · 10 + 1 = 141.
- Nächstes: (141 − 1 · 100) · 10 + 5 = 415.
- Genau das machen die zwei Zeilen in der Schleife, nur mit Basis 256 und modulo Q:
txtHash = (txtHash + Q − RM · altesZeichen % Q) % Qentfernt die führende Ziffer. Das+ Qverhindert negative Zwischenwerte.txtHash = (txtHash · R + neuesZeichen) % Qhängt die neue an.
Monte Carlo vs. Las Vegas (die Methode check):
- Monte Carlo (wie abgedruckt,
checkliefert immertrue): Gleicher Hash wird als Treffer gewertet. Garantiert linear, aber mit einer winzigen Wahrscheinlichkeit falsch (etwa 1/Q, bei Q in der Größenordnung von 10^20 praktisch nie). - Las Vegas:
checkvergleicht bei gleichem Hash tatsächlich die Zeichen. Immer korrekt, und mit extrem hoher Wahrscheinlichkeit trotzdem linear. - Behauptung P im Buch fasst genau das zusammen.
Kosten: Linear in N, aber mit einer relativ langen inneren Schleife: pro Textzeichen mehrere Multiplikationen und Modulo-Operationen. In der Praxis deshalb oft langsamer als die anderen Verfahren. Dafür kein Zusatzspeicher und leicht erweiterbar, etwa auf die Suche nach vielen Mustern gleichzeitig oder auf zweidimensionale Muster.
Die Teilstringsuche im Überblick (nach Tabelle 5.11 im Buch):
| Verfahren | Schlimmster Fall | Typisch | Zurück im Text | Immer korrekt | Zusatzspeicher |
|---|---|---|---|---|---|
| Brute Force | M · N | 1,1 N | ja | ja | 1 |
| KMP, voller Automat (5.6) | 2N | 1,1 N | nein | ja | M · R |
| Boyer-Moore, nur Heuristik (5.7) | M · N | N / M | ja | ja | R |
| Rabin-Karp Monte Carlo (5.8) | 7N | 7N | nein | sehr wahrscheinlich | 1 |
| Rabin-Karp Las Vegas (5.8) | 7N, sehr wahrscheinlich | 7N | ja | ja | 1 |
Faustregel: Brute Force reicht oft. Garantie oder Datenstrom → KMP. Lange Muster in normalem Text → Boyer-Moore. Viele Muster oder ungewöhnliche Strukturen → Rabin-Karp.
Kapitel 5 bisher:
- Algorithmus 5.1: LSD-Sortierverfahren [43]
- Algorithmus 5.2: MSD-Sortierverfahren [44]
- Algorithmus 5.3: 3-Wege-Quicksort für Strings [45]
- Algorithmus 5.4: Trie-Symboltabelle [46]
- Algorithmus 5.5: TST-Symboltabelle [47]
- Algorithmus 5.6: Knuth-Morris-Pratt [48]
- Algorithmus 5.7: Boyer-Moore [49]
- Algorithmus 5.8: Rabin-Karp [50]