Разрешение коллизий — различия между версиями
(→Стратегии поиска) |
|||
Строка 37: | Строка 37: | ||
Для защиты от кластеризации используется [[Двойное хеширование|двойное хеширование]] и [[Хеширование кукушки|хеширование кукушки]]. | Для защиты от кластеризации используется [[Двойное хеширование|двойное хеширование]] и [[Хеширование кукушки|хеширование кукушки]]. | ||
− | == | + | ==Двойное хеширование== |
− | + | '''Двойное хеширование''' {{---}} метод борьбы с коллизиями, возникающими при [[Открытое_и_закрытое_хеширование#Закрытое хеширование|закрытом хешировании]], основанный на использовании двух хеш-функций для построения различных последовательностей исследования хеш-таблицы. | |
− | + | ||
− | * | + | ===Принцип двойного хеширования=== |
+ | При двойном хешировании используются две независимые хеш-функции <tex> h_1(k) </tex> и <tex> h_2(k) </tex>. Пусть <tex> k </tex> {{---}} это наш ключ, <tex> m </tex> {{---}} размер нашей таблицы, <tex>n \mod m </tex> {{---}} остаток от деления <tex> n </tex> на <tex> m </tex>, тогда сначала исследуется ячейка с адресом <tex> h_1(k) </tex>, если она уже занята, то рассматривается <tex> (h_1(k) + h_2(k)) \mod m </tex>, затем <tex> (h_1(k) + 2 \cdot h_2(k)) \mod m </tex> и так далее. В общем случае идёт проверка последовательности ячеек <tex> (h_1(k) + i \cdot h_2(k)) \mod m </tex> где <tex> i = (0, 1, \; ... \;, m - 1) </tex> | ||
+ | |||
+ | Таким образом, операции вставки, удаления и поиска в лучшем случае выполняются за <tex>O(1)</tex>, в худшем {{---}} за <tex>O(m)</tex>, что не отличается от обычного [[Открытое_и_закрытое_хеширование#Линейное разрешение коллизий|линейного разрешения коллизий]]. | ||
+ | Однако в среднем, при грамотном выборе хеш-функций, двойное хеширование будет выдавать лучшие результаты, за счёт того, что вероятность совпадения значений сразу двух независимых хеш-функций ниже, чем одной. | ||
+ | |||
+ | <center> | ||
+ | <tex>\forall x \neq y \; \exists h_1,h_2 : p(h_1(x)=h_1(y))> p((h_1(x)=h_1(y)) \land (h_2(x)=h_2(y)))</tex> | ||
+ | </center> | ||
+ | |||
+ | ===Выбор хеш-функций=== | ||
+ | <tex> h_1 </tex> может быть обычной хеш-функцией. Однако чтобы последовательность исследования могла охватить всю таблицу, <tex> h_2 </tex> должна возвращать значения: | ||
+ | *не равные <tex> 0 </tex> | ||
+ | *независимые от <tex> h_1 </tex> | ||
+ | *взаимно простые с величиной хеш-таблицы | ||
+ | |||
+ | Есть два удобных способа это сделать. Первый состоит в том, что в качестве размера таблицы используется простое число, а <tex> h_2 </tex> возвращает натуральные числа, меньшие <tex> m </tex>. Второй {{---}} размер таблицы является степенью двойки, а <tex> h_2 </tex> возвращает нечетные значения. | ||
+ | |||
+ | Например, если размер таблицы равен <tex> m </tex>, то в качестве <tex> h_2 </tex> можно использовать функцию вида <tex> h_2(k) = k \mod (m-1) + 1 </tex> | ||
+ | |||
+ | [[Файл: Вставка при двойном хэшировании.svg.jpeg|thumb|right|Вставка при двойном хешировании]] | ||
+ | |||
+ | ===Пример=== | ||
+ | |||
+ | Показана хеш-таблица размером 13 ячеек, в которой используются вспомогательные функции: | ||
+ | |||
+ | <center> | ||
+ | <tex> h(k,i) = (h_1(k) + i \cdot h_2(k)) \mod 13 </tex> | ||
+ | </center> | ||
+ | |||
+ | <center> | ||
+ | <tex> h_1(k) = k \mod 13 </tex> | ||
+ | </center> | ||
+ | |||
+ | <center> | ||
+ | <tex> h_2(k) = 1 + k \mod 11 </tex> | ||
+ | </center> | ||
+ | |||
+ | Мы хотим вставить ключ 14. Изначально <tex> i = 0 </tex>. Тогда <tex> h(14,0) = (h_1(14) + 0\cdot h_2(14)) \mod 13 = 1 </tex>. Но ячейка с индексом 1 занята, поэтому увеличиваем <tex> i </tex> на 1 и пересчитываем значение хеш-функции. Делаем так, пока не дойдем до пустой ячейки. При <tex> i = 2 </tex> получаем <tex> h(14,2) = (h_1(14) + 2\cdot h_2(14)) \mod 13 = 9 </tex>. Ячейка с номером 9 свободна, значит записываем туда наш ключ. | ||
+ | |||
+ | Таким образом, основная особенность двойного хеширования состоит в том, что при различных <tex> k </tex> пара <tex> (h_1(k),h_2(k)) </tex> дает различные последовательности ячеек для исследования. | ||
+ | |||
+ | ===Простая реализация=== | ||
+ | Пусть у нас есть некоторый объект <tex> item </tex>, в котором определено поле <tex> key </tex>, от которого можно вычислить хеш-функции <tex> h_1(key)</tex> и <tex> h_2(key) </tex> | ||
+ | |||
+ | Так же у нас есть таблица <tex> table </tex> величиной <tex> m </tex>, состоящая из объектов типа <tex> item </tex>. | ||
+ | |||
+ | '''Вставка''' | ||
+ | <pre>add(item) | ||
+ | x = h1(item.key) | ||
+ | y = h2(item.key) | ||
+ | for (i = 0; i < m; i++) | ||
+ | if table[x] == null | ||
+ | table[x] = item | ||
+ | return | ||
+ | x = (x + y) mod m | ||
+ | table.resize() //ошибка, требуется увеличить размер таблицы</pre> | ||
+ | |||
+ | '''Поиск''' | ||
+ | <pre>search(key) | ||
+ | x = h1(key) | ||
+ | y = h2(key) | ||
+ | for (i = 0; i < m; i++) | ||
+ | if table[x] != null | ||
+ | if table[x].key == key | ||
+ | return table[x] | ||
+ | else | ||
+ | return null | ||
+ | x = (x + y) mod m | ||
+ | return null</pre> | ||
+ | |||
+ | ===Реализация с удалением=== | ||
+ | Что бы наша хеш-таблица поддерживала удаление, требуется добавить массив <tex>deleted</tex> типов <tex>bool</tex>, равный по величине массиву <tex>table</tex>. Теперь при удалении мы просто будем помечать наш объект ''как удалённый'', а при добавлении как ''не удалённый'' и замещать новым добавляемым объектом. При поиске, помимо равенства ключей, мы смотрим, удалён ли элемент, если да, то идём дальше. | ||
+ | |||
+ | '''Вставка''' | ||
+ | <pre>add(item) | ||
+ | x = h1(item.key) | ||
+ | y = h2(item.key) | ||
+ | for (i = 0; i < m; i++) | ||
+ | if table[x] == null || deleted[x] | ||
+ | table[x] = item | ||
+ | deleted[x] = false | ||
+ | return | ||
+ | x = (x + y) mod m | ||
+ | table.resize() //ошибка, требуется увеличить размер таблицы</pre> | ||
+ | |||
+ | '''Поиск''' | ||
+ | <pre>search(key) | ||
+ | x = h1(key) | ||
+ | y = h2(key) | ||
+ | for (i = 0; i < m; i++) | ||
+ | if table[x] != null | ||
+ | if table[x].key == key && !deleted[x] | ||
+ | return table[x] | ||
+ | else | ||
+ | return null | ||
+ | x = (x + y) mod m | ||
+ | return null</pre> | ||
+ | |||
+ | '''Удаление''' | ||
+ | <pre>remove(key) | ||
+ | x = h1(key) | ||
+ | y = h2(key) | ||
+ | for (i = 0; i < m; i++) | ||
+ | if table[x] != null | ||
+ | if table[x].key == key | ||
+ | deleted[x] = true | ||
+ | else | ||
+ | return | ||
+ | x = (x + y) mod m</pre> | ||
+ | |||
+ | ==См. также== | ||
+ | * [[Хеширование]] | ||
+ | * [[Хеширование_кукушки|Хеширование кукушки]] | ||
+ | |||
+ | == Литература == | ||
+ | * Бакнелл Дж. М. '''Фундаментальные алгоритмы и структуры данных в Delphi''', ''2003'' | ||
+ | * Кнут Д. Э. '''Искусство программирования, том 3. Сортировка и поиск''', ''2-е издание, 2000'' | ||
+ | * Томас Кормен, Чарльз Лейзерсон, Рональд Ривест, Клиффорд Штайн. '''Алгоритмы. Построение и анализ''', ''2010'' | ||
+ | * Седжвик Р. '''Фундаментальные алгоритмы на C. Части 1-4. Анализ. Структуры данных. Сортировка. Поиск''', ''2003'' | ||
+ | |||
+ | ==Ссылки== | ||
+ | * [http://en.wikipedia.org/wiki/Double_hashing Wikipedia {{---}} Double_hashing] | ||
+ | * [http://rain.ifmo.ru/cat/view.php/vis/hashtables/hash-2001-2 Пример хеш таблицы] | ||
+ | * [http://research.cs.vt.edu/AVresearch/hashing/double.php Пример хеш таблицы с двойным хешированием] | ||
[[Категория: Дискретная математика и алгоритмы]] | [[Категория: Дискретная математика и алгоритмы]] | ||
[[Категория: Хеширование]] | [[Категория: Хеширование]] |
Версия 23:27, 11 июня 2012
Поиск свободного места при закрытом хешировании - задача, возникающая при создании хеш-таблицы, использующей так называемое закрытое хеширование.
При использовании открытого хеширования такой проблемы не возникает, так как там в каждой ячейке хранится список всех элементов. При добавлении необходимо просто добавить элемент в начало списка.
Закрытое хеширование работает иначе: в каждой ячейке хеш-таблицы хранится только один элемент. Тогда при добавлении, если ячейка свободна, мы просто записываем добавляемый элемент в эту ячейку. Однако если эта ячейка занята - необходимо поместить добавляемый элемент в какую-нибудь другую свободную ячейку. Такие ситуации нередки, так как невозможно использовать хеш-функцию, не дающую коллизий, а каждой ячейке таблицы соответствует одно значение хеш-функции. Далее мы рассмотрим несколько стратегий поиска свободного места в данном случае.
Содержание
Стратегии поиска
Последовательный поиск
При попытке добавить элемент в занятую ячейку
начинаем последовательно просматривать ячейки и так далее, пока не найдём свободную ячейку. В неё и запишем элемент.Линейный поиск
Выбираем шаг
. При попытке добавить элемент в занятую ячейку начинаем последовательно просматривать ячейки и так далее, пока не найдём свободную ячейку. В неё и запишем элемент. По сути последовательный поиск - частный случай линейного, где .Квадратичный поиск
Шаг
не фиксирован, а изменяется квадратично: . Соответственно при попытке добавить элемент в занятую ячейку начинаем последовательно просматривать ячейки и так далее, пока не найдём свободную ячейку.Возможные проблемы
При поиске элемента может получится так, что мы дойдём до конца таблицы. Обычно поиск продолжается, начиная с другого конца. Однако, если мы придём в ту ячейку, откуда начинался поиск, то добавить элемент в текущую таблицу будет невозможно и необходимо провести операцию перехеширования.
Проверка наличия элемента в таблице
Проверка осуществляется аналогично добавлению: мы проверяем ячейку
и другие, в соответствии с выбранной стратегией, пока не найдём искомый элемент или свободную ячейку.Проблемы закрытого хеширования
Проблем две - крайне нетривиальное удаление элемента из таблицы и образование кластеров. Кластер - последовательность занятых клеток. Их наличие замедляет все операции с хеш-таблицей: при добавлении требуется перебирать всё больше элементов, при проверке тоже. Чем больше в таблице элементов, тем больше в ней кластеры и тем выше вероятность того, что добавляемый элемент попадёт в кластер. Для защиты от кластеризации используется двойное хеширование и хеширование кукушки.
Двойное хеширование
Двойное хеширование — метод борьбы с коллизиями, возникающими при закрытом хешировании, основанный на использовании двух хеш-функций для построения различных последовательностей исследования хеш-таблицы.
Принцип двойного хеширования
При двойном хешировании используются две независимые хеш-функции
и . Пусть — это наш ключ, — размер нашей таблицы, — остаток от деления на , тогда сначала исследуется ячейка с адресом , если она уже занята, то рассматривается , затем и так далее. В общем случае идёт проверка последовательности ячеек гдеТаким образом, операции вставки, удаления и поиска в лучшем случае выполняются за линейного разрешения коллизий. Однако в среднем, при грамотном выборе хеш-функций, двойное хеширование будет выдавать лучшие результаты, за счёт того, что вероятность совпадения значений сразу двух независимых хеш-функций ниже, чем одной.
, в худшем — за , что не отличается от обычного
Выбор хеш-функций
может быть обычной хеш-функцией. Однако чтобы последовательность исследования могла охватить всю таблицу, должна возвращать значения:
- не равные
- независимые от
- взаимно простые с величиной хеш-таблицы
Есть два удобных способа это сделать. Первый состоит в том, что в качестве размера таблицы используется простое число, а
возвращает натуральные числа, меньшие . Второй — размер таблицы является степенью двойки, а возвращает нечетные значения.Например, если размер таблицы равен
, то в качестве можно использовать функцию видаПример
Показана хеш-таблица размером 13 ячеек, в которой используются вспомогательные функции:
Мы хотим вставить ключ 14. Изначально
. Тогда . Но ячейка с индексом 1 занята, поэтому увеличиваем на 1 и пересчитываем значение хеш-функции. Делаем так, пока не дойдем до пустой ячейки. При получаем . Ячейка с номером 9 свободна, значит записываем туда наш ключ.Таким образом, основная особенность двойного хеширования состоит в том, что при различных
пара дает различные последовательности ячеек для исследования.Простая реализация
Пусть у нас есть некоторый объект
, в котором определено поле , от которого можно вычислить хеш-функции иТак же у нас есть таблица
величиной , состоящая из объектов типа .Вставка
add(item) x = h1(item.key) y = h2(item.key) for (i = 0; i < m; i++) if table[x] == null table[x] = item return x = (x + y) mod m table.resize() //ошибка, требуется увеличить размер таблицы
Поиск
search(key) x = h1(key) y = h2(key) for (i = 0; i < m; i++) if table[x] != null if table[x].key == key return table[x] else return null x = (x + y) mod m return null
Реализация с удалением
Что бы наша хеш-таблица поддерживала удаление, требуется добавить массив
типов , равный по величине массиву . Теперь при удалении мы просто будем помечать наш объект как удалённый, а при добавлении как не удалённый и замещать новым добавляемым объектом. При поиске, помимо равенства ключей, мы смотрим, удалён ли элемент, если да, то идём дальше.Вставка
add(item) x = h1(item.key) y = h2(item.key) for (i = 0; i < m; i++) if table[x] == null || deleted[x] table[x] = item deleted[x] = false return x = (x + y) mod m table.resize() //ошибка, требуется увеличить размер таблицы
Поиск
search(key) x = h1(key) y = h2(key) for (i = 0; i < m; i++) if table[x] != null if table[x].key == key && !deleted[x] return table[x] else return null x = (x + y) mod m return null
Удаление
remove(key) x = h1(key) y = h2(key) for (i = 0; i < m; i++) if table[x] != null if table[x].key == key deleted[x] = true else return x = (x + y) mod m
См. также
Литература
- Бакнелл Дж. М. Фундаментальные алгоритмы и структуры данных в Delphi, 2003
- Кнут Д. Э. Искусство программирования, том 3. Сортировка и поиск, 2-е издание, 2000
- Томас Кормен, Чарльз Лейзерсон, Рональд Ривест, Клиффорд Штайн. Алгоритмы. Построение и анализ, 2010
- Седжвик Р. Фундаментальные алгоритмы на C. Части 1-4. Анализ. Структуры данных. Сортировка. Поиск, 2003