Основы хеш-таблиц

Базовый Algorithms
≈ 2 мин чтения

От ключа к корзине

Хеш-таблица хранит данные не по порядковому номеру элемента, а по ключу. Для ключа вычисляется целое число — хеш, после чего из него получается индекс корзины (bucket) во внутреннем массиве. Вместо просмотра всех записей структура сразу переходит к небольшой области, где должна находиться нужная запись.

static int bucketIndex(int key, int capacity) {
    int hash = Integer.hashCode(key);
    return Math.floorMod(hash, capacity);
}

Если ёмкость таблицы равна capacity, индекс должен лежать в диапазоне от 0 до capacity - 1. Хеш сам по себе не является индексом: он может быть отрицательным и обычно имеет гораздо больший диапазон значений.

Что хранится внутри

Упрощённо хеш-таблицу можно представить как массив корзин. Каждая запись содержит исходный ключ и связанные с ним данные. Хеш помогает выбрать корзину, но при окончательной проверке структура всё равно должна убедиться, что найден именно нужный ключ.

Важно различать размер и ёмкость. Размер — это количество сохранённых записей, а ёмкость — количество доступных корзин во внутреннем массиве. По мере заполнения таблица может увеличивать ёмкость и перераспределять записи.

Почему говорят «в среднем O(1)»

При удачном распределении ключей хеш сразу приводит к корзине с небольшим количеством записей. Тогда поиск, добавление и удаление требуют примерно постоянного числа действий и имеют ожидаемую сложность O(1).

Это не абсолютная гарантия. Разные ключи могут попадать в одну корзину, а неудачное распределение увеличивает объём работы. В худшем случае операция может деградировать до O(n). Почему возникают такие совпадения и как с ними работают, разберём на странице о коллизиях.

Хеш-таблица ускоряет поиск ценой дополнительной памяти. В алгоритмах это типичный обмен: мы храним служебную структуру, чтобы не выполнять повторные линейные проходы по данным.