Хеш-таблицы
Если 47 хранится в таблице из 7 корзин, компьютер сразу идёт в корзину 47 mod 7 = 5, а не перебирает всю таблицу.
Превращаем ключ в позицию
До сих пор найти значение значило искать: до n шагов в неотсортированном массиве или связном списке. Но массив может дойти до любой позиции за один шаг. А что, если позицию подскажет сам ключ? Если бы ключи были маленькими целыми числами, скажем номерами учеников от 0 до 99, можно было бы держать массив из 100 ячеек и хранить ученика k в ячейке k. В конспектах MIT это называется массивом прямого доступа. Способ не работает, когда возможных ключей огромное число, например номера телефонов или слова, потому что ячейку для каждого возможного ключа себе не позволишь.
Хеш-функция решает это, сжимая любой ключ в небольшой диапазон позиций от 0 до m − 1. Простая функция для целых чисел: h(k) = k mod m, остаток от деления на m. Массив из m ячеек называется хеш-таблицей, а его ячейки часто называют корзинами. Чтобы сохранить 47 при m = 7, вычисли 47 mod 7 = 5 и положи число в корзину 5. Чтобы потом найти 47, вычисли то же самое и смотри только в корзину 5.
Коллизии и цепочки
Есть проблема. Если корзин 7, а возможных ключей больше 7, некоторым ключам придётся делить корзину: 12, 33 и 47 все дают остаток 5. Это называется коллизией, и конспекты MIT отмечают, что её не избежать по принципу Дирихле. Обычное средство называется методом цепочек: каждая корзина хранит маленький список, цепочку, из всех ключей, которые туда попали. Тогда поиск 47 стоит 1 шаг на хеш плюс одно сравнение на каждый проверенный ключ в цепочке 5. Если перед ним стоят 12 и 33, это 1 + 3 = 4 шага.
Какой длины цепочки? Если ключи распределяются равномерно, в каждой цепочке около n/m ключей, это число называется заполненностью (коэффициентом заполнения). Держи m примерно равным n, и цепочки останутся короткими, так что поиск, вставка и удаление в среднем занимают постоянное число шагов. Опасность в плохом распределении. Если каждый ключ попадает в одну и ту же корзину, одна цепочка хранит все n ключей, и таблица ничем не лучше связного списка. В лаборатории задай 7 корзин и вставь 14, 21 и 28: все они попадут в корзину 0.
Перетащи, чтобы повернуть. Колёсико или щипок для масштаба.
Хеш-таблицы в JavaScript
Самому писать хеш-таблицу приходится редко, потому что в JavaScript есть Map и Set. Спецификация ECMAScript говорит, что они «должны быть реализованы с помощью хеш-таблиц или других механизмов, которые в среднем дают время доступа, сублинейное по числу элементов». Иначе говоря, map.get(key) и set.has(x) должны работать быстрее, чем перебор всей коллекции. MDN добавляет, что Map хранит ключи в порядке вставки и, в отличие от обычного объекта, не содержит ключей по умолчанию, которые могли бы совпасть с твоими.
const price = new Map();
price.set("apple", 1.2);
price.set("bread", 2.5);
console.log(price.get("bread")); // 2.5, без перебора списка
console.log(price.has("milk")); // false
const seen = new Set([4, 8, 15]);
console.log(seen.has(8)); // true| Хеш-функция | Превращает ключ в номер корзины от 0 до m − 1, например k mod m. |
| Коллизия | Два разных ключа попадают в одну корзину. Неизбежна, когда ключей больше, чем корзин. |
| Метод цепочек | Каждая корзина хранит список всех ключей, которые в неё попали. |
| Заполненность | n/m, средняя длина цепочки. |
| Map и Set | Встроенные коллекции JavaScript с ключами; в среднем обязаны работать быстрее линейного перебора. |
- В какую корзину попадёт 47 при h(k) = k mod 7?
- В таблице 12, 25, 33, 47, 70 (вставлены в этом порядке), m = 7. Сколько шагов занимает поиск 47 (1 на хеш плюс сравнённые ключи)?
- Та же таблица: сколько шагов на поиск 12?
- Та же таблица: 19 не хранится. Сколько шагов займёт поиск 19?
- Та же таблица: какова заполненность n/m? (два знака после точки)
- Что такое коллизия?
- Слишком большой ключ
- Два разных ключа с хешем в одну корзину
- Двойное удаление ключа
- Таблица без ключей
- Почему ни одна хеш-функция не избежит коллизий, когда возможных ключей больше, чем корзин?
- Компьютеры округляют числа
- Принцип Дирихле
- Ключи всегда чётные
- Цепочки слишком короткие
- Как ведёт себя поиск ключа, если каждый ключ попадает в одну и ту же корзину?
- Всё ещё 1 шаг
- Как поиск в связном списке из n ключей
- Поиск становится невозможным
- Как двоичный поиск
- Что спецификация ECMAScript требует от времени доступа к Map?
- Ровно 1 шаг
- В среднем сублинейного по числу элементов
- Θ(n log n)
- Ничего
- Хеш Луна 1953 г. складывал соседние пары цифр и оставлял последнюю цифру каждой суммы. Во что превращается 31415926?
- Хеш-функция превращает ключ в номер корзины; k mod m является простой хеш-функцией.
- Коллизии неизбежны; метод цепочек хранит список в каждой корзине.
- Если заполненность n/m невелика, поиск, вставка и удаление в среднем занимают постоянное число шагов.
- Map и Set в JavaScript в среднем обязаны работать быстрее линейного перебора.
Источники курса (11)
- MIT OpenCourseWare 6.006 Introduction to Algorithms, Spring 2020 (Demaine, Ku, Solomon)
- lecture notes 2 Data Structures (interface vs data structure, Sequence and Set interfaces, stack = insert_last/delete_last, queue = insert_last/delete_first, array, linked list, dynamic array, amortized Θ(1) append)
- 4 Hashing (chaining, pigeonhole collisions, chain length n/m)
- 6 Binary Trees Part 1 (height, traversal order, O(h) operations) and 7 Binary Trees Part 2: AVL (Adelson-Velsky and Landis, 1962) at ocw.mit.edu/courses/6-006-introduction-to-algorithms-spring-2020/pages/lecture-notes
- MDN Web Docs, Array.prototype.shift (shifts all values left by 1)
- Array.prototype.unshift, Map (insertion order, Map vs Object, spec requires sublinear average access) and Set (developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects)
- ECMAScript 2025 Language Specification (tc39.es/ecma262)
- 24.1 Map Objects and 24.2 Set Objects ("must be implemented using either hash tables or other mechanisms that, on average, provide access times that are sublinear on the number of elements")
- Array.prototype.shift steps (Repeat, while k < len: move element k to k - 1)
- D. E. Knuth, The Art of Computer Programming vol. 3 (2nd ed. 1998) section 6.4 History (H. P. Luhn, IBM internal memorandum January 1953, hashing with chaining; digit pairs added mod 10, 31415926 -> 4548) via gwern.net copy, and IEEE Spectrum, "Hans Peter Luhn and the Birth of the Hashing Algorithm" (2018)
- "A brief history of the stack", SIGCIS (sigcis.org/files/A brief history.pdf: Bauer and Samelson applied for the patent in 1957, US 3,047,228 granted July 31, 1962; IPL list processing by Newell, Simon and Shaw at RAND 1956) and Wikipedia, Information Processing Language (Newell, Shaw, Simon, RAND and Carnegie Institute of Technology, about 1956).
Проверено: 2026-10-18