Sunteți pe pagina 1din 5

8.

RECURSIVITATE
Un algoritm recursiv se caracterizeaz prin proprietatea c se autoapeleaz, adic din interiorul lui se apeleaz pe el
nsui.Din afara algoritmului facem un prim apel al acestuia, dup care algoritmul se auto-apeleaz de un anumit numr de ori: la
fiecare nou auto-apelare, se execut din nou secvena de instruciuni ce reprezint corpul su, crendu-se un aa-numit lan de
auto-apeluri recursive.
Intuitiv, putem spune c un algoritm recursiv are acelai efect ca un ciclu: repet execuia unei anumite secvene de
instruciuni.Dar, la fel ca n cazul unui ciclu, este necesar ca repetarea s nu aib loc la infinit.De aceea, n corpul algoritmului
trebuie s existe cel puin o testare a unei condiii de oprire, la ndeplinirea creia se ntrerupe lanul de auto-apeluri.
Majoritatea algoritmilor repetitivi se pot implementa att n variant nerecursiv (care se mai numete i iterativ), folosind
cicluri, ct i n variant recursiv.Rmne n sarcina programatorului s aleag ntre implementarea iterativ i cea recursiv,
cntrind avantajele i dezavantajele fiecreia, de la caz la caz.Varianta recursiv este recomandat n special pentru problemele
definite prin relaii de recuren, care permit o formulare a rezultatelor mult mai clar i mai concis.Pe de alt parte, funcionarea
algoritmilor recursivi este n general mai greu de urmrit, i, n plus, acetia necesit un timp de execuie mai lung i un spaiu de
memorie mai mare.
Definiie Se numete subprogram recursiv (procedur recursiv sau funcie recursiv) un subprogram care din corpul lui se
apeleaz pe el nsui.
Orice subprogram recursiv trebuie s ndeplineasc dou cerine:
s se poat executa cel puin o dat fr a se auto-apela
toate auto-apelurile s se produc astfel nct s tind spre ndeplinirea condiiei de execuie fr auto-apelare.

Rolul stivei n execuia subprogramelor recursive


Stiva este o succesiune ordonat de elemente, delimitat prin dou capete, n care adugarea i eliminarea elementelor se pote
face pe la un singur capt, numit vrful stivei.n orice moment se poate scoate din stiv doar elementul care a fost introdus
ultimul, motiv pentru care spunem c stiva funcioneaz dup principiul LIFO (Last In First Out, n traducere Ultimul Intrat
Primul Ieit).Altfel spus, extragerea valorilor din stiv se face n ordine invers introducerii lor.
Limbajul Pascal dispune de propria sa stiv, numit stiva intern, gestionat de ctre compilator, care ocup o parte din
memoria intern rezervat programului.Orice subprogram (procedur sau funcie) folosete aceast stiv atunci cnd se execut.
n momentul n care un program (subprogram) P apeleaz un subprogram S, se salveaz automat pe stiva intern adresa de
revenire (adic adresa instruciunii imediat urmtoare apelului subprogramului) i contextul modulului apelant P (care cuprinde
totalitatea variabilelor locale i a parametrilor transmii prin valoare).
n cazul unui subprogram recursiv (care este att modul apelant ct i modul apelat), acest mecanism al stivei este de foarte
mare importan: atunci cnd se execut un lan de auto-apeluri recursive, la fiecare auto-apel variabilele locale i parametrii
subprogramului recursiv se salveaz pe stiv, iar la revenirea n ordine invers din lan aceste valori se restaureaz de pe stiv.

Exemple de algoritmi recursivi.Relaii se recuren


Un ir a1, a2, , an, este o succesiune de valori numite elementele irului, aranjate ntr-o ordine bine definit.Fiecare
element ocup n cadrul irului o poziie fixat, care se numete rangul elementului.
Unele iruri pot fi definite cu ajutorul unor formule care exprim orice termen al irului, ncepnd cu un anumit rang, n
funcie de termenul precedent sau n funcie de termenii precedeni.O astfel de formul se numete relaie de recuren.Pentru a
putea defini recurent un ir trebuie s indicm primul termen sau primii termeni.

