Algorithmen und Datenstrukturen II

Ähnliche Dokumente
Sortieralgorithmen. Inhalt: InsertionSort BubbleSort QuickSort. Marco Block

Rekursion. Annabelle Klarl. Einführung in die Informatik Programmierung und Softwareentwicklung

Probeklausur: Programmierung WS04/05

Hallo Welt für Fortgeschrittene

Übungsblatt 3: Algorithmen in Java & Grammatiken

Primitive Datentypen

Programmierkurs Java

Dr. Monika Meiler. Inhalt

Einstieg in die Informatik mit Java

5. Tutorium zu Programmieren

Kontrollstrukturen, Pseudocode und Modulo-Rechnung

Wiederholung Wozu Methoden? Methoden Schreiben Methoden Benutzen Rekursion?! Methoden. Javakurs 2012, 3. Vorlesung

Java: Der Einstieg. Algorithmen und Datenstrukturen II 1

Autor: Michael Spahn Version: 1.0 1/10 Vertraulichkeit: öffentlich Status: Final Metaways Infosystems GmbH

Objektorientierte Programmierung OOP Programmieren mit Java

Das erste Programm soll einen Text zum Bildschirm schicken. Es kann mit jedem beliebigen Texteditor erstellt werden.

Überblick. Lineares Suchen

Java 8. Elmar Fuchs Grundlagen Programmierung. 1. Ausgabe, Oktober 2014 JAV8

Grundlagen der Programmierung Prof. H. Mössenböck. 14. Schrittweise Verfeinerung

Propädeutikum zur Programmierung

Kapitel 6. Vererbung

5. Mustererkennung. Anwendungsbeispiele: suche Zeichenfolge in Text suche Bitmuster in Pixelbild. gegeben: endliches Alphabet Σ. Muster p Σ m, m IN

String s1, s2; Eine Zuweisung geschieht am einfachsten direkt durch Angabe des Strings eingeschlossen in doppelte Hochkommata:

Einführung in Java. PING e.v. Weiterbildung Andreas Rossbacher 24. März 2005

Mächtigkeit von WHILE-Programmen

Die Programmiersprache C Eine Einführung

1.2 Attribute und Methoden Aufbau einer Java-Klasse:

Einführung Datentypen Verzweigung Schleifen. Java Crashkurs. Kim-Manuel Klein May 4, 2015

Algorithmen mit konstantem Platzbedarf: Die Klasse REG

Informatik II Musterlösung

Propädeutikum zur Programmierung

Repetitorium Informatik (Java)

Kapitel 6. Vererbung

JAVA - Methoden

1. Typen und Literale (6 Punkte) 2. Zuweisungen (6 = Punkte)

Eine Klasse beschreibt Objekte mit gleichen Attributen und Methoden.

Kapitel 6. Vererbung

Algorithmentheorie Maximale Flüsse

Lösungsvorschläge. zu den Aufgaben im Kapitel 4

PIWIN 1 Übung Blatt 5

Propädeutikum zur Programmierung

Abgabe: (vor der Vorlesung) Aufgabe 2.1 (P) O-Notation Beweisen Sie die folgenden Aussagen für positive Funktionen f und g:

Java 7. Elmar Fuchs Grundlagen Programmierung. 1. Ausgabe, Dezember 2011 JAV7

Übung Grundlagen der Programmierung. Übung 03: Schleifen. Testplan Testergebnisse

Übersicht. Schleifen. Schleifeninvarianten. Referenztypen, Wrapperklassen und API. 9. November 2009 CoMa I WS 08/09 1/15

II. Grundlagen der Programmierung. 9. Datenstrukturen. Daten zusammenfassen. In Java (Forts.): In Java:

4 Codierung nach Viginere (Lösung)

Übersicht. Datenstrukturen und Algorithmen Vorlesung 5: Rekursionsgleichungen (K4) Übersicht. Binäre Suche. Joost-Pieter Katoen. 20.

Einführung in die Java- Programmierung

1 Polymorphie (Vielgestaltigkeit)

