Worek unikalnych, posortowanych wartości
unordered_map przechowuje pary klucz-wartość z szybkim wyszukiwaniem opartym na haszowaniu. std::set to jego prostszy kuzyn: przechowuje same wartości, bez powiązanych danych, i automatycznie pilnuje dwóch zasad. Każdy element jest unikalny (duplikaty są po cichu odrzucane), a elementy są zawsze trzymane w posortowanej kolejności.
Dlatego set to naturalny wybór przy pytaniach w rodzaju „czy to już się pojawiło?” albo „podaj różne elementy w posortowanej kolejności”. Wstawiasz bez martwienia się o duplikaty i iterujesz bez wcześniejszego sortowania.
Zauważ, że 10 wstawiono dwa razy i nie po kolei, a mimo to wynik jest posortowany i 10 pojawia się raz. Zbiór sam prowadzi całą ewidencję.
Wstawianie i usuwanie
insert dodaje wartość, jeśli jeszcze jej nie ma. Zwraca pair, którego .second to bool mówiący, czy wstawienie faktycznie nastąpiło; przydaje się, gdy chcesz wiedzieć, czy wartość była nowa:
Aby usunąć wartość, wywołaj erase z samą wartością: zwraca liczbę usuniętych elementów (0 albo 1 dla set). Usuwanie czegoś, czego nie ma, jest nieszkodliwe i nie jest błędem:
Sprawdzanie przynależności
Cały sens zbioru to szybkie sprawdzanie „czy to tu jest?”. Najczytelniej robi się to przez count, które zwraca 1 albo 0:
Od C++20 jest jeszcze czytelniejsza opcja, contains, która zwraca bool bezpośrednio:
if (primes.contains(7)) { /* ... */ } // C++20
Częsty błąd to sięganie po operator[] tak jak przy map. set nie ma operator[]: nie ma wartości do pobrania, jest tylko obecność do sprawdzenia. Używaj count albo contains, a nie s[7].
Jeśli potrzebujesz konkretnej pozycji (żeby ją usunąć albo spojrzeć na sąsiadów), użyj find, które zwraca iterator albo end():
Uporządkowana iteracja i zapytania o zakres
Ponieważ set jest posortowany, iteracja zawsze daje elementy od najmniejszego do największego, a sztuczki kontenerów uporządkowanych dostajesz za darmo. lower_bound(x) daje pierwszy element nie mniejszy niż x, a upper_bound(x) pierwszy element ściśle większy od x. Razem pozwalają przejrzeć zakres liczb bez sprawdzania każdego elementu:
Subtelna, ale ważna zasada: elementy zbioru są niezmienne. Iterator daje referencję const, więc nie możesz zmienić elementu w miejscu, bo mogłoby to zepsuć kolejność sortowania, na której opiera się kontener. Aby „zmienić” wartość, usuń starą i wstaw nową.
Domyślnie kolejność jest rosnąca (std::less). Aby uzyskać kolejność malejącą, podaj inny komparator jako drugi argument szablonu:
set, multiset czy unordered_set
std::set to jeden z trzech bliskich krewnych, a wybór właściwego ma znaczenie:
set<int> // unikalne wartości, posortowane, O(log n)
multiset<int> // dopuszcza duplikaty, posortowane, O(log n)
unordered_set<int> // unikalne wartości, BEZ kolejności, średnio O(1)
Sięgaj po unordered_set, gdy potrzebujesz tylko sprawdzania przynależności, a kolejność cię nie obchodzi: wyszukiwanie oparte na haszowaniu jest średnio szybsze niż O(log n) drzewa w set. Wybierz set, gdy potrzebujesz elementów w posortowanej kolejności, zapytań o zakres przez lower_bound/upper_bound albo stabilnego zachowania iteratorów. multiset stosuj tylko wtedy, gdy duplikaty mają znaczenie (na przykład w histogramie powtarzających się wartości): w multiset count(x) może zwrócić więcej niż 1, a erase(x) usuwa wszystkie kopie, chyba że usuwasz przez pojedynczy iterator.
Jedno klasyczne zastosowanie set: usunięcie duplikatów i posortowanie vector za jednym zamachem.
Zbudowanie zbioru z iteratorów wektora odrzuca każdy duplikat i sortuje resztę: bez ręcznej pętli i bez jawnego sortowania w duecie ze std::unique.
Dalej: pair i tuple
Na pair zwracanym przez insert pojawiają się już .first i .second, a structured bindings w połączeniu ze słowem kluczowym auto (auto [it, inserted]) czysto go rozpakowują. Takie lekkie typy „połącz kilka wartości razem” są w STL wszędzie. Dalej przyjrzymy się bezpośrednio pair i tuple: jak je budować, rozpakowywać i zwracać wiele wartości z funkcji bez definiowania całej struktury.
Najczęściej zadawane pytania
Czym jest set w C++?
std::set to kontener asocjacyjny, który przechowuje unikalne wartości w posortowanej kolejności. Wstawienie wartości, która już jest w zbiorze, nic nie robi, a iteracja przechodzi po elementach od najmniejszego do największego. Wyszukiwanie, wstawianie i usuwanie mają złożoność O(log n), bo pod spodem jest zrównoważone drzewo poszukiwań binarnych.
Jak sprawdzić, czy element istnieje w set w C++?
Użyj s.count(x), które zwraca 1, jeśli x jest w zbiorze, a 0, jeśli nie, albo s.contains(x) w C++20, które zwraca bool. Unikaj s.find(x) != s.end(), chyba że naprawdę potrzebujesz iteratora: kosztuje tyle samo, a jest bardziej rozwlekłe.
Czym się różni set od unordered_set w C++?
std::set trzyma elementy posortowane i oferuje operacje O(log n); std::unordered_set trzyma je bez określonej kolejności w tablicy mieszającej, ze średnim czasem operacji O(1). Użyj set, gdy potrzebujesz uporządkowanej iteracji albo zapytań o zakres, a unordered_set, gdy potrzebujesz tylko szybkiego sprawdzania przynależności i kolejność cię nie obchodzi.