Fakultät für Informatik Lehrstuhl 11 / Algorithm Engineering Prof. Dr. Petra Mutzel, Carsten Gutwenger André Gronemeier, Tobias Marschall, Hoi-Ming Wong Sommersemester 2008 DAP2 Praktikum – Blatt 0 Ausgabe: 8. April — Abgabe: keine Dieses Praktikumsblatt dient zur Vorbereitung und wird nicht bewertet. Trotzdem solltet ihr es auf jeden Fall in den Praktikumsgruppen bearbeiten, da euch dann die Bearbeitung der nächsten Praktikumsblätter deutlich leichter fallen wird. Die ersten Schritte in C++ erleichtert euch das Skript Von Java nach C++ von Thomas Willhalm (siehe http://i11www.iti. uni-karlsruhe.de/teaching/scripts/sources/java2c++.pdf). Aufgabe 0.1 Schreibe ein C++-Programm, das "Hello, C++!" mit dem C++-Ausgabestream cout ausgibt. Hinweise: • Verwende für deine main-Funktion die Signatur int main(). • Um die C++ Ausgabestreams verwenden zu können, musst du mit #include <iostream> zu Beginn der Quelldatei den entsprechenden Header inkludieren. • Die Symbole der C++-Ausgabestreams befinden sich im Namespace std. Daher ist es notwendig, dem Ausgabestream cout ein std:: voranzustellen, also std::cout. Alternativ können auch mit using namespace std; (vor der main-Funktion) alle Symbole des Namensraums std in den globalen Namensraum überführt werden. • Benutzt du den g++ Compiler, dann kannst du deine Quelldatei aufgabe 0 1.cpp mit g++ aufgabe 0 1.cpp -o aufgabe 0 1 compilieren und linken. Dabei wird mit der -o Option der Name des erzeugten Programms angegeben. Aufgabe 0.2 Schreibe ein Programm, das mit zwei positiven, ganzzahligen Eingabeparametern a und b aufgerufen wird und den größten gemeinsamen Teiler von a und b ausgibt. Wird das Programm nicht korrekt aufgerufen, so soll es eine Fehlermeldung mit einer Beschreibung des korrekten Aufrufs ausgeben. Benutze dazu Euklids Algorithmus, der sehr einfach rekursiv wie folgt formuliert werden kann: function Euclid(a, b) if b = 0 then return a else return Euclid(b, a mod b) end if end function Versuche auch, den Algorithmus ohne Rekursion zu implementieren. Hinweise: • Verwende für deine main-Funktion die Signatur int main(int argc, char *argv[]). Dabei ist argc die Anzahl der übergebenen Argumente (inklusive Programmname!) und argv ein C++-Array mit argc Elementen. • Die Funktion int atoi(const char *str) wandelt einen C++-String in einen Integer um; dazu muss vorher der Header <cstdlib> inkludiert werden. Aufgabe 0.3 Schreibe ein Programm, das die Zahl π mit Hilfe des Gauss-Legendre Algorithmus auf eine vorgegebene Anzahl von Stellen genau berechnet. Dieser Algorithmus ist gegeben durch folgende Iterationsvorschrift: ( 1 + bi−1 ) √ ( für 1/ 2 √ = ai−1 bi−1 für ai = bi 1 (a 2 i−1 i=0 i>0 ( 1/4 ti−1 − pi−1 (ai−1 − ai )2 ( 1 2pi−1 ti = pi = für i = 0 für i > 0 für i = 0 für i > 0 für i = 0 für i > 0 Dann ergibt sich die Näherung für π nach n Iterationsschritten als π≈ (an + bn )2 . 4tn Die Anzahl der korrekten Stellen in der Näherung verdoppelt sich dabei in jedem Iterationsschritt. Die tatsächliche Genauigkeit der Lösung ist natürlich durch den verwendeten Datentyp (float, double) für Fließkommazahlen beschränkt. Der g++ Compiler unterstützt auch 128-bit Fließkommazahlen mit dem Datentyp long double. Hinweise: • Funktionen für Fließkommazahlen wie sqrt (Quadratwurzel) befinden sich im Header <cmath>. • Man kann die Anzahl der ausgegebenen Stellen für Fließkommazahlen beim C++-Ausgabestream mit setprecision (im Header <iomanip>) angeben, z.B. cout << setprecision(10) << sqrt(2.0) << endl; Aufgabe 0.4 Schreibe ein Programm, das für einen Eingabeparameter n mit Hilfe des Sieb des Eratosthenes die Primzahlen bis n berechnet und ausgibt. Dieser Algorithmus legt zunächst ein boolesches Array isPrime an, das mit den Zahlen von 2, . . . , n indiziert werden kann und mit true initialisiert ist. Das bedeutet, zunächst sind alle Zahlen potentielle Primzahlen. Danach werden die Zahlen von i = 2, . . . , n durchgegangen. Falls isPrime[i] true ist, dann ist i tatsächlich eine Primzahl und für alle Vielfachen j > i von i wird isPrime[j] auf false gesetzt. Hinweise: • Lege das dynamische Array isPrime mit Hilfe des new-Operators an, z.B. legt int *a = new int[10]; ein Array a mit Indexbereich [0..9] an. • Denke daran, den dynamisch allozierten Speicher auch wieder freizugeben! Benutze dazu die Array-Variante des delete-Operators, z.B. delete [] a;. Aufgabe 0.5 Implementiere eine Klasse SinglyLinkedList, welche einfach verkettete Listen für doubleZahlen repräsentiert. Diese Klasse soll eine Iterator-Klasse SLLIterator verwenden; Instanzen dieser Klasse verweisen entweder auf ein Element der Liste oder stellen einen Null-Iterator dar, der auf kein Element verweist. Die Schnittstellen der beiden Klassen sehen wie folgt aus: class SLLIterator { public: SLLIterator(); // Erzeugt Iterator, der auf kein Listenlement zeigt. SLLIterator(const SLLIterator &it); // Erzeugt Kopie von Iterator it. // Gibt true zurück, wenn der Iterator auf ein Listenelement zeigt. bool valid() const; bool operator==(const SLLIterator it) const; // Test auf Gleichheit. bool operator!=(const SLLIterator it) const; // Test auf Ungleichheit. SLLIterator &operator=(const SLLIterator &it); // Zuweisungsoperator. // Gibt eine Referenz auf den Inhalt des Elementes zurück, auf das der Iterator zeigt. // Vorbedingung: Iterator zeigt auf ein Listenlement. double &operator*() const; // Präfix-Inkrementoperator: // Vorbedingung: Iterator zeigt auf ein Listenlement. // Verschiebt den Iterator zum nachfolgenden Element und gibt ihn zurück. SLLIterator &operator++(); // Postfix-Inkrementoperator: // Vorbedingung: Iterator zeigt auf ein Listenlement. // Gibt einen Kopie des Iterators zurück und verschiebt ihn danach zum nachfolgenden // Element. Der int-Parameter ist nur ein Dummy, um die Postfix-Notation zu // kennzeichnen; er wird nie benutzt. SLLIterator operator++(int); }; class SinglyLinkedList { public: SinglyLinkedList(); // Erzeugt eine leere Liste. SinglyLinkedList(const SinglyLinkedList &L); // Erzeugt eine Kopie von Liste L. bool empty() const; // Gibt wahr zurück, falls die Liste leere ist. SLLIterator begin () const; // Gibt Iterator auf erstes Element zurück. SLLIterator rbegin() const; // Gibt Iterator auf letztes Element zurück. void clear(); // Löscht alle Elemente in der Liste. SinglyLinkedList &operator=(const SinglyLinkedList &L); // Zuweisungsoperator. SLLIterator pushFront(double x); // Fügt x am Anfang der Liste hinzu. SLLIterator pushBack (double x); // Fügt x am Ende der Liste hinzu. // Löscht das erste Element der Liste und gibt dessen Inhalt zurück. // Vorbedingung: Die Liste ist nicht leer. double popFront(); }; // Ausgabeoperator für Listen. std::ostream &operator<<(std::ostream &os, const SinglyLinkedList &L); Schreibe die Deklaration der beiden Klassen in eine Datei SinglyLinkedList.h und die Implementierung in SinglyLinkedList.cpp. Schreibe ein Hauptprogramm, das für einen Eingabeparameter n eine Liste mit n zufälligen double-Werten erzeugt und mit Hilfe des Ausgabeoperators ausgibt. Benutze dabei ein Makefile, das die .cpp-Dateien einzeln in Objektdateien compiliert und diese dann linked. Hinweise: • Die Funktion rand() in <cstdlib> gibt eine ganzzahlige Zufallszahl aus dem Bereich [0..RAND MAX] zurück. • Verwende das Compilerflag -Wall, um alle Warnungen auszugeben; dein Quellcode sollte ohne Warnungen compilieren. • Achte in deinem Makefile darauf, dass die Aktionen, die für ein Ziel ausgeführt werden sollen (z.B. Aufruf des Compilers), durch Tabs (und nicht Leerzeichen!) eingerückt sein müssen. • Funktionen, deren Implementierung nur sehr wenig Code erfordert (z.B. Einzeiler), können ihr auch direkt in der Klassendeklaration angeben werden; komplexere Implementierungen sollten aber auf jeden Fall in die .cpp-Datei ausgelagert werden. • Du wirst den Speicher für Listenelemente beim Hinzufügen von Elementen dynamisch mit new allozieren müssen. Denke daran, diesen auch beim Entfernen von Elementen und im Destruktor wieder mit delete freizugeben. Vill Glèck! – das DAP2-Team