Biblioteca
Tutora
BibliotecaInformatică și TIC › clasa a XII-a

Proiectarea algoritmilor – complexitate

Analiza eficienței algoritmilor prin studiul complexității temporale și spațiale.

Bacalaureat

Ce măsurăm: complexitatea timp și complexitatea spațiu

Eficiența unui algoritm se judecă după două resurse:

De ce numărăm operații și nu secunde: timpul în secunde depinde de calculator, limbaj și compilator, pe când numărul de operații caracterizează algoritmul însuși. Această justificare se cere ca atare la teorie.

Pentru același algoritm, numărul de operații poate diferi la aceeași dimensiune n, în funcție de datele concrete; de aceea se disting:

Dacă nu se precizează altfel, complexitatea unui algoritm înseamnă comportarea în cazul defavorabil — garanția pe care o putem promite indiferent de date.

Există adesea un compromis timp-memorie: poți accelera un algoritm consumând memorie suplimentară (vectori de frecvență, tabele de programare dinamică, memoizare) sau poți economisi memorie cu prețul unor recalculări. Recunoașterea acestui compromis — de exemplu, de ce sortarea prin numărare e rapidă dar cere un vector auxiliar cât plaja valorilor — este exact genul de justificare punctată la subiectul de analiză.

Notația O — ordinul de complexitate

Notația O (O mare) exprimă ordinul de creștere al numărului de operații când n devine mare, ignorând constantele multiplicative și termenii de rang inferior: un algoritm cu 3n² + 20n + 7 operații este O(n²), pentru că la n mare termenul n² domină totul.

Ierarhia claselor uzuale, de la cea mai bună la cea mai slabă — trebuie știută pe de rost:

Cum citești complexitatea direct din structura codului:

Intuiția pe numere, bună la verificări rapide: pentru n = 1.000.000, log2(n) este aproximativ 20, n log n este aproximativ 20 de milioane (fezabil), iar n² este 10^12 (nefezabil pe timpul unui test).

Compararea algoritmilor: aceeași problemă, costuri diferite

Aceeași problemă admite adesea algoritmi cu ordine de complexitate radical diferite — iar subiectele cer explicit varianta eficientă.

Căutarea într-un vector: secvențială — O(n), merge pe orice vector; binară — O(log n), dar cere obligatoriu vector sortat. Aplicarea căutării binare pe vector nesortat dă rezultate greșite — condiția de aplicabilitate face parte din răspuns.

Verificarea primalității: testarea divizorilor până la n-1 este O(n); testarea doar până la radical din n este O(sqrt(n)) — argumentul: dacă n = a·b și a <= b, atunci a <= sqrt(n), deci orice număr compus are un divizor cel mult egal cu radicalul său.

Suma unui subșir de întrebări repetate: recalcularea fiecărei sume este O(n) per întrebare; cu sume parțiale precalculate (s[i] = suma primelor i elemente), fiecare întrebare costă O(1) — exemplu tipic de compromis timp-memorie.

Fibonacci: recursiv pur — exponențial; cu memorare sau iterativ — O(n).

Cel mai mare divizor comun: scăderi repetate — O(max(a, b)) în cazul defavorabil; algoritmul lui Euclid cu împărțiri — O(log(min(a, b))), pentru că restul cel puțin se înjumătățește la fiecare două împărțiri.

Regula practică pentru probleme de concurs și pentru subiectul III de BAC: estimezi n din enunț și alegi clasa de complexitate care încape. Pentru n de ordinul 10^5–10^6, algoritmii O(n²) sunt de regulă prea lenți; țintește O(n log n) sau O(n). Justificarea eficienței — de ce soluția ta nu parcurge de mai multe ori datele — este parte din punctaj, nu ornament.

Complexitatea algoritmilor de sortare

Sortările elementare, toate O(n²) în cazul defavorabil:

Sortările eficiente, O(n log n):

