← Назад к вопросам
Как решается коллизия в std::unordered_map?
2.0 Middle🔥 151 комментариев
#STL контейнеры и алгоритмы#Структуры данных и алгоритмы
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Разрешение коллизий в std::unordered_map
std::unordered_map в C++ использует chaining (раздельные цепочки) для разрешения коллизий. Когда два ключа имеют одинаковый хэш, они хранятся в одном bucket'е в виде связного списка.
Проблема: коллизии
std::unordered_map<std::string, int> map;
map["Alice"] = 30;
map["Bob"] = 25;
// Если hash("Alice") == hash("Bob") → коллизия!
Решение: Chaining
std::unordered_map хранит для каждого bucket'а список элементов:
Buckets:
[0] → nullptr
[1] → (Alice, 30) → nullptr
[2] → (Bob, 25) → (David, 28) → nullptr ← коллизия!
[3] → nullptr
...
Как это работает внутри
// Упрощённо, unordered_map выглядит так:
template<typename K, typename V>
class unordered_map {
private:
// Вектор списков
std::vector<std::list<std::pair<K, V>>> buckets;
public:
V& operator[](const K& key) {
// 1. Вычислить хэш
size_t h = std::hash<K>{}(key);
// 2. Найти bucket
size_t idx = h % buckets.size();
// 3. Найти элемент в цепочке
for (auto& pair : buckets[idx]) {
if (pair.first == key) {
return pair.second; // Нашли!
}
}
// 4. Не нашли → добавить
buckets[idx].push_back({key, V{}});
return buckets[idx].back().second;
}
};
Процесс разрешения коллизии при вставке
map["Alice"] = 30;
// Шаг 1: hash("Alice") = 123456
size_t hash_value = hash_function("Alice"); // 123456
// Шаг 2: bucket_index = 123456 % bucket_count
size_t index = hash_value % buckets.size(); // = 2
// Шаг 3: Проверить bucket[2]
if (bucket[2] пуст) {
bucket[2].push_back({"Alice", 30}); // Вставить
} else {
// Коллизия! Добавить в цепочку
bucket[2].push_back({"Alice", 30});
}
Процесс разрешения коллизии при поиске
auto val = map["Alice"]; // Или map.find("Alice")
// Шаг 1: hash("Alice") = 123456
size_t hash_value = hash_function("Alice");
// Шаг 2: bucket_index = 123456 % bucket_count
size_t index = hash_value % buckets.size();
// Шаг 3: Перебрать элементы в bucket'е
for (auto& [key, value] : buckets[index]) {
if (key == "Alice") {
return value; // Найден!
}
}
// Не найден
return nullptr;
Практический пример
#include <unordered_map>
#include <iostream>
int main() {
std::unordered_map<std::string, int> ages;
// Вставка
ages["Alice"] = 30;
ages["Bob"] = 25;
ages["Charlie"] = 35;
ages["David"] = 28;
// Информация о bucket'ах
std::cout << "Bucket count: " << ages.bucket_count() << std::endl; // 16
std::cout << "Load factor: " << ages.load_factor() << std::endl; // ~0.25
// Какой bucket для каждого ключа?
for (auto& [key, value] : ages) {
size_t bucket = ages.bucket(key);
size_t chain_len = ages.bucket_size(bucket);
std::cout << key << " → bucket " << bucket
<< " (chain length: " << chain_len << ")" << std::endl;
}
return 0;
}
// Вывод (примерно):
// Bucket count: 16
// Load factor: 0.25
// Alice → bucket 3 (chain length: 1)
// Bob → bucket 5 (chain length: 1)
// Charlie → bucket 2 (chain length: 1)
// David → bucket 5 (chain length: 2) ← коллизия с Bob!
Сложность при коллизиях
// Лучший случай: нет коллизий
// find(key) = O(1)
// Худший случай: все в одном bucket'е
// find(key) = O(n)
// Это возможно если hash_function плохая
// Средний случай: α = n/m (load factor)
// find(key) = O(1 + α) ≈ O(1)
Load Factor и Rehashing
std::unordered_map<std::string, int> map;
// Load factor = количество элементов / количество bucket'ов
float alpha = map.size() / map.bucket_count();
std::cout << "Load factor: " << map.load_factor() << std::endl;
std::cout << "Max load factor: " << map.max_load_factor() << std::endl; // 1.0
// Когда load_factor > max_load_factor → rehash
map.rehash(new_bucket_count); // Явный rehash
Что происходит при rehash:
Бefore rehash (bucket_count = 8):
[0] → Alice
[1] →
[2] → Bob, David ← коллизия
[3] →
...
After rehash (bucket_count = 16):
[0] → Alice
[2] → Bob
[5] → David
...
Когда возникают коллизии
1. Плохая хэш-функция
struct BadHash {
size_t operator()(const std::string& s) const {
return 42; // Всегда 42! Все элементы в одном bucket'е
}
};
std::unordered_map<std::string, int, BadHash> map;
// find() будет O(n)!
2. Множество элементов в малом bucket_count
auto map = std::unordered_map<int, int>();
for (int i = 0; i < 1000; i++) {
map[i] = i;
}
// Если bucket_count не растёт → много коллизий
std::cout << map.load_factor() << std::endl; // Может быть > 1.0
Оптимизация: избежание коллизий
// 1. Убедиться что bucket_count достаточно большой
std::unordered_map<std::string, int> map(1000); // Reserve space
// 2. Проверить что хэш-функция хорошая
std::cout << map.load_factor() << std::endl; // < 0.75
// 3. Явный rehash если нужно
if (map.load_factor() > 0.75) {
map.rehash(map.bucket_count() * 2);
}
// 4. Использовать reserve() для предсказания
map.reserve(10000); // Подготовить на 10000 элементов
Сравнение с std::map
// std::unordered_map (chaining for collisions)
std::unordered_map<int, std::string> umap;
// find() = O(1 + α) в среднем, O(n) в худшем
// std::map (никаких коллизий, красно-чёрное дерево)
std::map<int, std::string> map;
// find() = O(log n) всегда
// Когда использовать какой:
// unordered_map: когда нужно O(1) average case
// map: когда нужно O(log n) worst case гарантия
Итог
std::unordered_map разрешает коллизии через chaining:
- Вычислить хэш ключа
- Найти bucket по модулю хэша
- Поискать в цепочке списка элементов в bucket'е
- Добавить если не найдено
Средняя сложность: O(1) при load_factor < 0.75 Худшая сложность: O(n) если все в одном bucket'е
Сохранение хорошей load_factor через rehashing гарантирует O(1) производительность.