← Назад к вопросам

Приведи пример, когда следует использовать std::set

1.0 Junior🔥 141 комментариев
#STL контейнеры и алгоритмы

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Примеры использования std::set

std::set — это отсортированное множество уникальных элементов с логарифмической сложностью операций. Используется когда нужны гарантированная уникальность и порядок, и когда часто ищут элементы.

Когда выбирать std::set

Требуется:

  • Автоматическая уникальность элементов
  • Отсортированный порядок
  • Быстрый поиск O(log n)
  • Итерирование в порядке сортировки

НЕ выбирай, если:

  • Нужна максимальная скорость поиска → unordered_set
  • Нужен произвольный доступ → vector
  • Редко ищешь элементы → vector

Пример 1: Удаление дубликатов

#include <set>
#include <vector>
#include <iostream>

std::vector<int> removeDuplicates(const std::vector<int>& data) {
    std::set<int> uniqueSet(data.begin(), data.end());
    return std::vector<int>(uniqueSet.begin(), uniqueSet.end());
}

int main() {
    std::vector<int> nums = {3, 1, 4, 1, 5, 9, 2, 6, 5};
    auto result = removeDuplicates(nums);
    // Результат: [1, 2, 3, 4, 5, 6, 9] - отсортировано и уникально
    return 0;
}

Пример 2: Поиск пересечения двух массивов

#include <set>
#include <vector>
#include <algorithm>

std::vector<int> intersection(const std::vector<int>& a, 
                               const std::vector<int>& b) {
    std::set<int> setA(a.begin(), a.end());
    std::vector<int> result;
    
    for (int x : b) {
        if (setA.count(x)) {  // O(log n) поиск
            result.push_back(x);
            setA.erase(x);  // Избегаем дубликатов
        }
    }
    
    return result;
}

int main() {
    std::vector<int> nums1 = {1, 2, 2, 1};
    std::vector<int> nums2 = {2, 2};
    auto res = intersection(nums1, nums2);
    // Результат: [2]
    return 0;
}

Пример 3: LRU Кэш с таймаутом

#include <set>
#include <unordered_map>
#include <chrono>

struct CacheEntry {
    std::string key;
    std::string value;
    std::chrono::system_clock::time_point expiry;
    
    bool operator<(const CacheEntry& other) const {
        if (expiry != other.expiry) {
            return expiry < other.expiry;
        }
        return key < other.key;
    }
};

class ExpiringCache {
private:
    std::set<CacheEntry> expiryQueue;  // Отсортировано по времени
    std::unordered_map<std::string, std::string> data;
    
public:
    void set(const std::string& key, const std::string& value, int ttl_seconds) {
        auto expiry = std::chrono::system_clock::now() + 
                      std::chrono::seconds(ttl_seconds);
        data[key] = value;
        expiryQueue.insert({key, value, expiry});
    }
    
    void cleanup() {
        auto now = std::chrono::system_clock::now();
        while (!expiryQueue.empty() && 
               expiryQueue.begin()->expiry <= now) {
            data.erase(expiryQueue.begin()->key);
            expiryQueue.erase(expiryQueue.begin());
        }
    }
    
    std::string get(const std::string& key) {
        cleanup();
        auto it = data.find(key);
        return (it != data.end()) ? it->second : "";
    }
};

Пример 4: Граф конфликтов (для расраскраски)

#include <set>
#include <unordered_map>
#include <string>

class ConflictGraph {
private:
    // Для каждой переменной - set всех переменных, с которыми она конфликтует
    std::unordered_map<std::string, std::set<std::string>> conflicts;
    
public:
    void addConflict(const std::string& var1, const std::string& var2) {
        conflicts[var1].insert(var2);
        conflicts[var2].insert(var1);
    }
    
    bool hasConflict(const std::string& var1, const std::string& var2) {
        return conflicts[var1].count(var2) > 0;  // O(log n)
    }
    
    std::set<std::string> getConflicts(const std::string& var) {
        return conflicts[var];  // Автоматически отсортировано
    }
    
    void removeVariable(const std::string& var) {
        for (const auto& conflicting : conflicts[var]) {
            conflicts[conflicting].erase(var);  // O(log n)
        }
        conflicts.erase(var);
    }
};

int main() {
    ConflictGraph graph;
    graph.addConflict("x", "y");
    graph.addConflict("x", "z");
    graph.addConflict("y", "z");
    
    auto conflicts = graph.getConflicts("x");
    // Результат: отсортированное множество {"y", "z"}
    return 0;
}

Пример 5: Диапазонные запросы

#include <set>
#include <iostream>

int main() {
    std::set<int> scores = {10, 20, 30, 40, 50, 60, 70, 80, 90};
    
    // Найти все оценки в диапазоне [30, 70]
    auto lower = scores.lower_bound(30);  // >= 30
    auto upper = scores.upper_bound(70);  // > 70
    
    std::cout << "Оценки в диапазоне [30, 70]: ";
    for (auto it = lower; it != upper; ++it) {
        std::cout << *it << " ";  // 30 40 50 60 70
    }
    
    // Удалить все scores < 40
    auto it = scores.begin();
    while (it != scores.end() && *it < 40) {
        it = scores.erase(it);
    }
    
    return 0;
}

Производительность std::set

ОперацияСложностьПримечание
insertO(log n)Автоматическая сортировка
findO(log n)Бинарный поиск
eraseO(log n)Балансировка дерева
lower_boundO(log n)Первый >= key
upper_boundO(log n)Первый > key
iterationO(n)В отсортированном порядке

set vs unordered_set vs vector

std::set<int>           // Когда нужна сортировка и быстрый поиск
std::unordered_set<int> // Когда нужна максимальная скорость поиска (O(1))
std::vector<int>        // Когда нужен случайный доступ и итерирование

Практические советы

  • Эффективен для small-to-medium data (< 1M элементов)
  • Кэш-неэффективен для больших наборов (RB-tree разбросана в памяти)
  • Используй reserve() если знаешь размер заранее (unordered_set only)
  • Кастомные компараторы для сложного порядка сортировки

std::set — это универсальный инструмент для работы с уникальными отсортированными данными с гарантированной логарифмической сложностью операций.

Приведи пример, когда следует использовать std::set | PrepBro