Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
румбешт без юрца.docx
Скачиваний:
4
Добавлен:
25.09.2019
Размер:
724.17 Кб
Скачать

17.2 Нисходящее пошаговое проектирование

Составление алгоритмов сложных задач требует специального подхода . Этот подход известен в литературе как структурный. Основополагающим принципом структурного подхода к программированию является нисходящее пошаговое проектирование . этот принцип предполагает разбиение задачи на отдельные этапы (подзадачи) , причем каждая такая подзадача в дальнейшем рассматривается как отдельная независимая задача. Каждая такая подзадача может быть в свою очередь разбита на отдельные этапы (блоки) и ее схема может иметь различную степень детализации. Напомним ,что составление алгоритма задачи является промежуточным шагом процесса ее решения. Алгоритм в дальнейшем должен будет переведен на какой либо из языков программирования. Каждому блоку алгоритма будет соответствовать более или менее сложный фрагмент программы. Будем называть блок алгоритма элементарным, если его можно изобразить одним оператором языка программирования, а схему содержащую только элементарные блоки подробной. Соответственно схему алгоритма содержащую хотя бы один неэлементарный блок будем считать. Укрупненная схема алгоритма определяет основные этапы решения задачи и порядок их выполнения. Общая схема алгоритма будет содержать подробные схемы алгоритма всех своих этапов.

2.2 Структурное программирование

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

От ЮРЦА

шиш

ОТ МАКСА

27 Улучшение сортировки обменом. Во-первых, рассмотрим ситуацию, когда на каком-либо из проходов не произошло ни одного обмена. Что это значит? Это значит, что все пары расположены в правильном порядке, так что массив уже отсортирован. И продолжать процесс не имеет смысла (особенно, если массив был отсортирован с самого начала). Итак, первое улучшение алгоритма заключается в запоминании, производился ли на данном проходе какой-либо обмен. Если нет – алгоритм заканчивает работу.Процесс улучшения можно продолжить, если запоминать не только сам факт обмена, но и индекс последнего обмена k. Действительно: все пары соседних элементов с индексами, меньшими k, уже расположены в нужном порядке. Дальнейшие проходы можно заканчивать на индексе k, вместо того чтобы двигаться до установленной заранее верхней границы i. На этом основано второе улучшение алгоритма (см. рис. 4.11). В нем введена логическая переменная flag – признак наличия хотя бы одного обмена в проходе.

Рис. 4.11. Блок-схема алгоритма улучшенной сортировки обменом

Качественно другое улучшение алгоритма сортировки обменом можно получить из следующего наблюдения. Хотя легкий пузырек снизу поднимется наверх за один проход, тяжелые пузырьки опускаются с минимальной скоростью: один шаг за итерацию. Так что массив 2 3 4 5 6 1 будет отсортирован за 1 проход, а сортировка последовательности 6 1 2 3 4 5 потребует 5 проходов.

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

Рис. 4.12. Блок схема алгоритма шейкер-сортировки

Насколько описанные изменения повлияли на эффективность метода? В худшем случае эти виды улучшенной сортировки не отличаются от исходного алгоритма. Среднее количество сравнений, хоть и уменьшилось, но остается O(n2), в то время как число обменов не поменялось вообще никак. В лучшем случае, когда массив уже упорядочен, потребуется всего один проход и n-1 сравнение, что составляет O(n). Перестановки в этом случае не выполняются. Дополнительная память, очевидно, не требуется. Поведение усовершенствованных алгоритмов довольно естественное, почти отсортированный массив будет отсортирован намного быстрее случайного. Сортировка пузырьком и первые ее улучшения устойчивы, однако шейкер-сортировка утрачивает это качество.

Рис. 4.16. Бок-схема процедуры разделения массива

Очевидно, что временная сложность каждого разделение имеет порядок O(n). Количество шагов деления (глубина рекурсии) составляет приблизительно log2 n, если массив делится на более-менее равные части. Таким образом, общая временная сложность алгоритма быстрой сортировки составляет O(n log n), что и имеет место на практике.

