Lösung Serie 2

Werbung
U NIVERSIT ÄT B ERN
I NFORMATIK
VORLESUNG
EI
T YP
UL
B LATT
2 (1/5)
AUSGABE
HS 08
Lösungsvorschlag Serie 2 – Rekursion
1. Algorithmen-Paradigmen
Es gibt verschiedene Algorithmen-Paradigmen, also grundsätzliche Arten, wie man
einen Algorithmus formulieren kann.
Im funktionalen Paradigma werden Berechnungen durch Funktionen formuliert. Damit lassen sich natürlich v.a. mathematische Funktionen sehr einfach ausdrücken.
Es gibt Programmiersprachen, in welchen sich Algorithmen dieses Paradigmas unverändert implementieren lassen (z.B. Haskell). Die Fibonacci-Funktion wird funktional wie folgt definiert:
f ib(0) = f ib(1) = 1
f ib(n) = f ib(n − 1) + f ib(n − 2)
Algorithmen gemäss dem imperativen Paradigma bilden die verbreitetste Art, Algorithmen für Computer zu formulieren, da sie auf einem abstrakten Modell eines
üblichen Rechners basieren. Imperative Algorithmen basieren auf den Konzepten Anweisung und Variable. Dabei werden die Befehle in einer genau definierten Reihenfolge abgearbeitet. Das imperative Paradigma wird durch viele Programmiersprachen
wie C, C++, COBOL und auch Java realisiert.
Da Maschinenprogramme für übliche Rechner schlussendlich auch imperativ sind,
folgt daraus, dass z.B. in funktionalen Programmiersprachen implementierte Programme letztlich auch in imperative übersetzt werden.
Es ist deshalb üblich, Algorithmen gemäss dem imperativen Paradigma zu formulieren. Man sollte insbesondere darauf achten, die verschiedenen Konzepte nicht zu
vermischen.
(a) fib(n) als imperativer Algorithmus (rekursiv)
Der Pseudocode für den imperativen Algorithmus könnte folgendermassen aussehen:
int Algorithm fib(n):
// Input : n (nicht negativer Integer-Wert)
// Output: Fibonacci Zahl von n
// Zweck : Rekursive Berechnung der Fibonacci-Zahl
if n < 2 then
return 1
else
return (fib(n-1) + fib(n-2))
(b) Wie sich die Rekursion zur Laufzeit entfaltet, lässt sich Abbildung 1 entnehmen.
Kommentar:
Dieser rekursive Algorithmus zur Berechnung von f ib(n) stellt sich zur Laufzeit
als sehr aufwändig und ineffizient (exponentielle Komplexität) heraus. Immer
wieder werden Ausdrücke erneut berechnet (z. B. fib(3)), deren Resultat eigentlich bereits ausgerechnet wurde. Es werden also sehr viele redundante Berechnungen vorgenommen.
1
1
1
fib(0)
fib(2)
fib(3)
1
1
fib(1)
fib(4)
1
fib(1)
1
2
fib(2)
1
fib(0)
1
fib(5)
1
fib(1)
1
fib(2)
3
1
fib(3)
1
fib(0)
2
1
fib(1)
1
1
fib(1)
1
fib(2)
1
fib(3)
1
fib(0)
2
5
1
fib(1)
1
3
fib(4)
1
fib(1)
1
2
fib(2)
1
fib(0)
1
VORLESUNG
EI
fib(1)
1
2
3
5
8
fib(6)
13
U NIVERSIT ÄT B ERN
I NFORMATIK
T YP
UL
B LATT
2 (2/5)
Abbildung 1: Dynamische Entfaltung der Rekursion fib(6).
AUSGABE
HS 08
U NIVERSIT ÄT B ERN
I NFORMATIK
VORLESUNG
EI
T YP
UL
B LATT
2 (3/5)
AUSGABE
HS 08
(c) Es ist möglich, den Algorithmus so in einen rekursiven Algorithmus umzuformulieren, dass er eine lineare Zeitkomplexität aufweist:
fib(n) als imperativer Algorithmus (endrekursiv)
Der rekursive Algorithmus mit linearer Zeitkomplexität ist endrekursiv.
int Algorithm linrecfib(n, a1, a2):
// Input : n (nicht negativer Integer-Wert)
// Output : Fibonacci Zahl von n
// Zweck : Iterative Berechnung der Fibonacci-Zahl
if n==0
return a1
else
linrecfib(n-1, a2, a1+a2)
Dieser Algorithmus ist gleich effizient wie der nachfolgend vorgestellte iterative
Algorithmus.
fib(n) als imperativer Algorithmus (iterativ)
int Algorithm iterativfib(n):
// Input : n (nicht negativer Integer-Wert)
// Output : Fibonacci Zahl von n
// Zweck : Iterative Berechnung der Fibonacci-Zahl
a = b = result = 1
for (i=2; i<=n; i++)
result = a + b
b = a
a = result
return result
fib(n) explizit berechnet
Bisher mussten wir zur Berechnung einer Fibonacci-Zahl alle vorhergehenden
Zahlen berechnen. Der französische Mathematiker Jacques-Philippe-Marie Binet hat bereits 1843 eine Funktion gefunden, mit welcher sich eine beliebige
Fibonacci-Zahl explizit berechnen lässt:
Ã
Ã
√ !n+1
√ !n+1 
1  1+ 5
1
1− 5

