Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ЛЕКЦИИ САОД.docx
Скачиваний:
3
Добавлен:
01.03.2025
Размер:
406 Кб
Скачать
☆

3.7. Операции логического уровня над статическими структурами. Поиск

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

Объективным критерием, позволяющим оценить эффективность того или иного алгоритма, является, так называемый, порядок алгоритма. Порядком алгоритма называется функция O(N), позволяющая оценить зависимость времени выполнения алгоритма от объема перерабатываемых данных (N - количество элементов в массиве или таблице). Эффективность алгоритма тем выше, чем меньше время его выполнения зависит от объема данных. Большинство алгоритмов с точки зрения порядка сводятся к трем основным типам:

  • - степенные - O(N^a);

  • - линейные - O(N);

  • - логарифмические - O(logA(N)). (Здесь и далее запись вида "logА" обозначает "логарифм по основанию А").

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

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

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

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

Оформим описанный алгоритм в виде кусочка программы на Паскале. Пусть множество хранится в первых n элементах массива A. При этом не важен тип элементов множества, важна лишь возможность проверки эквивалентности элемента множества искомому элементу Key.

i := 1;

while (i<=n)and(A[i]<>Key) do Inc(i);

if A[i]=Key then <элемент найден> else <элемент не найден>;

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

Type

PListItem = ^TListItem;

TListItem = record

Item: TItem;

Next: PListItem;

end;

Рассмотрим добавление элемента Key во множество, хранимое в виде массива. Здесь достаточно просто дописать новый элемент в «конец» множества:

Inc(n); A[n] := Key;

Удалению элемента всегда предшествует успешный поиск. Это значит, что сложность операции удаления не может быть меньше сложности операции поиска. В данном случае при удалении нам придется сначала найти положение удаляемого элемента, затем все элементы, следующие за ним, сдвинуть на одну позицию к началу массива и уменьшить n. То есть в любом случае надо пробежать весь массив от начала до конца (это означает сложность T(n)):

i := 1;

while (i<=n)and(A[i]<>Key) do Inc(i); {поиск}

if A[i]=Key then

begin

while i<n do

begin

A[i] := A[i+1]; {сдвигаем}

Inc(i);

end;

Dec(n);

end;

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