5.1 Algorithmen 5.1.1 Der Algorithmenbegriff Duden: Algorithmus nach einem bestimmten Schema ablaufender Rechenvorgang Brockhaus: Algorithmus systematisches Rechenverfahren, das zu einer Eingabe nach endlich vielen Schritten ein Ergebnis liefert 1 Definition: Ein Algorithmus ist eine endliche Folge eindeutiger Anweisungen, mittels derer in endlich vielen Schritten aus einer spezifizierten Eingabe eine spezifizierte Ausgabe gewonnen wird. Dabei sollen die folgenden Punkte erfüllt sein: 1. Ein- und Ausgabe sind genau festgelegt; zu jeder Eingabe gibt es (genau) eine gültige Ausgabe (d.h. ein Algorithmus definiert eine Funktion Eingabe Ausgabe) Frage: Welche Funktion soll der Algorithmus berechnen? Problemspezifikation Frage: Berechnet der Algorithmus wirklich die spezifizierte Funktion? Korrektheitsuntersuchung 161
2. Das Aufschreiben des Algorithmus darf nur endlich viel Platz beanspruchen (also nicht: usw. ) 3. Jede Anweisung benötigt nur endlich viele Schritte und wird nur endlich oft ausgeführt Frage: Wie sehen die Schritte aus? Maschinenmodell Frage: Wird nach endlich vielen Schritten eine Ausgabe geliefert? Terminierung 4. Jede Anweisung hat ein eindeutiges (reproduzierbares) Resultat, und die jeweils nächste Anweisung ist eindeutig festgelegt Optional: Determinismus 5. Möglichst geringer Verbrauch von Ressourcen wie Rechenzeit, Speicher,... Effizienz 6. Der Algorithmus beruht auf einer nachvollziehbaren Idee und spiegelt diese klar wieder Verständlichkeit 162
Herkunft des Begriffs: Abu Ja far Mahammed Ibn Musa al-khowarizmi: Kitab al jabr w al-muqabala, 800 n. Chr. (Buch über das Rechnen mit arabischen Zahlen) al-khowarizmi: von (der Stadt) Khowarizm (heute Khiva, Usbekistan) verballhornt zu: Algorithmus al jabr: Zwang, Regel verballhornt zu: Algebra 163
5.1.2 Deterministische und nichtdeterministische Algorithmen 2 Beispiel: Berechnung von x y Deterministischer Algorithmus: 1. Lies Eingaben x und y 2. Falls x y: Weiter mit Schritt 3, sonst mit Schritt 4 3. Berechne a = y x; weiter mit Schritt 5 4. Berechne a = x y 5. Gib a aus Nichtdeterministischer Algorithmus: 1. Lies Eingaben x und y; weiter mit Schritt 2 oder 3 2. Berechne a = y x; weiter mit Schritt 4 3. Berechne a = x y 4. Falls a 0: Gib a aus, sonst gib a aus Wenn nicht explizit anders gesagt, sind alle betrachteten Algorithmen deterministisch. 164
5.1.3 Darstellungsformen für Algorithmen verbal Flussdiagramm (Programmablaufplan) Struktogramm (Nasi-Schneidermann-Diagramm) Pseudocode 3 Beispiel: Berechnung der Primzahlen n, wobei n einzulesen ist. Ansatz: Für n < 2 gibt es keine Primzahl n. Für n 2 ist 2 die kleinste der gesuchten Primzahlen. Als Primzahlen > 2 kommen nur ungerade Zahlen p in Frage. Teste für jede ungerade Zahl p mit 3 p n, ob sie einen Teiler t mit 1 < t < p besitzt (also nicht prim ist). Wenn es einen solchen Teiler gibt, dann auch einen mit t p. Es genügt also, Teilerkandidaten t mit t 2 p zu untersuchen. Da p ungerade ist, kommen nur ungerade t als Teiler in Frage. 165
Verbale Beschreibung: 1. Lies n 2. Wenn n < 2 dann stop 3. Gib erste Primzahl 2 aus 4. Teste für alle Kandidaten p = 3, 5,... mit p n, ob p prim ist (gemäß Schritt 5) 5. Wenn p durch keine der ungeraden Zahlen t = 3, 5,... mit t 2 p teilbar ist, dann gib p aus Flussdiagramm: Symbole: Aktion Verzweigung Ein-/Ausgabe flussdiagramm-process.eps flussdiagramm-conditional.eps flussdiagramm-i Übergang zu anderem Diagramm Anfang/Ende flussdiagramm-connect.eps flussdiagramm-terminate.eps 166
flussdiagramm-primzahlen1.eps 167
Struktogramm: lies n n 2 nein drucke 2 p := 3 solange p n t := 1 t := t + 2 bis p mod t = 0 oder t 2 > p t 2 > p nein drucke p erhöhe p um 2 ja ja Aktion Verzweigung Wiederholung Pseudocode: lies n wenn n 2 drucke 2 für p = 3 in Zweierschritten bis n t := 1 wiederhole t := t + 2 bis p durch t teilbar ist oder t 2 > p wenn t 2 > p dann drucke p 168
4 Beispiel: Euklidischer Algorithmus zur Bestimmung des größten gemeinsamen Teilers (ggt) zweier Zahlen Spezifikation: Eingabe: n, m N (nicht n, m Z (m = 0?) oder n, m Q,... ) Ausgabe: ggt = ggt(m, n) N, d.h. diejenige natürliche Zahl, die m und n teilt und für die gilt: Jeder gemeinsame Teiler t von m und n teilt auch ggt Algorithmus: 1. (Division mit Rest) Berechne m = q n + r, r, q N 0, 0 r < n 2. (Ergebnis) Falls r = 0 beende Algorithmus, ggt = n 3. (Ersetzen) m n, n r, gehe zu Schritt 1 Struktogramm: r := m mod n solange r 0 m := n n := r r := m mod n ggt := n 169 Rest der Division m/n
Pseudocode: r := m mod n solange r 0 m := n n := r r := m mod n ggt := n Zahlenbeispiel: m = 378, n = 2754 1 Durchlauf 8 < : 8 < : 8 < 1. 378 = 0 2754 + 378 2. 3. m 2754, n 378 1. 2754 = 7 378 + 108 2. 3. m 378, n 108 1. 378 = 3 108 + 54 2. : 3. m 108, n 54 j 1. 108 = 2 54 + 0 2. ggt = 54 170
Korrektheit: Ist die Größe ggt aus dem Algorithmus wirklich der ggt aus der Spezifikation? Zu zeigen: 1. ggt aus Schritt 2 ist gemeinsamer Teiler aller m, n, die im Algorithmus vorkommen 2. Ist t ein gemeinsamer Teiler der Eingabegrößen m und n, so teilt t auch alle m und n, die im Algorithmus vorkommen ( t teilt ggt) Beweis: Übung (ähnlich wie Terminierung und Definitheit). Terminierung: Zu zeigen: r = 0 wird nach endlich vielen Durchläufen erreicht. Beweis: Ist r i der Wert von r im i-ten Durchlauf, so gilt wegen Schritt 1 und 3 r i < r i 1 mit r 0 = n. Wegen r i N 0 folgt damit, dass nach spätestens n Durchläufen r = 0 gilt. qed. Definierte Schritte: Einzige Schwierigkeit : Division mit Rest. Hierzu ist aus der Grundschule ein Elementaralgorithmus bekannt. 171
Definitheit: Zu zeigen: In Schritt 1 gilt stets n, m N (für n N ist Division mit Rest nicht definiert) Beweis: Dies gilt nach Spezifikation für die Eingabe ( nach dem 0. Durchlauf ). Galt m, n N nach Schritt 3 im i-ten Durchlauf, so gilt im (i + 1)-ten Durchlauf m = n q + r, q, r N 0, 0 r < n. Ist r = 0, so terminiert der Algorithmus in Schritt 2. Ansonsten ist r N und n N, und damit auch nach Schritt 3 im (i+1)-ten Durchlauf m, n N. qed. Effizienz: Speicher: 4 natürliche Zahlen Rechenzeit: nicht schlecht 5 Bemerkung: Meist gibt es mehrere verschiedene Algorithmen zur Lösung eines Problems. 172
6 Beispiel: Primzahlberechnung mit dem Sieb des Eratosthenes 1. Gib eine Zahl n N vor 2. Füge die Zahlen 2,..., n in eine (zu Beginn leere) Liste Kandidaten ein 3. Bereite eine leere Liste Primzahlen vor 4. Wiederhole Schritte 5 und 6, bis die Liste Kandidaten leer ist 5. Bestimme die kleinste Zahl in Kandidaten und füge sie in Primzahlen ein 6. Entferne diese Zahl und alle ihre Vielfachen aus der Liste Kandidaten Zahlenbeispiel: n = 10 Kandidaten Primzahlen 2, 3, 4, 5, 6, 7, 8, 9, 10 3, 5, 7, 9 2 5, 7 2, 3 7 2, 3, 5 2, 3, 5, 7 Beachte den Speicherbedarf in Schritt 2 für große n! 173
5.1.4 Konstruktion von Algorithmen Top down-entwurf: Zerlege ein komplexes Problem in immer einfachere Probleme, bis schließlich Teilprobleme mit bekannter Lösung entstehen P top down P 1... P m P... 11 P bottom up 1k Bottom up-entwurf: Setze vorhandene Algorithmen für einfache Probleme zu Algorithmen für immer komplexere Probleme zusammen Wird häufig in Software-Bibliotheken eingesetzt Vergleiche unser Vorgehen bei der Digitaltechnik: Bauelemente Schaltungen für logische Funktionen Grundschaltungen (Addierer, Flip-Flop) kompletter Rechner Mehr zu Algorithmen: Informatik II: Algorithmen und Datenstrukturen 174
5.2 Phasen der Software-Entwicklung 1. Problemanalyse ergibt die Spezifikation Welche Anforderungen bestehen an die Software? Oft die komplizierteste und aufwändigste Aktivität 2. Entwurf der Algorithmen und Datenstrukturen Aufteilung in kleinere Einheiten Festlegung der Wechselwirkungen dieser Einheiten 3. Das Programm entsteht durch Implementierung (Umsetzung) der Algorithmen in einer bestimmten Programmiersprache Die Anweisungen der Algorithmen werden weiter aufgeschlüsselt, bis einzelnen Schritte effektiv (durch eine Maschine) ausgeführt werden können 4. Validierung Versuche nachzuweisen, dass das Programm den Anforderungen genügt Formale Methoden und/oder Tests 5. Wartung Korrektur von Fehlern Anpassung an sich ändernde Anforderungen Mehr dazu Software Engineering 175
5.3 Programmiersprachen Die Programmiersprache bildet die Schnittstelle zwischen Mensch und Rechner Mensch Programmiersprache Maschine Beide haben unterschiedliche Anforderungen Mensch Rechner Leichte Erlernbarkeit Lesbarkeit Ausdrucksstärke Direkte Ausführbarkeit oder einfache Umwandlung in ausführbaren Code Effizienter Code soll generiert werden können 176
5.3.1 Generationen von Programmiersprachen Maschinensprache: (Programmiersprache der 1. Generation) 00000010101111001010 00000010111111001000 00000011001110101000 Einzige Sprache, die von der Hardware direkt verstanden wird Sehr schwer zu lesen Prozessor-abhängig Assemblersprache: (Programmiersprache der 2. Generation) LAD ADD SPI I J K Gleiche Anweisungen wie Maschinensprache Anweisungen und Variablen haben Namen Programm muss vor der Ausführung in Maschinensprache übersetzt werden (durch ein anderes Programm, den Assembler) Immer noch Prozessor-abhängig 177
Höhere Programmiersprachen: (Programmiersprachen der 3. Generation) Ada (nach Ada Lovelace) BASIC (beginners all purpose symbolic instruction code) C (Nachfolger von B ) C++ (Erweiterung von C) COBOL (common business oriented language) FORTRAN (formula translation) Java (amerik. für Kaffee ) LISP (list processing) ALGOL, Haskell, Pascal, (PROLOG,) Smalltalk,... k = i + j ; Durch höheres Abstraktionsniveau besser lesbar Programm muss vor der Ausführung mittels eines Compilers in Maschinensprache übersetzt oder in einem Interpreter ausgeführt werden Weitgehend portabel, d.h. dasselbe Programm kann auf unterschiedlichen Maschinen laufen (Neu-Compilation für jede Maschine erforderlich) Typischerweise weniger effizient als optimierte Programme in Assemblersprache 178
Programmiersprachen der 4. Generation sind noch stärker an menschliche Begriffsbildung angelehnt SQL, Matlab,... find all records where name is "Smith" x = A \ b (löst lineares System Ax = b) Erfordern kaum Programmiererfahrung Häufig für Zugriff auf Datenbanken oder für Software- Prototypen eingesetzt Programmiersprachen der 5. Generation: uneinheitliche Klassifikation oder enthalten visuelle Tools zur Unterstützung der Programmentwicklung Visual BASIC,... erlauben dem Rechner, eigene Schlüsse zu ziehen häufig bei Künstlicher Intelligenz, Fuzzy Logic und Neuronalen Netzen eingesetzt PROLOG,... 179
5.3.2 Compiler und Interpreter Ein Compiler übersetzt ein Programm von einer Sprache A in eine Sprache B. Programm in Programmiersprache A A B Compiler Programm in Programmiersprache B Die beiden Programme sollen i. Allg. äquivalent sein, d.h. dieselbe Funktion Eingabe Ausgabe realisieren. Häufigste Variante: A ist höhere Programmiersprache (z.b. C), B die Maschinensprache eines bestimmten Prozessors; dann sagt man kurz A-Compiler (z.b. C-Compiler). "source code" Programm in höherer Programmiersprache Compiler "object code" Programm in Maschinensprache (Compiler) Programm in Assemblersprache (Assembler) (Intern evtl. in mehreren Stufen realisiert.) Es gibt aber auch FORTRAN-C-, Pascal-C-,... -Compiler. 180
Ein Interpreter ist ein Programm, das ein anderes Programm Anweisung für Anweisung liest, die Bedeutung jeder Anweisung bestimmt ( interpretiert ) und die entsprechenden Aktionen ausführt. Interpretation ermöglicht hohe Flexibilität, aber Interpretation kostet Zeit oft um einen Faktor 10 100 langsamer als entsprechendes compiliertes Programm moderne Interpreter beinhalten oft JIT-Compiler (just in time): Programm wird zunächst interpretiert Es wird mitgezählt, wie oft jede Anweisung ausgeführt wird Wird ein bestimmter Zählerstand erreicht, so wird die entsprechende Anweisung compiliert Sehr oft ausgeführte Anweisungen werden u.u. mehrmals compiliert, wobei immer mehr Zeit in die Optimierung des resultierenden Codes investiert wird 7 Beispiel: Überwiegend interpretierte Sprachen: BASIC, Java, Matlab, PROLOG, Skriptsprachen,... 181
5.3.3 Klassen von Programmiersprachen Imperative Sprachen : Folge von nacheinander ausgeführten Anweisungen gibt an, wie das Problem gelöst wird Prozedurale Sprachen : Variablen, Zuweisungen, Kontrollstrukturen Objektorientierte Sprachen : Deklarative Sprachen : Objekte und Klassen Abstrakte Datentypen und Vererbung Spezifikation dessen, was berechnet werden soll Festlegung, wie Berechnung verläuft, erfolgt durch Compiler Funktionale Sprachen : Funktionen ohne Seiteneffekte Rekursion Logische Sprachen : Regeln zur Definition von Relationen 182
Kenntnis verschiedener Sprachen (und Klassen) vorteilhaft: Eigene Ideen bei der Software-Entwicklung können besser ausgedrückt werden Nötig, um in konkreten Projekten geeignete Sprache auszuwählen Erleichtert das Erlernen weiterer Programmiersprachen (Nötig für den Entwurf neuer Programmiersprachen) 183
5.3.4 Wichtige Programmiersprachen 1960 COBOL FORTRAN ALGOL 60 LISP PL/1 Simula 1970 Pascal ALGOL 68 C Smalltalk PROLOG 1980 Scheme Ada C++ Eiffel Haskell 1990 Modula-3 Ada-95 Java p r o z e d u r a l o b j e k t o r i e n t i e r t funktional logisch
5.3.5 Syntax und Semantik von Programmiersprachen Programmiersprachen sind Sprachen, deren Syntax und Semantik genau festgelegt sind. Syntax: Legt fest, welche Wörter und Programme in der Sprache formuliert werden dürfen Semantik: formale Sprachen Legt die Bedeutung der zulässigen Wörter/Programme fest Syntaktisch falsche Wörter/Programme haben keine Semantik 185
5.3.6 Formale Sprachen 8 Definition: Ein Alphabet A ist eine nichtleere endliche Menge von Zeichen ( Buchstaben, Symbole). Ein Wort über dem Alphabet A ist eine endliche (evtl. leere) Folge von Zeichen aus A. ε bezeichnet das leere Wort. A bezeichnet die Menge aller Wörter über dem Alphabet A (einschl. ε). Eine (formale) Sprache L über A ist eine beliebige Teilmenge von A. 9 Beispiele: a) A 1 = {0, 1} A 1 = {ε, 0, 1, 00, 01, 10, 11, 000, 001,...} L = {0, 1, 10, 11, 100, 101,...} A 1 (Menge aller Binärdarstellungen natürlicher Zahlen mit 0 ohne führende Nullen) b) A 2 = {(, ), +,,, /, a} A 2 = {ε, (, ), +,,, /, a, (),..., (+ a),...} L sei die Sprache der korrekt geklammerten Ausdrücke in A 2 : L = {(((a))), (a + a), (a a) a + a,...} 186
Endliche Beschreibungsmöglichkeiten für die (meist unendlichen) Sprachen: Grammatik erzeugt die Sprache, oder Automat erkennt die Sprache. Eine Grammatik enthält Regeln, die festlegen, welche Wörter über einem Alphabet zur Sprache gehören und welche nicht. 10 Beispiel: Grammatik für Hund Katze-Sätze 1 Satz Subjekt Prädikat Objekt 2 Subjekt Artikel Attribut Substantiv 3 Artikel ε 4 Artikel der 5 Artikel die 6 Artikel das 7 Attribut ε 8 Attribut Adjektiv 9 Attribut Adjektiv Attribut 10 Adjektiv kleine 11 Adjektiv bissige 12 Adjektiv große 13 Substantiv Hund 14 Substantiv Katze 15 Prädikat jagt 16 Objekt Artikel Attribut Substantiv 187
Alphabet (z.b.): A = {der, die, das, kleine, bissige, große, Hund, Katze, jagt} Mit dieser Grammatik können z.b. die folgenden Wörter (Sätze) gebildet ( abgeleitet ) werden: der kleine bissige Hund jagt die große Katze die kleine Katze jagt der bissige Hund das große Katze jagt der kleine große bissige kleine... Katze Folgende Sätze sind nicht mit dieser Grammatik ableitbar: die Katze der Hund Katze und Hund der Hund jagt die Katze die jagt Hund 188
11 Definition: Eine Grammatik ist definiert durch ein Viertupel (N, T, P, S). Dabei ist N eine endliche Menge von Nichtterminalsymbolen (Variablen). Diese sind Symbole für syntaktische Abstraktionen, z.b. Satz, Subjekt, Prädikat, Objekt,... kommen nicht in den Wörtern der Sprache vor werden durch Anwendung der Produktionsregeln (s.u.) so lange ersetzt, bis nur noch Terminalsymbole übrig sind T eine endliche Menge von Terminalsymbolen T N = Elemente von T sind Zeichen des Alphabets, aus denen die Wörter der Sprache bestehen, z.b. der, die, das, kleine,... P eine endliche Menge von Produktionsregeln x y Regel x y bedeutet, dass das Teilwort x durch das Teilwort y ersetzt werden kann x V NV, y V, wobei V := N T (Vokabular), d.h. sowohl x als auch y können beliebige Nichtterminal- und Terminalsymbole enthalten, und x enthält mindestens ein Nichtterminal Beispiel: Anwendung der Regel Prädikat jagt auf das Wort 189
der kleine bissige Hund Prädikat Objekt liefert der kleine bissige Hund jagt Objekt S das Startsymbol spezielles Nichtterminalsymbol (S N), aus dem alle Wörter der Sprache mit Hilfe der Produktionsregeln erzeugt werden Beispiel: Satz Ein Wort w heißt (direkt) ableitbar aus dem Wort z, Schreibweise z w, wenn es Worte u, v V und eine Regel (x y) P gibt mit z = uxv und w = uyv, d.h. w entsteht aus z, indem man eine Regel auf ein Teilwort x von z anwendet. Die von der Grammatik erzeugte Sprache ist L(G) = {w T : S... w}, also die Menge aller aus dem Startsymbol ableitbaren Wörter, die nur Terminalsymbole enthalten. Zwei Grammatiken heißen äquivalent, wenn sie dieselbe Sprache erzeugen. Eine Grammatik heißt kontextfrei, wenn jede Produktion die Form X y besitzt mit X N, y V, d.h. links steht genau ein Nichtterminal (und sonst nichts). 190
12 Beispiel: G = (N, T, P, S) mit N = {A, B} T = {a, b, c, d} P = {A abcd, B abc, abc b} S = A Dann ist L(G) = {a n bc n d : n 0}, d.h. die Menge aller Wörter, die mit einer beliebigen Anzahl n von a s beginnen, gefolgt von genau einem b, dann gleich vielen c s wie a s zu Beginn, und schließlich genau einem d. Beweis: Zu zeigen: 1. : Alle Wörter in L besitzen diese Form 2. : Jedes solche Wort kann auch erzeugt werden Zu 2.: Wähle n N 0. Betrachte die Ableitung A Regel 1 Regel 2 Regel 2 abcd aabccd aaabcccd. (insgesamt n-mal Regel 2) Regel 2 Regel 3 a n abcc n d a n bc n d (Unterstrichen ist jeweils das Teilwort, auf das die nächste Regel angewendet wird.) 191
Zu 1.: Wir zeigen mit Induktion, dass jedes mit k 1 Schritten ableitbare Wort die Form a k Bc k d oder a k 2 bc k 2 d (nur im Falle k 2 möglich) besitzt. Hieraus folgt die Behauptung, denn: Die erste dieser Formen enthält ein Nichtterminal, gehört also nicht zu L(G) Das Startsymbol A gehört als Nichtterminal auch nicht zu L(G) Alle Worte in L(G) sind von der Form a k 2 bc k 2 d (mit k 2) k = 1: Auf das Startsymbol ist nur Regel 1 anwendbar es ergibt sich das Wort abcd der ersten Form. k 1 k: Nach k 1 Ableitungsschritten habe das Wort die Form a k 1 Bc k 1 d oder a k 3 bc k 3 d. Im Falle a k 3 bc k 3 d ist kein Nichtterminal mehr vorhanden überhaupt kein k-ter Schritt mehr durchführbar Im Falle a k 1 Bc k 1 d = a k 2 abcc k 2 d gibt es zwei Möglichkeiten: Regel 2 ergibt das Wort a k Bc k d Regel 3 ergibt a k 2 bc k 2 d Beide entsprechen der Behauptung. qed. G ist nicht kontextfrei, da die dritte Regel auf der linken Seite nicht nur ein Nichtterminal enthält. Ersetzt man P durch P = {A Bd, B abc, B b}, so erhält man eine zu G äquivalente kontextfreie Grammatik. 192