irul lui Fibonacci


irul lui Fibonacci este un ir de numere ntregi (F 1, F2,, Fn,) definit recurent astfel: primii doi termeni sunt egali cu 1,
apoi fiecare termen ncepnd cu al treilea, este egal cu suma dintre precedentul i anteprecedentul su.
Pentru un termen oarecare Fk (termenul de rang k), precedentul su este Fk-1 (de rang k-1), iar anteprecedentul su este Fk-2 (de
rang k-2).Astfel F1=1, F2=1 i Fk=Fk-1+Fk-2, k3.
De exemplu: F3=F2+F1=1+1=2 (pentru k=3), F4=F3+F2=2+1=3 (pentru k=4), F5=F4+F3=3+2=5 (pentru k=5) etc.
Se obine irul 1, 1, 2, 3, 5, 8, 13, 21, 34,
Pentru o descriere complet scriem o relaie de recuren care nglobeaz att formula de calcul, ct i valorile termenilor
definii separat:

1, pentru k 1 i 2
Fk
Fk 1 Fk 2 , pentru k 3
Caracterul recursiv al algoritmului pentru determinarea termenilor irului lui Fibonacci este evident.Pentru a calcula un termen
oarecare Fk, avem nevoie de termenii precedeni Fk-1i Fk-2.Dar aflarea termenilor Fk-1i Fk-2 se poate face cu acelai algoritm, doar
c n loc de k avem k-1 respectiv k-2.Prin urmare, algoritmul care calculeaz termenul F k trebuie s se auto-apeleze de dou ori,
n scopul determinrii termenilor Fk-1i Fk-2.
Vom scrie o funcie recursiv fib(k:integer):longint; - care va returna termenul Fk al irului.
deoarece primii doi termeni F1 i F2 sunt 1, rezult c n cazul k=1 sau k=2 funcia returneaz 1.Aceasta este condiia de
oprire a lanului de apeluri recursive.
n caz contrar, conform definiiei, termenul Fk este egal cu precedentul su Fk-1 plus anteprecedentul su Fk-2.Dar
- pentru a obine termenul Fk-1, funcia se va auto-apela cu parametrul k-1 (fib(k-1))
- pentru a obine termenul Fk-2, funcia se va auto-apela cu parametrul k-2 (fib(k-2))
1

- iar apoi, funcia fib(k) va ntoarce suma valorilor returnate n urma celor dou auto-apeluri (fib:=fib(k-1)+fib(k-2))
Deci n interiorul apelului fib(k) apar dou auto-apeluri recursive.n programul principal, se citete mai nti n, apoi afim al
n-lea termen Fn, apelnd fib(n) ca parametru al procedurii writeln.
Program fibonacci;
begin
Var n:integer;
write(n=);readln(n);
Function fib(k:integer):longint;
if n>0 then writeln(Termenul nr ,n:3, este ,fib(n))
begin
end.
if (k=1) or (k=2) then fib:=1
else fib:=fib(k-1)+fib(k-2)
end;
n implementarea recursiv se afieaz un singur termen, spre deosebire de variantele nerecursive n care se afieaz toi
termenii pn la Fn.Mai mult dect att, chiar i pentru a afia un singur termen, funcia fib se auto-apeleaz de mai multe ori cu
acelai parametru.Pentru exemplificare prezentm lanul de apeluri recursive n cazul n=5, unde se observ c apelurile fib(3) i
fib(1) se execut de 2 ori, iar fib(2) de 3 ori.
fib(5)
fib(5)=fib(4)+fib(3)=fib(3)+fib(2)+fib(3)=
=fib(2)+fib(1)+fib(2)+fib(2)+fib(1)=
=1+1+1+1+1=5
fib(4)
fib(3)
fib(3)
fib(2)

fib(2)

fib(2)

fib(1)

fib(1)

Relaii de recuren pentru expresii matematice


Nu numai pentru iruri putem defini relaii de recuren, ci i pentru expresii matematice, aa cum se vede n exemplele
urmtoare.

Factorialul unui numr natural

