Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Комплект Информатика / Лабораторный практикум.doc
Скачиваний:
178
Добавлен:
22.05.2015
Размер:
6.45 Mб
Скачать

Порядок выполнения работы

Пример №1.

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

Решение.Обозначим пары через Аа, Бб, Вв (маленькими буквами обозначим женщин). Вот схема перевозок, реализующая нужную переправу за 11 рейсов: (стрелки указывают направление движения лодки).

Рейс

Левый берег

В лодке

Правый берег

 1

 БбВв

 Аа=>

 Аа

 2

 А БбВв

 <=А

 а

 3

 А Б В

 б в=>

 а б в

 4

 Аа Б В

 <=а

 б в

 5

 Аа

 Б В=>

 БбВв

 6

 АаБб

 <=Бб

 Вв

 7

 а б

 А Б=>

 А Б Вв

 8

 а б в

 <=в

 А Б В

 9

 а

 б в=>

 А БбВв

 10

 а б

 <=б

 А Б Вв

 11

 

 а б=>

 АаБбВв

Пример №2.

Задача. Имеются два кувшина емкостью 3 и 8 л. Необходимо составить алгоритм, с помощью которого, пользуясь только этими двумя кувшинами, можно набрать 7 л воды.

Можно предположить, что кувшин емкостью 3 л необходимо ис­пользовать для того, чтобы отлить в него 1 л из полного кувшина емкостью 8 л. Таким образом, решение задачи сводится к поиску возможности поместить, например, 2 л воды в трехлитровый кувшин, затем наполнить восьмилитровый и перелить из него воду в трехлитровый кувшин, в котором до полного заполнения не хватает ровно I л. Задача реализуется следующим линейным алгоритмом (А — количество воды в трехлитровом кувшине, В — количество воды в восьмилитровом кувшине):

Требования к отчету

Отчет должен содержать:

  1. Тему и цель работы.

  2. Исходные данные по заданному варианту лабораторной работы.

  3. Описание хода выполнения работы.

  4. Результат выполнения работы.

  5. Ответы на контрольные вопросы.

  6. Выводы о проделанной работе.

Контрольные вопросы

  1. Назовите основные фазы, которые должны быть выполнены в процессе решения задачи.

  2. Являются ли эти фазы этапами, которым нужно непременно следовать при решении задачи?

  3. Что понимали под понятием алгоритм и какова важная особенность алгоритмов?

  4. С помощью чего реализуется цикл со счетчиком?

  5. В чем состоит различие между численными алгоритмами и логическими алгоритмами?

  6. Перечислите свойства алгоритмов.

  7. На какие этапы может разбиваться процесс разработки сложного алгоритма и как они называются?

  8. Существует ли единое решение алгоритмов?

Лабораторная работа № 16

Итерационные структуры в алгоритмах

Цель работы

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

Задание для самостоятельной подготовки

1.Изучить особенности построения итеративных структур алгоритмов [1], стр. 205-213.

2.Изучить содержание и порядок выполнения лабораторной работы.

Основы теории

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

Итерация — это организация обработки данных, при которой действия повторяются многократно, не приводя при этом к вызовам самих себя (не путать с рекурсией).

Когда какое-то действие необходимо повторить большое количество раз, в программировании используются циклы. Например, нужно вывести 200 раз на экран текст «Hello, World!». Вместо 200-кратного повторения одной и той же команды вывода текста часто создается цикл, который прокручивается 200 раз, и 200 раз выполняет то, что написано в теле цикла. Один шаг цикла и называется итерацией.

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

Давайте попробуем представить, как бы мы действовали при поиске определенного имени в списке гостей, содержащем около 20 фамилий. В данном случае можно было бы просмотреть весь список от начала и до конца, сравнивая каждый его элемент с искомым именем. Если требуемое имя будет найдено, поиск завершится успешно. Однако, если будет достигнут конец списка или обнаружено имя, расположенное по алфавиту после искомого, поиск завершится неудачей. (Не забывайте, что список упорядочен в алфавитном порядке, поэтому если будет найдено имя, расположенное по алфавиту после требуемого, то это означает, что нужного нам имени в списке нет.) Таким образом, наша задача — выполнять последовательный просмотр элементов списка в направлении сверху вниз до тех пор, пока не будут проверены все имена или нужное нам имя не будет найдено.