Rezultat teoretic cerut la grile: orice sortare bazată pe comparații face în cazul defavorabil cel puțin n log n comparații — nu există sortare prin comparații O(n).

Ocolirea acestei limite: sortarea prin numărare (count sort) nu compară elemente, ci numără aparițiile fiecărei valori într-un vector de frecvență — O(n + V), unde V este plaja valorilor; excelentă când valorile sunt mici (note, cifre, vârste), impracticabilă când plaja e uriașă. Condiția de aplicabilitate face parte din răspuns.

De reținut

complexitate timp
numărul de operații elementare executate de un algoritm, exprimat ca funcție de dimensiunea datelor de intrare; implicit se raportează cazul defavorabil
complexitate spațiu
cantitatea de memorie suplimentară folosită de algoritm, dincolo de spațiul ocupat de datele de intrare
notația O
notație care exprimă ordinul de creștere al timpului de execuție pentru date mari, ignorând constantele multiplicative și termenii de rang inferior
O(log n)
complexitate logaritmică, specifică algoritmilor care înjumătățesc la fiecare pas dimensiunea problemei — exemplul canonic fiind căutarea binară în vector sortat
O(n log n)
complexitatea sortărilor eficiente (MergeSort, HeapSort) și limita teoretică inferioară pentru orice sortare bazată pe comparații, în cazul defavorabil
caz favorabil / caz defavorabil
datele de intrare pentru care algoritmul execută cele mai puține, respectiv cele mai multe operații la aceeași dimensiune n
sortare prin selecție
sortare O(n²) care la fiecare pas aduce minimul zonei nesortate pe prima poziție a acesteia; numărul de comparații nu depinde de ordinea inițială a datelor
sortare prin inserție
sortare care plasează fiecare element la locul potrivit în zona deja sortată; O(n²) în cazul defavorabil, dar O(n) pe un vector deja sortat
QuickSort
sortare prin partiționare în jurul unui pivot, O(n log n) în medie și O(n²) în cazul defavorabil, când pivotul cade mereu pe o valoare extremă
sortare prin numărare
sortare fără comparații, care numără aparițiile fiecărei valori într-un vector de frecvență; complexitate O(n + V), aplicabilă doar când plaja valorilor V este rezonabil de mică

Greșeli frecvente

Greșit: Două cicluri succesive (neimbricate), fiecare de la 1 la n, sunt declarate O(n²)
Corect: Ciclurile succesive se adună, nu se înmulțesc: O(n) + O(n) = O(n); doar ciclurile imbricate dau produsul complexităților
Greșit: Se afirmă că un algoritm cu 5n + 100 operații este mai slab decât unul cu n² operații pentru că are constante mari
Corect: Ordinul de creștere decide pentru n mare: O(n) bate O(n²) de la un prag încolo, indiferent de constante; la n = 1000, 5n + 100 înseamnă circa 5.100 de operații, iar n² un milion
Greșit: Aplicarea căutării binare pe un vector nesortat
Corect: Căutarea binară funcționează numai pe vectori sortați — eliminarea unei jumătăți se bazează pe ordine; pe vector nesortat rezultatul este pur și simplu greșit, nu doar lent
Greșit: Se afirmă că QuickSort este întotdeauna O(n log n)
Corect: QuickSort este O(n log n) doar în medie; în cazul defavorabil (pivot mereu extrem, de exemplu pe vector deja sortat cu pivotul primul element) degradează la O(n²) — garanția O(n log n) în orice caz o dau MergeSort și HeapSort
Greșit: Se caută o sortare prin comparații cu complexitate O(n) pentru cazul general
Corect: Orice sortare bazată pe comparații necesită cel puțin n log n comparații în cazul defavorabil; timpul liniar se obține doar renunțând la comparații, ca la sortarea prin numărare, și doar când plaja valorilor o permite

Test — 6 întrebări ca la examen

1. Un algoritm execută exact 2n² + 30n + 1000 de operații pentru o intrare de dimensiune n. Ordinul său de complexitate este:
  1. O(n)
  2. O(n²)
  3. O(n³)
  4. O(2^n)