Однако, возможен случай таких входных данных, на которых алгоритм будет иметь временную сложность порядка O(n2). Такое происходит, если каждый раз в качестве опорного элемента выбирается максимум или минимум входной последовательности. Например, такая ситуация имеет место, если массив уже упорядочен, или упорядочен в обратной последовательности, а в качестве опорного элемента выбирается первый или последний элемент последовательности. Поэтому рекомендуется выбирать опорный элемент из середины последовательности или вообще – случайным образом. Если опорный элемент выбирается случайно, вероятность выбора минимального (максимального) элемента этого равна 1/n. И эта вероятность должна реализовываться на каждом шаге... Вообще говоря, малореальная ситуация.

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

Сортировка использует дополнительную память, так как приблизительная глубина рекурсии составляет log2 n, а данные о рекурсивных подвызовах каждый раз добавляются в стек.

28

улучшением метода сортировки обменом является так называемая "быстрая сортировка", предложенная Ч. Хоаром. Метод основан на подходе "разделяй и властвуй". Общая схема такова:

  1. из массива выбирается некоторый опорный элемент pivot = a[i];

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

  3. теперь массив состоит из трех подмножеств, как показано на рис. 4.13, причем левое подмножество содержит элементы меньшие, либо равные элементам правого;

Рис. 4.13. Результат разделения массива

  1. для левого и правого подмассивов: если в подмассиве не менее двух элементов, рекурсивно запускаем для него ту же процедуру сортировки.

В итоге получится полностью отсортированная последовательность.

На рис. 4.14 представлена блок-схема алгоритма быстрой сортировки. В отличие от ранее рассмотренных алгоритмов сортировки его параметрами являются сортируемая последовательность {a[i]} и индексы ее первого и последнего элемента – lb (lower bound) и ub (upper bound) соответственно.

Рис. 4.14. Блок-схема алгоритма быстрой сортировки

Рассмотрим алгоритм разделения массива подробнее. Его входными параметрами являются разделяемый массив {a[i]} и индексы ее первого (lb) и последнего элемента (ub).

  1. Выберем опорный элемент pivot, например, из середины разделяемой части: pivot=a[lb+(ub-lb)/2].

  2. Исключим элемент pivot из разделяемой последовательности, вставив вместо него a[lb]. Введем два указателя: i и j. В начале алгоритма они содержат, соответственно, индекс элемента, следующего за левым крайним, и индекс правого крайнего элемента последовательности.

  3. Будем двигать указатель i с шагом в 1 по направлению к концу массива, пока i j и не будет найден элемент a[i] pivot. Затем аналогичным образом начнем двигать указатель j от конца массива к началу, пока i j и не будет найден a[j] pivot.

  4. Далее, если i j, меняем a[i] и a[j] местами и продолжаем двигать i, j по тем же правилам.

  5. Повторяем шаги 3 – 4, пока i j.

  6. По завершению разделения указатель j содержит индекс последнего элемента части последовательности, содержащей элементы не превосходящие pivot. Переместим a[j] на первое место (a[lb]=a[j]). На место элемента a[j] вставим pivot (a[j]=pivot).

После завершения шага 6 исходная последовательность окажется разделенной на три части, как показано на рис. 4.13. Элементы, не превосходящие pivot, имеют индексы из диапазона lb…j – 1, элемент pivot имеет индекс p = j, а элементы не меньшие pivot имеют индексы j+1…ub.

Рассмотрим работу процедуры для массива a[0]...a[6] и опорного элемента pivot = a[3]. Рис. 4.15 иллюстрирует разделение для этого примера.

Рис. 4.15. Пример разделения массива

На шаге 6 массив разделен на три части: все элементы левой меньше либо равны pivot, все элементы правой – больше, либо равны pivot, средняя часть содержит только pivot. Разделение завершено.

