Școala Româno-Britanică
Modulul 5 Clasa a X-a | C++

Ora 12: Proiect integrat recursiv

Ce vei învăța astăzi: Să construiești o aplicație completă care combină mai multe funcții recursive: factorial, Fibonacci, CMMDC, suma cifrelor și inversarea unui șir (scrierea caracterelor în ordine inversă)

Rezultat practic: Scrii un proiect C++ complet, cu meniu, validări și cinci funcții recursive, integrând tot ce ai învățat în modul (Competența 6.5).

Competența 6.5 Săptămâna 30

Înainte de a începe

De ce contează? Proiectul final din acest modul cere exact aceste cinci funcții recursive integrate cu meniu și validări, iar arhitectura prezentată aici este modelul pe care îl vei folosi.

Ce știi deja?

Din ora 11, poți parcurge recursiv o matrice pe linii și coloane? Care este cazul de bază când termini de parcurs o matrice?

Ideea-cheie: Modularizarea (împărțirea programului în funcții independente, fiecare cu o singură responsabilitate): fiecare funcție recursivă este scrisă și testată separat, apoi doar apelată din meniu, fără a modifica corpul funcțiilor.
SINTEZĂ TEORETICĂ ȘI EXEMPLE GHIDATE

Ce vei învăța astăzi?

După această lecție vei proiecta arhitectura unei aplicații modulare, vei scrie cinci funcții recursive complete și vei construi un meniu funcțional cu validarea intrărilor (verificarea datelor citite înainte de utilizare).

Arhitectura proiectului integrat
Cinci funcții recursive în C++
Meniu interactiv și validări
Proiect complet stil BAC

Arhitectura proiectului integrat

Definiție: Un proiect integrat este o aplicație care reunește mai multe funcții independente, fiecare cu o responsabilitate clară, coordonate de un program principal. Aplicația noastră oferă cinci prelucrări recursive: factorial, fib, cmmdc, suma_cifre și inversare_sir.

Principiul modularizării: fiecare funcție este scrisă, testată și corectată separat; integrarea lor în meniu nu modifică corpul funcțiilor, ci doar le apelează.

Analogia: o firmă cu cinci departamente

Imaginează-ți o firmă în care fiecare departament rezolvă un singur tip de sarcină: departamentul de calcul primește un număr și întoarce factorialul, altul întoarce termenul Fibonacci, și așa mai departe. Recepția (funcția main) primește cererea clientului, o trimite departamentului potrivit și afișează răspunsul. Dacă un departament funcționează greșit, îl reparăm pe el, fără să atingem celelalte.

Recepția

Este funcția main: afișează meniul și trimite opțiunea mai departe

Departamentele

Sunt funcțiile recursive, fiecare cu o singură responsabilitate

Cererea clientului

Este opțiunea citită: 1, 2, 3, 4 sau 5

Funcție Ce calculează Cazul de bază
factorial(n) n! = n · (n − 1) · ... · 1 n == 0 → 1
fib(n) Termenul n din șirul lui Fibonacci n == 0 → 0, n == 1 → 1
cmmdc(a, b) Cel mai mare divizor comun b == 0 → a
suma_cifre(n) Suma cifrelor lui n n == 0 → 0
inversare_sir(s, i, j) Inversează caracterele unui șir i ≥ j → oprire

Aplicația completă cu cele cinci funcții recursive

Programul afișează un meniu cu cinci opțiuni numerotate și opțiunea 0 pentru ieșire. Pentru fiecare opțiune, citește datele necesare, apelează funcția recursivă potrivită și afișează rezultatul. Structura do...while asigură afișarea meniului cel puțin o dată.

