Fondamenti dell’Informatica
Ricorsione e Iterazione
Simona Ronchi Della Rocca
(dal testo: Kfoury, Moll and Arbib, cap.5.2)
Definiamo innanzitutto una relazione d’ordine tra le funzioni. Siano φ e ψ funzioni unarie da naturali a naturali:
φ ≤ ψ (φ `e minore di ψ, o φ approssima ψ) se e solo se ∀x ∈ dom(φ).φ(x) = ψ(x). Cio`e, dove la minore `e definita, `e definita anche la maggiore, e assumono entrambe lo stesso valore. Diciamo inoltre che φ < ψ (φ `e strettamente minore di ψ) se e solo se φ ≤ ψ ed esiste ameno un valore x dove ψ `e definita e φ no.
Ad esempio, la funzione:
h(x) =
x se x `e pari
⊥ altrimenti
`e tale che h ≤ Id e anche h < Id (Id `e la funzione identit`a).
Consideriamo una semplice funzione ricorsiva:
f att(x) = se x = 0 allora 1 altrimenti x ∗ f att(x − 1) fatt pu`o essere approssimata dalla seguente catena di funzioni parziali:
f att(0)(x) =
1 se x = 0
⊥ altrimenti
f att(1)(x) =
1 se x = 0, 1
⊥ altrimenti f att(n)(x) =
1 ∗ 2 ∗ ... ∗ x se x ≤ n
⊥ altrimenti
tali che:
f att(0) < f att(1)< ... < f att(n)< f att(n+1)< ...
Se dobbiamo calcolare il fattoriale del numero m, ci baster`a usare una approssi- mazione f att(n), con n ≥ m. In altre parole, la definizione ricorsiva di una funzione pu`o essere sviluppata all’infinito, ma ogni volta che vogliamo calco- larla su di uno specifico valore, il numero degli sviluppi (chiamate ricorsive) `e sempre finito e dipende dal valore in input. In altre parole nella computazione reale non ci serve mai tutta la potenza della ricursione.
Fatte queste considerazioni preliminari, vogliamo ora mostrare l’equivalenza tra ricursione e iterazione. Cio`e vogliamo dimostrare il seguente teorema:
Teorema. Dato un programma ricorsivo, esiste un programma iterativo che calcola la stessa funzione.
Estendiamo il linguaggio WHILE con una nozione di procedura ricorsiva, come fatto nel testo a pag. 110 (versione inglese), con la semantica operazionale come data informalmente nel testo. Sia FATT il programma cos`ı ottenuto.
Possiamo scrivere, per ogni n, il programma iterativo che calcola la funzione f att(n). Sia e l’indice del seguente programma, che calcola la funzione ovunque indefinita:
Pe= begin X1 = 1; while X1 6= 0 do begin end end Sia F AT T0=
begin if X1 = 0 then X1 := 1 else begin X2 := X1; X1 := pred(X1);
Pe;
X1 := X2 ∗ X1 end
end
Ovviamente F AT T0 calcola la funzione f att0. Sia ora F AT T+0 il programma F AT T0 con X3 al posto di X2, cio`e modificato nel seguente modo:
begin if X1 = 0 then X1 := 1 else begin X3 := X1; X1 := pred(X1);
Pe;
X1 := X3 ∗ X1 end
end Sia F AT T1=
begin if X1 = 0 then X1 := 1 else begin X2 := X1; X1 := pred(X1);
F AT T+0; X1 := X2 ∗ X1
end end
La ridenomina della variabile X2 in X3 `e necessaria per poter effettuare il passaggio dei parametri, senza cancellare il dato iniziale. Ovviamente F AT T1 calcola la funzione f att1.
Sia f la funzione di riscrittura di programmi tale che, applicata ad un pro- gramma di indice i, lo riscrive nel modo seguente, ottenendo cos`ı il programma di indice f (i):
begin if X1 = 0 then X1 := 1 else begin X2:= X1; X1 := pred(X1);
Pi dove ogni variabile Xj, con j ≥ 2 `e rimpiazzata da Xj+1; X1 := X2 ∗ X1
end end
Quindi F AT T0= Pf (e), e possiamo allora scrivere F AT T1 nel modo seguente:
begin if X1 = 0 then X1 := 1 else begin X2 := X1; X1 := pred(X1);
Pf (e);
X1 := X2 ∗ X1 end
end
cio`e F AT T1= Pf2(e), usando la notazione:
f0(e) = e, fn+1(e) = f (fn(e)).
In generale, la funzione f att(n)sar`a quindi calcolata dal programma F AT Tn = Pfn+1(e), definito nel modo seguente:
begin if X1 = 0 then X1 := 1 else begin X2 := X1; X1 := pred(X1);
Pfn(e);
X1 := X2 ∗ X1 end
end In altre parole,
φfn+1(e)(a) = f attn(a)
Teorema. Per ogni n, esiste m tale che f att(m)(n) = f att(n).
Dimostrazione. Ovvia, basta prendere m = n.
Possiamo quindi scrivere un programma iterativo, che calcola il fattoriale, calcolando prima quale `e l’approssimazione necessaria, e poi applicando questa all’input? Invece di riferirci solo alla funzione fattoriale, vediamo di porci il problema nel caso pi`u generale.
Sia k una funzione definita in modo ricorsivo. k `e approssimata da una catena infinita di funzioni parziali:
k(0)< k(1)< ... < k(n)< k(n+1)< ...
Vale in generale un teorema simile al precedente:
Teorema. Per ogni n, esiste m tale che k(m)(n) = k(n).
Possiamo scrivere, analogamente a quanto fatto per il fattoriale, un pro- gramma iterativo che calcola ciascuna delle approssimazioni k(i)(i ≥ 0). Siano Ki questi programmi. Ricordiamo che ciscuno di questi programmi richiama al suo interno il programma Pe, che cicla indefinitamente. Analogamente a quanto fatto per la funzione fattoriale, possiamo definire una sequenza di programmi, K0, K1, ... tali che Ki calcola la funzione k(i). Esiste quindi una funzione di
riscrittura di programmi, sia fk, tale che Kn= Pfn+1
k (e). Possiamo scrivere un programma iterativo, che calcola k, calcolando prima quale `e l’approssimazione necessaria, e poi applicando il programma corrispondente all’input?
Cio`e, `e possibile implementare il seguente algoritmo, per ogni funzione k e programmi Ki?
Algoritmo A:
1. sia a il dato in input;
2. calcola quale `e il minimo m tale che k(m)(a) = k(a);
3. esegui il programma Kmsull’input a.
NOTA. Questo programma NON `e ricorsivo, perch`e i programmi che cal- colano le approssimazioni k(n) sono programmi iterativi.
Il problema, per implementare l’algoritmo A, `e che non possiamo procedere per tentativi, ad esempio provando ad eseguirlo nel modo seguente:
1. sia a il dato in input;
2. poni n = 0;
3. esegui Kn sull’input a e va a 4;
4. se l’esecuzione di Kn sull’input a `e terminata normalmente, esci dando in output il contenuto di X1;
5. se l’esecuzione di Kn non termina, incrementa n di 1 e torna a 3.
infatti tale algoritmo non `e implementabile, data l’indecidibilit`a del problema dell’alt.
Possiamo per`o rendere effettivo l’algoritmo, trasformandolo nel modo seguente.
Sia F LAG una variabile speciale e sia Pu il seguente programma:
Pu= begin F LAG := 1 end
e trasformiamo ogni programma Kn sostituendo, al posto di Pe, il programma Pu. Quindi Kn = Pfn+1
k (u).
Ricordiamoci che ogni programma Kn ha come indice fkn+1(e), e quindi il programma trasformato avr`a come indice fkn+1(u).
La differenza tra Pfn
k(e) e il programma trasformato Pfn
k(u) `e che, mentre il primo pu`o ciclare, per particolari valori di input, il programma trasformato termina sempre, e in particolare pone F LAG = 1 in corrispondenza ai valori di input per cui Pfn
k(e)cicla.
Possiamo quindi scrivere il seguente algoritmo B:
1. sia a il dato in input;
2. n:=1;
3. FLAG:=0; esegui Pfn
k(u) (cio`e il trasformato di Kn) sull’input a. Se e quando la computazione si ferma, va a 4;
4. Se FLAG =1, incrementa n di 1 e va a 3;
5. Se FLAG=0, restituisci in output il valore di X1.
Infatti FLAG =1 indica che `e stato eseguito Pu, il che corrisponderebbe all’esecuzione di Penel programma originale Kn.
Possiamo scrivere un programma WHILE che realizza l’algoritmo B. Sia h la funzione di riscrittura di programmi tale che Ph(x) `e il programma seguente:
begin F LAG := 0;
{Px dove ogni test C `e trasformato in C AND FLAG=0}
if F LAG = 0 then X1 := succ(X1) else X1 := 0 end
Il programma Ph(fn+1
k (u)) calcola la seguente funzione:
φh(fn+1
k (u))(a) =
φfn+1
k (e)(a) + 1 = kn(a) + 1 se Pfn
k(e)su a non cicla
0 altrimenti
Quindi l’algoritmo B `e implementato dal seguente programma:
begin N := 0; while Φ(h(fkN(u)), X1) = 0 do N := succ(N );
X1 := pred(Φ(h(fkN(u)), X1)) end
che calcola quindi, in modo iterativo, la funzione k.
Quindi abbiamo dimostrato il teorema voluto:
Theorema Per ogni programma ricorsivo, c’`e un programma iterativo che calcola la stessa funzione.