На рис. 4.16 приведена блок-схема алгоритма разделения массива.

Рис. 4.16. Бок-схема процедуры разделения массива

Очевидно, что временная сложность каждого разделение имеет порядок O(n). Количество шагов деления (глубина рекурсии) составляет приблизительно log2 n, если массив делится на более-менее равные части. Таким образом, общая временная сложность алгоритма быстрой сортировки составляет O(n log n), что и имеет место на практике.

Однако, возможен случай таких входных данных, на которых алгоритм будет иметь временную сложность порядка O(n2). Такое происходит, если каждый раз в качестве опорного элемента выбирается максимум или минимум входной последовательности. Например, такая ситуация имеет место, если массив уже упорядочен, или упорядочен в обратной последовательности, а в качестве опорного элемента выбирается первый или последний элемент последовательности. Поэтому рекомендуется выбирать опорный элемент из середины последовательности или вообще – случайным образом. Если опорный элемент выбирается случайно, вероятность выбора минимального (максимального) элемента этого равна 1/n. И эта вероятность должна реализовываться на каждом шаге... Вообще говоря, малореальная ситуация.

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

Сортировка использует дополнительную память, так как приблизительная глубина рекурсии составляет log2 n, а данные о рекурсивных подвызовах каждый раз добавляются в стек.

29

Дальнейшим развитием метода сортировки с включениями является сортировка методом Шелла, называемая по-другому сортировкой включениями с уменьшающимся расстоянием. Не будем описывать алгоритм в общем виде, а ограничимся случаем, когда число элементов в сортируемом массиве является степенью числа 2. Для массива с 2n элементами алгоритм работает следующим образом. На первой фазе производится сортировка включением всех пар элементов массива, расстояние между которыми есть 2(n-1). На второй фазе производится сортировка включением элементов полученного массива, расстояние между которыми есть 2(n-2). И так далее, пока не дойдем до фазы с расстоянием между элементами, равным единице, и не выполним завершающую сортировку с включениями.

Рассмотрим пример сортировки массива a[0].. a[15] методом Шелла (см. рис. 4.20).

Рис. 4.20. Пример сортировки методом Шелла

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

Единственной характеристикой сортировки Шелла является приращение – расстояние между сортируемыми элементами, в зависимости от прохода. В конце приращение всегда равно единице – метод завершается обычной сортировкой вставками, но именно последовательность приращений определяет рост эффективности. Использованный в примере набор расстояний 8, 4, 2, 1 – неплохой выбор, особенно, когда количество элементов – степень двойки. В общем случае алгоритм Шелла естественно переформулируется для заданной последовательности из t расстояний между элементами h1, h2, ..., ht, для которых выполняются условия ht = 1 и hk-1 > hk. Кнут предложил выбирать шаги из ряда обратного следующему: 1, 4, 13, 40, 121,…, а вычислять их по следующей формуле: ; , где k=1…t-1; ; – наибольшее целое, не превосходящее log3 n; n – размерность исходного массива. На рис. 4.21 приведена блок-схема алгоритма сортировки Шелла, с выбором шагов, предложенных Кнутом.

Рис. 4.21. Блок-схема алгоритма сортировки Шелла

Кнут показал, что при последовательности расстояний между элементами …, 121, 40, 13, 4, 1 количество операций сравнения при выполнении алгоритма Шелла даже в наихудшем случае имеет порядок O(n1.5). Для случайной последовательности при n < 60000 временная сложность алгоритма имеет порядок O(n1.25).

Сортировка Шелла естественна, но в отличие от сортировок вставками неустойчива.

30

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

Пирамида (Heap) – это бинарное дерево высоты k, удовлетворяющая следующим требованиям:

  1. узлами дерева являются элементы массива;

  2. дерево является сбалансированным, т.е. все листья имеют глубину k или k – 1, при этом уровень k – 1 полностью заполнен, а уровень k заполнен слева направо;

  3. выполняется "свойство пирамиды": каждый элемент меньше, либо равен родителю, а корнем дерева является максимальный элемент массива.

