Algorithmen und Datenstrukturen Prof. Martin Lercher Institut für Informatik Heinrich-Heine-Universität Düsseldorf Teil 4 Suchen in Texten Version vom: 15. November 2016 1 / 39
Vorlesung 8 15. November 2016 2 / 39
Suchen in Texten Häufig wird in einer Folge von Zeichen (in einem Text) eine Zeichenkette (ein Muster) gesucht (Pattern Matching). Man interessiert sich entweder für ein oder für alle Vorkommen des Musters im Text. Beispiele: 1 Suchmaschinen 2 Textverarbeitungsprogramme 3 Editoren 4 Filesuche 5 Mustersuche in DNA-Sequenzdaten 6 Plagiaterkennung 3 / 39
Suchen in Texten Textsuche Gegeben: Alphabet Σ, Text T Σ, T = a 0,..., a n 1 mit a i Σ und Muster P Σ, P = b 0,..., b m 1 mit b i Σ Aufgabe: Berechne i N 0 mit 0 i n m und i ist die Anfangsposition des Musters P im Text T, d.h., a i = b 0, a i+1 = b 1,..., a i+m 1 = b m 1, oder i = n falls das Muster P nicht im Text T enthalten ist. 4 / 39
Suchen in Texten - Einfache Suche Methode: Durchsuche den gesamten Text schrittweise von links nach rechts und vergleiche bei jeder Position von links das Muster zeichenweise mit dem Text. Vergleiche das Zeichen im Muster auf Position j mit dem Zeichen im Text auf Position i. Verschiebe das Wort bei einem Mismatch um eine Position nach rechts. Setze i auf i j + 1 und j auf 0. Erhöhe bei einem Match beide Indices um 1. Setze i auf i + 1 und j auf j + 1. Bei j = m wurde ein Vorkommen des Musters im Text gefunden. Setze i auf i m + 1 und j auf 0. 5 / 39
Suchen in Texten - Einfache Suche (1) int naive search (char T [ ], int n, char P[ ], int m) (2) { (3) if (m > 0) { (4) int i := 0; (5) int j := 0; (6) while ((i < n) and (j < m)) { (7) if (T [i] = P[j]) { (8) i + +; (9) j + +; (10) } (11) else { (12) i := i + 1 j; (12) j := 0; (13) } (14) } (15) if (j = m) { (16) return(i m); (17) } (18) } (19) return(n); (20) } 6 / 39
Suchen in Texten - Einfache Suche Beispiel Als Clowns verkleidete Gauner sind meist leicht zu. ^ (Mismatch) ^ (Mismatch)... ^ (Mismatch) ^ (Mismatch) ^ (Mismatch) ^ (Mismatch) 7 / 39
Suchen in Texten - Einfache Suche Beispiel Als Clowns verkleidete Gauner sind meist leicht zu.... ^ (fertig) 8 / 39
Suchen in Texten - Einfache Suche Analyse: Der naive Algorithmus führt im ungünstigsten Fall viele Vergleiche durch. (n m + 1) m O(n m) 9 / 39
Suchen in Texten - Knuth-Morris-Pratt Das Verfahren von Knuth-Morris-Pratt: Methode: Vergleiche das Zeichen im Muster auf Position j mit dem Zeichen im Text auf Position i. Wenn das Zeichen im Muster auf Position j nicht mit dem Zeichen im Text auf Position i übereinstimmt, dann setze j auf die größte Position l zwischen 0 und j 1 zurück, so das b 0,..., b l 1 = a i l,..., a i 1. Speichere die Positionen im next-array. Donald Erwin Knuth Autor von The Art of Computer programming Entwickler von TEX( L A TEX) 10 / 39
Suchen in Texten - Knuth-Morris-Pratt Beispiel Beispiel: i i i T...011011......011011......011011... P 011010 011010 011010 j j j Sei next[j] die von rechts her nächste Stelle im Muster, die man mit der i-ten Position im Text vergleichen muss, falls T [i] und P[j] verschieden sind. Beispiel Das Array next[] wird für das Muster in einer Vorphase vorausberechnet. P = 0 1 0 1 0 1 1 0 0 j = 0 1 2 3 4 5 6 7 8 next[j] = -1 0 0 1 2 3 4 0 1 11 / 39
Suchen in Texten - Knuth-Morris-Pratt Berechnung des next-arrays (1) void init next array (int next[ ], char P[ ], int m) (2) { (3) if (m > 0) { (4) int i := 0; (5) int j := 1; (6) next[0] := 1; (7) while (i < m 1) { (8) if ((j = 1) or (P[i] = P[j])) { (9) i + +; (10) j + +; (11) next[i] := j; } (12) } (13) else { (14) j := next[j]; (15) } (16) } (17) } (18) } 12 / 39
Suchen in Texten - Knuth-Morris-Pratt Textsuche mit dem berechneten next-array (1) int kmp search (char T [ ], int n, char P[ ], int m, int next[ ]) (2) { (3) if (m > 0) { (4) int i := 0; (5) int j := 0; (6) while ((i < n) and (j < m)) { (7) if ((j = 1) or (T [i] = P[j])) { (8) i + +; (9) j + +; (10) } (11) else { (12) j := next[j]; (13) } (14) } (15) if (j = m) { (16) return(i m); (17) } (18) } (19) return(n); (20) } 13 / 39
Suchen in Texten - Knuth-Morris-Pratt Analyse: Variable j kann nur so oft verkleinert werden, wie sie zuvor erhöht wurde. Variable j wird immer zusammen mit Variable i erhöht. Also wird j nur so oft verkleinert, wie i erhöht wird. Ist das next-array bekannt, so hat das Verfahren eine lineare Laufzeit O(n). Da das next-array in O(m) Schritten aufgebaut werden kann, benötigt der gesamte Algorithmus O(n + m) Schritte. 14 / 39
Das Verfahren von Boyer-Moore: Methode: Beim Verfahren von Boyer und Moore werden die Zeichen im Muster von rechts nach links mit den Zeichen im Text verglichen. Wenn es beim Vergleich der Zeichen im Text mit den Zeichen im Muster zu einem Mismatch kommt, dann verschiebe das Muster so weit wie möglich nach rechts, jedoch maximal um die Musterlänge. So weit wie möglich = setze j auf die Position des letzten Vorkommens von T [i] im Muster P. i := Position des ersten Mismatches (von rechts gesehen) im Text j := zugehörige Position im Muster (Achtung: j läuft beim Vergleich rückwärts: m 1,..., 0) 15 / 39
Beispiel Als Clowns verkleidete Gauner sind meist leicht zu. ^ (Mismatch) ^ (Mismatch) ^ (Mismatch)... ^ (Mismatch) ^ (fertig) 16 / 39
Das vereinfachte Verfahren (Heuristik) nach Boyer und Moore: Eine Verschiebung des Musters um 1 entspricht einer Erhöhung von i um (m 1 j) + 1 also um m j. Eine Verschiebung des Musters um k entspricht einer Erhöhung von i um (m 1 j) + k. Der sogenannte delta-1-wert (bad character shift) beschreibt für jedes Zeichen c im Text das erste Vorkommen des Zeiches im Muster (von rechts gezählt). Kommt c im Muster nicht vor, so ist delta-1(c) = m. 17 / 39
Beispiel Alphabet: Σ = {a, b, c, d} P = a b c a b a delta-1(a) = 0 delta-1(b) = 1 delta-1(c) = 3 delta-1(d) = 6 18 / 39
(1) int bm suche (char T [], int n, char P[], int m) (2) { (3) int i := m 1; (4) int j := m 1; (5) while ((j 0) and (i n 1)) { (6) if (T [i] = P[j]) { (7) i ; (8) j ; (9) } (10) else { (11) i := i + Musterverschiebung ; // Muss noch definiert werden! (12) j := m 1; // Neuanfang am Ende des Musters (13) } (14) } (15) if (j < 0) { (16) return (i + 1); // gefunden! (17) } (18) else (19) return (n); // nicht gefunden. (20) } (21) } 19 / 39
In dem vereinfachten Verfahren von Boyer und Moore wird immer dann, wenn es zu einem Mismatch kommt, i auf i + max{(m 1 j) + 1, delta-1(t [i])} und j auf m 1 gesetzt. Bemerkungen: Kommt es zu einem Mismatch zwischen T [i] und P[j], dann wird das Muster so weit nach rechts geschoben, bis das Zeichen T [i] im Text über dem rechtesten Vorkommen von T [i] im Muster steht. Kommt das Zeichen T [i] im Muster gar nicht vor, dann wird das Muster so weit nach rechts geschoben, bis das Zeichen T [i] im Text vor dem ersten Zeichen im Muster steht. Das Muster wird jedoch mindestens um eine Position nach rechts geschoben (i := i + m j). 20 / 39
Obwohl obiges Verfahren eine worst-case-laufzeit von O(n m) hat (T = 00 0, P = 100 0), hat es sich in der Praxis ausgezeichnet bewährt. Für genügend kurze Muster und hinreichend große Alphabete werden im Mittel O(n/m) viele Schritte durchgeführt. Das Muster wird dann also nahezu immer um seine Gesamtlänge nach rechts verschoben. 21 / 39
Kleine Modifikation Der delta-1-wert für das letzte Zeichen P[m 1] im Muster P kann auch auf das zweite Vorkommen des Zeichens im Muster (von rechts gezählt) gesetzt werden, da ein delta-1-wert von 0 eine Musterverschiebung von 0 entspricht. Beispiel mit Alphabet Σ = {a, b, c, d}: P = a b c a b a delta -1(a) = 2 delta -1(b) = 1 delta -1(c) = 3 delta -1(d) = 6 Hier wird das Muster so weit nach rechts geschoben, dass das zweite Vorkommen von rechts des letzten Zeichens im Muster unter der Position i + (m 1 j) im Text steht. 22 / 39
Verbesserung des einfachen Verfahrens von Boyer und Moore Die Sub-Muster im Muster können ähnlich wie bei dem Verfahren von Knuth Morris und Pratt mit berücksichtigt werden. Der sogenannte delta-2-wert beschreibt, um welchen Wert i maximal erhöht werden kann, wenn an einer Position j im Muster ein Mismatch eintritt. Genutzt wird die Information, dass auf den Positionen i + 1,..., i + (m 1 j) im Text die gleichen Zeichen stehen, wie im Muster auf den Positionen j,..., m 1. Wird zusätzlich noch die Information genutzt, dass auf Position i im Text ein anderes Zeichen steht als auf Position j im Muster, dann erhalten wir den verbesserten delta-2-wert. 23 / 39
Beispiel P = 1 0 0 1 0 1 0 1 0 j = 0 1 2 3 4 5 6 7 8 delta-2(j) = 15 14 13 7 6 5 4 3 1 i T...x... j j = 8 i i := i + 1 T...x... j j := m-1 24 / 39
Beispiel i T...x0... j j = 7 i i := i + 3 T...x0... j j := m-1 i T...x10... j j = 6 i i := i + 4 T...x10... j j := m-1 25 / 39
Beispiel i T...x010... j j = 5 i i := i + 5 T...x010... j j := m-1 i T...x1010... j j = 4 i i := i + 6 T...x1010... j j := m-1 26 / 39
Beispiel i T...x01010... j j = 3 i i := i + 7 T...x01010... j j := m-1 i T...x101010... j j = 2 i i := i + 13 T...x101010... j j := m-1 27 / 39
Beispiel i T...x0101010... j j = 1 i i := i + 14 T...x0101010... j j := m-1 i T...x00101010... j j = 0 i i := i + 15 T...x00101010... j j := m-1 28 / 39
In dem erweiterten Verfahren von Boyer und Moore wird immer dann, wenn es zu einem Mismatch auf Position j im Muster kommt, i auf und j auf gesetzt. Bemerkungen: i + max{delta -1(T [i]), delta-2[j]} m 1 Diese Verbesserung beseitigt zwar die worst-case Instanzen bei der Textsuche (wie bei Knuth-Morris-Pratt, hier ohne Beweis), bringt aber in der Praxis eigentlich nichts. Deshalb wird in der Regel mit der vereinfachten Version gearbeitet. Da der delta-2-wert mindestens m j ist, kann m j (Musterverschiebung um 1) aus der Maximumbildung herausgenommen werden. 29 / 39
Beispiel Beispiel für den verbesserten delta-2-wert. P = 1 0 0 1 0 1 0 1 0 j = 0 1 2 3 4 5 6 7 8 delta-2(j) = 15 14 13 7 11 7 9 7 1 i T...x... x nicht 0 j j = 8 i i := i + 1 T...x... j j := m-1 30 / 39
Beispiel i T...x0... x nicht 1 j j = 7 i i := i + 7 T...x0... j j := m-1 i T...x10... x nicht 0 j j = 6 i i := i + 9 T...x10... j j := m-1 31 / 39
Beispiel i T...x010... x nicht 1 j j = 5 i i := i + 7 T...x010... j j := m-1 i T...x1010... x nicht 0 j j = 4 i i := i + 11 T...x1010... j j := m-1 32 / 39
Beispiel i T...x01010... x nicht 1 j j = 3 i i := i + 7 T...x01010... j j := m-1 i T...x101010... x nicht 0 j j = 2 i i := i + 13 T...x101010... j j := m-1 33 / 39
Beispiel i T...x0101010... x nicht 0 j j = 1 i i := i + 14 T...x0101010... j j := m-1 i T...x00101010... x nicht 1 j j = 0 i i := i + 15 T...x00101010... j j := m-1 34 / 39
(1) int bm suche (char T [ ], int n, char P[ ], int m) (2) { (3) int i := m 1; (4) int j := m 1; (5) while ((j 0) and (i n 1)) { (6) if (T [i] = P[j]) { (7) i ; (8) j ; (9) } (10) else { (11) i := i + max{delta -1(T [i]), delta-2[j]}; (12) j := m 1; (13) }; (14) } (15) if (j < 0) { (16) return (i + 1); (17) } (18) else (19) return (n); (20) } (21) } 35 / 39
-Horspool Das Verfahren nach Boyer-Moore vereinigt die Vorteile der Match-Heuristik von Knuth-Morris-Pratt und der Occurrence-Heuristik. Es ist damit eines der theoretisch wie praktisch schnellsten Verfahren. Allerdings ist die Match-Heuristik umständlich zu implementieren. Eine vereinfachte Variante wurde von Horspool vorgeschlagen. Dieses Verfahren implementiert eine Modifikation der Occurrence-Heuristik und ist in der Praxis äusserst schnell (obwohl die worst-case Laufzeit in Θ(m n) liegt). 36 / 39
-Horspool Horspools Verfahren verschiebt immer entsprechend der letzten Position im Text, nicht entsprechend der Position die einen Mismatch ergab. Dadurch wird das Muster immer nach rechts verschoben, ohne dass wir eine Fallunterscheidung machen müssen. Wir müssen also für jeden Buchstaben des Alphabets Σ wissen, wo er im Muster am weitesten rechts (ausser ganz am Ende) vorkommt: shift[w] = { m 1 max{i < m 1 P[i] = w} falls w in P[0... m 2] m sonst 37 / 39
-Horspool (1) int bmh suche (char T [ ], int n, char P[ ], int m) (2) { (3) // Berechnung der shift-tabelle (4) for (k = 0; k < Σ ; k + +) (5) shift[k] := m ; (6) for (k = 0; k < m 1; k + +) (7) shift[p[k]] := m 1 k ; (8) // Suche (9) i := 0 ; (10) while (i + m n) { (11) j := m 1 ; (12) while (T [i + j] = P[j]) { (13) j := j ; (14) if (j < 0) (15) return(i) ; // gefunden! (16) } (17) i := i + shift[t [i + m 1]] ; // shift entspr. dem letzten Buchstaben (18) } (19) return(-1) ; // nicht gefunden. (20) } 38 / 39
-Horspool Beispiel Suche Date in Der Detektiv hatte ein Date mit der Mutter aller Clowns. 39 / 39