Открыть сервис

Хеш-таблица

Хеш-таблица (также хэш-таблица, от англ. 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 →