Теперь рассмотрим очень эффективный способ реализации словарей, позволяющих хранить пары вида (ключ, значение). К основным операциям над словарем относятся: добавления пары, поиска и удаления пары по ключу.
Для реализации словарей можно использовать массивы или связные списки и двоичные деревья поиска.
Хеширование основано на идее распределения ключей в одномерном массива H[0..., называющемся хеш-таблицей. Хеш-функция h назначает каждому из ключей хеш-адрес, который представляет собой целое число от 0 до m-1. Каждая ячейка содержит связный список пар, у которых ключи получили хеш-адрес, соответствующий данной ячейке. Таким образом, все операции со словарем будут выполняться только в небольшом списке пар с совпадающим хеш-адресом.
Хеш-функция должна удовлетворять двум требованиям:
Например, если ключи представляют собой неотрицательные целые числа, то хеш-функция может иметь вид h(K) = K mod m. Если ключи — символы некоторого алфавита, то можно применить к номеру символу "ord"(K) в алфавите ту же функцию, что и для целых чисел.
Если K — символьная строка c_0 c_1... c_{n-1}, можно вычислить h(K) по формуле:
h larr 0
for i in [0..n-1]
quad h larr (h*P+"ord"(c_i)) mod m
где P – константа, обычно некоторое простое число.
Для упрощения здесь P=1
h(ARE) = (1 + 18 + 5) mod 13 = 11
h(SOON) = (19 + 15 + 15 + 14) mod 13 =11
Если мы хотим найти хеш-таблице слово KID, то должны начать с вычисления для него хеш-функции: h(KID) = 11. Поскольку список, присоединенный к ячейке 11, не пуст, мы должны просмотреть ключи в этом списке. После сравнения слова KID сначала со словом ARE, а затем со словом SOON мы убеждаемся, что искомого слова в таблице нет.
Выбрав размер хеш-таблицы m меньшим, чем количество ключей n, мы обречены на коллизии — ситуации, когда два или более ключей хешируются в одну и ту же ячейку хеш-таблицы. Но коллизий следует ожидать, даже если m значительно больше n. В наихудшем случае все ключи могут быть хешированы в одну ячейку хеш-таблицы. К счастью, при соответствующем выборе размера хеш-таблицы и хорошей хеш-функции такая ситуация встречается очень редко.
Эффективность поиска зависит от длины связных списков, которая, в свою очередь, зависит от размеров m хеш-таблицы и качества хеш-функции. Если хеш-функция распределяет n ключей по m ячейкам практически равномерно, то в каждом списке будет содержаться примерно около n//m пар.
Cреднее количество сравнений для последовательного поиска равно
C_{"avg"} (n)=p/2+n-(p*n)/2 in Theta(n),
где n – длина списка, p – вероятность наличия ключа в списке.
При помощи хеширования мы достигаем снижения среднего размера связного списка в m раз. Так как m>n, то C_{"avg"} (n//m) in Theta(1), что лучше эффективности Theta(log n) для двоичных деревьев поиска.
Такая эффективность получена ценой излишнего потребления памяти – многие ячейки хэш-таблицы остаются пустыми.