std::unordered_map в C++: хеш-таблица для быстрого поиска с примерами

std::unordered_map в C++: хеш-таблица для быстрого поиска с примерами

std::unordered_map в C++: хеш-таблица для быстрого поиска с примерами

std::unordered_map в C++ — это контейнер из стандартной библиотеки, который хранит данные в формате «ключ-значение» и обеспечивает очень быстрый доступ по ключу. Если говорить проще, это хеш-таблица: вы передаёте ключ, а контейнер почти мгновенно находит связанное с ним значение.

Поисковый запрос, под который полезна эта статья: «unordered_map C++ примеры». Разберём синтаксис, основные операции, практические задачи и ошибки, которые часто допускают начинающие.

Когда нужен std::unordered_map

Используйте std::unordered_map, когда вам нужно быстро:

  • найти значение по уникальному ключу;
  • посчитать количество одинаковых элементов;
  • хранить настройки, кэш, словарь, таблицу пользователей;
  • проверять, встречался ли объект раньше.
  • Главная особенность: элементы внутри unordered_map не отсортированы. Если вам нужен обход ключей в порядке возрастания, лучше использовать std::map. Если порядок не важен, чаще всего unordered_map быстрее.

    Подключение и базовый пример

    Для работы нужен заголовочный файл <unordered_map>. Рассмотрим простой словарь возрастов:

    #include <iostream>
    #include <unordered_map>
    #include <string>
    
    int main() {
        std::unordered_map<std::string, int> ages;
    
        ages["Anna"] = 25;
        ages["Ivan"] = 30;
        ages["Oleg"] = 22;
    
        std::cout << "Возраст Ivan: " << ages["Ivan"] << 'n';
    
        return 0;
    }

    Тип std::unordered_map<std::string, int> означает: ключ — строка, значение — целое число.

    Добавление элементов: operator[] и insert

    Самый простой способ добавить или изменить элемент — квадратные скобки:

    std::unordered_map<std::string, int> scores;
    
    scores["Alice"] = 100;  // добавление
    scores["Alice"] = 150;  // изменение значения

    Но у operator[] есть важная особенность: если ключа нет, он будет создан автоматически со значением по умолчанию. Для int это будет 0, для std::string — пустая строка.

    Если вы не хотите случайно создавать элемент, используйте insert или emplace:

    std::unordered_map<std::string, int> scores;
    
    scores.insert({"Bob", 80});
    scores.emplace("Kate", 95);

    emplace часто предпочтительнее, потому что создаёт объект прямо внутри контейнера без лишнего копирования.

    Проверка наличия ключа

    Типичная ошибка новичков — проверять наличие ключа через operator[]:

    if (scores["Tom"] == 0) {
        // Плохо: если ключа Tom не было, он только что появился в map
    }

    Правильный способ — использовать find:

    auto it = scores.find("Tom");
    
    if (it != scores.end()) {
        std::cout << "Tom найден, баллы: " << it->second << 'n';
    } else {
        std::cout << "Tom не найденn";
    }

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

    if (scores.contains("Tom")) {
        std::cout << "Ключ существуетn";
    }

    Практический пример: подсчёт частоты слов

    Одна из самых популярных задач для std::unordered_map — посчитать, сколько раз встречается каждый элемент. Например, частоту слов:

    #include <iostream>
    #include <unordered_map>
    #include <string>
    #include <vector>
    
    int main() {
        std::vector<std::string> words = {
            "cpp", "java", "cpp", "python", "cpp", "java"
        };
    
        std::unordered_map<std::string, int> frequency;
    
        for (const auto& word : words) {
            ++frequency[word];
        }
    
        for (const auto& pair : frequency) {
            std::cout << pair.first << ": " << pair.second << 'n';
        }
    
        return 0;
    }

    Здесь выражение ++frequency[word] работает удобно: если слова ещё нет, оно создаётся со значением 0, затем увеличивается до 1.

    Обход элементов

    Обходить unordered_map можно через range-based for. Но помните: порядок вывода не гарантирован.

    for (const auto& [name, age] : ages) {
        std::cout << name << " - " << age << 'n';
    }

    Синтаксис [name, age] называется structured bindings и доступен с C++17. Если вы изучаете современный C++ и хотите уверенно разбираться не только в контейнерах, но и в базовых принципах языка, посмотрите курс «C++ с нуля до уверенного уровня: практика, задачи и понятные объяснения».

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

    Удалить элемент можно по ключу:

    ages.erase("Oleg");

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

    if (ages.erase("Oleg") == 1) {
        std::cout << "Элемент удалёнn";
    } else {
        std::cout << "Такого ключа не былоn";
    }

    at() против operator[]

    Для чтения значений можно использовать at(). В отличие от квадратных скобок, он не создаёт новый элемент. Если ключа нет, будет выброшено исключение std::out_of_range.

    try {
        std::cout << ages.at("Maria") << 'n';
    } catch (const std::out_of_range&) {
        std::cout << "Ключ Maria не найденn";
    }

    Практическое правило: для добавления и счётчиков удобен operator[], для безопасного чтения — find, contains или at.

    Производительность и reserve

    В среднем операции поиска, вставки и удаления в std::unordered_map выполняются за O(1). Но при большом количестве вставок контейнер может перераспределять внутренние корзины. Если вы заранее знаете примерное число элементов, используйте reserve:

    std::unordered_map<std::string, int> users;
    users.reserve(10000);

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

    Ключи пользовательского типа

    Для стандартных типов вроде int, std::string, long long хеш уже определён. Но если ключ — ваша структура, нужно указать, как сравнивать объекты и как вычислять хеш.

    #include <iostream>
    #include <unordered_map>
    
    struct Point {
        int x;
        int y;
    
        bool operator==(const Point& other) const {
            return x == other.x && y == other.y;
        }
    };
    
    struct PointHash {
        std::size_t operator()(const Point& p) const {
            return std::hash<int>{}(p.x) ^ (std::hash<int>{}(p.y) << 1);
        }
    };
    
    int main() {
        std::unordered_map<Point, std::string, PointHash> points;
    
        points[{10, 20}] = "Дом";
        points[{5, 7}] = "Магазин";
    
        std::cout << points[{10, 20}] << 'n';
    }

    Для новичков это уже более продвинутая тема, но важно знать: unordered_map может работать не только со строками и числами.

    Частые ошибки

  • Ожидать отсортированный порядок. В unordered_map порядок обхода непредсказуем.
  • Использовать operator[] только для проверки. Так можно случайно добавить новый элемент.
  • Забывать про reserve. При массовой вставке предварительное резервирование ускоряет работу.
  • Выбирать unordered_map всегда. Если нужны отсортированные ключи или диапазонные запросы, берите std::map.
  • Краткая шпаргалка

    std::unordered_map<std::string, int> m;
    
    m["one"] = 1;              // добавить или изменить
    m.emplace("two", 2);       // добавить эффективнее
    
    if (m.find("one") != m.end()) {
        // ключ найден
    }
    
    m.erase("two");            // удалить по ключу
    std::cout << m.size();     // количество элементов

    Вывод

    std::unordered_map в C++ — отличный контейнер для быстрого доступа по ключу. Он особенно полезен для словарей, подсчёта частот, кэшей и проверки уникальности. Главное — помнить, что элементы не сортируются, а operator[] может создавать новые записи. Если эти особенности учитывать, хеш-таблица станет одним из самых удобных инструментов в вашем C++-коде.

    Источник

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

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