Алгоритмы и структуры данных

Hash Tables

Хеш-таблицы

актуальноТекущий рабочий стандарт

Направления: Основы · Алгоритмы и структуры данных

Доступ по ключу за константу в среднем. Основа словарей и множеств — и структура, на которой держится большая часть повседневного кода.

Ключевые тезисы

  • Средняя стоимость операции O(1), худшая — O(n) при массовых коллизиях.
  • разрешают цепочками или открытой адресацией; выбор влияет на поведение при заполнении.
  • Порядок обхода не гарантирован по смыслу структуры — полагаться на него нельзя.

Какую задачу решает

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

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

Подробный разбор

10 подтем — объяснения, формулы, примеры и интерактивные графики.

«Для каждого заказа найти пользователя», «есть ли такой элемент среди уже виденных», «сколько раз встречается значение» — если ответ ищется проходом по коллекции, а сам вопрос задаётся в цикле, получается . Миллион заказов и миллион пользователей — это операций.

Отсортировать и искать бинарно — уже лучше, , но порядок здесь никому не нужен: вопрос-то про точное совпадение ключа, а не про соседей по величине.

На практике

Вычислить по ключу адрес ячейки и обратиться к ней напрямую — вместо того чтобы искать. Поиск превращается в узнавание.

Отсюда средняя стоимость операции — константа, не зависящая от размера коллекции. Плата известна заранее: памяти, отсутствие порядка и худший случай, который надо держать в уме.

a:  2   7   11   15        цель 9

2:   нужно 9 - 2 = 7   ->  семёрку ещё не видели, запоминаем двойку
7:   нужно 9 - 7 = 2   ->  двойка в таблице есть  ->  ответ 2 + 7
Вместо поиска второго числа заранее считаем, что именно нужно

Ключевой ход — перевернуть вопрос. Мы не ищем пару к текущему элементу среди оставшихся, а заранее знаем, какое число нам нужно, и проверяем, встречалось ли оно. Один проход вместо перебора пар.

a b a c b a

a -> 3
b -> 2
c -> 1

теперь любой вопрос про количество -- одно обращение
Частоты: последовательность превращается в таблицу «объект → сколько раз»

Таблица — это массив корзин и функция, переводящая ключ в его номер: . Разные ключи могут попасть в одну корзину — это , и её нужно разрешать. Два основных способа: цепочки (в корзине список) и открытая адресация (ищем следующую свободную ячейку по правилу пробирования).

ОперацияСредний случайХудший случай
Поиск по ключу — все ключи в одной корзине
Вставка амортизированно при рехешировании
Удаление
Память плюс запас под коэффициент заполнения
  • Коэффициент заполнения — отношение числа элементов к числу корзин; при превышении порога таблица перестраивается вдвое больше.
  • Рехеширование стоит , но случается всё реже — отсюда амортизированная константа на вставку.
  • Требование к ключу: неизменяемость и согласованность хеша с равенством. Равные ключи обязаны иметь равный хеш.

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

Оба допущения можно нарушить, и тогда таблица деградирует до списка. Если ключи подобраны злонамеренно под известную хеш-функцию, все они попадут в одну корзину: это не теоретический риск, а класс атак на веб-сервисы (HashDoS). Защита — рандомизация хеша при старте процесса, и в современных языках она включена по умолчанию.

Сигнал в условииЧто класть в таблицу
«Есть ли дубликат», «встречалось ли раньше»множество увиденных
«Сколько раз», «самый частый»счётчик
Пара или тройка с условием, массив не отсортировандополнение до цели: значение → индекс
«Сгруппировать», «объединить одинаковые по смыслу»каноническая форма → список
«Сколько подотрезков с суммой k»префиксная сумма → сколько раз встречалась
Нужен порядок, диапазон или ближайший по значениюне таблица, а дерево или
Ключи — целые в небольшом диапазонеобычный массив-счётчик: он быстрее