Selbsteinstufungstest Vorkurs Programmieren

VBA-Programmierung: Zusammenfassung

II.1.1. Erste Schritte - 1 -

Komplexität von Algorithmen

Informatik I WS 07/08 Tutorium 24

Theoretische Informatik. Reguläre Sprachen und Automaten

Praktikum zu Einführung in die Informatik für LogWiIngs und WiMas Wintersemester 2015/16. Vorbereitende Aufgaben

Einführung in die C-Programmierung

KV Software Engineering Übungsaufgaben SS 2005

Java Einführung VARIABLEN und DATENTYPEN Kapitel 2

Rekursion. Annabelle Klarl. Einführung in die Informatik Programmierung und Softwareentwicklung

Einführung Datentypen Verzweigung Schleifen Funktionen Dynamische Datenstrukturen. Java Crashkurs. Kim-Manuel Klein

Einstieg in die Informatik mit Java

1. Grundlagen Sortieren Vertauschen Selektion Einfügen Quicksort Suchen...

Studentische Lösung zum Übungsblatt Nr. 7

Teil 2 - Softwaretechnik. Modul: Programmierung B-PRG Grundlagen der Programmierung 1 Teil 2. Übersicht. Softwaretechnik

Einführung in die Informatik für Hörer aller Fakultäten II. Andreas Podelski Stephan Diehl Uwe Waldmann

Dr. Monika Meiler. Inhalt

Algorithmische Kernsprache. Zuweisung, einfache und bedingte Anweisung, Blöcke, Schleifen, return, debugging.

IT-Basics 2. DI Gerhard Fließ

Erster Kontakt mit Java und Pseudocode

13 Java 4 - Entwurfsmuster am Beispiel des Rucksackproblems

Das Briefträgerproblem

Bitte beachten Sie: Es gibt mehr als einen Lösungsweg. Ihre Lösung kann auch richtig sein, wenn sie nicht mit der hier gezeigten Lösung übereinstimmt.

Einführung in die Programmierung für Wirtschaftsinformatik

Algorithmentheorie Randomisierung. Robert Elsässer

Gliederung. Tutorium zur Vorlesung. Gliederung. Gliederung. 1. Gliederung der Informatik. 1. Gliederung der Informatik. 1. Gliederung der Informatik

3.2 Binäre Suche. Usr/local/www/ifi/fk/menschen/schmid/folien/infovk.ppt 1

Technische Universität Braunschweig Institut für Programmierung und Reaktive Systeme

1. Erste Schritte 2. Einfache Datentypen 3. Anweisungen und Kontrollstrukturen 4. Verifikation 5. Reihungen (Arrays)

Grundlagen der Programmierung Prof. H. Mössenböck. 6. Methoden

Informatikgrundlagen (WS 2015/2016)

Abschnitt: Algorithmendesign und Laufzeitanalyse

Beispiellösungen zu den Übungen Datenstrukturen und Algorithmen SS 2008 Blatt 6

Programmieren I. Kontrollstrukturen. Heusch 8 Ratz Institut für Angewandte Informatik

Zur Vereinfachung betrachten wir nun nur noch Funktionen f, die einen Funktionswert f nµberechnen. Sie werden alle in einer Tabelle dargestellt:

Ein erstes Java-Programm

Zeichenketten. Hauptseminar Hallo Welt! für Fortgeschrittene. SS Mai 2006 Tobias Hager.

Probeklausur: Programmierung WS04/05

1. Der Begriff Informatik 2. Syntax und Semantik von Programmiersprachen. I.2. I.2. Grundlagen von von Programmiersprachen.

Objektorientierte Programmierung

Kapitel 6. Komplexität von Algorithmen. Xiaoyi Jiang Informatik I Grundlagen der Programmierung

EINI WiMa/LW. Einführung in die Informatik für Naturwissenschaftler und Ingenieure. Vorlesung 2 SWS WS 11/12

Inf 12 Aufgaben

Datenbankanwendungsprogrammierung Crashkurs Java

public class SternchenRechteckGefuellt {

Transkript:

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.