Sunteți pe pagina 1din 6

Recursivitate

1. Noiunea de algoritm recursiv Un algoritm recursiv se caracterizeaz prin proprietatea c se auto-apeleaza, adic din interiorul lui se apeleaz pe el nsui. Din afara algoritmului facem un prim apel al acestuia, dup care algoritmul se auto-apeleaza de un anumit numr de ori: la fiecare nou auto-apelare a algoritmului, se execut din nou secvena de instruciuni ce reprezint corpul su, eventual cu alte date, crendu-se un aa-numit lan de auto-apeuri recursive. Intuitiv, putem spune c un algoritm recursiv are acelai efect ca i un ciclu: repet execuia unei anumite secvene de instruciuni. Dar, la fel ca n cazul unui ciclu, este necesar ca repetarea s nu aiba 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 in variant nerecursiv (care se mai numete si iterativ), folosind cicluri, ct i n variant recursiv. Rmne n sarcina programatorului s aleag ntre implementarea iterativ i cea recursiv, cntarind 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. Extinznd definiia, vom numi funcie recursiv, o funcie care din corpul ei se apeleaz pe ea nsi. Dar orice funcie 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 ncat s se tind spre ndeplinirea condiiei de execuie fr auto-apelare. 2. Exemple de algoritmi recurisivi. Relaii de recuren iruri definite prin relaii de 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 recurena. Pentru a putea defini recurent un ir, mai trebuie sa indicm primul termen sau primii termeni. irul lui Fibonacci: este un ir de numere ntregi ( F1 ,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 si anteprecedentul su. Pentru un termen oarecare Fk (termenul de rang k), precedentul sau este Fk-1 (de rang k-1), iar antecedentul su este Fk-2 (de rang k-2). Astfel, F1=1, F2=1 i Fk=Fk-1+Fk-2 k 3. 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 1, pentru k = 1 i 2 Fk = care nglobeaz att formula de calcul, ct i valorile termenilor Fk 1 + Fk 2 , pentru k 3 definii separat: 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-1 i Fk-2. Dar aflarea termenilor Fk-1 i 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 Fk trebuie s se auto-apeleze de dou ori, n scopul determinrii termenilor Fk-1 i Fk-2. 1

3. Relaii de recuren pentru expresii matematice Nu numai pentru iruri putem defini relaii de recuren, ci si pentru expresii matematice. Factorialul unui numr natural. Factorialul unui numr natural k este k!=123...(k-1)k (produsul numerelor naturale pn la k), care se mai poate scrie k!=k(k-1)...321. Dar (k-1)...321 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 ns c factorialul lui 0 nu se poate calcula cu relaia anterioar, acesta fiind un caz care trebuie tratat separat. Folosind faptul c 0!=1 (definit matematic), obinem relaia de recurena 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)!.

Suma cu n termeni: suma printre 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.

Pe caz general, irul primelor n numere naturale impare este (1, 3, 5,.., 2n-1). Notnd termenii irului cu a1, 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 elemente naturale este Sn=a1+a2+...+an=1+3+5+...+2n-1. Dac al n-lea termen, cel de rang n, este 2*n-1, atunci al (n-1)-ulea termen este 2(n-1)-1, adic 2*n-3 (pur i simplu am nlocuit pe n cu n-1). Astfel, Sn=1+3+5+...+(2*n-3)+(2*n-1). Dar 1+3+5+... +(2*n-3) reprezint suma primelor n-1 numere naturale impare notata Sn-1, deci Sn = (2n-1)+ Sn-1 . Pentru n=0, avem cazul particular S0=0. Obinem astfel relaia de recuren complet : Sn= (2n-1 ) + S
0, pentru n = 0 n 1 , pentru n > 0

i aceast relaie genereaz un algoritm recursiv: pentru a calcula suma Sn, avem nevoie de suma Sn-1. Aplicaii: 1.Deducei formula termenului general pentru irurile de mai jos: 1 2 3 4 a) 2, 4, 8, 16, ... b) 1, 5, 9, 13, ... c) , , , , ... d)* 0, 1, 1, 3, 7, 17, 41 ... 2 3 4 5 2. deducei o relaie de recuren care s defineasc urmtoarele expresii: a) produsul primelor n numere naturale pare p=2*4*6*.....*(2n); b) suma primelor n numere naturale S=1+2+3+...+n; c) expresia E=1+4+7+...+(3n-2); d) expresia F=3*7*11*...... *(4n-1). 3. Deducei termenii urmtoarelor iruri definite prin relaii de recuren: a) an+1=1+a 2 ( ) n 1, a0=1 n b) xn=2* xn-1+xn-2 ( ) n 2, x0=0, x1=1 2

