Metode de programare – introducere
Prezentarea unor metode elementare de rezolvare a problemelor: metoda forței brute și metoda greedy la nivel introductiv.
Bacalaureat
De la problemă la metodă: de ce nu ajunge o singură rețetă
Până acum ai scris algoritmi punctuali: suma cifrelor, minimul unui vector. O metodă de programare este un nivel deasupra: o strategie generală de atac, aplicabilă unei familii întregi de probleme. Când primești o problemă nouă, întrebarea corectă nu este „ce instrucțiuni scriu?”, ci „ce tip de strategie se potrivește?”.
Cele trei strategii studiate în clasa a IX-a, la nivel introductiv:
- forța brută – generezi toate variantele posibile și le verifici pe fiecare;
- greedy – construiești soluția pas cu pas, alegând de fiecare dată varianta care pare cea mai bună acum;
- divide et impera – spargi problema în subprobleme de același fel, mai mici, le rezolvi și combini rezultatele.
Diferența dintre ele nu este de sintaxă, ci de garanții și cost: forța brută găsește sigur soluția, dar poate dura enorm; greedy este rapid, dar nu întotdeauna corect; divide et impera este rapid și corect, dar se aplică doar problemelor care se lasă sparte natural în bucăți.
La examen, recunoașterea metodei dintr-un algoritm dat este o cerință în sine — de aceea merită să reții pentru fiecare metodă tiparul ei, nu doar un exemplu memorizat.
Metoda forței brute: generează și verifică
Forța brută (căutarea exhaustivă) parcurge tot spațiul soluțiilor posibile și testează fiecare candidat. Schema generală: pentru fiecare candidat posibil, dacă îndeplinește condițiile problemei, îl reții sau îl afișezi.
Exemple tipice pentru clasa a IX-a:
- divizorii unui număr n: verifici pe rând toate numerele de la 1 la n;
- verificarea primalității: testezi dacă vreun d între 2 și n-1 divide n (rafinare: e suficient până la radical din n — dacă n are un divizor propriu, are unul cel mult egal cu radicalul său);
- toate perechile (a, b) cu o proprietate: două bucle imbricate care generează fiecare combinație;
- căutarea secvențială într-un vector — cea mai simplă formă de forță brută.
Avantajele: corectitudine garantată (nu ai cum să ratezi soluția dacă verifici tot) și simplitate — algoritmul se scrie ușor și e ușor de demonstrat.
Dezavantajul decisiv: explozia numărului de candidați. Pentru perechi ai n la pătrat candidați, pentru triplete n la cub; la n = 1000, tripletele înseamnă deja un miliard de verificări. De aceea forța brută este mereu punctul de plecare — soluția de siguranță — iar restul metodelor există tocmai ca să evite parcurgerea întregului spațiu atunci când se poate.
Metoda Greedy: alege mereu ce pare cel mai bun acum
Greedy (lacom) construiește soluția pas cu pas: la fiecare pas alege varianta care aduce câștigul maxim în acel moment, iar alegerea făcută nu se mai revizuiește niciodată. De aici vin și viteza, și riscul metodei.
Exemplul clasic: plata unei sume cu număr minim de bancnote din sistemul 1, 5, 10, 50, 100. Strategia: ia mereu cea mai mare bancnotă care încape în suma rămasă. Pentru 130 alegi 100, apoi 10, 10, 10 — patru bancnote, optim.
Alte aplicații standard: problema spectacolelor (alegerea numărului maxim de spectacole care nu se suprapun, ordonându-le după ora de încheiere) și amestecuri de tip maximizarea sumei: alegi cele mai mari k elemente dintr-un vector sortat.
Partea critică, cerută explicit la teorie: greedy nu garantează întotdeauna optimul. Alegerea local optimă poate bloca soluția global optimă. Contraexemplul canonic: cu bancnote de 1, 3 și 4, pentru suma 6 greedy alege 4 + 1 + 1 (trei bancnote), deși optimul este 3 + 3 (două). Metoda dă optimul doar pentru probleme cu o anumită structură — iar la nivelul clasei a IX-a reții că fiecare aplicare greedy trebuie justificată, nu presupusă corectă.
Tiparul recunoscut la examen: sortare după un criteriu + parcurgere cu alegeri definitive = aproape sigur greedy.
Divide et Impera: împarte și stăpânește
Divide et impera rezolvă o problemă în trei timpi:
- împarte problema în două sau mai multe subprobleme de același tip, dar de dimensiuni mai mici;
- stăpânește: rezolvă subproblemele — de regulă recursiv, până ajung atât de mici încât se rezolvă direct (cazul de bază);
- combină rezultatele subproblemelor în rezultatul problemei inițiale.
Exemplul emblematic la acest nivel: căutarea binară într-un vector sortat. Compari valoarea căutată cu elementul din mijloc: dacă e egală, ai terminat; dacă e mai mică, repeți căutarea doar în jumătatea stângă; altfel doar în dreapta. La fiecare pas dimensiunea se înjumătățește, deci într-un vector cu un milion de elemente ajung circa 20 de comparații — față de un milion la căutarea secvențială. Condiția de aplicare este esențială și des uitată: vectorul trebuie să fie sortat; pe un vector neordonat căutarea binară dă rezultate greșite.
Alte exemple pe același schelet: minimul/maximul unui vector determinat prin împărțirea în două jumătăți, iar în clasele următoare sortările rapide (MergeSort, QuickSort).
Recunoașterea la examen: funcție recursivă cu parametrii stânga și dreapta (capetele unei porțiuni), care calculează mijlocul și se autoapelează pe jumătăți — acesta e tiparul divide et impera.
Cât de repede merge? Primele noțiuni de eficiență
Doi algoritmi corecți pot diferi enorm în timpul de execuție. Eficiența se estimează numărând operațiile dominante (comparații, atribuiri repetate) în funcție de dimensiunea datelor n — nu cronometrând pe un calculator anume.
Ordinele de mărime pe care trebuie să le recunoști:
- liniar – o singură parcurgere: suma elementelor, căutarea secvențială; timpul crește proporțional cu n;
- pătratic – două bucle imbricate care parcurg fiecare pereche: sortările elementare (selecție, bubble sort); la dublarea lui n, timpul crește de patru ori;
- logaritmic – dimensiunea se înjumătățește la fiecare pas: căutarea binară; chiar și pentru n uriaș, numărul de pași rămâne mic (log în baza 2 din 1.000.000 este circa 20).
Două comparații care fixează intuiția: căutarea binară (logaritmică) bate zdrobitor căutarea secvențială (liniară), dar cere vector sortat; iar un algoritm liniar isteț bate unul pătratic naiv de îndată ce n depășește câteva mii.
Morala practică, în spiritul subiectelor de examen care cer „algoritm eficient”: eficiența se câștigă din idee (alt algoritm, altă metodă), nu din cosmetizarea codului. A muta o instrucțiune sau a redenumi variabile nu schimbă ordinul de mărime; a înlocui generarea tuturor perechilor cu o parcurgere unică, da.
De reținut
- metodă de programare
- strategie generală de rezolvare aplicabilă unei familii de probleme, precum forța brută, greedy sau divide et impera
- forța brută (căutare exhaustivă)
- metoda care generează toate soluțiile candidate și o verifică pe fiecare; garantează corectitudinea, dar poate fi foarte lentă
- metoda greedy
- metoda care construiește soluția pas cu pas, alegând la fiecare pas varianta optimă local, fără a reveni asupra alegerilor făcute
- optim local vs. optim global
- alegerea cea mai bună la pasul curent (local) nu conduce întotdeauna la cea mai bună soluție de ansamblu (global) — de aceea greedy nu garantează mereu optimul
- divide et impera
- metoda care împarte problema în subprobleme de același tip mai mici, le rezolvă recursiv și combină rezultatele
- căutare binară
- căutarea într-un vector sortat prin compararea cu elementul din mijloc și continuarea doar în jumătatea care poate conține valoarea
- caz de bază
- subproblema suficient de mică pentru a fi rezolvată direct, la care se opresc apelurile recursive ale metodei divide et impera
- algoritm liniar
- algoritm al cărui număr de operații crește proporțional cu dimensiunea n a datelor, de exemplu o singură parcurgere a unui vector
- algoritm pătratic
- algoritm cu două bucle imbricate pe datele de intrare, al cărui timp crește de patru ori la dublarea lui n, precum sortările elementare
- algoritm logaritmic
- algoritm care reduce dimensiunea problemei la jumătate la fiecare pas, precum căutarea binară; numărul de pași rămâne mic chiar pentru n foarte mare
Greșeli frecvente
Greșit: Greedy ar garanta întotdeauna soluția optimă
Corect: Greedy garantează doar rapiditate; optimul depinde de structura problemei — cu bancnote de 1, 3, 4 și suma 6, greedy dă trei bancnote (4+1+1), deși optimul e două (3+3)
Greșit: Căutarea binară s-ar putea aplica pe orice vector
Corect: Căutarea binară funcționează numai pe vectori sortați; pe un vector neordonat compararea cu mijlocul elimină jumătăți care pot conține chiar valoarea căutată
Greșit: Pentru a verifica dacă n este prim ar trebui testați toți divizorii până la n-1
Corect: E suficient până la radical din n: dacă n = a·b cu a <= b, atunci a <= radical din n; testarea până la n-1 e corectă, dar inutil de lentă și punctată ca ineficientă la cerințele de eficiență
Greșit: Un algoritm care merge repede pe un exemplu mic ar fi eficient
Corect: Eficiența se judecă după creșterea numărului de operații cu n: un algoritm pătratic pare rapid la n = 100, dar devine inutilizabil la n = 1.000.000, unde unul liniar sau logaritmic rămâne instantaneu
Greșit: Divide et impera ar însemna orice împărțire a programului în funcții
Corect: Metoda cere subprobleme de același tip cu problema inițială, rezolvate de regulă recursiv și combinate; simpla organizare a codului în subprograme este proiectare modulară, nu divide et impera
Test — 6 întrebări ca la examen
1. Un algoritm care generează toate perechile (i, j) dintr-un vector și verifică fiecare pereche folosește metoda:
- greedy
- divide et impera
- forței brute
- programării dinamice
Vezi răspunsul
forței brute. Generarea și verificarea tuturor candidaților este exact definiția forței brute. Greedy — distractorul frecvent — nu generează tot, ci face la fiecare pas o singură alegere definitivă, fără să exploreze restul variantelor.
2. Plătind suma 130 cu bancnote de 100, 50, 10, 5, 1 prin strategia „ia mereu cea mai mare bancnotă care încape”, câte bancnote folosești?
- 3
- 4
- 5
- 2
Vezi răspunsul
4. 100 + 10 + 10 + 10 = 130, deci patru bancnote. Varianta 3 e capcana pentru cei care presupun că există o bancnotă de 30 sau combină 100 + 50 uitând că 150 depășește suma; greedy ia 100, apoi de trei ori 10.
3. Care condiție este obligatorie pentru aplicarea căutării binare într-un vector?
- vectorul să aibă un număr par de elemente
- vectorul să fie sortat
- vectorul să conțină doar numere pozitive
- vectorul să nu conțină elemente egale
Vezi răspunsul
vectorul să fie sortat. Doar într-un vector sortat compararea cu elementul din mijloc permite eliminarea sigură a unei jumătăți. Numărul de elemente, semnul valorilor sau duplicatele nu împiedică metoda — distractorii mizează pe detalii care par tehnice, dar sunt irelevante.
4. Într-un vector sortat cu 1024 de elemente, căutarea binară face în cazul cel mai defavorabil aproximativ:
- 1024 de comparații
- 512 comparații
- 10 comparații
- 2 comparații
Vezi răspunsul
10 comparații. La fiecare pas dimensiunea se înjumătățește: 1024 → 512 → ... → 1, adică 10 pași (2 la puterea 10 = 1024). Varianta 512 e tentantă pentru că înjumătățește o singură dată; 1024 descrie căutarea secvențială, nu binară.
5. Cu bancnote de 1, 3 și 4, pentru suma 6, strategia greedy „cea mai mare bancnotă mai întâi” dă:
- soluția optimă, 2 bancnote
- 3 bancnote, deși optimul este 2
- 2 bancnote, deși optimul este 3
- nicio soluție
Vezi răspunsul
3 bancnote, deși optimul este 2. Greedy alege 4, apoi 1 și 1 — trei bancnote; optimul este 3 + 3, două bancnote. Este contraexemplul standard care arată că alegerea optimă local poate rata optimul global; prima variantă e capcana pentru cine crede că greedy e mereu corect.
6. Pentru a verifica eficient dacă numărul natural n > 1 este prim, este suficient să îi cauți divizori proprii:
- de la 2 până la n - 1
- de la 2 până la n / 2
- de la 2 până la radical din n
- doar printre numerele impare
Vezi răspunsul
de la 2 până la radical din n. Dacă n = a·b cu a <= b, atunci a <= radical din n, deci orice număr compus are un divizor cel mult egal cu radicalul său. Varianta n/2 e corectă logic, dar mult mai lentă — la cerințele de eficiență se punctează limita radical; iar testarea doar a imparelor ratează divizorul 2.
Deschide varianta interactivă — cu AI care îți explică