Algoritm proiect integrat: repetă afișează meniul: 1 - Factorial 2 - Fibonacci 3 - CMMDC 4 - Suma cifrelor 5 - Inversare șir 0 - Ieșire citește opțiunea dacă opțiunea == 1 atunci citește n; afișează factorial(n) dacă opțiunea == 2 atunci citește n; afișează fib(n) dacă opțiunea == 3 atunci citește a, b; afișează cmmdc(a, b) dacă opțiunea == 4 atunci citește n; afișează suma_cifre(n) dacă opțiunea == 5 atunci citește s; afișează inversarea lui s cât timp opțiunea != 0
Program C++ - proiect integrat cu cinci funcții recursive
#include <iostream>
#include <cstring>
using namespace std;

// Pasul 1: factorialul recursiv
long long factorial(int n)
{
    if (n == 0)
        return 1;
    return n * factorial(n - 1);
}

// Pasul 2: termenul Fibonacci recursiv
long long fib(int n)
{
    if (n == 0)
        return 0;
    if (n == 1)
        return 1;
    return fib(n - 1) + fib(n - 2);
}

// Pasul 3: CMMDC prin algoritmul lui Euclid, recursiv
int cmmdc(int a, int b)
{
    if (b == 0)
        return a;
    return cmmdc(b, a % b);
}

// Pasul 4: suma cifrelor, recursiv
int suma_cifre(int n)
{
    if (n == 0)
        return 0;
    return n % 10 + suma_cifre(n / 10);
}

// Pasul 5: inversarea unui sir de caractere, recursiv, prin interschimbari
void inversare_sir(char s[], int i, int j)
{
    if (i >= j)
        return;
    char aux = s[i];
    s[i] = s[j];
    s[j] = aux;
    inversare_sir(s, i + 1, j - 1);
}

int main()
{
    int opt, n, a, b;
    char s[101];
    do
    {
        // Pasul 6: afisam meniul
        cout << "1. Factorial" << endl;
        cout << "2. Fibonacci" << endl;
        cout << "3. CMMDC" << endl;
        cout << "4. Suma cifrelor" << endl;
        cout << "5. Inversare sir" << endl;
        cout << "0. Iesire" << endl;
        cout << "Optiunea: ";
        cin >> opt;
        // Pasul 7: executam operatia ceruta
        if (opt == 1)
        {
            cout << "n = ";
            cin >> n;
            cout << "Factorial = " << factorial(n) << endl;
        }
        else if (opt == 2)
        {
            cout << "n = ";
            cin >> n;
            cout << "Fibonacci = " << fib(n) << endl;
        }
        else if (opt == 3)
        {
            cout << "a = ";
            cin >> a;
            cout << "b = ";
            cin >> b;
            cout << "CMMDC = " << cmmdc(a, b) << endl;
        }
        else if (opt == 4)
        {
            cout << "n = ";
            cin >> n;
            cout << "Suma cifrelor = " << suma_cifre(n) << endl;
        }
        else if (opt == 5)
        {
            cout << "sir = ";
            cin >> s;
            inversare_sir(s, 0, strlen(s) - 1);
            cout << "Inversat = " << s << endl;
        }
        else if (opt != 0)
        {
            cout << "Optiune inexistenta!" << endl;
        }
    } while (opt != 0);
    return 0;
}

Verificări rapide pentru fiecare funcție

  • factorial(5) = 120
  • fib(10) = 55
  • cmmdc(48, 18) = 6
  • suma_cifre(1234) = 10
  • inversare_sir("abc") = "cba"

Meniul interactiv și validarea intrărilor

Definiție: Un meniu interactiv este o structură de control care afișează opțiunile disponibile, citește alegerea utilizatorului și execută acțiunea corespunzătoare, repetând procesul până la alegerea ieșirii. Validarea verifică dacă valoarea citită este permisă înainte de a fi folosită.

De ce este importantă validarea?

  • Opțiunile din afara intervalului 0-5 trebuie să afișeze un mesaj, nu să termine programul cu eroare.
  • Un n negativ la factorial ar produce apeluri recursive infinite, deoarece n nu mai ajunge niciodată la 0.
  • Un n prea mare la Fibonacci ar face programul extrem de lent din cauza recalculărilor.