Factorialul unui numr natural k este k! 1 2 3 ... ( k 1) k (produsul numerelor naturale pn la k), care se mai poate
scrie k! k ( k 1) ... 3 2 1 .Dar ( k 1) ... 3 2 1 este tocmai ( k 1)! (produsul numerelor naturale pn la k-1).de
aici se deduce o aa numit relaie de recuren: k! k ( k 1)! Observm c factorialul lui 0 nu se pate calcula cu relaia
anterioar, acesta fiind un caz care trebuie tratat separat.Folosind faptul c 0!=1 (definit matematic), obinem relaia de recuren
complet.

1, pentru k 0
k!
k (k 1)!, pentru k 0

Caracterul recursiv const n faptul c din corpul algoritmului care calculeaz k! se auto-apeleaz algoritmul pentru a calcula

(k-1)!
Vom scrie o funcie care primete ca parametru un ntreg k i returneaz k! function fact(k:integer):longint; Pentru a calcula
k! este nevoie de valoarea lui (k-1)!, care apoi se nmulete cu k.Ce ar trebui s fac funcia recursiv fact(k), pentru a returna
factorialul lui k ? Ar trebui s se auto-apeleze cu parametrul k-1, apoi valoarea returnat de fact(k-1), respectiv (k-1)!, trebuie
nmulit cu k.Aadar am obinut pn acum relaia: fact(k)=k*fact(k-1)
Dar fact(k-1) se execut la fel, genernd un nou auto-apel, s.a.m.d.Se pune ns problema opririi lanului de apeluri recursive
la un moment dat.Aa cum am spus, n corpul funciei trebuie s existe o condiie care, atunci cnd devine adevrat, ntrerupe
auto-apelurile.Aceasta este dat tocmai de cazul particular k=0, cnd funcia va returna direct valoarea 1.Scriem acum relaia de
recuren sub o alt form, referitoare la funcia fact:

1, pentru k 0
fact(k )
k fact(k 1), pentru k 0

Acum corpul funciei recursive este evident:


dac valoarea lui k este 0, funcia va returna 1 (fact:=1)
n caz contrar, funcia se va auto-apela i va returna (tot prin numele su) valoarea expresiei k*fact(k-1) (fact:=k*fact(k-1))
n programul principal, se citete numrul cruia i se calculeaz factorialul, ntr-o variabil global n i se apeleaz fact(n) ca
parametru al procedurii writeln, pentru a afia valoarea returnat n!
Program factorial;
Var n:integer;
Function fact(k:integer):longint;
begin
begin
write(n=);readln(n);
if k=0 then fact:=1
writeln(n,!=,fact(n))
else fact:=k*fact(k-1)
end.

end;

Rolul stivei n execuia funciei recursive fact


S urmrim funcionarea algoritmului n cazul n care de la tastatur se citete n=3.Din afara funciei recursive se face un
singur apel al acestuia, care va declana lanul de auto-apeluri.n cazul nostru, din programul principal apelm fact(3).
Reamintim c la fiecare auto-apel, se salveaz pe stiv, mpreun cu adresa de revenire i contextul modulului apelant, alctuit
din variabilele sale locale i parametrii transmii prin valoare.Funcia fact nu are variabile locale, deci contextul su este
reprezentat doar de valoarea parametrului k la fiecare apel.
(1) Apelul fact(3): k=3 testeaz k=0 ? nu
3
fact(3) returneaz 3*fact(2) (execut ramura else)
Din corpul lui fact(3) se auto-apeleaz fact(2), dar nainte de aceasta se salveaz pe stiv valoarea parametrului modulului apelant,
adic 3.
(2) Auto-apelul fact(2): k=2 testeaz k=0 ? nu
2
fact(2) returneaz 2*fact(1) (execut ramura else)
3
Modulul apelant este acum fact(2), deci pe stiv se salveaz 2.
(3) Auto-apelul fact(1): k=1 testeaz k=0 ? nu
fact(1) returneaz 1*fact(0) (execut ramura else)
Modulul apelant este acum fact(1), deci pe stiv se salveaz 1.

1
2
3

(4) Auto-apelul fact(0): k=0 testeaz k=0 ? da