Vezi răspunsul
O(n²). Notația O reține doar termenul dominant, fără constante: 2n² domină pe 30n și pe 1000 pentru n mare, deci O(n²). Cine alege O(n) s-a lăsat înșelat de coeficientul mic al lui n² față de constanta 1000 — la n mare raportul se inversează categoric.
2. Care este complexitatea secvenței: un ciclu for de la 1 la n, urmat (nu imbricat) de alt ciclu for de la 1 la n?
  1. O(n²), pentru că sunt două cicluri
  2. O(2^n)
  3. O(n), pentru că ciclurile succesive se adună
  4. O(n log n)
Vezi răspunsul
O(n), pentru că ciclurile succesive se adună. Ciclurile executate unul după altul își adună costurile: n + n = 2n, adică O(n). Prima variantă e capcana standard — produsul apare doar la cicluri imbricate, unde pentru fiecare pas al primului se execută integral al doilea.
3. Un ciclu de forma: i = n; cât timp i > 1 execută i = i / 2; are complexitatea:
  1. O(n)
  2. O(n / 2)
  3. O(log n)
  4. O(1)
Vezi răspunsul
O(log n). Variabila se înjumătățește la fiecare pas, deci numărul de pași este numărul de înjumătățiri până la 1, adică aproximativ log2(n). Varianta O(n/2) tentează prin împărțirea la 2 din cod, dar constanta 1/2 nu schimbă ordinul — iar aici nu n se reduce liniar, ci exponențial de repede.
4. Care sortare garantează complexitatea O(n log n) inclusiv în cazul defavorabil?
  1. QuickSort
  2. sortarea prin inserție
  3. MergeSort
  4. bubble sort cu test de oprire
Vezi răspunsul
MergeSort. MergeSort împarte mereu vectorul în jumătăți egale, deci costul n log n nu depinde de date. QuickSort este distractorul cel mai puternic — excelent în medie, dar degradează la O(n²) când pivotul cade repetat pe extreme; inserția și bubble sort sunt O(n²) în cazul defavorabil.
5. Pe un vector deja sortat crescător, sortarea prin inserție execută:
  1. aproximativ n²/2 comparații, ca în orice caz
  2. n-1 comparații — cazul său favorabil, O(n)
  3. 0 comparații, pentru că vectorul e sortat
  4. n log n comparații
Vezi răspunsul
n-1 comparații — cazul său favorabil, O(n). Fiecare element nou se compară o singură dată cu vecinul din stânga, constată că e la locul lui și se oprește — n-1 comparații în total. Zero comparații e imposibil: algoritmul nu are de unde ști că vectorul e sortat fără să verifice; prima variantă descrie sortarea prin selecție, insensibilă la ordinea inițială.
6. Trebuie sortate crescător un milion de numere naturale, toate din intervalul [1, 100]. Cea mai eficientă abordare este:
  1. QuickSort, fiind cea mai rapidă sortare generală
  2. sortarea prin numărare, cu un vector de frecvență de 100 de poziții
  3. sortarea prin selecție, având puține interschimbări
  4. căutarea binară aplicată repetat
Vezi răspunsul
sortarea prin numărare, cu un vector de frecvență de 100 de poziții. Plaja valorilor este minusculă față de n, deci numărarea aparițiilor dă O(n + 100), practic liniar — sub orice sortare prin comparații, care nu poate coborî sub n log n. QuickSort tentează ca reflex de „cea mai bună sortare”, dar aici informația suplimentară din enunț (valori mici) schimbă instrumentul optim; căutarea binară nici măcar nu este o metodă de sortare.
Deschide varianta interactivă — cu AI care îți explică
← Baze de dateElemente de programare orientată pe obiecte (OOP) →
BiologieChimieEconomieFilosofieFizicăGeografieInformatică și TICIstorieLogică și argumentareMatematicăPsihologieLimba și literatura română