25
2.4 1 2.4 Rekursion versus Iteration Beispiel Fakultätsfunktion (factorial): Spezifikation (= applikatives Programm): fact n = if n<2 then 1 else n*fact(n-1) imperativ: int fact(int n){ return n<2 ? 1 : n*fact(n-1); } und iterativ: int fact(int n){ int result = 1; while(n>1) result *= n--; return result; }

2.4 Rekursion versus Iteration - inf.fu-berlin.de · 2.4 9 Verschiedene Rekursionsarten, je nach dem möglichen (dynamischen) Verhalten einer Inkarnation: nichtlineare Rekursion:

  • Upload
    vantruc

  • View
    216

  • Download
    0

Embed Size (px)

Citation preview

Page 1: 2.4 Rekursion versus Iteration - inf.fu-berlin.de · 2.4 9 Verschiedene Rekursionsarten, je nach dem möglichen (dynamischen) Verhalten einer Inkarnation: nichtlineare Rekursion:

2.4 1

2.4 Rekursion versus Iteration

Beispiel Fakultätsfunktion (factorial):

Spezifikation (= applikatives Programm):

fact n = if n<2 then 1 else n*fact(n-1)

imperativ: int fact(int n){ return n<2 ? 1 : n*fact(n-1); }

und iterativ: int fact(int n){ int result = 1;

while(n>1) result *= n--; return result; }

Page 2: 2.4 Rekursion versus Iteration - inf.fu-berlin.de · 2.4 9 Verschiedene Rekursionsarten, je nach dem möglichen (dynamischen) Verhalten einer Inkarnation: nichtlineare Rekursion:

2.4 2

iterativ: schneller - denn Unterprogrammaufrufe kosten Zeit;

rekursiv: schöner, einfacher, naheliegender, „besser“ ?

int pos(int x) { // position of x in a[] for(int i=0; ; i++) if(x==a[i]) return i;}int pos(int x, int i) { // ..... at i or beyond return x==a[i] ? i : pos(x,i+1);}

Was ist besser?

Page 3: 2.4 Rekursion versus Iteration - inf.fu-berlin.de · 2.4 9 Verschiedene Rekursionsarten, je nach dem möglichen (dynamischen) Verhalten einer Inkarnation: nichtlineare Rekursion:

2.4 3

int position(int unten, int oben) { // Einschachteln,// rekursiv, aus 2.3.1

int zeiger = (unten+oben)/2;

if (a[zeiger]< x) return position(zeiger,oben); else if(a[zeiger]==x) return zeiger; else /* a[zeiger]> x */ return position(unten,zeiger);}

int position(int unten, int oben) { // iterativ, aus 2.1.3 for(;;){ int zeiger = (unten+oben)/2;

if (a[zeiger]< x) unten = zeiger; else if(a[zeiger]==x) return zeiger; else /* a[zeiger]> x */ oben = zeiger; }}

? Systematische Umwandlung Iteration Rekursion ?

Page 4: 2.4 Rekursion versus Iteration - inf.fu-berlin.de · 2.4 9 Verschiedene Rekursionsarten, je nach dem möglichen (dynamischen) Verhalten einer Inkarnation: nichtlineare Rekursion:

2.4 4

Ein anderes Beispiel stimmt skeptisch:

reverse[] = []reverse(x:xs) = reverse xs ++ [x]

String reverse(String s) { // poor efficiency! return s.length()==0 ? "" :

reverse(s.substring(1)) + s.charAt(0); }

String reverse(String s) { // good efficiency! char[] a = s.toCharArray(); for(int i=0; i<a.length/2 ; i++) { char x = a[i]; a[i] = a[a.length-1-i]; a[a.length-1-i] = x; } return new String(a);

}

Page 5: 2.4 Rekursion versus Iteration - inf.fu-berlin.de · 2.4 9 Verschiedene Rekursionsarten, je nach dem möglichen (dynamischen) Verhalten einer Inkarnation: nichtlineare Rekursion:

2.4 5

Beispiel für einfache Umwandlungsregel: der Effekt von

while(C){ S }

kann auch erzielt werden durch Aufruf von

void whileCS() { if(C){S; whileCS();} }

Beachte: Beim rekursiven Abstieg wird S wiederholt,beim rekursiven Aufstieg passiert nichts mehr!

void whileCS(){ while(C){ S } }

void whileCS() { if(C){S; whileCS();} }

Page 6: 2.4 Rekursion versus Iteration - inf.fu-berlin.de · 2.4 9 Verschiedene Rekursionsarten, je nach dem möglichen (dynamischen) Verhalten einer Inkarnation: nichtlineare Rekursion:

2.4 6

... und verallgemeinert:

void loop() { // iterative for(;;) {

S1 if(C1) return; S2 if(C2) return; ..... } }

↔void loop() { // recursive

S1 if(C1) return; S2 if(C2) return; ..... loop(); }

Page 7: 2.4 Rekursion versus Iteration - inf.fu-berlin.de · 2.4 9 Verschiedene Rekursionsarten, je nach dem möglichen (dynamischen) Verhalten einer Inkarnation: nichtlineare Rekursion:

2.4 7

Entrekursivierung

• ist eine Programmtransformation,die eine rekursive Prozedur in eine iterative Prozedur umwandelt,

• ist von Bedeutung, weil:

iterativ braucht weniger Zeit und Speicher,

Spezifikation ist häufig rekursiv formuliert.

Systematische Verfahren ?

Page 8: 2.4 Rekursion versus Iteration - inf.fu-berlin.de · 2.4 9 Verschiedene Rekursionsarten, je nach dem möglichen (dynamischen) Verhalten einer Inkarnation: nichtlineare Rekursion:

2.4 8

2.4.1 Entrekursivierung endrekursiver Prozeduren

Zur Erinnerung:

Ein Prozeduraufruf erzeugt eine Inkarnation (activation, instance) der Prozedur, die beim Rücksprung beendet wird (und verloren geht).

Ein rekursiver Aufruf heißt schlicht, wenn unmittelbar nach Beendigung der erzeugten Inkarnationauch die erzeugende Inkarnation beendet wird.

Page 9: 2.4 Rekursion versus Iteration - inf.fu-berlin.de · 2.4 9 Verschiedene Rekursionsarten, je nach dem möglichen (dynamischen) Verhalten einer Inkarnation: nichtlineare Rekursion:

2.4 9

Verschiedene Rekursionsarten, je nach dem möglichen (dynamischen) Verhalten einer Inkarnation:

nichtlineare Rekursion:dynamisch mehr als ein rekursiver Aufruf, z.B.fibonacci hanoi(1.4.3) qsort(2.3.2)

lineare Rekursion:dynamisch höchstens ein rekursiver Aufruf, z.B. loop position pos gcd(1.4.3) fact reverse

Endrekursion:alle rekursiven Aufrufe sind schlicht, z.B.loop position pos gcd(1.4.3)

Endrekursion ist am einfachsten, auch für die Entrekursivierung:

Page 10: 2.4 Rekursion versus Iteration - inf.fu-berlin.de · 2.4 9 Verschiedene Rekursionsarten, je nach dem möglichen (dynamischen) Verhalten einer Inkarnation: nichtlineare Rekursion:

2.4 10