ФормаЧто хранитТипичное применение
Множествотолько факт присутствиядубликаты, пересечения, посещённые состояния
Счётчикключ → числочастоты, анаграммы, топ-k вместе с кучей
Словарь-индексключ → позиция или объектсоединение двух таблиц, кеш по идентификатору
Группировкаканоническая форма → списоканаграммы, дедупликация записей
Словарь плюс списокключ → узел спискаLRU-кеш: порядок и доступ по ключу сразу
Bloom-фильтрбиты вместо ключей«точно нет или возможно да» при жёстком лимите памяти

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

  • Изменяемый ключ. Положили список или объект, изменили его — и запись больше не находится: хеш изменился, корзина осталась прежней.
  • Хеш не согласован с равенством. Равные объекты с разными хешами дают дубликаты в множестве.
  • Опора на порядок обхода. Он не гарантирован по смыслу структуры; нужен порядок — сортируйте явно.
  • Память. Таблица держит запас под заполнение: на миллиардах ключей это гигабайты, и иногда дешевле отсортировать.
  • Вещественные ключи. 0.1 + 0.2 и 0.3 — разные ключи; для дробных величин используют округление до целых единиц.
  • Коллизионная атака. Если ключи приходят извне, нужна рандомизация хеша — иначе на операцию по чужому выбору.

СтруктураКогда она лучшеЧто теряем
Дерево поисканужны порядок, диапазоны, ближайший по величинелогарифм вместо константы
Массив-счётчикключи — небольшие целыене подходит для произвольных ключей
Сортировкаданных больше, чем памяти; нужен один проход по группам, зато предсказуемая память
Два указателявход уже отсортированнужна упорядоченность
Bloom-фильтрпамять критична, ложные срабатывания допустимынельзя хранить значения и удалять

  1. Почему средняя стоимость операции — константа, а худшая — ?
  2. Почему таблица растёт вдвое, а не на фиксированное число корзин?
  3. Что произойдёт, если положить в множество изменяемый объект и затем его изменить?
  4. Группировка анаграмм: какой ключ выбрать и почему он одинаков у всех слов группы?
  5. Когда массив-счётчик предпочтительнее ?

Чем заменяют

Видеолекции

Записи университетских курсов, где эта тема звучит. Где у записи есть тайм-коды, ссылка открывает её с нужной секунды; где их не проставили — с начала.

Связанные темы

Структуры данных

Arrays, Lists, Stacks and Queues85%

Массивы, списки, стеки и очереди · Алгоритмы и структуры данных

Четыре базовых способа хранить последовательность. Отличаются не тем, что умеют, а тем, что у них дёшево, а что дорого.

Trees and Heaps85%

Деревья и кучи · Алгоритмы и структуры данных

Иерархические структуры, дающие логарифмические операции: двоичное дерево поиска для упорядоченных запросов, куча — для быстрого минимума.

Sorting Algorithms85%

Сортировки · Алгоритмы и структуры данных

Быстрая, слиянием, пирамидальная — три способа упорядочить данные за O(n log n) с разными свойствами по памяти, устойчивости и худшему случаю.

Order Statistics85%

Порядковые статистики · Алгоритмы и структуры данных

Найти k-й по величине элемент, не сортируя весь массив. Quickselect делает это в среднем за линию.

Caching85%

Кеширование · Системный дизайн ML-сервисов

Не считать то, что уже посчитано: кеш предсказаний, признаков и эмбеддингов.

Проверить себя

Тема встречается в тесте по направлениям — ошибки приведут обратно на эту страницу.

Следующий шагПроверить себя: Алгоритмы и структуры данныхТема встречается в этом тесте 2 раза. Ошибка приведёт обратно на эту страницу — с объяснением, что именно не сошлось.Перейти →

Глава «Алгоритмы и структуры данных» последний раз правилась . Нашли ошибку — напишите.