Tehnici de programare
Se introduc tehnici fundamentale de proiectare a algoritmilor pentru probleme mai complexe.
De la algoritm simplu la tehnică de programare
Până acum ai rezolvat probleme în care drumul spre rezultat era direct: citești, calculezi, afișezi. Dar există probleme la care trebuie să explorezi mai multe variante: în câte moduri pot așeza 4 elevi în bancă? Care combinație de monede plătește exact o sumă? Pentru asemenea probleme informatica a dezvoltat tehnici de programare — rețete generale de construire a algoritmilor, nu soluții pentru o singură problemă.
Cea mai simplă tehnică este forța brută (căutarea exhaustivă): generezi TOATE variantele posibile și le verifici pe rând. E garantat corectă — nu ratează nicio soluție — dar poate fi îngrozitor de lentă: numărul de variante crește exploziv. Pentru 4 elevi în bancă există 24 de aranjări, pentru 10 elevi — peste 3,6 milioane!
De aici nevoia de tehnici mai deștepte:
- backtracking — explorează variantele pas cu pas, dar abandonează din timp drumurile care sigur nu duc la soluție;
- divide et impera — sparge problema în bucăți mai mici de același fel, le rezolvă separat și combină rezultatele.
Cuvântul-cheie al capitolului este spațiul soluțiilor: mulțimea tuturor variantelor care ar putea fi soluții. Forța brută îl parcurge integral; backtracking-ul îl parcurge inteligent, tăind ramurile moarte. Deosebirea dintre cele două este exact ceea ce trebuie să poți explica.
Metoda backtracking – ideea
Backtracking înseamnă, cuvânt cu cuvânt, „întoarcere pe pași” — exact ca atunci când cauți ieșirea dintr-un labirint: mergi înainte cât poți, iar când dai de fundătură te întorci la ultima intersecție și încerci alt drum.
Cum se traduce asta în construirea soluțiilor: soluția se construiește element cu element, ca un vector x[1], x[2], ..., x[n]. La fiecare pas:
- alegi o valoare candidat pentru poziția curentă;
- verifici condițiile de continuare — poate valoarea asta să facă parte dintr-o soluție? (de exemplu: să nu se repete, să fie în ordine crescătoare, să nu se atace două regine pe tabla de șah);
- dacă DA, avansezi la poziția următoare;
- dacă NU, încerci următoarea valoare candidat pe aceeași poziție;
- când ai epuizat toți candidații unei poziții, te întorci (backtrack) la poziția anterioară și schimbi valoarea de acolo.
Când toate cele n poziții sunt completate corect, ai găsit o soluție — o afișezi și continui căutarea celorlalte.
Puterea metodei stă în verificarea condițiilor DIN TIMP: dacă primele două elemente deja încalcă regula, nu mai are rost să construiești restul — tai dintr-o singură mișcare mii de variante imposibile. Asta o deosebește de forța brută, care ar genera varianta completă și abia la final ar constata că e invalidă.
Probleme celebre rezolvate cu backtracking: permutările, problema reginelor pe tabla de șah, colorarea hărților, ieșirea din labirint.
Generarea permutărilor și a combinărilor
Cele două aplicații-standard ale backtracking-ului sunt generările de aranjamente de obiecte.
Permutările răspund la întrebarea: în câte moduri pot ORDONA n obiecte distincte? Pentru n = 3 și mulțimea {1, 2, 3}, permutările sunt: 123, 132, 213, 231, 312, 321 — șase în total. Numărul lor este n factorial: n! = 1 · 2 · ... · n. La permutări ordinea contează și fiecare element apare exact o dată — condiția de continuare la generare este ca valoarea candidat să nu fi fost deja folosită.
Combinările răspund la altă întrebare: în câte moduri pot ALEGE k obiecte din n, fără să conteze ordinea? Din {1, 2, 3, 4} luate câte 2: 12, 13, 14, 23, 24, 34 — șase combinări. Pentru că ordinea NU contează (12 și 21 sunt aceeași alegere), convenim să generăm elementele în ordine strict crescătoare — condiția de continuare este x[i] > x[i−1]. Așa fiecare alegere apare o singură dată.
Deosebirea dintre cele două, întrebare de teorie garantată:
- permutare: folosește TOATE elementele, ordinea contează;
- combinare: alege DOAR k elemente, ordinea nu contează.
Există și aranjamentele: alegi k din n și ordinea contează — hibridul celor două.
Verificare rapidă a înțelegerii: podiumul (locurile 1, 2, 3) dintr-o cursă cu 8 alergători este un aranjament (contează cine e pe ce loc); echipa de 3 elevi trimisă la concurs este o combinare (nu contează ordinea în echipă).
Divide et impera – împarte și stăpânește
Divide et impera („împarte și stăpânește”) rezolvă o problemă mare prin trei mișcări repetate:
- Împarte problema în subprobleme mai mici, DE ACELAȘI FEL cu problema inițială;
- Stăpânește: rezolvă subproblemele — dacă sunt încă mari, le împarți la rândul lor; dacă au devenit banale (o singură valoare), le rezolvi direct;
- Combină rezultatele subproblemelor în rezultatul problemei mari.
Pentru că subproblemele sunt de același fel cu întregul, implementarea naturală este recursivă — funcția se apelează pe jumătăți din ce în ce mai mici, până la cazul de bază.
Exemplul pe care îl cunoști deja fără să știi: căutarea binară! Problema „caută x în vector” se împarte în „caută x într-o JUMĂTATE de vector”, iar cazul de bază este intervalul gol sau elementul găsit. Aici pasul de combinare e trivial — răspunsul jumătății alese e chiar răspunsul final.
Alt exemplu clasic: maximul unui vector prin înjumătățire — maximul întregului este cel mai mare dintre maximul jumătății stângi și maximul jumătății drepte; cazul de bază: un singur element își este propriul maxim.
Când ajută cu adevărat tehnica? Când împărțirea reduce masiv munca — la fiecare nivel tai problema la jumătate, deci adâncimea împărțirilor e mică (pentru n = 1000, doar vreo 10 niveluri). Sortările rapide studiate la liceu (MergeSort, QuickSort) sunt tot divide et impera.
Nu orice problemă se pretează: dacă subproblemele NU sunt de același fel cu întregul sau nu se pot combina simplu, tehnica nu se aplică.
Cum alegi tehnica potrivită
La probleme concrete, prima decizie e alegerea tehnicii — iar alegerea greșită duce fie la program corect dar inutil de lent, fie la complicații fără rost.
Întrebările de pus, în ordine:
- Cere problema TOATE variantele posibile (toate așezările, toate submulțimile, toate drumurile)? → backtracking. Semnale în enunț: „generați toate...”, „afișați toate modurile...”, „în câte feluri...”.
- Se poate sparge problema în jumătăți de același fel, cu rezultate ușor de combinat? → divide et impera. Semnale: vector mare, operație asociativă (maxim, sumă, căutare), structură care se înjumătățește natural.
- E spațiul de căutare mic (câteva sute sau mii de variante)? → forța brută e perfect acceptabilă: simplă de scris, imposibil de greșit. Nu disprețui forța brută la probleme mici!
Două avertismente pentru practică:
- La backtracking, uitarea condițiilor de continuare transformă programul în forță brută mascată — generează tot și „explodează” la n mare. Condițiile de continuare sunt esența metodei, nu un detaliu.
- La divide et impera, cazul de bază lipsă sau greșit produce recursivitate infinită — aceeași boală ca la orice funcție recursivă.
Merită reținută și imaginea de ansamblu: aceste tehnici nu concurează, ci se completează. Un program real poate căuta binar (divide et impera) într-un catalog sortat și, separat, poate genera cu backtracking orarul claselor. Tehnica este unealta; problema dictează unealta potrivită.
De reținut
- tehnică de programare
- metodă generală de proiectare a algoritmilor, aplicabilă unei întregi clase de probleme, nu doar unui caz particular
- forța brută (căutare exhaustivă)
- tehnica ce generează toate variantele posibile și le verifică pe rând; garantat corectă, dar lentă la spații mari de căutare
- backtracking
- tehnica ce construiește soluția element cu element și revine la pasul anterior când condițiile de continuare nu mai pot fi îndeplinite
- condiții de continuare
- verificările efectuate la fiecare pas al backtracking-ului, care decid dacă valoarea curentă poate face parte dintr-o soluție
- permutare
- o ordonare a tuturor celor n elemente ale unei mulțimi; numărul permutărilor este n factorial
- combinare
- o alegere de k elemente din n, în care ordinea nu contează; la generare, elementele se păstrează în ordine strict crescătoare
- aranjament
- o alegere ordonată de k elemente din n: se selectează doar k elemente, iar ordinea lor contează
- divide et impera
- tehnica ce împarte problema în subprobleme mai mici de același fel, le rezolvă (de regulă recursiv) și combină rezultatele
- caz de bază
- subproblema suficient de mică pentru a fi rezolvată direct, fără o nouă împărțire; oprește recursivitatea la divide et impera
Greșeli frecvente
Greșit: Backtracking scris fără condiții de continuare, verificând soluția doar la final
Corect: Verificarea se face LA FIECARE PAS al construcției; fără ea, metoda degenerează în forță brută și devine inutilizabilă la dimensiuni mari
Greșit: Confundarea permutărilor cu combinările
Corect: Permutarea ordonează TOATE elementele (ordinea contează); combinarea alege doar k elemente, iar ordinea NU contează — 12 și 21 sunt aceeași combinare
Greșit: Numărarea combinărilor cu tot cu dubluri (12 și 21 numărate separat)
Corect: La combinări fiecare alegere se numără o singură dată; la generare, regula ordinii strict crescătoare elimină automat dublurile
Greșit: Aplicarea divide et impera pe subprobleme de alt fel decât problema inițială
Corect: Tehnica cere subprobleme de ACELAȘI fel cu întregul, altfel rezolvarea recursivă și combinarea rezultatelor nu funcționează
Greșit: Disprețuirea forței brute în orice situație
Corect: La spații mici de căutare (sute sau mii de variante), forța brută este alegerea corectă: simplă, sigură și suficient de rapidă
Test — 6 întrebări ca la examen
1. Numele metodei backtracking vine de la faptul că algoritmul:
- parcurge vectorul de la coadă spre început
- revine la pasul anterior când drumul curent nu mai poate duce la o soluție
- sortează soluțiile în ordine descrescătoare
- verifică soluțiile doar după ce le-a generat complet
Vezi răspunsul
revine la pasul anterior când drumul curent nu mai poate duce la o soluție. Backtracking înseamnă întoarcere pe pași: la fundătură, algoritmul revine la ultima alegere și încearcă alt candidat. Ultima variantă descrie exact opusul — forța brută, care generează complet și verifică la final; capcana stă în asemănarea celor două.
2. Câte permutări are mulțimea {1, 2, 3, 4}?
- 4
- 16
- 24
- 256
Vezi răspunsul
24. Numărul permutărilor a n elemente este n! = 1·2·3·4 = 24. Varianta 16 e capcana 4·4 (ca și cum fiecare poziție ar accepta orice element, cu repetiție), dar la permutări elementele nu se repetă.
3. Din mulțimea {1, 2, 3, 4} se generează combinările de câte 2 elemente. Câte sunt?
- 6
- 12
- 8
- 16
Vezi răspunsul
6. Combinările sunt 12, 13, 14, 23, 24, 34 — șase, pentru că ordinea nu contează. Varianta 12 e capcana aranjamentelor: ea numără și perechile inversate (21, 31...), adică exact dublul, ca și cum ordinea ar conta.
4. La generarea combinărilor prin backtracking, condiția x[i] > x[i−1] (ordine strict crescătoare) are rolul de a:
- accelera afișarea soluțiilor
- asigura că fiecare alegere de elemente apare o singură dată
- permite repetarea elementelor în soluție
- sorta automat vectorul de intrare
Vezi răspunsul
asigura că fiecare alegere de elemente apare o singură dată. Cum ordinea nu contează la combinări, aceeași alegere ar apărea în mai multe ordini (12, 21); impunând ordinea crescătoare, fiecare alegere are o reprezentare unică. Condiția nu are legătură cu viteza afișării — capcana primei variante.
5. Care dintre următorii algoritmi studiați este un exemplu direct de divide et impera?
- căutarea secvențială
- sortarea prin selecție
- căutarea binară
- numărarea vocalelor dintr-un șir
Vezi răspunsul
căutarea binară. Căutarea binară reduce problema la aceeași problemă pe o jumătate de vector — exact șablonul divide et impera. Căutarea secvențială, distractorul înrudit, parcurge liniar tot vectorul fără nicio împărțire în subprobleme.
6. O problemă cere afișarea TUTUROR modurilor în care 5 elevi pot fi așezați pe 5 scaune. Tehnica potrivită și numărul soluțiilor sunt:
- divide et impera; 25 de soluții
- backtracking (generare de permutări); 120 de soluții
- backtracking (generare de combinări); 10 soluții
- căutare binară; 5 soluții
Vezi răspunsul
backtracking (generare de permutări); 120 de soluții. Toate așezările ordonate ale celor 5 elevi sunt permutări: 5! = 120, generate natural cu backtracking. Combinările — distractorul — ar ignora ordinea pe scaune, dar aici contează exact cine pe ce scaun stă.
Deschide varianta interactivă — cu AI care îți explică