De reținut

  • Funcțiile se definesc înaintea funcției main, pentru a putea fi apelate.
  • Bucla do...while este potrivită pentru meniuri: corpul se execută cel puțin o dată.
  • Fiecare funcție are un singur scop, un caz de bază clar și un pas recursiv care reduce problema.
  • Un else if (opt != 0) final tratează opțiunile inexistente.

Exercițiu de completare

Completează enunțul despre proiectul integrat cu cuvintele potrivite:

Proiectul integrat reunește mai multe funcții ______, fiecare cu o singură responsabilitate. Meniul este implementat cu o structură ______, care execută corpul cel puțin o dată. Opțiunile inexistente trebuie tratate cu un mesaj, proces numit ______. Funcțiile sunt definite înaintea funcției ______.

Bancă de cuvinte:
recursive do...while validare main

Exercițiu de completare

Completează funcțiile recursive ale proiectului:
long long factorial(int n)
{
    if (n == 0)
        ______ 1;
    return n * factorial(______);
}
int cmmdc(int a, int b)
{
    if (______)
        return a;
    return cmmdc(b, a ______);
}
Bancă de cuvinte:
return n - 1 b == 0 % b

Test de înțelegere

Test rapid - proiect integrat:

1. Structura potrivită pentru un meniu este:

2. Cazul de bază al funcției inversare_sir este:

3. Dacă utilizatorul introduce opțiunea 9, programul corect:

Ai grijă la: validarea intrărilor - fără ea, un n negativ sau o opțiune inexistentă poate duce la apeluri recursive infinite sau la rezultate greșite; verifică întotdeauna domeniul.

Aprofundare

Eficiența în proiecte integrate. Într-un proiect real, funcțiile recursive costisitoare (Fibonacci naiv) pot fi optimizate cu memoizare sau variante iterative. Proiectul devine astfel nu doar corect, ci și rapid pentru valori mari de intrare, un criteriu des întâlnit în baremele de evaluare.

Funcții auxiliare și protecția intrărilor. O versiune robustă adaugă funcții precum citire_validata(), care citește valorile în buclă până când acestea respectă restricțiile (n ≥ 0, opțiune în intervalul 0-5). Astfel, funcțiile recursive nu primesc niciodată valori care le-ar duce în recursie infinită.

Extinderea proiectului. Adăugarea unei a șasea funcții (de exemplu, numărul de cifre sau turnurile din Hanoi) presupune doar trei pași: definirea funcției, afișarea noii opțiuni în meniu și o nouă ramură în structura de decizie. Această extensibilitate este dovada calității modularizării.

Reutilizarea codului. Funcțiile proiectului pot fi copiate direct în alte programe: ele nu depind de meniu, ci doar de parametrii lor. Acesta este motivul pentru care se scriu fără afișări interne, întorcând doar rezultatul.

APLICARE ȘI REZOLVARE DE PROBLEME

PbInfo - Inversarea unui șir de caractere recursiv

Enunț:

Cerință: Se citește un șir de caractere s. Să se afișeze șirul inversat, obținut prin interschimbarea caracterelor, folosind un subprogram recursiv.

Date de intrare:

Programul citește de la tastatură un șir de caractere cu cel mult 100 de caractere.

Date de ieșire:

Programul va afișa pe ecran șirul inversat.

Restricții și precizări:

Șirul conține doar litere mici ale alfabetului englez. Rezolvarea se face cu un subprogram recursiv care realizează interschimbări.

Exemplu:

Intrare:

informatica

Ieșire:

acitamrofni
Cum gândim soluția:
  1. Definim funcția inversare_sir(s, i, j) cu indicii extremităților
  2. Cazul de bază: dacă i ≥ j, ne oprim
  3. Interschimbăm s[i] cu s[j]
  4. Reapelăm cu i + 1 și j - 1
Scrie soluția în C++:
Soluție corectă:
#include <iostream>
#include <cstring>
using namespace std;