С помощью нашего псевдокода этот процесс можно представить следующим образом:

Выбрать в качестве Проверяемое Значение первый элемент Списка;

While(Искомое Значение > Проверяемое Значение и есть непроверенные элементы Списка)

do (выбрать в качестве Проверяемое Значение следующий элемент Списка)

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

if (Искомое Значение = Проверяемое Значение)

then{сообщить об успехе}

else{сообщить о неудаче}

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

if (Список пуст)

then (сообщить о неудаче)

else (...)

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

Применить процедуру Поиск к списку пассажиров, чтобы найти имя "Даррел Бейкер".

Эта инструкция позволяет установить, является ли Даррел Бейкер пассажиром некоторого рейса. Вот еще один пример:

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

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

Рисунок 1 - Алгоритм последовательного поиска, записанный с помощью псевдокода

Итак, можно сказать, что представленный на рисунке 1 алгоритм последовательно рассматривает все элементы списка. По этой причине данный алгоритм называется алгоритмом последовательного поиска (sequentialsearch). Всилу своей простоты он часто применяется ккоротким спискам либо когда это необходимо по каким-то иным соображениям. Однако в случае длинных списков этот метод оказывается менее эффективным, чем другие (в чем мы скоро убедимся).

Неоднократное использование инструкции или последовательности инструкций представляет собой важную алгоритмическую концепцию. Одним из методов организации такого повторения является итеративная структура, известная как цикл (loop); здесь последовательность инструкций, называемая телом цикла, многократно выполняется под контролем некоторого управляющего процесса. Типичный пример цикла можно найти в алгоритме последовательного поиска. Здесь инструкция whileиспользуется в целях управления повторным выполнением единственной инструкции "выбрать следующий элемент списка как Проверяемое значение". Общий синтаксис инструкции while имеет такой вид:

While (условие) do {тело цикла}.

Эта инструкция представляет собой типичный образец циклической структуры, т.е. при ее выполнении циклически совершаются следующие действия:

проверить условие

выполнить тело цикла

проверить условие

выполнить тело цикл

. .

проверить условие

Эти действия будут продолжаться до тех пор, пока заданное условие будет выполняться.

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

Выполнить инструкцию "Добавить каплю серной кислоты" три раза.

Эта циклическая структура эквивалентна такой последовательности инструкций:

Добавить каплю серной кислоты.

Добавить каплю серной кислоты.

Добавить каплю серной кислоты.

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

while (уровень рН больше, чем 4) do{добавить каплю серной кислоты}

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

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

Управление циклом состоит из трех операций: инициализации, проверки и модификации (рисунок 2), причем все они обязательны для успешного управления циклом. Назначение операции проверки состоит в обеспечении своевременного окончания циклического процесса за счет отслеживания возникновения условия, указывающего, что цикл пора заканчивать. Это условие называют условием завершения цикла. Именно для выполнения операции проверки некоторое условие обязательно указывается при записи каждой инструкции while нашего псевдокода. Однако условие, задаваемое в инструкции while, — это условие продолжения цикла, а условие завершения — это отрицание условия, заданного в инструкции while. Поэтому в инструкции while показанной на рисунке 2, условие завершения выглядит следующим образом:

(Искомое Значение ≤ Проверяемое Значение) или (больше нет не рассмотренных элементов)

Рисунок 2 - Операции управления циклом

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

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

Число 1while(Число ≠ 6) do{Число Число + 2}

В данном случае условием завершения является выражение Число ≠ 6. Однако переменная Число инициализируется значением 1, а затем увеличивается на 2 на каждом этапе модификации. Таким образом, при выполнении цикла переменной число будут присваиваться значения 1, 3, 5, 7, 9,.... и ее значение никогда не будет равно 6. В результате выполнение данного цикла никогда не закончится.

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

