Documente Academic
Documente Profesional
Documente Cultură
2020-2021
Curs 1
Cuprins
1. RECURSIVITATE
• Noţiuni introductive
• Funcţii recursive. Incărcarea stivei
• Ieşirea din recursivitate
• Concluzii
• Exemple
2
1. RECURSIVITATE
1.1. Noţiuni introductive
3
• Factorialul:
• n!= 1*2*3*...*n, ca şi definiţie nerecursivă
• n! = n * (n-1)!, ca şi definiţie recursivă
• 0! = 1
• Combinările:
C(n,k) = C(n-1, k) + C(n-1, k-1), C(n,0) =1; C(n,1)=n;
• Aranjamentele:
A(n,k ) = n*A(n-1, k-1), A(n,1)=n; 4
• cmmdc(m, n) =
• m, dacă n=0
• cmmdc(n, m%n), altfel
• cmmdc(m, n) =
• m, dacă m=n
• cmmdc(m-n, n), dacă m>n
• cmmdc(m, n-m), dacă m<n
5
• Tipuri de recursivitate:
– Date definite recursiv (la nivelul limbajului C/C++ prin
intermediul structurilor ce au un câmp de tip pointer la
structura ce tocmai se definește)
– Proceduri/Funcții recursive
6
1.2. Funcţii recursive
7
• Factorialul
• n! = n * (n-1)!
• 0! = 1
int factorial(int n)
{
if (n == 0)
return 1;
else
return n*factorial(n-1);
}
void main(void)
{
cout << “Factorial de 3 = ” << factorial(3) << endl;
}
8
• Orice apel recursiv al unei funcţii poate fi văzut ca o nouă
instanţă de calcul pentru acea funcţie
9
10
#include <iostream>
#include <conio.h>
using namespace std;
void main(void)
{
int n = 4;
cout << "\nStart proces recursiv: Factorial(" << n << ")...\n" ;
cout << "\n\nFactorial(" << n << ") = " << factorial(n) << endl;
_getch();
}
11
int factorial(int n)
{
int val;
cout << "\n\t factorial(" << n << ")";
if (n == 0)
{
cout << "\n\n\t return 1";
return 1;
}
else
{
val = n*factorial(n-1);
cout << "\n\t return " << val;
return val;
}
}
12
• La apelul unei funcţii (inclusiv la un autoapel), pe stivă se
vor depune:
– parametrii de apel (valorile parametrilor transmişi prin valoare sau
adresele parametrilor transmişi prin referinţă) în locațiile rezervate
parametrilor formali
– variabilele locale (dacă sunt)
– adresa de revenire la instrucţiunea imediat următoare apelului
13
Stiva program
adresa2
Stiva program 0
adresa2 adresa2
Stiva program 1 1
Stiva program 2 2 2
3 3 3 3
14
• Şirul lui Fibonacci
• f(n) = 0, dacă n=0
• f(n) = 1, dacă n=1
• f(n) = f(n-1) + f(n-2), dacă n>=2
int fib(int n)
{
if (n < 2)
return n;
else
return (fib(n-1) + fib(n-2)); // recursivitate neliniara
}
void main(void)
{
cout << “Fibonacci de 7 = ” << fib(7) << endl;
}
15
• Metoda este însă foarte ineficientă, timpul de calcul fiind
de ordinul Φ (numit secţiunea de aur) ce reprezintă un
timp exponenţial
• Ineficienţa este dată şi de faptul că se evaluează unele
elemente ale şirului de mai multe ori:
16
17
• Primele n numere din şirul lui Fibonacci se pot calcula
iterativ folosind un şir ajutător :
int fib(int n)
{
int i=1, j=0, k;
for(k=1; k < n; k++)
{
j = i+j;
i = j-i;
}
return j;
}
19
• Combinările
C(n,k) = C(n-1,k) + C(n-1,k-1); C(n,0) =1; C(n,1)=n;
int comb(int n, int k)
{
if(k==0) return 1;
if(k==1) return n;
return (comb(n-1, k) + comb(n-1, k-1)); // recursivitate neliniara
}
21
• Precizări
– Implementările anterioare sunt simpliste
– Implementările reale ar trebui să mai aibă în vedere și:
• Verificări/validări ale parametrilor de apel coroborate cu tipul
de date asociat și cu constrângeri specifice problemei de
rezolvat (de exemplu parametrul funcției factorial() este de tip
întreg cu semn, adică funcția ar putea fi apelată și cu valori
negative ale parametrului de apel...)
• Estimarea domeniului de valori pentru tipul returnat și alegerea
unui tip asociat corespunzator (unsigned, unsigned long.
double, ...)
22
• Câteva concluzii:
– Un algoritm recursiv presupune mai multe nivele de
lucru
– Pe fiecare nivel se întâmplă acelaşi lucru dar la o altă
dimensiune a problemei (de obicei, mai mică) (avem
așa numitul caz recursiv)
– Există cel puţin un nivel ce admite o rezolvare directă
(cazul/cazurile de bază)
• Posibilităţi:
– se asociază funcţiei recursive un parametru întreg n şi apelul se
face cu valorile (n-1), (n-2),… cât timp n>0
– se asociază funcţiei recursive un parametru logic iar apelul se
face atâta timp cât parametrul are valoarea TRUE
24
1.4 Concluzii
• La implemetarea unei funcții recursive, se verifică
următoarele:
– Existența cazului/cazurilor de bază și returnarea de valori
corecte
– Existența cazului/cazurilor recursive și returnarea de valori
corecte
– Evitarea recursivității infinite
int suma_cifre_recursiv(int n)
{
if (n < 10)
return n;
else
return n%10 + suma_cifre_recursiv((int)(n/10));
}
int suma_cifre_iterativ(int n)
{
int s=0;
while(n != 0) {
s += n%10;
n /= 10;
}
return s; 26
}
int suma_cifre_recursiv(int n)
{
int val1, val2;
cout << "\n\t suma_cifre_recursiv(" << n << ")";
if (n<10)
{
cout << "\n\n\t return " << n;
return n;
}
else
{
val1 = n%10;
val2 = suma_cifre_recursiv((int)(n/10));
cout << "\n\t return " << val1 << " + " << val2;
return val1 + val2;
}
}
27
• Exemplul 2: suma elementelor unui vector numeric
– Abordare:
• Recursivă:
– ultimul element + suma vectorului format din primelor (n-1) elemente
– cazul de bază: vectorul are un singur element
• Nerecursivă: suma, element cu element
29
• Există tipuri de probleme care de obicei se rezolvă cu
ajutorul funcţiilor recursive, în general în cazul
problemelor a căror metode de rezolvare se pot defini
recursiv:
– metode de divizare, divide et impera;
– metode de căutare cu revenire, backtracking;
– metode în care datele sunt definite recursiv, etc.
30