void inversare_sir(char s[], int i, int j)
{
    if (i >= j)
        return;
    char aux = s[i];
    s[i] = s[j];
    s[j] = aux;
    inversare_sir(s, i + 1, j - 1);
}

int main()
{
    char s[101];
    cin >> s;
    inversare_sir(s, 0, strlen(s) - 1);
    cout << s;
    return 0;
}

Ce învățăm: interschimbarea se face înaintea apelului recursiv; indicii se apropie unul de celălalt, deci numărul de apeluri este jumătate din lungimea șirului.

PbInfo - Aplicație cu meniu și funcții recursive

Enunț:

Cerință: Se cere un program cu meniu care oferă următoarele opțiuni: 1 - factorial(n), 2 - suma cifrelor lui n, 0 - ieșire. Pentru fiecare opțiune numerică, programul citește n și afișează rezultatul, folosind funcții recursive. Meniul se reafișează până la alegerea opțiunii 0.

Date de intrare:

Programul citește opțiuni și valori de la tastatură, până la introducerea opțiunii 0.

Date de ieșire:

Programul va afișa rezultatul operației alese pentru fiecare opțiune.

Restricții și precizări:

0 ≤ n ≤ 20 pentru factorial; 0 ≤ n ≤ 2.000.000.000 pentru suma cifrelor.

Exemplu:

Intrare:

1 5
2 1234
0

Ieșire:

120
10
Cum gândim soluția:
  1. Definim factorial(n) și suma_cifre(n) recursive
  2. În main, folosim do...while pentru a afișa meniul și a citi opțiunea
  3. Pentru fiecare opțiune, citim n, apelăm funcția și afișăm rezultatul
  4. Bucla se termină când opțiunea citită este 0
Scrie soluția în C++:
Soluție corectă:
#include <iostream>
using namespace std;

long long factorial(int n)
{
    if (n == 0)
        return 1;
    return n * factorial(n - 1);
}

int suma_cifre(int n)
{
    if (n == 0)
        return 0;
    return n % 10 + suma_cifre(n / 10);
}

int main()
{
    int opt, n;
    do
    {
        cout << "1. Factorial" << endl;
        cout << "2. Suma cifrelor" << endl;
        cout << "0. Iesire" << endl;
        cout << "Optiunea: ";
        cin >> opt;
        if (opt == 1)
        {
            cin >> n;
            cout << factorial(n) << endl;
        }
        else if (opt == 2)
        {
            cin >> n;
            cout << suma_cifre(n) << endl;
        }
    } while (opt != 0);
    return 0;
}

Ce învățăm: meniul combină citirea opțiunii, validarea implicită (opțiunile 1 și 2) și apelul funcțiilor recursive, într-o structură do...while.

EXERCIȚII PRACTICE

Exercițiul Practic: Extinderea proiectului integrat

Sarcini de lucru

Completează sarcinile următoare pentru a-ți dezvolta propriul proiect integrat recursiv

Sarcina 1: Adaugă opțiunea „Numărul de cifre"

Cerință: Extinde proiectul integrat cu o funcție recursivă numar_cifre(n) care întoarce numărul de cifre ale lui n, și cu opțiunea 6 în meniu. Scrie doar funcția nouă și ramura if corespunzătoare. Pentru n = 12345, rezultatul este 5.

Soluție pentru Sarcina 1:
int numar_cifre(int n)
{
    if (n < 10)
        return 1;
    return 1 + numar_cifre(n / 10);
}

// in main, in interiorul buclei do...while:
else if (opt == 6)
{
    cout << "n = ";
    cin >> n;
    cout << "Numarul de cifre = " << numar_cifre(n) << endl;
}

Verifică: pentru n = 12345, cazul de bază este n < 10, deci numărul de cifre este 5.

Sarcina 2: Scrie o funcție de afișare recursivă a cifrelor

