Menu

C++ set: уникальные отсортированные элементы с std::set

Как std::set хранит уникальные и автоматически отсортированные значения в C++: вставка, проверка наличия с помощью count и find, обход по порядку и различия между set, multiset и unordered_set.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Набор уникальных отсортированных значений

Вы уже видели, как 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, когда нужны только быстрые проверки наличия и порядок неважен.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