Documente Academic
Documente Profesional
Documente Cultură
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.
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)
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
end;
1
2
3
0
1
2
3
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
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.
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
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.