Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лк08_Алгоритмы поиска.doc
Скачиваний:
11
Добавлен:
07.07.2019
Размер:
64 Кб
Скачать

4

Алгоритмы поиска

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

Обычно мы считаем, что данные делятся на записи, и каждая запись имеет хотя бы один ключ используемый при поиске.

Целью поиска является нахождение всех записей подходящих к заданному ключу поиска.

Сформулируем точно задачу поиска.

Пусть имеется таблица записей R1, R2,…RN, снабженных соответственно ключами K1, K2,…KN. Требуется найти запись с данным ключом K. У этой задачи может быть два исхода: удачный, когда найдено положение нужного ключа, и неудачный, когда установлено, что искомого ключа в таблице нет.

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

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

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

Еще одра возможная реализация - это предположить, что каждая запись имеет уникальный идентификатор, и что поиск производится по данному идентификатору и ключу.

Возможны также и другие решения данной проблемы.

Линейный (последовательный) поиск

Линейный поиск – самый простой из известных способов.

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

Именно так поступает человек, когда ищет что-то в неупорядоченном множестве. Например, при поиске нужной визитки в некотором неупорядоченном множестве визиток человек просто перебирает все визитки в поисках нужной.

Приведем алгоритм последовательного поиска, знакомый всем программистам.

Пусть множество хранится в первых N элементах массива Х. При этом не важен тип элементов множества, важна лишь возможность проверки эквивалентности элемента множества искомому элементу Key.

Функция Locate возвращает номер искомого ключа или N+1, если ключ не найден.

Function Locate( x:vector; k: <тип ключа>): integer;

var i:integer;

begin

i:=1;

while (i<=N) and (k<>x[i]) do inc(i);

Locate:=i

end;

Анализ работы этого алгоритма не представляет труда.

Время работы зависит от количества сравнений ключей С и параметра успеха S =1 при удаче и 0 при неудаче. Последовательный поиск, реализованный на массиве, производит N сравнений для неуспешного поиска, и в среднем N/2 сравнений для успешного. Это означает сложность алгоритма поиска T(n). Для неуспешного поиска это свойство выводится прямо из программного кода: каждая запись должна быть проверена, чтобы установить факт того, что данный ключ не найден.

Для успешного поиска, если мы предположим, что любая из записей имеет равную вероятность быть искомой, то среднее количество сравнений будет:

(1+2+...+N)/N = (N+1)/2 @ N/2

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

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

Type

PListItem = ^TListItem;

TListItem = record

Item: TItem;

Next: PListItem;

end;

Быстрый последовательный поиск

Этот метод поиска является небольшим усовершенствованием предыдущего с помощью граничного элемента.

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

Здесь использован принцип: если во внутреннем цикле программы проверяются два или более условия, нужно постараться оставить только одно сравнение.

В цикле while производится два сравнения: (i<=n) и (A[i]<>Key). Избавимся от одного из них (от первого), положив A[n+1] := Key. Другими словами, достаточно просто дописать новый элемент в «конец» множества.

Тогда функция поиска будет выглядеть так:

Function Locate( x:vector; k: integer): integer;

var i:integer;

begin

i:=1; x[n+1]:=K;

while K<>x[i] do inc(i);

Locate:=i

end;

При поиске по большим таблицам скорость улучшенного алгоритма увеличивается на 30% по сравнению с первым вариантом.Надо сказать, что хотя такой фрагмент кода будет работать быстрее, но его теоретическая сложность остается такой же – T(n).