4. Rolul stivei in executarea subprogramelor recursive Stiva este o succesiune ordonata de elemente, delimitate prin doua capete, in care adaugarea si eliminarea elementelor se poate face pe la un singur capat, numit varful stivei. In orice moment putem scoate din stiva doar elemental care a fost introdus ultimul, motiv pentru care spunem ca stiva functioneaza dupa principiul LIFO(Last In First Out, in traducere Ultimul intrat primul iesit). Altfel spus, extragerea valorilor din stiva se face in ordine inversa introducerii lor. Limbajul C++ dispune de propria sa stiva, numita stiva interna, gestionata de catre compilator, care ocupa o parte din memoria interna rezervata programului. Orice functie foloseste aceasta stiva atunci cand se executa. In momentul in care o functie P apeleaza o functie S, se salveaza automat pe stiva interna adresa de revenire si contextual modulului apelant P( care cuprinde totalitatea variabilelor locale si a parametrilor transmisi prin valoare). Odata cu inchiderea executiei modulului apelat S, se revine in P, restaurandu-se valorile salvate pe stiva interna. In cazul unei functii recursive (care este atat modulul apelant cat si modulul apelat), acest mecanism al stivei este de foarte mare importanta: atunci cand se executa un lant de auto-apeluri recursive, la fiecare auto-apel variabilele locale si parametri functiei recursive se salveaza pe stiva, iar la revenirea in ordine inversa din lant aceste valori se restaureaza de pe stiva. 5. Functii recursive Functii recursive care returneaza o valoare Aplicatie rezolvat: Calculul factorialului unui numar natural. Fiind dat un numar natural n, sa se afiseze n!, folosind o functie recursiva. Reamintim : n!= 1 * 2 *.*n. De exemplu, 4!= 1*2*3*4=24 Algoritmul in varianta ne-recursiva Valoarea lui n!, se calculeaza ca un produs in variabila p. Initializam p cu 1. Intr-un ciclu, in variabila k parcurgem pe rand toate numerele naturale de la 1 la n (valoarea initiala a contorului este k=1, ciclul se executa cat timp k<=n, iar trecerea la pasul urmator se face prin incrementarea k++). La fiecare pas inmultim cu p valoarea curenta a lui k (p=p*k, relatie care se poate scrie si sub forma p*=k). cin >> n; p=1; for(k=1; k<=n; k++) p=p*k; cout<< \n<<p; 1 , pentru k = 0 k!= Varianta recursiv: Plecm de la relaia de recuren: k (k - 1)! , pentru k > 0 Vom scrie o funcie long fact(int k) care primete ca parametru un k intreg i returneaz k!. Observm c pentru a calcula k!, avem nevoie de valoarea lui (k-1)! Care apoi se nmulete cu k. 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, .am.d. se pune ns problema opririi lanului de apeluri recursive la un moment dat. 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 (0!=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

Exemplu : pentru k=3, sintetiznd lanul recursiv obinem: fact(3)=3*fact(2)=3*2*fact(1)=3*2*1*fact(0)=3*2*1*1=6

Programul este: #include<iostream.h> #include<conio.h> int n; long fact(int k) { if(k==0) return 1; else return k*fact(k-1); } void main() { cout<<n=; cin>>n; cout<<fact(n); getch(); }

Comparaie ntre varianta ne-recursiv i varianta recursiv: n varianta ne-recursiv, prin contorul k al ciclului vor trece pe rnd numerele care se nmulesc, 1,2,3,,n-1,n. n varianta recursiv, aceste numere vor fi succesiv valorile parametrului formal k, dar n ordine invers: n,n-1,.,2,1. astfel: Prima valoare a lui k este n, dat de apelul extern fact(n); La fiecare execuie a funciei fact, valoarea lui k se decrementeaz cu 1 prin auto-apelul fact(k-1); Lanul recursiv are loc pn cnd valoarea lui k ajunge la 0.

Aplicatie rezolvat:irul lui Fibonacci S se afieze al n-ulea termen al irului lui Fibonacci, folosind o funcie recursiv. irul are primii doi termeni egali cu 1 i fiecare din urmtorii termeni este egal cu suma dintre termenul precedent i termenul antepreced Algoritmul in varianta recursiva #include<iostream.h> Algoritmul in varianta ne-recursiva int n; #include<iostream.h> long fib(int k) void main() { { int F[20], n, k; if(k==1 || k==2) return 1; cout<<n=; cin>>n; else return fib(k-1)+fib(k-2); F[1]=F[2]=1; } for(k=3; k<=n; k++) void main() { F[k]=F[k-1]+F[k-2]; { cout<<F[k]<< ; cout<<n=; cin>>n; } if(n>0) Comparaie ntre varianta ne-recursiv i cout<<Termenul de rang<<n<<este<<fib(n); varianta recursiv: } n varianta recursiv am afiat un singur termen, spre deosebire de varianta nerecursiv n care se afiau 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 dou ori, iar fib(2) de trei ori. 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(2) fib(1) fib(5) fib(2) fib(2) fib(3) fib(1)

n problemele n care nu este necesar memorarea tuturor termenilor, este preferabil varianta recursiv.

Aplicatie rezolvat: Cel mai mare divizor comun S se scrie o funcie recursiv pentru calculul celui mai mare divizor comun a dou numere naturale a i b, folosind algoritmul lui Eucluid. Varianta ne-recursiva: Algoritmul lui Euclid cu diferene pentru determinarea c.m.md.c 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 aa-b, iar dac a<b se modific b prin scderea bb-a. Cnd cele dou numere a i b au devenit egale se oprete ciclul, obinndu-se c.m.m.d.c. care este a (sau b). Deci ciclul se repet ct timp a este diferit de b. Obs: Algoritmul prezentat nu va funciona dac cel puin unul dintre numere este negativ sau zero. Pentru a evita aceste dou cazuri, poate fi pus condiia suplimentar (a>0) SI (b>0).
#include<iostream.h> void main() { int a,b; cout<<a=; cin>>a; cout<<b=; cin>>b; if(a>0 && b>0) {while(a!=b) if(a>b) a=a-b; else b=b-a; cout<<cmmdc=<<a;} else cout<<nu pot calcula cmmdc; } Exemplu: Fie a=15; b=6 Pas 1: a!=b ? 15!=6 ? da a>b ? 15>6 ? da=> aa-b =>a=15-6, a=9; Pas 2: a!=b ? 9!=6 ? da a>b ? 9>6 ? da=> aa-b =>a=9-6, a=3; Pas 3: a!=b ? 3!=6 ? da a>b ? 3>6 ? nu=> bb-a =>b=6-3, b=3; Pas 4: a!=b ? 3!=3 ? nu => iese din ciclu c.m.m.d.c. (a,b) =a=b=3

Varianta recursiva: Dac a<b atunci variabilei a i se atribuie valoarea expresiei a-b. n varianta recursiv, a este parametru al funciei recursive cmmdc. Pentru ca a s ia valoarea lui 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). Dac a>b atunci b trebuie sa ia valoarea expresiei b-a, lucru care recursiv se produce printr-un autoapel: din functia cmmdc(a,b) se auto-apeleaza cmmdc(a, b-a) (primul parametru a rmnnd nemodificat). Mai rmne s stabilim condiia care ncheie lanul de auto-apeluri recursive. 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. a, daca a = b Pe baza acestor observaii, definiia recursiv a cmmdc(a,b)= cmmdc (a b, b), daca a > b algoritmului este evident: cmmdc (a, b a ), daca a < b Funcia recursiv int cmmdc(int a, int b), care returneaz cel mai mare divizor comun al numerelor a i b, transpune efectiv ntr-o instruciune if aceast definiie:
#include<iostream.h> int a,b; int cmmdc(int a, int b) { if(a==b) return a; else if(a>b) return cmmdc(a-b, b); else return cmmdc(a, b-a); } void main() { cout<<a=; cin>>a; cout<<b=; cin>>b; if(a>0 && b>0) cout<<cmmdc=<<cmmdc(a, b); else cout<<nu pot calcula cmmdc; }

