Documente Academic
Documente Profesional
Documente Cultură
Recursivitate
Recursivitate
Nu numai pentru şiruri putem defini relaţii de recurenţă, ci si pentru expresii matematice.
Factorialul unui număr natural. Factorialul unui număr natural k este k!=1•2•3•...•(k-1)•k (produsul
numerelor naturale până 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 până la k-1). De aici se deduce o aşa numită relaţie de
recurenţă: k!=k•(k-1) ! . Observăm însă că factorialul lui 0 nu se poate calcula cu relaţia anterioară,
acesta fiind un caz care trebuie tratat separat. Folosind faptul că 0!=1 (definit matematic), obţinem relaţia
de recurenţa completă:
Pe caz general, şirul primelor n numere naturale impare este (1, 3, 5,.., 2n-1). Notând termenii
şirului cu a1, a2, ..., an , observăm 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. Obţinem astfel relaţia de recurenţă completă :
0, pentru n = 0
Sn= (2n-1 ) + S
n −1 , pentru n > 0
Şi această relaţie generează un algoritm recursiv: pentru a calcula suma Sn, avem nevoie de suma Sn-1.
Aplicaţii:
1.Deduceţi 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. deduceţi o relaţie de recurenţă care să definească următoarele 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).
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
3
Programul este:
#include<iostream.h>
#include<conio.h>
int n; Comparaţie între varianta ne-recursivă şi varianta recursivă:
long fact(int k) • În varianta ne-recursivă, prin contorul k al ciclului vor trece pe
{ if(k==0) rând numerele care se înmulţesc, 1,2,3,…,n-1,n.
return 1; • În varianta recursivă, aceste numere vor fi succesiv valorile
else parametrului formal k, dar în ordine inversă: n,n-1,….,2,1. astfel:
return k*fact(k-1); Prima valoare a lui k este n, dată de apelul extern fact(n);
} La fiecare execuţie a funcţiei fact, valoarea lui k se
void main() decrementează cu 1 prin auto-apelul fact(k-1);
{ cout<<”n=”; cin>>n; Lanţul recursiv are loc până când valoarea lui k ajunge la 0.
cout<<fact(n);
getch();
}
În problemele în care nu este necesară memorarea tuturor termenilor, este preferabilă varianta recursivă.
4
Aplicatie rezolvată: Cel mai mare divizor comun
Să se scrie o funcţie 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 diferenţe pentru determinarea c.m.md.c a două numere
naturale a şi b se bazează pe scăderi care se repetă într-un ciclu. La fiecare pas, se modifică prin aceste
scăderi, fie a,fie b. Dacă a>b se modifică a prin scăderea a←a-b, iar dacă a<b se modifică b prin
scăderea b←b-a. Când cele două numere a şi b au devenit egale se opreşte ciclul, obţinându-se
c.m.m.d.c. care este a (sau b). Deci ciclul se repetă cât timp a este diferit de b.
Obs: Algoritmul prezentat nu va funcţiona dacă cel puţin unul dintre numere este negativ sau zero.
Pentru a evita aceste două cazuri, poate fi pusă condiţia suplimentară „(a>0) SI (b>0)”.
#include<iostream.h>
void main() Exemplu: Fie a=15; b=6
{ int a,b; Pas 1: a!=b ? 15!=6 ? da a>b ? 15>6 ?
cout<<”a=”; cin>>a; da=> a←a-b =>a=15-6, a=9;
cout<<”b=”; cin>>b; Pas 2: a!=b ? 9!=6 ? da a>b ? 9>6 ?
if(a>0 && b>0) da=> a←a-b =>a=9-6, a=3;
{while(a!=b) Pas 3: a!=b ? 3!=6 ? da a>b ? 3>6 ?
if(a>b) a=a-b; nu=> b←b-a =>b=6-3, b=3;
else b=b-a; Pas 4: a!=b ? 3!=3 ? nu => iese din
cout<<”cmmdc=”<<a;} ciclu
else cout<<”nu pot calcula cmmdc”; 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 funcţiei recursive cmmdc. Pentru ca a să ia valoarea lui a-b,este suficient ca din interiorul
funcţiei 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 rămâne nemodificat).
• Dacă a>b atunci b trebuie sa ia valoarea expresiei b-a, lucru care recursiv se produce printr-un auto-
apel: din functia cmmdc(a,b) se auto-apeleaza cmmdc(a, b-a) (primul parametru a rămânând
nemodificat).
• Mai rămâne să stabilim condiţia care încheie lanţul de auto-apeluri recursive. Lanţul 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 căutat.
a, daca a = b
• Pe baza acestor observaţii, definiţia recursivă a
cmmdc(a,b)= cmmdc (a − b, b), daca a > b
algoritmului este evidentă: cmmdc (a, b − a ), daca a < b
• Funcţia recursivă int cmmdc(int a, int b), care
returnează cel mai mare divizor comun al numerelor a şi b, transpune efectiv într-o instrucţiune if
această definiţie:
#include<iostream.h>
int a,b; Ex: a=15, b=6.
int cmmdc(int a, int b) În programul principal facem apelul cmmdc(15, 6);
{ if(a==b) return a;
else În funcţie, a→15, b→6;a>b=>functia returneaza
if(a>b) return cmmdc(a-b, b); cmmdc(a-b, b), respectiv cmmdc(9,6);
else return cmmdc(a, b-a); Auto-apelul cmmdc(9,6): a=9,b=6, a>b=>functia
} returneaza cmmdc(a-b, b), respectiv cmmdc(3,6);
void main()
{ cout<<”a=”; cin>>a; Auto-apelul cmmdc(3,6): a=3,b=6, a<b=>functia
cout<<”b=”; cin>>b; returneaza cmmdc(a, b-a), respectiv cmmdc(3,3);
if(a>0 && b>0) Auto-apelul cmmdc(3,3): a=3,b=3, a=b=>functia
cout<<”cmmdc=”<<cmmdc(a, b); returneaza a, adica 3, care este cel mai mare divizor
else
cout<<”nu pot calcula cmmdc”;
comun al lui 15 si 6.
}
Aplicatie:
Scrieţi o funcţie recursivă care returnează suma cifrelor unui număr natural x dat ca parametru.
Functii recursive de tip void
5
Limbajul C++ permite implementarea unor funcţii recursive care nu returnează nici o valoare
(tipul valorii returnate este void). Acestea nu se deosebesc de cele care returnează o valoare decât prin
faptul că auto-apelul funcţiei din interiorul ei nu mai este însoţit de cuvântul “return”. Astfel de funcţii
se folosesc în general atunci când dorim să realizăm afişări recursive ale unor caractere, şiruri, cuvinte.
Aplicatie rezolvată: Se consideră programul următor:
#include<iostream.h>
void ex(int k) 1. Deduceti sirul de numere afisat de catre program, daca de la
{ tastatura se introduce valoarea n=3;
if(k>0) a) 4 3 2 1 b) 3 2 1 c) 1 1 1 d) 1 2 3 4 e) 1 2 3
{ ex(k-1);
cout<<k<<” “; 2. Formulati o fraza care sa descrie actiunea functiei recursive ex.
}
} Rezolvare:
void main( ) 1) Reamintim: Mecanismul funcţiilor se bazează pe următorul principiu: în
{ momentul apelului unei funcţii, se salvează pe stivă contextul moduluilui
cout<<”n=”; cin>>n; apelant(alcătuit din variabilele locale şi parametrii transmişi prin valoare) şi
ex(n); adresa de revenire.
}
• La revenirea în modulul apelant, se restaurează în ordine inversă valorile salvate pe stivă(atât ale
variabilelor locale cât şi ale parametrilor). În cazul unei funcţii recursive, aceasta este atât modulul
apelant cât şi modulul apelat. În cazul nostru, funcţia ex nu are variabile locale, deci contextul ei, salvat
pe stivă, este reprezentat doar de parametrul k.
• Primul apel ex(n), executat în funcţia main (unde valoarea lui n se citeşte anterior de la tastatură),
declanşează un lanţ de auto-apeluri recursive. În corpul funcţiei ex, parametrul formal k este înlocuit cu
valoarea n (parametrul actual). În linia if, se testează dacă valoarea lui k este mai mare decât 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.
• Aşadar, 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 main→ex(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
1
3,2,1, in aceasta ordine. In
Prg.princ ex(3) ex(2) ex(1) ex(0) lantul revenirii, se
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 k=2 k=1
afiseaza 3 afiseaza 2 afiseaza 1
2) Pe caz general, programul afiseaza primele n numere naturale 1,2,3,...,n in aceasta ordine.