Набор уникальных отсортированных значений
Вы уже видели, как unordered_map хранит пары «ключ-значение» с быстрым поиском на основе хеша. std::set — более простой родственник: он хранит только значения (без связанных данных) и автоматически следит за двумя правилами. Каждый элемент уникален (дубликаты молча отбрасываются), и элементы всегда хранятся в отсортированном порядке.
Это делает set естественным выбором для вопросов вроде «видел ли я это раньше?» или «дай мне различные элементы по порядку». Вы вставляете, не беспокоясь о дубликатах, и обходите без предварительной сортировки.
Обратите внимание: мы вставили 10 дважды и не по порядку, но вывод всё равно отсортирован, и 10 появляется один раз. set сам выполнил всю учётную работу за вас.
Вставка и удаление
insert добавляет значение, если его ещё нет. Он возвращает pair, у которой .second — это bool, сообщающий, действительно ли произошла вставка. Это удобно, когда нужно узнать, было ли значение новым:
Чтобы удалить значение, вызовите erase с самим значением — он возвращает количество удалённых элементов (0 или 1 для set). Удаление того, чего нет, безвредно, это не ошибка:
Проверка наличия
Главное предназначение set — быстрые проверки «есть ли это здесь?». Самый понятный способ — count, который возвращает 1 или 0:
Начиная с C++20 есть ещё более читаемый вариант — contains, который возвращает bool напрямую:
if (primes.contains(7)) { /* ... */ } // C++20
Распространённая ошибка — тянуться к operator[], как сделали бы с map. У set нет operator[]: здесь нечего получать, можно лишь проверить наличие. Используйте count или contains, а не s[7].
Если нужна сама позиция (чтобы удалить элемент или посмотреть соседей), используйте find, который возвращает итератор или end():
Упорядоченный обход и запросы по диапазону
Поскольку set отсортирован, обход всегда выдаёт элементы от меньшего к большему, а приёмы упорядоченных контейнеров достаются вам бесплатно. lower_bound(x) даёт первый элемент, не меньший x, а upper_bound(x) — первый элемент, строго больший x; вместе они позволяют просмотреть числовой диапазон, не проверяя каждый элемент:
Тонкое, но важное правило: элементы set неизменяемы. Итератор отдаёт вам const-ссылку, поэтому изменить элемент на месте нельзя — это может нарушить порядок сортировки, на который опирается контейнер. Чтобы «изменить» значение, удалите старое и вставьте новое.
По умолчанию порядок возрастающий (std::less). Для убывающего порядка укажите другой компаратор в качестве второго аргумента шаблона:
set vs multiset vs unordered_set
std::set — один из трёх близких родственников, и правильный выбор имеет значение:
set<int> // уникальные значения, отсортированы, O(log n)
multiset<int> // допускает дубликаты, отсортированы, O(log n)
unordered_set<int> // уникальные значения, БЕЗ порядка, в среднем O(1)
Берите unordered_set, когда нужны только проверки наличия и порядок неважен — его поиск на основе хеша в среднем быстрее, чем древовидное O(log n) у set. Выбирайте set, когда нужны элементы в отсортированном порядке, запросы по диапазону с lower_bound/upper_bound или стабильное поведение итераторов. Используйте multiset только когда дубликаты значимы (например, гистограмма повторяющихся значений): в multiset count(x) может вернуть больше 1, а erase(x) удаляет все копии, если только вы не удаляете по одному итератору.
Классическое применение set: убрать дубликаты и отсортировать vector за один приём.
Создание set из итераторов вектора отбрасывает каждый дубликат и сортирует остальное — без ручного цикла, без танца с std::sort и std::unique.
Далее: pair и tuple
Теперь вы видели, как .first и .second появляются у pair, которую возвращает insert, и как структурированные привязки (auto [it, inserted]) аккуратно её распаковывают. Эти лёгкие типы для «связывания нескольких значений вместе» встречаются в STL повсюду. Дальше мы подробно рассмотрим pair и tuple: как их создавать, распаковывать и возвращать несколько значений из функции, не определяя целую структуру.
Часто задаваемые вопросы
Что такое set в C++?
std::set — это ассоциативный контейнер, который хранит уникальные значения в отсортированном порядке. Вставка уже существующего значения ничего не делает, а обход проходит по элементам от меньшего к большему. Поиск, вставка и удаление выполняются за O(log n), потому что в основе лежит сбалансированное двоичное дерево поиска.
Как проверить, есть ли элемент в C++-set?
Используйте s.count(x), который возвращает 1, если x присутствует, и 0, если нет, либо s.contains(x) в C++20, который возвращает bool. Избегайте s.find(x) != s.end(), если вам на самом деле не нужен итератор: стоимость та же, но запись более многословна.
В чём разница между set и unordered_set в C++?
std::set хранит элементы отсортированными и предлагает операции за O(log n); std::unordered_set хранит их в произвольном порядке с помощью хеш-таблицы и выполняет операции в среднем за O(1). Используйте set, когда вам нужен упорядоченный обход или запросы по диапазону, и unordered_set, когда нужны только быстрые проверки наличия и порядок неважен.