Algorithmen und Datenstrukturen - Informatik/SWS

108 downloads 331 Views 614KB Size Report
20. Dez. 2012 ... sten Datenstrukturen und wie man diese verarbeitet (Algorithmen). ... Gunter Saake, Kay-Uwe Sattler Algorithmen und Datenstrukturen, Eine ...
Algorithmen und Datenstrukturen Dr. Beatrice Amrhein

5. Januar 2016

ii

¨ Zur umfassenden Ausbildung eines Software-Ingenieurs gehoren grundlegende Kenntnisse der wichtigsten Datenstrukturen und wie man diese verarbeitet (Algorithmen). Das Kennen von geeigneten Datenstrukturen hilft dem Programmierer, die Informationen richtig zu organisieren und besser strukturierte Programme zu schreiben.

Lerninhalte -

Abstrakte Datentypen, Spezifikation ¨ von Algorithmen, Komplexitat Algorithmen-Schemata: Greedy, Iteration, Rekursion ¨ Wichtige Datenstrukturen: Listen, Stacks, Queues, Baume, Heaps Suchen und Sortieren, Hash-Tabellen ¨ Sprachen, Pattern Matching Endliche Automaten, regulare Kontextfreie Grammatiken, Parser

Lernziele Die Studierenden kennen die wichtigsten Datenstrukturen mit ihren Methoden. Sie kennen die klassi¨ ¨ ¨ ¨ schen Algorithmen und konnen sie anwenden. Ausserdem knnen sie Komplexitatsabsch atzungen von Algorithmen vornehmen.