Ex: a=15, b=6. n programul principal facem apelul cmmdc(15, 6); n funcie, a15, b6;a>b=>functia returneaza cmmdc(a-b, b), respectiv cmmdc(9,6); Auto-apelul cmmdc(9,6): a=9,b=6, a>b=>functia returneaza cmmdc(a-b, b), respectiv cmmdc(3,6); Auto-apelul cmmdc(3,6): a=3,b=6, a<b=>functia returneaza cmmdc(a, b-a), respectiv cmmdc(3,3); Auto-apelul cmmdc(3,3): a=3,b=3, a=b=>functia returneaza a, adica 3, care este cel mai mare divizor comun al lui 15 si 6.

Aplicatie: Scriei o funcie recursiv care returneaz suma cifrelor unui numr natural x dat ca parametru. Functii recursive de tip void 5

Limbajul C++ permite implementarea unor funcii recursive care nu returneaz nici o valoare (tipul valorii returnate este void). Acestea nu se deosebesc de cele care returneaz o valoare dect prin faptul c auto-apelul funciei din interiorul ei nu mai este nsoit de cuvntul return. Astfel de funcii se folosesc n general atunci cnd dorim s realizm afiri recursive ale unor caractere, iruri, cuvinte. Aplicatie rezolvat: Se consider programul urmtor: #include<iostream.h> 1. Deduceti sirul de numere afisat de catre program, daca de la void ex(int k) tastatura se introduce valoarea n=3; { a) 4 3 2 1 b) 3 2 1 c) 1 1 1 d) 1 2 3 4 e) 1 2 3 if(k>0) { ex(k-1); 2. Formulati o fraza care sa descrie actiunea functiei recursive ex. cout<<k<< ; } Rezolvare: } 1) Reamintim: Mecanismul funciilor se bazeaz pe urmtorul principiu: n void main( ) momentul apelului unei funcii, se salveaz pe stiv contextul moduluilui { apelant(alctuit din variabilele locale i parametrii transmii prin valoare) i cout<<n=; cin>>n; adresa de revenire. ex(n); } La revenirea n modulul apelant, se restaureaz n ordine invers valorile salvate pe stiv(att ale variabilelor locale ct i ale parametrilor). n cazul unei funcii recursive, aceasta este att modulul apelant ct i modulul apelat. n cazul nostru, funcia ex nu are variabile locale, deci contextul ei, salvat pe stiv, este reprezentat doar de parametrul k. Primul apel ex(n), executat n funcia main (unde valoarea lui n se citete anterior de la tastatur), declaneaz un lan de auto-apeluri recursive. n corpul funciei ex, parametrul formal k este nlocuit cu valoarea n (parametrul actual). n linia if, se testeaz dac valoarea lui k este mai mare dect zero. n caz afirmativ, se auto-apeleaz ex(k-1); n acest moment ns, se salveaz pe stiv contextul modulului apelant ex(n), deci valoarea lui n. Aadar, dac de la tastatur se introduce n=3, vom avea apelul ex(3), apoi: k 3; k>0? 3>0? da => se salveaza pe stiva 3 si are loc auto-apelul ex(2) Auto-apelurile care urmeaza se executa similar: Auto-apelul ex(2): k 2; k>0? 2>0? da => se salveaza pe stiva 2 si are loc auto-apelul ex(1) Auto-apelul ex(1): k 1; k>0? 1>0? da => se salveaza pe stiva 1 si are loc auto-apelul ex(0) Auto-apelul ex(0): k 0; k>0? 0>0? nu => in acest moment s-a incheiat lantul de auto-apeluri pe care il reamintim: functia mainex(3) ex(2) ex(1) ex(0) Urmeaza lantul de reveniri, in ordine inversa: ex(0) ex(1) ex(2) ex(3) functia main Asadar, pentru n=3 au fost aslvate pe stiva valorile 3,2,1, in aceasta ordine. In 1 lantul revenirii, se Prg.princ ex(3) ex(2) ex(1) ex(0) 2 2 restaureaza si se tiparesc 3 3 3 valorile respective in ordinea inversa salvarii, respectiv 1,2,3. restaureaza restaureaza restaureaza Raspuns corect: e)
k=3 afiseaza 3 k=2 afiseaza 2 k=1 afiseaza 1

2) Pe caz general, programul afiseaza primele n numere naturale 1,2,3,...,n in aceasta ordine. 6

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