Prozeduren und von ihnen erzeugte Prozesse Gültigkeitsbereiche und Rekursion . . . direkte Umsetzung in Scheme (define (fact n) (if (= n 1) ; 1! = 1 1 (* n (fact (- n 1))))) • Stand: Wir kennen – Elementare (“primitive”) Operationen – Kombination dieser Operationen – Abstraktion zusammengesetzter Operationen Hinweis: Ausführliche Diskussion in Algorithmik 1, Kap. 8 • Analogie zum Schachspiel: Wir kennen die Regeln, aber nicht • (Partielle) Korrektheit gemäss Spezifikation – auf welche Züge es ankommt (welche Prozeduren wir wie kombinieren sollen) – was die Konsequenzen eines Zuges sind (was die Ausführung einer Prozedur bewirkt) • Zusammenhang zwischen Rekursionsschema und vollständiger Induktion • Termination Keine zirkuläre Definition: fact wird nicht durch “sich selbst” definiert ⇒ “vermindertes” Teilproblem • Beachte: Lokales vs. globales Verhalten G. Görz, FAU, Inf.8 3–1 Lineare Rekursion und Iteration 3–3 Anwendung des Substitutionsmodells auf die Berechnung von (fact 6) Prozedur: Schema für die lokale Entwicklung eines Berechnungsprozesses Beispiel: Fakultätsfunktion, n ∈ N n! = n · (n − 1) · (n − 2) · . . . · 3 · 2 · 1 Also: 1! = 1 und n! ist n · (n − 1)! für alle n > 1 n! = n · ((n − 1) · (n − 2) · . . . · 3 · 2 · 1) = n · (n − 1)! G. Görz, FAU, Inf.8 G. Görz, FAU, Inf.8 3–2 (fact 6) (* 6 (fact 5)) (* 6 (* 5 (fact 4))) (* 6 (* 5 (* 4 (fact 3)))) (* 6 (* 5 (* 4 (* 3 (fact 2))))) (* 6 (* 5 (* 4 (* 3 (* 2 (fact 1)))))) (* 6 (* 5 (* 4 (* 3 (* 2 1))))) (* 6 (* 5 (* 4 (* 3 2)))) (* 6 (* 5 (* 4 6))) (* 6 (* 5 24)) (* 6 120) 720 ⇒ Linear rekursiver Prozeß zur Berechnung von 6! G. Görz, FAU, Inf.8 3–4 Lineare Rekursion: Anmerkungen Linear iterativer Prozeß Motivation: Wir nehmen — im Beispiel — eine andere Perspektive ein: Anmerkungen zum Berechnungsprozeß: • Zwei Phasen: Expansion und Kontraktion Beschreibung einer Regel zur Berechnung von n!, indem wir zuerst 1 mit 2, dann das Ergebnis mit 3, dann mit 4, etc. multiplizieren, bis wir n erreicht haben. • Während der Expansion werden Berechnungsschritte “aufgeschoben”: (n − 1) Multiplikationen — Buchhaltung über “Zwischeninformation”! D.h.: Wir halten in jedem Schritt ein Produkt fest zusammen mit einem Zähler, der von 1 bis n läuft. Aufwand der Buchhaltung hier linear proportional zu n • In der Kontraktion werden die Multiplikationen ausgeführt • Es gibt keine “Zwischenergebnisse” — Zwischenzustände ergeben sich aus der Buchhaltung G. Görz, FAU, Inf.8 3–5 In jedem Schritt ändern sich der Zähler und das Produkt gleichzeitig nach der Regel product ← counter · product counter ← counter + 1, wobei n! gleich dem Wert des Produkts ist, wenn der Zähler n überschreitet. G. Görz, FAU, Inf.8 Beispiel: Fakultät iterativ Lineares Rekursionsschema (define (fact n) 1. Finde heraus, wie ein Rekursions-Schritt durchzuführen ist. 2. Zerlege das Problem in “Arbeitsschritt” und “vermindertes” Teilproblem 3. Lege fest, wann das (schon gelöste) Basisproblem erreicht ist (Rekursionsende) Schema für linear rekursive Funktionen: ; Blockstruktur! (define (fact-iter product counter) (if (> counter n) product (fact-iter (* counter product) (+ counter 1)) )) (fact-iter 1 1)) (define (f_linrec ...) (cond (P <STOP>) { (Q (g (f_linrec ...))) } (else (h (f_linrec ...))) )) G. Görz, FAU, Inf.8 3–7 3–6 G. Görz, FAU, Inf.8 3–8 Beispiel: Linear iterativer Prozeß Schema für rest-rekursive Funktionen Linear iterativer Prozeß zur Berechnung von 6! (erste Version) auch: end-rekursiv, rumpf-rekursiv (“tail recursive”) (fact 6) (fact-iter 1 1) (fact-iter 1 2) (fact-iter 2 3) (fact-iter 6 4) (fact-iter 24 5) (fact-iter 120 6) (fact-iter 720 7) 720 (define (f_tailrec ...) (cond (P <STOP>) { (Q (f_tailrec ...)) } (else (f_tailrec ...)) )) Anforderung an jede Scheme-Implementation: Rest-rekursive Prozeduren müssen intern als iterative Prozesse ausgeführt werden ⇒ keine Belastung der internen Buchhaltung Hinweis: Verschränkte Restrekursion (Beispiel: Algorithmik 1) G. Görz, FAU, Inf.8 3–9 Anmerkungen zum Berechnungsprozeß 3–11 Äquivalenz der linear-rekursiven und der rest-rekursiven Version • Keine verschiedenen Phasen • “Zwischeninformation” in den Variablen product, counter, max-count (bzw. n) (define (fact n) (if (= n 1) 1 (* n (fact (- n 1))))) • Zwischenergebnisse werden explizit in der Akkumulatorvariablen product festgehalten (akkumuliert) • Aufwand ist ebenfalls linear (Sequentielle Aufzählung der Elemente des Definitionsbereichs: natürliche Zahlen) • Der durch fact-iter verursachte Berechnungsprozeß ist iterativ; die Prozedur fact selbst jedoch (rest-) rekursiv • “Iterativ” vs. “rekursiv” in Bezug auf Prozesse betrifft die Buchhaltung und die Aufschiebung von Berechnungsschritten G. Görz, FAU, Inf.8 G. Görz, FAU, Inf.8 3–10 (define (fact-i n product) (if (= n 1) product (fact-i (- n 1) (* product n)))) Vereinfachte Variante der rest-rekursiven Fakultätsfunktion: zählt abwärts Beh.: ∀n ≥ 1. (fact n) = (fact-i n 1) G. Görz, FAU, Inf.8 3–12 Baumrekursion n = 1: (fact 1) = 1 = (fact-i 1 1) n > 1: (fact n) = (* n (fact (- n = (* n (fact-i (= (fact-i (- n 1) = (fact-i (- n 1) = (fact-i n 1) Beispiel: Fibonacci (Leonardo von Pisa: Kaninchenaufgabe) 1))) n 1) 1)) (* n 1)) (* 1 n)) Definition Induktionsannahme Lemma Rekursionsgleichungen: F0 = 0 Definition F1 = 1 Fn = Fn−1 + Fn−2 Lemma: (* a (fact-i n z)) = (fact-i n (* a z)) n = 1: (* a (fact-i 1 z)) = (* a z) = (fact-i 1 (* a z)) G. Görz, FAU, Inf.8 n > 1: (* a (fact-i n z)) = (* a (fact-i (- n 1) (* z n))) = (fact-i (- n 1) (* a (* z n))) = (fact-i (- n 1) (* (* a z) n)) = (fact-i n (* a z)) G. Görz, FAU, Inf.8 ergibt die Folge: 0 1 1 2 3 5 8 13 21 34 55 89 . . . 3–13 G. Görz, FAU, Inf.8 3–15 Als Scheme-Prozedur: Definition Induktionsannahme Assoziativität Definition 3–14 (define (fib n) (cond ((= n 0) 0) ((= n 1) 1) (else (+ (fib (- n 1)) (fib (- n 2)))) )) G. Görz, FAU, Inf.8 3–16 Fibonacci-Baum Baum-Rekursion Die Fibonacci-Prozedur ist ein charakteristisches Beispiel für Baum-Rekursion: (fib 20) = 6765, (fib 30) = 832040 • Folge der Verarbeitungsschritte bildet strukturell den zu verarbeitenden Baum nach ! ⇒ “baum-rekursiv” • Schema für baum-rekursive Funktionen: (define (f_treerec ...) ... (G (f_treerec ...) (f_treerec ...) ...) ...) G. Görz, FAU, Inf.8 3–17 G. Görz, FAU, Inf.8 3–19 Fibonacci iterativ Fibonacci-Baum (Forts.) • Der Fibonacci-Baum enthält fib(n + 1) Blätter: fib(0) bzw. fib(1) • Wie groß ist fib(n) ungefähr? Die baum-rekursive Fibonacci-Prozedur ist in höchstem Maß ineffizient, Sei φ definiert durch: φ2 = φ + 1 √ also: φ = (1 + 5)/2 ≈ 1.618 (“goldenes Verhältnis”) √ Dann: fib(n) ≈ φn/ 5 ⇒ Umbau in eine iterative Prozedur mit zwei Akkumulatorvariablen a, b: D.h., der Fibonacci-Baum wächst exponentiell in Bezug auf die Blätteranzahl: exponentiell abhängig von n Die meiste Zeit wird zur (erneuten) Berechnung bereits berechneter Zwischenergebnisse verbraucht; z.B. die Hälfte zur Berechnung von Fn−2, was bereits bei der Berechnung von Fn−1 verwendet worden war. • Speicherbedarf = Anzahl der Ebenen im Baum: linear proportional zu n G. Görz, FAU, Inf.8 3–18 Anfangswerte a = 1, b = 0 Simultane Transformationen G. Görz, FAU, Inf.8 a b ←a+b ←a 3–20 Iterationsschritte: n n n n n n n i = = = = = = = 1 2 3 4 5 6 7 a a a a a a a = = = = = = = 1 1 2 3 5 8 13 fib(i) Aufwände von Algorithmen und Komplexitätsmaße b b b b b b b = = = = = = = 0 1 1 2 3 5 8 Rekapitulation: Algorithmik 1, Kap. 13 Um die Effizienz von Algorithmen vergleichen zu können, braucht man eine Abschätzung der Zeit oder des Speicherplatzes, die sie abhängig von der benötigen. Sei n ein Eingabeparameter (“Größe der Eingabe”); es werden die Funktionen T (n) ≈ Zeitbedarf Ressourcen P (n) ≈ Platzbedarf untersucht, wobei die Größenordnung “O” des Ressourcenbedarfs (Ordnung des Aufwands) interessiert. fib(i − 1) G. Görz, FAU, Inf.8 3–21 G. Görz, FAU, Inf.8 Fibonacci: rest-rekursiv Aufwände der rekursiven Beispiele (define (fib n) Rekursive Fakultät: T (n) = O(n) P (n) = O(n) (define (fib-iter a b count) (if (= count 0) b (fib-iter (+ a b) a (- count 1)))) Iterative Fakultät: T (n) = O(n) P (n) = O(1) Rekursive Fibonacci-Prozedur: T (n) = O(φn) P (n) = O(n) (fib-iter 1 0 n)) Iterative Fibonacci-Prozedur: Rest-rekursiv: konstanter Speicherbedarf, linearer Zeitbedarf (“Zwischenwerte” werden jeweils nur einmal berechnet!) G. Görz, FAU, Inf.8 3–23 T (n) = O(n) P (n) = O(1) 3–22 G. Görz, FAU, Inf.8 3–24 Schnelle Exponentiation Beispiel: Exponentiation (Potenzbildung) Verwendung von Quadraten, z.B. b8 = (b4)2 = ((b2)2)2 Basis b, Exponent n (integer ≥ 0) Umformung allgemein: 0 b =1 bn = b · bn−1 bn = Rekursive Fassung: P (n) = O(n) G. Görz, FAU, Inf.8 3–25 G. Görz, FAU, Inf.8 (define (expt b n) T (n) = O(log n) P (n) = O(log n) (define (expt-iter counter product) (if (= counter 0) product (expt-iter (- counter 1) (* b product)))) (expt-iter n 1)) G. Görz, FAU, Inf.8 3–27 b2n benötigt eine Multiplikation mehr als bn, damit: Iterative Fassung: T (n) = O(n) (bn/2)2 ← n gerade b · bn−1 ← n ungerade (define (fast-expt b n) (cond ((= n 0) 1) ((even? n) (square (fast-expt b (/ n 2)))) (else (* b (fast-expt b (- n 1)))))) (define (expt b n) (if (= n 0) 1 (* b (expt b (- n 1))))) T (n) = O(n) z.B. n = 1000: 14 Multiplikationen P (n) = O(1) 3–26 G. Görz, FAU, Inf.8 3–28 Ein weiteres Beispiel: Primzahltest Primzahltest (3) (define (prime? n) (define (smallest-divisor n) ; n eliminierbar ... (find-divisor n 2)) (define (find-divisor n test-divisor) (cond ((> (square test-divisor) n) n) ((divides? test-divisor n) test-divisor) (else (find-divisor n (+ test-divisor 1))))) (define (divides? a b) (= (remainder b a) 0)) (= n (smallest-divisor n))) • Vorgehen – Suche die kleinste Zahl ≥ 2, die n teilt. – n ist Primzahl ⇔ die gefundene Zahl ist n selbst. • Um eine Rekursion zu ermöglichen, wird die Fragestellung verallgemeinert: Suche die kleinste Zahl ≥ t, die n teilt. Ansatz: Ist t2 > n ? – ja: nur n selbst teilt n – nein: t teilt n ? ∗ ja: t ist der kleinste Teiler von n ∗ nein: Suche weiter ab t + 1 G. Görz, FAU, Inf.8 Aufwandsordnung der Schrittzahl, um n als Primzahl zu identifizieren: √ T (n) = O( n) 3–29 G. Görz, FAU, Inf.8 Schneller Primzahltest: Fermat-Test Primzahltest (2) Spezifikationsskizze: Zwei Zahlen heißen kongruent modulo n, wenn sie beide bei der Division durch n denselben Rest haben. 1. n ist Primzahl, falls gilt: t teilt n → t = 1 ∨ t = n, d.h. n hat keine echten Teiler Der Divisionsrest einer Zahl a bei der Division durch n heißt auch der Rest von a modulo n oder einfach a modulo n. 2. Jede Zahl hat mindestens zwei Teiler. Für Primzahlen gilt: zweitkleinster-Teiler(n) = n Kleiner Fermatscher Satz: Wenn n Primzahl und a natürliche Zahl mit 2 ≤ a < n, so gilt: 3. zweitkleinster-Teiler(n): kleinster Teiler von n, der größer ist als 1 (smallest-divisor n) 4. Prozedur “finde-größeren-Teiler”: (find-divisor n test-divisor) test-divisor ist der kleinste Teiler, der versucht wird. √ Abbruchkriterium: Ist n keine Primzahl, so muß es einen Teiler ≤ n haben. √ Denn: Falls d teilt n, so auch n/d teilt n; entweder d oder n/d ≤ n G. Görz, FAU, Inf.8 3–31 3–30 an ≡ a mod n Idee des Fermat-Tests: Wähle ein a < n und berechne Rest von an mod n. Falls Rest = a, dann ist n eventuell eine Primzahl, andernfalls keine. Iteriert ⇒ “probabilistischer” Algorithmus G. Görz, FAU, Inf.8 3–32 Beispiel zum Fermat-Test Fermat-Test vervollständigt Beispiel: n = 5 15 25 35 45 55 = = = = = (define (fermat-test n) ≡ 1 mod 5 ≡ 2 mod 5 ≡ 3 mod 5 ≡ 4 mod 5 ≡ 0 mod 5 1 32 243 1024 3125 (define a (+ 2 (random (- n 2)))) (= (exptmod a n n) a)) Gegenbeispiel: n = 4 4 1 = 24 = 34 = 1 16 81 Idee zum Zeitgewinn: Wähle a (1 < a < n) zufällig aus mittels Zufallszahlen-Generators (random r). ≡ 1 mod 4 ≡ 0 mod 4 ≡ 1 mod 4 Test mit mehreren, aber nicht allen a. Jedes a, das den Test besteht, ist ein Indiz für n prim. G. Görz, FAU, Inf.8 3–33 exptmod G. Görz, FAU, Inf.8 3–35 (define (fast-prime? n times) (cond ((= times 0) #t) ((fermat-test n) (fast-prime? n (- times 1))) (else #f))) Für den Fermat-Test wird der Rest von be mod m gesucht, d.h. be ≡ x mod m Erster Ansatz: √ Aufwand: O(log n) gegenüber O( n) für prime? ! (define (exptmod2 b e m) (modulo (fast-expt b e) m)) exptmod2 ist ungeeignet für den Fermat-Test, denn z.B. beim Test von 997 wird benötigt: a997 ≡ a mod 997 ? Aber: (fast-expt a 997) für a < 997 funktioniert nur für kleine a (≤ 4) ⇒ Gesucht: ein “kleineres” exptmod! (s. Anhang) G. Görz, FAU, Inf.8 3–34 G. Görz, FAU, Inf.8 3–36 Anmerkungen zu fast-prime? Zur Komplexitätsabschätzung Gut: f (n) ist Polynom (polynomiale Komplexität) • Verfahren ist probabilistisch: Ergebnis #t gibt Evidenz für Primzahl, keine Sicherheit Schlecht: sonst (exponentielle Komplexität) • Es gibt Zahlen, die das Verfahren irreführen: 561, 1105,. . . (Carmichaelsche Zahlen — 16 Stück < 100.000 bei 8500 Primzahlen) Beachte: “worst case” Abschätzungen (ungünstigster Fall) Exponentielle Lösungen können sehr wohl brauchbar sein, wenn: • Es gibt Varianten des Verfahrens, die nicht in die Irre geführt werden können • ein Programm nur wenige Male verwendet wird; • ein Programm nur mit “kleinen” Eingaben benutzt wird; • Es gibt Verfahren mit geringerer Fehlerwahrscheinlichkeit als Wahrscheinlichkeit eines Hardwarefehlers während Laufzeit • eine effizientere Lösung ist so kompliziert ist, dass sie nicht mehr modifiziert werden kann; • Primzahltests haben praktischen Nutzen, z.B. sind sie in der Kryptographie Basis der meisten modernen “unbreakable Codes” G. Görz, FAU, Inf.8 • Zeiteffizienz auf Kosten von Platzeffizienz geht. 3–37 G. Görz, FAU, Inf.8 3–39 Abschließende Bemerkungen • Die meisten der behandelten rekursiven Prozesse (Ausnahme z.B. fib) haben dieselbe Ordnung im Wachstum von Zeit- und Speicherbedarf ANHANG ZUR SELBSTÄNDIGEN NACHARBEIT • Bezeichnungen für die gängigen Fälle: Iteration für konstanten Platzbedarf Rekursion für nicht-konstanten Platzbedarf Lineare Iteration und lineare Rekursion für Prozesse, bei denen alle Anforderungen — mit Ausnahme des Platzbedarfs für einen Iterationsschritt — linear sind G. Görz, FAU, Inf.8 3–38 G. Görz, FAU, Inf.8 3–40 Ein “kleineres” exptmod exptmod (Forts.) Das Problem: be ≡ x mod m (define (exptmod b e m) (cond ((= e 0) 1) ((even? e) (remainder (square (exptmod b (/ e 2) m)) m)) (else (remainder (* b (exptmod b (- e 1) m)) m)))) Falls e gerade: be = (be/2)2 dann: be ≡ (be/2)2 mod m ≡ a2 mod m mit (be/2)2 ≡ a mod m G. Görz, FAU, Inf.8 3–41 Entsprechend für e ungerade: G. Görz, FAU, Inf.8 3–43 Vorgehen analog zu fast-expt — gleiche Rekursionsidee: be = b · be−1 dann: G. Görz, FAU, Inf.8 • Fallunterscheidung für gerade und ungerade Exponenten • sukzessives Quadrieren, sodass die Anzahl der Schritte logarithmisch mit dem Exponenten wächst be ≡ a1 · a2 mod m mit b ≡ a1 mod m be−1 ≡ a2 mod m T (n) = O(log n) 3–42 G. Görz, FAU, Inf.8 P (n) = O(log n) 3–44