De ce contează?
Imaginează-ți două oglinzi față în față: fiecare reflectă imaginea celeilalte, la nesfârșit. Recursivitatea funcționează la fel — o funcție se „privește” pe sine. Dar, spre deosebire de oglinzi, ea trebuie să știe când să se oprească. Cât face 5!? Dacă știi deja că 4! = 24, atunci 5! = 5 × 24 = 120: rezolvi problema mare folosind soluția uneia mai mici.
Ce este recursivitatea?
O funcție este recursivă când se apelează pe ea însăși cu un argument mai mic. O folosim pentru că unele probleme se definesc natural prin ele însele: factorialul, parcurgerea unui arbore, șirul lui Fibonacci. În loc să scriem un cod complicat cu bucle, descriem doar cum se reduce problema la una mai simplă — restul îl face calculatorul, repetând automat reducerea.
Orice recursivitate are două părți obligatorii:
- Cazul de bază — condiția de oprire (fără el, funcția rulează la infinit)
- Pasul recursiv — problema se reduce la o versiune mai mică
Lipsa oricăreia dintre ele este greșeala clasică: fără caz de bază obții recursie infinită; fără pas care micșorează argumentul, nu ajungi niciodată la caz de bază.
Factorialul
Definiția matematică:
n! = n × (n-1) × ... × 2 × 1
0! = 1 (prin convenție)Rescrisă recursiv: n! = n × (n-1)!
Cazul de bază este 0! = 1. Fără el, funcția ar apela (-1)!, (-2)!, ...
până la stack overflow.
Implementare recursivă C++
#include <iostream>
using namespace std;
long long factorial(int n) {
if (n == 0) return 1; // caz de baza
return n * factorial(n - 1); // pas recursiv
}
int main() {
cout << factorial(5) << endl; // 120
cout << factorial(10) << endl; // 3628800
return 0;
}Stiva de apeluri
Când apelăm factorial(4), fiecare apel îl așteaptă pe cel mai mic. Mai întâi se coboară până la cazul de bază, punând câte un cadru pe stivă, apoi se urcă înmulțind răspunsurile întoarse:
COBORÂRE (apeluri puse pe stivă) URCARE (returnuri, de jos în sus)
───────────────────────────────── ────────────────────────────────────
factorial(4) = 4 × factorial(3) ┐ ↑ 24 = 4 × 6
factorial(3) = 3 × factorial(2) │ ↑ 6 = 3 × 2
factorial(2) = 2 × factorial(1)│ ↑ 2 = 2 × 1
factorial(1) = 1 × factorial(0)│ ↑ 1 = 1 × 1
factorial(0) = 1 ◄── caz de bază ──────────────► 1Cele cinci apeluri stau simultan pe stivă, fiecare reținând cu ce trebuie să își înmulțească rezultatul întors. La cazul de bază factorial(0) = 1 oprirea declanșează urcarea: răspunsurile se întorc în ordine inversă, înmulțindu-se pe rând — 1 → 1 → 2 → 6 → 24.
Produsele parțiale se construiesc pe drumul de întoarcere, fiecare folosindu-l pe cel de dedesubt:
| rezultat | 1 | 1 | 2 | 6 | 24 |
| nivel | 0! | 1! | 2! | 3! | 4! |
bază | final |
Vizualizează recursivitatea ca o stivă de scrisori: pui câte una pe stivă (apeluri recursive), iar când ajungi la bază le deschizi de sus în jos (returnuri).
Vizualizare
Urmărește cum fiecare apel factorial(n) se adaugă pe stivă până la cazul de bază, apoi cum returnurile se înmulțesc înapoi, de jos în sus:
Implementare iterativă (comparație)
long long factorialIterativ(int n) {
long long rezultat = 1;
for (int i = 2; i <= n; i++) {
rezultat *= i;
}
return rezultat;
}Ambele variante fac aceleași n înmulțiri, deci au același timp. Diferența este la
spațiu: versiunea recursivă ține pe stivă n apeluri simultan, pe când cea iterativă
folosește o singură variabilă.
| Versiune | Timp | Spațiu |
|---|---|---|
| Recursivă | O(n) | O(n) (stiva de apeluri) |
| Iterativă | O(n) | O(1) |
Capcane reale la factorial:
- Overflow tăcut.
intse depășește deja la 13! (3 miliarde > 2,1 miliarde), iar rezultatul devine un număr „aiurea”, fără eroare. Foloseștelong long— dar nici el nu ajunge de la 21! încolo (atunci e nevoie de calcul modulo un prim sau big integer). - Caz de bază greșit. Scrii
if (n == 1) return 1;și uiți de0!. Lafactorial(0)funcția sare peste oprire și apeleazăfactorial(-1),factorial(-2)… → stack overflow. - Tipul de return. Dacă declari
int factorial(...)dar înmulțești corect, valoarea se taie tot laint, oricât de mare ai facen. Tipul de return contează la fel de mult ca tipul variabilei.