f (n) = √
−√
2
2
5
5
2. (a) Der rekursive Algorithmus zur Berechnung von sum(n) kann wie folgt aussehen:
Algorithm sum(n):
// Input : ganze Zahl n>=1
// Output: Summe von 1 bis n
if n==1 then
return 1
else
return n + sum(n-1)
(b) Die rekursive Implementation von sum(n) unter a) ist eine lineare, direkte Rekursion, die nicht endrekursiv ist (als letzte Operation wird die Addition ausgeführt).
U NIVERSIT ÄT B ERN
I NFORMATIK
VORLESUNG
EI
T YP
UL
B LATT
2 (4/5)
AUSGABE
HS 08
(c) Der iterative Algorithmus zur Berechnung von sum(n) kann wie folgt aussehen:
int Algorithm sum(n):
// Input : ganze Zahl n>=1
// Output: Summe von 1 bis n
sum = 0
for i=1 to n do
sum = sum + i
return sum
3. (a) Die Lösung ist in Abbildung 2 dargestellt.
y
4
3
5
2
6
1
7
8
1
2
0
1
2
3
4
0
0
3
4
5
x
Abbildung 2: Die gefüllte Figur
Nachstehend ist die Reihenfolge der rekursiven Aufrufe aufgeführt. Dabei gibt
die Zahl rechts die momentane Rekursions- bzw. Stacktiefe an.
Flood_Fill(2,3)
Flood_Fill(3,3)
Flood_Fill(4,3)
Flood_Fill(5,3)
Flood_Fill(3,3)
Flood_Fill(4,4)
Flood_Fill(4,2)
Flood_Fill(5,2)
Flood_Fill(3,2)
Flood_Fill(4,3)
Flood_Fill(4,1)
Flood_Fill(5,1)
Flood_Fill(3,1)
Flood_Fill(4,2)
Flood_Fill(4,0)
Flood_Fill(2,3)
Flood_Fill(3,4)
Flood_Fill(3,2)
Flood_Fill(1,3)
Flood_Fill(2,3)
Flood_Fill(0,3)
Flood_Fill(1,4)
Flood_Fill(1,2)
Flood_Fill(2,2)
Flood_Fill(0,2)
Flood_Fill(1,3)
Flood_Fill(1,1)
1
2
3
4
4
4
4
5
5
5
5
6
6
6
6
3
3
3
2
3
3
3
3
4
4
4
4
U NIVERSIT ÄT B ERN
I NFORMATIK
VORLESUNG
EI
Flood_Fill(2,1)
Flood_Fill(3,1)
Flood_Fill(1,1)
Flood_Fill(2,2)
Flood_Fill(2,0)
Flood_Fill(0,1)
Flood_Fill(1,2)
Flood_Fill(1,0)
Flood_Fill(2,4)
Flood_Fill(2,2)
T YP
UL
B LATT
2 (5/5)
AUSGABE
HS 08
5
6
6
6
6
5
5
5
2
2
(b) Die maximale Rekursions- bzw. Stacktiefe beträgt 6.
(c) Beurteilung des Algorithmus: Es handelt sich um einen höchst ineffizienten Algorithmus. Für nur 9 Punkte braucht man schon einen Stack der Tiefe 6 und es
kommt zu 37 Funktionsaufrufe. Der Algorithmus füllt i. Allg. nur dann die ganze
Figur, wenn die Stacktiefe nicht begrenzt wird. Wird diese aber nicht begrenzt,
so kann es zu einem Speicherüberlauf kommen. Es gibt auch Varianten des
Flood-Fill-Algorithmus, die die Stacktiefe begrenzen und im Fall von Löchern
den Algorithmus in diesen Löchern wieder starten. Das Auffinden dieser Löcher
ist aber schwierig.
Herunterladen