Algorithmen und Datenstrukturen II AG Praktische Informatik Technische Fakultät Vorlesung Sommer 2009
Teil I Algorithmen zur exakten Suche in Texten
Die Klasse String Zeichenketten sind in Java Objekte. Sie können mit dem +-Operator zu neuen Objekten verknüpft werden. String str1 = "hello"; String str2 = " world"; String str3 = str1 + str2;
Grundlegende objektbezogene Methoden der Klasse String: Beispiel public int length() liefert die Anzahl der Zeichen in der Zeichenkette. Beispiel public char charat(int index) liefert das Zeichen an der Position index der Zeichenkette. Beispiel public int indexof(char ch, int fromindex) liefert die erste Position fromindex, an der das Zeichen ch in der Zeichenkette vorkommt, bzw. 1, falls das Zeichen nicht vorkommt.
Beispiel public int lastindexof(char ch, int fromindex) liefert die letzte Position fromindex, an der das Zeichen ch in der Zeichenkette vorkommt, bzw. 1, falls das Zeichen nicht vorkommt (lastindexof liest also im Gegensatz zu indexof die Zeichenkette von rechts nach links). Beispiel public boolean startswith(string prefix) liefert true gdw. die Zeichenkette mit dem angegebenen prefix beginnt. Beispiel public boolean endswith(string suffix) liefert true gdw. die Zeichenkette mit dem angegebenen suffix endet.
Beispiel public String substring(int beginindex, int endindex)] liefert das Teilwort der Zeichenkette zwischen den Positionen beginindex und endindex 1. Beispiel public char[] tochararray() wandelt die Zeichenkette in ein Feld von Zeichen um. Beispiel public boolean regionmatches (int start, String other, int ostart, int len) liefert true gdw. der Bereich der Zeichenkette mit dem Bereich der Zeichenkette String other übereinstimmt (s.u.). Der Vergleich beginnt bei der Position start bzw. ostart. Es werden nur die ersten len Zeichen verglichen.
Beispiel public boolean equals(object anobject) liefert true gdw. die übergebene Objektreferenz auf ein Objekt vom Typ String mit demselben Inhalt zeigt. (Dies steht im Gegensatz zu ==, was die Objekt-(referenz-)gleichheit testet.)
Beispiel public class DemoStringClass { public static void main(string[] args) { String str = "bielefeld"; System.out.println(str.length()); System.out.println(str.charAt(0)); System.out.println(str.indexOf( e,3)); System.out.println(str.lastIndexOf( e,3)); System.out.println(str.startsWith("biele")); System.out.println(str.endsWith("feld")); System.out.println(str.substring(0,5)); System.out.println(str.substring(5,9)); System.out.println( str.regionmatches(0,"obiele",1,5)); if(str == args[0]) System.out.println("str == args[0]"); if(str.equals(args[0])) System.out.println("str.equals(args[0])"); } } > java DemoStringClass bielefe 9 b 4 2 true true biele feld true str.equals(args[0])
Grundlegende Definitionen Die Suche nach allen (exakten) Vorkommen eines Musters in einem Text ist ein Problem, das häufig auftritt (z.b. in Editoren, Datenbanken etc.). Wir vereinbaren folgende Konventionen (wobei Σ ein Alphabet ist): Muster P = P[0... m 1] Σ m Text T = T [0... n 1] Σ n P[j] = Zeichen an der Position j P[b... e] = Teilwort von P zwischen den Positionen b und e
Grundlegende Definitionen Die Suche nach allen (exakten) Vorkommen eines Musters in einem Text ist ein Problem, das häufig auftritt (z.b. in Editoren, Datenbanken etc.). Wir vereinbaren folgende Konventionen (wobei Σ ein Alphabet ist): Muster P = P[0... m 1] Σ m Text T = T [0... n 1] Σ n P[j] = Zeichen an der Position j P[b... e] = Teilwort von P zwischen den Positionen b und e Beispiel P = abcde, P[0] = a, P[3] = d, P[2... 4] = cde.
Notation leeres Wort: ε Länge eines Wortes x: x Konkatenation zweier Worte x und y: xy
Präfixe und Suffixe Definition Ein Wort w ist ein Präfix eines Wortes x (w x), falls x = wy gilt für ein y Σ. w ist ein Suffix von x (w x), falls x = yw gilt für ein y Σ. x ist also auch ein Präfix bzw. Suffix von sich selbst.
Präfixe und Suffixe Definition Ein Wort w ist ein Präfix eines Wortes x (w x), falls x = wy gilt für ein y Σ. w ist ein Suffix von x (w x), falls x = yw gilt für ein y Σ. x ist also auch ein Präfix bzw. Suffix von sich selbst. Lemma Es seien x, y und z Zeichenketten, so dass x z und y z gilt. Wenn x y ist, dann gilt x y. Wenn x y ist, dann gilt y x. Wenn x = y ist, dann gilt x = y.
Das Problem der exakten Suche Definition Ein Muster P kommt mit der Verschiebung s im Text T vor, falls 0 s n m und T [s... s + m 1] = P[0... m 1] gilt. In dem Fall nennen wir s eine gültige Verschiebung. Das Problem der exakten Textsuche ist nun, alle gültigen Verschiebungen zu finden. Mit den Bezeichnungen S 0 = ε und S k = S[0... k 1] (d.h. S k ist das k-lange Präfix eines Wortes S Σ l für 0 k l) können wir das Problem der exakten Textsuche folgendermaßen formulieren: Definition Finde alle Verschiebungen s, so dass P T s+m.
Beispiel Textsuche Beispiel T = bielefeld oh bielefeld P = feld
Beispiel Textsuche Beispiel T = bielefeld oh bielefeld P = feld Gültige Verschiebungen: s = 5 und s = 18.
Naive Lösung Beispiel (Naive-StringMatcher) Naive-StringMatcher(T, P) 1 n length[t ] 2 m length[p] 3 for s 0 to n m do 4 if P[0... m 1] = T [s... s + m 1] then 5 print Pattern occurs with shift s
... ist ineffizient T = abrakadabraxasa, P = abraxas a b r a k a d a b r a x a s a
... ist ineffizient T = abrakadabraxasa, P = abraxas a b r a k a d a b r a x a s a a b r a x a s
... ist ineffizient T = abrakadabraxasa, P = abraxas a b r a k a d a b r a x a s a a b r a x a s a b r a x a s
... ist ineffizient T = abrakadabraxasa, P = abraxas a b r a k a d a b r a x a s a a b r a x a s a b r a x a s a b r a x a s
... ist ineffizient T = abrakadabraxasa, P = abraxas a b r a k a d a b r a x a s a a b r a x a s a b r a x a s a b r a x a s a b r a x a s a b r a x a s a b r a x a s a b r a x a s
Implementierung in Java Beispiel (Java-Implementierung) public static void naivematcher(string text, String pattern) { int m = pattern.length(); int n = text.length(); for(int s=0; s<=n-m; s++) { if(text.regionmatches(s,pattern,0,m)) System.out.println("naiveMatcher: Pattern occurs with shift "+s); } }
Worst-case Zeiteffizienz 1 Die zwei Zeichen stimmen nicht überein.
Worst-case Zeiteffizienz Auch wenn Text und Muster zeichenweise von links nach rechts und nur bis zum ersten Mismatch 1 verglichen werden, ist die worst-case Zeiteffizienz des naiven Algorithmus O ( n m ). 1 Die zwei Zeichen stimmen nicht überein.
Worst-case Zeiteffizienz Auch wenn Text und Muster zeichenweise von links nach rechts und nur bis zum ersten Mismatch 1 verglichen werden, ist die worst-case Zeiteffizienz des naiven Algorithmus O ( n m ). z.b. wird für T = a n, P = a m die for-schleife n m + 1 mal ausgeführt und in jedem Test in Zeile 4 werden m Zeichen verglichen. 1 Die zwei Zeichen stimmen nicht überein.
Wie kann man den naiven Algorithmus verbessern? Idee 1: Überspringe ein Teilwort w von T, falls klar ist, dass w P (BM-Algorithmus 1977). Idee 2: Merke Informationen über bisherige Vergleiche und nutze diese, um neue, unnötige Vergleiche zu vermeiden (KMP-Algorithmus 1977).
Der Boyer-Moore-Algorithmus Der BM-Algorithmus legt wie der naive Algorithmus das Muster zunächst linksbündig an den Text, vergleicht die Zeichen des Musters dann aber von rechts nach links mit den entsprechenden Zeichen des Textes. Beim ersten Mismatch benutzt er zwei Heuristiken, um eine Verschiebung des Musters nach rechts zu bestimmen.
Die bad-character Heuristik Falls beim Vergleich von P[0... m 1] und T [s... s + m 1] (von rechts nach links) ein Mismatch P[j] T [s + j] für ein j mit 0 j m 1 festgestellt wird, so schlägt die bad-character Heuristik eine Verschiebung des Musters um j k Positionen vor, wobei k der größte Index (0 k m 1) ist mit T [s + j] = P[k]. Wenn kein k mit T [s + j] = P[k] existiert, so sei k = 1. Man beachte, dass j k negativ sein kann.
Die good-suffix Heuristik Falls beim Vergleich von P[0... m 1] und T [s... s + m 1] (von rechts nach links) ein Mismatch P[j] T [s + j] für ein j mit 0 j m 1 festgestellt wird, so wird das Muster so weit nach rechts geschoben, bis das bekannte Suffix T [s + j + 1... s + m 1] wieder auf ein Teilwort des Musters passt. Hierfür ist eine Vorverarbeitung des Musters notwendig, auf die wir hier aber nicht näher eingehen wollen.
bad-character vs. good suffix bad character good suffix {}}{... w r i t t e n _ n o t i c e _ t h a t... s r e m i n i s c e n c e (a)... w r i t t e n _ n o t i c e _ t h a t... s + 4 r e m i n i s c e n c e (b)... w r i t t e n _ n o t i c e _ t h a t... s + 3 r e m i n i s c e n c e (c)
Zeitkomplexität Boyer-Moore Algorithmus worst-case: O ( n m )
Der Boyer-Moore-Horspool-Algorithmus Der BM-Algorithmus verdankt seine Schnelligkeit vor allem der bad-charakter Heuristik. Daher wurde 1980 von Horspool eine Vereinfachung des BM-Algorithmus vorgeschlagen: Die bad-charakter Heuristik wird derart modifiziert, dass sie immer eine positive Verschiebung vorschlägt. Damit wird die good-suffix Heuristik überflüssig (und auch die Vorverarbeitung einfacher).
falls P[j] T [s + j] für ein j (0 j m 1)
falls P[j] T [s + j] für ein j (0 j m 1) s s + m 1 k wobei k größter Index zwischen 0 und m 2 mit T [s + m 1] = P[k]
falls P[j] T [s + j] für ein j (0 j m 1) s s + m 1 k wobei k größter Index zwischen 0 und m 2 mit T [s + m 1] = P[k] wenn kein k (0 k m 2) mit T [s + m 1] = P[k] existiert
falls P[j] T [s + j] für ein j (0 j m 1) s s + m 1 k wobei k größter Index zwischen 0 und m 2 mit T [s + m 1] = P[k] wenn kein k (0 k m 2) mit T [s + m 1] = P[k] existiert s s + m
falls P[j] T [s + j] für ein j (0 j m 1) s s + m 1 k wobei k größter Index zwischen 0 und m 2 mit T [s + m 1] = P[k] wenn kein k (0 k m 2) mit T [s + m 1] = P[k] existiert s s + m Verschiebespanne: λˆt [s+m 1] = min {m} m 1 k 0 k m 2 und T [s+m 1] = P[k]
falls P[j] T [s + j] für ein j (0 j m 1) s s + m 1 k wobei k größter Index zwischen 0 und m 2 mit T [s + m 1] = P[k] wenn kein k (0 k m 2) mit T [s + m 1] = P[k] existiert s s + m Verschiebespanne: λˆt [s+m 1] = min {m} m 1 k 0 k m 2 und T [s+m 1] = P[k] bei einem Match wird um λ [ T [s + m 1] ] verschoben
Verhalten des BMH-Algorithmus bei einem Mismatch... g o l d e n _ f l e e c e _ o f... s r e m i n i s c e n c e... g o l d e n _ f l e e c e _ o f... s + 3 r e m i n i s c e n c e
Verhalten des BMH-Algorithmus bei einem Treffer... g o l d e n _ f l e e c e _ o f... s f l e e c e... g o l d e n _ f l e e c e _ o f... s + 2 f l e e c e
Vorverarbeitung des Musters Beispiel (Berechnung der Funktion λ) computelastoccurrencefunction(p, Σ) 1 m length[p] 2 for each character a Σ do 3 λ[a] = m 4 for j 0 to m 2 do 5 λ [ P[j] ] m 1 j 6 return λ
Vorverarbeitung des Musters Beispiel (Berechnung der Funktion λ) computelastoccurrencefunction(p, Σ) 1 m length[p] 2 for each character a Σ do 3 λ[a] = m 4 for j 0 to m 2 do 5 λ [ P[j] ] m 1 j 6 return λ Die worst-case-komplexität von computelastoccurrencefunction ist O ( Σ + m ).
BMH-Algorithmus im Pseudo-Code Beispiel (BMH-Matcher) BMH-Matcher(T, P, Σ) 1 n length[t ] 2 m length[p] 3 λ computelastoccurrencefunction(p, Σ) 4 s 0 5 while s n m do 6 j m 1 7 while j 0 and P[j] = T [s + j] do 8 j j 1 9 if j = 1 then 10 print Pattern occurs with shift s 11 s s + λ [ T [s + m 1] ]
Zeitkomplexität BMH Zur Verifikation, ob eine Verschiebung s gültig ist, wird O(m) Zeit benötigt. Insgesamt hat der BMH-Algorithmus die worst-case Zeitkomplexität O ( n m + Σ ) (z.b. für T = a n, P = a m ).
Der Knuth-Morris-Pratt-Algorithmus Nochmal der naive Ansatz: Der naive Algorithmus ist ineffizient, da unabhängig von bereits stattgefundenen erfolgreichen Vergleichen das Muster immer um ein Zeichen verschoben wird. Im folgenden Beispiel ist dies noch einmal anhand der Suche nach dem Wort abraxas im Text abrakadabra dargestellt. a b r a k a d a b r a a b r a x a s a b r a x a s a b r a x a s a b r a x a s
Größere Verschiebung: Beispiel a b r a k a d a b r a a b r a x a s a b r a x a s
Größere Verschiebung: Beispiel a b r a k a d a b r a a b r a x a s a b r a x a s Man erkennt, dass hier drei unnötige Vergleiche vermieden wurden!
Funktionsweise Beim KMP-Algorithmus wird das Muster P zeichenweise von links nach rechts mit dem Text T verglichen. Es werden Informationen über bereits stattgefundene Vergleiche ausgenutzt, um weitere unnötige Vergleiche zu vermeiden. Als Ausgangssituation sei P q ein Präfix von P, das an Position s im Text vorkommt. Beispiel... P q... s P q Dann gehen wir gemäß der folgenden Fälle vor:
(A) q = m, d.h. s ist gültige Verschiebung: 1. Bestimme k = π[m] = max{k : k < m und P k P m}. 2.a) Falls k > 0, verschiebe das Muster nach rechts, so dass das Präfix P k von P unter dem Suffix P k von T [s... s + P 1] liegt.......
(A) q = m, d.h. s ist gültige Verschiebung: 1. Bestimme k = π[m] = max{k : k < m und P k P m}. 2.a) Falls k > 0, verschiebe das Muster nach rechts, so dass das Präfix P k von P unter dem Suffix P k von T [s... s + P 1] liegt.... P k... P k
(A) q = m, d.h. s ist gültige Verschiebung: 1. Bestimme k = π[m] = max{k : k < m und P k P m}. 2.a) Falls k > 0, verschiebe das Muster nach rechts, so dass das Präfix P k von P unter dem Suffix P k von T [s... s + P 1] liegt.... P k... P k... P k... P k
(A) q = m, d.h. s ist gültige Verschiebung: 1. Bestimme k = π[m] = max{k : k < m und P k P m}. 2.a) Falls k > 0, verschiebe das Muster nach rechts, so dass das Präfix P k von P unter dem Suffix P k von T [s... s + P 1] liegt.... P k... P k... P k... P k 2.b) Falls k = 0, verschiebe das Muster P um die Länge P.......
(B) q < m:... a... b
(B) q < m:... } {{ a }... P q } {{ } P q b
(B) q < m:... P k a... }{{} P q P k } {{ } P q b
(B) q < m:... P k a... }{{} P q P k } {{ } P q b (a) a b und q 0: bestimme π[q] = max{k : k < q und P k P q}, d.h. die Länge des längsten Präfixes von P, das auch Suffix von P q ist; verschiebe P nach rechts, so dass das Suffix P k von P q über dem Präfix P k von P liegt.... P k a... P k b
(b) a b und q = 0: verschiebe P um 1 nach rechts.... a... b
(c) a = b: mache weiter mit q + 1.... P q a... P q a... P q+1... s P q+1
Beobachtung: Für jeden der vier Fälle gilt: das Muster wird nie nach links geschoben entweder wird ein neues Zeichen des Textes gelesen (B.c), oder das Muster wird um mindestens eine Position nach rechts geschoben (A), (B.a), (B.b).
Zeitkomplexität KMP Wir bestimmen die worst-case Zeitkomplexität des KMP-Algorithmus: Fälle (B.b) und (B.c) erfordern jeweils konstanten Zeitaufwand. In (A) und (B.a) wird die Funktion π benötigt mit π[q] = max{k : k < q und P k P q }, d.h. π[q] = Länge des längsten Präfixes von P, das ein echtes Suffix von P q ist. π hängt nur von P ab und kann daher in einem Vorverarbeitungsschritt (VV ) berechnet und als Tabelle abgespeichert werden. Gegeben diese Tabelle, erfordern auch (A) und (B.a) nur konstanten Zeitaufwand.
Zeitkomplexität KMP Wir bestimmen die worst-case Zeitkomplexität des KMP-Algorithmus: Fälle (B.b) und (B.c) erfordern jeweils konstanten Zeitaufwand. In (A) und (B.a) wird die Funktion π benötigt mit π[q] = max{k : k < q und P k P q }, d.h. π[q] = Länge des längsten Präfixes von P, das ein echtes Suffix von P q ist. π hängt nur von P ab und kann daher in einem Vorverarbeitungsschritt (VV ) berechnet und als Tabelle abgespeichert werden. Gegeben diese Tabelle, erfordern auch (A) und (B.a) nur konstanten Zeitaufwand. Damit ergeben sich folgende Komplexitäten: O(VV ) + O(n) Zeit und O(m) Speicherplatz im worst-case!
Beispiel Präfixfunktion Die Präfixfunktion π : {1, 2,..., m} {0, 1,..., m 1} für ein Muster P[0... m 1] ist definiert durch π[q] = max{k : k < q und P k P q }, also die Länge des längsten Präfixes von P, das ein echtes Suffix von P q ist. Sie lautet für unser Beispielwort P = abrakadabra:
Beispiel Präfixfunktion Die Präfixfunktion π : {1, 2,..., m} {0, 1,..., m 1} für ein Muster P[0... m 1] ist definiert durch π[q] = max{k : k < q und P k P q }, also die Länge des längsten Präfixes von P, das ein echtes Suffix von P q ist. Sie lautet für unser Beispielwort P = abrakadabra: Beispiel q 0 1 2 3 4 5 6 7 8 9 10 11 P[q] a b r a k a d a b r a π[q] 0 0 0 1 0 1 0 1 2 3 4
Naive Präfixfunktion Beispiel (Naive Präfixfunktion) computeprefixfunction(p) 1 m length[p] 2 π[1] 0 3 for q 2 to m do 4 k q 1 5 while k > 0 and P[0... k 1] P[0... q 1] do 6 k k 1 7 π[q] k 8 return π
KMP-Matcher in Pseudo-Code Beispiel (KMP-Matcher) KMP-Matcher(T, P) 1 n length[t ] 2 m length[p] 3 π computeprefixfunction(p) 4 q 0 5 for i 0 to n 1 do 8 if P[q] = T [i] then 9 q q + 1
KMP-Matcher in Pseudo-Code Beispiel (KMP-Matcher) KMP-Matcher(T, P) 1 n length[t ] 2 m length[p] 3 π computeprefixfunction(p) 4 q 0 5 for i 0 to n 1 do 8 if P[q] = T [i] then 9 q q + 1 10 if q = m then 11 print Pattern occurs with shift i m + 1 12 q π[q]
KMP-Matcher in Pseudo-Code Beispiel (KMP-Matcher) KMP-Matcher(T, P) 1 n length[t ] 2 m length[p] 3 π computeprefixfunction(p) 4 q 0 5 for i 0 to n 1 do 6 while q > 0 and P[q] T [i] do 7 q π[q] 8 if P[q] = T [i] then 9 q q + 1 10 if q = m then 11 print Pattern occurs with shift i m + 1 12 q π[q]
Effizientere Berechnung von π (I) Wir gehen nun der Frage nach, wie die Funktion π effizienter berechnet werden kann. Unter der Annahme, dass die Werte von π für 1,..., q 1 schon berechnet sind, wollen wir π[q] berechnen. Gesucht: π[q] = max{k : k < q und P k P q }
Effizientere Berechnung von π (I) Wir gehen nun der Frage nach, wie die Funktion π effizienter berechnet werden kann. Unter der Annahme, dass die Werte von π für 1,..., q 1 schon berechnet sind, wollen wir π[q] berechnen. Gesucht: π[q] = max{k : k < q und P k P q } Welche P k kommen in Frage? Alle P k = P[0... k 1] für die gilt: P }{{ k 1 } P q 1 }{{} P[0...k 2] P[0...q 2] und P[k 1] = P[q 1], also alle echten Suffixe von P q 1, die sich zu einem echten Suffix von P q erweitern lassen.
Effizientere Berechnung von π (II) Die Menge der Längen aller echten Suffixe von P q 1, die auch Präfixe von P sind, ist: {k 1 : k 1 < q 1 : P k 1 P q 1 } = {k : k < q 1 : P k P q 1 }.
Effizientere Berechnung von π (II) Die Menge der Längen aller echten Suffixe von P q 1, die auch Präfixe von P sind, ist: {k 1 : k 1 < q 1 : P k 1 P q 1 } = {k : k < q 1 : P k P q 1 }. Damit muss gelten: π[q] = max{k + 1 : k < q 1, P k P q 1 und P[k ] = P[q 1]}.
Effizientere Berechnung von π (III) Nun kann man folgende Gleichheit zeigen: Lemma {k : k < q 1 : P k P q 1 } = {π[q 1], πˆπ[q 1], π 3 [q 1],..., π t [q 1]}, {z } =π 2 [q 1] wobei π t [q 1] = 0 gilt.
Effizientere Berechnung von π (III) Nun kann man folgende Gleichheit zeigen: Lemma {k : k < q 1 : P k P q 1 } = {π[q 1], πˆπ[q 1], π 3 [q 1],..., π t [q 1]}, {z } =π 2 [q 1] wobei π t [q 1] = 0 gilt. Schließlich erhalten wir: π[q] = max{k +1 : k {π[q 1],..., π t [q 1]} und P[k ] = P[q 1]}.
Effizientere Berechnung von π (IV) Damit erhalten wir folgendes Verfahren, um π[q] zu berechnen: Wir schauen π[q 1] in der Tabelle von π nach und prüfen, ob sich das echte Suffix P π[q 1] von P q 1 zu einem echten Suffix von P q erweitern lässt. Wenn ja, so ist π[q] = π[q 1] + 1. Wenn nein, so iterieren wir diesen Prozess, d.h. wir machen das Gleiche mit π(π[q 1]) usw. Diese Überlegungen sollten die folgende Berechnungsmethode der Funktion π motivieren.
Effizientere Berechnung von π (V) Beispiel computeprefixfunction2(p) 1 m length[p] 2 π[1] 0 3 k 0 4 for q 2 to m do 5 while k > 0 and P[k] P[q 1] do 6 k π[k] 7 if P[k] = P[q 1] then 8 k k + 1 9 π[q] k 10 return π
Laufzeit Die Laufzeit von computeprefixfunction2 beträgt O(m). Dies kann man folgendermaßen sehen:
Laufzeit Die Laufzeit von computeprefixfunction2 beträgt O(m). Dies kann man folgendermaßen sehen: 1 Die for-schleife in Zeile 4 wird m 1-mal durchlaufen, demzufolge auch die Zeilen 7-9.
Laufzeit Die Laufzeit von computeprefixfunction2 beträgt O(m). Dies kann man folgendermaßen sehen: 1 Die for-schleife in Zeile 4 wird m 1-mal durchlaufen, demzufolge auch die Zeilen 7-9. 2 Der Wert von k ist anfangs 0 und wird insgesamt höchstens m 1-mal in Zeile 8 um jeweils 1 erhöht.
Laufzeit Die Laufzeit von computeprefixfunction2 beträgt O(m). Dies kann man folgendermaßen sehen: 1 Die for-schleife in Zeile 4 wird m 1-mal durchlaufen, demzufolge auch die Zeilen 7-9. 2 Der Wert von k ist anfangs 0 und wird insgesamt höchstens m 1-mal in Zeile 8 um jeweils 1 erhöht. 3 Bei jedem Durchlauf der while-schleife in den Zeilen 5 und 6 wird der Wert von k um mindestens 1 erniedrigt. Da k nicht negativ wird (Zeile 5), kann die while-schleife insgesamt also höchstens k-mal durchlaufen werden.
Korrektheit der Berechnung der Präfixfunktion Das wesentliche Lemma im Korrektheitsbeweis besagt, dass man alle Präfixe P k, die Suffix eines gegebenen Präfixes P q sind, berechnen kann, indem man die Präfixfunktion π iteriert. Definition Es sei π [q] = { q, π[q], π 2 [q], π 3 [q],..., π t [q] } wobei π 0 [q] = q und π i+1 [q] = π [ π i [q] ] für i 0; dabei sei vereinbart, dass die Folge in π [q] abbricht, wenn π t [q] = 0 erreicht wird.
Lemma Es sei P[0... m 1] ein Muster und π die dazu gehörige Präfixfunktion. Dann gilt π [q] = {k : P k P q } für q = 1,..., m.
Wir beweisen das Lemma, indem wir (1) π [q] {k : P k P q } und (2) {k : P k P q } π [q] zeigen.
Wir beweisen das Lemma, indem wir (1) π [q] {k : P k P q } und (2) {k : P k P q } π [q] zeigen. 1 Wir zeigen: i π [q] impliziert P i P q. Sei also i π [q]. Für i gibt es ein u N mit i = π u [q]. Wir beweisen P π u [q] P q durch Induktion über u.
Induktionsanfang: u = 0. Dann ist i = π 0 [q] = q und die Behauptung gilt.
Induktionsanfang: u = 0. Dann ist i = π 0 [q] = q und die Behauptung gilt. Induktionshypothese: Für i 0 = π u 0 [q] gilt P i0 P q.
Induktionsanfang: u = 0. Dann ist i = π 0 [q] = q und die Behauptung gilt. Induktionshypothese: Für i 0 = π u 0 [q] gilt P i0 P q. Induktionsschritt: Zu zeigen ist: für i = π u 0+1 [q] gilt P i P q. Es gilt i = π [ π u 0 [q] ] = π[i 0 ]. Mit der Induktionshypothese folgt P i0 P q. Weiterhin gilt i = π[i 0 ] = max{k : k < i 0 und P k P i0 }, also gilt P i P i0 P q. Da transitiv ist, folgt P i P q.
2 Wir beweisen {k : P k P q } π [q] durch Widerspruch. Wir nehmen an j {k : P k P q }\π [q].
2 Wir beweisen {k : P k P q } π [q] durch Widerspruch. Wir nehmen an j {k : P k P q }\π [q]. Ohne Einschränkung der Allgemeinheit sei j die größte Zahl mit dieser Eigenschaft. Da j q und q {k : P k P q } π [q] gilt, folgt j < q. Es sei j die kleinste Zahl in π [q], die größer als j ist (da q π [q] gilt, existiert solch ein j immer). Wegen j {k : P k P q } gilt P j P q. Weiterhin impliziert j π [q] wg. (1) P j P q. Mit j < j folgt damit P j P j.
2 Wir beweisen {k : P k P q } π [q] durch Widerspruch. Wir nehmen an j {k : P k P q }\π [q]. Ohne Einschränkung der Allgemeinheit sei j die größte Zahl mit dieser Eigenschaft. Da j q und q {k : P k P q } π [q] gilt, folgt j < q. Es sei j die kleinste Zahl in π [q], die größer als j ist (da q π [q] gilt, existiert solch ein j immer). Wegen j {k : P k P q } gilt P j P q. Weiterhin impliziert j π [q] wg. (1) P j P q. Mit j < j folgt damit P j P j. Es gibt kein j, j < j < j, mit P j P j P j
2 Wir beweisen {k : P k P q } π [q] durch Widerspruch. Wir nehmen an j {k : P k P q }\π [q]. Ohne Einschränkung der Allgemeinheit sei j die größte Zahl mit dieser Eigenschaft. Da j q und q {k : P k P q } π [q] gilt, folgt j < q. Es sei j die kleinste Zahl in π [q], die größer als j ist (da q π [q] gilt, existiert solch ein j immer). Wegen j {k : P k P q } gilt P j P q. Weiterhin impliziert j π [q] wg. (1) P j P q. Mit j < j folgt damit P j P j. Es gibt kein j, j < j < j, mit P j P j P j Gäbe es solch ein j, so müsste j π [q] gelten, denn j ist die größte Zahl mit j {k : P k P q }\π [q].
j π [q] kann aber nicht gelten, denn j ist die kleinste Zahl in π [q], die größer als j ist. Also muss j = max{k : k < j und P k P j } = π[j ] und damit j π [q] gelten. Dieser Widerspruch beweist {k : P k P q } π [q].
Definition Für q = 2, 3,..., m sei E q 1 = { k : k < q 1, k π [q 1], P[k] = P[q 1] }.
Definition Für q = 2, 3,..., m sei E q 1 = { k : k < q 1, k π [q 1], P[k] = P[q 1] }. Die Menge E q 1 π [q 1] enthält alle k π [q 1] = {k : P k P q 1 } mit k < q 1 und P k+1 P q ; denn aus P k = P[0... k 1] P q 1 = P[0... q 2] und P[k] = P[q 1] folgt P k+1 = P[0... k] P q = P[0... q 1]).
Lemma Sei P[0... m 1] ein Muster und π die zugehörige Präfixfunktion. Für q = 2, 3,..., m gilt: { 0 falls E q 1 = π[q] = 1 + max{k E q 1 } falls E q 1
Beweis. Mit der Vereinbarung max{} = 0 gilt: π[q] = max k : k < q und P k P q
Beweis. Mit der Vereinbarung max{} = 0 gilt: π[q] = max k : k < q und P k P q = max k : k < q, P k 1 P q 1 und P[k 1] = P[q 1]
Beweis. Mit der Vereinbarung max{} = 0 gilt: π[q] = max k : k < q und P k P q = max k : k < q, P k 1 P q 1 und P[k 1] = P[q 1] (30) = max k : k 1 < q 1, k 1 π [q 1] und P[k 1] = P[q 1]
Beweis. Mit der Vereinbarung max{} = 0 gilt: π[q] = max k : k < q und P k P q = max k : k < q, P k 1 P q 1 und P[k 1] = P[q 1] (30) = max k : k 1 < q 1, k 1 π [q 1] und P[k 1] = P[q 1] k =k 1 = max k + 1 : k < q 1, k π [q 1] und P[k ] = P[q 1]
Beweis. Mit der Vereinbarung max{} = 0 gilt: π[q] = max k : k < q und P k P q = max k : k < q, P k 1 P q 1 und P[k 1] = P[q 1] (30) = max k : k 1 < q 1, k 1 π [q 1] und P[k 1] = P[q 1] k =k 1 = = max k + 1 : k < q 1, k π [q 1] und P[k ] = P[q 1] max k + 1 : k E q 1
Beweis. Mit der Vereinbarung max{} = 0 gilt: π[q] = max k : k < q und P k P q = max k : k < q, P k 1 P q 1 und P[k 1] = P[q 1] (30) = max k : k 1 < q 1, k 1 π [q 1] und P[k 1] = P[q 1] k =k 1 = = max k + 1 : k < q 1, k π [q 1] und P[k ] = P[q 1] max k + 1 : k E q 1 Fall 1: E q 1 = : Dann gilt π[q] = max{} = 0.
Beweis. Mit der Vereinbarung max{} = 0 gilt: π[q] = max k : k < q und P k P q = max k : k < q, P k 1 P q 1 und P[k 1] = P[q 1] (30) = max k : k 1 < q 1, k 1 π [q 1] und P[k 1] = P[q 1] k =k 1 = = max k + 1 : k < q 1, k π [q 1] und P[k ] = P[q 1] max k + 1 : k E q 1 Fall 1: E q 1 = : Dann gilt π[q] = max{} = 0. Fall 2: E q 1 : Dann gilt π[q] = max{1 + k : k E q 1} = 1 + max{k : k E q 1}.
K. Arnold, J. Gosling: Java TM - Die Programmiersprache. Addison-Wesley, 1996. T.H. Cormen, C.E. Leierson, R.L. Rivest: Introduction to Algorithms. MIT Press, 1990. D. Flanagan: Java in a Nutshell. O Reilly & Associates Inc., 1996. F. Jobst: Programmieren in Java. Hanser Verlag, 1996. H. Klaeren: Vom Problem zum Programm. 2.Auflage, B.G. Teubner Verlag, 1991. K. Echtle, M. Goedicke: Lehrbuch der Programmierung mit Java. dpunkt-verlag, 2000.