Initial commit
This commit is contained in:
@@ -0,0 +1,274 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Страница 7</title>
|
||||
</head>
|
||||
<body>
|
||||
<p style="text-align: center">
|
||||
<i><b>Страница 7</b></i>
|
||||
</p>
|
||||
<hr />
|
||||
<h1 style="text-align: center">Решение проблем коллизий</h1>
|
||||
<h2 style="text-align: center">
|
||||
Открытая и прямая адресации [5, c. 170-179]
|
||||
</h2>
|
||||
<p>Оглавление:</p>
|
||||
<ul>
|
||||
<li><a href="#description">Описание.</a></li>
|
||||
<li><a href="#linear">Линейное пробирование.</a></li>
|
||||
<li><a href="#quadratic">Квадратичное пробирование.</a></li>
|
||||
<li><a href="#pseudorandom">Псевдослучайное пробирование.</a></li>
|
||||
<li><a href="#double">Двойное хеширование.</a></li>
|
||||
</ul>
|
||||
<div>
|
||||
<h3 style="text-align: center"><a name="description">Описание</a></h3>
|
||||
<p style="text-align: justify">
|
||||
Безусловно, прямое связывание имеет свои преимущества. Основное из них
|
||||
заключается в том, что общее количество значений не зависит от
|
||||
количества блоков. Однако есть у таких хеш-таблиц и некоторые
|
||||
недостатки. Например, если элементов в блоках накопится слишком много,
|
||||
поиск нужного займет продолжительное время. Чтобы сократить временные
|
||||
затраты, число блоков можно увеличить, но тогда вы рискуете получить ряд
|
||||
пустых, которые потребуют места в памяти и не будут использоваться
|
||||
таблицей.
|
||||
</p>
|
||||
<p style="text-align: justify">
|
||||
Еще один способ реализации хеш-таблиц — <i>открытая адресация</i>.
|
||||
В этом случае значения хранятся в массиве, а функция хеширования
|
||||
представляет собой некоторые расчеты.
|
||||
</p>
|
||||
<p style="text-align: justify">
|
||||
Хеш-таблица с <i>прямой адресацией</i> отличается от хеш-таблицы с
|
||||
открытой адресацией тем, что в первом случае гарантировано должна
|
||||
использоваться идеальная хеш-функция. Это обусловлено тем, что в данной
|
||||
хеш-таблице должны отсутствовать коллизии, соответственно, как и методы
|
||||
их разрешения. Стоит отметить, что под данное описание подходит
|
||||
<i>идеальное хеширование</i> хеш-таблицы<sup>1</sup>.
|
||||
</p>
|
||||
<p style="text-align: justify">
|
||||
В разных видах открытой адресации используются различные функции
|
||||
хеширования. Неодинакова и политика разрешения коллизий, но в общем
|
||||
случае она выглядит так: для каждого значения в массиве подбирается
|
||||
несколько ячеек, и если первая уже занята, алгоритм пробует использовать
|
||||
вторую, затем третью и так до тех пор, пока не найдет свободную или не
|
||||
придет к выводу, что таковой нет.
|
||||
</p>
|
||||
<p style="text-align: justify">
|
||||
Серия ячеек, которую алгоритм подбирает для значения, называется пробной
|
||||
последовательностью. По ее средней длине хорошо оценивать наполненность
|
||||
хештаблицы. В идеале пробная последовательность должна равняться 1 или
|
||||
2, большие цифры говорят о полной таблице.
|
||||
</p>
|
||||
<p style="text-align: justify">
|
||||
Иногда политика разрешения коллизий такова, что для элемента может не
|
||||
найтись свободной ячейки, даже когда она есть. Если пробная
|
||||
последовательность повторяет саму себя перед тем, как проверить
|
||||
очередную запись, некоторые записи могут остаться неиспользованными.
|
||||
</p>
|
||||
<p style="text-align: justify">
|
||||
Чтобы найти элемент в хеш-таблице, алгоритм следует за пробной
|
||||
последовательностью, пока не произойдет одно из трех событий.
|
||||
</p>
|
||||
<ol>
|
||||
<li style="text-align: justify">
|
||||
Если пробная последовательность сумела отыскать элемент, задача
|
||||
выполнена.
|
||||
</li>
|
||||
<li style="text-align: justify">
|
||||
Если пробная последовательность находит пустую запись в массиве,
|
||||
элемента нет.
|
||||
</li>
|
||||
<li style="text-align: justify">
|
||||
Пробная последовательность проверяет <i>M</i> записей (по размеру
|
||||
массива) — и алгоритм приходит к выводу, что значение
|
||||
отсутствует. Последовательность может перебрать не все элементы, но
|
||||
если пройдет по всем, вы будете знать, что они точно пересмотрены или
|
||||
что целевой элемент не найден. Она также может проверить в цикле одну
|
||||
и ту же позицию несколько раз. В любом случае значение не должно
|
||||
присутствовать, поскольку иначе оно бы добавлялось к массиву с
|
||||
использованием той же пробной последовательности.
|
||||
</li>
|
||||
</ol>
|
||||
<p style="text-align: justify">
|
||||
При разумном заполнении хеш-таблицы открытая адресация работает очень
|
||||
быстро. Если длина пробной последовательности равна 1 или 2, добавление
|
||||
и нахождение элементов выполняются за время <i>O(1)</i>. Но если массив
|
||||
из N элементов существенно переполнен, производительность снижается. В
|
||||
наихудшем случае алгоритм придет к выводу, что элемента в массиве нет,
|
||||
за время <i>O(N)</i>. Поиск присутствующих элементов также будет
|
||||
выполняться крайне медленно.
|
||||
</p>
|
||||
<p style="text-align: justify">
|
||||
Вы можете увеличить размер массива, чтобы уменьшить коэффициент
|
||||
наполненности хеш-таблицы. Для этого создайте новый массив и рехешируйте
|
||||
элементы в нем. Для каждого из них операция займет <i>O(1)</i> времени,
|
||||
а общая производительность алгоритма составит <i>O(N)</i>.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 style="text-align: center">
|
||||
<a name="linear">Линейное пробирование</a>
|
||||
</h3>
|
||||
<p style="text-align: justify">
|
||||
В линейном пробировании политика разрешения коллизий добавляет к каждой
|
||||
ячейке постоянное число (чаще всего 1), называемое шагом по индексу,
|
||||
которое генерирует пробную последовательность. При каждом очередном
|
||||
добавлении берется размер массива по модулю, стало быть, при
|
||||
необходимости последовательность возвращается к началу массива.
|
||||
</p>
|
||||
<p style="text-align: justify">
|
||||
Предположим, в хеш-таблице 100 элементов, а правило хеширования звучит
|
||||
следующим образом: N связано с ячейкой N mod 100. Тогда пробная
|
||||
последовательность для значения 2197 проверяет ячейки 97, 98, 99, 0, 1,
|
||||
2 и т. д.
|
||||
</p>
|
||||
<p style="text-align: justify">
|
||||
На рисунке 7.1 представлен массив из 10 записей, который уже содержит
|
||||
несколько значений. Чтобы добавить в него новое значение 71, используя
|
||||
линейную пробную последовательность, нужно связать его с ячейкой 71 mod
|
||||
10 = 1. Но эта ячейка уже занята значением 61, поэтому алгоритм
|
||||
переходит к ячейке 2, которая тоже заполнена. Следующей должна быть
|
||||
ячейка 3 — она свободна, и алгоритм размещает там 71.
|
||||
</p>
|
||||
<div style="text-align: center">
|
||||
<img src="7.1.png" width="800" />
|
||||
<p style="text-align: center">
|
||||
Рисунок 7.1 - линейная пробная последовательность
|
||||
</p>
|
||||
</div>
|
||||
<p style="text-align: justify">
|
||||
Преимущество данного метода — в его простоте. Если необходимо,
|
||||
пробная последовательность пройдет по каждой ячейке массива и вставит
|
||||
элемент в свободное место, если оно еще осталось. Но есть и
|
||||
сопутствующий недостаток — так называемая
|
||||
<i>первичная кластеризация</i>, которая проявляется в образовании
|
||||
больших блоков смежных записей и приводит к длинным пробным
|
||||
последовательностям. В результате при добавлении нового элемента и его
|
||||
хешировании к какой-либо записи в кластере пробная последовательность
|
||||
вынуждена пройти через весь кластер, чтобы найти свободную ячейку.
|
||||
</p>
|
||||
<p style="text-align: justify">
|
||||
Чтобы понять, как формируются кластеры, рассмотрим пример пустой
|
||||
хеш-таблицы. Предположим, в ней содержится <i>N</i> записей и существует
|
||||
вероятность <i>1/N</i>, что она закончится в любой данной позиции при
|
||||
добавлении случайного числа. А теперь представим, что <i>K</i> —
|
||||
конечная позиция таблицы. Существует вероятность <i>1/N</i>, что новое
|
||||
случайное число попадет в позицию K и линейное пробирование попытается
|
||||
расположить элемент в позиции <i>K + 1</i>. Но та же вероятность
|
||||
характерна и для случая, при котором новый элемент будет связан с
|
||||
позицией <i>K + 1</i> напрямую. Значит, существует вероятность
|
||||
<i>2/N</i>, что элемент займет конечную позицию <i>K + 1</i> и
|
||||
сформируется малый кластер.
|
||||
</p>
|
||||
<p style="text-align: justify">
|
||||
Через какое-то время таких кластеров станет много. Чем больше они будут,
|
||||
тем больше вероятность того, что новый элемент добавится в конец одного
|
||||
из них. В итоге более мелкие кластеры сольются в более крупные, массив
|
||||
заполнится ими — и возникнут длинные пробные последовательности.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 style="text-align: center">
|
||||
<a name="quadratic">Квадратичное пробирование</a>
|
||||
</h3>
|
||||
<p style="text-align: justify">
|
||||
Возникновение больших кластеров при линейном пробировании связано с тем,
|
||||
что новые элементы связываются с ячейками, стоящими в конце группы, и
|
||||
постепенно увеличивают ее. Предотвратить подобную ситуацию помогает
|
||||
<i>квадратичное пробирование</i>. Для создания пробной
|
||||
последовательности в качестве шага по индексу берется квадрат количества
|
||||
ячеек. Другими словами, если в линейном пробировании существует
|
||||
последовательность <i>K, K + 1, K + 2, K + 3,</i> ... то в квадратичном
|
||||
варианте она будет выглядеть так:
|
||||
<i>K, K + 12, K + 22, K + 32, ...</i> В этом случае, если два
|
||||
элемента окажутся связанными с разными позициями в одном и том же
|
||||
кластере, они не обязательно будут придерживаться одной пробной
|
||||
последовательности и попадут в конец кластера.
|
||||
</p>
|
||||
<p style="text-align: justify">
|
||||
На рисунке 7.2 показана хеш-таблица, в начале которой есть группа из
|
||||
пяти элементов. Новое значение 71 получает пробную последовательность
|
||||
<i>1, 1 + 12 = 2, 1 + 22 = 5, 1 + 32 = 10</i> и не добавляется к
|
||||
существующему кластеру. Значение <i>93 </i>поначалу связано с тем же
|
||||
кластером, но согласно собственной пробной последовательности
|
||||
<i>3, 3 + 12 = 4, 3 + 22 = 7</i> также не попадает в него.
|
||||
</p>
|
||||
<div style="text-align: center">
|
||||
<img src="7.2.png" width="800" />
|
||||
<p style="text-align: center">Рисунок 7.2 - Квадратичное пробирование</p>
|
||||
</div>
|
||||
<p style="text-align: justify">
|
||||
Квадратичное пробирование предотвращает первичную, но не
|
||||
<i>вторичную кластеризацию</i>, при которой значения, связанные с
|
||||
одинаковой начальной позицией в массиве, получают одну и ту же пробную
|
||||
последовательность, иногда очень длинную. В результате образуется точно
|
||||
такая же группа элементов, но уже не собранных вместе, а распределенных
|
||||
по всему массиву.
|
||||
</p>
|
||||
<p style="text-align: justify">
|
||||
Еще один недостаток квадратичного пробирования связан с тем, что оно
|
||||
может не найти свободную позицию, даже если в хеш-таблице их несколько.
|
||||
Дело в том, что с каждым разом перемещение по массиву происходит все
|
||||
дальше и дальше, и незаполненная ячейка попросту пропускается.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 style="text-align: center">
|
||||
<a name="pseudorandom">Псевдослучайное пробирование</a>
|
||||
</h3>
|
||||
<p style="text-align: justify">
|
||||
Это пробирование подобно линейному, за исключением того, что шаг по
|
||||
индексу формирует псевдослучайная функция изначально связанной ячейки.
|
||||
Предположим, что это ячейка <i>K</i>, тогда пробная последовательность
|
||||
будет выглядеть следующим образом: <i>K, K + p, K + 2p, ...</i> где p
|
||||
определяется псевдослучайной функцией.
|
||||
</p>
|
||||
<p style="text-align: justify">
|
||||
Подобно квадратичному пробированию, псевдослучайное предотвращает только
|
||||
первичную кластеризацию, но страдает от вторичной: значения, связанные с
|
||||
одной и той же начальной позицией, размещаются в хеш-таблице согласно
|
||||
одной и той же пробной последовательности. Точно так же псевдослучайное
|
||||
пробирование может пропускать некоторые неиспользуемые записи.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 style="text-align: center">
|
||||
<a name="double">Двойное хеширование</a>
|
||||
</h3>
|
||||
<p style="text-align: justify">
|
||||
Чтобы избавиться от вторичной кластеризации значения, связанные с одной
|
||||
и той же начальной ячейкой, должны получать разные пробные
|
||||
последовательности. И здесь пригодится двойное хеширование. Оно похоже
|
||||
на псевдослучайное пробирование, только шаг для индекса задается не
|
||||
псевдослучайной функцией начальной ячейки, а второй функцией
|
||||
хеширования.
|
||||
</p>
|
||||
<p style="text-align: justify">
|
||||
Допустим, значения <i>A</i> и <i>B</i> связаны с позицией <i>K</i>. В
|
||||
псевдослучайном пробировании шаг по индексу <i>p</i> генерируется
|
||||
функцией <i>F<sub>1</sub> (K)</i>, затем оба значения используют пробную
|
||||
последовательность <i>K, K + p, K + 2p, K + 3p, ...</i> В двойном
|
||||
хешировании для связывания начальных значений <i>А</i> и
|
||||
<i>В</i> применяется функция псевдослучайного хеширования
|
||||
<i>F<sub>2</sub></i
|
||||
>. В итоге при одном и том же начальном <i>K</i> образуются две пробные
|
||||
последовательности с различными шагами по индексу:
|
||||
<i>p<sub>A</sub> = F<sub>2</sub> (A)</i> и
|
||||
<i>p<sub>B</sub> = F<sub>2</sub> (B)</i> соответственно.
|
||||
</p>
|
||||
<p style="text-align: justify">
|
||||
Несмотря на то, что двойное хеширование хорошо справляется с первичной и
|
||||
вторичной кластеризациями, оно точно так же, как и псевдослучайное
|
||||
пробирование, может пропускать неиспользуемые записи.
|
||||
</p>
|
||||
</div>
|
||||
<hr />
|
||||
<p>
|
||||
<sup>1</sup>Данный метод хеширования хорошо описан в книге Томаса Кормена
|
||||
[2]
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user