Ist eine Funktion
f gegeben werden wir die Darstellung T(
f)(
n) nutzen, um eine asymptotische Abschätzung der Anzahl der notwendigen
Reduktionsschritte zu geben.
n charakterisiert dabei die Größe des übergebenen Arguments. Des weiteren wird die
Eager Evaluation
als Auswertungsgrundlage genutzt. Wir verwenden nicht die Lazy Evaluation um Ausdrücke zu reduzieren, weil es bei gewissen Audrücken
schwierig ist, eine genaue Abschätzung zu geben. Ein Beispiel ist folgende Definition:
minlist = head . sort
Nutzen wir die Eager Evaluation zur Auswertung, ist die Berechnung kompositionell möglich (mit
n Länge der Liste):
T(minlist)(n) = T(sort)(n) + T(head)(n)
Unsere Vorgehensweise um
minlist zu berechnen ist: Liste sortieren in T(sort)(n) Reduktionsschritten, dann den Kopf der
Liste in T(head)(n) Schritten ermitteln. Diese Gleichung ist in der Lazy Evaluation nicht gültig, da wir nicht mit Bestimmtheit
sagen können, dass wir
sort xs, um den Kopf der Liste zu ermitteln, in Normalform transformieren müssen. Die zeitliche Analyse
unter der Eager Evaluation ist
einfacher und
kompositionell. Es gelten weiterhin die Gesetze, die im ersten Kapitel
erarbeitet worden:
- theoretische obere Grenzen für Lazy und Eager Evaluation identisch
- häufig: untere Grenzen für Lazy und Eager Evaluation identisch
Beispiel: reverse
Im folgenden Beispiel werden zwei verschiedene Definitionen einer Funktion
reverse analysiert.
reverse dreht eine übergebene Liste um.
Beide Funktionen liefern das gleiche Ergebnis, besitzen aber deutliche Unterschiede in ihrer Laufzeit.
01 reverse1 [] = []
02 reverse1 (x:xs) = reverse1 xs ++ [x]
03
04 reverse2 = foldl prefix []
05 where prefix xs x = x : xs
|
Betrachten wir zuerst die Laufzeit von
reverse1.
T(reverse1)(0) = O(1) = Θ(1)
T(reverse1)(n+1) = T(reverse1)(n) + T(++)(n,1)
Die Aussage der ersten Gleichung ist, dass wir eine konstante Laufzeit zur Umkehrung einer leeren Liste benötigen. Die zweite Gleichung
setzt sich aus der Laufzeit zusammen, die für die Drehung einer Liste der Größe
n benötigt wird und die Laufzeit um eine ein-elementige Liste
mit einer n-elementigen Liste zu konkatenieren.
Eine Laufzeituntersuchung von T(++)(n,1) ergibt T(++)(n,1) = Θ(n), da T(++)(n,m) = Θ(n). Als Zwischenergebnis unserer
Berechnung erhalten wir:
T(reverse1)(n+1) = T(reverse1)(n) + Θ(n)
Führen wir nun die Rekursion T(reverse1)(n) durch erhalten wir Summierung:
reverse1 besitzt also eine Laufzeitkomplexität Θ(n
2)
Bevor wir mit der Laufzeitanalyse für
reverse2 beginnen können, müssen wir die fold-Funktion aus der Definition eliminieren, um
eine direkte Rekursion zu erhalten. Dazu führen wir die Hilfsfunktion
accum ein.
01 reverse2 = foldl prefix []
02 where prefix xs x = x : xs
03
04 -- neue Definition mit accum
05 reverse2 xs = accum [] xs
06 accum ws [] = ws
07 accum ws (x:xs) = accum (x:ws) xs
|
Die Funktionsweise von
accum wird nach der Bearbeitung des vierten Kapitels
"Parameter Akkumulation" deutlicher. Wir übergeben
accum initial eine Ergebnisliste (leere Liste) und eine Quellliste.
accum fügt rekursiv das aktuelle Element der Quellliste
vorne an die Ergebnisliste an, bis die Quellliste komplett bearbeitet ist.
Laufzeituntersuchung
reverse2:
T(reverse2)(m,0) = O(1) = Θ(1)
T(reverse1)(m,n+1) = O(1) + T(accum)(m,n)
m entspricht der Länge der Ergebnisliste und ist für die Laufzeit irrelevant.
n
entspricht der Länge der Quellliste. Wie man deutlich an der zweiten Gleichung erkennt stehen Länge der Quellliste und Laufzeit in einem linearem
Verhältnis zueinander. Die Laufzeit beträgt daher: Θ(n). Die O(1) Komplexität in der zweiten Gleichung gibt die cons-Operation wieder.