T proc() {locals ... return E; // recursive... return proc();... return E; ... return proc();... }

→T proc() {locals loop: for(;;) { ... return E; // iterative

... continue loop;

... return E;

... continue loop;

... } }

2.4.1.1 ... ohne Parameter

Beachte: nichtlokale Variable unproblematisch lokale Variable unproblematisch (bei Entrekursivierung

- nicht in umgekehrter Richtung!) Bei T=void muss implizites return explizit gemacht werden !

Page 11: 2.4 Rekursion versus Iteration - inf.fu-berlin.de · 2.4 9 Verschiedene Rekursionsarten, je nach dem möglichen (dynamischen) Verhalten einer Inkarnation: nichtlineare Rekursion:

2.4 11

T proc(A a) {locals ... return G(a); // recursive... return proc(F(a));... }

T proc(A a) {locals loop: for(;;) { ... return G(a); // iterative

... a = F(a); continue loop;

... } }

2.4.1.2 ... mit Parametern

(lokale und nichtlokale Variable auch hier unproblematisch)

Page 12: 2.4 Rekursion versus Iteration - inf.fu-berlin.de · 2.4 9 Verschiedene Rekursionsarten, je nach dem möglichen (dynamischen) Verhalten einer Inkarnation: nichtlineare Rekursion:

2.4 12

Beispiel 1: Euklidischer Algorithmus (1.3.4, 1.4.3):

static int gcd(int a, int b) {if(b==0) return a;else return gcd(b,a%b);

}→ static int gcd(int a, int b) {

loop: for(;;) { if(b==0) return a;else {// (a,b) = (b,a%b);

int r = a%b; a = b; b = r; continue loop; } }

}

Vergleiche dies mit 1.3.4, S. 29 !

Page 13: 2.4 Rekursion versus Iteration - inf.fu-berlin.de · 2.4 9 Verschiedene Rekursionsarten, je nach dem möglichen (dynamischen) Verhalten einer Inkarnation: nichtlineare Rekursion:

2.4 13

Beispiel 2: Einschachtelungsverfahren:

int position(int unten, int oben) { int zeiger = (unten+oben)/2;

if (a[zeiger]< x) return position(zeiger,oben); else if(a[zeiger]==x) return zeiger; else /* a[zeiger]> x */ return position(unten,zeiger);}→int position(int unten, int oben) {loop: for(;;) { int zeiger = (unten+oben)/2;

if (a[zeiger]< x) unten = zeiger; // oben = oben; else if(a[zeiger]==x) return zeiger; else /* a[zeiger]> x */ oben = zeiger; // unten=unten;}

(continue loop; bereits gestrichen, vgl. S. 3)

Page 14: 2.4 Rekursion versus Iteration - inf.fu-berlin.de · 2.4 9 Verschiedene Rekursionsarten, je nach dem möglichen (dynamischen) Verhalten einer Inkarnation: nichtlineare Rekursion:

2.4 14

Beispiel 3: mit Objektaufrufen

class Spelling { // linked list of Strings String word;

Spelling next; Spelling(String w, Spelling n) {

word = w; next = n; }

void insert(String w) { if(next==null || next.word.compareTo(w)>0)

next = new Spelling(w,next); else if(next.word.equals(w)); /* ignore */ else next.insert(w);}boolean check(String w) { if(word.equals(w)) return true; else if(next==null) return false; else return next.check(w);}}

Page 15: 2.4 Rekursion versus Iteration - inf.fu-berlin.de · 2.4 9 Verschiedene Rekursionsarten, je nach dem möglichen (dynamischen) Verhalten einer Inkarnation: nichtlineare Rekursion:

2.4 15

Modifizierte Technik:Zielobjekt des Operationsaufrufs als zusätzlichen Parameter betrachten

boolean check(String w) { // check(x,w) Spelling x = this; // simulate parameter x if(x.word.equals(w)) return true; else if(x.next==null) return false; else return x.next.check(w); // check(x.next,w)}

boolean checkIter(String w) { // iterative version Spelling x = this;loop: for(;;) if(x.word.equals(w)) return true; else if(x.next==null) return false; else x = x.next;

// w = w; // continue loop;

}

Page 16: 2.4 Rekursion versus Iteration - inf.fu-berlin.de · 2.4 9 Verschiedene Rekursionsarten, je nach dem möglichen (dynamischen) Verhalten einer Inkarnation: nichtlineare Rekursion:

2.4 16

2.4.2 Nicht endrekursive Prozeduren

Einbettung (embedding) erlaubt Entrekursivierung bei linearer Rekursion:

finde zu der gegebenen Prozedur p0 eine verallgemeinerteVersion p1, die endrekursiv ist und die Leistung der gegebenen Prozedur umfasst;

ersetze den Rumpf von p0 durch einem Rumpf, der auseinem Aufruf von p1 besteht und bestätige dadurch die Verwendbarkeit von p1;

ersetze den Rumpf von p1 durch eine iterative Version underhalte dadurch eine gleichnamige Version p2;

ersetze in p0 den Aufruf von p1 durch den Rumpf von p2.

Page 17: 2.4 Rekursion versus Iteration - inf.fu-berlin.de · 2.4 9 Verschiedene Rekursionsarten, je nach dem möglichen (dynamischen) Verhalten einer Inkarnation: nichtlineare Rekursion:

2.4 17

Beispiel 1:

static int fact(int n) { if(n<2) return 1; else return n*fact(n-1); }

static int fact(int n, int acc) { // using accumulator

if(n<2) return acc; else return f(n-1,n*acc);

static int fact(int n) {

return fact(n,1); }

Page 18: 2.4 Rekursion versus Iteration - inf.fu-berlin.de · 2.4 9 Verschiedene Rekursionsarten, je nach dem möglichen (dynamischen) Verhalten einer Inkarnation: nichtlineare Rekursion:

2.4 18

static int fact(int n, int acc) { // using accumulator

for(;;) if(n<2) return acc; else acc *= n--;}// (n,acc)=(n-1,n*acc)

Vereinfachung:

while(n>1) acc *= n--; return acc;

static int fact(int n) {

int acc = 1; // einbettender Parameter wird lokale Variable while(n>1) acc *= n--; return acc; } Endversion - vgl. S. 1 !

Page 19: 2.4 Rekursion versus Iteration - inf.fu-berlin.de · 2.4 9 Verschiedene Rekursionsarten, je nach dem möglichen (dynamischen) Verhalten einer Inkarnation: nichtlineare Rekursion:

String reverse(String s) { Beispiel 2 (vgl. S. 4) return s.length()==0 ? "" :

reverse(s.substring(1)) + s.charAt(0);

} Einbettung:String reverse(String s, String acc) {

if(s.length()==0) return acc; else return reverse(s.substring(1),s.charAt(0)+acc);

} ... benutzt in neuer Version:String reverse1(String s) {

return reverse(s,"");} Einbettung iterativ:String reverseIter(String s, String acc) {

for(;;) if(s.length()==0) return acc; else{acc = s.charAt(0)+acc; s = s.substring(1);}

} Neue Version mit vereinfachter iterativer Einbettung:String reverseIter(String s) { String acc = "";

while(s.length()!=0) { acc = s.charAt(0)+acc; s = s.substring(1); } return acc; ... bringt aber kaum Effizienzgewinn.

}

Page 20: 2.4 Rekursion versus Iteration - inf.fu-berlin.de · 2.4 9 Verschiedene Rekursionsarten, je nach dem möglichen (dynamischen) Verhalten einer Inkarnation: nichtlineare Rekursion:

2.4 20

Nichtlineare Rekursion ( hanoi, qsort, ...):

Umwandlung in Iteration erfordert weitergehende Hilfsmittel( ALP III)

Page 21: 2.4 Rekursion versus Iteration - inf.fu-berlin.de · 2.4 9 Verschiedene Rekursionsarten, je nach dem möglichen (dynamischen) Verhalten einer Inkarnation: nichtlineare Rekursion:

2.4 21

2.4.3 Umwandlung von Schleifen in rekursive Funktionen

Gegeben: eine Schleife

Gesucht: eine Haskell-Funktion

Feststellung: Dies ist prinzipiell einfacher als Entrekursivierung,weil Rekursion ausdrucksstärker als Iteration ist.

Zur Erinnerung (S. 5): der Effekt von

while(C){ S }

kann auch erzielt werden durch Aufruf vonvoid whileCS() { if(C){S; whileCS();} }

Zu lösen bleibt: imperativer Zustand funktionale Argumente

Page 22: 2.4 Rekursion versus Iteration - inf.fu-berlin.de · 2.4 9 Verschiedene Rekursionsarten, je nach dem möglichen (dynamischen) Verhalten einer Inkarnation: nichtlineare Rekursion:

2.4 22

Allgemeine Form der while-Schleife über dem Zustand z eines Programms:

void whileCF() { while( C(z) ) z = F(z); }

↔ (S. 5)

void whileCF() { if( C(z) ) { z = F(z); whileCF(); }

↔ Argumente statt nichtlokaler Variablen:

T whileCF(T z) { if(C(z)) return whileCF(F(z));

else return z; }

D.h. Effekt der Schleife wird durch z = whileCF(z) erzielt.

Page 23: 2.4 Rekursion versus Iteration - inf.fu-berlin.de · 2.4 9 Verschiedene Rekursionsarten, je nach dem möglichen (dynamischen) Verhalten einer Inkarnation: nichtlineare Rekursion:

2.4 23

Direkte Umformulierung nach Haskell liefert

whileCF z | C z = whileCF(F z) | otherwise = z

Resümee:

void whileCF() { while( C(z) ) z = F(z); }

whileCF z | C z = whileCF(F z) | otherwise = z

Page 24: 2.4 Rekursion versus Iteration - inf.fu-berlin.de · 2.4 9 Verschiedene Rekursionsarten, je nach dem möglichen (dynamischen) Verhalten einer Inkarnation: nichtlineare Rekursion:

2.4 24

C und F zu Argumenten machen ! Allgemein verwendbares Funktional als Entwurfsmuster (2.3):

while :: (t->bool) -> (t->t) -> t -> t

while cond func init

| cond init = while cond func (func init) | otherwise = init

Page 25: 2.4 Rekursion versus Iteration - inf.fu-berlin.de · 2.4 9 Verschiedene Rekursionsarten, je nach dem möglichen (dynamischen) Verhalten einer Inkarnation: nichtlineare Rekursion:

2.4 25

Beispiel: Approximation der Quadratwurzel x = √amit Newton-Verfahren:

xi+1 = (xi + a/xi) / 2 (z.B. mit x0 = 1)

while(Math.abs(x*x-a) > eps) x = (x+a/x)/2;

Zustand: z = (a,x,eps) , dabei a und eps konstant.

Bedingung: inaccurate a x eps = abs(x*x-a) > eps

Änderung: improve a x eps = (a, (x+a/x)/2, eps)

... und damit sqrt = while inaccurate improve

... und damit z.B. sqrt 2 1 0.0001 (2, 1.414..., 0.0001)