Sunteți pe pagina 1din 8

Programarea recursiva

9. Programarea algoritmilor recursivi


Introducere.

O metoda frecvent folosita pentru definirea functiilor o constituie


recursia. Spunem ca o functie este recursiva daca ea se autoapeleaza.
Autoapelul poate fi direct sau indirect prin intermediul unor functii intermediare.

Exemplul 1. Functia factorial fac: N->N


|- 1, daca n=0,
fac(n) = | (1)
|- n*f(n-1), altfel

fac(5) =5*fac(4)=
=5*(4*fac(3))=
=5*(4*(3*fac(2)))=
=5*(4*(3*(2*fac(1))))=
=5*(4*(3*(2*(1*fac(0)))))=
=5*(4*(3*(2*(1*1))))=
=5*(4*(3*(2*1)))=
=5*(4*(3*2))=
=5*(4*6)=
=5*24=
=120.

Exemplul 2. Functia lui Ackerman ac: N x N -> N


|- n+1, daca m=0, (a)
ac(m,n)=| ac(m-1,1), daca n=0, (2) (b)
|- ac(m-1,ac(m,n-1)), altfel. (c)

ac(2,2)=ac(1,ac(2,1)) (c)
= ac(1,ac(2,0)) (c)
= ac(1,1) (b)
= ac(0,ac(1,0)) (c)
= ac(0,1) (b)

= 2 (a)
Programarea calculatoarelor
= 3 (a)
= ac(0,ac(1,2)) (c)
= ac(0,ac(1,1)) (c)
= ac(0,ac(1,0)) (c)
= ac(0,1)
(b)
= 2 (a)
= 3 (a)
= 4 (a)
= 5 (a)
=ac(0,ac(1,4))
(c)
= ac(0,ac(1,3)) (c)
= ac(0,ac(1,2)) (c)
= ac(0,ac(1,1)) (c)
= ac(0,ac(1,0)) (c)
= ac(0,1) (b)
= 2 (a)
= 3 (a)
= 4 (a)
= 5 (a)
= 6 (a)
=7 (a)

În ambele definitii se recunoaste usor prezenta unor autoreferiri


(autoapelari) a functiei.

Orice definitie recursiva trebuie sa satisfaca urmatoarea conditie de


existenta: valoarea functiei trebuie sa fie direct calculabila ori calculabila cu
ajutorul unor valori direct calculabile. Astfel definitia functiei incons: N x N ->
N

|- 1, daca n=0,
incons(n)= | (3)
|- n*incons(n+1) altfel.
Programarea recursiva
este inconsistenta, deoarece valorile ei pentru n>0 nu pot fi calculate. Intr-
adevar, modul în care este descrisa autoreferinta din (3), nu permite utilizarea
valorii incons(0)=1. Aceasta definitie devine însa consistenta, daca definitia
devine:
incons: N- U {0} -> Z.

În general o functie recursiva se poate realiza si nerecursiv, adica fara


sa se autoapeleze. De obicei, recursivitarea nu conduce la economie de
memorie si nici la executia mai rapida a programelor. Ea permite insa o
descriere mai compacta si mai clara a functiilor care exprima procese de calcul
recursive. Exista situatii în care functiile recursive nu sunt recomandate
deoarece implica consum mare de timp si memorie în comparatie cu varianta
nerecursiva a aceluiasi proces de calcul (vezi functia Fibonacci).

De aceea, rezolvarile cu ajutorul functiilor recursive urmeaza sa se


adopte numai dupa un studiu atent al implicatiilor pe care le au acestea în
privinta necesarului de memorie si timp de executie.

Exista câteva tipuri de probleme care, de obicei, se rezolva cu ajutorul


functiilor recursive. Astfel, se recomanda sa se utilizeze functii recursive pentru
probleme a caror metode de rezolvare se pot defini recursiv. În caeasta clasa
se includ metodele: "Divide et impera", "Backtracking", etc..

Exemplul 3. Functia Fibonacci fib: N -> N


|- 1 , daca n=0 sau n=1
fib(n)=|
|- fib(n-2)+fib(n-1) , altfel.

fib(5)=fib(3) + fib(4)
=(fib(1)+fib(2))
= 1 +(fib(0)+fib(1))
= 1 +1

= +2
=3 + (fib(2)+fib(3))
= (fib(0)+fib(1))
Programarea calculatoarelor
= 1 +1
= 2 +(fib(1)+fib(2))
= 1 +(fib(0)+fib(1))
= 1 +1
= +2
= +3
= + 5
=8