fact(0) returneaz 1 i se oprete lanul de apeluri recursive
n stiv se creeaz totui un ultim nivel, pe care se reine parametrul 0.

0
1
2
3

Odat ncheiat lanul de auto-apeluri recursive, ncepe aa-numitul lan de reveniri.


Mai nti se restaureaz n parametrul k valoarea aflat n vrful stivei, adic 0.
Apoi, din fact(0) se revine n fact(1).n parametru k trebuie s se restaureze valoarea 1.
De unde este luat aceasta ? Evident c din vrful stivei ! Pe acest nivel se calculeaz valoarea pe care trebuie s o returneze fact(1), adic 1*fact(0)=1*1=1,deci fact(1) returneaz 1.
Absolut analog, din fact(1) se revine n fact(2), unde restaureaz n k valoarea 2 luat
din vrful stivei i se calculeaz valoarea pe care trebuie s o returneze fact(2), adic
2*fact(1)=2*1*1

1
2
3
2
3

Din fact(2) se revine n fact(3), unde se restaureaz k=3 i se calculeaz 3*fact(2)=3*2*1*1.Stiva a devenit goal i s-a
obinut valoarea lui 3!
Sintetiznd lanul recursiv avem: fact(3)=3*fact(2)=3*2*fact(1)=3*2*1*fact(0)=3*2*1*1

Sume cu n termeni: suma primelor n numere naturale impare


De exemplu pentru n=5, irul primelor cinci numere naturale impare este (1, 3, 5, 7, 9), iar suma acestora este S5=1+3+5+7+9.
Observm c se poate stabili o coresponden ntre rangul unui termen i valoarea sa.Astfel:
- primul termen, cu rangul 1, este 1, care se mai poate scrie 2*1-1
- al doilea termen, cu rangul 2, este 3, care se mai poate scrie 2*2-1
.
- ultimul termen, cu rangul n=5, este 9, adic 2*5-1, adic 2*n-1
n caz general, irul primelor n numere naturale impare este (1, 3, 5, , 2n-1).Notnd termenii irului cu a 1, a2,, an,
observm c un termen oarecare ak (de rang k) are valoarea 2*k-1.Vom spune c irul de mai sus este definit prin formula
termenului general ak=2*k-1.
Suma primelor n numere naturale impare este Sn=a1+a2++an=1+3+5++2n-1.
Dac al n-lea termen, cel de rang n, este 2n-1, atunci al (n-1)-lea termen este 2 (n-1)-1, adic 2n-3 (pur i simplu am nlocuit
pe n cu n-1 ).Astfel Sn=1+3+5++2n-3+2n-1.Dar 1+3+5++2n-3 reprezint suma primelor n-1 numere naturale impare notat
Sn-1, deci Sn=2n-1+Sn-1.Pentru n=0 avem cazul particular S0=0.Obinem astfel relaia de recuren complet:

0, pentru n 0
Sn
2n 1 Sn1 , pentru n 0
i aceast relaie genereaz un algoritm recursiv: pentru a calcula suma Sn, avem nevoie de suma Sn-1.

Cel mai mare divizor comun


3

