Menu

set w C++: unikalne, posortowane elementy w std::set

Jak std::set przechowuje w C++ unikalne, automatycznie posortowane wartości: wstawianie, sprawdzanie przynależności przez count i find, iteracja w kolejności oraz różnice między set, multiset i unordered_set.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

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.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