while (условие) do {действие}

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

Рисунок 3 - Структура цикла while-do

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

Рисунок 4 - Структура repeat-until

В нашем псевдокоде общий синтаксис цикла этого типа имеет следующий вид:

repeat {действие} until (условие)

Рассмотрим конкретный пример:

repeat {взять монету из кармана} until (в кармане нет монет)

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

while (в кармане есть монета) do{взять монету из кармана}

Придерживаясь терминологии нашего псевдокода, будем называть эти структуры оператором цикла с условием продолжения и оператором цикла с условием завершения. Иногда оператор цикла while называют априорным циклом (pretestloop), или циклом с предусловием, поскольку условие завершения проверяется до выполнения тела цикла, а оператор цикла repeat называется апостериорным циклом (posttestloop), или циклом с постусловием, поскольку условие завершения проверяется после выполнения цикла.

Цикл с параметром (for). Оператор цикла с параметром for имеет следующий формат:

for( выражение_1 ; выражение_2 ; выражение_3 ) тело

Выражение_1 обычно используется для установления начального значения переменных, управляющих циклом. Выражение_2 – это выражение, определяющее условие, при котором тело цикла будет выполняться. Выражение_3 определяет изменение переменных, управляющих циклом после каждого выполнения тела цикла.

Схема выполнения оператора for:

1. Вычисляется выражение_1.

2. Вычисляется выражение_2.

3. Если значения выражения_2 отлично от нуля (истина), выполняется тело цикла, вычисляется выражение_3 и осуществляется переход к пункту 2, если выражение_2 равно нулю (ложь), то управление передается на оператор, следующий за оператором for.

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

В этом примере вычисляются квадраты чисел от 1 до 9

for (i=1; i<10; i++) b=i*i;

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

for (;;)

{ ...

... break;

...

}

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

Проиллюстрируем особенности трех типов цикла на примере вычисления приближенного значения

(1)

для заданного значения х. Вычисления будем продолжать до тех пор, пока очередной член ряда остается больше заданной точно­сти. Обозначим точность через eps, результат - b, очередной член ряда - r, номер члена ряда - i. Для получения i-ro члена ряда нужно (i-l)-й член умножить на х и разделить на i, что по­зволяет исключить операцию возведения в степень и явное вы­числение факториала. Опустив определения переменных, опера­торы ввода и проверки исходных данных, а также вывода ре­зультатов, запишем три фрагмента программ.

/* Цикл а предусловием */

i = 2;

b = 1.0;

r = х;

while(r >eps || r < -eps)

{

b=b+r;

r=r*x/i;

i++ ;

}

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

/* Цикл с постусловием */

i=l ;

b=0.0;

r=l.0;

do {

b=b+r;

r=r*x/i;

i++ ;

}

while( r >= eps || r <= -eps );

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

/* Параметрический цикл */

i=2;

b=1.0;

r=х;

for( ; r >eps || г< -eps ; )

{

b=b+r;

r=r*x/i;

i=i+l;

}

Условие окончания параметрического цикла такое же, как и в цикле while.

Все три цикла записаны по возможности одинаково, чтобы подчеркнуть сходство циклов. Однако в данном примере цикл for имеет существенные преимущества. В заголовок цикла (в качестве выражения_1) можно ввести инициализацию всех пе­ременных:

for (i=2, b=1.0, r=x ; r>eps || r<-eps ; )

{

b=b+r;

r=r*x/i;

i=i+l;

}

В выражение 1 можно включать операцию изменения счет­чика членов ряда:

for( i=2, b=1.0, r=x ; r>eps || r<-eps ; i++)

{

b=b+r;

r=r*x/i;

}

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

for(i=2, b=1.0, r=x ; r>eps || r<-eps; b+=r, r*=x/i, i++);

В данном случае тело цикла - пустой оператор. Для сокра­щения выражения 1 в нем использованы составные операции присваивания и операция икремента.