S se scrie o funcie recursiv pentru calculul celui mai mare divizor comun a dou numere naturale a i b, folosind
algoritmul lui Euclid.
Algoritmul lui Euclid cu diferene pentru calculul celui mai mare divizor comun a dou numere naturale a i b, se bazeaz pe
scderi care se repet ntr-un ciclu.La fiecare pas, se modific prin aceste scderi, fie a, fie b: dac a>b se modific a prin
scderea a:=a-b, iar dac a<b se modific b prin scderea b:=b-a.Cnd cele dou numere au devenit egale, se oprete ciclul,
obinndu-se cel mai mare divizor comun care este a (sau b pentru c sunt egale).Deci ciclul se repet ct timp a<>b.
while a<>b do
if a>b then a:=a-b
else b:=b-a;
Raionamentul care ne conduce la implementarea recursiv a algoritmului de mai sus este foarte simplu.Se pleac de la ce se
ntmpl n varianta nerecursiv la fiecare pas al ciclului.
n corpul ciclului nerecursiv dac a>b atunci variabilei a i se atribuie valoarea expresiei a-b.n varianta recursiv a este
parametru al funciei recursive cmmdc.Consecina ? Pentru ca a s ia valoarea a-b este suficient ca din interiorul funciei
cmmdc(a, b)s se auto-apeleze cmmdc(a-b, b).Astfel parametrul formal a este nlocuit cu parametrul actual a-b sau, altfel
spus, a-b devine noul a (al doilea parametru, b, rmne nemodificat).
Absolut analog, dac a<b atunci b trebuie s ia valoarea expresiei b-a, lucru care recursiv se produce printr-un alt auto-apel:
din funcia cmmdc(a, b) se auto-apeleaz cmmdc(a, b-a) (primul parametru rmnnd nemodificat)
Mai rmne de stabilit condiia care ncheie lanul de auto-apeluri recursive.n varianta nerecursiv ciclul se execut atta timp
ct a<>b.Prin urmare, lanul recursiv se ncheie n momentul n care parametrii a i b au devenit egali, caz n care valoarea lor
comun reprezint cel mai mare divizor comun cutat.Pe baza acestor observaii, definiia recursiv a algoritmului este evident:

a, dac a b

cmmdc(a, b) cmmdc(a b, b), dac a b


cmmdc(a, b a), dac a b

Funcia recursiv cmmdc(a, b:integer):integer; - care returneaz cel mai mare divizor comun al numerelor a i b transpune
efectiv ntr-o instruciune if aceast definiie:
dac a=b, funcia returneaz a, reprezentnd cel mai mare divizor comun (cmmdc:=a) acesta fiind cazul care ntrerupe lanul
recursiv
n caz contrar:
- dac a>b se auto-apeleaz cmmdc(a-b,b), funcia returnnd valoarea ntoars de ctre acest auto-apel
(cmmdc:=cmmdc(a-b, b))
- n caz contrar (a<b) se auto-apeleaz similar cmmdc(a, b-a) (cmmdc:=cmmdc(a, b-a))
n programul principal se citesc cele dou numere a i b, apoi:
- dac a>0 i b>0, se apelez cmmdc(a, b) afind valoarea returnat
- n caz contrar se tiprete un mesaj (algoritmul prezentat mai sus nu va funciona dac cel puin unul dintre numere este
negativ sau zero)
Program cel_mai_mare_divizor_comun;
begin
Var a,b:integer;
write(dati numerele:);readln(a,b);
Function cmmdc(a, b:integer):integer;
if (a>0) and (b>0) then writeln(c.m.m.d.c=,cmmdc(a,b))
begin
else writeln(Nu calculez c.m.m.d.c.)
if a=b then cmmdc:=a
end.
else if a>b then cmmdc:=cmmdc(a-b, b)
else cmmdc:=cmmdc(a, b-a)
end;
S urmrim evoluia algoritmului recursiv pentru a=15 i b=6.
n programul principal se face apelul cmmdc(15, 6).n funcie a>b funcia returneaz cmmdc(a-b, b), adic cmmdc(9, 6)
Auto-apelul cmmdc(9, 6): a=9, b=6, a>b funcia returneaz cmmdc(a-b, b), adic cmmdc(3, 6)
Auto-apelul cmmdc(3, 6): a=3, b=6, a<b funcia returneaz cmmdc(a, b-a), adic cmmdc(3, 3)
Auto-apelul cmmdc(3, 3): a=3, b=3, a=b funcia returneaz a, adic 3, care este cel mai mare divizor comun al lui 15 i 6.
Din punct de vedere al eficienei, putem alege oricere dintre variante, sindura deosebire constnd n faptul c varianta
nerecursiv este ceva mai scurt.
Procedurile recursive nu aduc nimic nou fa de ceea ce s-a spus despre funciile recursive.Singura diferen este cea
cunoscut deja: o procedur recursiv nu returneaz nici o valoare.

