Тимофей: Выбор хеш-функции должен зависеть от данных, с которыми вы работаете. Допустим, вы работаете с целыми числами. Тогда можно воспользоваться методом деления.
Метод деления
В случае работы с целыми числами самый простой вариант — брать остаток от деления.
h(k)=kmodM, где
M определяет диапазон значений
[0,M−1]. То есть от этого числа будем считать остаток от деления.
k — аргумент, от которого хотим вычислить значение хеш-функции
h(k).
Тимофей: Значение
M нужно подбирать с учётом специфики задачи и имеющихся ресурсов. Если параметр будет слишком мал, возникнет много коллизий. Если слишком большой, потребуется много памяти для хранения таблицы.
Алла: А как именно выбрать
M?
Гоша: Можно взять в качестве
M степень двойки. В программировании часто используют константы такого вида.
То есть
M=2p для некоторого числа
p.
Тимофей: Это неудачный выбор. В этом случае функция
h(k) — просто набор младших битов числа
k. То есть значение не зависит от старших байтов.
Гоша: Почему не зависит? Можешь привести пример?
Переведём это число в двоичную систему счисления.
M=24=16=10000. Посчитаем значение
h(k) для некоторых
k.
Например, для
k=5. Переведём число
k в двоичную систему и посчитаем остаток от деления на
M.
Гоша: А как считать остаток от деления одного числа на другое, если они представлены в двоичной системе счисления?
Тимофей: Деление чисел в двоичной системе счисления производится по тем же правилам, что и для десятичных чисел. Даже ещё проще, поскольку мы имеем всегда только две цифры — 0 и 1.
h(5)=h(0101)=5 h(21)=h(10101)=5 h(37)=h(100101)=5 h(53)=h(110101)=5 h(69)=h(1000101)=5 Гоша: Да, действительно. Функция выдаёт одинаковый ответ для разных
k. Будут возникать коллизии. А зачем нужно было переводить числа в двоичную систему? Разве так нельзя было посчитать остаток от деления?
Тимофей: Можно. Мы перевели числа в двоичную систему, чтобы лучше понять утверждение про младшие биты.
Алла: Как же тогда подобрать
M?
Тимофей: В качестве
M нужно брать простое число.
Гоша: Напомни, что такое простое число?
Рита: Простое число — это число, которое делится без остатка только на себя и на 1.
Гоша: А, точно. Я знал, просто забыл.
Алла: А почему нужно брать простое число?
Тимофей: Рассмотрим пример. Пусть имеется хеш-таблица размером 12, и есть набор ключей
[0,1,...,100].
Так как 3 — делитель 12, ключи, которые делятся на 3, попадут в ячейки, номера которых тоже делятся на 3.
[0,12,24,36,...] попадут в ячейку с номером 0.
[3,15,27,39,...] — в ячейку с номером 3.
[6,18,30,42,...] — в ячейку с номером 6.
[9,21,33,45,...] — в ячейку с номером 9.
Чтобы избежать коллизий, нужно уменьшить число общих делителей
M и элементами множества ключей. В этом поможет выбор простого числа в качестве размера таблицы.
Тимофей: Хороший результат получается, если в качестве
M выбирать простое число, далеко отстоящее от степеней двойки.
Например, при наличии 2000 элементов в качестве
M можно взять число 701. Оно простое и примерно на равном расстоянии отстоит от чисел, являющихся степенями двойки
(512=29<701<210=1024). При таком выборе
M с высокой вероятностью количество элементов, для которых значение хеш-функции совпадёт, будет в среднем равно
3(7012000≈2,85).
Реализуем код хеш-функции, которая для определения индекса элемента берёт остаток от деления на 17.
Скопировать кодJSX
def hashFunction(key)
{
return key % 17
}
Гоша: А что обозначает знак
%?
Тимофей: Это остаток от деления по модулю 17. Например, если на вход функции подать число 19, то остаток будет 2. Если 15, то остаток будет 15. Можно просто говорить «15 по модулю 17».