Informatik 1 (251-0832-00) D-MAVT F2011 Rekursion, Signaturen Yves Brise 20110420 - Übungsstunde 8 Nachbesprechung Blatt 5 & 6 Blatt 5 • Sortieren • Pseudocode • Vektoren • Verschlüsselung Blatt 6 • Zeiger, structs • Listen • Niederschlagsanalyse (Vorsicht int-Division) Yves Brise 20110420 - Übungsstunde 8 Initialisierung Dynamisches Feld Ein dynamisches Feld kann nicht mit geschwungenen Klammern initialisiert werden! Verwende direkten Elementzugriff, oder Konstruktoren. struct foo { int a, b; }; int main() { foo* test = new foo[3] = {{0,1},{0,2},{0,3}}; delete[] test; return 0; } test.cpp:6: error: expected primary-expression before ‘{’ token Yves Brise 20110420 - Übungsstunde 8 Initialisierung Dynamisches Feld struct foo { int a, b; }; int main() { foo* test = new foo[3]; test[0] = {0,1}; test[1] = {0,2}; test[2] = {0,3}; delete[] test; return 0; } test.cpp:7: error: expected primary-expression before ‘{’ token Yves Brise 20110420 - Übungsstunde 8 Initialisierung Dynamisches Feld Direkter Elementzugriff struct foo { int a, b; }; int main() { foo* test = new foo[3]; test[0].a = 0; test[0].b = 1; test[1].a = 0; test[1].b = 2; test[2].a = 0; test[2].b = 3; delete[] test; return 0; } OK! Aber recht umständlich... Yves Brise 20110420 - Übungsstunde 8 Initialisierung Dynamisches Feld Direkter Elementzugriff struct foo { foo () {this->a = 0; this->b = 0;} foo (int a, int b) { this->a = a; this->b = b; } int a, b; }; int main() { foo* test = new foo[3]; Sehr komfortabel, vor allem wenn die Datentypen komplizierter werden. test[0] = foo(0,1); test[1] = foo(0,2); test[2] = foo(0,3); delete[] test; return 0; } Yves Brise 20110420 - Übungsstunde 8 Blatt 8, Aufgabe 1 Fibonacci Zahlen Teilaufgabe a) Definition Berechnung 0, 1, fib(n) := fib(n − 1) + fib(n − 2), if n = 0, if n = 1, if n ≥ 2 fib(2) = fib(1) + fib(0) = 1+0 = 1 Teilaufgabe b) Schaut euch die Auwertung aus a) einmal genau an... Yves Brise 20110420 - Übungsstunde 8 Blatt 8, Aufgabe 2 Labyrinth Finden Sie einen Weg durch das Labyrith! Darstellung als char Feld, wobei X für Hindernis, S für Startpunkt, Z für Ziel und die Zeichen >,<,^,v für rechts, links, oben und unten steht. Anfangssituation Weg gefunden! Yves Brise 20110420 - Übungsstunde 8 Blatt 8, Aufgabe 2 Das Framework Sie müssen nur die Datei labyrinth.cpp bearbeiten. Zusätzlich enthält das Verzeichnis die Datei labyrinth.map, die Karte des Labyrinths codiert. int loadLabyrinth(char *filename); void printLabyrinth(); int main(int argc, char* argv[]) { if (argc == 2) { if (!loadLabyrinth(argv[1])) { printLabyrinth(); resetLabyrinth(); } } return 0; } Yves Brise Labyrith laden und ausgeben mit Kommandozeilenparameter. Alterntiv kann auch “labyrinth.map” als Argument an loadLabyrinth übergeben werden. 20110420 - Übungsstunde 8 Blatt 8, Aufgabe 2 Die Datenstruktur struct tLab { // die wirkliche groesse des Labyrinths // diese Zahlen werden aus dem Labyrinth-File eingelesen int size_x; int size_y; // labyrinthOriginal enthaelt die original zeichen aus dem // labyrinth file. Es wird durch resetLabyrinth() in die // oeffentliche labyrinth-variable kopiert // das labyrinthOriginal hat vertauschte X und Y coordinaten! // dies macht das Einlesen einfacher char labyrinth_original[80][80]; // die "gebrauchs-version" des labyrinths. Wenn // zeichen geschrieben werden, wird diese Version veraendert char labyrinth[80][80]; } lab; Yves Brise 20110420 - Übungsstunde 8 Blatt 8, Aufgabe 2 Zeichen schreiben/lesen char zeichenAnPosition(int x, int y) { return lab.labyrinth[x][y]; } void setzeZeichenAnPosition(char zeichen, int x, int y) { lab.labyrinth[x][y] = zeichen; } if (zeichenAnPosition(x,y) == 'X') {...} setzeZeichenAnPosition('S', x, y); Es wird nicht überprüft, ob die Operationen erlaubt sind. Das ist ihre Aufgabe. Zum Beispiel soll das schreiben nur in leere Felder möglich sein. Yves Brise 20110420 - Übungsstunde 8 Blatt 8, Aufgabe 2 Die rekursive Lösung bool sucheWeg(int sx, int sy, int zx, int zy) { // wir sind auf dem ziel gefunden if (sx == zx && sy == zy) { return true; } } Yves Brise • Wenn das Startfeld nicht • Danach versuchen wir alle 4 Himmelsrichtungen. // versuche Schritt nach rechts setzeZeichenAnPosition('>', sx, sy); if (sucheWeg(sx+1, sy , zx, zy)) return true; // nicht erfolgreich von diesem Feld aus setzeZeichenAnPosition('.', sx, sy); // return false; sind, können wir true zurück geben. leer ist, können wir false zurückgeben. // Diese Feld ist nicht leer. // Hier geht es nicht weiter. if (zeichenAnPosition(sx, sy) != ' ') { return false; } // versuche Schritt nach links // versuche Schritt nach unten // versuche Schritt nach oben • Wenn wir schon am Ziel • Wichtig: Markiere erfolglose Felder mit ’.’ oder ähnlich, damit von da aus nicht mehr gesucht wird. • Auch wichtig: Sie können annehmen, dass das Labyrinth begrenzt ist mir ’X’. 20110420 - Übungsstunde 8 Blatt 8, Aufgabe 3 Telefonbuch int main() { eintrag * buch = 0; ! • Die main Funktion hat nur // Erstelle leeres Telefonbuch // Ermoeglicht dem Benutzer die naechste Aktion zu waehlen int wahl = 1; while (wahl != 0) { // Erleutere die Wahlmoeglichkeit cout << "Was moechten Sie als naechstes tun?" << endl; cout << "0: Programmende" << endl; cout << "1: Eintrag erstellen" << endl; cout << "2: Telefonbuch ausgabe" << endl; cout << "3: Eintrag loeschen" << endl << endl; cin >> wahl; cin.ignore(); // Rufe je nach Wahl die richtige Funktion auf switch (wahl) { case 0: deleteBuch(buch); break; case 1: addEintrag(buch); break; case 2: printBuch(buch); break; case 3: deleteEintrag(buch); break; default: cout << "keine gueltige Angabe" << endl; } } zum Zweck, den Programmablauf zu bestimmen. • Die eigentliche Funktionalität ist in die Fuktionen deleteBuch, addEintrag, printBuch, deleteEintrag ausgelagert } return 0; Yves Brise 20110420 - Übungsstunde 8 Blatt 8, Aufgabe 3 void void void void • Man beachte die Signatur addEintrag(eintrag * &node); deleteBuch(eintrag * &node); printBuch(eintrag * node); deleteEintrag(eintrag * &node); mit Parameter Typ eintrag * & • Traversieren der Liste notwendig void deleteBuch(eintrag * &node) { eintrag * temp; // Gehe durch die verkettete Liste und loesche jeden Eintrag while (node != 0) { temp = node; node = node->next; delete temp; } } Yves Brise • Nicht vergessen, den Speicherplatz wieder freizugeben. 20110420 - Übungsstunde 8 Blatt 7, Aufgabe 4 Turtle Graphics Siehe Demo! Yves Brise 20110420 - Übungsstunde 8 Blatt 7, Aufgabe 5 Raytracing Bildebene und Objekte werden in einer 3D Darstellung modelliert.Vom Augpunkt werden dann die Schnittpunkte vieler Halbgeraden berechnet. Raytracing findet prominente Anwendung in der 3D Computergraphik. Yves Brise 20110420 - Übungsstunde 8 Blatt 7, Aufgabe 5 Das Framework Sie müssen nur die Datei main.cpp aus dem Ordner framework bearbeiten. Die Datei framework7.zip auf der Kurshomepage enthält alles, was sie brauchen.Wenn Sie alles richtig gemacht haben, dann schreibt das Programm eine Datei out.ppm. Dies ist eine Bilddatei. Sie können die .ppm Dateien mit Gimp (Windows, Linux) oder ToyViewer (OS X) anschauen. Datenstruktur für Vektoren, Farben und Strahlen typedef float[3] Vector3f; typedef Vector3f color_rgb; Yves Brise struct ray { Vector3f p; // Punkt Vector3f d; // Richtung }; 20110420 - Übungsstunde 8 Blatt 7, Aufgabe 5 /****************************************************************************** * Funktion write_picture(color_rgb( ) * * Schreibt ein Bild(Array aus Farben) in out.ppm im PPM-Format * * Eingabeargumente: Array pic, das aus Elementen mit 3 Farben (RGB) besteht * * Rueckgabewerte: kein3e * ******************************************************************************/ void write_picture(color_rgb * pic); pic ist ein Zeiger auf ein Feld, welches das Bild darstellt (kann man eindimensional oder mehrdimensional machen). /****************************************************************************** * Funktion generate_ray() * * Generiert die den Strahl fuer den Pixel (x,y) * * Eingabeargumente: Koordinaten x, y. Kameramodel model * * Rueckgabewerte: Strahl welcher durch den Pixel verlaeuft * ******************************************************************************/ ray generate_ray(int x, int y, camera_model model); Rückgabewert ist eine Instanz von struct ray. Yves Brise 20110420 - Übungsstunde 8 Blatt 7, Aufgabe 5 a) Zuerst ausprobieren ein Bild zu schreiben. int main() { Vector3f pos(-4,0,1); Vector3f lookat(0,0,1); Vector3f up(0,0,1); camera_model camera; camera = init_camera(pos, lookat, up,1); color_rgb color_rgb color_rgb color_rgb color_rgb color_rgb rot(1,0,0); gruen(0,1,0); blau(0,0,1); gelb(1,1,0); schwarz(0,0,0); weiss(1,1,1); Grösse des Bildes: const int res_x = 256; const int res_y = 256; color_rgb *pic = ...; // Allen Bildpunkten blau zuweisen write_picture(pic); return 0; } Yves Brise 20110420 - Übungsstunde 8 Blatt 7, Aufgabe 5 b) Strahl mit Ebene schneiden. Ebene soll durch Funktion fixiert sein (xy-Ebene). float int_plane(ray strahl, Vector3f &normale) { // Position der Flaeche (s. Aufgabenstellung Vector3f plane_n(0,0,1); float s=0; // Nomale ist immer die Flaechennormale normale = plane_n; // Formel (s. Aufgabenstellung) if (plane_n.dot(strahl.d) == 0) { // Schnittpunkt im unendlichen return -1e30; } else { return (s-plane_n.dot(strahl.p))/plane_n.dot(strahl.d); } } Einzige benötigte Operation ist Vektorprodukt: Yves Brise Vector3f a(1,1,1); Vector3f b(1,0,0); a.dot(b); // gibt float zurück b.dot(a); // ist äquivalent 20110420 - Übungsstunde 8 Blatt 7, Aufgabe 5 c) float int_sphere(ray strahl, Vector3f &normale) { // Position der Kugel Vector3f c(0,0,0.7); // Radius float r=1.2; // Vektor vom Strahlursprung zum Zentrum der Kugel Vector3f dst = strahl.p - c; // Loesen der quadratischen Gleichung // |t*d + p - c|^2 - r^2 = 0 // = |d|^2*t^2 +2d*(p-c)*t + |p-c|^2 - r^2 = 0 // a1*t*t+b1*t+c1 // a1 = d*d = 1 // b1 = 2*d*dst // c1 = dst*dst -r*r float b1 = 2*dst.dot(strahl.d); float c1 = dst.dot(dst)-r*r; float d1 = b1*b1-4*c1; float t = d1 > 0 ? (- b1 - sqrt(d1))/2 : -1.1; Strahl mit Kugel schneiden. Kugel soll durch Funktion fixiert sein (c=(0,0,0), r=1.2). Normale muss berechnet werden! // Normale auf der Kugel ist der // Schnittpunkt - Zentrum normale = strahl.p + t*strahl.d - c; normale.normalize(); return t; } Yves Brise 20110420 - Übungsstunde 8 Blatt 7, Aufgabe 5 d) for(int y = 0; y < res_y; y++) for(int x = 0; x < res_x; x++) { strahl = generate_ray(x,y,camera); Endresultat: farbe = schwarz; // Hintergrund ist sehr weit weg t_closest = 1e30; t = int_sphere(strahl,normale); if (t>0.0 && t< t_closest) { // Die Kugel wird getroffen. // Die Farbe wird rot, die mit der // Beleuchtung skaliert wird. farbe = rot;//*(-1*strahl.d.dot(normale)); // AUFGABE C) farbe = rot*(-1*strahl.d.dot(normale)); // Entfernung wird gespeichert t_closest = t; } t = int_plane(strahl,normale); if (t>0.00 && t< t_closest) { // Die Flaeche ist am naechsten. Farbe wird gelb farbe = gelb; // AUFGABE C) farbe = gelb*(-1*strahl.d.dot(normale)); t_closest = t; } // Dem Pixel wird die Farbe zugewiesen pic[y*res_x+x] = farbe; } } Yves Brise 20110420 - Übungsstunde 8 Rekursion • Funktionen rufen sich selbst oder wechselseitig auf. • Rekursion ist gleich mächtig wie Iteration. • Abbruchbedingung wichtig unsigned int a(unsigned int n, unsigned int m){ if (n == 0) return m + 1; if (m == 0) return a(n,1); return a(n, a(n + 1, m)); } Beispiel: Ackermann Funktion a(0, m) := m+1 a(n + 1, 0) := a(n, 1) a(n + 1, m + 1) := a(n, a(n + 1, m)) Yves Brise Wächst extrem schnell: a(3,13) wird auf heutigen Rechnern schon sehr lange dauern. a(4,4) grösser als die Anzahl der Atome im Uniersum. 20110420 - Übungsstunde 8 Binomialkoeffizient Implementieren Sie die Funktion binom! Überlegen Sie sich mögliche Probleme und die Effizienz Ihrer Variante. A B � n� k := n! k!(n−k)! 0, � n� 1, � k := � n−1 k C + 0, � n� 1,� k := � n n−1 k k−1 �n−1� k−1 , if n < k, if n = k or k = 0, if n > k, k > 0 if n < k, if n ≥ k, k = 0, if n ≥ k, k > 0 unsigned int binom(unsigned int n, unsigned int k); Yves Brise 20110420 - Übungsstunde 8 Binomialkoeffizient Variante A unsigned int fac(unsigned int n) { if (n == 0) return 1; return n * fac(n-1); } unsigned int binom(unsigned int n, unsigned int k) { return fac(n) / fac(k) / fac(n-k); } Nachteil: Die Zahlen, die als Zwischenresultate berechnet werden, sind viel grösser als das Resultat. Frühzeitiger integer-Überlauf. Yves Brise 20110420 - Übungsstunde 8 Binomialkoeffizient Variante B unsigned int binom(unsigned int n, unsigned int k) { if (n < k) return 0; if (n == k || k == 0) return 1; return binom(n-1, k) + binom(n-1, k-1); } Nachteil: Viele Werte werden mehrfach berechnet. Diese Rekursion ist sehr ineffizient. Verbesserung: Speichern Sie die schon berechneten Werte (Pascalsches Dreieck). Sogenanntes Dynamisches Programmieren. Dies braucht jedoch mehr Speicher... Yves Brise 20110420 - Übungsstunde 8 Binomialkoeffizient Variante C unsigned int binom(unsigned int n, unsigned int k) { if (n < k) return 0; if (k == 0) return 1; return n * binom(n-1, k-1) / k; } Vorsicht: Wir müssen zuerst multiplizieren und dann erst durch k dividieren. Ansonsten ist das Resultat sehr wahrscheinlich falsch wegen der ganzzahligen Division. Yves Brise 20110420 - Übungsstunde 8 Prüfungsaufgabe Die Funktion kum3 soll die kumulative Summe der Argumente berechnen und zurück geben. Das erste Argument bleibt unverändert, das zweite Argument soll die Summe von a und b sein. Das dritte Argument c soll zum Ende die Summe aller Argumente zu Beginn sein. 1. Eignet sich eine gegebene Signatur für die Implementierung? 2. Falls Ja, dann implementieren Sie die entsprechende Funktion. void kum3(int * const a, int * const b, int * const c); void kum3(int a, int b, int c); void kum3(const int *a, const int *b, const int *c); void kum3(int &a, int &b, int &c); Yves Brise 20110420 - Übungsstunde 8 Prüfungsaufgabe void kum3(int * const a, int * const b, int * const c); void kum3(int a, int b, int c); void kum3(const int *a, const int *b, const int *c); void kum3(int &a, int &b, int &c); Implementierung: void kum3(int * const a, int * const b, int * const c){ *b += *a; *c += *b; } void kum3(int &a, int &b, int &c) { b += a; c += b; } Yves Brise 20110420 - Übungsstunde 8