Чтение онлайн

на главную

Жанры

Фундаментальные алгоритмы и структуры данных в Delphi

Бакнелл Джулиан М.

Шрифт:

Полный вариант кода класса TtdHashTableLinear можно найти на Web-сайте издательства, в разделе материалов. После выгрузки материалов отыщите среди них файл TDHshLnP.pas.

Другие схемы открытой адресации

Хотя описанный класс хеш-таблиц был разработан для решения основной проблемы, возникающей при использовании схемы с открытой адресацией линейного зондирования (тенденции к кластеризации занятых ячеек), мы кратко рассмотрим несколько других схем с открытой адресацией.

Квадратичное зондирование

Первая из таких схем - квадратичное зондирование (quadratic probing). При использовании этого алгоритма контроль и предотвращение создания кластеров осуществляется путем проверки не следующей по порядку ячейки, а ячеек, которые расположены все дальше от исходной. Если первое зондирование оказывается безрезультатным, мы проверяем следующую ячейку.

В случае неудачи этой попытки мы проверяем ячейку, которая расположена через четыре ячейки. Если и эта попытка неудачна, мы проверяем ячейку, расположенную через девять ячеек - и т.д., причем последующие зондирования выполняются для ячеек, расположенных через 16, 25, 36 и так далее ячеек. Этот алгоритм позволяет предотвратить образование кластеров, которые могут появляться в результате применения линейного зондирования, однако он может приводить и к ряду нежелательных проблем. Во-первых, если для многих ключей хеширование генерирует один и тот же индекс, все их последовательности зондирования должны будут выполняться вдоль одного и того же пути. В результате они образуют кластер, но такой, который кажется распределенным по хеш-таблице. Однако вторая проблема значительно серьезнее: квадратичное зондирование не гарантирует посещение всех ячеек. Максимум в чем можно быть уверенным, если размер таблицы равен простому числу, это в том, что квадратичное зондирование обеспечит посещение, по меньшей мере, половины ячеек хеш-таблицы. Таким образом, образом, можно говорить о выполнении задачи-минимум, но не задачи-максимум.

В этом легко убедиться. Начнем квадратичное зондирование с 0-й ячейки хеш-таблицы, содержащей 11 ячеек, и посмотрим, какие ячейки будут посещены при этом. Последовательность посещений выглядит следующим образом: 0, 1, 5, 3, 8, после чего зондирование снова начинается с ячейки 0. Мы никогда не посещаем ячейки 2, 4, 7, 9. По-моему, одной этой проблемы достаточно, чтобы в любом случае избегать применения квадратичного зондирования, хотя ее можно было бы избегнуть, не позволяя хеш-таблице заполняться более чем на половину.

Псевдослучайное зондирование

Следующая возможность - применение псевдослучайного зондирования (pseudorandom probing). Этот алгоритм требует использования генератора случайных чисел, который можно сбрасывать в определенный момент. Применительно к рассматриваемому алгоритму, из числа рассмотренных в 6 главе генераторов наиболее подошел бы минимальный стандартный генератор случайных чисел, поскольку его состояние однозначно определяется одним характеристическим значением - начальным числом. Алгоритм определяет следующую последовательность действий. Выполните хеширование ключа для получения хеш-значения, но не выполняйте деление по модулю на размер таблицы. Установите начальное значение генератора равным этому хеш-значению. Сгенерируйте первое случайное число с плавающей точкой (в диапазоне от 0 до 1) и умножьте его на размер таблицы для получения целочисленного значения в диапазоне от 0 до размера таблицы минус 1. Эта точка будет точкой первого зондирования. Если ячейка занята, сгенерируйте следующее случайное число, умножьте его на размер таблицы и снова выполните зондирование. Продолжайте выполнять упомянутые действия до тех пор, пока не найдете свободную ячейку. Поскольку при одном и том же заданном начальном значении генератор случайных чисел будет генерировать одни и те же случайные числа в одной и той же последовательности, для одного и того же хеш-значения всегда будет создаваться одна и та же последовательность зондирования.

Все это звучит достаточно обнадеживающе. Ценой ряда сложных и продолжительных вычислений, необходимых для получения случайного числа, этот алгоритм предотвратит образование кластеров, возникающих в результате линейного зондирования. Однако при этом возникает одна небольшая проблема: нет никакой гарантии, что рандомизированная последовательность обеспечит посещение каждой ячейки таблицы.

