Algorithmen und Datenstrukturen (Beispiele in C++) Rekursion
Transcription
Algorithmen und Datenstrukturen (Beispiele in C++) Rekursion
FB Informatik Prof. Dr. R.Nitsch Algorithmen und Datenstrukturen (Beispiele in C++) Rekursion Reiner Nitsch 8471 [email protected] Rekursion /108/ FB Informatik Prof. Dr. R.Nitsch Rekursive Funktionen rufen sich wiederholt selbst auf tun dies, um jeweils ein kleineres Problem zu lösen enden erst dann ohne erneuten Selbstaufruf, wenn das Problem so klein ist, dass es gelöst werden kann ( base case). Beispiel: Dreieckszahlen d0 0 d1 0 1 d2 0 1 2 d3 0 1 2 3 dn n k d0 x d1 x x x d2 x x x x x x d3 x x x x x x x x x x d4 x x x x x x x x x x x x x x x d5 k 0 n n 1 2 Iterative Lösung: 07.06.2011 int triangleNumber ( int n ) { if ( n<0) throw std::invalid_argument("ERROR in triangleNumber"); int dn = 0; for ( int k=0; k<=n; ++k ) dn += k; assert( dn == n*(n+1)/2 ); return dn ; } 2 Rekursive Berechnung der Dreieckszahlen Rekursive Definition der Dreickzahlen: x x x x x dn = n + Rest = n + dn-1 x x x x x x x x x x FB Informatik Prof. Dr. R.Nitsch int triangleNumber( int n ) { assert( n>=0 ); if ( n==0 ) return 0; //base case: terminiert Rekursion else return n + triangleNumber( n-1 ); } d5 = 5 + Rest 5 + d4 Aufruf in main: Rekursion wird durch das Konzept der lokalen cout << triangleNumber(3); Objekte ermöglicht: Jeder Aufruf erzeugt einen neuen Satz aller lokalen Objekte auf dem Stack! n Rückgabewert 1 9 3 2 3 3+3 = 6 2 2+1 = 3 1 0+1 = 1 4 8 7 6 0 5 07.06.2011 0 Rücksprungadresse n=3 Rückgabewert Rücksprungadresse n=2 Rückgabewert Rücksprungadresse n=1 Rückgabewert Rücksprungadresse n=0 Rückgabewert Vorsicht bei globalen Variablen in rekursiven Funktionen! 3 Rekursive Berechnung von n! Iterative Definition 1 n! 1 2 ... n für n = 0 für n>0 Rekursive Definition 1 n! n n 1 ! 07.06.2011 für n = 0 für n>0 FB Informatik Prof. Dr. R.Nitsch // Rekursive Lösung long factorial ( int n ) { if ( n<0) throw std::invalid_argument("…"); if ( n == 0 ) return 1; else { long temp = factorial(n-1); if ( LONG_MAX/n > temp ) { /* Overflow-Verarbeitung */ return -1; } else return n * temp; } } 4 Noch ein Beispiel FB Informatik Prof. Dr. R.Nitsch Invertieren einer beliebig langen Zeichenkette void readStringOutputReverseString() { char c; Schnittstelle eines Kellerspeichers (Stack): cin.get(c); void push(T t) // add element t if ( c == '\n' ) T& top() // access last element added return; void pop() // delete last element added else { reverseString (); Iterative Variante: cout << c; } Aufruf-Stack wird durch lokalen Stack ersetzt! return; void readStringOutputReverseString() { } char c; Stack stack; cin.get(c); while( c != '\n' ) stack.push(c); // c auf Stack legen while(!stack.empty()) { cout << stack.top(); // C lesen stack.pop(); // und vom Stack nehmen return; } 07.06.2011 5 Türme von Hanoi - Problembeschreibung 07.06.2011 A B C Subturm Aufgabe: Turm aus 100 Scheiben umsetzen 2 Ablagestellen immer nur eine Scheibe transportieren Es darf keine größere auf einer kleineren Scheibe liegen Turm von A nach C unter Zuhilfenahme von B Algorithmischer Kern 1. Turm außer unterster Scheibe (Subturm) transportieren von A nach B (kleineres Problem) 2. unterste Scheibe transportieren von A nach C (base case) 3. Subturm von B nach C transportieren (kleineres Problem) FB Informatik Prof. Dr. R.Nitsch 7 Pseudocode-Algorithmus Modul move( n, quelle, senke, arbBer ) // Bewegt einen Turm der Höhe n von Quelle zur // Senke unter Verwendung des Arbeitsbereichs falls n=1 dann bewege 1 Scheibe von Quelle zur Senke sonst move( n-1, Quelle, arbBer, Senke ) bewege 1 Scheibe von Quelle zur Senke move( n-1, arbBer, Senke, Quelle ) 07.06.2011 FB Informatik Prof. Dr. R.Nitsch Schlüsselwort "Modul" kennzeichnet den Algorithmus als Unterprogramm. 8 Türme von Hanoi - Implementation in C++ FB Informatik Prof. Dr. R.Nitsch void move( int n, char source, char dest, char wspace ) { static int width(-5); //statische Variable mit Gültigkeitsbereich Programm width += 5; cout << endl; cout << string( width, ' ' ) << "Mein Auftrag ist: " << n << " Scheibe(n) " << "von " << source << " nach " << dest << endl; if (n==1) // Base Case (realer Transport) cout << string( width, ' ' ) << "Ächz…Schwitz: " //Anwendung << " Scheibe Nr. " << n void main(){ << "von " << source move( 3,'A','C','B' ); << " nach " << dest << endl; cout << endl; else { // Unteraufträge (virtueller Transport) } // Subturm zur Hilfsposition move( n-1, source, wspace, dest ); //unterste Scheibe zur Zielposition 'dest' (realer Transport) cout << string( width,' ') << "Ächz…Schwitz: " << Scheibe Nr. " << n << " von " << source << " nach " << dest << endl; // Subturm von Hilfsposition 'wspace' zur Zielposition 'dest' move( n-1, wspace, dest, source ); width -=5; } //END else } 07.06.2011 9 Türme von Hanoi - Ausgabe FB Informatik Prof. Dr. R.Nitsch A Mein Auftrag ist: 3 Scheibe(n) von A nach C Mein Auftrag ist: 2 Scheibe(n) von A nach B Mein Auftrag ist: 1 Scheibe(n) von A nach Ächz … Schwitz: Scheibe 1 von A nach C Ich transportiere: Scheibe 2 von A nach B Mein Auftrag ist: 1 Scheibe(n) von C nach Ächz … Schwitz : Scheibe 1 von C nach B Ächz … Schwitz : Scheibe 3 von A nach C Mein Auftrag ist: 2 Scheibe(n) von B nach C Mein Auftrag ist: 1 Scheibe(n) von B nach Ächz … Schwitz: Scheibe 1 von B nach A Ächz … Schwitz: Scheibe 2 von B nach C Mein Auftrag ist: 1 Scheibe(n) von A nach Ächz … Schwitz: Scheibe 1 von A nach C Press any key to continue 07.06.2011 B C C B A C 10 Türme von Hanoi - Komplexität FB Informatik Prof. Dr. R.Nitsch Turm hat laut Überlieferung insgesamt n Scheiben 1. Mönch Scheibe n 1 mal (=20) transportieren 2. Mönch Scheibe n-1 2 mal (=21) transportieren 3. Mönch Scheibe n-2 4 mal (=22) transportieren 4. Mönch Scheibe n-3 8 mal (=23) transportieren i. Mönch Scheibe n+1-i 2i-1 mal transportieren n. Mönch Scheibe 1 2n-1 mal transportieren Summe: T n 2 i 1 2n 1 Beispiel: n=5 1 10 100 1000 10000 11111 20 21 22 23 24 25-1 O(2n) Exponentielles Wachstum! i 1 Laut Überlieferung geht die Welt unter, wenn alle 100 Scheiben transportiert sind. Wann wird das sein? Annahme: Scheiben werden im Sekundentakt transportiert In wieviel Jahren ist dann der Weltuntergang? Ausrechnen: 07.06.2011 11 Backtracking - Wie kommt die Maus zum Käse? FB Informatik Prof. Dr. R.Nitsch wichtiges Algorithmenmuster für Such- und Optimierungsprobleme realisiert allgemeine systematische Suchtechnik Alle Lösungen eines Lösungsraums werden gefunden. 1 1 M 2 2 3 (1,1) (1,2) (2,2) Abb rechts: Alle möglichen (nichtzyklischen) Wege vom Startpunkt (1,1) in Form eines Baumes. Seine Wurzel ist der Startpunkt. (2,1) (1,3) (3,2) (3,3) (2,3) (3,1) 3 K Für jedes Feld ist entscheidbar, ob es eine Lösung ("Käse gefunden") oder keine Lösung ("Käse nicht gefunden") ist. Von jedem Feld aus werden alle noch nicht betretenen direkten Nachbarfelder aufgesucht. Wenn alle Nachbarfelder besucht wurden (oder keine existieren) wird die Suche beim Vorgängerfeld fortgesetzt (Backtracking). Jedes Feld (bis auf das Startfeld) hat ein Vorgängerfeld von dem aus es betreten wurde. Merkt sich dieses die Maus, kann sie den Wegebaum (s.o.) konstruieren und damit den Weg von jedem Feld zum Start rekonstruieren. Merkt sich die Maus für jedes Feld außerdem die Weglänge, kann sie für jedes Feld auch den kürzesten Weg von diesem zum Start ermitteln. Das wiederholte Aufsuchen von Feldern muss verhindert werden. 07.06.2011 12 Backtracking - Prinzip FB Informatik Prof. Dr. R.Nitsch Voraussetzungen für Backtracking KM ist die Menge der Knoten K. K0 ist der Anfangsknoten. Für jeden Knoten Ki kann die Menge der direkten Folgeknoten bestimmt werden. Für jeden Knoten ist entscheidbar, ob er eine Lösung ist oder nicht. Backtracking-Muster in Pseudokode: procedure backtrack(Knoten k) begin … Terminierung setzt voraus: • endlichen Lösungsraum • bereits getestete Konfigurationen werden nicht erneut betreten (Markierung) if ( k ist Lösung ) then ( gib k aus ) else for each ( direkten Folgeknoten k' von k ) do backtrack( k' ) end do end if end 07.06.2011 13 Backtracking - Fallstudie "Labyrinth" FB Informatik Prof. Dr. R.Nitsch Aufgabe: Von beliebiger (freier) Position im Labyrinth aus alle Ausgänge finden. Diskussion: Wie würde ein Blinder die Aufgabe lösen? Modellierung des Labyrinths z.B. durch eine 2dimensionale Datenstruktur aus char-Elementen (x=0,y=0) Zur Kodierung und Verwaltung der Konfigurationen soll der hier beschriebene ADT Labyrinth verwendet werden! Labyrinth private Attribute … private Methoden … + get(int x, int y):char + mark(int x, int y, char label):void + toStream(ostream&):void + insert(string zeile):void Klassendiagramm 07.06.2011 y x rrrrrrrrrrrr rwwwwww wwwr rw wwww wwwr rw wwwwr rw wwwwwwwwr rwwwwwwwwwwr rrrrrrrrrrrr Rückgabewerte von get(x,y) 'w' für Wand 'r' für Rand ' ' für 'frei' 'A' für Ausgang '▓' für Markierung (=char(178)) '\0' für Zugriff ausserhalb des definierten Bereichs (Sentinel) 14 Backtracking - Fallstudie "Labyrinth" #include "Labyrinth.h" #include <iostream> #include <cassert> using namespace std; void oneStep(Labyrinth& lab, int x, int y); FB Informatik Prof. Dr. R.Nitsch Labyrinth private Attribute … private Methoden … + get(int x, int y):char + mark(int x, int y, char label):void + toStream(ostream&):void + insert(string zeile):void //Globales Objekt void main() { Labyrinth lab; lab.insert("rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr"); lab.insert("rwwwwwwwwwwwwwwwwwwwww wwwwwwwwwwwwwwww wwwr"); lab.insert("rw www w wwwwwwwwwwwwwwww w wr"); lab.insert("rw wwwwwwww wwww ww wwww w w wr"); lab.insert("rw wwwwwwwwwwwwwwwwwwwwww www wwww ww w w wr"); lab.insert("rw ww wwww www wwww ww w w wr"); lab.insert("rwwwwww ww ww w wwwwwww wwww ww w w wr"); lab.insert("rww wwwwww wwww www ww wr"); lab.insert("r wwwwwwwwwwwwwwwwwwww wwwwwwwww www ww wr"); lab.insert("rww www ww www ww wr"); lab.insert("rww wwwwwwwwwwwwwwwwwwwwww www wr"); lab.insert("rwwwwww ww wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwr"); lab.insert("rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr"); oneStep(lab,22,1); // Übergeben wird der Ausgangspunkt der Suche } 07.06.2011 15 Backtracking - Fallstudie "Labyrinth" FB Informatik Prof. Dr. R.Nitsch void oneStep(Labyrinth& lab, int x, int y){ enum Label { ERROR='\0',FREE=' ',BORDER='r',EXIT='A',VISITED=char(178) }; char label = get(x,y); if ( label==ERROR ){ // Test der Vorbedingung cout << "Zugriffsfehler!" << endl; //Grenzüberschreitung assert(false); Labyrinth } private Attribute … else if ( label==BORDER ) {//Entscheiden ob Lösung vorliegt private Methoden … lab.mark( x, y, EXIT ); //Ausgang markieren //Labyrinth auf Bildschirm ausgeben + get(int x, int y):char ++exitNumber; + mark(int x, int y, Label l):void //'A' ist Endstation -> zurück lab.toStream(cout); + toStream(ostream&):void return; + insert(string zeile):void } // Feld als 'betreten' markieren; else if ( label==FREE ) { // verhindert erneutes betreten lab.mark(x,y,VISITED); lab.toStream(cout); // Jetzt alle Folgekonfigurationen durchprobieren oneStep( x+1, y ); // nach rechts weitersuchen (x=0,y=0) x oneStep( x , y+1 ); // nach unten weitersuchen y oneStep( x-1, y ); // nach links weitersuchen rrrr oneStep( x , y-1 ); // nach oben weitersuchen rwww } else return; // hier ist eine Wand oder markiert d.h. zurück rw w // und in vorheriger Konfiguration weiter suchen 07.06.2011 16 Labyrinth - Bildschirmausgabe rrrrrrrrrrrrrrrrrrrrrrArrrrrrrrrrrrrrrrArrrr rwwwwwwwwwwwwwwwwwwwww▓wwwwwwwwwwwwwwww▓wwwr rw▓▓▓▓▓▓▓▓▓▓www▓▓▓▓▓▓w▓wwwwwwwwwwwwwwww▓w wr rw▓wwwwwwww▓▓▓▓▓wwww▓▓▓ww▓▓▓▓▓wwww▓▓▓▓w▓w wr rw▓wwwwwwwwwwwwwwwwwwwwww▓www▓wwww▓ww▓w▓w wr rw▓▓▓▓▓▓▓▓▓ww▓▓▓▓wwww▓▓▓▓▓www▓wwww▓ww▓w▓w wr rwwwwww▓ww▓▓▓▓ww▓w▓▓▓▓wwwwwww▓wwww▓ww▓w▓w wr rwwwwww▓wwwwwwww▓▓▓wwwwwwwwww▓▓▓▓▓▓ww▓w▓w wr rww▓▓▓▓▓wwwwwwwwwwwww▓▓▓▓▓▓wwwww wwww▓w▓▓▓wr rww▓www▓wwwwwwwwww▓▓▓▓wwww▓ww▓ww wwww▓www▓wr rww▓www▓ww▓▓▓▓▓www▓wwwwwww▓ww▓ww wwww▓www▓wr rww▓www▓ww▓www▓wwwwwwwwwww▓ww▓wwwwwww▓www▓wr rww▓www▓▓▓▓www▓ww▓w▓▓▓▓▓▓▓▓ww▓▓▓▓▓▓▓▓▓www▓Ar rww▓wwwwwwwwww▓ww▓w▓wwwwwwwwwwwwwwwww▓www▓wr rww▓wwwwwwwwww▓ww▓▓▓ww wwww▓▓▓▓▓▓▓▓www▓wr rww▓▓▓▓▓▓▓▓▓ww▓wwww▓wwwww wwww▓wwwwwwwwww▓wr rww▓wwwwwwwwww▓▓▓▓▓▓wwww▓▓▓▓▓▓▓www▓▓▓▓▓ww▓wr A▓▓▓wwwwwwwwwwwwwwwwwwww▓wwwwwwwww▓www▓ww▓wr rww▓wwww▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ww▓www▓ww▓wr rww▓wwww▓wwwwwwwwwwwwwwwwwwwwww▓▓▓▓www▓▓▓▓wr rwwwwwww▓wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwr rrrrrrrrArrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr 5 Ausgaenge gefunden. Press any key to continue FB Informatik Prof. Dr. R.Nitsch Die Vollständigkeit der Suche ist sichergestellt durch Suche in alle Richtungen bei jedem freien Feld in oneStep(x,y) Gedächtnis des Backtracking Algorithmus in Form des Rücksprungs an die einen Aufruf zurück liegende Aufrufadresse. Demo_v2 07.06.2011 17 Backtracking - Zusammenfassung FB Informatik Prof. Dr. R.Nitsch Der Aufwand (Laufzeit / Speicherbedarf ) hängt von der Größe des Lösungsraums ab ist oft exponentiell vom Problemumfang abhängig Beispiel: "Maus&Käse" mxm Labyrinth, zu jedem Feld gibt es 3 Folgekonfigurationen insgesamt 3n2 Konfigurationen Aufwandsbegrenzung durch Varianten des Backtracking: Abbruch nach der ersten gefundenen Lösung Sackgassen im Voraus erkennen und nicht betreten (Branch-and-Bound) Maximale Rekursionstiefe vorgeben (z.B. Schachprogramme) Zusammenfassung: Das Backtracking (Rückverfolgung) Verfahren ist eine Problemlösungsmethode der Algorithmik wird angewendet, wenn analytische Methoden versagen ist eine Trial&Error Methode, bei der alle Alternativen durchprobiert werden und nur weiterverfolgt werden, wenn sie (noch) erfolgreich sein können. ist meistens rekursiv implementiert geeignet für Spielprogramme, Planungs- und Optimierungsprobleme 07.06.2011 18 Rekursion oder Iteration? Wer die Wahl hat, hat die Qual! FB Informatik Prof. Dr. R.Nitsch Wann verwendet man Iteration? Wenn der Standard-Algorithmus als offensichtliche Lösung iterativ ist. Beispiele: xn, n!,… Zum Durchlaufen von Tabellenwerten Beispiele: Matrixoperationen, Zählschleifen (Zahlenschloß) Wann verwendet man Rekursion? Wenn das zugrunde liegende Problem oder die verwendete Datenstruktur rekursiv definiert sind. Beispiele: Fibonacci Zahlen, Backtracking Algorithmen, Binärer Baum, Verkettete Listen Hinweis: Rekursive Datenstruktur = Datenstruktur, die einen Zeiger auf eine Datenstruktur desselben Typs enthält. 07.06.2011 19 FB Informatik Prof. Dr. R.Nitsch So, das war´s erst mal! 07.06.2011 20