Хеш-таблица
Хеш-таблица (также хэш-таблица, от англ. hash table) — это структура данных, реализующая интерфейс ассоциативного массива, то есть позволяющая хранить пары «ключ — значение» и выполнять операции добавления, поиска и удаления элемента по ключу. Основная идея хеш-таблицы заключается в использовании хеш-функции, которая преобразует ключ в индекс массива (слот), где хранится соответствующее значение. Благодаря этому время выполнения операций в среднем составляет O(1), что делает хеш-таблицы одной из наиболее эффективных структур для организации быстрого доступа к данным.
Принцип работы
Хеш-таблица представляет собой массив (обычно называемый таблицей или бакетами), каждый элемент которого может хранить один или несколько элементов данных. При добавлении новой пары «ключ — значение» вычисляется хеш-значение ключа с помощью хеш-функции. Полученное значение (обычно целое число) приводится к размеру массива, чаще всего с помощью операции взятия остатка от деления (mod). Результат является индексом слота, в который помещается запись.
При поиске элемента по ключу процесс повторяется: вычисляется хеш, определяется индекс, и проверяется содержимое соответствующего слота. Если ключ совпадает, значение возвращается. Если нет — возникает коллизия (см. ниже).
Хеш-функции
Хеш-функция — это детерминированная функция, которая отображает произвольные данные (ключи) в целые числа фиксированного диапазона. Качественная хеш-функция для хеш-таблицы должна удовлетворять нескольким требованиям:
- Равномерность: распределение хешей должно быть близким к равномерному, чтобы минимизировать количество коллизий.
- Быстрота вычисления: функция должна выполняться за минимальное время.
- Детерминированность: для одного и того же ключа всегда должно возвращаться одно и то же значение.
Примеры простых хеш-функций для строк:
- Полиномиальное хеширование: хеш вычисляется как сумма кодов символов, умноженных на степени некоторого основания (например, 31 или 131). Используется в Java для
String.hashCode(). - CRC32, MD5, SHA-1: криптографические хеш-функции, редко применяемые в хеш-таблицах из-за избыточной сложности, но обеспечивающие хорошее распределение.
Коллизии и методы их разрешения
Коллизия возникает, когда два разных ключа дают одинаковый индекс в таблице. Поскольку размер таблицы конечен, коллизии неизбежны. Существует два основных подхода к их разрешению:
Метод цепочек (открытое хеширование)
В каждой ячейке массива хранится не одно значение, а ссылка на структуру данных, способную вместить несколько элементов — чаще всего связный список или динамический массив. При коллизии новый элемент просто добавляется в конец списка соответствующего слота. Поиск требует последовательного просмотра списка. Время поиска в худшем случае пропорционально длине цепочки. Для поддержания эффективности таблица периодически увеличивается (рехеширование).
Открытая адресация (закрытое хеширование)
Все элементы хранятся непосредственно в массиве. При коллизии алгоритм последовательно проверяет другие ячейки таблицы (зондирование) до тех пор, пока не найдёт свободную. Существует несколько стратегий зондирования:
- Линейное зондирование: проверяются ячейки с индексами
i+1, i+2, i+3, ...(по модулю размера). Просто, но приводит к кластеризации — скоплению занятых ячеек. - Квадратичное зондирование: проверка ячеек
i+1², i+2², i+3², ...— уменьшает кластеризацию, но не исключает её полностью. - Двойное хеширование: используется вторая хеш-функция для вычисления шага зондирования. Обеспечивает наилучшее распределение.
При удалении элементов в открытой адресации нельзя просто очищать ячейку — это нарушит цепочки зондирования. Вместо этого ячейки помечаются специальным флагом (deleted).
Коэффициент заполнения и рехеширование
Коэффициент заполнения (load factor) — отношение числа хранимых элементов к размеру таблицы. При увеличении коэффициента растёт вероятность коллизий и, соответственно, время операций. Когда коэффициент превышает некоторый порог (обычно 0,7–0,75), таблица расширяется: создаётся новый массив большего размера (часто вдвое), и все существующие элементы заново вставляются в него с использованием новой хеш-функции (привязанной к новому размеру). Этот процесс называется рехешированием и может быть затратным по времени, но происходит редко.
Реализации в языках программирования
Хеш-таблицы являются встроенной или стандартной структурой данных во многих языках:
- C++:
std::unordered_map(стандартная библиотека, начиная с C++11). - Java:
java.util.HashMap— использует метод цепочек, начальная ёмкость 16, коэффициент заполнения 0,75. В Java 8+ при длине цепочки более 8 она преобразуется в сбалансированное дерево (красно-чёрное) для улучшения производительности. - Python:
dict— встроенный тип, реализован с открытой адресацией (сложная схема с псевдослучайным зондированием). - C#:
System.Collections.Generic.Dictionary<TKey, TValue>— использует метод цепочек. - JavaScript:
MapиObject(в последнем ключи ограничены строками и символами). - Go:
map— встроенный тип, реализован с использованием хеш-таблицы с цепочками.
Применение
Хеш-таблицы широко используются в программировании и информатике благодаря высокой скорости операций:
- Реализация ассоциативных массивов (словарей) — основное применение.
- Кэширование (например, в веб-браузерах для хранения DNS-записей или результатов вычислений).
- Поиск дубликатов — проверка уникальности элементов за O(1) в среднем.
- Индексирование в базах данных — хеш-индексы для быстрого поиска по ключу (например, в PostgreSQL).
- Сборка мусора — для отслеживания ссылок между объектами.
- Криптография — хеш-таблицы используются для хранения паролей (солёные хеши) и в цепочках блоков (блокчейн).
Преимущества и недостатки
Преимущества
- Среднее время операций O(1) — значительно быстрее, чем у деревьев поиска (O(log n)) или списков (O(n)).
- Простота реализации (особенно методом цепочек).
- Эффективность при большом количестве операций поиска и вставки.
Недостатки
- В худшем случае (при неудачной хеш-функции или множестве коллизий) время может деградировать до O(n).
- Потребление памяти: таблица может быть разрежена (коэффициент заполнения обычно не превышает 0,75), что приводит к избыточному расходу памяти.
- Не поддерживает упорядоченный обход элементов — порядок хранения не соответствует порядку вставки (за исключением специальных реализаций, например,
LinkedHashMapв Java). - Рехеширование может вызывать временные задержки.
Интересные факты
- Термин «хеш» происходит от французского «hacher» (рубить, измельчать) — отсылка к идее «перемешивания» данных.
- Первое описание хеш-таблицы в открытой печати принадлежит Гансу Петеру Луну (1953 год), который использовал её для поиска в словаре.
- В 1960-х годах Арнольд Думей разработал метод открытой адресации и ввёл понятие «коэффициент заполнения».
- В современных реализациях (например, в Java HashMap) при большом количестве коллизий цепочки преобразуются в деревья, что предотвращает атаки типа «хеш-коллизия» (Denial of Service через подбор ключей с одинаковым хешем).
Источники
- Кормен Т., Лейзерсон Ч., Ривест Р., Штайн К. «Алгоритмы: построение и анализ» (глава 11 «Хеш-таблицы»)
- Седжвик Р., Уэйн К. «Алгоритмы на Java» (глава 3.4 «Хеш-таблицы»)
- Документация Oracle Java SE:
java.util.HashMap - Python Software Foundation: «The Python Language Reference» (раздел о типе
dict) - Knuth D.E. «The Art of Computer Programming, Volume 3: Sorting and Searching» (раздел 6.4 «Hashing»)
BFOmetr — база данных и аналитика по компаниям России.
На главную BFOmetr →