Нельзя не согласиться, что вероятность постоянного пропуска пустой ячейки достаточно низка, но это возможно, если таблица заполнена в значительной степени. Еще хуже то, что последовательность зондирований может стать очень большой до попадания в пустую ячейку. Следовательно, имеет смысл обеспечить невозможность значительного заполнения таблицы и изменение ее размера, если это происходит. С этого момента можно также продолжить использовать линейное зондирование с применением автоматически расширяемой хеш-таблицы. Это проще и быстрее.

Двойное хеширование

В заключение рассмотрим двойное хеширование (double hashing). На практике эта схема оказывается наиболее удачной из всех альтернативных схем с открытой адресацией. Итак, выполним хеширование ключа элемента в значение индекса. Назовем его h(_1_). Выполним зондирование этой ячейки. Если она занята, выполним хеширование ключа путем применения совершенно иного и независимого алгоритма хеширования для получения

другого значения индекса. Назовем его h(_2_). Выполним зондирование ячейки h(_1_) + h(_2_). Если она занята, выполним зондирование ячейки h(_1_) + 2h(_2_), затем h(_1_) + 3h(_2_) и так далее (понятно, что все вычисления выполняются с делением по модулю на размер таблицы). Обоснование этого алгоритма следующее: если первая функция хеширования для двух ключей генерирует один и тот же индекс, очень маловероятно, что вторая функция хеширования сгенерирует для них то же самое значение. Таким образом, два ключа, которые первоначально хешируются в одну и ту же ячейку, затем не будут соответствовать одной и той же последовательности зондирования. В результате мы можем ликвидировать "неизбежную" кластеризацию, сопряженную с линейным зондированием. Если размер таблицы равен простому числу, последовательность зондирования обеспечит посещение всех ячеек, прежде чем начнется сначала, что позволит избежать проблем, связных с квадратичным и псевдослучайным зондированием. Единственная реальная проблема, возникающая при использовании двойного хеширования, - если не принимать во внимание необходимость вычисления дополнительного хеш-значения - состоит в том, что вторая функция хеширования по понятным причинам никогда не должна возвращать значение, равное 0. На практике эту проблему легко решить, выполняя деление по модулю на размер таблицы минус 1 (в результате мы получим значение в диапазоне от 0 до TableSize-2), а затем добавляя к результату единицу.

Например, при использовании строковых ключей можно было бы вызвать функцию хеширования Вайнбергера TDPJWHash для вычисления основных хеш-значений, а затем вызвать простую функцию хеширования TDSimpleHash для вычисления хеш-значений, которые будут использоваться для пропуска ячеек. Я предлагаю читателям самостоятельно выполнить это простое упражнение по реализации такой хеш-таблицы двойного хеширования.

Разрешение конфликтов посредством связывания

Если мы готовы использовать дополнительные ячейки, кроме тех, которые требуются самой хеш-таблице, можно воспользоваться другой эффективной схемой разрешения конфликтов - схемой с закрытой адресацией. Этот метод называется связыванием (chaining). В его основе лежит очень простой принцип: хеширование ключа элемента для получения значения индекса. Но вместо того, чтобы хранить элемент в ячейке, которая определяется значением индекса, мы сохраняем его в односвязном списке, помещенном в эту ячейку.

Поиск элемента достаточно прост. Мы хешируем ключ с целью получения соответствующего индекса, а затем выполняем поиск требуемого элемента в связном списке, помещенном в этой ячейке.