Informationen zum Unterricht Grundlage ist ein Skript, das die wichtigsten Lerninhalte umfasst. Unterrichtssprache: Deutsch (Fachliteratur zum Teil in Englisch) ¨ ¨ Umfang: 12 halbtagige Blocke a` 4 Lektionen Dozentin: Beatrice Amrhein, Empfohlene Literatur: - Reinhard Schiedermeier Programmieren mit Java, Eine methodische Einfuhrung. Pearson Studi¨ um ISBN 3-8273-7116-3. - Robert Sedgewick Algorithms in Java. Addison-Wesley Professional; 2002 ISBN 978-0-20136120-9 - M. T. Goodrich & R. Tamassia Algorithm Design: Foundations, Analysis, and Internet Examples. John Wiley & Sons, Inc. ISBN: 0-471-38365-1. - Gunter Saake, Kay-Uwe Sattler Algorithmen und Datenstrukturen, Eine Einfuhrung mit Java. ¨ dpunkt, 2004. ISBN 3-89864-255-0.

Inhaltsverzeichnis 1

Einfuhrung ¨

1-1

1.1 Die wichtigsten Ziele dieses Kurses . . . . . . . . . . . . . . . . . . . . . . . . . . . .

1-1

1.2 Einige Begriffe: Datenstrukturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

1-2

1.3 Einige Begriffe: Algorithmen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

1-9

1.4 Algorithmen Schema: Iteration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1-12 1.5 Algorithmen Schema: Greedy (die gierige Methode) . . . . . . . . . . . . . . . . . . . . 1-12 1.6 Algorithmen Schema: Rekursion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1-15 ¨ 1.7 Ubung 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1-20 2

3

¨ von Algorithmen Komplexitat ¨ 2.1 Komplexitatstheorie . . . . . . . . ¨ 2.2 Komplexitatsanalyse . . . . . . . . ¨ . . . . . 2.3 Asymptotische Komplexitat ¨ 2.4 Ubung 2 . . . . . . . . . . . . . .

2-1 . . . . . . . . . . . . . . . . . . . . . . . . . . . .

2-1

. . . . . . . . . . . . . . . . . . . . . . . . . . . .

2-2

. . . . . . . . . . . . . . . . . . . . . . . . . . . .

2-5

. . . . . . . . . . . . . . . . . . . . . . . . . . . .

2-9

Datentypen: Listen, Stacks und Queues

3-1

3.1 Array Listen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

3-1

3.2 Doppelt verkettete Listen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

3-5

3.3 Stacks und Queues

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3-11

3.4 Iteratoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3-13 ¨ 3.5 Ubung 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3-14 4

¨ Datentypen: Baume, Heaps 4-1 ¨ 4.1 Baumdurchlaufe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4-4 ¨ Suchbaume ¨ 4.2 Binare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4-8 ¨ 4.3 B-Baume . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4-10 4.4 Priority Queues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4-15 ¨ 4.5 Ubung 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4-21

5

Suchen

5-1

5.1 Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

5-1

5.2 Lineare Suche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ¨ Suche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.3 Binare

5-2 5-3

5.4 Hashing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-5 ¨ 5.5 Ubung 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-14 6

Sortieren

6-1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

6-2

6.2 Insertion Sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

6-4

6.3 Divide-and-Conquer Sortieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

6-5

6.4 Quicksort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

6-6

6.1 Selection Sort

iv

Inhaltsverzeichnis

6.5 Sortieren durch Mischen (Merge Sort) . . . . . . . . . . . . . . . . . . . . . . . . . . . 6-9 ¨ 6.6 Ubung 6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6-12 7

Pattern Matching

7-1

¨ Ausdrucke 7.1 Beschreiben von Pattern, Regulare ¨

. . . . . . . . . . . . . . . . . . . . . .

7-1

7.2 Endliche Automaten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-3 ¨ 7.3 Automaten zu regularen Ausdrucken . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-7 ¨ ¨ 7.4 Ubung 7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-10 8

Top Down Parser 8.1 Kontextfreie Grammatik

8-1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

8-2

8.2 Top-Down Parser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8-7 ¨ 8.3 Ubung 8 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8-14 9

Kryptologie

9-1

9.1 Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

9-2

9.2 Einfache Verschlusselungmethoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . ¨

9-3

9.3 Vernamchiffre, One Time Pad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

9-5

9.4 Moderne symmetrische Verfahren . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

9-6

9.5 Asymmetrische Verfahren: Public Key Kryptosysteme . . . . . . . . . . . . . . . . . . . 9-7 ¨ 9.6 Ubung 9 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9-12

1

Einfuhrung ¨

1.1 Die wichtigsten Ziele dieses Kurses Die wichtigsten Ziele des Algorithmen und Datenstrukturen Kurses sind: ¨ • Die Studierenden kennen die wichtigsten Datenstrukturen, konnen damit arbeiten, und kennen deren Vor- und Nachteile sowie deren Anwendungsgebiete. ¨ • Die Studierenden erhalten die Grundlagen, um wahrend der Design Phase die richtigen Datenstruk¨ turen auszuwahlen und dann richtig einzusetzen. ¨ • Die Studierenden kennen die wichtigsten Komplexitatsklassen und deren Einfluss auf das Laufzeitverhalten eines Systems. ¨ • Die Studierenden kennen die klassischen Algorithmen und konnen diese anwenden. Sie kennen



¨ deren Einsatzgebiete (wann soll welcher Algorithmus benutzt werden) und kennen die Komplexitat ¨ dieser Algorithmen (in Abhangigkeit der darunterliegenden Datenstrukturen). ¨ ¨ Die Studierenden erhalten einen Uberblick uber verschiedene Vorgehensweisen bei Problemlosun¨ ¨ ¨ gen und kennen deren Starken und Schwachen.

1-2

1 Einfuhrung ¨

1.2 Einige Begriffe: Datenstrukturen

Definition: Daten sind Information, welche (maschinen-) lesbar und bearbeitbar sind und in einem Bedeutungskontext stehen. Die Information wird dazu in Zeichen oder Zeichenketten codiert. Die Codierung ¨ klarer Regeln, der sogenannten Syntax. erfolgt gemass Daten sind darum Informationen mit folgenden Eigenschaften: ¨ den semantischen Teil (die Bedeutung) des Datenobjekts. 1. Die Bezeichnung erklart 2. Die Wertemenge bestimmt die Syntax (die Form oder Codier-Regel) des Datenobjekts. 3. Der Speicherplatz lokalisiert das Datenobjekts im Speicher und identifiziert dieses eindeutig.

Definition: Ein Datentyp ist eine (endliche) Menge (der Wertebereich des Typs) zusammen mit einer Anzahl Operationen. Der Wertebereich eines Datentyps bestimmt, was fur ¨ Werte ein Objekt dieses Typs annehmen kann. Die Elemente des Wertebereichs bezeichnet man auch als Konstanten des Datentyps. ¨ Dazu gehoren die Methoden oder Operatoren, welche auf dem Wertebereich definiert sind und somit ¨ auf Objekte dieses Typs angewandt werden konnen.

Beispiel: Der Wertebereich des Datentyps int besteht aus Auf diesem Datentyp gibt es die Operationen

Es ist wichtig, dass wir zwischen der (abstrakten) Beschreibung eines Datentyps (Spezifikation) und ¨ dessen Implementierung unterscheiden. Wenn wir komplizierte Probleme losen wollen, mussen wir von ¨ ¨ den Details abstrahieren konnen. Wir wollen nicht wissen (mussen) wie genau ein Datentyp implementiert ¨ ¨ ist, sondern bloss, wie wir den Datentyp verwenden konnen (welche Dienste er anbietet).

1.2 Einige Begriffe: Datenstrukturen

1-3

Jedes Objekt besitzt einen Datentyp, der bestimmt, welche Werte dieses Objekt annehmen kann und welche Operationen auf diesen Werten erlaubt sind. In allen Programmiersprachen gibt es nun Variablen, ¨ ¨ welche diese Objekte reprasentieren konnen. Es stellt sich nun die Frage, ob auch den Variablen zwingend ein Datentyp zugewiesen werden soll. Diese Frage wird in verschiedenen Programmiersprachen unterschiedlich beantwortet.

In untypisierten Sprachen wird den Variablen keinen Datentyp zugeordnet. Das heisst, jede Variable ¨ kann Objekte von einem beliebigen Typ reprasentieren. Die Programmiersprachen Smalltalk und Lisp ¨ sind typische Reprasentanten dieser Philosophie. In untypisierten Sprachen kann der Compiler keine sogenannten Typentests durchfuhren. Zur Kompi¨ ¨ lationszeit sind alle Operationen auf allen Variablen moglich. Es wird also zur Compilationszeit nicht nachgepruft, erlaubt sind. Unerlaubte Operationen fuhren zu Lauf¨ ob gewisse Operationen uberhaupt ¨ ¨ zeitfehlern.

In typisierten Sprachen wird allen Variablen ein Datentyp zugeordnet. Entweder mussen alle Variablen ¨ deklariert werden, wie in den Sprachen Pascal, C, C++ oder Eiffel, oder der Datentyp wird aus der Notation der Variablen klar wie etwa in der Sprache Fortran oder Basic (in Basic sind Variablen, welche mit dem Zeichen % enden, vom Typ Integer). In einer typisierten Sprache kann schon der Compiler entscheiden, ob die angegebenen Operationen typkorrekt sind oder nicht.

Als atomare Typen bezeichnen wir Datentypen, die in einer Sprache schon vordefiniert sind. Die atomaren Typen sind die grundlegenden Bausteine des Typsystems einer Programmiersprache. Aus diesen ¨ atomaren Typen konnen mit Hilfe von Mengenoperationen (Subtypen, Kartesische Produkte, Listen, ...) ¨ ¨ weitere Typen abgeleitet werden. Welche atomaren Typen zur Verfugung stehen, hangt von der gewahl¨ ten Programmiersprache ab. In allen wichtigen Programmiersprachen existieren die atomaren Typen Integer (ganze Zahlen), Float (reelle Zahlen, Fliesskomma), Boolean (logische Werte) und Char (Schriftzeichen). Dabei ist zu bemerken, dass diese atomaren Typen naturlich nur eine endliche Teilmenge aus dem Bereich der ganzen, bzw. der ¨ ¨ reellen Zahlen darstellen konnen.

1-4

1 Einfuhrung ¨

Beispiel: Der strukturierte Typ Array wird aus zwei gegebenen Datentypen, einem Grundtyp und einem Indextyp konstruiert. Der Grundtyp ist ein beliebiger atomarer oder abgeleiteter Datentyp. Der Indextyp ist normalerweise ein Subtyp (oder Intervall) des Typs int . Auf Arrays ist immer ein Selektor definiert, welcher es erlaubt, ein einzelnes Element des Arrays zu lesen oder zu schreiben.

Definition: Ein strukturierter Datentyp (eine Klasse) entsteht, wenn wir Elemente von beliebigen Typen zu einer Einheit zusammenfassen. Ein solcher Typ ist formal gesprochen das kartesische Produkt von beliebigen Datentypen.

DT = DT1 × DT2 × DT3 × . . . × DTn ¨ Die Datentypen DT1 , . . . , DTn konnen atomare oder auch strukturierte Typen sein. ¨ ausserdem die Spezifikation der zugehorigen ¨ Dazu gehort Operationen oder Methoden auf DT . Beispiel: Wir definieren ein einfaches Interface PushButton als Basis fur ¨ einen Button auf einer Be¨ nutzeroberflache).

1.2 Einige Begriffe: Datenstrukturen

1-5

Abstrakter Datentyp Der abstrakte Datentyp ist ein wichtiges Konzept in der modernen Informatik: Die Philosophie der objektorientierten Sprachen basiert genau auf dieser Idee. Der abstrakte Datentyp dient dazu, Datentypen ¨ unabhangig von deren Implementation zu definieren. Die Idee des abstrakten Datentyps beruht auf zwei wichtigen Prinzipien: dem Geheimnisprinzip und dem Prinzip der Wiederverwendbarkeit.

Geheimnisprinzip: Dem Benutzer eines Datentyps werden nur die auf diesem Datentyp erlaubten Operationen (mit deren Spezifikation) bekanntgegeben. Die Implementation des Datentyps bleibt fur ¨ den Benutzer verborgen (abstrakt, Kapselung).

Die Anwendung dieses Prinzips bringt folgende Vorteile: ¨ • Der Anwender kann den Datentyp nur im Sinne der Definition verwenden. Er hat keine Moglichkeit, Eigenschaften einer speziellen Implementation auszunutzen. ¨ • Die Implementation eines Datentyps kann jederzeit verandert werden, ohne dass die Benutzer des Datentyps davon betroffen sind.

• Die Verantwortungen zwischen dem Anwender und dem Implementator des Datentyps sind durch die Interface-Definitionen klar geregelt. Die Suche nach Fehlern wird dadurch erheblich vereinfacht. Wiederverwendbarkeit: Ein Datentyp (Modul) soll in verschiedenen Applikationen wiederverwendbar ¨ ¨ werden mussen. sein, wenn ahnliche Probleme gelost ¨

Die Idee hinter diesem Prinzip ist klar. Es geht darum, die Entwicklungszeit von Systemen zu reduzieren. Das Ziel ist, Softwaresysteme gleich wie Hardwaresysteme zu bauen, das heisst, die einzelnen Komponenten eines Systems werden eingekauft, eventuell parametrisiert und zum Gesamtsystem verbunden.

1-6

1 Einfuhrung ¨

Ein abstrakter Datentyp definiert einen Datentyp nur mit Hilfe des Wertebereichs und der Menge der Operationen auf diesem Bereich. Jede Operation ist definiert durch ihre Spezifikation, also die Input- und Output-Parameter und die Vor- und Nachbedingungen. ¨ Die Datenstruktur ist dann eine Instanz eines (abstrakten) Datentyps. Sie beinhaltet also die Reprasentation der Daten und die Implementation von Prozeduren fur ¨ alle definierten Operatoren. Wir sprechen hierbei auch von der logischen, bzw. der physikalischen Form von Datenelementen. Die Definition des abstrakten Datentyps ist die logische, deren Implementation die physikalische Form des Datenelements. Der abstrakte Datentyp spezifiziert einen Typ nicht mit Hilfe einer Implementation, sondern nur als eine Liste von Dienstleistungen, die der Datentyp dem Anwender zur Verfugung stellt. Die Dienstleistungen ¨ nennt man auch Operationen, Methoden oder Funktionen. Ein abstrakter Datentyp kann viele verschiedene Implementationen oder Darstellungen haben. Der abstrakte Datentyp gibt darum nicht an, wie die verschiedenen Operationen implementiert oder die Daten ¨ reprasentiert sind. Diese Details bleiben vor dem Benutzer verborgen.

Beispiel: Der abstrakte Datentyp Stack wird durch die Menge der angebotenen Dienste definiert: Einfugen eines Elements (push ), entfernen eines Elements (pop ), lesen des obersten Elements (peek ) und ¨ prufen auf leer (empty ). ¨ Eine solche Beschreibung berucksichtigt also nur, was ein Stack dem Anwender zu bieten hat. ¨ Bei den verschiedenen Methoden muss stehen, was die Methoden tun oder bewirken (Nachbedingung) ¨ und was fur Vorbedingungen) an die Verwendung der Methoden ¨ Voraussetzungen (Einschrankungen, gestellt sind.1 ¨ In Java konnte ein Interface fur ¨ einen Stack wie folgt aussehen:

1 Optimalerweise steht noch dabei, welchen Aufwand die Methode hat.

1.2 Einige Begriffe: Datenstrukturen

1-7

public interface Stack { /** * Pushes an item onto the top of this stack. * @param item the item to be pushed onto this stack. * @return the item argument. */ E push(E item); /** * Removes the object at the top of this stack and returns that * object as the value of this function. * @return The object at the top of this stack (the last * item of the Vector object). * @exception EmptyStackException if this stack is empty. */ E pop(); /** * Looks at the object at the top of this stack without removing * it from the stack. * @return the object at the top of this stack (the last * item of the Vector object). * @exception EmptyStackException if this stack is empty. */ E peek(); /** * Tests if this stack is empty. * @return true if and only if this stack contains no items; */ boolean empty(); } Bei den Methoden push und empty gibt es keine Vorbedingungen. Die Methoden pop und peek werfen eine Runtime-Exception, wenn der Stack leer ist.

1-8

1 Einfuhrung ¨

¨ ¨ Die Spezifikation eines Datentyps muss vollstandig, prazise und eindeutig sein. Weiter wollen wir keine Beschreibung, die auf der konkreten Implementation des Datentyps basiert, obwohl diese die geforderten ¨ Kriterien erfullen wurde. Eine Beschreibung, die auf der Implementation basiert, fuhrt zu einer Uberspe¨ ¨ ¨ zifikation des Datentyps. ¨ Konkret konnen wir den Datentyp Stack zum Beispiel als Arraystruktur (mit einem Zeiger auf das aktuelle oberste Element head des Stacks) implementieren. Flexibler ist allerdings die Implementation mit Hilfe einer Listenstruktur..

1.3 Einige Begriffe: Algorithmen

1-9

1.3 Einige Begriffe: Algorithmen Ein Algorithmus2 beschreibt das Vorgehen oder eine Methode, mit der eine Aufgabe oder ein Problem ¨ werden kann, bzw. mit der eine Funktion berechnet werden kann. Ein Algorithmus besteht aus gelost ¨ einer Folge von einfachen (Rechen-)Schritten (Anweisungen), welche zur Losung der gestellten Aufgabe fuhren. Der Algorithmusgedanke ist keine Besonderheit der Informatik. In fast allen Naturwissenschaften ¨ ¨ aber auch im Alltag werden Arbeitsvorgange mit Hilfe von Algorithmen beschrieben. Jeder Algorithmus muss die folgenden Eigenschaften erfullen: ¨ 1. Er muss aus einer Reihe von konkret ausfuhrbaren ¨ Schritten bestehen. 2. Er muss in einem endlichen Text beschreibbar sein. ¨ 3. Er darf nur endlich viele Schritte benotigen (Termination). ¨ 4. Er darf zu jedem Zeitpunkt nur endlich viel Speicherplatz benotigen. 5. Er muss bei der gleichen Eingabe immer das selbe Ergebnis liefern. ¨ 6. Nach der Ausfuhrung eines Schrittes ist eindeutig festgelegt, welcher Schritt als nachstes auszufuh¨ ¨ ren ist. 7. Der vom Algorithmus berechnete Ausgabewert muss richtig sein (Korrektheit).

Bemerkung: Die Forderung nach Eindeutigkeit wird etwa in parallelen oder probabilistischen Algorith¨ men zum Teil fallengelassen. Nach dem Abschluss eines einzelnen Schrittes ist der nachste Schritt nicht ¨ ¨ eindeutig bestimmt, sondern es existiert eine endliche Menge von (moglichen) nachsten Schritten. Die ¨ Auswahl des nachsten Schrittes aus der gegebenen Menge ist nichtdeterministisch.

Der Anspruch, dass alle Algorithmen terminieren mussen, bedeutet, dass nicht alle von uns benutzten ¨ Programme Algorithmen sind. Editoren, Shells oder das Betriebssystem sind alles Programme, die nicht (von selber) terminieren. ¨ Wir konnen aber jedes dieser Programme als Sammlung von verschiedenen Algorithmen betrachten, welche in verschiedenen Situationen zur Anwendung kommen. ¨ 825 vor Christus ein ˆ 2 Das Wort Algorithmus stammt vom Persischen Autor Abu Ja’far Mohammed ibn Mus ı, welcher ungefahr ˆ aˆ al-Khowarizmˆ Buch uber arithmetische Regeln schrieb. ¨

1-10

1 Einfuhrung ¨

Algorithmen werden der Einfachheit halber oft in einer Pseudocode Sprache formuliert. Damit erspart man sich alle technischen Probleme, welche die konkrete Umsetzung in eine Programmiersprache mit¨ bringen konnte.

¨ Beispiel: Grosster gemeinsamer Teiler von m und n: Die kleinere der beiden Zahlen wird so lange von ¨ der grosseren subtrahiert, bis beide Werte gleich sind. Dies ist dann der GgT von m und n. Initialisiere m und n Wiederhole solange m und n nicht gleich sind Ja

Ist m > n ?

Verringere m um n

Verringere n um m

Gib m aus

Siehe auch [4]: Programmieren in Java, Kapitel 3.

Beispiele von Algorithmen in Java

int proc( int n ) { return n/2; }

bool isPrim( int n ) { return false; }

//

Nein

return true if n is a prime

1.3 Einige Begriffe: Algorithmen

¨ Das nachste Beispiel stammt von L. Collatz (1937):

long stepNum( long n ) { // return number of steps long m = 0; while( n > 1 ) { if( n%2 == 0 ){ n = n/2; } else { n = 3*n + 1; } m++; } return m; }

1-11

1-12

1 Einfuhrung ¨

1.4 Algorithmen Schema: Iteration Unter einem Algorithmen-Schema verstehen wir ein Verfahrens-Muster oder eine allgemeine Methode, ¨ werden kann. Nicht jede Methode ist fur mit welcher ein Problem gelost ¨ jedes Problem gleich gut geeignet. Umso wichtiger ist es also, die verschiedenen Algorithmen-Schemata zu kennen. ¨ ¨ Ein Problem wird durch Iteration gelost, falls der zugehorige Algorithmus einen Loop (while- oder forSchleife) benutzt. Iteration ist zum Beispiel dann sinnvoll, wenn die Daten in einem Array (oder einer 3 Liste) abgelegt sind und wir mit jedem Element des Array die gleichen Schritte durchfuhren mussen . ¨ ¨ Beispiel: Das Addieren zweier Vektoren kann wie folgt implementiert werden:

public DVector sum(DVector v1) throws VectorException { if (v1.size != size) throw new VectorException("Incompatible vector length"); DVector res = new DVector(size); for (int i = 0; i < size; i++) res.value[i] = v1.value[i] + value[i]; return res; }

1.5 Algorithmen Schema: Greedy (die gierige Methode) ¨ ¨ Greedy-Verfahren werden vor allem dann erfolgreich eingesetzt, wenn von n moglichen Losungen eines ¨ Problems die bezuglich einer Bewertungsfunktion f optimale Losung gesucht wird (Optimierungsproble¨ me). Die Greedy-Methode arbeitet in Schritten, ohne mehr als einen Schritt voraus- oder zuruckzublicken. Bei ¨ ¨ jedem Schritt wird aus einer Menge von moglichen Wegen derjenige ausgesucht, der den Bedingungen des Problems genugt ¨ und lokal optimal ist. 3 Solche Algorithmen lassen sich oft auch sehr einfach parallelisieren.

1.5 Algorithmen Schema: Greedy (die gierige Methode)

1-13

Wir wollen die Arbeitsweise dieser Methode an einem anschaulichen Beispiel illustrieren. Wir nehmen ¨ an, dass jemand sich irgendwo auf einem Berg befindet und so schnell wie moglich zum Gipfel kommen ¨ mochte. Eine einfache Greedy-Strategie fur ¨ dieses Problem ist die folgende: Bewege dich immer entlang ¨ ¨ der grossten Steigung nach oben bis dies nicht mehr moglich ist, das heisst, bis in allen Richtungen die Wege nur noch nach unten fuhren. ¨ Dieser Ansatz ist in der Abbildung 1.1 dargestellt. Es ist ein typischer Greedy-Ansatz. Man schaut nicht ¨ jeweils die lokal optimale Strategie. zuruck ¨ und wahlt

Abbildung 1.1: Hill climbing Maximum Lokales Maximum

Abbildung 1.2: Erreichen eines lokalen Maximums mit Greedy

In der Abbildung 1.2 sehen wir aber, dass diese Strategie nicht unbedingt zum (optimalen) Ziel fuhrt. Hat ¨ der Berg mehrere Nebengipfel, so bleiben wir vielleicht auf einem solchen Nebengipfel stehen. ¨ ¨ Bei Problemen dieser Art liefert oft nur ein exponentieller Algorithmus eine global beste Losung, wahrend 4 ¨ ein heuristischer Ansatz mit Greedy nicht immer die beste Losung liefert, dies aber in polynomialer Zeit. ¨ Ahnliche Probleme sind das Finden von kurzesten Wegen, oder besten (Spiel-)Strategien, Verpackungs¨ ¨ probleme (moglichst viele verschieden grosse Kisten in einen Lastwagen packen) oder Scheduling von verschieden langen Prozessen auf Mehrprozessor-Rechnern. 4 Eine Heuristik ist eine Richtlinie, ein generelles Prinzip oder eine Daumenregel, welche als Entscheidungshilfe benutzt werden kann.

1-14

1 Einfuhrung ¨

Ein weiteres Problem dieser Art ist das Suchen eines minimalen Pfades in einem allgemeinen Graphen. ¨ ¨ Um eine optimale Losung zu finden, mussten wir im wesentlichen samtliche Pfade abgehen und deren ¨ ¨ das Problem viel schneller, indem er jeweils lokal Gewichte aufschreiben. Ein Greedy-Algorithmus lost ¨ den kurzesten (leichtesten) Pfad wahlt. Allerdings findet man mit dieser Methode nicht unbedingt den ¨ insgesamt kurzesten Pfad. ¨ Es existieren aber auch Probleme, bei denen der Greedy-Ansatz zum optimalen Ergebnis fuhrt. Ein ¨ ¨ das folgende Problem: Finde ein minimales (maximales) Gerust Greedy-Algorithmus lost ¨ in einem ge¨ man jeweils die Kante, die das kleinste (grosste) ¨ wichteten Graphen. Dabei wahlt Gewicht hat und keinen ¨ Zyklus verursacht. Der Algorithmus ist fertig, sobald ein zusammenhangender Teilgraph entstanden ist. 9

9

3

3

7

5

6

6

5

5

x

7

5 4

4 6

8

7

6

8

7

3

5

1

8

8

7 8

8 4 3

5

1 4

y

3 7

9

5

x

1.6 Algorithmen Schema: Rekursion

1-15

1.6 Algorithmen Schema: Rekursion Rekursion ist ein fundamentales Konzept der Informatik. Eine Prozedur heisst rekursiv, wenn sie sich direkt oder indirekt selber aufruft. Dabei mussen wir darauf achten, dass eine Abbruchbedingung existiert, ¨ damit die Prozedur in jedem Fall terminiert. ¨ Beispiele: Die rekursive Implementation der Fakultatsfunktion:

long factorial( int n ) { if( n = 0 && index < size) return unlink(node(index)); else throw new IndexOutOfBoundsException( ... ); } E unlink(Node x) { final E element = x.item; final Node next = x.next; final Node prev = x.prev; if (prev == null) { first = next; } else { prev.next = next; } if (next == null) { last = prev; } else { next.prev = prev; } x.item = null; size--; return element; }

x.prev = null;

x.next = null;

3.3 Stacks und Queues

3-11

3.3 Stacks und Queues Ein Interface fur ¨ einen Stack hatten wir im Abschnitt 1.2 bereits gesehen. Stacks sind einfache Listen¨ strukturen, bei denen bloss am Kopf Elemente eingefugt, gelesen, bzw. geloscht werden durfen. ¨ ¨

Wir betrachten hier die Implementation eines Stacks mit Hilfe von Zeigerstrukturen.

public class Stack { private Node first; private int size = 0; public E push(E item) { first = new Node(item, first); size++; return item; } public E pop() { if (size==0) throw new EmptyStackException(); Node e = first; E result = e.element; first = e.next; e.element = null; e.next = null; size--; return result; } public E peek() { if (size==0) throw new EmptyStackException(); return first.element;

3-12

3

Datentypen: Listen, Stacks und Queues

} public boolean empty() { return size == 0; } private static class Node { E element; Node next; Node(E element, Node next) { this.element = element; this.next = next; } } }

¨ ¨ In einer Queue konnen Elemente nur am Ende angefugt Ele¨ werden. Nur am Kopf der Queue konnen ¨ mente gelesen, bzw. geloscht werden.

3.4 Iteratoren

3-13

3.4 Iteratoren Auf Listenstrukturen hat man ublicherweise eine Hilfsklasse, welche zum Durchlaufen der Liste dient. Die ¨ zwei wichtigsten Methoden von Iterator Klassen sind hasNext zum Prufen, ob das Ende der Liste erreicht ¨ ¨ ist, sowie die Methode next, welche den Inhalt des nachsten Elements zuruckgibt. ¨

public interface Iterator { /** * Returns true if the iteration has more elements. */ boolean hasNext(); /** * Returns the next element in the iteration. * @exception NoSuchElementException iteration has no more elements. */ E next(); ... }

3-14

3

Datentypen: Listen, Stacks und Queues

¨ 3.5 Ubung 3 Fur ¨ die Implementationsaufgabe finden Sie Rahmenprogramme unter www.sws.bfh.ch/∼amrhein/AlgoData/ 1. List Iterator Entwerfen Sie (ausgehend vom Rahmenprogramm) eine Klasse ListItr, welche als Iterator fur ¨ die LinkedList verwendet werden kann. Implementieren Sie dazu in der MyLinkedList Klasse eine innere Klasse ListItr mit einem Konstruktor ListItr(int index), welcher ein ListIterator Objekt erzeugt, welches an die Position index zeigt. Implementieren Sie ausserdem die Methoden Object next(), boolean hasNext(), boolean hasPrevious() und Object previous().

2. Queue ¨ dem gegebenen Rahmenprogramm. Implementieren Sie eine Queue gemass -

Implementieren Sie die Queue zuerst als Liste.

-

Als zweites implemtieren Sie die Queue als Array. In der Array-basierten Queue durfen Sie annehmen, dass die Queue nicht mehr als MAX viele ¨ ¨ Elemente enthalten muss. Uberlegen Sie sich eine Implementierung, welche nicht nach jedem ¨ Einfugen oder Loschen den ganzen Array umkopiert. ¨

3. Das Collection Interface Zeichnen Sie die Klassenhierarchie der (wichtigsten) Collection Klassen. Zeichnen Sie die Hierarchie der Interfaces List, Queue, Set und SortedSet, sowie der Klassen ArrayList, HashSet, LinkedHashSet, LinkedList, PriorityQueue, Stack, TreeSet, Vector

4

¨ Datentypen: Baume, Heaps

¨ Alle bisher betrachteten Strukturen waren linear in dem Sinn, dass jedes Element hochstens einen Nach¨ folger hat. In einem Baum kann jedes Element keinen, einen oder beliebig viele Nachfolger haben. Baume sind wichtig als Strukturen in der Informatik, da sie auch oft im Alltag auftauchen: zum Darstellen von ¨ Abhangigkeiten oder Strukturen, als Organigramme von Firmen, als Familienstammbaum, aber auch zum Beschleunigen der Suche. Definition: Ein Graph ist definiert als ein Paar B = (E, K) bestehend aus je einer endlichen Menge E von Ecken (Knoten, Punkten) und einer Menge von Kanten. Eine Kante wird dargestellt als Zweiermenge von Ecken {x, y}, den Endpunkten der Kante.

¨ ¨ Ein Baum ist ein Graph mit der zusatzliche Einschrankung, dass es zwischen zwei Ecken nur eine (direkte oder indirekte) Verbindung gibt1 . ¨ ¨ Wir befassen uns hier zuerst vor allem mit einer besonderen Art von Baumen: den Binarbaumen. Ein ¨ falls jeder Knoten hochstens ¨ Baum heisst binar, zwei Nachfolger hat. ¨ 1 Ein Baum ist ein zusammenhangender Graph ohne Zyklen.

4-2

.

¨ 4 Datentypen: Baume, Heaps

.

.

. . . . .. . .. ¨ Definition: Ein binarer Baum besteht aus einer Wurzel (Root) und (endlich vielen) weiteren Knoten und verbindenden Kanten dazwischen. Jeder Knoten hat entweder keine, ein oder zwei Nachfolgerknoten. ¨ Ein Weg in einem Baum ist eine Liste von disjunkten, direkt verbunden Kanten. Ein binarer Baum ist ¨ ¨ ¨ vollstandig (von der Hohe n), falls alle inneren Knoten zwei Nachfolger haben und die Blatter maximal ¨ Weglange n bis zur Wurzel haben. ¨ Jedem Knoten ist eine Ebene (level) im Baum zugeordnet. Die Ebene eines Knotens ist die Lange des ¨ Pfades von diesem Knoten bis zur Wurzel. Die Hohe (height) eines Baums ist die maximale Ebene, auf der sich Knoten befinden. ¨ Ein binarer Baum besteht also aus Knoten mit einem (Zeiger auf ein) Datenelement data , einem linken Nachfolgerknoten left und einem rechten Nachfolgerknoten right .

left

public class BinaryTreeNode { protected T data; protected BinaryTreeNode leftChild; protected BinaryTreeNode rightChild;

right

4-3

public BinaryTreeNode(T item){ data=item; } // tree traversals public BinaryTreeNode inOrderFind(T item) { . . . } public BinaryTreeNode postOrderFind(T item) { . . . } public BinaryTreeNode preOrderFind(T item) { . . .} // getter and setter methods . . . public class BinaryTree { protected BinaryTreeNode rootTreeNode; public BinaryTree(BinaryTreeNode root) { this.rootTreeNode = root; } // tree traversals public BinaryTreeNode inOrderFind(T item) { return rootTreeNode.inOrderFind(item); } public BinaryTreeNode preOrderFind(T item) { ... } public BinaryTreeNode postOrderFind(T item) { ... } public BinaryTreeNode postOrderFindStack(T item) { ... } //getter and setter methods . . .

4-4

¨ 4 Datentypen: Baume, Heaps

¨ 4.1 Baumdurchlaufe ¨ ¨ Baume konnen auf verschiedene Arten durchlaufen werden. Die bekanntesten Verfahren sind Tiefensuche (depth-first-search, DFS) und Breitensuche (breadth-first-search, BFS). Tiefensuche kann unter¨ ¨ schieden werden in die drei Typen praorder, postorder und inorder, abhangig von der Reihenfolge der rekursiven Aufrufe.

4.1.1 Tiefensuche ¨ Praorder

• Betrachte zuerst den Knoten (die Wurzel des Teilbaums), • durchsuche dann den linken Teilbaum, • durchsuche zuletzt den rechten Teilbaum. Inorder

• Durchsuche zuerst den linken Teilbaum, • betrachte dann den Knoten, • durchsuche zuletzt den rechten Teilbaum. Postorder

• Durchsuche zuerst den linken Teilbaum, • durchsuche dann den rechten Teilbaum, • betrachte zuletzt den Knoten.

¨ 4.1 Baumdurchlaufe

.

. . .. . . . . . . . .. . ..

.

4-5

.

. . . .. . . . . . . . .. . ..

¨ Wir betrachten als Beispiel fur ¨ die Tiefensuche den Praorder-Durchlauf.

public BinaryTreeNode preOrderFind(T item) { if (data.equals(item)) return this; if (leftChild != null) { BinaryTreeNode result = leftChild.preOrderFind(item); if (result != null) return result; } if (rightChild != null) { BinaryTreeNode result = rightChild.preOrderFind(item); if (result != null) return result; } return null;

4-6

¨ 4 Datentypen: Baume, Heaps

}

4.1.2 Tiefensuche mit Hilfe eines Stacks ¨ ¨ Mit Hilfe eines Stacks konnen wir die rekursiven Aufrufe in der praorder Tiefensuche vermeiden. Auf dem ¨ Stack werden die spater zu behandelnden Baumknoten zwischengespeichert.

public BinaryTreeNode preOrderFindStack(T item) { Stack stack = new Stack(); stack.push(this.rootTreeNode); while (!stack.isEmpty()) { BinaryTreeNode tmp = stack.pop(); if (tmp.getData().equals(item)) return tmp; if (tmp.getRightChild() != null) stack.push(tmp.getRightChild()); if (tmp.getLeftChild() != null) stack.push(tmp.getLeftChild()); } return null; }

¨ 4.1 Baumdurchlaufe

4-7

4.1.3 Breitensuche mit Hilfe einer Queue Bei der Breitensuche besucht man jeweils nacheinander die Knoten der gleichen Ebene:

• Starte bei der Wurzel (Ebene 0). ¨ ¨ • Bis die Hohe des Baumes erreicht ist, setze den Level um eines hoher und gehe von links nach rechts durch alle Knoten dieser Ebene.

.

.

.

. . . . .. . .. Bei diesem Verfahren geht man nicht zuerst in die Tiefe, sondern betrachtet von der Wurzel aus zuerst ¨ alle Elemente in der naheren Umgebung. Um mittels Breitensuche (levelorder) durch einen Baum zu wandern, mussen wir uns alle Baumknoten einer Ebene merken. Diese Knoten speichern wir in einer ¨ ¨ ¨ Queue ab, so dass wir spater darauf zuruckgreifen konnen. ¨

public BinaryTreeNode levelOrderFind(T item) { QueueImpl queue = new QueueImpl(); queue.add(rootTreeNode); while (!queue.isEmpty()) { BinaryTreeNode tmp = queue.poll(); if (tmp.getData().equals(item)) return tmp; if (tmp.getLeftChild() != null) queue.add(tmp.getLeftChild()); if (tmp.getRightChild() != null) queue.add(tmp.getRightChild()); } return null; }

¨ 4 Datentypen: Baume, Heaps

4-8

¨ Suchbaume ¨ 4.2 Binare ¨ ¨ Ein binarer Suchbaum ist ein Baum, welcher folgende zusatzliche Eigenschaft hat: Alle Werte des linken Nachfolger-Baumes eines Knotens K sind kleiner, alle Werte des rechten ¨ Nachfolger-Baumes von K sind grosser als der Wert von K selber. ¨ ¨ Der grosse Vorteil von binaren Suchbaumen ist, dass wir sowohl beim Einfugen als auch beim Suchen ¨ von Elementen immer bloss einen der zwei Nachfolger untersuchen mussen. Falls der gesuchte Wert ¨ kleiner ist als der Wert des Knotens, suchen wir im linken Teilbaum, anderenfalls im rechten Teilbaum weiter.

¨ Beispiel: Die folgenden zwei Baume entstehen durch Einfugen der Zahlen 37, 43, 53, 11, 23, 5, 17, 67, ¨ 47 und 41 in einen leeren Baum. Einmal werden die Zahlen von vorne nach hinten eingefugt, ¨ das zweite Mal von hinten nach vorne.

¨ Suchbaume ¨ 4.2 Binare

public class BinarySearchTreeNode { public void add(T item) { int compare = data.compareTo(item); if (compare > 0) { // (data > item)? if (leftChild == null) leftChild = new BinarySearchTreeNode(item); else leftChild.add(item); // left recursion } else { // (item >= data) if (rightChild == null) rightChild = new BinarySearchTreeNode(item); else rightChild.add(item); // right recursion } } public BinarySearchTreeNode find(T item) { int compare = data.compareTo(item); if (compare == 0) return this; if (compare > 0 && leftChild != null) // data > item return leftChild.find(item); if (compare < 0 && rightChild != null) // data < item return rightChild.find(item); return null; } . . . }

4-9

¨ 4 Datentypen: Baume, Heaps

4-10

¨ 4.3 B-Baume ¨ ¨ Ein B-Baum ist ein stets vollstandig balancierter und sortierter Baum. Ein Baum ist vollstandig balanciert, ¨ wenn alle Aste gleich lang sind. In einem B-Baum darf die Anzahl Kindknoten variieren. Ein 3-4-5 BBaum ist zum Beispiel ein Baum, in welchem jeder Knoten maximal 4 Datenelemente speichern und ¨ jeder Knoten (ausser der Wurzel und den Blattern) minimal 3 und maximal 5 Nachfolger haben darf (der ¨ Wurzelknoten hat 0-4 Nachfolger, Blatter haben keine Nachfolger). ¨ ¨ Durch die flexiblere Anzahl Kindknoten ist das Rebalancing weniger haufig notig. Ein Knoten eines B-Baumes speichert:

• • • •

eine variable Anzahl s von aufsteigend sortierten Daten-Elementen k1 , . . . , ks eine Markierung isLeaf, die angibt, ob es sich bei dem Knoten um ein Blatt handelt.

s + 1 Referenzen auf Kindknoten, falls der Knoten kein Blatt ist. ¨ Jeder Kindknoten ist immer mindestens zur Halfte gefullt. ¨

Die letzte Bedingung lautet formal: es gibt eine Schranke m, so dass m 0)) { Collections.swap(heap, crt, parent(crt)); crt = parent(crt); } } }

4.4 Priority Queues

4-19

¨ ¨ befindet sich im Element heap[0] und wird vom removeMax Das Element mit der hochsten Prioritat Heap entfernt. heap[0] wird nun mit heap[length-1] uberschrieben und length um eins verringert. ¨ ¨ ¨ Damit erhalten wir wieder einen fast vollstandigen binaren Baum. Das neue Element heap[0] verletzt nun vermutlich die Heapbedingung. ¨ seiner beiden Nachfolger und fahren so fort, bis die Wir vertauschen also heap[0] mit dem grosseren Heapbedingung wieder erfullt ¨ ist.

65

public T removeMax() { if (heap.isEmpty()) return null; Collections.swap(heap, 0, heap.size() - 1); T element = heap.remove(heap.size() - 1); if (heap.size() > 1) siftDown(0); return element; }

4-20

¨ 4 Datentypen: Baume, Heaps

private void siftDown(int position) { while (!isLeaf(position)) { int j = leftChild(position); if ((j < heap.size() - 1) // heap[j] < heap[j+1] && (heap.get(j).compareTo(heap.get(j + 1)) < 0)) { j++; } // heap[position] >= heap[j] if (heap.get(position).compareTo(heap.get(j)) >= 0) { return; } Collections.swap(heap, position, j); position = j; } }

¨ 4.5 Ubung 4

4-21

¨ 4.5 Ubung 4 ¨ Suchbaume ¨ Binare ¨ Suchbaume, ¨ Bauen Sie aus der folgenden Zahlenreihe zwei binare indem Sie die Zahlen einmal von links nach rechts und einmal von rechts nach links lesen. 39, 40, 50, 10, 25, 5, 19, 55, 35, 38, 12, 16, 45

Heaps ¨ Loschen Sie aus dem folgenden Heap zuerst drei Elemente, fugen Sie danach ein neues Element ¨ ¨ 42 in den Heap ein. mit Prioritat 52 43

32

35 17

20 22

12

15 6

8

18 5

3

BTree ¨ Fugen Sie im folgenden BTree zuerst das Element 42 ein, loschen Sie dann die Elemente 28 und 45. ¨

4-22

5

Suchen

5.1 Grundlagen ¨ Suchen ist eine der haufigsten Operationen, die mit dem Computer ausgefuhrt werden. Normalerweise ¨ geht es darum, in einer Menge von Daten die richtigen Informationen zu finden. Wir kennen zum Beispiel ¨ einen Namen und suchen die zugehorige Mitgliedernummer. Oder wir geben (mit Hilfe einer EC-Karte) ¨ eine Kontonummer ein und das System sucht das dazugehorige Konto. Oder wir kennen eine Telefon¨ nummer und suchen den dazugehorigen Abonnenten, usw. Wenn wir im folgenden jeweils Listen von Zahlen durchsuchen, so tun wir das bloss der Einfachheit ¨ halber. Die gleichen Algorithmen konnen naturlich fur ¨ ¨ beliebige Objekte angewandt werden. Ein Objekt kann zum Beispiel eine Klasse Adresse mit den Member-Variablen name, vorname, strasse, wohnort, telefonNummer, kundenNummer sein. Dann verwenden wir eine der Member-Variablen als Suchschlussel, also zum Beispiel Adresse.name . ¨ ¨ Da Suchalgorithmen so haufig verwendet werden, lohnt es sich, diese effizient zu implementieren. An¨ derseits spielt naturlich die Lange der zu durchsuchenden Datenmenge eine entscheidende Rolle: Je ¨ ¨ grosser die Datenmenge, desto wichtiger die Effizienz der Suche. Ausserdem spielt die benutzte Datenstruktur eine entscheidende Rolle. Wir werden hier jeweils annehmen, dass wir auf alle Elemente der Datenfolge schnellen wahlfreien Zugriff haben (wie z. Bsp. in einer ArrayList). Falls dies nicht der Fall ist, sind gewisse Suchalgorithmen sehr viel weniger effizient.

5-2

5 Suchen

Falls kein wahlfreier Zugriff existiert, kann dies mit Hilfe eines Pointer-Arrays simuliert werden, in welchem die Adressen der Daten-Objekte gespeichert sind. Die richtige Wahl der benutzten Datenstruktur ist entscheidend, ob ein Algorithmus effizient implementiert werden kann oder nicht.

5.2 Lineare Suche Wie der Name schon sagt, gehen wir bei der linearen Suche linear durch die Suchstruktur und testen jedes Element, bis wir das gesuchte finden oder ans Ende gelangen.

/** * Searches for the first occurence of the given argument. * @param elem an object. * @return the index of the first occurrence of the argument in * this list; returns -1 if the object is not found. */ public int indexOf(Object elem) { if (elem == null) { for (int i = 0; i < size; i++) if (elementData[i]==null) return i; } else { for (int i = 0; i < size; i++) if (elem.equals(elementData[i])) return i; } return -1; } ¨ Die for -Schleife bricht spatestens dann ab, wenn das letzte Element der Liste gepruft ¨ ist.

¨ der linearen Suche Komplexitat ¨ Um die Effizienz der linearan Suche zu bestimmen, bestimmen wir die Anzahl der notigen Vergleiche in ¨ ¨ Abangigkeit von der Lange n der Folge.

¨ Suche 5.3 Binare

5-3

¨ Suche 5.3 Binare ¨ ¨ ¨ ¨ Eine Folge, auf die sehr haufig zugegriffen (und nicht so haufig verandert) wird, sollte wenn moglich ¨ sortiert gehalten werden1 . Dies lasst sich leicht realisieren, indem die neuen Elemente jeweils an der richtigen Stelle einsortiert werden2 . Falls die Daten so dargestellt sind, kann das Suchen auf sehr viel schnellere Art und Weise realisiert ¨ Suche. Bei der binaren ¨ werden, zum Beispiel durch binare Suche wird die Folge in zwei Teile

Elem[0] |

· · · Elem[p-1] {z }

Elem[p+1] |

· · · Elem[len] {z }

geteilt und das Element Elem[p] mit dem zu suchenden Element a verglichen. Falls Elem[p] kleiner als a ist, suchen wir in der rechten Teilfolge weiter, andernfalls in der linken. r = size l=0

a

p = (r+l)/2

¨ Die ersten drei Schritte der binaren Suche

Da die Folge sortiert ist, ist dieses Vorgehen korrekt. 1 Dies setzt naturlich voraus, dass sich die Datenelemente sortieren lassen, also eine Ordnungsrelation < auf den Datenschlusseln existiert. ¨ ¨ In Java bedeutet dies, die Elemente mussen das Comparable Interface erfullen, d.h. eine compareTo() Methode haben. ¨ ¨ ¨ ¨ 2 Im Abschnitt uber Baume sind wir bereits der speziell dafur Baumes begegnet ¨ ¨ konzipierten Datenstruktur des binaren

5-4

5 Suchen

¨ Wenn wir die Folge jeweils nicht in der Mitte teilen, sondern p = l oder p = r wahlen, erhalten wir die ¨ lineare Suche als Spezialfall der binaren Suche.

/** * return index where item is found, or -1 */ public int binarySearch( Comparable[ ] a, T x ) { int l = 0; int p; int r = a.length - 1; while( l 0 ) r = p - 1; // else return p; } return -1 }

¨ der binaren ¨ Komplexitat Suche

a[p] < x

a[p] > x

5.4 Hashing

5-5

5.4 Hashing ¨ Eine Hash-Tabelle ist eine Ubersetzungstabelle, mit der man rasch auf jedes gesuchte Element einer ¨ einer zeigerbasierten Liste bietet. Hashtabellen Liste zugreifen kann, welche aber trotzdem die Flexibilitat werden zum Beispiel auch bei Compilern benutzt, um die Liste der Variablen (und ev. deren Typen) zu verwalten. ¨ Ein einfaches Beispiel einer Hash-Tabelle ist ein Array. In einem Array konnen wir auf jedes Element direkt zugreifen. Einen Array als Hash-Tabelle zu benutzen ist dann gunstig, wenn wir genugend Platz ¨ ¨ ¨ ¨ haben, um einen Array der Lange Anzahl moglicher Schlussel anzulegen. ¨ ¨ Beispiel: Falls alle Mitglieder eines Vereins unterschiedliche Initialen haben, konnen wir dies als Schlussel fur ¨ ¨ die Hashtabelle benutzen. Berta Amman Doris Bucher Chris Carter Friedrich Dunner ¨

BA DB CC FD

7→ 7 → 7 → 7 →

(2,1) (4,2) (3,3) (6,4)

¨ ¨ Mit einem Array der Lange 26×26 konnen wir auf jedes Mitglied direkt zugreifen. Hash−Tabelle

(H,R)

1

Menge aller Schlüssel (D,H) (U,M)

(C,F)

... (H,A) (B,A)

(C,C)

(W,S)

2

3

4

... Daten(BA)

Daten(DB) Daten(CC)

...

Benötigte Schlüssel (D,B) (F,D)

1 2 3 4 5 6

Daten(FD)

Der Nachteil hierbei ist, dass wir bei dieser Methode offensichtlich viel Speicherplatz verschwenden. Aus¨ serdem konnen wir nicht sicher sein, dass nicht eines Tages ein neues Mitglied mit bereits existierenden ¨ Initialen in unseren Verein eintreten will. Fur zu ¨ beide Probleme versuchen wir im folgenden Losungen finden.

5-6

5 Suchen

5.4.1 Hash-Funktionen Eine Hash-Funktion ist eine Methode, mit welcher wir mit Hilfe von einfachen arithmetischen Operationen ¨ die Speicherstelle eines Elementes aus seinem Schlussel berechnen konnen. Optimalerweise sollte eine ¨ Hash-Funktion jedem Element einer Menge einen anderen Funktionswert zuordnen (vermeiden von Kol¨ lisionen). Dies ist leider in der Regel nicht moglich. Allerdings gibt es Hash-Funktionen, welche diesen Anspruch besser, und solche, welcher ihn weniger gut erfullen. ¨

Definition: Eine Hashfunktion sollte mindestens die folgenden Eigenschaften erfullen. ¨ ¨ • Der Hashwert eines Objekts muss wahrend der Ausfuhrung der Applikation gleich bleiben. ¨ ¨ Der Hashwert darf aber bei einer nachsten Ausfuhrung der Applikation anders sein. ¨ ¨ der equals() Methode, muss der Hashwert der beiden Objekte • Falls zwei Objekte gleich sind gemass gleich sein. ¨ • Zwei verschiedene Objekte konnen den gleichen Hashwert haben. Allerdings wird die Performance von Applikationen verbessert, wenn unterschiedliche Objekte unterschiedliche Hashwerte haben.

Die folgende Hash-Funktion summiert die ASCII-Werte der Buchstaben eines Strings. Fur ¨ das Einfugen ¨ ¨ in die Hashtabelle muss dieser Wert dann modulo der Lange der Hashtabelle genommen werden.

long Elem_hash(Object key) { String keyString = key.toString(); int sum = 0; for (int i = 0; i < keyString.length(); i++) sum = sum + keyString.charAt(i); return sum; } ¨ Wie wir am folgenden Beispiel sehen, funktioniert diese Methode leider nicht allzu gut. Alle Worter, die aus den gleichen Buchstaben (in anderer Reihenfolge) bestehen, haben den gleichen Funktionswert. ¨ Aber auch sonst verteilt diese Methode verschiedene Worter offensichtlich nicht optimal (erzeugt viele Kollisionen).

5.4 Hashing

5-7 Anna

Jochen

Otto

Gabi

Tina

Kurt

Elem_hash()

Tina

Otto

Jochen

Anna

Kurt

0

1

2

3

4

Gabi

5

6

7

8

9

10

Eine oft benutzte, gut streuende Hash-Funktion fur ¨ Strings ist die ELF hash -Methode.

long ELFhash(String key) { long h = 0; for (int i = 0; i < key.length(); i++) { h = (h >> 24; // XOR, Shift right h = h & ˜g; } return h; } Anna

Jochen

Otto

Gabi

Tina

Kurt

ELF_hash()

Kurt

0

Otto

1

2

Gabi Anna

3

4

5

Tina

6

Jochen

7

8

9

10

5-8

5 Suchen

Naturlich kann auch die ELF hash -Methode nicht alle Kollisionen vermeiden. Die ELF hash -Methode ¨ ¨ ¨ zum Beispiel fur mit Tabellenlange 11 erhalt ¨ “Otto” und “Martin” den gleichen Schlussel. ¨ Die Frage ist darum: wie behandelt man Kollisionen?

5.4.2 Double Hashing Bei Double Hashing verwenden wir zwei verschiedene Hashfunktionen Hash1 und Hash2 3 . Hash1 dient dazu, die Hashadresse eines Schlussels in der Tabelle zu suchen. Hash2 dient dazu, bei einer Kollision ¨ ¨ in der Tabelle den nachsten freien Platz zu suchen. Hash 1 (k) 0

Hash 2(k)

leeres Feld besetztes Feld

2

neuer Eintrag

3 4 5 6 7 8 9 10 11

Einfugen ¨ eines Objekts mit Schlussels ¨ k: Zuerst wird die Hashadresse Hash1 (k) mod N berechnet. Ist dieser Tabellenplatz noch leer, dann wird das Objekt dort eingetragen. Ist der Tabellenplatz schon besetzt, so wird nacheinander bei den Adressen (Hash1 (k) + Hash2 (k)) mod N, (Hash1 (k) + 2 · Hash2 (k)) mod N,

... ¨ ¨ 3 Im einfachsten Fall wahlen wir Hash2 (k) = p fur N der Hashtabelle nicht teilt. Dann ¨ p gleich 1 oder fur ¨ eine Primzahl p, welche die Lange erhalten wir den sog. Linear Probing Algorithmus. Im Allgemeinen kann Hash2 (k) eine beliebige Funktion sein, welche Werte relativ prim zu N liefert.

5.4 Hashing

5-9

¨ gesucht, bis ein freier Platz gefunden wird (N = Lange der Tabelle). An der ersten freien Stelle wird das Objekt eingetragen. ¨ Damit bei der Suche nach einem freien Platz alle Elemente der Tabelle durchsucht werden konnen, muss Hash2 (k) fur ¨ alle k relativ prim zu N sein (d.h. Hash2 (k) und N haben keine gemeinsamen Primfaktoren). Sobald die Hashtabelle den maximalen Fullstand ubersteigt (z.B. 60%), muss die ¨ ¨ ¨ Hashtabelle vergrossert und ein Rehashing vorgenommen werden (alle Elemente neu zuteilen). Suchen des Objekts mit Schlussels ¨ k:

Die Hashadresse Hash1 (k) mod N wird berechnet. Ist das gesuchte Element mit Schlussel k an dieser Adresse gespeichert, ist die ¨ Suche erfolgreich. Falls nicht, wird der Wert Hash2 (k) berechnet und das Element in den Tabellen¨ platzen (Hash1 (k) + Hash2 (k)) mod N, (Hash1 (k) + 2 · Hash2 (k)) mod N,

... gesucht. Die Suche wird durch eine der drei folgenden Bedingungen abgebrochen: • Das Element wird gefunden. Die Suche ist erfolgreich abgeschlossen.

• Ein leerer Tabellenplatz wird gefunden, • oder wir geraten bei der Suche in einen Zyklus (hypothetischer Fall).

¨ In den beiden letzten Fallen ist das gesuchte Element nicht in der Tabelle. Hash1 (k) Hash2 (k)

leeres Feld besetztes Feld gelöschtes Feld

5-10

5 Suchen

¨ ¨ Loschen eines Schlussels: ¨ Das Loschen von Elementen aus einer Tabelle mit Double Hashing ist etwas heikel. Damit ein in der Tabelle eingetragener Schlussel in jedem Fall wieder gefunden wird, ¨ ¨ durfen wir die Elemente aus der Tabelle nicht einfach loschen, da wir nicht wissen, ob sie eventuell ¨ als Zwischenschritt beim Einfugen von anderen Elementen benutzt wurden. ¨ Jeder Tabelleneintrag muss darum ein Flag besitzen, welches angibt, ob ein Eintrag leer, benutzt ¨ oder geloscht ist. Die oben beschriebene Suche darf dann nur bei einem leeren Element abgebro¨ ¨ chen werden. Beim Loschen eines Schlussels aus der Tabelle muss der Tabellenplatz mit geloscht ¨ markiert werden. Als zweite Hashfunktion genugt ¨ oft eine sehr einfache Funktion, wie zum Beispiel Hash2 (k) = k mod 8 + ¨ 1. Um sicherzustellen, dass diese Funktionswerte teilerfremd zur Lange der Hashtabelle sind, kann man ¨ ¨ zum Beispiel als Tabellenlange eine Primzahl wahlen.

5.4 Hashing

5-11

5.4.3 Bucket Hashing ¨ Eine andere Moglichkeit, Kollisionen zu behandeln, ist das Aufteilen des Hash-Arrays in verschiedene ¨ ¨ buckets. Eine Hashtabelle der Lange H wird dabei aufgeteilt in H/B Teile (buckets), von der Grosse B. Es wird nur eine Hashfunktion benutzt, Elemente mit gleichem Hashwert (modulo Hashsize) werden ¨ ¨ im selben Bucket abgelegt. Die Hashfunktion sollte die Datenelemente moglichst gleichmassig uber die ¨ verschiedenen Buckets verteilen. Hashtable

Bucket Hashing, ¨ Buckets der Lange 4

Overflow

0

leer

1

besetzt

2

gelöscht

3 4 5 6 7 8 ...

Einfugen: ¨ Die Hashfunktion ordnet das Element dem entsprechenden Bucket zu. Falls der erste Platz im Bucket bereits besetzt ist, wird das Element im ersten freien Platz des Bucket abgelegt. ¨ Falls ein Bucket voll besetzt ist, kommt der Eintrag in einen Uberlauf (overflow bucket) von genugen¨ ¨ ¨ der Lange am Ende der Tabelle. Alle Buckets teilen sich den selben Uberlauf-Speicher. ¨ ¨ Naturlich soll der Uberlauf-Speicher moglichst gar nie verwendet werden. Sobald die Hashtabelle ¨ einen gewissen Fullgrad erreicht hat, muss ein Rehashing erfolgen, so dass die Zugriffe schnell ¨ bleiben (die Buckets nicht uberlaufen). ¨ Suchen: Um ein Element zu suchen, muss zuerst mittels der Hashfunktion der Bucket gesucht werden, in welchem das Element liegen sollte. Falls das Element nicht gefunden wurde und im Bucket noch ¨ ¨ freie Platze sind, kann die Suche abgebrochen werden. Falls der Bucket aber keine freien Platze mehr hat, muss der Overflow durchsucht werden bis das Element gefunden wurde, oder alle Elemente des Overflow uberpr uft ¨ ¨ sind. ¨ ¨ Loschen: Auch beim Bucket Hashing mussen wir beim Loschen vorsichtig sein. Falls der Bucket noch ¨ ¨ ¨ freie Platze hat, konnen wir den Tabellen-Platz einfach freigeben. Falls nicht, muss der Platz als ¨ “geloscht” markiert werden, damit beim Suchen der Elemente auch der Overflow durchsucht wird.

5-12

5 Suchen

¨ ¨ Eine Variante des Bucket Hashing ist die folgende: Wir wahlen wiederum eine Bucket-Grosse B. Wir teilen die Hashtabelle aber nicht explizit in Buckets auf, sondern bilden jeweils einen virtuellen Bucket rund um den Hashwert. Dies hat den Vorteil, dass jeder Tabellenplatz als Ausgangsposition fur ¨ das ¨ Einfugen benutzt werden kann, was die Anzahl Kollisionen bei gleicher Tabellen-Grosse vermindert. ¨ Bucket Hashing Variante, ¨ Buckets der Lange 5

Hashtable

Overflow leer besetzt gelöscht

P

neuer Eintrag

P+1 P+2

Einfugen: ¨ Die Hashfunktion berechnet den Platz P in der Hashtabelle. Falls dieser Platz bereits besetzt ist, werden die Elemente rund um P in der Reihenfolge P + 1, P + 2, . . ., P + B − 2, P + B − 1 durchsucht, bis ein freier Platz gefunden wurde. Falls kein freier Platz in dieser Umgebung gefunden ¨ wird, kommt das Element in den Uberlauf-Speicher. Suchen: Um ein Element zu suchen, muss zuerst mittels der Hashfunktion der Platz P in der Hashtabelle bestimmt werden. Falls das Element an der Stelle P nicht gefunden wird, werden die Elemente rund um P in der selben Reihenfolge wie oben durchsucht. Falls wir das Element finden, oder auf einen leeren Platz stossen, kann die Suche abgebrochen werden. Andernfalls muss der Overflow durchsucht werden bis das Element gefunden wurde, oder der ganze Overflow durchsucht ist. ¨ ¨ Loschen: Das geloschte Feld muss auch in dieser Variante markiert werden, damit wir beim Suchen keinen Fehler machen.

5.4 Hashing

5-13

5.4.4 Separate Chaining Die flexibelste Art, um Kollisionen zu beheben, ist mit Hilfe von Separate Chaining. Bei dieser Methode hat in jedes Element der Hashtabelle einen next-Zeiger auf eine verkettete Liste. Einfugen ¨ des Schlussels ¨ k: Zuerst wird die Hashadresse Hash(k) berechnet. Dann wird der neue Schlussel am Anfang der verketteten Liste eingefugt. ¨ ¨ Suchen des Schlussels ¨ k: Die Hashadresse Hash(k) wird berechnet. Dann wird k mit den Schlusseln ¨ in der entsprechenden Liste verglichen, bis k entweder gefunden wird oder das Ende der Liste erreicht ist. ¨ Loschen des Schlussels ¨ k: Das Element wird einfach aus der Liste in Hash(k) entfernt.

Hashing mit Separate Chaining

5-14

5 Suchen

¨ 5.5 Ubung 5 Linear Probing Fugen Sie mit den Hashfunkionen Hash1 (k) = k und Hash2 (k) = 1 die Liste der Zahlen ¨ 2, 3, 14, 12, 13, 26, 28, 15 ¨ in eine leere Hashtabelle der Lange 11 ein. Wieviele Vergleiche braucht man, um festzustellen, dass die Zahl 46 nicht in der Hashtabelle ist?

Double Hashing Fugen Sie mit den Hashfunkionen Hash1 (k) = k und Hash2 (k) = k mod 8 + 1 die Liste der Zahlen ¨ 2, 3, 14, 12, 13, 26, 28, 15 ¨ in eine leere Hashtabelle der Lange 11 ein. Wieviele Vergleiche braucht man, um festzustellen, dass die Zahl 46 nicht in der Hashtabelle vorkommt?

Bucket Hashing Fugen Sie mit der Hashfunkion Hash(k) = k die Zahlen 4, 3, 14, 12, 13, 26, 28, 15, 2, 20 ¨ ¨ ¨ leere Hashtabelle der Lange 8 ein mit Bucket Grosse 3 ein.

in eine

Overflow:

Java Hashtabelle Sie finden unter ∼amrhein/AlgoData/uebung5 eine vereinfachte Version der Klasse Hashtable der java.util Library. Finden Sie heraus, welche der verschiedenen im Skript vorgestellten Varianten in der Java Library benutzt werden.

6

Sortieren

¨ ¨ Sortierprogramme werden vorallem fur von Daten benotigt, wenn die Daten zum ¨ die die Prasentation ¨ ¨ Beispiel sortiert nach Zeit, Grosse, letzten Anderungen, Wert, ... dargestellt werden sollen. Wenn die Mengen nicht allzu gross sind (weniger als 500 Elemente), genugt ¨ oft ein einfach zu implemen¨ von O(n2 ), wobei tierender, langsamer Suchalgorithmus. Diese haben normalerweise eine Komplexitat der Aufwand bei fast sortierten Mengen geringer sein kann (Bubble Sort). Zum Sortieren von grossen Datenmengen lohnt es sich allerdings, einen O(n log2 (n)) Algorithmus zu implementieren. Noch mehr als bei Suchalgorithmen spielen bei Sortieralgorithmen die Datenstrukturen eine entscheidende Rolle. Wir nehmen an, dass die zu sortierende Menge entweder eine Arraystruktur (ein BasisTyp-Array wie float[n] oder eine ArrayList ) oder eine Listenstruktur (wie zum Beispiel LinkedList ) ¨ ist. Listen erlauben zwar keinen wahlfreien Zugriff, dafur Listenelemente durch Umketten (also ¨ konnen ohne Umkopieren) von einer unsortierten Folge F in eine sortierte Folge S uberf uhrt werden. Arrays ¨ ¨ ¨ erlauben wahlfreien Zugriff. Dafur und Loschen (Umsortieren) von Elementen viele ¨ sind beim Einfugen ¨ ¨ Kopier-Schritte notig. Speziell ist zu beachten, dass viele Sortier-Algorithmen auf Array-Strukturen zwar sehr schnell aber nicht stabil sind.

¨ der Vergleichsfunktion Definition: Ein Sortier-Algorithmus heisst stabil, falls Elemente, welche gemass gleich sind, ihre Originalreihenfolge behalten.

6-2

6

Sortieren

6.1 Selection Sort ¨ Beim Sortieren durch Auswahlen teilen wir die zu sortierende Menge F (scheinbar) in eine unsortierte Teilmenge U und eine sortierte Menge S. Aus U wird jeweils das kleinste Element gesucht und mit dem letzten Element von S vertauscht. Wegen dieser Vertausch-Aktionen ist dieser Algorithmus eher geeignet fur aber dazu, dass der Algorithmus nicht stabil ist. ¨ Arraystrukturen. Die Vertauschungen fuhren ¨

public void selectionSort(int l, int r) { int min_pos; for( int i=l; i 0) newList.insertSorted(list.removeFirst()); return newList; }

6.3 Divide-and-Conquer Sortieren Das abstrakte Divide-and-Conquer Prinzip (vgl. Kap. 1.6.2) lautet wie folgt: 1. Teile das Problem (divide) ¨ die Teilprobleme (conquer ) 2. Lose ¨ 3. Kombiniere die Teillosungen (join) ¨ Beim Sortieren gibt es zwei Auspragungen: Hard split / Easy join: Dabei wird die gesamte Arbeit beim Teilen des Problems verrichtet und die Kombination ist trivial, das heisst, F wird so in Teilfolgen F1 und F2 partitioniert, dass zum Schluss ¨ die sortierten Teilfolgen einfach aneinandergereiht werden konnen: S = S1 S2 . Dieses Prinzip fuhrt ¨ zum Quicksort-Algorithmus. Easy split / Hard join: Dabei ist die Aufteilung in Teilfolgen F = F1 F2 trivial und die Hauptarbeit liegt beim Zusammensetzen der sortierten Teilfolgen S1 und S2 zu S. Dieses Prinzip fuhrt zum Mergesort¨ Algorithmus.

6-6

6

Sortieren

6.4 Quicksort Einer der schnellsten und am meisten benutzten Sortieralgorithmen auf Arrays ist der Quicksort-Algo¨ rithmus (C.A.R. Hoare, 1960). Seine Hauptvorteile sind, dass er nur wenig zusatzlichen Speicherplatz ¨ braucht, und dass er im Durchschnitt nur O(n log2 (n)) Rechenschritte benotigt. ¨ 1 . Dann wird der zu sortierende Bereich F Beim Quicksort wird zuerst ein Pivot-Element q ausgewahlt so in zwei Teilbereiche Fklein und Fgross partitioniert, dass in Fklein alle Elemente kleiner als q und in Fgross alle ¨ Elemente grosser gleich q liegen. Danach wird rekursiv Fklein und Fgross sortiert. Am Ende werden die je sortierten Folgen wieder zusammengefugt. ¨ ¨ Die Prozedur partition sucht jeweils von links ein Element g, welches grosser und von rechts ein Element k, welches kleiner als der Pivot ist. Diese zwei Elemente g und k werden dann vertauscht. Dies wird so lange fortgesetzt, bis sich die Suche von links und von rechts zusammen trifft. Zuruckgegeben wird ¨ der Index der Schnittstelle. Anschliessend an partition wird der Pivot an der Schnittstelle eingesetzt (vertauscht mit dem dortigen Element). Um zu sehen, was beim Quicksort-Algorithmus passiert, betrachten wir das folgende Tracing: ¨ werden, optimalerweise ist q aber der Median aller zu sortierenden Werte. Da die Berechnung des Medians viel zu 1 q kann beliebig gewahlt ¨ ¨ ¨ man als Pivot oft einfach das erste Element. Eine bessere Wahl ist das Element an der Stelle length/2 im Array als aufwandig ware, wahlt Pivot; dies fuhrt bei beinahe sortierten Folgen zu einem optimalen Algorithmenverlauf. ¨

6.4 Quicksort

54

93

6-7

83

22

7

19

94

48

27

72

39

70

13

28

95

36 100

4

12

6-8

6

Sortieren

private int partition( int l, int r, E pivot ) { do{ // Move the bounds inward until they meet while( array.get(++l).compareTo(pivot) < 0 && l < r ); // Move left bound right while( array.get(--r).compareTo(pivot) > 0 && l < r); // Move right bound left if( l < r ) swap( l, r ); // Swap out-of-place values } while(l < r ); // Stop when they cross if( array.get(r).compareTo(pivot) < 0 ) return r+1; return r; // Return position for pivot } ¨ Quicksort teilt die zu sortierende Menge so lange auf, bis die Teile eine kritische Lange (z.B. 4 oder 8) unterschritten haben. Fur Listen wird der einfachere Selection-Sort Algorithmus benutzt, da ¨ kurzere ¨ dieser fur ¨ kurze Listen einen kleineren Overhead hat.

public void quickSort( int i, int j ) { int pivotindex = findPivot(i, j); swap(pivotindex, i);

// stick pivot at i

int k = partition(i, j+1, array.get(i)); // k is the position for the pivot swap(k, i); // put pivot in place if( (k-i) > LIMIT ) // sort left partition quickSort( i, k-1 ); else // with selection-sort selectionSort( i, k-1 ); // for short lists if( (j-k) > LIMIT ) quickSort( k+1, j ); // sort right partition else selectionSort( k+1, j ); }

6.5 Sortieren durch Mischen (Merge Sort)

6-9

¨ des Quicksort-Algorithmus ist im besten Fall O(n log2 (n). Im schlechtesten Fall (worst Die Komplexitat ¨ O(n2 ). case), wenn das Pivot-Element gerade ein Rand-Element ist, dann wird die Komplexitat ¨ des Quicksort Algorithmus. Es Das Finden eines gunstigen Pivot Elements ist zentral fur ¨ ¨ die Komplexitat ¨ ¨ gibt verschiedene Verfahren zum Losen diesese Problems wie, wahle als Pivot das erste Element des ¨ ¨ ¨ Arrays, wahle das Element in der Mitte des Arrays oder wahle drei zufallige Elemente aus dem Array und nimm daraus das mittlere. Eine Garantie fur ¨ eine gute Wahl des Pivots liefert aber keines dieses Verfahren. Ausserdem ist der Quicksort Algorithmus nicht stabil.

6.5 Sortieren durch Mischen (Merge Sort) Der Merge-Sort-Algorithmus ist einer der besten fur ¨ Datenmengen, die als Listen dargestellt sind. Die ¨ Rechenzeit ist in (best case und worst case) O(n log2 (n)). Ausserdem wird kein zusatzlicher Speicher ¨ benotigt und der Algorithmus ist stabil. Beim Merge-Sort wird die Folge F mit Hilfe einer Methode divideList() ¨ grosse Halften geteilt.

private MSList[] divideList(MSList list) { MSList result[] = (MSList[]) new MSList[2]; result[0] = new MSList(); int length = list.size(); for (int i = 0; i < length / 2; i++) result[0].addFirst(list.removeFirst()); result[1] = list; return result; }

¨ in zwei (moglichst) gleich

6-10

6

Sortieren

Die (im rekursiven Aufruf) sortierten Folgen werden dann in einem linearen Durchgang zur sortierten Endfolge zusammen gefugt ¨ (gemischt).

private MSList mergeSort(MSList list) { if (list.size() < LIMIT) return insertSort(list); // divide list into two sublists MSList[] parts = divideList(list); MSList leftList = parts[0]; MSList rightList = parts[1]; leftList = mergeSort(leftList); rightList = mergeSort(rightList);

// left recursion // right recurstion

return merge(leftList, rightList); } ¨ Das Mischen geschieht dadurch, dass jeweils das kleinere Kopfelement der beiden Listen ausgewahlt, ¨ und als nachstes ¨ herausgelost Element an die neue Liste angefugt ¨ wird.

4

7

3

3

13

4

8

28

7

12

8

15

6.5 Sortieren durch Mischen (Merge Sort)

6-11

private MSList merge(MSList left, MSList right) { MSList newList = new MSList(); while (left.size() > 0 && right.size() > 0) { if (left.getFirst().compareTo(right.getFirst()) < 0) newList.addLast(left.removeFirst()); else newList.addLast(right.removeFirst()); } for (int i = left.size(); i > 0; i--) newList.addLast(left.removeFirst()); for (int i = right.size(); i > 0; i--) newList.addLast(right.removeFirst()); return newList; }

divide

Beispiel: Wie Mergesort genau funktioniert, wollen wir anhand dieses Beispiel nachvollziehen.

merge

sort

3

2

1

6

3

2

1

6

3

2

1

6

3

4

8

2

2

3

1

6

3

4

2

8

2

3

1

6

3

4

2

8

3

4 3

2

1

4

3

2

3

4

2

8

6

2

1

2

8

2 8

3

6-12

6

Sortieren

¨ 6.6 Ubung 6 InsertSort, SelectionSort Erstellen Sie ein Tracing vom InsertSort-, bzw. vom SelectionSort-Algorithmus auf der Eingabe

F = {42, 3, 24, 17, 13, 5, 10} Insert Sort 42

3

24

17

Selection Sort 13

5

10

42

3

24

17

13

5

10

Mergesort, Quicksort Erstellen Sie je ein Tracing vom Mergesort-, bzw. vom Quicksort-Algorithmus (einmal mit dem PivotElement an der Stelle 1, dann an der Stelle (r+l)/2 , r der rechte, l der Linke Rand des Bereichs) auf der Eingabe

F = {42, 3, 24, 33, 13, 5, 7, 25, 28, 14, 46, 16, 49, 15} HeapSort ¨ Uberlegen Sie sich ein Verfahren welches eine gegebene Menge mit Hilfe eines Heaps sortiert. Welchen Aufwand hat dieses Verfahren im besten / im schlechtesten Fall? Selbststudium: BucketSort, RadixSort Lesen Sie die Unterlagen zum Selbststudium uber die zwei weiteren Suchalgorithmen: Bucket Sort ¨ und Radix Sort. Sortieren Sie die obige Menge F mit Hilfe von RadixSort mit 5 Buckets.

¨ 6.6 Ubung 6

6-13 Quick Sort 42

3

24

33

13

5

7

25

28

14

46

16

49

15

42

3

24

33

13

5

7

25

28

14

46

16

49

15

6-14

7

Pattern Matching

¨ Pattern Matching ist eine Technik, mit welcher ein String aus Text oder Binardaten nach einer Zeichenfolge durchsucht wird. Die gesuchten Zeichenfolgen werden dabei in Form eines Suchmusters (Pattern) angegeben. Solche Algorithmen werden in der Textverarbeitung (Suchen nach einem Zeichenstring in einer Datei) aber auch von Suchmaschinen auf dem Web verwendet. Das Hauptproblem beim Pattern-Matching ist: Wie kann entschieden werden, ob ein Text ein gegebenes Muster erfullt. ¨

¨ Ausdrucke 7.1 Beschreiben von Pattern, Regulare ¨ ¨ ¨ Ausdrucke) Zunachst brauchen wir eine Sprache, mit welcher wir die Pattern (hier regulare beschreiben ¨ ¨ ¨ konnen (um zu sagen, welche Art von Wortern oder Informationen wir suchen). Sei E ein Alphabet, d.h. eine endliche Menge von Zeichen. Ein Wort uber E ist eine endliche Folge von ¨ Zeichen aus E :

w = e1 e2 . . . en ,

ei ∈ E

Das leere Wort, das aus keinem Zeichen besteht, bezeichnen wir mit λ. ¨ Mit E ∗ bezeichnen wir die Menge aller Worter uber dem Alphabet E . ¨

7-2

7 Pattern Matching

Beispiel: Fur ¨ E = {A, B} ist E ∗ =

Die Pattern bauen wir mit Hilfe der folgenden drei Regeln zusammen: ¨ Definition: Ein (einfacher) regularer Ausdruck ist ein Wort (String), welches mit Hilfe der folgenden drei Operationen zusammengebaut wird: ¨ ¨ Konkatenation setzt zwei Worter zusammen. So wird aus den Wortern ’AB’ und ’BC’ das neue Wort ’ABBC’. ¨ ¨ Auswahl erlaubt uns, zwischen einem der Worter auszuwahlen. Das heisst AB|B ist entweder das Wort ’AB’ oder das Wort ’B’. Wir nennen solche Terme auch Or-Ausdrucke. ¨ ¨ Iteration repetiert das gegebene Wort beliebig oft (auch 0 mal). A(AB)∗ entspricht also den Wortern ’A’, ’AAB’, ’AABAB’, · · ·. ¨ Zu beachten ist in diesem Zusammenhang die Bindungsstarke der drei Operationen: Die Iteration (∗ ) ¨ ¨ bindet starker als die Konkatenation. Am schwachsten bindet der Oder-Strich (|). Beispiele

¨ Der Ausdruck (A|BC)∗ AB erzeugt die Worter:

¨ Der Ausdruck AB∗ C|A(BC)∗ erzeugt die Worter:

¨ ¨ Wir beschranken uns hier auf diese wenigen Moglichkeiten zum Erzeugen von Pattern, obwohl es naturlich noch viele weitere, sehr elegante Operationen gibt: Zum Beispiel mit dem Zeichen ’.’ fur ¨ ¨ einen ¨ beliebigen Buchstaben konnten lange Or-Ausdrucke viel kurzer dargestellt werden. AB(A|B| · · · |Z)∗ G ¨ ¨ ¨ entspricht dann AB( . )∗ G. Allerdings konnen wir damit keine neuen Wortmengen definieren. Darum begenugen wir uns vorerst mit den obigen Operationen. ¨ ¨ Um zu entscheiden, ob ein Wort einen regularen Ausdruck erfullt, ¨ setzen wir endliche Automaten ein.

7.2 Endliche Automaten

7-3

7.2 Endliche Automaten ¨ ¨ Endlichen Automaten begegnen wir im taglichen Leben in Form von Getrankeautomaten, Billetautomaten, Bancomaten ... . Allen ist gemeinsam, dass sie ein einfaches endliches Eingabealphabet und ein ¨ einfaches endliches Ausgabealphabet haben und jeweils eine endliche Menge von Zustanden annehmen ¨ konnen. Beispiel Ein Bancomat funktioniert wie folgt: Nachdem die EC-Karte eingeschoben wurde, muss der Benutzer die Geheimzahl (Pincode) eingeben. Falls der Pincode korrekt war, bekommt der Benutzer eine ¨ Auswahl prasentiert (Geld abheben, Kontostand abfragen, abbrechen). Je nach Verhalten des Benutzers gibt der Bancomat den gewunschten Geldbetrag aus und/oder gibt die EC-Karte wieder zuruck. ¨ ¨ Geldbetrag Geld ausgeben Auswahl Abbruch Karte ausgeben

Auswahl Geld

5 korrekter Pincode Auswahl Kontostand

3 Karte

1

einschieben

ok

2

4 falscher Pincode

6

Error

Karte ausgeben

¨ Definition: Ein endlicher (deterministischer) Automat besteht aus einer endlichen Menge von Zustan¨ den, einem Anfangs- (oder Start-) Zustand und einem oder mehreren Endzustanden (oder akzeptieren¨ den Zustanden).

• Eingabe: Ein Automat wird von aussen bedient, d.h. er wird mit Eingabedaten versorgt. Es gibt also eine endliche Menge E von Eingabezeichen, die der Automat lesen kann und die eine gewisse Aktion ¨ auslosen. Die Menge E heisst Eingabealphabet.

• Zustand: Ein deterministischer Automat befindet sich stets in einem bestimmten Zustand. Die endli¨ ¨ che Menge Z der moglichen Zustande heisst die Zustandsmenge.

7-4

7 Pattern Matching

• Zustandsubergang: ¨ Die Verarbeitung eines einzelnen Eingabezeichens kann durch eine Nachfolgefunktion, ein Zustandsdiagramm oder durch eine Zustandstafel beschrieben werden. Unter der Einwirkung der Eingabe kann er von einem Zustand in einen andern ubergehen. ¨

• Ausgabe: Im Laufe seiner Arbeit kann der Automat eine Ausgabe produzieren, d.h. er kann Ausgabedaten ausgeben. Die endliche Menge A der produzierten Ausgabezeichen heisst Ausgabealphabet.

Beispiel: Fur ¨ den Bancomat setzen wir Eingabe:

E = {EC-Karte, Pincode (korrekt/falsch), Auswahl (Geld, Kontostand ...), Geldbetrag, Ok }

¨ Zustande:

Z = {1 (Startzustand), 2 (warten auf Pincode), 3 (warten auf Auswahl), 4 (Kontostand anzeigen), 5 (warten auf Betrag) }

Ausgabe:

A = {EC-Karte, Kontostand, Geld}.

und definieren die Funktionsweise durch die folgende Zustandstafel: Zustand Eingabe

1

2

3

4

5

6 (Error)

Karte (E) korrekter (K) Pincode falscher Pincode (F) Auswahl (G) Geld Auswahl Kontostand (S) Auswahl Abbruch (A) Geldbetrag (B) Ok (O)

¨ ¨ Mit Hilfe von endlichen Automaten konnen wir entscheiden, ob ein gegebenes Wort einem regularen Ausdruck entspricht, also ob ein gefundenes Wort in unser Schema (das vorgegebene Pattern) passt.

7.2 Endliche Automaten

7-5

¨ Beispiel: Worter, welche vom obigen Automaten (Bancomaten) akzeptiert werden, sind:

¨ ¨ Worter, welche nicht zum obigen Automaten gehoren (nicht akzeptiert werden) sind:

Ein endlicher Automat heisst deterministisch, falls jede Eingabe des Eingabealphabetes in jedem Zustand erlaubt ist und zu einem eindeutigen Nachfolgezustand fuhrt. Ein nichtdeterministischer endlicher ¨ ¨ Automat kann fur haben. ¨ jeden Zustand und jede Eingabe null, einen oder mehrere Nachfolgezustande Der Automat des vorigen Beispiels ist ein deterministischer endlicher Automat, da alle Eingaben nur ¨ in einen eindeutigen Nachfolgezustand fuhren. Nichtdeterministische Automaten konnen aber immer in ¨ ¨ deterministische Automaten ubergef uhrt werden, indem neue mehrdeutige Zustande eingefuhrt werden. ¨ ¨ ¨

Beispiele Das folgende ist ein deterministischer, endlicher Automat: in jedem Zustand fuhrt jede Eingabe ¨ zu einem eindeutigen Nachfolgezustand: A A

A

1 4 0

B B

A

C

2

A B

C

C

B 3

Zustand

B

C

C

Eingabe A B C

0

1

2

3

4

7-6

7 Pattern Matching

Das folgende ist ein nichtdeterministischer, endlicher Automat: Manche Eingaben fuhren in gewissen ¨ ¨ Zustanden zu keinem oder zu mehr als einem Nachfolgezustand: A A

1

C

C

Zustand 4

0

A

Eingabe

A

B

2

1

2

3

4

A

B

B

0

C

A

B

3

C

C

¨ ¨ Ein weiteres Konstrukt in nichtdeterministischen Automaten sind sogenannte leere Uberg ange, das ¨ ¨ ¨ ¨ ¨ ¨ heisst Uberg ange ohne gelesenes Zeichen. Solche Uberg ange heissen auch epsilon-Uberg ange und werden mit ε bezeichnet. A A

1

C

C

Zustand 4

0

Eingabe

B

C

B

A

A

ε

ε

2

C

B

3

B C

0

1

2

3

4

¨ 7.3 Automaten zu regularen Ausdrucken ¨

7-7

¨ 7.3 Automaten zu regularen Ausdrucken ¨ ¨ ¨ ¨ Wir suchen nun einen zu einem regularen Ausdruck aquivalenten Automaten. Ausgehend vom regularen ¨ Ausdruck konnen wir mit den folgenden Regeln den entsprechenden nichtdeterministischen endlichen Automaten herleiten. Durch das Symbol bezeichnet.

wird der Startzustand, durch

der akzeptierende Zustand des Automaten

Konkatenation: E1 E2 E1 :

1

2

E2 :

3

4

Beispiel: B A B

C A

A

.

7-8

7 Pattern Matching

Auswahl: E1 | E2 E1 :

1

2

E2 :

3

4

.

Beispiel: B A B

C A

A

Iteration: E∗ E:

1

2

Beispiel: A

B C

.

¨ 7.3 Automaten zu regularen Ausdrucken ¨

7-9

¨ Im allgemeinen kann man sehr viel kleinere Automaten konstruieren, welche den gleichen regularen Ausdruck beschreiben. Es gibt auch einen Algorithmus, welcher aus einem nichtdeterministischen Auto¨ ¨ maten einen deterministischen Automaten (ohne leere Uberg ange) erzeugt. Diesen Algorithmus wollen wir hier aber nicht behandeln. Beispiel 1: Ein endlicher Automat fur ¨ den Ausdruck (AB|C)∗ (A|B)

¨ Beispiel 2: Ein Automat fur Ausdruck (AB|BC)∗ (C|BC) ¨ den regularen

7-10

7 Pattern Matching

¨ 7.4 Ubung 7 ¨ 1. Finden Sie jeweils einen endlichen Automaten und/oder einen regularen Ausdruck, welcher die fol¨ gende Menge von Wortern uber dem Alphabet {A, B,C} beschreibt: ¨ ¨ - Alle Worter, welche mit A anfangen und mit C enden. -

¨ Alle Worter, welche eine gerade Anzahl A enthalten und mit C enden.

-

¨ Alle Worter, deren Anzahl Buchstaben durch 3 teilbar sind.

-

¨ Alle Worter, welche eine gerade Anzahl B und eine gerade Anzahl C enthalten.

¨ 2. Erzeugen Sie je einen nichtdeterministischen Automaten fur Ausdrucke: ¨ die regularen ¨ - (A*BC)*|BB -

((AB|B)(BA|CB))*

- (BB|CAB*|AB)* Sie mussen dabei die angegebenen Regeln zur Erzeugung des Automaten nicht strikt befolgen – Sie ¨ ¨ ¨ konnen auch versuchen, einen Automaten mit weniger Zustanden zu finden.

3. Erzeugen Sie eine Zustandsubergangs-Tabelle fur ¨ ¨ den folgenden Automaten.

ε

ε

0

C

B

ε

1

A

A

2

B

4

B 3

A

¨ 7.4 Ubung 7

7-11

¨ 4. Lesen Sie die Worter AACBC, ABACA und ACACB mit Hilfe der von Ihnen erzeugten Zustandsuber¨ gangstabelle, indem Sie Schritt fur ¨ Schritt den aktuellen Zustand notieren.

5. Holen Sie sich das Java Programm unter ∼amrhein/AlgoData/uebung7 und beantworten Sie die folgenden Fragen: ¨ - Erklaren Sie die Methoden find , group , start , end , split und replaceAll des java.regex Package. -

¨ ¨ Erganzen Sie das gegebene Programm: Geben Sie vom Input String alle Worter aus, welche weniger als 5 Buchstaben haben.

7-12

7 Pattern Matching

¨ Die wichtigsten regularen Ausdrucke ¨ Bedeutung

\ . x ˆx [x] () | {x} {x,} {x,y} ? * + ˆ $

Escape, um Instanzen von Zeichen zu finden, welche als Metazeichen benutzt werden (wie Punkt, Klammer, ... ) Ein beliebiges Zeichen (ausser newline) Eine Instanz von x Jedes Zeichen ausser x Alle Zeichen in diesem Bereich (z. B. [abuv] die Buchstaben a, b, u oder v, [a-z] alle Kleinbuchstaben) runde Klammern dienen fur ¨ die Gruppierung der OR Operator (Auswahl) (a|A) Der Ausdruck muss genau x mal vorkommen Der Ausdruck muss mindestens x mal vorkommen ¨ Der Ausdruck kommt mindestens x mal und hochstens y mal vor Abkurzung fur ¨ ¨ {0, 1} Abkurzung fur ¨ ¨ {0, } Abkurzung fur ¨ ¨ {1, } Start einer neuen Zeile Ende der Zeile

Beispiele

[a-zA-Z]* Beliebig viele Zeichen aus Buchstaben (z. B. ugHrB). [A-Z0-9]{8} Acht Zeichen aus A bis Z und 0 bis 9, (z. B. RX6Z45UB). [A-Z]([a-z])+ Ein Grossbuchstabe gefolgt von mindestens einem Kleinbuchstaben (z.Bsp Stu). ([0-9]-){2}[0-9]} Drei Zahlen, durch Striche getrennt (z. B. 2-1-8). [A-G]{2,} Mindestens zwei Grossbuchstaben aus A bis G (z. B. BGA). [bRxv]{3} Drei Buchstaben aus b, R, x und v (z. B. xxv).

8

Top Down Parser

¨ ¨ Hohere Programmiersprachen wie Java oder C++ konnen von einem Prozessor nicht direkt verarbeitet ¨ werden. Die in diesen Sprachen geschriebenen Programme mussen zuerst in eine fur ¨ ¨ den gewahlten Prozessor geeignete Maschinensprache ubersetzt werden. Programme, die diese Aufgabe ubernehmen, ¨ ¨ ¨ nennt man Compiler. Ein Compiler hat im Prinzip zwei Aufgaben zu losen: 1. Erkennen der legalen Programme der gegebenen Sprache. Das heisst, der Compiler muss testen, ob die Syntax des Programms korrekt ist oder nicht. Diese Operation nennt man parsing und das ¨ einen Parser. Programm, das diese Aufgabe lost, 2. Generieren von Code fur ¨ die Zielmaschine. Auch wenn wir keinen Compiler schreiben wollen, so kommt es doch oft vor, dass wir komplizierte Benutzereingaben wie zum Beispiel -

Arithmetische Ausdrucke: 2(a − 3) + b − 21 ¨

-

Polynome: a + 3x2 − 4x + 16

-

Boole’sche Ausdrucke a ⊙ (b ⊙ c) ⊕ b ¨

-

eine Befehlssprache

-

ein Datenubermittlungsprotokoll ¨

-

...

erkennen und verarbeiten mussen. ¨

8-2

8

Top Down Parser

8.1 Kontextfreie Grammatik Mit Hilfe einer Grammatik beschreiben wir den Aufbau oder die Struktur (-Regeln) einer Sprache. Kontextfreie Grammatiken1 dienen als Notation zur Spezifikation einer Sprachsyntax.

Beispiel: Ein einfacher deutscher Satz kann zum Beispiel eine der folgenden Strukturen annehmen: S ’ist’ A S ’ist’ A ’und ’ A S ’ist’ A ’oder ’ A wobei S normalerweise die Form Artikel Nomen ¨ ’gross’, ’schnell’ oder ’lang’. hat und A ersetzt werden kann durch ein Adjektiv wie ’schon’, Eine kontextfreie Grammatik (bzw. ein Syntaxdiagramm) fur ¨ dieses Konstrukt sieht dann etwa wie folgt aus: Grammatik (EBNF: Extended Backus-Naur Form) Satz S Artikel Artikel Nomen Nomen

::= ::= ::= ::= ... ::= ::=

S ’ist’ A

A A Adjektiv Adjektiv

::= ::= ::= ::= ::= ...

Satz

S

S

Artikel

A

ist

Artikel Nomen ’der’

Nomen

’ein’ ’Baum’

Artikel

der ein

’Hund’

... A

Syntax-Diagramm

A

Adjektiv

Adjektiv Adjektiv ’und’ A

und

Adjektiv ’oder’ A

oder

¨ ’schon’ ’gross’

Adjektiv

A

...

¨ Chomsky-Hierarchie vier Typen von Grammatiken: Typ 3: lineare Grammatik (entpricht endlichem Automat), Typ 2: kontext1 Es gibt gemass freie Grammatik (nur Nichtterminale auf der linken Seite), Typ 1: kontextsensitive Grammatik (nichtverkurzend), Typ 0: allgemeine Grammatik ¨ ¨ (ohne Einschrankung).

8.1 Kontextfreie Grammatik

8-3

Eine solche Ersetzungs-Regel heisst eine Produktion. Ein Satz, welcher dieser Grammatik entspricht, ist:

Ein Satz, welcher nicht dieser Grammatik entspricht, ist:

¨ Eine kontextfreie Grammatik beschreibt eine Sprache (Menge von Wortern oder Strings).

¨ Definition: Zu einer kontextfreien Grammatik gehoren vier Komponenten. 1. Eine Menge von Terminalen, d.h. von Symbolen, die am Ende einer Herleitung stehen (hier fettgedruckte Zeichenketten wie ’ist’, ’und’ oder ’oder’). 2. Eine Menge von Nichtterminalen, d.h. von Namen oder Symbolen, die weiter ersetzt werden (hier kursiv gedruckte Zeichenketten). 3. Eine Menge von Produktionen (l ::= r). Jede Produktion besteht aus einem Nichtterminal, das die linke Seite der Produktion bildet, einem Pfeil und einer Folge von Terminalen und/oder Nichtterminalen, die die rechte Seite der Produktion darstellen. 4. Ein ausgezeichnetes Nichtterminal dient als Startsymbol. Das Startsymbol ist immer das Nichtterminal auf der linken Seite der ersten Produktion.

Als vereinfachte Schreibweise fassen wir alle rechten Seiten der Produktionen zusammen, welche das gleiche Nichtterminal als linke Seite haben, wobei die einzelnen Alternativen durch ‘|’ (zu lesen als “oder”) getrennt werden. Ziffer Ziffer .. .

::= ’0’ ::= ’1’

Ziffer ::= ’9’ kann also auch als Ziffer ::= ’0’ | ’1’ | ’2’ | ’3’ | ’4’ | ’5’ | ’6’ | ’7’ | ’8’ | ’9’ geschrieben werden.

8-4

8

Top Down Parser

Definition: Die Herleitung eines Wortes aus einer Grammatik geschieht wie folgt: Wir beginnen mit dem Startsymbol und ersetzen dann jeweils in dem hergeleiteten Wort ein Nichtterminal durch die rechte Seite einer Produktion. Dies wird so lange wiederholt, bis im Ausdruck keine Nichtterminale mehr vorkommen. ¨ Alle Worter, die aus dem Startsymbol herleitbar sind, bilden zusammen die von der Grammatik definierte Sprache.

Beispiel:

Die Grammatik G1 besteht aus den Produktionen:

G1 :

S ::= ′ A′ | ′ A′ S | b b ::= ′ B′ | ′ B′ b

¨ G1 erzeugt die folgende Menge von Wortern:

G2 besteht aus den Produktionen: G2 :

S ::= ′ A′ | ′ B′ | ′ A′ b | ′ B′ a a ::= ′ A′ | ′ A′ b b ::= ′ B′ | ′ B′ a

¨ G2 erzeugt die Worter:

8.1 Kontextfreie Grammatik

8-5

Beispiel Die Menge der arithmetischen Ausdrucke wird durch die folgende Grammatik erzeugt: ¨ expression term factor number digit

::= term { ′ + ′ term | ′ − ′ term } ::= factor { ′ ∗ ′ factor } ::=

′ ′

( expression ′ )′ | number

::= digit { digit } ::=

′ ′

0 | ′ 1′ | ′ 2′ | ′ 3′ | ′ 4′ | ′ 5′ | ′ 6′ | ′ 7′ | ′ 8′ | ′ 9′

Geschweifte Klammern bedeuten in der EBNF-Schreibweise null oder beliebig viele Wiederholungen. Eine Expression ist also ein Term oder eine Summe von Termen. Ein Term ist ein Faktor oder ein Produkt von Faktoren. Ein Faktor ist ein Klammer-Ausdruck oder eine Zahl. Eine Zahl besteht aus einer oder mehreren Ziffern. + expression

term

* term

factor



Um zu uberpr ufen, ob ein Ausdruck ein korrekter arithmetischer Ausdruck ist, beschreiben wir den Her¨ ¨ leitungsprozess mit Hilfe eines Herleitungsbaumes (Parsetree): Der Parsetree von 12 ∗ 4 − 3 ∗ (2 + 14) + 21

8-6

8

Top Down Parser

Beispiel: Suchmaschinen erlauben ublicherweise die komplexe Suche nach Mustern wie ¨

⊕ (schule

software

⊙ schweiz)

Dafur uber Zei¨ braucht man eine Sprache, mit welcher der Benutzer diese (boole’schen) Ausdrucke ¨ ¨ chenfolgen eingeben kann. Die Menge der boole’schen Ausdrucke kann zum Beispiel wie folgt erzeugt ¨ werden: bexpr

::= bterm [ ′ ⊕ ′ bexpr ]

bterm

::= bfac [ ′ ⊙ ′ bterm ]

bfac

::= [ ′ − ′ ] ′ (′ bexpr ′ )′ | [ ′ − ′ ] string

string

::= letter { letter }

letter

::=

′ ′

a | ′ b′ | . . . | ′ z′

Eckige Klammern bezeichnet in der EBNF Schreibweise ein optionales Vorkommen (null oder einmal). .. bexpr .... bterm ............. .... ... .... .... ..... ... . .  . . .... ...... +g . ................

bexpr Wir zeichnen den Parsetree des boole’schen Ausdrucks

( a ⊙ b ) ⊙

ac ⊕ abc

8.2 Top-Down Parser

8-7

8.2 Top-Down Parser ¨ Aus einer solchen Grammatik lasst sich nun relativ leicht ein (rekursiver) Top-Down Parser herleiten: Auf¨ passen mussen wir dabei nur, dass wir keine nichtterminierenden Zyklen einbauen. Vermeiden konnen ¨ ¨ ein oder zwei Zeichen vorauslesen. Dies kann bei Klammerausdrucken wir das, indem wir falls notig oder ¨ ¨ sein, bzw. immer dann, wenn es mehrere Moglichkeiten ¨ Operationszeichen notig gibt.

8.2.1 Parser fur ¨ arithmetische Ausdrucke ¨ Zum Implementieren des Parsers fur schreiben wir eine Klasse Arithmetic¨ arithmetische Ausdrucke ¨ ExpressionParser . Darin definieren wir die Member-Variablen parseString fur ¨ den zu parsenden ¨ String, position fur des zu parsenden Strings. ¨ die aktuelle Lese-Position und length die Lange

public class ArithmeticExpressionParser { private String parseString; private int position; private int length; public void parse(String aExpr) throws ParseException { . . . } // one method per grammar line . . . } Die Methode parse() ruft die Startfunktion expression() String gelesen wurde.

auf und testet am Schluss, ob der ganze

public void parse(String aExpr) throws ParseException { position = 0; parseString = aExpr; length = parseString.length(); expression(); if (position < length) throw new ParseException(position, "parse"); }

8-8

8

Top Down Parser

¨ expression() fangt (laut Grammatik) immer mit einem Term an, also rufen wir zuerst die Prozedur term() auf. Falls ein ’+ ’ oder ein ’- ’ im Ausdruck folgt, haben wir weitere Terme und es ist ein Loop ¨ notig.

private void expression() throws ParseException { term(); while (position < length) { if (getNext() == ’+’ || getNext() == ’-’) { position++; term(); } } } ¨ getNext() liest das folgende Zeichen (ohne die Position zu verandern).

private char getNext() { return parseString.charAt(position); } term()

¨ fangt (laut Grammatik) mit einem Faktor an. Falls darauf ein ’* ’ folgt, ist ein rekursiver Aufruf

¨ notig.

private void term() throws ParseException { factor(); while (position < length) { if (getNext() == ’*’) { position++; factor(); } } }

8.2 Top-Down Parser

8-9

¨ ¨ In factor() ist nun eine Abfrage notig, um sicherzustellen, dass jede geoffnete Klammer wieder geschlossen wurde. Falls keine Klammer gelesen wurde, muss an dieser Stelle eine oder mehrere Ziffern stehen.

private void factor() throws ParseException { if (position < length && getNext() == ’(’) { position++; expression(); if (position < length && getNext() == ’)’) { position++; } else { throw new ParseException(position, "factor, ’)’ expected"); } } else { number(); } } ¨ number() muss mindestens eine Ziffer lesen konnen, andernfalls ist an dieser Stelle im Ausdruck ein Fehler.

private void number() throws ParseException { int n = position; while (position < length && isNumber(getNext())) { position++; } if (n == position) { throw new ParseException(position, "number, digit expected"); } }

8-10

8

Top Down Parser

8.2.2 Parser fur ¨ boole’sche Ausdrucke ¨ ¨ Als nachstes bauen wir einen Parser fur ¨ die von der Grammatik von Seite 8-6 erzeugten boole’schen Ausdrucke: ¨

bexpr

::= bterm [ ′ ⊕ ′ bexpr ]

bterm

::= bfac [ ′ ⊙ ′ bterm ]

bfac

::= [ ′ − ′ ] ′ (′ bexpr ′ )′ | [ ′ − ′ ] string

string

::= letter { letter }

letter

::=

′ ′

a | ′ b′ | . . . | ′ z ′

Da wir auf der normalen Tastatur die Zeichen ein + fur ¨ ′ ⊕ ′ und ein ∗ fur ¨ ′ ⊙′ .



⊙ ′ und



⊕ ′ nicht haben, schreiben wir im Programm

¨ Die Methode parse() konnen wir (fast) aus dem letzten Abschnitt kopieren.

8.2 Top-Down Parser

public void parse(String aExpr) throws ParseException { position = 0; parseString = aExpr; length = parseString.length(); booleanExpression(); if (position < length) throw new ParseException(position, "parse"); } Ein boole’scher Ausdruck ist entweder ein einfacher Term oder ein OR-Ausdruck (bterm + bexpr)2 .

private void booleanExpression( ) throws ParseException {

2 vgl. ∼ amrhein/AlgoData/Parser

8-11

8-12

8

Top Down Parser

Ein bterm ist ein AND-Term, also ein bfac oder ein bfac multipliziert mit einem bterm.

private void booleanTerm( ) throws ParseException {

Ein booleanFactor ist ein String, ein negierter String, ein boole’scher Ausdruck oder ein negierter boole’scher Ausdruck.

private void booleanFactor(

) throws ParseException {

8.2 Top-Down Parser

booleanName liest so lange als Buchstaben im Ausdruck erscheinen.

void booleanName() throws ParseException { int n = position; while (position < length && isAlpha(parseString.charAt(position))) position++; if (n == position) throw new ParseException(position, "booleanVariable: letter expected"); }

8-13

8-14

8

Top Down Parser

¨ 8.3 Ubung 8 Syntax-Diagramm Gegeben ist das folgende Syntaxdiagramm: S

T

T

(

+

S

-

S

F

*

T

)

/ F F

a b c

1. Schreiben Sie es in eine Grammatik um. 2. Welche der folgenden Terme sind durch diese Grammatik erzeugt worden? (a) a*(b-c) (c) a+(a*b)+c (e) (a+b)/(a-b) (b) a/(b*c) (d) a+b+(c*b) (f) ((a*b)/(b*c)) Parser Gegeben sei die folgende Grammatik:

r ::= s ::= t ::=

′ ′ ′

L +′ s | s N ′ | ′ N ′ ′ (′ t ′ )′ ′ ′ ′ ′ M + r | r



1. Welche der folgenden Ausdrucke sind von dieser Grammatik erzeugt worden? ¨ (a) L + N

(d) L + N (N + M)

(b) N (M + N)

(e) N (L + N (M)))

(c) L + N (L + N) (f) N (N (L + M)) 2. Schreiben Sie einen Parser fur ¨ diese Grammatik. Grammatiken 1. Geben Sie eine Grammatik fur dem Alphabet { A,B } an, welche ¨ die Menge aller Strings uber ¨ ¨ hochstens zwei gleiche Buchstaben in Folge haben. 2. Erweitern Sie die Grammatik fur so, dass auch Exponentiation (∧ ) und ¨ arithmetische Ausdrucke ¨ Division (/) als Operationen erlaubt sind.

9

Kryptologie

Mit der zunehmenden Vernetzung, insbesondere seit das Internet immer mehr Verbreitung findet, sind Methoden zum Verschlusseln von Daten immer wichtiger geworden. Kryptologie fand ihren Anfang vor al¨ ¨ lem in militarischen Anwendungen. Seit der Erfindung des elektronischen Geldes findet die Kryptologie1 aber immer mehr Anwendungen im kommerziellen Bereich. Die wichtigsten Stichworte sind hier: Ge¨ Integriat ¨ ¨ (Falschungssicherheit). heimhaltung, Authentifizierung(Nachweisen der eigenen Identitat), Um zu wissen, wie sicher eine Verschlusselungsmethode ist, mussen wir aber auch die andere Seite ¨ ¨ ¨ kennen. Wahrend sich die Kryptographie (Lehre des Verschlusselns) mit den verschiedenen Verschlusse¨ ¨ ¨ lungs Methoden beschaftigt, lehrt die Kryptoanalyse, wie man Codes knackt. Nur wenn wir die Methoden ¨ der Code-Knacker kennen, konnen wir beurteilen, ob eine Verschlusselungsmethode fur ¨ ¨ unser Ansinnen brauchbar (d.h. genugend sicher) ist. ¨ Kryptologie = Kryptographie + Kryptoanalyse In der Regel gilt, je kritischer die Daten sind, desto sicherer muss die Verschlusselung sein, und desto ¨ ¨ ¨ aufwandiger ist das Verschlusselungsverfahren. Je schwieriger namlich ein Code zu knacken ist, desto ¨ ¨ teurer ist eine Attacke fur Attacke versuchen, ¨ den Kryptoanalytiker. Er wird also nur dann eine aufwandige wenn er sich einen entsprechenden Gewinn erhoffen kann. Prinzipiell gilt:

• es gibt keine einfachen Verfahren, die trotzdem einigermassen sicher sind. • (fast) jeder Code ist knackbar, falls genugend Zeit und genugend verschlusselter Text vorhanden ist. ¨ ¨ ¨ 1 Weitere Informationen zu Kryptographie findet man zum Beispiel unter home.nordwest.net/hgm/krypto

.

9-2

9 Kryptologie

9.1 Grundlagen ¨ Ein klassisches Kryptosystem besteht aus einem Sender, einem Empfanger und einer Datenleitung, an ¨ welcher ein Horcher2 zuhort. Horcher

Klartext Sender

unsichere Datenleitung

Schlüssel

Empfänger

Klartext

Schlüssel

sichere Übertragung Der Sender verschlusselt eine Meldung (den Klartext) und sendet diesen uber die unsichere Datenlei¨ ¨ ¨ tung dem Empfanger. Das Verschlusseln geschiet entweder Zeichenweise (Stromchiffre) oder der Text ¨ ¨ ¨ ¨ wird erst in Blocke aufgeteilt und dann werden die Blocke verschlusselt (Blockchiffre). Der Emfpanger ¨ entschlusselt dann die Meldung wieder mit der Umkehrfunktion. Der verwendete Schlussel muss vorher ¨ ¨ auf sicherem Weg (zum Beispiel per Kurier) ubermittelt werden. ¨ ¨ ¨ Diese Situation ist heute Normalfall nicht (mehr) gewahrleistet. So mochte der Kunde im WWW nicht vorher per Post mit jedem Anbieter geheime Schlussel austauschen. Trotzdem will er sicher sein, dass kein ¨ ¨ Horcher das Bankpasswort, die Bestell/Transaktionsdaten oder die Kreditkartennummer erfahrt. Ausser¨ dem ist das Verwalten von vielen Schlusseln sehr aufwandig (und unsicher). ¨ ¨ ¨ Das Ziel ist also, ein moglichst effizientes und doch sicheres System zu haben, welches mit moglichst wenig Verwaltungsaufwand auskommt.

¨ 2 Als Sender/Empfanger mussen wir davon ausgehen, dass der Horcher weiss, welches Verschlusselungsverfahren angewandt wurde. ¨ ¨

9.2 Einfache Verschlusselungmethoden ¨

9-3

9.2 Einfache Verschlusselungmethoden ¨ ¨ Eine der einfachsten und altesten Methoden zur Verschlusselung von Texten wird Kaiser Caesar zuge¨ schrieben. Die Methode heisst darum auch die Caesar Chiffre. Dabei wird jeder Buchstabe des Textes durch den um k Buchstaben verschobenen Buchstaben ersetzt (Stromchiffre). Beispiel Klartext:

A

B

C

D

E

F

G

H

I

Verschlusselt: ¨

D

E

F

G

H

I

J

K

L

··· ···

W

X

Z

Y

Z

A

B

C

Der Klartext geheimer Text wird damit zu Klartext:

G

E

H

Verschlusselt: ¨

J

H

K

E

I

M

E

R

T

E

X

T

Der Schlussel ist k = 3, da alle Buchstaben um drei verschoben werden. ¨ ¨ ¨ Dieser Code lasst sich sehr leicht knacken, auch wenn wir den Schlussel nicht kennen. Nach hochstens ¨ 27 Versuchen haben wir den Code entziffert. Verschlusselt: ¨

X

Y

W

J

S

L

E

L

J

M

J

N

R

Klartext: Eine etwas verbesserte Methode ist, ein Schlusselwort als Additionstabelle zu benutzen (Blockchiffre). ¨ ¨ ` Chiffre. Dabei andert Diese Verschlusselungsart ist bekannt als Vigenere sich die Anzahl zu verschie¨ benden Buchstaben jeweils in einem Zyklus, welcher gleich lang wie das Schlusselwort ist. ¨ Beispiel Wir benutzen das Wort geheim als Schlussel. Die Verschlusselung geschieht dann nach fol¨ ¨ gendem Schema: Klartext:

D

I

E

S

E

R

Schlussel ¨

G

E

H

E

I

M

Verschlusselt: ¨

G

T

E

X

T

E

H

E

I

I M

S

T

···

9-4

9 Kryptologie

¨ ¨ Eine weitere Moglichkeit besteht darin, alle Buchstaben zufallig zu permutieren. Dies fuhrt zu einer Per¨ mutationschiffre. Der Schlussel ist dann die auf den Buchstaben benutzte Permutationsfunktion (bzw. ¨ die Umkehrung davon). Klartext:

A

B

C

D

E

F

G

H

I

J

K

Verschlusselt: ¨

S

H

X

A

K

D

G

R

.

.

.

L

···

¨ Eine leicht verbesserte Version von Permutationschiffren benutzt Permutationen von Blocken, zum Bei¨ spiel von Zweierblocken: Klartext:

AA

AB

AC

AD

AE

AF

AG

AH

AI

AJ

Verschlusselt: ¨

RS

HI

WX

CD

JK

RE

HG

UV

.

.

···

Damit mussten im Prinzip 27! (bzw. 272 !) Variationen getestet werden, um den Text zu entziffern. Leider ¨ ist die Kryptoanalyse auch fur einfach, sofern ein normaler (deut¨ diese zwei Verschlusselungsmethoden ¨ ¨ ¨ scher) Text ubermittelt wird. Um solche Texte zu knacken, arbeitet man mit Haufigkeitsanalysen. So lasst ¨ sich zum Beispiel die Verschlusselung des Buchstabens E schnell erraten, da dies mit grossem Abstand ¨ ¨ der haufigste Buchstabe ist. E

