PDF Handout 4x4

Werbung
Experiment: Die Türme von Hanoi
8. Rekursion
Mathematische Rekursion, Terminierung, der Aufrufstapel,
Beispiele, Rekursion vs. Iteration
Links
Mitte
Rechts
213
212
Mathematische Rekursion
Rekursion in Java: Genauso!
(
1
falls n ≤ 1
n! =
n · (n − 1)!, andernfalls
Viele mathematische Funktionen sind sehr natürlich rekursiv
definierbar.
Das heisst, die Funktion erscheint in ihrer eigenen Definition.
public static int fakultaet ( int n) {
if (n <= 1) {
return 1;
} else {
return n ∗ fakultaet (n−1);
}
}
(
1,
falls n ≤ 1
n! =
n · (n − 1)!, andernfalls
218
n! ⇔ fakultaet(n)
n − 1! ⇔ fakultaet(n−1)
219
Unendliche Rekursion
Rekursive Funktionen: Terminierung
ist so schlecht wie eine Endlosschleife. . .
. . . nur noch schlimmer (“verbrennt” Zeit und Speicher)
Wie bei Schleifen brauchen wir
Fortschritt Richtung Terminierung
Beispiel: f() → f() → f() → f() → . . . stack overflow
public static void f () {
f ();
}
fakultaet(n)
terminiert sofort für n ≤ 1, andernfalls wird die Funktion rekursiv mit
Argument < n aufgerufen.
“n wird mit jedem Aufruf kleiner.”
220
Rekursive Funktionen: Auswertung
221
Der Aufrufstapel
Beispiel: fakultaet(4)
n=1
public static int fakultaet (int n){
}
Bei jedem Funktionsaufruf:
Wert des Aufrufarguments kommt auf
einen Stapel
if (n <= 1) return 1;
return n * fakultaet(n-1); // n > 1
Es wird immer mit dem obersten Wert
gearbeitet
Am Ende des Aufrufs wird der oberste
Wert wieder vom Stapel gelöscht
Initialisierung des formalen Arguments: n = 4
Rekursiver Aufruf mit Argument n − 1, also 3
fakultaet(1)
n=2
fakultaet(2)
n=3
fakultaet(3)
n=4
fakultaet(4)
1! = 1
1
2 · 1! = 2
2
3 · 2! = 6
6
4 · 3! = 24
24
System.out.println(fakultaet(4))
222
223
Euklidischer Algorithmus
Euklidischer Algorithmus in Java
(
a,
falls b = 0
gcd(a, b) =
gcd(b, a mod b), andernfalls
findet den grössten gemeinsamen Teiler gcd(a, b) zweier
natürlicher Zahlen a und b
basiert auf folgender mathematischen Rekursion:
gcd(a, b) =
(
a,
public static int gcd(int a, intb){
if (b == 0) {
return a;
} else {
Terminierung: a mod b < b, also
return gcd(b, a%b);
wird b in jedem rekursiven Aufruf
}
kleiner.
}
falls b = 0
gcd(b, a mod b), andernfalls
225
224
Fibonacci-Zahlen
Fibonacci-Zahlen in Java
Laufzeit


falls n = 0
0,
Fn := 1,
falls n = 1


Fn−1 + Fn−2, falls n > 1
fib(50) dauert „ewig”, denn es berechnet
F48 2-mal, F47 3-mal, F46 5-mal, F45 8-mal, F44 13-mal,
F43 21-mal ... F1 ca. 109 mal (!)
public static int fib ( int n){
if (n == 0 || n == 1){
return n;
} else {
return fib (n−1) + fib(n−2);
}
}
Resultat: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 . . .
226
Korrektheit und Terminierung
sind klar, aber...
228
Schnelle Fibonacci-Zahlen
Schnelle Fibonacci-Zahlen in Java
public static int fib ( int n)\{
if (n == 0) return 0;
if (n <= 2) return 1;
int a = 1;
int b = 1;
for ( int i = 3; i <= n; ++i){
int a_old = a;
a = b;
b += a_old;
}
return b;
}
Idee:
Berechne jede Fibonacci-Zahl nur einmal, in der Reihenfolge
F0 , F1 , F2 , . . . , Fn !
Merke dir jeweils die zwei letzten berechneten Zahlen (Variablen a
und b)!
Berechne die nächste Zahl als Summe von a und b!
// F(1)
// F(2)
// F(i−2)
// F(i−1)
// F(i−1) += F(i−2) −> F(i)
230
229
Das Suchproblem
Gegeben
Menge von Datensätzen.
9. Suchen
Beispiele
Telefonverzeichnis, Wörterbuch, Symboltabelle
Lineare Suche, Binäre Suche, Interpolationssuche, Exponentielle
Suche, Untere Schranken [Ottman/Widmayer, Kap. 3.2, Cormen et
al, Kap. 2: Problems 2.1-3,2.2-3,2.3-5]
Jeder Datensatz hat einen Schlüssel k .
Schlüssel sind vergleichbar: eindeutige Antwort auf Frage k1 ≤ k2
für Schlüssel k1 , k2 .
Aufgabe: finde Datensatz nach Schlüssel k .
231
232
Das Auswahlproblem
Suche in Array
Gegeben
Array A mit n Elementen (A[1], . . . , A[n]).
Schlüssel b
Gegeben
Menge von Datensätzen mit vergleichbaren Schlüsseln k .
Gesucht: Index k , 1 ≤ k ≤ n mit A[k] = b oder ”nicht gefunden”.
Gesucht: Datensatz, mit dem kleinsten, grössten, mittleren
Schlüssel. Allgemein: finde Datensatz mit i-kleinstem Schlüssel.
22
20
32
10
35
24
42
38
28
41
1
2
3
4
5
6
7
8
9
10
233
234
Lineare Suche
Suche in sortierten Array
Durchlaufen des Arrays von A[1] bis A[n].
Gegeben
Sortiertes Array A mit n Elementen (A[1], . . . , A[n]) mit
A[1] ≤ A[2] ≤ · · · ≤ A[n].
Schlüssel b
Bestenfalls 1 Vergleich.
Schlimmstenfalls n Vergleiche.
Annahme: Jede Anordnung der n Schlüssel ist
gleichwahrscheinlich. Erwartete Anzahl Vergleiche:
Gesucht: Index k , 1 ≤ k ≤ n mit A[k] = b oder ”nicht gefunden”.
N
1 X
N +1
i=
.
N i=1
2
235
10
20
22
24
28
32
35
38
41
42
1
2
3
4
5
6
7
8
9
10
236
Binärer Suchalgorithmus BSearch(A,b,l,r)
Divide and Conquer!
Suche b = 23.
10
20
22
24
28
32
35
38
41
42
1
2
3
4
5
6
7
8
9
10
10
20
22
24
28
32
35
38
41
42
1
2
3
4
5
6
7
8
9
10
10
20
22
24
28
32
35
38
41
42
1
2
3
4
5
6
7
8
9
10
10
20
22
24
28
32
35
38
41
42
1
2
3
4
5
6
7
8
9
10
10
20
22
24
28
32
35
38
41
42
1
2
3
4
5
6
7
8
9
10
Input : Sortiertes Array A von n Schlüsseln. Schlüssel b. Bereichsgrenzen
0 ≤ l, r ≤ n
Output : Index des gefundenen Elements. 0, wenn erfolglos.
if n = 0 then return 0
m ← b(l + r)/2c
if l > r then // erfolglose Suche
return 0
else if b = A[m] then// gefunden
return m
else if b < A[m] then// Element liegt links
return BSearch(A, B, l, m − 1)
else // b > A[m]: Element liegt rechts
return BSearch(A, B, m + 1, r)
b < 28
b > 20
b > 22
b < 24
erfolglos
238
237
Analyse (Schlimmster Fall)
Analyse (Schlimmster Fall)
Rekurrenz (n = 2k )
(
d
falls n = 1,
T (n) =
T (n/2) + c falls n >= 1.
(
d
falls n = 1,
T (n) =
T (n/2) + c falls n >= 1.
Teleskopieren:
T (n) = T
n
2n +c=T
n
4
+i·c
i
2
n
=T
+ log2 n · c.
n
⇒ Annahme: T (n) = d + c log2 n
=T
Vermutung : T (n) = d + c · log2 n
Beweis durch Induktion:
+ 2c
Induktionsanfang: T (1) = d.
Hypothese: T (n/2) = d + c · log2 n/2
Schritt (n/2 → n)
T (n) = T (n/2) + c = d + c · (log2 n − 1) + c = d + c log2 n.
239
240
Resultat
Iterativer binärer Suchalgorithmus
Input : Sortiertes Array A von n Schlüsseln. Schlüssel b.
Output : Index des gefundenen Elements. 0, wenn erfolglos.
l ← 1; r ← n
while l ≤ r do
m ← b(l + r)/2c
if A[m] = b then
return m
else if A[m] < b then
l ←m+1
else
r ←m−1
Theorem
Der Algorithmus zur binären sortierten Suche benötigt Θ(log n)
Elementarschritte.
return 0;
242
241
Korrektheit
Geht es noch besser?
Algorithmus bricht nur ab, falls A leer oder b gefunden.
Annahme: Gleichverteilung der Werte im Array.
Invariante: Falls b in A, dann im Bereich A[l, ..., r]
Beispiel
Name ”Becker” würde man im Telefonbuch vorne suchen.
”Wawrinka" wohl ziemlich weit hinten.
Binäre Suche vergleicht immer zuerst mit der Mitte.
Beweis durch Induktion
Induktionsanfang: b ∈ A[1, .., n] (oder nicht)
Hypothese: Invariante gilt nach i Schritten
Schritt:
Binäre Suche setzt immer m = l +
b < A[m] ⇒ b ∈ A[l, .., m − 1]
b > A[m] ⇒ b ∈ A[m + 1, .., r]
243
r−l
2
.
244
Interpolationssuche
Erwartete relative Position von b im Suchintervall [l, r]
ρ=
b − A[l]
∈ [0, 1].
A[r] − A[l]
Neue ”Mitte”: l + ρ · (r − l)
Anzahl Vergleiche im Mittel O(log log n) (ohne Beweis).
? Ist Interpolationssuche also immer zu bevorzugen?
! Nein: Anzahl Vergleiche im schlimmsten Fall Ω(n).
245
Herunterladen