При выборе места вставки элемента в связный список доступно несколько возможностей. Его можно сохранить в начале связного списка или в конце, или же можно обеспечить, чтобы связные списки были упорядочены, и сохранить элемент в соответствующей позиции сортировки. Все три варианта имеют свои преимущества. Первый вариант означает, что недавно вставленные элементы будут найдены первыми в случае их поиска (имеет место своего рода эффект стека). Следовательно, этот метод наиболее подходит для тех приложений, в которых, скорее всего, поиск новых элементов будет выполняться чаще, нежели поиск старых. Второй вариант означает противоположное: первыми будут найдены "наиболее старые" элементы (имеет место эффект типа очереди). Следовательно, он больше подходит для тех случаев, когда вероятность поиска более старых элементов больше вероятности поиска новых. Третий вариант предназначен для тех случаев, когда не существует предпочтений в отношении поиска более старых или новых элементов, но любой элемент нужно найти максимально быстро. В этом случае для облегчения поиска в связном списке можно прибегнуть к бинарному поиску. В действительности, если верить результатам выполненных мною тестов, третий вариант обеспечивает заметное преимущество только при наличии большого количества элементов в каждом связном списке. На практике лучше ограничить среднюю длину связных списков, при необходимости расширяя хеш-таблицу. Некоторые программисты экспериментировали, применяя деревья бинарного поиска к каждой ячейке (см. главу 8), а не к связным спискам. Однако полученные при этом преимущества оказались не особенно большими.

Первый упомянутый выше вариант вставки элемента в связный список имеет одно замечательное следствие. При успешном поиске элемента его можно переместить в начало связного списка, исходя из предположения, что если мы искали элемент, то, вероятно, довольно скоро будем искать его снова. Таким образом, элементы, поиск которых выполняется наиболее часто, будут перемещаться в верхнюю часть соответствующих связных списков.

Удаление элемента до смешного просто, если сравнить его с бегом по кругу, имевшим место при удалении элемента из хеш-таблицы с линейным зондированием. Достаточно найти элемент в соответствующем связном списке и разорвать связь с ним. Выполнение этих действий для односвязного списка описано в главе 3.

Поделиться:
Популярные книги

Предатель. Цена ошибки

Кучер Ая
Измена
Любовные романы:
современные любовные романы
5.75
рейтинг книги
Предатель. Цена ошибки

Вечный. Книга V

Рокотов Алексей
5. Вечный
Фантастика:
боевая фантастика
попаданцы
рпг
5.00
рейтинг книги
Вечный. Книга V

Мимик нового Мира 13

Северный Лис
12. Мимик!
Фантастика:
боевая фантастика
юмористическая фантастика
рпг
5.00
рейтинг книги
Мимик нового Мира 13

Идеальный мир для Лекаря 15

Сапфир Олег
15. Лекарь
Фантастика:
боевая фантастика
юмористическая фантастика
аниме
5.00
рейтинг книги
Идеальный мир для Лекаря 15

Чужой ребенок

Зайцева Мария
1. Чужие люди
Любовные романы:
современные любовные романы
6.25
рейтинг книги
Чужой ребенок

Новый Рал 3

Северный Лис
3. Рал!
Фантастика:
попаданцы
5.88
рейтинг книги
Новый Рал 3

Кодекс Охотника. Книга XV

Винокуров Юрий
15. Кодекс Охотника
Фантастика:
попаданцы
аниме
5.00
рейтинг книги
Кодекс Охотника. Книга XV

Возвышение Меркурия. Книга 12

Кронос Александр
12. Меркурий
Фантастика:
героическая фантастика
попаданцы
аниме
5.00
рейтинг книги
Возвышение Меркурия. Книга 12

Провинциал. Книга 5

Лопарев Игорь Викторович
5. Провинциал
Фантастика:
космическая фантастика
рпг
аниме
5.00
рейтинг книги
Провинциал. Книга 5

Польская партия

Ланцов Михаил Алексеевич
3. Фрунзе
Фантастика:
попаданцы
альтернативная история
5.25
рейтинг книги
Польская партия

Бальмануг. (Не) Любовница 1

Лашина Полина
3. Мир Десяти
Фантастика:
юмористическое фэнтези
попаданцы
5.00
рейтинг книги
Бальмануг. (Не) Любовница 1

Чиновникъ Особых поручений

Кулаков Алексей Иванович
6. Александр Агренев
Фантастика:
попаданцы
альтернативная история
5.00
рейтинг книги
Чиновникъ Особых поручений

Последний попаданец 2

Зубов Константин
2. Последний попаданец
Фантастика:
юмористическая фантастика
попаданцы
рпг
7.50
рейтинг книги
Последний попаданец 2

Черный Маг Императора 7 (CИ)

Герда Александр
7. Черный маг императора
Фантастика:
фэнтези
попаданцы
5.00
рейтинг книги
Черный Маг Императора 7 (CИ)