Objektorientierte Programmierung VL: Prof. Dr. Marco Block-Berlitz - Freie Universität Berlin Proinformatik III Text: Hinnerk van Bruinehsen - Grafiken: Jens Fischer powered by SDS.mint SoSe 2011 1 Teil I 1. Tag 1 Einführung in die Algorithmik 1.1 Definition Algorithmus Allgemein lässt sich der Begriff wie folgt charakterisieren: • Es ist eine Menge von Regeln, die ein Problem löst. • Eine Menge von Anweisungen, die garantiert in einer endlichen Anzahl von Schritten (Terminierung), eine korrekte Lösung für jedes Beispiel eines gegebenen Problems findet. • Eine Sequenz von Schritten, die die Eingabedaten in die gewünschten Resultate überführt. Beispiel: Sieb des Eratosthenes Ein Algorithmus zur Ermittlung von Primzahlen. sieb(n) notiere aufsteigend alle natürlichen Zahlen k:=1 do m:=erste, nicht markierte Zahl größer k markiere die Vielfachen von m, außer m selbst k:=m √ while (k<= n) alle unmarkierten Zahlen sind Primzahlen Beispiel: sieb (20) Start: 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 für t = 1, k = 1, m = 2: 2 3 x 5 x 7 x 9 x 11 x 13 x 15 x 17 x 19 x für t = 2, k = 2, m = 3 2 3 x x 7 x x 11 x 13 x x 17 x 19 5 x x x für t = 3, k = 3, m = 5 2 2 3 x x 7 x x 11 x 13 x x 17 x 19 5 x x x Wir √ schätzen die Wurzel grob ab: 20 ∼ =5 Beispiel: Binäre Suche, so verfahren wir beispielsweise bei der Telefonbuchsuche: Gegeben sei eine aufsteigend sortierte Folge von Elementen a1 , a2 , .., an und ein gesuchtes Element x: bin_suche (a 1, a 2, ..., a n, x) i := 1 //linker Rand j := n //rechter Rand while (i < j) do m := ⌈ i+j 2 ⌉ //aufgerundet if (x > a m) i := m + 1 else if (x < a m) j := m − 1 else return a m endwhile return −1 a1 1. Schritt: |{z} a2 .... am |{z} ... m i an |{z} j Wenn x > am , dann verschieben wir den linken Rand auf m + 1: 2.Schritt: a1 a2 .... am am+1 | {z } i 1.2 ... an |{z} j Vom Problem zur Lösung am Rechner Alle Probleme der Infomatik haben etwas gemein. Das interne Verhalten basiert jeweils auf • der Darstellung: Bestimmung einer systematischen Codierung der Information, so dass diese effizient manipuliert werden kann. • der Transformation: die sukzessive Modifizierung des Zustands durch das Abarbeiten des Algorithmus, mit dem Zweck, das gewünschte Resultat zu liefern. 1.3 Eigenschaften von Algorithmen • Diskretheit: ein diskreter Algorithmus arbeitet schrittweise das Problem ab, d.h. er ist aus elementaren Operationen zusammengesetzt. • Finitheit: 3 – statisch: Die Beschreibung eines Algorithmus besitzt nur eine endliche Länge. – dynamisch: Ein Algorithmus nimmt während seiner Ausführung nur endlich viel Platz zur Speicherung von Zwischenresultaten in Anspruch. • Terminierung: Ein Algorithmus nennt man terminierend, wenn er bei jeder Anwendung nach endlich vielen Verarbeitungsschritten anhält und ein Resultat liefert. • Determinismus: Ein Algorithmus nennt man deterministisch, wenn zu jedem Zeitpunkt seiner Ausführung höchstens eine Möglichkeit der Fortsetzung besteht. Um für einen Algorithmus die Komplexität analysieren zu können, müssen wir entscheiden, auf welchem Rechnermodell wir arbeiten wollen. Pentium II, 266 MHz: 1 MB RAM,... Programmiersprachen: Haskell, Java, Pascal... davon müssen wir unabhängig sein! Compiler Es muss ein theoretisches, abstraktes Rechnermodell sein. Wir wählen eine Registermaschine, die RAM (Random Access Machine) [1963]. RAM: Eine RAM ist ein idealisierter von-Neumann-Rechner und hat folgende Eigenschaften: 1) jede einfache (atomare, elementare) Operation, z.B. +, −, ∗, /, %, =, if, ... benötigt nur eine Zeiteinheit bzw. einen Schritt. 2) Schleifen und Methoden sind keine elementaren Operationen, setzen sich aber aus diesen zusammen 3) Jeder Speicherzugriff dauert genau eine Zeiteinheit. Es gibt unbegrenzten Speicher. 1.4 Komplexität von Algorithmen Die Komplexität eines Algorithmus beschreibt dessen Kosten (Speicher, Laufzeit. Meistens reicht der Speicher in der Praxis aus, so dass die Laufzeit das interessante Komplexitätsmaß ist. Diese kann anhand der Anzahl der elementaren Operationen berechnet werden und hängt von der Dimension der Eingabe ab: [x1 , x2 , ..., xn ] {z } | n Das Sortieren von Listen ist sicherlich abhängig von der Größe der Liste. 1.4.1 Die Landau-Symbolik Es gibt verschiedene Beschreibungsmöglichkeiten, um die Anzahl der Elementarschritte in Abhängigkeit von der Größe der Eingabe n anzugeben. O-Notation: Mit der O-Notation geben wir eine obere Schranke für die Komplexität eines Algorithmus an. Wir drücken damit aus: dass ein Algorithmus nicht schlechter ist als...“. ” Mathematisch: f, g : N → R+ 4 f : Marcos binäre Suche, g: Michas grausame Suche Eine Funktion f (n) hat als obere Schranke g(n), wir schreiben dann f (n) ∈ O(g(n)) wenn es eine positive Konstante n0 ∈ N und C ∈ R+ gibt, so dass alle Werte von f (n) für n ≥ n0 immer kleiner/gleich c · g(n) sind. Wir schreiben dafür kurz f (n) ∈ O(g(n)): ∃n0 ∈ N, c ∈ R+ : ∀n ≥ n0 : f (n) ≤ c · g(n) Abbildung 1: Visuelle Interpretation Beispiel: Angenommen wir haben für einen Algorithmus A die folgende Laufzeit ermittelt: A(n) : 3n4 + 5n3 + 7 log n Jetzt wollen wir diesen mit B(n) vergleichen, der die Laufzeit von n4 hat. Mathematisch gesehen sind beide gleich! Wir behaupten also, dass A(n) ∈ O(B(n)) Beweis: 3n4 + 5n3 + 7 · log n ≤ 3n4 + 5n4 + 7n4 = 15n4 = 15n4 Für n ≥ 1: 15n4 ∈ O(n4 ) n0 = 1, c = 15 Die O-Notation ist nur dann sinnvoll, wenn die zur Abschätzung verwendete Funktion g(n) sinnvoll ist, also keine überflüssigen Konstanten oder Terme niedrigerer Ordnung enthält. 5 Beispiel: 1) Marco-Programmiersprache 1 Zw | sum := 0 2n Zw, n Add | for (i=1 to n) _____________ | sum = sum+i 1 + 3n ∈ O(n) | 2) sum = 0 for (i=1 to n) for (j =1 to i) sum = sum +1 Java |1 Zw |2n Zw, 2n Add, n if |____________________ | 1 + 5n ∈ O(n) | 1 | +n ·) ( n · ( | 2 | 1 ) | ) |_____________________ ⇒ O(nˆ2) | 1 + nˆ2 2 Abbildung 2: Zu Beispiel 2 Wichtige Komplexitätsklassen: (nach unten zunehmende Laufzeit) Laufzeit 1 logk n log √n n n n2 n3 nk 2n n! ∼ nn Bezeichung konstant polylogarithmisch logarithmisch linear quadratisch kubisch polynomial exponentiell faktoriell Optimalität Ein Algorithmus für ein gegebenes Problem ist dann optimal, wenn seine Komplexität eine untere Schranke für die Komplexität anderer Algorithmen darstellt, die das Problem ebenfalls lösen. [ Aoptimal (n), weiter Algorithmen sind: B(n), C(n), D(n) |{z} A(n) ∈ O(B(n)) ∧ A(n) ∈ O(C(n)) ∧ A(n) ∈ O(D(n)) Ω-Notation: Mit der Ω-Notation geben wir eine untere Schranke an. Also analog zu O-Notation: ∃n0 ∈ N, c ∈ R+ : ∀n ≥ n0 : f (n) ≥ c · g(n) wir schreiben dann f (n) ∈ Ω(g(n)) Θ-Notation: Sollte gelten, dass f (n) ∈ O(g(n)) und g(n) ∈ O(f (n)), dann haben beide die gleiche Komplexität. ⇒ f (n) ∈ Θ(g(n)) 6