Algorithmen und Datenstrukturen Werner Struckmann Wintersemester 2005/06 1. Der Algorithmenbegriff 1.1 Der intuitive Algorithmenbegriff 1.2 Ein Beispiel: Sortieren durch Einfügen 1.3 Programmiersprachen: der praktische Algorithmenbegriff 1.4 Berechenbarkeit: der formale Algorithmenbegriff Der intuitive Algorithmenbegriff Gegeben sei ein „Problem“. Eine Handlungsvorschrift, deren mechanisches Befolgen ◮ ohne Verständnis des Problems ◮ mit sinnvollen Eingabedaten ◮ zur Lösung des Problems führt, wird Algorithmus genannt. Ein Problem, für dessen Lösung ein Algorithmus existiert, heißt berechenbar. 1.1 Der intuitive Algorithmenbegriff 1-1 Beispiele für Algorithmen ◮ Zerlegung handwerklicher Arbeiten in einzelne Schritte, ◮ Kochrezepte, ◮ Verfahren zur schriftlichen Multiplikation, ◮ Algorithmus zur Bestimmung des größten gemeinsamen Teilers zweier natürlicher Zahlen. 1.1 Der intuitive Algorithmenbegriff 1-2 Präzisierung des Begriffs Ein Algorithmus ist eine wohldefinierte Rechenvorschrift, die eine (evtl. leere) Menge von Größen als Eingabe verwendet und eine Menge von Größen als Ausgabe erzeugt. Ein Algorithmus ist also eine Abfolge von Rechenschritten, die die Eingabe in die Ausgabe umwandelt. ◮ Der Algorithmus muss durch einen endlichen Text in einer wohldefinierten Sprache beschrieben sein. ◮ Die Objekte der Berechnung müssen klar sein. ◮ Die Operationen müssen mechanisch ausführbar sein. ◮ Die Reihenfolge der Operationen muss feststehen. 1.1 Der intuitive Algorithmenbegriff 1-3 Eigenschaften von Algorithmen ◮ Terminierend: Für alle korrekten Eingaben hält der Algorithmus nach endlich vielen Schritten an. ◮ Vollständigkeit: Alle Fälle, die bei korrekten Eingabedaten auftreten können, werden berücksichtigt. ◮ Determiniert: Der Algorithmus liefert bei jedem Ablauf mit den gleichen Eingaben das gleiche Ergebnis. ◮ Deterministisch: Der Algorithmus läuft bei jedem Ablauf mit den gleichen Eingaben durch diesselbe Berechnung. 1.1 Der intuitive Algorithmenbegriff 1-4 Beispiele zu Eigenschaften Nichtterminierender Algorithmus: 1. Wähle zufällig eine natürliche Zahl. 2. Ist die Zahl gerade, wiederhole ab 1. 3. Ist die Zahl ungerade wiederhole ab 1. Nicht vollständiger Algorithmus: 1. Wähle zufällig eine Zahl x . 2. Wähle zufällig eine Zahl y . 3. Das Ergebnis ist x /y . Was ist, wenn y = 0 sein sollte? 1.1 Der intuitive Algorithmenbegriff 1-5 Beispiele zu Eigenschaften Nicht determinierter Algorithmus: 1. Wähle zufällig eine natürliche Zahl zwischen 260 und 264 . 2. Prüfe, ob die Zahl eine Primzahl ist. 3. Falls nicht, wiederhole ab 1. Das Ergebnis ist immer eine Primzahl, aber nicht immer die gleiche, daher ist der Algorithmus nicht determiniert. Nichtdeterministischer Algorithmus, jedoch determiniert: 1. Mische das Eingabefeld zufällig. 2. Prüfe, ob jedes Element kleiner als das rechts von ihm stehende ist. 3. Falls nicht, wiederhole ab 1. 1.1 Der intuitive Algorithmenbegriff 1-6 Algorithmen und Datenstrukturen ◮ Algorithmen ◮ ◮ ◮ ◮ ◮ ◮ elementare Operationen sequenzielle, bedingte, wiederholte Ausführung Unterprogramme die Schritte werden an anderer Stelle beschrieben und sind mehrfach verwendbar. Rekursionen derselbe Algorithmus wird auf ein oder mehrere gleichartige Teilprobleme angewendet. „gleichzeitige“ Ausführung Datenstrukturen ◮ ◮ einfache Daten: Zahlen, Zeichen, Wahrheitswerte komplexe Datenstrukturen: Felder, Listen, Bäume 1.1 Der intuitive Algorithmenbegriff 1-7 Codierung von Daten ◮ Ein Code ist eine (berechenbare) Funktion, die jeder Zeichenkette einer Urbildmenge eindeutig eine Zeichenkette aus einer Bildmenge zuordnet. ◮ In technischen Systemen dienen Codes überwiegend der Darstellung von Nachrichten. ◮ Codierungen ermöglichen beispielsweise die Verwendung von Nachrichten in Rechnern und die Übertragung von Nachrichten. ◮ Beispiel „Zahldarstellung in Stellenwertsystemen“: (22)10 = (112)4 = (10110)2 ◮ Beispiel „ASCII-Code zur Darstellung von Zeichen“: B 7−→ 66 7−→ 1000010 1.1 Der intuitive Algorithmenbegriff 1-8 Sortieren durch Einfügen ◮ Spezifikation: Eine Liste < a1 , a2 , . . . , an > von n ganzen Zahlen, n ≥ 1, soll aufsteigend sortiert werden. ◮ Objekte: Zahlen, Listen von Zahlen. ◮ Operationen: Vergleiche, Einfügen in Liste, Löschen aus Liste. ◮ Eingabe: Eine Liste < a1 , a2 , . . . , an > von n Zahlen. ◮ Ausgabe: Eine Permutation < a1′ , a2′ , . . . , an′ > der Eingabe mit a1′ ≤ a2′ ≤ . . . ≤ an′ . Die zu sortierenden Zahlen werden auch als Schlüssel bezeichnet. Beim Sortieren durch Einfügen wird jede Zahl aus der Ausgangsliste an die richtige Stelle in der Zielliste eingefügt. 1.2 Ein Beispiel: Sortieren durch Einfügen 1-9 Sortieren durch Einfügen 1. Fange beim zweiten Element an. 2. Bewege die Zahl an die richtige Stelle der Liste 3. Schiebe dazu solange Elemente in der Liste nach rechts bis die richtige Stelle erreicht ist. 4. Wiederhole die Schritte mit dem nächsten Element bis das Ende der Liste erreicht ist. 5. Die Liste hat jetzt die richtige Sortierung und kann zurückgegeben werden 1.2 Ein Beispiel: Sortieren durch Einfügen 1-10 Beispiel 5 2 4 6 1 3 2 4 5 6 1 3 2 5 4 6 1 3 1 2 4 5 6 3 2 4 5 6 1 3 1 2 3 4 5 6 1.2 Ein Beispiel: Sortieren durch Einfügen 1-11 Pseudocode insertionSort(A) j ← 2; while j ≤ length(A) do key ← A[j]; // insert A[j] into the // sorted sequence A[1 .. j-1] i ← j - 1; while i > 0 und A[i] > key do A[i + 1] ← A[i]; i ← i - 1; od; A[i + 1] ← key; j ← j + 1; od; 1.2 Ein Beispiel: Sortieren durch Einfügen 1-12 Korrektheit und Komplexität ◮ Korrektheit: Leistet der Algorithmus das Gewünschte? Das heißt: Sortiert der Algorithmus die Liste und terminiert dann? ◮ Komplexität: Wie viele Rechenschritte und wie viel Speicher benötigt der Algorithmus? 1.2 Ein Beispiel: Sortieren durch Einfügen 1-13 Schleifeninvariante In jeder Iteration gilt: ◮ A [1..j − 1] enthält immer die Elemente, die auch vorher dort lagen. ◮ Die Elemente in diesem Bereich sind sortiert. Eine Schleifeninvariante ◮ Initialisierung: gilt vor dem ersten Schleifendurchlauf. ◮ Fortsetzung: gilt vor dem n + 1-Schleifendurchlauf, falls sie vor dem n-Schleifendurchlauf galt. ◮ Terminierung: liefert eine nützliche Bedingung, sobald die Schleife abbricht. 1.2 Ein Beispiel: Sortieren durch Einfügen 1-14 Schleifeninvariante im Beispiel Schleifeninvariante: Das Teilfeld A [1..j − 1] besteht aus den ursprünglich in A [1..j − 1] enthaltenen Elementen in geordneter Reihenfolge. Initialisierung: j = 2. A [1] besteht aus dem Element, was vorher auch schon dort war. Die Liste A [1] ist sortiert. Fortsetzung: A [j − 1], A [j − 2], . . . werden jeweils nach rechts verschoben, A [j ] an der richtigen Stelle eingefügt. Terminierung: j = n + 1 eingesetzt in die Invariante ergibt: A [1..n] enthält die Elemente, die vorher in A [1..n] enthalten waren, in geordneter Reihenfolge. 1.2 Ein Beispiel: Sortieren durch Einfügen 1-15 Übersicht Code Kosten insertionSort(A) j ← 2; c1 while j ≤ length(A) do c2 key ← A[j]; c3 i ← j - 1; c4 while i > 0 und A[i] > key c5 do; A[i + 1] ← A[i]; c6 c7 i ← i - 1; od A[i + 1] ← key; c8 j ← j + 1; c9 od; Anzahl 1 n n−1 n−1 Pn j =2 tj Pn (tj − 1) j = 2 Pn j =2 (tj − 1) n−1 n−1 tj : Anzahl, wie oft der Test der inneren Schleife ausgeführt wird 1.2 Ein Beispiel: Sortieren durch Einfügen 1-16 Laufzeit Die Gesamtlaufzeit ergibt sich zu: T (n) = c1 + c2 n + c3 (n − 1) + c4 (n − 1) + c5 · n X tj j =2 n n X X + c6 · (tj − 1) + c7 · (tj − 1) j =2 j =2 + c8 (n − 1) + c9 (n − 1) 1.2 Ein Beispiel: Sortieren durch Einfügen 1-17 Günstigster Fall Wenn das Feld sortiert ist, gilt immer A[i] ≤ key , also tj = 1. T (n) = c1 + c2 n + c3 (n − 1) + c4 (n − 1) + c5 (n − 1) + c8 (n − 1) + c9 (n − 1) = an + b = Θ(n) 1.2 Ein Beispiel: Sortieren durch Einfügen 1-18 Ungünstigster Fall Wenn das Feld umgekehrt sortiert ist, gilt nie A[i] ≤ key , also tj = j . T (n) = c1 + c2 n + c3 (n − 1) + c4 (n − 1) ! 1 + c5 n(n + 1) − 1 2 1 1 + c6 (n − 1)n + c7 (n − 1)n 2 2 + c8 (n − 1) + c9 (n − 1) = an2 + bn + c = Θ(n2 ) j Mittlerer Fall: tj ≈ 2 . Ergibt ebenfalls Θ(n2 ). 1.2 Ein Beispiel: Sortieren durch Einfügen 1-19 Programm und Programmiersprache Ein Programm ist die Formulierung eines Algorithmus und seiner Datenbereiche in einer Programmiersprache. Eine Programmiersprache erlaubt es, Algorithmen präzise zu beschreiben. Insbesondere legt eine Programmiersprache ◮ die elementaren Operationen, ◮ die Möglichkeiten zu ihrer Kombination und ◮ die zulässigen Datenbereiche eindeutig fest. Unter „programmieren“ versteht man den Vorgang des Erstellens eines Programms. 1.3 Programmiersprachen: der praktische Algorithmenbegriff 1-20 Entwicklung der Programmiersprachen JAVA 1995 93 91 SCHEME−Standard 89 87 Programmiersprachen in der Informatikausbildung C++ 85 83 81 ADA 79 CSP ALGOL68 67 59 ◮ Modula-2 ◮ Scheme ◮ Scheme/Java ◮ Java LOGO 69 61 Algol 68 PROLOG PASCAL 71 63 ◮ SCHEME C 73 65 Algol SMALLTALK80 MODULA2 77 1975 ◮ OCCAM SIMULA PL/I BASIC COBOL ALGOL LISP 57 1955 FORTRAN 1.3 Programmiersprachen: der praktische Algorithmenbegriff 1-21 Definition von Programmiersprachen Unter Semiotik versteht man die Lehre von der Entstehung, dem Aufbau und der Wirkungsweise von Zeichen und Zeichenkomplexen. Sie umfasst die folgenden Bereiche. Die lexikalische Struktur einer Programmiersprache bestimmt die textuellen Grundbausteine der Programme. Solche Bausteine sind etwa Schlüsselwörter und Bezeichner. Sie werden zum Beispiel durch Aufzählung oder reguläre Ausdrücke bestimmt. Die Syntax einer Programmiersprache beschreibt, wie aus den Grundbausteinen vollständige Programme gebildet werden können. In den meisten Fällen wird die Syntax einer Programmiersprache durch eine kontextfreie Grammatik festgelegt. 1.3 Programmiersprachen: der praktische Algorithmenbegriff 1-22 Definition von Programmiersprachen Die Bedeutung der syntaktisch korrekten Programme ist durch die Semantik der Sprache gegeben. Sie kann beispielsweise mithilfe von Zustandsfolgen (operationelle Semantik) oder durch Funktionen, die den syntaktischen Einheiten zugeordnet sind (denotationale Semantik), definiert werden. Die Pragmatik einer Programmiersprache untersucht ihre Anwendbarkeit und Nützlichkeit. Sie gehört nicht zur Definition der Sprache. 1.3 Programmiersprachen: der praktische Algorithmenbegriff 1-23 Definition von Programmiersprachen Lexikalische Struktur: Bezeichner: Buchstabe · (Buchstabe, Ziffer)∗ Schlüsselwörter: while, do, od Syntax: <Folge> <Anweisung> <Zuweisung> <While> ::= ::= ::= ::= <Anweisung> ; <Folge> | <Anweisung> <Zuweisung> | <While> | ... <Bezeichner> := <arith. Ausdruck> while <log. Ausdruck> do <Folge> od (Operationelle) Semantik: Eine (partielle) Funktion f, die Zustände auf Zustände abbildet. 1.3 Programmiersprachen: der praktische Algorithmenbegriff 1-24 Klassifikation der Programmiersprachen Die Programmiersprachen lassen sich grob in drei Klassen einteilen: ◮ Maschinensprachen Bits und Bytes, für den menschlichen Leser kaum verständlich ◮ Maschinenorientierte Sprachen (Assembler) stellen die Befehle in einem Mnemo-Code dar ADDIC 23, R0 STO R0, #12004 ◮ Problemorientierte Sprachen imperative, funktionale, objektorientierte, deduktive Sprachen Ein Computer versteht nur Maschinensprachen! 1.3 Programmiersprachen: der praktische Algorithmenbegriff 1-25 Implementierung von Programmiersprachen Compiler übersetzen Quellprogramme aus problemorientierten Sprachen in äquivalente Zielprogramme in Maschinensprachen: cc -o prog prog.c prog input output Interpreter lesen das Programm zusammen mit den Eingabedaten ein und führen es aus: scm prog.scm input output Mischverfahren übersetzen das Programm zunächst mit einem Compiler in eine Zwischensprache. Das übersetzte Programm wird anschließend interpretiert: javac prog.java java prog input output 1.3 Programmiersprachen: der praktische Algorithmenbegriff 1-26 Verarbeitung von Java-Programmen ◮ Zuerst wird ein Quellprogramm vom Compiler in Bytecode übersetzt. ◮ Im zweiten Schritt wird der Bytecode vom Interpreter ausgeführt. Der Bytecode kann als Maschinencode der sogenannten virtuellen Java-Maschine angesehen werden. Bytecode ist portabel. Java−Quellprogramm javac Java−Bytecode java VMfürWindows java VMfürLinux 1.3 Programmiersprachen: der praktische Algorithmenbegriff 1-27 Berechenbarkeit/Entscheidbarkeit ◮ Um zu zeigen, dass ein Problem berechenbar ist, kann man einen Algorithmus angeben. ◮ Gibt es mathematisch beschreibbare Problemstellungen, die nicht berechenbar sind? Solch ein Nachweis setzt eine mathematisch exakte Formulierung des Algorithmenbegriffs voraus. ◮ Es existieren verschiedene Ansätze zur Präzisierung des Algorithmenbegriffs: Turing-Maschinen, Markov-Algorithmen, partiell-rekursive Funktionen,. . . 1.4 Berechenbarkeit: der formale Algorithmenbegriff 1-28 Turing-Maschine ◮ Alan M. Turing (1912–1954), britischer Mathematiker: „Berechenbar“ heißt auf einer Maschine ausführbar. ◮ Turing-Maschine: mathematisches Modell einer Rechenmaschine. 1.4 Berechenbarkeit: der formale Algorithmenbegriff 1-29 Bestandteile einer Turing-Maschine ◮ beliebig langes Band bestehend aus einzelnen Feldern, ◮ Alphabet B von Zeichen, die in den Feldern gespeichert werden können, ◮ Lese-/Schreibkopf für genau ein Feld, Aktionen r , l , s < B , ◮ ◮ ◮ ◮ ◮ Kopf ein Feld nach rechts bewegen: r , Kopf ein Feld nach links bewegen: l , stoppen: s , schreiben von x ∈ B : x , ◮ Turing-Tafel, ◮ Steuereinheit steuert Bewegungen und Schreibaktionen, jeweils in einem von endlich vielen Zuständen. 1.4 Berechenbarkeit: der formale Algorithmenbegriff 1-30 Turing-Tafel Eine Turing-Tafel besteht aus einer Folge von Anweisungen der Gestalt (z , x , a , z ′ ) mit ◮ z ∈ Z = {0, 1, . . . , m}: Zustand der Maschine vor Ausführung der Anweisung, ◮ x ∈ B : Zeichen auf dem Band, ◮ a ∈ A = {r , l , s } ∪ B : Aktion, ◮ z ′ ∈ Z ∪ {⊥}: Folgezustand nach Ausführung der Anweisung. ⊥ ist der Endzustand. Für alle z ∈ Z , x ∈ B gibt es genau eine Anweisung (z , x , a , z ′ ), mit a ∈ A , z ′ ∈ Z ∪ {⊥}. Eine Turing-Maschine arbeitet also deterministisch. 1.4 Berechenbarkeit: der formale Algorithmenbegriff 1-31 Aktionen z a=l ... 1 0 3 ... a=s ... 1 0 3 ... a=2 a=r z′ = ⊥ ... 1 0 3 ... 1.4 Berechenbarkeit: der formale Algorithmenbegriff z′ z′ ... 1 0 3 ... z′ ... 1 2 3 ... 1-32 Alternative Darstellung Zustandsüberführungsdiagramm: x3 : a3 x0 : a0 z0 x2 : a2 z1 z11 x1 : a1 z2 1.4 Berechenbarkeit: der formale Algorithmenbegriff ... 1-33 Die churchsche These ◮ Eine (partielle) Funktion f : N0 → N0 heißt turing-berechenbar, wenn es eine Turing-Maschine gibt, die für jeden Eingabewert n aus dem Definitionsbereich nach endlich vielen Schritten mit der Bandinschrift f (n) anhält. ◮ Die Turing-Berechenbarkeit ist ein mathematisches Modell zur Beschreibung von Algorithmen. ◮ These von Church: Der intuitive Begriff „berechenbar“ wird durch den mathematischen Begriff „turing-berechenbar“ erfasst. Es handelt sich hier um eine prinzipiell nicht beweisbare These. ◮ Weitere mathematische Modelle (s. oben) erwiesen sich als äquivalent. Dies ist ein starkes Indiz für die Gültigkeit der churchschen These. 1.4 Berechenbarkeit: der formale Algorithmenbegriff 1-34 Das Halteproblem ◮ Das Halteproblem für Turing-Maschinen ist nicht entscheidbar (berechenbar), d. h., es gibt keinen Algorithmus (Turing-Maschine), der für alle Turing-Maschinen und für alle möglichen Eingaben entscheidet, ob die Turing-Maschine mit dieser Eingabe anhält oder nicht. ◮ Dieser Satz schließt nicht aus, dass man für spezielle Turing-Maschinen entscheiden kann, ob sie halten. Er besagt lediglich, dass es kein allgemeines Verfahren gibt. 1.4 Berechenbarkeit: der formale Algorithmenbegriff 1-35