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