На рис. 4.22 приведен пример пирамиды, имеющей глубину 4.

Рис. 4.22. Пример пирамиды

Для представления пирамиды не требуется создавать специальную структуру данных – ее можно хранить прямо в сортируемом массиве. Соответствие между геометрической структурой пирамиды как дерева и массивом устанавливается по следующей схеме: в a[0] хранится корень дерева, левый и правый сыновья элемента a[i] хранятся в a[2i+1] и a[2i+2] соответственно.

Для массива, хранящего в себе пирамиду, выполняется следующее характеристическое свойство: a[i] a[2i+1] и a[i] a[2i+2].

Запишем в виде массива пирамиду, изображенную на рис. 4.22. Слева - направо, сверху - вниз: 94, 67, 18, 44, 55, 12, 06, 42. На рисунке место элемента пирамиды в массиве обозначено цифрой вверху и справа от него.

Восстановить пирамиду из массива как геометрический объект легко – достаточно вспомнить схему хранения и нарисовать, начиная от корня.

Пирамидальная сортировка включает две фазы: приведение исходного массива к виду пирамиды (построение пирамиды) и собственно сортировка. Рассмотрим эти фазы подробнее.

Фаза построения пирамиды. Начинать построение пирамиды можно с a[l]...a[n – 1], где l = n div 2. Эта часть массива удовлетворяет свойству пирамиды, так как не существует индексов i и j, таких, что i = 2l+1 и j = 2l+2, просто потому, что такие i и j находятся за границей массива.

Следует заметить, что неправильно говорить о том, что a[l] ... a[n – 1] является пирамидой как самостоятельный массив. Это, вообще говоря, не верно: его элементы могут быть любыми. Свойство пирамиды сохраняется лишь в рамках исходного, основного массива a[0] ... a[n – 1].

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

Чтобы при добавлении элемента сохранялась пирамидальность, будем использовать следующую процедуру расширения пирамиды a[k+1]...a[n – 1] влево на новый элемент a[k]:

  1. смотрим на потомков a[k] слева и справа (если он существует) – в массиве это a[2k+1] и a[2k+2] и выбираем наибольшего из них.

  2. если этот элемент больше a[k] – меняем его с a[k] местами и идем к шагу 1, имея в виду новое положение a[k] в массиве. Иначе конец процедуры.

Таким образом, новый элемент a[k] как бы "просеивается" сквозь пирамиду.

На рис. 4.23 а) приведена блок-схема процедуры просеивания элемента сквозь пирамиду (downHeap). Входными параметрами этой процедуры являются: {a[i]} – преобразуемый массив, k – индекс просеиваемого элемента, ub – индекс последнего элемента массива.

Применяя процедуру просеивания к элементам a[l-1], a[l-2],…, a[0] получим пирамиду. На рис. 4.23 б) приведена блок-схема алгоритма построения пирамиды (MakeHeap).

а) б)

Рис. 4.23. Блок-схемы процедур приведения массива к пирамидальному виду: а) процедура "просеивания" элемента сквозь пирамиду б) алгоритм построения пирамиды

Рассмотрим пример построения пирамиды из исходного массива: 44, 55, 12, 42, 94, 18, 6, 67. В геометрической интерпретации элементы a[n div 2] ... a[n-1] (94, 18, 6, 67) является листьями в бинарном дереве. Один за другим остальные элементы продвигаются на свои места, и так, пока не будет построена вся пирамида. На рис. 4.24 изображен процесс построения пирамиды. Неготовая часть пирамиды (начало массива) окрашена в белый цвет, удовлетворяющий свойству пирамиды конец массива – в темный.

Рис. 4.24. Иллюстрация построения пирамиды

