Skupovi i mape: set, multiset, map

Lekcija 7 od 11 · Strukture podataka

Kontejneri set i map čuvaju elemente uređene i omogućavaju dodavanje, brisanje i traženje u vremenu O(log n). Na takmičenjima služe za brojanje pojavljivanja, uklanjanje duplikata, praćenje najmanjeg i najvećeg elementa i traženje najbližeg elementa dok se skup menja.

Pre nego što počneš: potrebno predznanje – mape u C++, lower_bound.
Kako da učiš: svaki primer prekucaj i pokreni, a zadatke na kraju prvo reši sam/a; uputstvo i rešenje otvori tek posle toga. Svi programi su u C++-u.

set: skup različitih elemenata

set čuva svaki element najviše jednom, uvek sortirano. Dodavanje postojećeg elementa ne menja ništa.

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

int main() {
    set<int> s;
    s.insert(5);
    s.insert(1);
    s.insert(9);
    s.insert(5);                           // 5 već postoji – ništa se ne menja
    cout << "veličina: " << s.size() << endl;

    for (int x : s) cout << x << " ";      // uvek rastuće
    cout << endl;

    cout << "ima li 9? " << s.count(9) << endl;   // 1 ili 0
    s.erase(9);
    cout << "ima li 9? " << s.count(9) << endl;

    cout << "najmanji: " << *s.begin() << endl;
    cout << "najveći: " << *s.rbegin() << endl;
    return 0;
}
OperacijaZnačenjeSloženost
s.insert(x)dodaje xO(log n)
s.erase(x)briše xO(log n)
s.count(x)1 ako x postoji, inače 0O(log n)
*s.begin(), *s.rbegin()najmanji i najveći elementO(1)
s.lower_bound(x)iterator na prvi element >= xO(log n)
s.size(), s.empty()broj elemenata, da li je prazanO(1)

Traženje najbližeg u skupu koji se menja

Sortiran niz i lower_bound rade dok se niz ne menja. Ako se elementi dodaju između upita, koristimo metodu s.lower_bound(x) skupa. Primer: parking mesta se zauzimaju jedno po jedno, a za svako novo vozilo tražimo prvo slobodno mesto sa brojem >= x.

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

int main() {
    int n, q;
    cin >> n >> q;                      // mesta 1..n, q vozila
    set<int> slobodna;
    for (int i = 1; i <= n; i++) slobodna.insert(i);

    while (q--) {
        int x;
        cin >> x;                       // vozač želi mesto x ili prvo veće
        auto it = slobodna.lower_bound(x);
        if (it == slobodna.end()) {
            cout << "nema" << endl;
        } else {
            cout << *it << endl;
            slobodna.erase(it);         // mesto je sada zauzeto
        }
    }
    return 0;
}

multiset: skup sa ponavljanjem

multiset dozvoljava više jednakih elemenata, a i dalje je sortiran. Pažnja: ms.erase(x) briše sve kopije broja x; jednu kopiju brišemo preko iteratora: ms.erase(ms.find(x)).

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

int main() {
    multiset<int> ms = {4, 1, 4, 7, 4};
    cout << "četvorki: " << ms.count(4) << endl;
    ms.erase(ms.find(4));                // briše samo jednu četvorku
    cout << "četvorki: " << ms.count(4) << endl;
    for (int x : ms) cout << x << " ";
    cout << endl;
    return 0;
}

Multiset je zgodan kada treba stalno znati najmanji i najveći od trenutnih elemenata, a elementi se i dodaju i brišu (na primer, cene u prodavnici koje se menjaju).

map: parovi ključ – vrednost

map<K, V> svakom ključu pridružuje vrednost. Osnove su opisane u lekciji Rečnik podataka – mape u C++; ovde je najvažnija primena – brojanje pojavljivanja kada su vrednosti prevelike za običan niz brojača (do 109, negativne, ili reči).

#include <iostream>
#include <map>
#include <string>
using namespace std;

int main() {
    int n;
    cin >> n;
    map<string, int> br;                // reč -> broj pojavljivanja
    for (int i = 0; i < n; i++) {
        string rec;
        cin >> rec;
        br[rec]++;                      // nova reč automatski dobija 0, pa se uveća
    }
    for (auto &par : br)                // ključevi su sortirani abecedno
        cout << par.first << " " << par.second << endl;
    return 0;
}
Zamka: samo čitanje br[x] za ključ koji ne postoji dodaje ga sa vrednošću 0. Za proveru postojanja koristi br.count(x).

unordered_set i unordered_map

Verzije bez uređenja koriste heš tabelu: dodavanje i traženje su u proseku O(1), ali elementi nisu sortirani i nema lower_bound. Koriste se kada redosled nije bitan, a operacija ima mnogo.

set / mapunordered_set / unordered_mapvector + sort
TraženjeO(log n)O(1) prosečnoO(log n) (binarna pretraga)
Dodavanje u toku radaO(log n)O(1) prosečnosporo (O(n))
Sortiran redosleddaneda
Najbliži elementda (lower_bound)neda
Kadadinamički skup, uređenostsamo brojanje / postojanjepodaci se ne menjaju

Zadaci za vežbu

Za svaki zadatak postoji primer, uputstvo i rešenje. Rešenja su provereno tačna na primerima iz teksta.

ZADATAK 01Težina: Lako

Broj različitih

Dato je n celih brojeva (do 109 po apsolutnoj vrednosti, n ≤ 2·105). Ispiši koliko je među njima različitih.

Primer

Ulaz

6
5 -2 5 1000000000 -2 7

Izlaz

4
ZADATAK 02Težina: Lako

Najčešća reč

Dato je n reči. Ispiši reč koja se javlja najviše puta i broj njenih pojavljivanja; ako ih ima više, ispiši abecedno prvu.

Primer 1

Ulaz

7
mama ima mama tata ima mama baka

Izlaz

mama 3

Primer 2

Ulaz

4
b a b a

Izlaz

a 2
ZADATAK 03Težina: Srednje

Najjeftiniji i najskuplji

U prodavnici se događaju promene. Upit + x dodaje proizvod cene x, upit - x uklanja jedan proizvod cene x (sigurno postoji), a upit ? traži da se ispišu najmanja i najveća trenutna cena (ili prazno).

Primer

Ulaz

7
+ 5
+ 3
+ 5
?
- 3
- 5
?

Izlaz

3 5
5 5
Česte greške i saveti
  • set ne čuva duplikate; ako su potrebni, koristi multiset.
  • ms.erase(x) briše sve kopije, ms.erase(ms.find(x)) samo jednu.
  • Ne poziva se lower_bound(s.begin(), s.end(), x) nad skupom – to je O(n). Ispravno je s.lower_bound(x).
  • Pre dereferenciranja iteratora (*it) proveri da nije end().

Proveri znanje

Otvori kviz

Skupovi i mape

1. Koliko elemenata ima set posle insert(3), insert(3), insert(5)?




2. Koja je složenost dodavanja u set?




3. Šta se desi kada se pročita br["x"] za ključ koji ne postoji u mapi br?




4. Kako se iz multiseta uklanja samo jedna kopija broja x?