multiset — mulțime ordonată cu duplicate

Mediu~15 min15 pași

De ce contează?

Imaginează-ți o cutie în care arunci bilete de tombolă. Dacă două persoane scriu același număr, ambele bilete rămân în cutie — nu se anulează. Și de fiecare dată când scotoci în cutie, biletele sunt deja aranjate de la cel mai mic număr la cel mai mare. Asta este un multiset: păstrează totul, inclusiv duplicatele, și le ține mereu ordonate.

Ce este un multiset și de ce diferă de set

Un multiset este o colecție ordonată care permite valori duplicate. Un set obișnuit refuză un al doilea element egal: dacă inserezi 5 de două ori, în set rămâne un singur 5. Multiset-ul le păstrează pe amândouă.

Asta înseamnă că count(valoare) poate fi mai mare decât 1 — chiar exact câte copii ai inserat. Tot ce e ordonat și logaritmic la set rămâne la fel aici: elementele sunt mereu sortate crescător, iar insert, find, count și erase sunt O(log n).

Îl folosești când ai nevoie de duplicate plus ordine: minimul și maximul dintr-o mulțime care se schimbă, mediana glisantă, sau pur și simplu să numeri câte elemente sunt mai mici decât o valoare.

Exemplu pas cu pas

Pornim cu un multiset gol și inserăm pe rând: 4, 2, 4, 7, 4, 2.

  • insert 4 → {4}
  • insert 2 → {2, 4} (se așază înaintea lui 4, ordonat)
  • insert 4 → {2, 4, 4} (duplicat acceptat)
  • insert 7 → {2, 4, 4, 7}
  • insert 4 → {2, 4, 4, 4, 7}
  • insert 2 → {2, 2, 4, 4, 4, 7}

Acum count(4) întoarce 3, iar count(2) întoarce 2. Minimul e primul, 2, maximul e ultimul, 7. Dacă apelezi erase(4), dispar toate cele trei apariții deodată — atenție aici, e cea mai frecventă capcană.

multiset
2
2
4
4
4
7
begin
rbegin
Multiset ordonat crescător. *begin() e minimul (2), *rbegin() e maximul (7). Valoarea 4 apare de 3 ori.

Implementare C++

Multiset-ul vine tot din header-ul <set>. Iată operațiile esențiale și, mai ales, diferența dintre erase(valoare) și erase(find(...)):

#include <iostream>
#include <set>
using namespace std;

int main() {
    multiset<int> s;
    s.insert(4);
    s.insert(2);
    s.insert(4);
    s.insert(7);
    s.insert(4);                    // acum avem {2, 4, 4, 4, 7}

    cout << s.count(4) << "\n";     // 3 (trei aparitii)
    cout << s.size() << "\n";       // 5
    cout << *s.begin() << "\n";     // 2 (minimul)
    cout << *s.rbegin() << "\n";    // 7 (maximul)

    // CAPCANA: erase(valoare) sterge TOATE aparitiile
    multiset<int> a = s;
    a.erase(4);                     // scoate toti cei trei de 4
    cout << a.count(4) << "\n";     // 0

    // CORECT pentru o singura aparitie: erase(find(valoare))
    multiset<int> b = s;
    auto it = b.find(4);            // iterator spre UN singur 4
    if (it != b.end()) {
        b.erase(it);               // sterge doar acel element
    }
    cout << b.count(4) << "\n";     // 2 (au mai ramas doi)

    return 0;
}

Reține tiparul: find returnează un iterator spre o singură copie, iar erase(iterator) scoate exact acel element. erase(valoare) lucrează pe valoare și mătură tot ce e egal cu ea.

Complexitate

OperațieTimp
insert / find / countO(log n)
erase(iterator)O(log n) amortizat
erase(valoare)O(log n + k), k = aparițiile șterse
*begin() / *rbegin() (min/max)O(1)
Observația-cheie

Multiset-ul te scapă de o problemă reală: min/max dinamic. Cu un vector ar trebui să recalculezi minimul după fiecare ștergere în O(n). Aici inserezi, ștergi și citești *s.begin() mereu în timp logaritmic — perfect pentru mediană glisantă sau pentru cele mai mici k elemente la un moment dat.

Greșeli frecvente

Greșeli frecvente de concurs:

  • erase(valoare) șterge TOT (capcana #1): pe {4, 4, 4}, erase(4) lasă mulțimea goală. Pentru o singură copie: s.erase(s.find(4)).
  • Uiți că count poate fi > 1: tratezi multiset ca pe un set și presupui că count(x) e 0 sau 1. Pe duplicate, întoarce câte copii există.
  • Iterator invalidat după erase: după ce ai șters elementul spre care punctează it, nu-l mai refolosi. erase(it) îți poate da iteratorul următor: it = s.erase(it);.
  • find pe mulțime goală: dacă valoarea nu există, find întoarce end(). Apelarea erase(s.find(x)) fără verificare, când x lipsește, e comportament nedefinit. Testează it != s.end() întâi.

Recapitulare

  • Multiset = set ordonat care permite duplicate, deci count poate fi > 1; toate operațiile sunt O(log n).
  • erase(valoare) șterge toate aparițiile; pentru una singură folosești s.erase(s.find(valoare)) — după ce verifici că nu e end().
  • Minimul e *s.begin(), maximul *s.rbegin(): ideal pentru min/max dinamic, mediană sau cele mai mici k elemente într-o mulțime care se schimbă.

Întrebarea 1 / 3

Ce face `s.erase(5)` pe un multiset care conține valoarea 5 de trei ori?