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

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

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

std::set в C++ — это ассоциативный контейнер из стандартной библиотеки STL. Он полезен, когда нужно хранить набор уникальных значений, быстро проверять наличие элемента и получать данные в отсортированном виде без ручной сортировки.

Типичный поисковый запрос по этой теме — «std::set C++ примеры» или «как хранить уникальные элементы в C++». Именно под такую практическую задачу и разберём контейнер set.

Что такое std::set

std::set находится в заголовочном файле <set>. Внутри он обычно реализован как сбалансированное бинарное дерево, поэтому основные операции работают за O(log n):

  • добавление элемента;
  • поиск элемента;
  • удаление элемента.
  • Главные свойства std::set:

  • элементы не повторяются;
  • значения автоматически сортируются;
  • нельзя изменить элемент прямо внутри set так, чтобы нарушился порядок;
  • итерация идёт по возрастанию, если не задан другой компаратор.
  • Простой пример std::set

    Допустим, пользователь вводит числа, а нам нужно вывести только уникальные значения по возрастанию.

    #include <iostream>
    #include <set>
    
    int main() {
        std::set<int> numbers;
    
        numbers.insert(5);
        numbers.insert(2);
        numbers.insert(10);
        numbers.insert(2); // повтор, не будет добавлен
    
        for (int value : numbers) {
            std::cout << value << ' ';
        }
    
        return 0;
    }

    Результат:

    2 5 10

    Обратите внимание: мы добавили 2 два раза, но в контейнере он остался только один раз. Кроме того, значения вывелись не в порядке добавления, а в отсортированном порядке.

    Как понять, был ли элемент добавлен

    Метод insert возвращает пару: итератор и логическое значение. Второе значение равно true, если элемент действительно добавлен, и false, если такой элемент уже был.

    #include <iostream>
    #include <set>
    
    int main() {
        std::set<std::string> names;
    
        auto result = names.insert("Anna");
    
        if (result.second) {
            std::cout << "Имя добавленоn";
        } else {
            std::cout << "Такое имя уже естьn";
        }
    
        result = names.insert("Anna");
    
        if (!result.second) {
            std::cout << "Повторное добавление не сработалоn";
        }
    
        return 0;
    }

    Это удобно, например, при регистрации логинов, тегов, идентификаторов или уникальных кодов.

    Поиск элемента в std::set

    Для поиска используется метод find. Если элемент найден, возвращается итератор на него. Если нет — end().

    #include <iostream>
    #include <set>
    
    int main() {
        std::set<int> ids = {101, 205, 310, 404};
    
        int target = 205;
    
        if (ids.find(target) != ids.end()) {
            std::cout << "ID найденn";
        } else {
            std::cout << "ID не найденn";
        }
    
        return 0;
    }

    В C++20 можно использовать более читаемый метод contains:

    if (ids.contains(205)) {
        std::cout << "Есть такой IDn";
    }

    Если вы пишете код под C++17 или более старый стандарт, используйте find.

    Удаление элементов

    Удалять элементы можно по значению или по итератору. Самый простой вариант — erase(value).

    #include <iostream>
    #include <set>
    
    int main() {
        std::set<int> numbers = {1, 2, 3, 4, 5};
    
        numbers.erase(3);
    
        for (int number : numbers) {
            std::cout << number << ' ';
        }
    
        return 0;
    }

    Результат:

    1 2 4 5

    Метод erase возвращает количество удалённых элементов. Для std::set это будет либо 0, либо 1, потому что дубликатов нет.

    Сортировка по убыванию

    По умолчанию std::set сортирует значения по возрастанию. Чтобы хранить элементы по убыванию, можно передать компаратор std::greater.

    #include <iostream>
    #include <set>
    #include <functional>
    
    int main() {
        std::set<int, std::greater<int>> numbers = {4, 1, 7, 2};
    
        for (int number : numbers) {
            std::cout << number << ' ';
        }
    
        return 0;
    }

    Результат:

    7 4 2 1

    std::set со своими структурами

    Чтобы хранить в set объекты пользовательского типа, нужно объяснить контейнеру, как их сравнивать. Например, отсортируем пользователей по возрасту, а при равном возрасте — по имени.

    #include <iostream>
    #include <set>
    #include <string>
    
    struct User {
        std::string name;
        int age;
    };
    
    struct UserCompare {
        bool operator()(const User& a, const User& b) const {
            if (a.age != b.age) {
                return a.age < b.age;
            }
    
            return a.name < b.name;
        }
    };
    
    int main() {
        std::set<User, UserCompare> users;
    
        users.insert({"Ivan", 25});
        users.insert({"Anna", 20});
        users.insert({"Petr", 25});
    
        for (const User& user : users) {
            std::cout << user.name << ": " << user.age << 'n';
        }
    
        return 0;
    }

    Важно: для std::set два элемента считаются одинаковыми не через ==, а через компаратор. Если ни один объект не меньше другого, set считает их эквивалентными.

    Когда использовать std::set

    std::set хорошо подходит, если вам нужно:

  • хранить только уникальные элементы;
  • часто проверять наличие значения;
  • получать элементы сразу в отсортированном порядке;
  • быстро находить ближайшие элементы через lower_bound и upper_bound.
  • Пример с lower_bound: найдём первое число, которое не меньше заданного.

    std::set<int> numbers = {10, 20, 30, 40};
    
    auto it = numbers.lower_bound(25);
    
    if (it != numbers.end()) {
        std::cout << "Первое подходящее число: " << *it << 'n';
    }

    Здесь будет найдено число 30.

    std::set, std::unordered_set или vector?

    Новички часто выбирают std::set «на всякий случай», но это не всегда лучший вариант.

  • std::set — элементы уникальны и отсортированы, операции обычно O(log n).
  • std::unordered_set — элементы уникальны, но порядок не гарантируется, поиск в среднем O(1).
  • std::vector — хорош, если данных мало или важен порядок добавления; для уникальности придётся писать дополнительную проверку.
  • Если сортировка не нужна, а важна максимальная скорость поиска, часто лучше подойдёт std::unordered_set. Если элементов немного, простой vector может оказаться понятнее и быстрее из-за меньших накладных расходов.

    Частые ошибки новичков

  • Ожидать порядок добавления. std::set хранит элементы отсортированно, а не в порядке вставки.
  • Пытаться изменить элемент через итератор. Это запрещено, потому что изменение может сломать внутренний порядок дерева.
  • Забывать про компаратор. Для своих типов нужно явно определить логику сравнения.
  • Использовать set там, где нужен multiset. Если дубликаты должны сохраняться, берите std::multiset.
  • Практический пример: уникальные слова из текста

    Ниже программа считывает слова до конца ввода и выводит уникальные слова в алфавитном порядке.

    #include <iostream>
    #include <set>
    #include <string>
    
    int main() {
        std::set<std::string> words;
        std::string word;
    
        while (std::cin >> word) {
            words.insert(word);
        }
    
        std::cout << "Уникальные слова:n";
    
        for (const std::string& item : words) {
            std::cout << item << 'n';
        }
    
        return 0;
    }

    Такой код можно использовать как основу для простого анализа текста, списка тегов или проверки повторяющихся значений.

    Рекомендации

  • Используйте std::set, когда нужны уникальность и сортировка одновременно.
  • Для проверки наличия в C++20 предпочитайте contains, а в C++17 — find.
  • Не храните в set изменяемые данные, по которым выполняется сравнение.
  • Для пользовательских структур всегда тщательно продумывайте компаратор.
  • Если вы хотите уверенно разобраться не только с std::set, но и со всей базой C++: типами, функциями, ООП, STL и практическими задачами, посмотрите курс «Программирование на C++ с Нуля до Гуру» — пошаговый путь от первых программ до уверенной разработки.

    Вывод

    std::set в C++ — удобный контейнер для уникальных отсортированных данных. Он не заменяет все остальные контейнеры, но отлично решает задачи, где нужно быстро проверять наличие элемента и поддерживать порядок. Главное — помнить, что set не хранит порядок вставки и определяет уникальность через правило сравнения.

    Источник

    НЕТ КОММЕНТАРИЕВ

    Оставить комментарий