Lecție-punte: complexitățile structurilor STL

Mediu~13 min15 pași

De ce contează?

Un meșter bun nu are o singură unealtă, are o trusă. Înainte să atingă o șurubelniță întreabă: ce fel de șurub, cap drept sau cruce, cât de strâns? La fel cu structurile STL: înainte să scrii o linie de cod, întreabă ce cere problema — ordine, duplicate, viteză sau acces direct — și unealta se alege singură.

Întrebarea care decide

Toate structurile din capitol stochează date. Diferă cât costă fiecare operație și ce garanții îți dau. Pune-ți, pe rând, patru întrebări:

  • Am nevoie de elemente în ordine (parcurgere sortată, lower_bound)? → arbore: set / map
  • Vreau doar viteză și nu-mi pasă de ordine? → hash: unordered_set / unordered_map
  • Pot avea chei duplicate? → multiset / multimap
  • Îmi trebuie mereu cel mai prioritar element? → priority_queue
  • Am nevoie de acces direct la al i-lea element? → vector

Tabel de complexități STL

StructuraAccesInserareCăutareOrdonat?
vectorO(1) la indexO(1) amortizat la coadăO(n)nu
listO(n)O(1) la pointer datO(n)nu
dequeO(1) la indexO(1) la ambele capeteO(n)nu
set / multisetO(log n)O(log n)da (crescător)
unordered_setO(1) mediu, O(n) răuO(1) mediu, O(n) răunu
map / multimapO(log n) după cheieO(log n)O(log n)da (după cheie)
unordered_mapO(1) mediu după cheieO(1) mediu, O(n) răuO(1) mediu, O(n) răunu
priority_queueO(1) doar vârfulO(log n)parțial (doar maximul/minimul)
bitsetO(1) la bitO(1) la bitO(n/64) operații pe blocurinu

Reguli de decizie + cum recunoști în enunț

  • „elementele distincte în ordine crescătoare”, „cel mai mic element ≥ x”, „lower_bound” → set / map (arbore ordonat).
  • „doar verific dacă există / numărul de apariții, foarte rapid, ordinea nu contează” → unordered_set / unordered_map.
  • „pot apărea valori egale și le vreau pe toate” → multiset / multimap.
  • „scot mereu maximul / minimul rămas”, „următorul task după prioritate”, „Dijkstra” → priority_queue.
  • „accesez al i-lea element”, „șir de frecvențe pe indici mici” → vector.
  • „adaug și scot la ambele capete” (deque monoton, sliding window) → deque.
Observația-cheie

„O(1)” la unordered_* este mediu, nu garantat. La coliziuni multe (sau cu chei alese advers, ca în unele teste) o operație poate degenera la O(n), iar întregul algoritm la O(n²). Când îți trebuie o garanție pe cel mai rău caz, set / map cu O(log n) sigur sunt alegerea mai sigură.

Observația-cheie

map și set au O(log n), dar cu factor constant mare: fiecare nod e alocat separat în memorie, deci ai sărituri (cache miss) și alocări dese. Pentru n mic sau valori într-un interval restrâns, un vector simplu (chiar și pentru frecvențe) bate de departe arborele, deși „pe hârtie” arborele pare la fel de bun.

Greșeli de alegere

Greșeli frecvente

Greșeli frecvente de concurs:

  • unordered_* când ai nevoie de ordine: vrei să parcurgi crescător sau folosești lower_bound, dar hash-ul nu păstrează nicio ordine — răspuns greșit, nu doar lent.
  • map când nu ai nevoie de sortare: plătești log(n) și factor constant mare degeaba; dacă ordinea nu contează, unordered_map e clar mai rapid.
  • Te bazezi pe O(1) garantat la hash: pe teste ostile coliziunile te duc la O(n) pe operație și depășești timpul; nu presupune niciodată hash perfect.
  • set pentru numărat când ajunge un vector de frecvență: dacă valorile sunt mici (ex. 0..10⁶), freq[x]++ într-un vector e O(1) real, fără logaritm și fără alocări — set/map aici e risipă.

Recapitulare

  • Întreabă întâi: ordine? duplicate? viteză? acces direct? — răspunsul îți alege structura.
  • Ordine + lower_boundset/map; doar viteză → unordered_*; duplicate → multi*; prioritate → priority_queue; index → vector.
  • O(1) la hash e doar mediu; arborii sunt O(log n) garantat dar cu factor constant mare — alege după garanția de care ai nevoie.

Întrebarea 1 / 3

Ai nevoie să afli rapid câte numere distincte sunt dintr-un șir și să le parcurgi în ordine crescătoare la final. Ce alegi?