2.4 Rekursion versus Iteration - inf.fu-berlin.de · 2.4 9 Verschiedene Rekursionsarten, je nach...

Post on 28-Aug-2019

215 views 0 download

Transcript of 2.4 Rekursion versus Iteration - inf.fu-berlin.de · 2.4 9 Verschiedene Rekursionsarten, je nach...

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 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?

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 ?

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);

}

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();} }

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(); }

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 ?

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.

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:

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 !

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)

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 !

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)

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);}}

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;

}

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.

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); }

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 !

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.

}

2.4 20

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

Umwandlung in Iteration erfordert weitergehende Hilfsmittel( ALP III)

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

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.

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

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

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)