Фаза сортировки. Как видно из свойств пирамиды, в корне всегда находится максимальный элемент. Отсюда вытекает алгоритм сортировки:

  1. берем верхний элемент пирамиды a[0]...a[n-1] (первый в массиве) и меняем с последним местами. Теперь "забываем" об этом элементе и далее рассматриваем массив a[0]...a[n – 2]. Для превращения его в пирамиду достаточно просеять лишь новый первый элемент;

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

На рис. 4.25 приведен пример иллюстрирующий этот алгоритм.

Рис. 4.25. Иллюстрация алгоритма пирамидальной сортировки

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

На рис. 4.26 приведена блок-схема алгоритма пирамидальной сортировки (HeapSort).

Каково быстродействие получившегося алгоритма? Учитывая, что высота пирамиды не более log2 n, процедура downHeap требует времени исполнения порядка O(log n). Построение пирамиды занимает порядка O(n log n) операций, причем более точная оценка дает даже O(n) за счет того, что реальное время выполнения downHeap зависит от высоты уже созданной части пирамиды.

Вторая фаза занимает O(n log n) времени: O(n) раз берется максимум и происходит просеивание бывшего последнего элемента. Плюсом является стабильность метода: среднее число пересылок (n log n)/2, и отклонения от этого значения сравнительно малы.

Рис. 4.26. Блок-схема алгоритма пирамидальной сортировки

Пирамидальная сортировка не использует дополнительной памяти. Метод не является устойчивым: по ходу работы массив так "перетряхивается", что исходный порядок элементов может измениться случайным образом. Поведение неестественно: частичная упорядоченность массива никак не учитывается.

31

Поиск необходимой информации в массиве данных одна из фундаментальных задач теоретического программирования. Приведем ее постановку.

Задача поиска элемента в массиве. Пусть есть массив a0, a1... an-1, состоящий из n элементов. Требуется установить, имеется ли в массиве элемент x, и, если имеется, то каков его индекс.

Как и в задаче сортировки, возможна ситуация, когда элементы состоят из нескольких полей, например так, как показано на рис. 4.1. Таким образом, при обсуждении алгоритмов поиска мы предполагаем, что информация содержится в записях, составляющих некоторый список, который представляет сбой массив данных в программе. Номера, записей в массиве идут от 0 до n – 1, где nполное число записей. В общем случае, поиск может осуществляться не во всем массиве, а только в некоторой непрерывной его части, задаваемой нижнем и верхним индексами – lb (lower bound) и ub (upper bound) соответственно.

Из всех полей элемента массива нас будут интересовать значения лишь одного – ключевого поля. Обычно поиск производится не просто для проверки того, что нужный элемент в массиве имеется, но и для того, чтобы получить данные, относящиеся к этому значению ключа. Например, ключевое значение может быть номером сотрудника или порядковым номером, или любым другим уникальным идентификатором. После того, как нужный ключ найден, программа может, скажем, частично изменить связанные с ним данные или просто вывести всю запись. Во всяком случае, перед алгоритмом поиска стоит важная задача определения местонахождения ключа. Поэтому алгоритмы поиска возвращают индекс записи, содержащей нужный ключ. Если ключевое значение не найдено, то алгоритм поиска обычно возвращает значение индекса, превышающее верхнюю границу массива. Для наших целей мы будем предполагать, что элементы массива имеют номера от 0 до n – 1. Это позволит нам возвращать –1 в случае, если целевой элемент отсутствует в массиве.

Массив может быть не отсортированным или отсортированным по значению ключевого поля. В не отсортированном массиве порядок элементов случаен, а в отсортированном они идут в порядке возрастания ключа.

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

32

Линейный поиск. Работа алгоритма заключается в том, что элементы массива, начиная с первого a[lb], последовательно сравнивается с искомым элементом. Сравнение элементов продолжается до тех пор, пока не будут просмотрены все элементы a[lb]a[ub], или очередной элемент массива не равен искомому. По завершении цикла поиска анализируется ситуация, в которой он был завершен. Если был найден целевой элемент, то решением является его индекс, а, если был достигнут конец последовательности, то решением является –1.

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

