Ответ
Хеш-таблица (ассоциативный массив) — это структура данных, обеспечивающая быстрый доступ к значению по ключу в среднем за O(1). В стандартной библиотеке C++ представлена контейнерами std::unordered_map, std::unordered_set, std::unordered_multimap, std::unordered_multiset.
Принцип работы:
- Хеш-функция: Ключ передается в хеш-функцию (
std::hash<T>по умолчанию или пользовательская), которая возвращает числовой хеш-код. - Индексирование: Хеш-код отображается на индекс в массиве «корзин» (buckets), обычно с помощью операции
hash % bucket_count. - Разрешение коллизий: Если разные ключи попали в одну корзину (коллизия), элементы хранятся в виде связного списка (метод цепочек) внутри этой корзины.
Пример использования std::unordered_map:
#include <unordered_map>
#include <string>
#include <iostream>
int main() {
// Объявление хеш-таблицы (ключ - std::string, значение - int)
std::unordered_map<std::string, int> phonebook;
// Вставка элементов. Средняя сложность O(1).
phonebook["Alice"] = 12345;
phonebook.insert({"Bob", 67890});
phonebook.emplace("Charlie", 11111);
// Поиск по ключу. Средняя сложность O(1).
auto it = phonebook.find("Alice");
if (it != phonebook.end()) {
std::cout << "Alice's number: " << it->second << 'n'; // 12345
}
// Прямой доступ через operator[]. Если ключа нет, он будет создан.
std::cout << phonebook["Bob"]; // 67890
// Итерация по всем элементам (порядок не гарантирован!)
for (const auto& [name, number] : phonebook) {
std::cout << name << ": " << number << 'n';
}
}
Важные особенности и настройка в C++:
- Производительность зависит от качества хеш-функции (минимизация коллизий) и коэффициента загрузки (load factor).
- Коэффициент загрузки — это
size() / bucket_count(). При превышенииmax_load_factor()происходит рехеширование — увеличение числа корзин и перераспределение всех элементов, что операция дорогая O(n). - Пользовательская хеш-функция и компаратор:
struct MyKey { std::string a, b; }; struct MyKeyHash { std::size_t operator()(const MyKey& k) const { return std::hash<std::string>()(k.a) ^ (std::hash<std::string>()(k.b) << 1); } }; struct MyKeyEqual { bool operator()(const MyKey& lhs, const MyKey& rhs) const { return lhs.a == rhs.a && lhs.b == rhs.b; } }; std::unordered_map<MyKey, int, MyKeyHash, MyKeyEqual> my_map; - В худшем случае (все ключи попали в одну корзину) сложность деградирует до O(n).
Ответ 18+ 🔞
А, хеш-таблица! Ну это ж, ебать мои старые костыли, классика жанра. Представь себе огромный шкаф с кучей ящиков, и ты по названию на бумажке моментально находишь нужный ящик и хватаешь оттуда носки. Вот это она и есть — ассоциативный массив, где ключ (название) ведёт тебя прямиком к значению (носкам). В плюсах это std::unordered_map и его братва.
Как это, блядь, работает, если по-простому:
- Хеш-функция: Ты суёшь туда ключ — строку, число, свою структуру — а она тебе выплёвывает число, типа номер ящика. Стандартная функция
std::hashобычно справляется, но если она кривая — будет пиздец и коллизии. - Индексирование: Этот номер ящика (хеш) потом прикидывают, сколько вообще ящиков есть, и получают итоговый индекс. Грубо говоря,
хеш % количество_ящиков. - Коллизии (ЁПРСТ): А вот если два разных ключа, например, "Алиса" и "Боб", получили один и тот же номер ящика? Вот это пизда, коллизия. Так вот, в каждой корзине (ящике) тогда висит просто связный список, куда пихают все элементы, которые в этот ящик попали. И когда ищешь, то сначала находишь ящик за O(1), а потом в этом списке внутри ящика уже линейно ищешь нужный ключ. Поэтому если хеш-функция — говно, и всё попало в одну корзину, то твоя O(1) превращается в O(n), и ты сидишь и плачешь.
Пример, чтобы вообще всё стало ясно:
#include <unordered_map>
#include <string>
#include <iostream>
int main() {
// Объявляем нашу телефонную книгу. Ключ - имя (строка), значение - номер.
std::unordered_map<std::string, int> phonebook;
// Запихиваем контакты. В среднем - быстро, O(1).
phonebook["Alice"] = 12345; // Просто через квадратные скобки
phonebook.insert({"Bob", 67890}); // Через insert
phonebook.emplace("Charlie", 11111); // Через emplace (эффективнее)
// Ищем Алису. find() возвращает итератор.
auto it = phonebook.find("Alice");
if (it != phonebook.end()) { // Если нашли, а не упёрлись в конец
std::cout << "Номер Алисы: " << it->second << 'n'; // 12345
}
// Можно и так, через operator[]. Но осторожно: если ключа нет, он его СОЗДАСТ со значением по умолчанию (для int это 0)!
std::cout << phonebook["Bob"]; // 67890
// Пройтись по всем. Порядок — хуй пойми какой, он не гарантирован!
for (const auto& [name, number] : phonebook) {
std::cout << name << ": " << number << 'n';
}
}
Важные фишки, на которых можно обжечься:
- Производительность — это священная корова. Зависит от двух вещей: чтобы хеш-функция разбрасывала ключи равномерно (минимум коллизий), и от коэффициента загрузки.
- Коэффициент загрузки — это, грубо говоря, насколько ящики заполнены. Считается как
размер / количество_корзин. Когда он превышаетmax_load_factor()(по умолчанию 1.0), происходит рехеширование — ёперный театр, создаётся новый массив корзин побольше, и ВСЕ элементы заново перераспределяются. Операция дорогая, O(n). Поэтому если ты заранее знаешь, сколько элементов примерно будет, позовиreserve(). - Своя хеш-функция и сравнение. Допустим, ключ — твоя структура. Надо объяснить компилятору, как её хешировать и сравнивать.
struct MyKey { std::string a, b; }; // Твоя хеш-функция. Должна возвращать size_t и быть const. struct MyKeyHash { std::size_t operator()(const MyKey& k) const { // Комбинируем хеши от полей. XOR — частое, но не идеальное решение. return std::hash<std::string>()(k.a) ^ (std::hash<std::string>()(k.b) << 1); } }; // Функция сравнения на равенство (нужна на случай коллизий). struct MyKeyEqual { bool operator()(const MyKey& lhs, const MyKey& rhs) const { return lhs.a == rhs.a && lhs.b == rhs.b; } }; // Объявляем мапу с нашими кастомными штуками. std::unordered_map<MyKey, int, MyKeyHash, MyKeyEqual> my_map; - Худший случай — это когда твоя хеш-функция — сосалка, и все ключи летят в одну корзину. Тогда поиск деградирует до O(n), и твоя красивая хеш-таблица превращается в fucking связный список. Доверия ебать ноль к такой функции.
Видео-ответы
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