В чем разница set и unordered_set
set и unordered_set — это два разных контейнера из стандартной библиотеки C++ (<set> и <unordered_set> соответственно), предназначенные для хранения уникальных элементов. Однако они отличаются принципами внутреннего устройства, временем выполнения операций, порядком хранения элементов и требованиями к типам данных.
📦 set
🔹 Определение
std::set<int> mySet;
-
Основан на самобалансирующемся двоичном дереве поиска (обычно Red-Black Tree).
-
Все элементы отсортированы по возрастанию (или по пользовательскому компаратору).
-
Все элементы уникальны.
🔹 Время выполнения операций
Операция | Сложность |
---|---|
Вставка | O(log n) |
--- | --- |
Удаление | O(log n) |
--- | --- |
Поиск | O(log n) |
--- | --- |
Перебор по порядку | O(n) |
--- | --- |
🔹 Особенности
-
Элементы автоматически отсортированы.
-
Можно удобно использовать итераторы для получения диапазона значений.
-
Поддерживает lower_bound, upper_bound, equal_range и другие методы, завязанные на порядок.
🧺 unordered_set
🔹 Определение
std::unordered_set<int> myUnorderedSet;
-
Основан на хэш-таблице.
-
Элементы не отсортированы, порядок произвольный.
-
Все элементы также уникальны.
🔹 Время выполнения операций (в среднем)
Операция | Сложность (средняя) | Худший случай |
---|---|---|
Вставка | O(1) | O(n) |
--- | --- | --- |
Удаление | O(1) | O(n) |
--- | --- | --- |
Поиск | O(1) | O(n) |
--- | --- | --- |
Перебор | O(n) | O(n) |
--- | --- | --- |
🔹 Особенности
-
Очень быстрый доступ по хэшу.
-
Нет сортировки.
-
Требует, чтобы тип поддерживал:
-
std::hash<T> — хэш-функция.
-
operator== — для сравнения элементов.
-
⚙️ Внутреннее устройство
Характеристика | set | unordered_set |
---|---|---|
Структура | Красно-чёрное дерево | Хэш-таблица |
--- | --- | --- |
Хранение | Отсортировано | Неупорядочено |
--- | --- | --- |
Эффективность вставки | Медленнее (O(log n)) | Быстрее (O(1) в среднем) |
--- | --- | --- |
Итерация по порядку | Да | Нет |
--- | --- | --- |
Поддержка диапазонов | Да (lower_bound, upper_bound) | Нет |
--- | --- | --- |
Хэш-функция нужна? | Нет | Да (std::hash<T>) |
--- | --- | --- |
Сравнение через | operator< (по умолчанию) | operator== |
--- | --- | --- |
🧪 Примеры
set:
std::set<int> s;
s.insert(5);
s.insert(1);
s.insert(3);
// Вывод: 1 3 5
for (int val : s)
std::cout << val << " ";
#### **unordered_set:**
std::unordered_set<int> us;
us.insert(5);
us.insert(1);
us.insert(3);
// Вывод: порядок не определён, например: 3 1 5
for (int val : us)
std::cout << val << " ";
🔍 Пример использования с пользовательским типом
Для unordered_set:
Нужен std::hash<MyType> и operator==:
struct Point {
int x, y;
bool operator==(const Point& other) const {
return x == other.x && y == other.y;
}
};
namespace std {
template <>
struct hash<Point> {
size_t operator()(const Point& p) const {
return hash<int>()(p.x) ^ (hash<int>()(p.y) << 1);
}
};
}
std::unordered_set<Point> points;
points.insert({1, 2});
Для set:
Нужен operator<:
struct Point {
int x, y;
bool operator<(const Point& other) const {
return x == other.x ? y < other.y : x < other.x;
}
};
std::set<Point> points;
points.insert({1, 2});
🧮 Где использовать set, а где unordered_set
Сценарий | Лучше использовать |
---|---|
Частые вставки, удаления, проверка наличия | unordered_set |
--- | --- |
Нужна сортировка по значениям | set |
--- | --- |
Нужно упорядоченное перебирание | set |
--- | --- |
Пользовательский тип с отсутствием хэша | set |
--- | --- |
Максимальная скорость без сортировки | unordered_set |
--- | --- |
Нужна работа с диапазонами (lower_bound и т.д.) | set |
--- | --- |
🧠 Память и аллокации
-
set использует дерево → больше указателей, выделений памяти на каждый узел.
-
unordered_set использует большой массив бакетов (таблицу), но может потреблять больше памяти из-за запасов на перераспределение.
🛠️ Особенности реализации
-
unordered_set может работать хуже, если:
-
плохая хэш-функция,
-
часто происходят коллизии (много значений попадает в одну корзину),
-
маленький размер таблицы (нужно правильно задавать reserve()).
-
Пример коллизии:
std::unordered_set<int> us;
us.reserve(1000); // уменьшает количество перераспределений
🔐 Потокобезопасность
-
И set, и unordered_set не потокобезопасны.
-
Если нужно использовать в нескольких потоках — нужны блокировки или структуры из concurrent-библиотек.
🔚 Итоговое сравнение (таблица)
Свойство | set | unordered_set |
---|---|---|
Хранение | Отсортированное дерево | Хэш-таблица |
--- | --- | --- |
Порядок элементов | Сортированный | Произвольный |
--- | --- | --- |
Вставка/удаление/поиск | O(log n) | O(1) в среднем |
--- | --- | --- |
Доступ по индексу | Нет | Нет |
--- | --- | --- |
Требует хэш-функцию | Нет | Да |
--- | --- | --- |
Поддерживает диапазоны поиска | Да | Нет |
--- | --- | --- |
Использование итераторов | Поддержка bidirectional | Поддержка только forward |
--- | --- | --- |
Таким образом, set и unordered_set — два мощных инструмента, подходящих для разных задач. Один обеспечивает сортировку и устойчивую производительность, другой — высокую скорость доступа без гарантированного порядка.