Проверим последний элемент последовательности. Если он совпадает с искомым, то решением будет индекс ub. Иначе, заменим элемент a[ub] на искомый. Тогда проверка на окончание последовательности становится не нужной. Гарантированно целевой элемент будет найден. Если этот элемент находится внутри последовательности, то результатом будет его индекс. Если же этим элементом оказался a[ub], то искомого элемента в массиве нет и решение есть –1.

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

Рис. 4.27. Блок-схема алгоритма линейного поиска

33

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

При реализации алгоритма блочного поиска ставится вопрос о количестве и размере выделяемых блоков. Желательно, чтобы все блоки имели одинаковый размер. Пусть последовательность состоит из n элементов и при поиске разбивается на m равных по размеру блоков (может быть за исключением последнего). Тогда в каждом блоке будет не более n/m элементов. В худшем случае, если искомый элемент окажется в последнем блоке, то для поиска блока потребуется просмотреть m элементов массива. Выполнить линейный поиск в блоке можно, просмотрев не более чем n/m элементов. Следовательно, общее число обрабатываемых элементов не превышает m + n/m. Эта величина зависит от числа блоков и будет минимальна, когда ее производная равна нулю, т.е. при . Учитывая, что количество боков должно быть целым числом, в качестве значения m следует выбрать – наибольшее целое, не превышающее . Это значение представляет собой размер блока. Когда количество блоков в точности совпадает с m. В случае когда , количество боков больше m, но не больше чем на два.

На рис. 4.28 приведена бок-схема алгоритма блочного поиска. В этом алгоритме рассчитывается размер блока . И далее в цикле, пока не будет найден блок, потенциально содержащий целевой элемент, или индекс последнего элемента текущего блока не выйдет за границы последовательности, просматривается последний элемент очередного блока. Если этот элемент больше или равен целевому, то к текущему блоку применяется алгоритм линейного поиска. Если среди рассмотренных блоков не нашлось блока, потенциально содержащего целевой элемент и имеется еще не просмотренный последний блок последовательности, то он анализируется аналогичным образом. В случае, когда целевой элемент оказывается больше a[ub] решение есть –1.

Рис. 4.28. Блок-схема алгоритма блочного поиска

34

Бинарный поиск подобно блочному поиску может использоваться только для упорядоченного массива. Идея этого алгоритма заключается в следующем.Имеем массив {a[i]} из n элементов. Пусть индексы в концах анализируемой части массива: lb=0, ub=n-1. Вычислим индекс серединного элемента анализируемой части массива m=lb+(ub-lb) div 2. Сравним значение в серединном элементе с искомым значением x. Если совпадение найдено, решением является m. Если a[m] < x, то следует проводить повторный поиск в правой половине рассматриваемого массива. Если a[m]>x, то проводить повторный поиск в левой половине рассматриваемого массива. Если искомый элемент не находится в массиве, то возвратить индикатор сбоя.Рассмотрим пример бинарного поиска. Дан массив целых чисел А. Найти элемент с заданным значением 33. На рис. 4.29 представлены этапы бинарного поиска этого элемента

Рис. 4.29. Пример бинарного поиска элемента в массиве

При последовательном поиске в этом случае требуется 8 сравнений, а при бинарном поиске – только три.

На рис. 4.30 приведена блок-схема алгоритма бинарного поиска.

Рис. 4.30. Блок-схема алгоритма бинарного поиска

35

Списком называется упорядоченное множество, состоящее из переменного числа элементов, к которым применимы операции включения, исключения. Список, отражающий отношения соседства между элементами, называется линейным. Длина списка равна числу элементов, содержащихся в списке, список нулевой длины называется пустым списком. Линейные связные списки являются простейшими динамическими структурами данных.

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

Списки бывают трех видов:

1) Односвязные;

2) Циклические;

3) Двусвязные.

36