Cerință: Scrie o funcție recursivă afiseaza_cifre(n) care afișează cifrele lui n separate prin spații, de la ultima către prima. Pentru n = 1234, se afișează 4 3 2 1. Indică și cazul de bază.

Soluție pentru Sarcina 2:
#include <iostream>
using namespace std;

void afiseaza_cifre(int n)
{
    if (n == 0)
        return;
    cout << n % 10 << " ";
    afiseaza_cifre(n / 10);
}

int main()
{
    int n;
    cin >> n;
    afiseaza_cifre(n);
    return 0;
}

Verifică: afișarea se face înaintea apelului recursiv, deci cifrele apar la coborârea pe stivă: 4 3 2 1.

Scrie pe caiet

Titlul lecției

Proiect integrat recursiv. A douăsprezecea lecție din Modulul 5 îți arată cum se construiește o aplicație completă cu cinci funcții recursive.

Definiția lecției

Un proiect integrat este o aplicație care reunește mai multe funcții independente, fiecare cu o singură responsabilitate, coordonate de funcția main. Aplicația oferă cinci prelucrări recursive: factorial, Fibonacci, CMMDC, suma cifrelor și inversarea unui șir. Meniul interactiv folosește structura do...while, care afișează opțiunile, citește alegerea și execută acțiunea până la opțiunea de ieșire. Validarea intrărilor verifică datele citite înainte de utilizare, prevenind recursia infinită. Principiul modularizării spune că fiecare funcție se scrie și se testează separat, apoi doar se apelează din meniu. Astfel, o greșeală se repară într-o singură funcție, fără a atinge restul programului.

Definiții esențiale

Algoritmul cheie - pseudocod

Algoritm proiect integrat: repetă afișează meniul (1-Factorial, 2-Fibonacci, 3-CMMDC, 4-Suma cifrelor, 5-Inversare șir, 0-Ieșire) citește opțiunea execută operația corespunzătoare opțiunii cât timp opțiunea != 0

Algoritmul cheie - cod C++

Program C++ - proiect integrat (esențial)
#include <iostream>
using namespace std;

long long factorial(int n)
{
    if (n == 0)
        return 1;
    return n * factorial(n - 1);
}

long long fib(int n)
{
    if (n == 0)
        return 0;
    if (n == 1)
        return 1;
    return fib(n - 1) + fib(n - 2);
}

int cmmdc(int a, int b)
{
    if (b == 0)
        return a;
    return cmmdc(b, a % b);
}

int suma_cifre(int n)
{
    if (n == 0)
        return 0;
    return n % 10 + suma_cifre(n / 10);
}

int main()
{
    int opt, n, a, b;
    do
    {
        cout << "1. Factorial 2. Fibonacci 3. CMMDC ";
        cout << "4. Suma cifrelor 0. Iesire" << endl;
        cout << "Optiunea: ";
        cin >> opt;
        if (opt == 1) { cin >> n; cout << factorial(n) << endl; }
        else if (opt == 2) { cin >> n; cout << fib(n) << endl; }
        else if (opt == 3) { cin >> a >> b; cout << cmmdc(a, b) << endl; }
        else if (opt == 4) { cin >> n; cout << suma_cifre(n) << endl; }
        else if (opt != 0) cout << "Optiune inexistenta!" << endl;
    } while (opt != 0);
    return 0;
}

Greșeli comune de evitat

Exemplu practic

Scenariu real: la un joc educativ, opțiunea 5 din meniu inversează cuvântul introdus; «rom» devine «mor». Funcția recursivă face interschimbarea:

Program C++ - inversarea unui șir (exemplu practic)
void inversare_sir(char s[], int i, int j)
{
    if (i >= j)
        return;
    char aux = s[i];
    s[i] = s[j];
    s[j] = aux;
    inversare_sir(s, i + 1, j - 1);
}

Apelul inversare_sir(s, 0, 2) transformă șirul «rom» în «mor».

Sfaturi practice și trucuri