-

N

R

I

S

T

A

D

H

U

L

15.36

15.15

8.84

6.86

6.36

5.39

4.73

4.58

4.39

4.36

3.48

2.93

...

¨ Am zweithaufigsten ist das Leerzeichen, dann der Buchstabe N usw.. Da Sprache im allgemeinen stark redundant ist, kann man oft mit Hilfe von ein paar wenigen erkannten Buchstaben bereits den ganzen Text entziffern. ¨ ¨ Auch fur kurzer Lange) funktioniert die gleiche Attacke. Fur ¨ Blockpermutationen (mit Blocken ¨ diese ¨ ¨ ¨ braucht man eine Haufigkeitsanalyse der Zweier- (Dreier-)Blocke einer Sprache. Der haufigste Zweierblock in der deutschen Sprache ist die Silbe en. Der Text muss allerdings genugend lang sein, d.h. ¨ ¨ ¨ genugend viele Blocke aufweisen, um eine aussagekraftige Statistik zu erhalten. ¨ ¨ ¨ ` Blockchiffren angewendet werden (sepaSolche Haufigkeitsanalysen konnen genau gleich auf Vigenere ¨ rat auf jeden Buchstaben des Schlusselwortes), falls die Texte viel langer sind als das Schlusselwort. ¨ ¨

9.3 Vernamchiffre, One Time Pad

9-5

9.3 Vernamchiffre, One Time Pad ` Chiffre das Schlusselwort ¨ Falls in der Viginere aus einer zufalligen Buchstabenfolge besteht, die ebenso ¨ lang ist wie der verschlusselte Text, und falls jeder Schlussel nur einmal verwendet wird, ist das Ver¨ ¨ schlusselungs-Verfahren absolut sicher. Man nennt dieses Verfahren die Vernam-Chiffre oder das One ¨ Time Pad. ¨ Die Vernam-Chiffre ist allerdings sehr umstandlich, da fur Zeichen zuvor ein Zei¨ jedes zu ubermittelnde ¨ chen uber einen sicheren Weg transportiert werden muss. Dennoch wird sie benutzt, wenn absolute, ¨ ¨ ist. beweisbare Sicherheit notig ¨ Statt einer zufalligen Buchstabenfolge (welche auf einem sicheren Weg ubermittelt werden muss), wird ¨ ¨ als Schlussel haufig eine Zufalls-Zahlenreihe benutzt. Diese wird mit Hilfe einer geheimen, vorher aus¨ ¨ gemachten Zufallsfunktion generiert. Solange der Horcher diese Funktion nicht durch Sabotage erfahrt, ist dieses Verfahren sehr effizient und ebenfalls sicher. Oft werden sogar mehrere Zufalls-Funktionen kombiniert oder abwechlungsweise benutzt. Beispiel: Eine Funktion, welche eine gute “Zufallsfolge” herstellt, ist zum Beispiel die Funktion f (n) = ⌊100(sin(n) + 1)⌋. Die erzeugte Zahlenreihe ist:

n

:

1

2

3

4

5

6

7

8

9

10

11

12

...

f (n)

:

184

190

114

24

4

72

165

198

141

45

0

46

...

f (n) mod 27 :

22

1

6

24

4

18

3

9

6

18

0

19

...

Mit Hilfe von solchen Funktionen werden sogennante Verschlusselungs/Entschl usselungs-Maschinen ge¨ ¨ ¨ ¨ baut, zum Beispiel fur von grossen Datenmengen notig ¨ Telefone, die typischerweise die Ubertragung ¨ erzeugen jeweils die gleiche Zufallsfolge zum (binaren) ¨ machen. Beide Gerate Ver- bzw. Entschlusseln ¨ der geheimen Texte. Die im Telefon eingebauten Schlusselerzeuger sind dann im Prinzip nichts anderes ¨ als gute und effiziente Zufallszahlen-Generatoren.

9-6

9 Kryptologie

9.4 Moderne symmetrische Verfahren Die meisten der heute eingesetzten symmetrischen Verfahren sind Blockchiffren. Dabei wird der Text in ¨ ¨ ¨ ¨ Blocke einer gewissen Lange (haufig 8 oder 16 Zeichen, also 64 oder 128 Bits) zerlegt. Auf diese Blocke wird dann eine Kombination von verschiedenen einfachen Verschlusselungsverfahren (z.B. modulare Ad¨ ¨ dition, Substitution, Linear-Transformation, Vertauschen von Teilblocken, ...) angewandt. Das folgende Bild zeigt zum Beispiel die Anordnung fur ¨ DES.

Normalerweise wird ein geheimer, vorher vereinbarter Schlussel verwendet. Das Entschlusseln geschieht ¨ ¨ indem alle Operationen in umgekehrter Reihenfolge (invers) angewandt werden. Beispiele von Blockhiffren sind: DES (Data Encryption Standard) oder DEA (Data Encryption Algorithm) ¨ , benutzt einen Schlussel der Lange 56. Triple-DES: Dreimaliges Anwenden von DES mit zwei oder ¨ drei verschiedenen Schlusseln. RC4 Rivest Cipher, 1987 von Ronald L. Rivest, eine Stromchiffre IDEA: ¨ ¨ arbeitet ahnlich wie DES, CAST, ... SSL (Secure Socket Layer) verwendet RC4, DES oder Triple-DES. S-HTTP (Secure HTTP) verwendet DES, Triple-DES oder IDEA. PGP (Pretty Good Privacy) benutzt verschiedene der Verfahren IDEA, Triple-DES, RSA, ...

9.5 Asymmetrische Verfahren: Public Key Kryptosysteme

9-7

9.5 Asymmetrische Verfahren: Public Key Kryptosysteme Bei kommerziellen Applikationen wie zum Beispiel Telebanking, beim Benutzen von elektronischem Geld ¨ oder beim Versenden von (geheimer) Email ist es zu aufwandig, mit jedem Kunden/Partner vorher geheime Schlussel auszutauschen. Um genugend Sicherheit zu bieten, mussten lange Schlussel verwendet ¨ ¨ ¨ ¨ ¨ ¨ werden, welche haufig gewechselt werden. (Die mit den Banken vereinbarten Schlussel/Passw orter die¨ nen beim Telebanking in der Regel in erster Linie zur Authentifikation/Identifikation des Kunden.) Es gibt aber Verfahren, welche ohne die Verteilung von geheimen Schlusseln auskommen, und die darum ¨ Public Key Kryptosysteme (PKK) genannt werden3 . Wie der Name sagt, benutzen PKKs nicht geheime, ¨ sondern offentliche Schlussel zum Verschlusseln der Texte. ¨ ¨ Verschlüsselung

öffentlicher Schlüssel des Empfängers

Entschlüsselung

Übermittlung

geheimer Schlüssel des Empfängers

In PKKs werden fur Funktionen benutzt, welche leicht zu berechnen, aber ohne ¨ die Verschlusselung ¨ ¨ ¨ ¨ zusatzliches Wissen nicht invertierbar sind. Diese Einwegfunktionen konnen dann offentlich bekannt gegeben werden. Da die Funktionen schwierig zu invertieren sind, ist das Entschlusseln nicht einfach ¨ ¨ moglich. Es gilt dann also: ¨ • Jeder kann mit dem offentlichen Schlussel Meldungen codieren. ¨ • Nur wer den geheimen Schlussel kennt, kann die Meldung decodieren. ¨ Definition: Eine bijektive Funktion f : X → Y heisst eine Einwegfunktion falls gilt:

• Die Funktion f ist leicht (mit wenig Rechenaufwand) zu berechnen. 3 Da fur zum Ver- bzw. Entschlusseln verwendet werden, spricht man auch von asymmetrischen ¨ diese Verfahren zwei verschiedene Schlussel ¨ ¨ Verfahren.

9-8

9 Kryptologie

¨ • Die Umkehrfunktion f −1 ist ohne zusatzliche (geheime) Informationen sehr schwierig (mit grossem Aufwand) zu berechnen.

Ein einfaches Modell fur ¨ eine Einwegfunktion ist ein Telefonbuch, mit welchem sehr schnell zu jedem ¨ Namen mit Adresse die Telefonnummer gefunden werden kann. Hingegen ist es sehr aufwandig, mit ¨ Hilfe eines Telefonbuchs zu einer Telefonnummer den zugehorigen Namen zu finden. Es musste das ¨ ganze Buch durchsucht werden. ¨ Das Finden von sicheren Einwegfunktionen ist nicht einfach. Wenn wir den Schlussel kennen, konnen wir ¨ in allen bisherigen Verfahren sehr leicht die Umkehrfunktion berechnen. Das Suchen von guten, schnellen und beweisbar sicheren Einwegfunktionen ist ein zentrales Forschungsgebiet der Kryptologie. Heute sind aber schon einige praktisch verwendbare (nicht beweisbar sichere) Einwegfunktionen bekannt. Die Idee bei einem Public Key Kryptosystem ist, dass jeder Teilnehmer fur ¨ sich ein Paar von Schlusseln ¨ ¨ generiert: einen offentlichen Schlussel zum Verschlusseln der Meldungen, und einen privaten, gehei¨ ¨ ¨ men Schlussel zum Entschlusseln. Der geheime Schlussel darf aus dem offentlichen Schlussel nur mit ¨ ¨ ¨ ¨ ¨ riesigem Aufwand oder gar nicht berechnet werden konnen. Der erste Schlussel generiert dann eine Einwegfunktion, welche nur mit Kenntnis des zweiten Schlus¨ ¨ sels (oder nur mit sehr grossem Aufwand) invertiert werden kann. ¨ Um einem Teilnehmer eine Meldung zu verschicken, verschlusseln wir die Meldung mit dessen offentli¨ chem Schlussel. Nur der Adressat, der den geheimen Schlussel kennt, kann die Einwegfunktion invertie¨ ¨ ren, also die verschlusselte Meldung wieder entschlusseln. ¨ ¨

Definition: Fur die folgenden Bedingungen erfullt ¨ ein PKK mussen ¨ ¨ sein: 1. Es gibt genugend viele Paare (V, E) von Verschlusselungsund Entschlusselungsfunktionen (bzw. ¨ ¨ ¨ ¨ von offentlichen und geheimen Schlusseln (v,e)). ¨ 2. Fur ¨ jede Meldung M gilt E(V (M)) = M . 3. V ist eine Einwegfunktion. 4. V und E sind leicht zu berechnen, wenn man den Schlussel v, bzw. e kennt. ¨

9.5 Asymmetrische Verfahren: Public Key Kryptosysteme

9-9

¨ Das erste PKK ist der Diffie-Hellmann Algorithmus von 1976. Auf einer ahnlichen Idee basiert der um 1978 von R. Rivest, A. Shamir und L. Adleman gefundene RSA Algorithmus. Die darauf basierenden Verfahren werden deshalb RSA-Kryptosysteme genannt.

9.5.1 Das RSA Verfahren Das RSA-Verfahren ist heute das am meisten benutzte PKK. RSA bildet die Grundlage fur ¨ SSL (Secure Socket Layer), welche vor allem fur ¨ WWW gebraucht werden, fur ¨ SET (Secure Electronic Transactions), welche im Zusammenhang mit elektronischem Geld wichtig sind, fur ¨ S/Mime, also sichere Email und vieles mehr (z.B. S-HTTP, SSH, ...). Die Sicherheit des RSA-Verfahren basiert auf dem Problem, eine grosse Zahl in ihre Primfaktoren zu zerlegen und aus dem Problem des diskreten Logarithmus. Das Berechnen von M v modm ist relativ einfach. Fur ¨ das Berechnen der v-ten Wurzel modulo m ist bisher kein schneller Algorithmus bekannt. Die beiden Schlussel werden so erzeugt: ¨ ¨ • Wahle zwei verschiedene grosse Primzahlen p und q und berechne deren Produkt: m = p ∗ q. Setze

n = (p − 1) · (q − 1).

¨ • Wahle einen beliebigen Wert e, der kleiner ist als m und teilerfremd zu n. Zu diesem wird dasjenige

v berechnet, fur ¨ das gilt: e · v = 1 + l · n (Euklid’scher Algorithmus).

˜ = M˜ e mod m. Es gilt dann fur V (M) = M v mod m fur E(M) ¨ das Verschlusseln: ¨ ¨ das Entschlusseln: ¨ v

V(M) = M mod m

öffentlicher Schlüssel des Empfängers

Übermittlung

e

E(M) = M mod m

geheimer Schlüssel des Empfängers

¨ Es gilt namlich nach dem Satz von Fermat

E(V (M)) = (V (M))e mod m = M ve mod m = M (1+ln) modm = M

9-10

9 Kryptologie

9.5.2 Authentifikation mit Hilfe von RSA ¨ ¨ Mit Hilfe eines RSA-Kryptosystems konnen wir auch feststellen, ob der Absender einer Meldung tatsachlich derjenige ist, der er zu sein vorgibt. Durch umgekehrtes Anwenden des RSA-Verfahrens kann der ¨ Empfanger nachprufen, ob die Meldung vom richtigen Sender stammt. ¨ Wir wissen bereits, dass E(V (M)) = M ve mod m = M gilt. Die Potenzoperation ist aber kommutativ, ¨ so dass auch M ev mod m = M gilt. Wir konnen also die Operationen Verschlusseln/Entschl usseln auch ¨ ¨ umdrehen. Verschlüsselung

Übermittlung

Entschlüsselung

öffentlicher Schlüssel des Senders

geheimer, privater Schlüssel des Senders

¨ Nur wenn beim Entschlusseln eines Textes mit dem offentlichen Schlussel des Absenders Klartext ent¨ ¨ ¨ steht, stammt die Meldung von diesem Absender. Da nur der Absender den zu seinem offentlichen Schlussel passenden geheimen Schlussel kennt, kann nur dieser eine solche Meldung verfassen. ¨ ¨

¨ 9.5.3 Integritatspr ufung: ¨ Fingerabdruck, Message Digest ¨ Ein zentrales Problem vor allem von grossen Anbietern (Banken, Online-Verkaufern, ...) ist, den Kunden ¨ zu garantieren, dass eine (unverschlusselte) Webseite tatsachlich die richtige Seite ist (und nicht eine ¨ ¨ ¨ werden. gefalschte). Dieses Problem kann mit Hilfe einer Hashfunktion und eines PKKs gelost Übermittlung

Hash Funktion

1 0 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1

=?

Hash Funktion

Verschlüsselung

geheimer, privater Schlüssel des Senders

Entschlüsselung

öffentlicher Schlüssel des Senders

9.5 Asymmetrische Verfahren: Public Key Kryptosysteme

9-11

¨ Falls beim Entschlusseln des Hashcodes mit dem offentlichen Schlussel des Absenders der gleiche Wert ¨ ¨ ¨ herauskmmt, sind wir sicher, dass unterwegs niemand die Meldung verandert hat. ¨ Allerdings funktioniert dies nur, wenn der uns bekannte offentliche Schlussel korrekt ist, uns also kein ¨ ¨ falscher Schlussel vorgetauscht wird. Dies garantieren spezielle Firmen und Institutionen, sogenannte ¨ Trustcenter wie TC TrustCenter, VeriSign oder Thawte.

9.5.4 Kombinierte (Hybride) Verfahren Eine kombinierte Methode verbindet die Sicherheit von RSA mit der Schnelligkeit von symmetrischen Verschlusselungsmethoden, wie zum Beispiel DES. ¨ DES

DES Schlüssel

0110

000000000 111111111 111111111 000000000 111111111 000000000 111111111 000000000 111111111 000000000

Übermittlung

Digital Envelope

Verschlüsselung

öffentlicher Schlüssel des Empfängers

Ein Digital Envelope wird erzeugt, indem der Text durch ein schnelles, weniger sicheres Verfahren ver¨ schlusselt wird (zum Beispiel mit DES), der DES-Schlussel selber wird mit dem offentlichen RSA-Schlus¨ ¨ ¨ ¨ sel des Empfangers verschlusselt. ¨ ¨ Auf diese Weise konnen die Vorteile beider Systeme kombiniert werden. Es mussen keine geheimen ¨ Schlussel auf einem (langsamen) sicheren Weg vorher vereinbart werden. Ausserdem kann fur ¨ ¨ jede ¨ Ubermittlung ein neuer DES-Schlussel verwendet werden. Durch einmaliges Verwenden jedes DES¨ Schlussels wird die Sicherheit des DES-Verfahrens erheblich verbessert. ¨ ¨ Nur der Empfanger kann den DES-Schlussel lesen, da er dazu seinen privaten RSA-Schlussel braucht. ¨ ¨ Danach kann er mit Hilfe des DES-Schlussels den Text entziffern. ¨

9-12

9 Kryptologie

¨ 9.6 Ubung 9 Fragen / Aufgaben zur Kryptologie ¨ uhrung ¨ Sie finden die Losungen zu den Fragen entweder im Skript oder unter dem Link Einf in die Kryp¨ ¨ ¨ der Ubungsseite. tologieauf 1. Welches sind heute die zentralen Einsatzgebiete der Kryptologie? 2. Was bedeutet Kryptografie? 3. Was bedeutet Kryptoanalyse? 4. Was ist Steganografie? ¨ ¨ 5. Wie heisst der Uberbegriff fur Zeichenweise ablauft? ¨ die Verfahren, bei welcher die Verschlusselung ¨ ¨ 6. Gewisse Verfahren teilen den Text zuerst in gleich grosse Blocke (zum Beispiel 64 Bit) auf: Welches ¨ ist der Uberbegriff fur ¨ diese Verfahren? 7. Skizzieren Sie ein symmetrisches Kryptosystem. ¨ 8. Verschlusseln Sie mit der Casar Chiffre und dem Schlussel B (k=1) Ihren Namen. ¨ ¨ ` Chiffre und dem Schlussel 9. Verschlusseln Sie mit der Viginere BBC Ihren Namen. ¨ ¨ 10. Welches sind die Vor- und Nachteile des One Time Pad (Vernam Chiffre)? 11. Was waren die Hauptgrunde dafur, ¨ ¨ dass die Enigma im zweiten Weltkrieg geknackt werden konnte? ¨ (Punkte 3, 6 und 7 der Aufzahlung unter http://www.nwn.de/hgm/krypto/ → Enigma) 12. Welche symmetrischen Kryptoverfahren werden heute (noch) verwendet? 13. Skizzieren Sie ein asymmetrisches Kryptosystem. 14. Welches ist der Hauptunterschied zwischen symmetrischen und asymmtetrischen Verfahren? 15. Wer hat das erste PKK erfunden? 16. Worauf basiert die Sicherheit von RSA? ¨ man mit einem PKK das Authentizitatsproblem? ¨ 17. Wie lost ¨ man mit einem PKK das Integritatsproblem? ¨ 18. Wie lost 19. Was ist der Vorteil von hybriden Kryptoverfahren?

Literaturverzeichnis [AHU74] Aho, Hopcroft, and Ullman. The Design and Analysis of Computer Algorithms. Addison Wesley, Reading, Massachusetts, 1974. ISBN 0-201-00029-6. [AU95]

A.V. Aho and J.D. Ullman. Foundations Of Computer Science C Edition. Computer Science Press An Imprint of W.H. Freeman and Company, New York, 1995. ISBN 0-7167-8284-7.

[Bud94] Timothy A. Budd. Classic Data Structures in C++. Addison-Wesley, Reading, MA, 1994. [Knu73a] D.E. Knuth. The Art of Computer Programing, volume 3 Sorting and Searching. Addison Wesley, Reading, MA, 1973. ISBN 0-201-03803-X. [Knu73b] D.E. Knuth. The Art of Computer Programing, volume 1 Fundamental Algorithms. Addison Wesley, Reading, MA, 1973. ISBN 0-201-03809-9. [Knu81] D.E. Knuth. The Art of Computer Programing, volume 2 Seminumerical algorithms. Addison Wesley, Reading, MA, 1981. ISBN 0-201-03822-6. [Sed92] Robert Sedgewick. Algorithmen in C++. Addison Wesley, Bonn, 1992. ISBN 3-89319-462-2. [Sed03] Robert Sedgewick. Algorithmen in Java. Grundlagen, Datenstrukturen, Sortieren, Suchen.. Pearson Studium, 2003. ISBN 3-82737-072-8. [Sha97] Clifford A. Shaffer. A Practical Introduction to Data Structures and Algorithm Analysis. Prentice Hall, London, 1997. ISBN 0-131-90752-2, C++ Version. [Sha98] Clifford A. Shaffer. A Practical Introduction to Data Structures and Algorithm Analysis: Java Edition. Prentice Hall, London, 1998. ISBN 0-136-60911-2. [Wir86]

N. Wirth. Algorithmen und Datenstrukturen mit Modula-2. Teubner, Stuttgart, 1986. ISBN 0-13-629031-0.

[SaSa04] Gunter Saake, Kay-Uwe Sattler Algorithmen und Datenstrukturen, Eine Einfuhrung mit Java. ¨ dpunkt, 2004. ISBN 3-89864-255-0. [Schied05] Reinhard Schiedermeier Programmieren mit Java, Eine methodische Einfuhrung. Pearson ¨ Studium, 2005 ISBN 3-8273-7116-3.

W-2

Index

abstrakter Datentyp, 1-5, 1-6 Algorithmus, 1-9 asymptotisches Verhalten, 2-5, 2-7 ¨ 2-1 Komplexitat, O-Notation, 2-7 Allgemeine Strukturen, 1-4 Array Grundtyp, 1-4 Indextyp, 1-4 Selektor, 1-4 Atomare Typen, 1-3 Auswahl, 7-2 Automat, 7-3 ¨ ε-Ubergang, 7-6 Ausgabe, 7-4 deterministischer, 7-5 Eingabe, 7-3 Eingabealphabet, 7-3 endlicher, 7-2 ¨ leerer Ubergang, 7-6 nichtdeterministischer, 7-5 Zustand, 7-3 Zustandsdiagramm, 7-4 Zustandstafel, 7-4 B-Baum, 4-10 ¨ Baume, 4-1 BinNode, 4-2 ¨ Baumdurchlaufe, 4-4 Breitensuche, 4-7 Inorder, 4-4 Levelorder, 4-7 Postorder, 4-4

¨ Praorder, 4-4 Tiefensuche, 4-4, 4-6 ¨ Binarbaum, 4-1, 4-2 ¨ Suchbaume, ¨ Binare 4-8 Caesar Chiffre, 9-3 Compiler, 8-1 Daten, 1-2 Bezeichung, 1-2 Semantik, 1-2 Syntax, 1-2 Wertemenge, 1-2 Datenstruktur, 1-6 Datentyp, 1-2 Konstanten, 1-2 Methoden, 1-2 Operatoren, 1-2 Wertebereich, 1-2 Divide and Conquer, 1-18 EBNF, 8-2 Einwegfunktion, 9-8 Extended Backus-Naur Form, 8-2 Grammatik, 8-2 Herleitung, 8-3 kontextfreie, 8-2 Nichtterminalsymbol, 8-3 Produktion, 8-3 Terminalsymbol, 8-3 Hashing, 5-5 Bucket Hashing, 5-11 Double Hashing, 5-8

Kollision, 5-6 Linear Probing, 5-8 Separate Chaining, 5-13 Heap, 4-15 Heapbedingung, 4-16 Iteration, 1-12, 7-2 Klassen, 1-4 ¨ 2-1 Komplexitat, Best-Case, 2-4 Worst-Case, 2-4 Konkatenation, 7-2 Kryptoanalyse, 9-1 Kryptologie, 9-1 Caesar Chiffre, 9-3 Einwegfunktion, 9-8 Permutationschiffre, 9-4 Public Key Kryptosystem, 9-7, 9-8 RSA-Kryptosystem, 9-9 Vernam-Chiffre, 9-5 ` Chiffre, 9-3 Vigenere Kryptosystem, 9-2 Liste, 3-1 Array Liste, 3-1 doppelt verkettet, 3-5 Node, 3-5 O-Notation, 2-6 Parser, 8-1 Pattern, 7-1 Alphabet, 7-1 Wort, 7-1 Pattern Matching, 7-1 Permutationschiffre, 9-4 Priority Queue, 4-15 Priority Queues, 4-15 Pseudocode, 1-10 Public Key Kryptosystem, 9-7, 9-8 Queue, 3-1, 3-11 ¨ Regularer Ausdruck, 7-2 Rekursion, 1-15 RSA-Kryptosystem, 9-9 Sortieren

Insertion-Sort, 6-4 Merge-Sort, 6-9 Quick-Sort, 6-6 Selection-Sort, 6-2 stabiler Algorithmus, 6-1 Spezifikation, 1-8 Sprache, 8-3 Herleitung, 8-3 Stack, 3-1, 3-11 Suchen, 5-1 ¨ Suche, 5-3 Binare Lineare Suche, 5-2 Top Down Parser, 8-1 Trustcenter, 9-11 Vernam-Chiffre, 9-5 Verschlusselung, 9-1 ¨ ` Chiffre, 9-3 Vigenere