Mentionam ca daca se tine cont de asociativitatea operatiei de


adunare, calculul valorilor functiei fib, pentru n>1, se reduce la o iteratie. Pentru
un n>1, oarecare, numarul de iteratii este egal cu n-1, cu mult mai mic decât în
cazul recursiv.

fib(2)=fib(0)+fib(1)=1+1=2;
fib(3)=fib(1)+fib(2)=1+2=3;
fib(4)=fib(2)+fib(3)=2+3=5;
fib(5)=fib(3)+fib(4)=3+5=8.

Implementarea recursivitatii în limbajele C si C++

În limbajele C si C++ se pot defini functii recursive fara a fi nevoie de a


specifica în mod explicit acest lucru.

La apelurile recursive ale unei functii, aceasta este reapelata înainte de


a reveni din ea. La fiecare reapel a functiei, parametrii si variabilele locale
(automatice) ale ei se aloca pe stiva într-o zona independenta. De aceea,
aceste date au valori distincte la fiecare reapelare. Nu acelasi lucru se întampla
cu variabilele statice. Ele ocupa tot timpul aceeasi zona de memorie si deci ele
pastreaza valoarea la un reapel.

Orice apel al unei functii conduce la o revenire din functia respectiva la

punctul urmator celui din care s-a facut apelul.


Programarea recursiva
La revenirea dintr-o functie stiva revine la starea dinaintea apelului. De
aceea, orice revenire dintr-un apel recursiv va conduce la revenirea
parametrilor si a variabilelor locale la valorile dinaintea apelului.

Exemplu: Functia fac.

double fac(int n) // calculeaza n!


{
if(n==0)
return 1.0;
else
return n*fac(n-1);
}

Trasare. Consideram apelul: fac(3);


La apelul din functia principala se construieste pe stiva o zona în care
se pastreaza adresa de revenire din functia principala, precum si valoarea
parametrului formal (variabile locale nu apar).

Stiva programului:
-------- Adresa de revenire din functia
rev | adr1 | principala
--------
n | 3 |
--------
(a)

Deoarece n>0, se executa alternativa else. Aceasta contine expresia:


(1) n*fac(n-1)

care autoapeleaza (direct) functia fac. Reapelarea functtei se realizeaza cu

valoarea n-1=3-1=2. Prin aceasta se construieste pe stiva o noua zona, care


contine revenirea la evaluarea expresiei de mai sus, precum si valoarea noua a
parametrului n=2.
Programarea calculatoarelor
Stiva programului:

-------- Adresa de revenire la evaluarea


rev | adr1 | expresiei (1)
--------
n | 2 |
--------
rev | adr1 |
--------
n | 3 |
--------
(b)

La noua reapelare a functiei fact, n=2 > 0, deci din nou se executa
alternativa else.

Evaluarea expresiei (1) conduce la o reapelare cu valoarea n-1=2-


1=1. Se obtine configuratia:

Stiva programului:

-------- Adresa de revenire la evaluarea


rev | adr1 | expresiei (1)
--------
n | 1 |
--------
Programarea recursiva
rev | adr1 |
--------
n | 2 |
--------
rev | adr1 |
--------
n | 3 |
--------
(c)

Din nou n=1 > 0 si deci se face o noua reapelare si se obtine


configuratia:

Stiva programului:

-------- Adresa de revenire la evaluarea


rev | adr1 | expresiei (1)
--------
n | 0 |
--------
rev | adr1 |
Programarea calculatoarelor
--------
n | 1 |
--------
rev | adr1 |
--------
n | 2 |
--------
rev | adr1 |
--------
n | 3 |
--------
(d)

În acest moment n=0, deci se revine din functie cu valoarea 1. Se


curata stiva si se revine la configuratia din figura c. Adresa de revenire permite
continuarea evaluarii expresiei (1). În acest moment n are valoarea 1 si cum s-a
revenit tot cu valoarea 1, se realizeaza produsul:
1*1=1

Dupa evaluarea expresiei (1) se realizeaza o noua revenire tot cu


valoarea 1. Dupa curatirea stivei se ajunge la configuratia din figura b. În acest
moment n=2. Evaluând expresia (1) se realizeaza produsul:
2*1=2

Se revine cu valoarea 2. Dupa curatirea stivei se ajunge la configuratia


din figura a cu n=3. Se evalueaza expresia (1) si se obtine:
3*2=6

Se revine din functie cu valoarea 6 (3!), conform adresei de revenire, în


functia principala din care s-a apelat functia fac.