4.5 Открытая адресация; способы вычисления последовательности испробованных мест: линейная последовательность проб, квадратичная последовательность проб, двойное хеширование
В отличие от хеширования с цепочками, при открытой адресации (open addressing) никаких списков нет, а все записи хранятся в самой хеш-таблице каждая ячейка таблицы содержит либо элемент динамического множества, либо NIL. Поиск заключается в том, что мы определённым образом просматриваем элементы таблицы, пока не найдём то, что ищем, или не удостоверимся, что элемента с таким ключом в таблице нет. Тем самым число хранимых элементов не может быть больше размера таблицы: коэффициент заполнения не больше 1.
Конечно, и при хешировании с цепочками можно использовать свободные места в хеш-таблице для хранения списков, но при открытой адресации указатели вообще не используются: последовательность просматриваемых ячеек вычисляется. За счет экономии памяти на указателях можно увеличить количество позиций в таблице, что уменьшает число коллизий и сокращает поиск.
Чтобы добавить новый элемент в таблицу с открытой адресацией, ячейки которой занумерованы целыми числами от 0 до т – 1, мы просматриваем её, пока не найдем свободное место. Если всякий раз просматривать ячейки подряд (от 0-й до (m – 1)-й), потребуется время (n), но суть в том, что порядок просмотра таблицы зависит от ключа. Иными словами, мы добавляем к хеш-функции второй аргумент – номер попытки (нумерацию начинаем с нуля), так что хеш-функция имеет вид
h: U x {0,1,..., m – 1} → {0, 1,...,m – 1}
(U – множество ключей). Последовательность испробованных мест, или последовательность проб (probe sequence), для данного ключа k имеет вид
h(k,0),h(k,1),…,h(k,m – 1);
функция h должна быть такой, чтобы каждое из чисел от 0 до т – 1 встретилось в этой последовательности ровно один раз (для каждого ключа все позиции таблицы должны быть доступны). Ниже приводится текст процедуры добавления в таблицу Т с открытой адресацией; в нем подразумевается, что записи не содержат дополнительной информации, кроме ключа. Если ячейка таблицы пуста, в ней записан NIL (фиксированное значение, отличное от всех ключей).
Листинг 4.4 – Процедура добавления элемента в таблицу с открытой адресацией
При поиске элемента с ключом k в таблице с открытой адресацией ячейки таблицы просматриваются в том же порядке, что и при добавлении в нее элемента с ключом k. Если при этом мы натыкаемся на ячейку, в которой записан NIL, то можно быть уверенным, что искомого элемента в таблице нет (иначе он был бы занесён в эту ячейку). (Внимание: предполагается, что никакие элементы из таблицы не удаляются!)
Вот текст процедуры поиска hash-search(если элемент с ключом k содержится в таблице Т в позиции j, процедура возвращает j, в противном случае она возвращает nil).
Листинг 4.5 – Процедура поиска элемента в таблице с открытой адресацией
Удалить элемент из таблицы с открытой адресацией не так просто. Если просто записать на его место NIL, то в дальнейшем мы не сможем найти те элементы, в момент добавления которых в таблицу это место было занято (и из-за этого был выбран более далёкий элемент в последовательности испробованных мест). Возможное решение – записывать на место удалённого элемента не nil, а специальное значение deleted («удалён»), и при добавлении рассматривать ячейку с записью deleted как свободную, а при поиске – как занятую (и продолжать поиск). Недостаток этого подхода в том, что время поиска может оказаться большим даже при низком коэффициенте заполнения. Поэтому, если требуется удалять записи из хеш-таблицы, предпочтение обычно отдают хешированию с цепочками.
В нашем анализе открытой адресации мы будем исходить из предположения, что хеширование равномерно (uniform) в том смысле, что все m! перестановок множества {0,1,...,m – 1} равновероятны. На практике это вряд ли так, хотя бы по той причине, что для этого необходимо, чтобы число возможных ключей было как минимум ≤ m!, где m – число хеш-значений. Поэтому обычно пользуются более или менее удачными суррогатами, вроде описываемого ниже двойного хеширования.
Обычно применяют такие три способа вычисления последовательности испробованных мест: линейный, квадратичный и двойное хеширование. В каждом из этих способов последовательность h(k, 0), h(k, 1),..., h(k, т – 1) будет перестановкой множества {0,1,..., m – 1} при любом значении ключа k, но ни один из этих способов не является равномерным по той причине, что они дают не более m2 перестановок из m! возможных. Больше всего разных перестановок получается при двойном хешировании; не удивительно, что и на практике этот способ дает лучшие результаты.
const
В = {подходящая константа};
empty = ' '; {10 пробелов}
deleted = '**********'; {10 символов *}
c = 1; {шаг перебора}
MaxCase = {подходящая константа}; {Max количество попыток}
type
TypeElem = string[10]
HTableOpen = array[0..B-1] of TypeElem;
Теперь приведем сами алгоритмы на примере линейного опробования. Для остальных методов повторного хеширования алгоритмы идентичны.
procedure Clear_HTableOpen(var A: HTableOpen);
{Процедура очистки хеш-таблицы}
var
IndexSeg: integer;
begin
for IndexSeg:=0 to B-l do A[IndexSeg] := empty;
end;
function Find_HTableOpen(x: TypeElem;
var A: HTableOpen;
var IndexSeg: integer): boolean;
{функция поиска элемента x в хеш-таблице. Принимает значение true, если найден и возвращает номер сегмента, в котором располагается найденный элемент, или принимает значение false и возвращает 0}
var
i: integer;
begin
i := 0;
repeat
IndexSeg := ((h(x) + c*i) mod B + {лин.опр.с цикл.переходом}
(h(x) + c*i) div B ) {смещение после перехода}
mod B; {ограничение смещения}
i := i + 1;
until (A[IndexSeg] = x) or {нашли}
(A[IndexSeg] = empty) or {дальше уже нет}
(i > MaxCase); {слишком долго ищем}
if A[IndexSeg] = x then begin
Find_HTableOpen := true;
end else begin
Find_HTableOpen := false;
IndexSeg := 0;
end;
end;
function Add_HTableOpen(x: TypeElem;
var A: HTableOpen): boolean;
{Процедура добавления элемента x в хеш-таблицу. Возвращает true, если элемент добавлен, и false – в обратном}
var
i,
IndexSeg: integer; {номер сегмента}
begin
if not Find_HTableOpen(x, A, IndexSeg) then begin
{Если в таблице элемент уже есть, то добавлять не надо}
i := 0;
repeat
IndexSeg := ((h(x) + c*i) mod B +
(h(x) + c*i) div B )
mod B;
i := i + 1;
until (A[IndexSeg] = empty) or {нашли место}
(A[IndexSeg] = deleted) or {тоже можно занять}
(i > MaxCase); {слишком долго ищем}
if (A[IndexSeg] = empty) or
(A[IndexSeg] = deleted) then begin
A[IndexSeg] := x;
Add_HTableOpen := true;
end else begin
Add_HTableOpen := false;
end;
end
end;
procedure Del_HTableOpen(x: TypeElem; var A: HTableOpen);
{Процедура удаления элемента x из хеш-таблицы}
var
IndexSeg: integer; {номер сегмента}
begin
if Find_HTableOpen(x, A, IndexSeg) then begin
{Если в таблице элемент уже нет, то удалять не надо}
A[IndexSeg] := deleted;
end
end;
Листинг 4.5 – Словарные операции при открытой адресации
- Министерство образования Российской Федерации
- Содержание
- 1.2 Скорость роста функций
- 1.3 Анализ алгоритмов; время работы в лучшем, худшем случаях и в среднем
- 1.4 Типы данных, структуры данных и абстрактные типы данных
- 1.5 Динамические множества
- 2 Алгоритмы сортировок
- 2.1 Понятие внутренней и внешней сортировки
- 2.2 Сортировка вставками
- 2.3 Сортировка слиянием
- 2.3.1 Описание алгоритма
- 2.3.2 Анализ времени работы алгоритмов «разделяй и властвуй»
- 2.3.2 Анализ времени работы сортировки слиянием через рекуррентное соотношение
- 2.3.3 Анализ времени работы сортировки слиянием через геометрическую интерпретацию
- 2.4 Пирамидальная сортировка
- 2.4.1 Введение в алгоритм
- 2.4.2 Сохранение основного свойства кучи
- 2.4.3 Построение кучи
- 2.5 Быстрая сортировка
- 2.5.1 Введение в алгоритм
- 2.5.2 Описание
- 2.5.3 Разбиение массива
- 2.5.4 Особенности работы быстрой сортировки
- 2.6 Особенности реализации алгоритмов сортировки; сортировка за линейное время
- 2.6.1 Введение
- 2.6.2 Разрешающее дерево сортировки сравнениями
- 2.7 Цифровая сортировка
- 2.8 Сортировка вычерпыванием
- 2.8.1 Описание алгоритма
- 2.8.2 Вероятностный анализ времени работы сортировки вычерпыванием
- 2.8.3 Анализ времени работы сортировки вычерпыванием через геометрическую интерпретацию
- 2.9 Сортировка подсчетом
- 2.9.1 Описание алгоритма
- 2.9.2 Анализ времени работы
- 3 Элементарные и нелинейные структуры данных
- 3.1 Элементарные структуры: список, стек, очередь, дек
- 3.1.1 Список Линейный однонаправленный список
- Линейный двунаправленный список
- Двунаправленный список с фиктивными элементами
- Циклические списки
- Циклический однонаправленный список
- Циклический двунаправленный список
- 3.1.2 Стек
- 3.1.3 Очередь
- 3.1.3 Дек
- 3.2 Нелинейные структуры данных
- 3.2.1 Представление корневых деревьев в эвм
- Обходы деревьев
- 3.2.2 Двоичные деревья Спецификация двоичных деревьев
- Реализация
- Обходы двоичных деревьев
- 3.2.3 Двоичные деревья поиска Основные операции
- Минимум и максимум
- Следующий и предыдущий элементы
- Добавление и удаление
- Случайные деревья поиска
- Оптимальные деревья поиска
- 4 Хеширование
- 4.1 Введение
- 4.2 Прямая адресация; таблицы с прямой адресацией
- 4.3 Хеш – таблицы; возникновение коллизий и их разрешение
- Разрешение коллизий с помощью цепочек
- Анализ хеширования с цепочками
- 4.4 Способы построения хеш – функций Выбор хорошей хеш-функции
- Ключи как натуральные числа
- Деление с остатком
- Умножение
- Универсальное хеширование
- 4.5 Открытая адресация; способы вычисления последовательности испробованных мест: линейная последовательность проб, квадратичная последовательность проб, двойное хеширование
- Линейная последовательность проб
- 1 / (1 – )
- 5 Основные принципы разработки алгоритмов
- 5.1 Введение в теорию графов
- 5.1.1 Графы
- 5.1.2 Представление графов
- 5.2 Алгоритмы на графах: поиск в ширину, поиск в глубину
- 5.2.1 Поиск в ширину (волновой алгоритм)
- 5.2.2 Анализ поиска в ширину
- 5.2.3 Деревья поиска в ширину
- 5.2.4 Поиск в глубину
- 5.2.5 Анализ поиска в глубину
- 5.2.6 Свойства поиска в глубину
- 5.2.7 Классификация рёбер
- 5.3 Топологическая сортировка, задача о разбиении графа на сильно связанные компоненты
- 5.3.1 Топологическая сортировка
- 5.3.2 Сильно связные компоненты
- 5.4 Алгоритм построения минимального остовного дерева
- 5.4.1 Остовные деревья минимальной стоимости
- 5.4.2 Построение минимального покрывающего дерева
- 5.4.3 Алгоритмы Крускала и Пpимa
- 5.4.4 Алгоритм Крускала
- 5.4.5 Алгоритм Прима
- 5.5 Задача нахождения кратчайших путей на графах; алгоритм Флойда; алгоритм Дейкстры
- 5.5.1 Нахождение кратчайшего пути
- 5.5.2 Алгоритм Дейкстры
- 5.5.3 Алгоритм Флойда
- 5.6 Поиск с возвратом
- 5.6.1 Введение
- 5.6.2 Переборные алгоритмы
- 5.6.3 Метод ветвей и границ
- 5.6.4 Метод альфа-бета отсечения
- 5.6.5 Локальные и глобальные оптимальные решения
- 5.7 Метод декомпозиции ( «Разделяй и властвуй»)
- 5.7.1 «Ханойские башни»
- 5.8 Жадные алгоритмы и динамическое программирование
- 5.8.1 Задача о выборе заявок
- 5.8.2 Дискретная задача о рюкзаке
- 5.8.3 Непрерывная задача о рюкзаке
- 5.8.4 Числа Фибоначчи
- 5.8.5 Задача триангуляции многоугольника
- 5.8.6 Дп, жадный алгоритм или что-то другое?