RECURSIVITATE INDIRECT
Exist dou tipuri de subprograme (proceduri i funcii) recursive:
a) Subprograme direct recursive: un subprogram Q n corpul cruia exist
cel puin un auto-apel ( Q apeleaz pe Q ) se numete subprogram direct
recursiv. Acest gen de subprograme a fost studiat n detaliu pn acum.

procedure Q();

begin
.
Q();
.

end;
procedure B();forward;
procedure A();
.
begin
..
B();
..
end;
procedure B;
..
begin

A();

end;
Folosind dou funcii recursive, s se calculeze valoarea funciilor matematice f(x) i g(x), pentru o valoare a argumentului x
introdus de la tastatur.
b) Subprograme indirect recursive:dou subprograme A i B se numesc indirect
recursive dac se apeleaz reciproc (A apeleaz B i B apeleaz A).
Pentru a putea fi executat, orice subprogram (nerecursiv sau recursiv) trebuie s
fie scris naintea modulului apelant. Dac se dorete scrierea subprogramului dup
modulul apelant, atunci trebuie fcut o declaraie anticipat a subprogramului
respectiv, cu directiva forward.
Presupunem c avem dou proceduri A i B ( scrise n aceast ordine ), care se
apeleaz reciproc. Procedura A apeleaz procedura B, dar modulul apelat B se afl
dup cel apelant A. n consecin, pentru ca acest apel s poat fi executat, trebuie
fcut o declaraie anticipat a procedurii B, folosind directiva forward. Dac s-a
declarat anticipat procedura B, cnd se scrie efectiv aceasta nu va mai conine lista
parametrilor formali (a cazul unei funcii va lipsi din antet i tipul valorii returnate).
Situaia ar fi fost una similar dac s-ar fi scris procedurile n ordinea B, A.n acest
caz trebuia declarat anticipat procedura A.

1 (xg ), pentru x 3 5, pentru x 0


xf )( 2 , xg )(
x , pentru x 3 2*x (xf 1), pentru x 0
Funciile recursive F(x) i G(x) returneaz valoarea funciilor matematice f(x), respectiv g(x), pentru o valoare a argumentului
x.Cele dou funcii sunt recursive, dar una prin intermediul celeilalte.Altfel spus, din interiorul funciei F(x) se apeleaz G(x)
(pentru x<=3), iar din funcia G(x) de apeleaz F(x+1) (pentru x>=0).Aadar, datorit faptului c cele dou funcii se apeleaz
reciproc, se poate spune c avem de-a face cu un caz tipic de recursivitate indirect.
Exemplificm modul n care recurenele de mai sus calculeaz valoarea funciei f(x) pentru x=2:
f(2)=1+g(2)=1+2*2+f(3)=1+2*2+1+g(3)=1+2*2+1+2*3+f(4)=1+2*2+1+2*3+4 2=28
Aa cum s-a precizat n partea teoretic, atunci cnd dou subprograme se apeleaz reciproc, unul dintre ele trebuie declarat
anticipat cu directiva forward.Am optat pentru declararea anticipat a funciei G:
Function G(x:integer):integer;forward;
n acest caz, antetul funciei G nu va mai conine lista parametrilor formali i tipul valorii returnate, iar antetul funciei F este
cel obinuit:
Function G; function F(x:integer):integer;
Corpurile celor dou funcii traduc efectiv relaiile de recuren din enun.Astfel, pentru funcia F(x): dac x>3 funcia
returneaz x*x, iar n caz contrar returneaz 1 plus valoarea ntoars de ctre G(x).Analog descriem funcia G(x): dac x<0
returneaz 5, iar n caz contrar returneaz 2*x plus valoarea ntoars de ctre F(x+1)
n programul principal se citete o valoare a argumentului x, apoi se apeleaz succesiv F(x) i G(x) cu afiarea valorii
returnate.
Program functii;
Var x:integer;
Function G(x:integer):integer;forward;
begin
Function F(x:integer):integer;
write(x=);readln(x);
Begin
writeln(f(,x,)=,F(x));
if x>3 then F:=x*x
writeln(g(,x,)=,G(x));
else F:=2*x+G(x)
end.
end;
function G;
begin
if x<0 then G:=5
else G:=2*x+F(x+1)
end;

S-ar putea să vă placă și