4.3. Структуры отображений (map)¶
Отображение (map) — это «обобщённый массив», который хранит пары вида ключ → значение.
flowchart LR
A1["apple"] --> B1[5]
A2["banana"] --> B2[3]
A3["orange"] --> B3[8]
В обычном массиве ключи — это подряд идущие целые числа 0, 1, …, n-1. В map же:
- ключи могут быть любого типа (например,
string,int,pair<int,int>и т. д.), - ключи не обязаны быть последовательными,
- по ключу можно быстро получить связанное с ним значение.
В стандартной библиотеке C++ есть две основные реализации:
map— основан на сбалансированном дереве. Доступ/вставка/удаление работают заO(log n).unordered_map— использует хеширование. Доступ/вставка/удаление работают в среднем заO(1).
Выбор между ними обычно зависит от задачи:
mapхранит ключи упорядоченными, аunordered_mapчасто быстрее, но порядок не гарантирует.
1) Пример: словарь «слово → частота/оценка»¶
Создадим отображение, где ключ — строка, а значение — целое число.
#include <bits/stdc++.h>
using namespace std;
int main() {
map<string,int> m;
m["apple"] = 4;
m["orange"] = 3;
m["piano"] = 9;
cout << m["orange"] << "\n"; // 3
}
flowchart TB
A[map<string,int> counts]
A --> B["counts[apple] = 5"]
A --> C["counts[banana] = 3"]
A --> D["counts[orange] = 8"]
B --> E[Ключ: apple]
B --> F[Значение: 5]
C --> G[Ключ: banana]
C --> H[Значение: 3]
D --> I[Ключ: orange]
D --> J[Значение: 8]
Важная особенность operator[]¶
Если вы обращаетесь к m[key], а такого ключа нет, C++ автоматически добавит его в map со значением по умолчанию:
- для
intэто0, - для
stringэто пустая строка"", - для
vector<int>это пустой вектор и т. д.
Пример:
map<string,int> m;
cout << m["unknown"] << "\n"; // 0
// теперь ключ "unknown" реально существует в m
Это удобно для подсчёта частот:
string s;
map<string,int> cnt;
while (cin >> s) {
cnt[s]++; // если ключа не было, станет 1
}
Но иногда это опасно, потому что вы можете «случайно» засорить структуру лишними ключами.
2) Проверка существования ключа¶
Чтобы проверить, есть ли ключ, используйте count (вернёт 0 или 1 для map):
if (m.count("unknown")) {
// ключ существует
}
Альтернатива (часто удобнее, если нужно получить итератор):
auto it = m.find("unknown");
if (it != m.end()) {
// it->first — ключ
// it->second — значение
}
3) Перебор всех пар ключ–значение¶
map хранит элементы отсортированными по ключу, поэтому перебор идёт в порядке ключей:
for (auto kv : m) {
cout << kv.first << " " << kv.second << "\n";
}
Если хочется без копирования пары (актуально для больших значений), используйте ссылку:
for (const auto &kv : m) {
cout << kv.first << " " << kv.second << "\n";
}
4) Когда что выбирать¶
Выбирайте map, если:¶
- нужен упорядоченный перебор ключей,
- нужны операции вроде
lower_bound/upper_boundпо ключам, - важна гарантированная асимптотика
O(log n).
Выбирайте unordered_map, если:¶
- порядок ключей не важен,
- хотите максимальную среднюю скорость (
O(1)), - ключи хорошо хешируются (стандартные типы — да).