Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекции ЯП (Кузьмин) ч.2_new (Паскаль).docx
Скачиваний:
3
Добавлен:
01.07.2025
Размер:
3.97 Mб
Скачать

14. Циклы с неизвестным числом повторений

В тех циклах, которые мы рассматривали до этого, имелся т.н. параметр цикла, который выполнял следующие функции:

  1. С его помощью задавалось нужное количество повторений тела цикла.

  2. Выполнение цикла начиналось с нужной компоненты.

  3. С его помощью выполнялся переход к нужным данным (следующей компоненте) на очередном повторении цикла.

В циклах с неизвестным числом повторений параметр цикла отсутствует. Для этих циклов необходимо "вручную" выполнить следующее:

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

  • с нужного слагаемого при нахождении суммы;

  • с нужного элемента массива, при обработке массива.

  1. Надо позаботиться, чтобы на следующем выполнении (повторении) тела цикла использовались новые данные (следующая компонента).

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

Существует две разновидности циклов с неизвестным числом повторений на псевдокоде и Паскале:

Псевдокод:

пока ситуация1 повторить

операторы;

Паскаль:

while ситуация1 do

операторы;

Цикл с предусловием. Он может ни разу не выполниться, если ситуация1 не произошла. Эту ситуацию называют ситуацией продолжения.



Псевдокод:

повторить

операторы ;

пока не ситуация2;

Паскаль:

repeat

операторы ;

until ситуация2;



Цикл с постусловием. Он выполняется всегда хотя бы 1 раз. Цикл с постусловием будет выполняться до тех пор, пока ситуация2 не наступит. Эту ситуацию называют ситуацией завершения.

логическая переменная или лог. выражение

Тело цикла для цикла с предусловием может состоять только из одного оператора. Для цикла с постусловием тело может состоять из нескольких операторов.

В общем случае структура цикла с неизвестным числом повторений имеет следующий вид:

Цикл с предусловием:

{подготовка цикла}

Установить нач значение результата

Встать на нужный (1-й) компонент

нач пока продолжать повтор повторение

нц

выполнить очередную итерацию

кон перейти к следующей компоненте

кц

тело цикла

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

{подготовка цикла}

Установить нач. значение результата

Встать на нужный (1-й) компонент

повторить

выполнить очередную итерацию

перейти к следующей компоненте

пока не завершить;

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

цикл с предусловием:

< подготовка цикла>

цикл:

если продолжить

то

нц

тело цикла

идти к цикл

кц

всё

цикл с постусловием:

< подготовка цикла>

цикл:

тело цикла

если закончить

то идти к финиш

иначе идти к цикл

все

финиш:

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

Циклы, которые не будут выполняться: Примеры простых бесконечных пустых циклов:

While (false) do; - ни разу while true do

Repeat while true; - только один раз. repeat until false;

Рассмотрим простые правильные примеры (шаблоны) использования циклов с неизвестным числом повторений:

1. Цикл, управляемый счетчиком (аналог цикла for, но с произвольным шагом счетчика)

{подготовка цикла}

Счетчик:=0; // инициализация счетчика

while Счетчик <= Конечное_значение do

begin

... проверка счетчика

<Использование текущего значения счетчика>

Счетчик := Счетчик + Шаг; //модификация счетчика (увеличение или уменьшение)

end;

2. цикл, управляемый т.н. «сигнальной меткой»

Используется при обработке потока входных данных, количество которых заранее неизвестно (такие потоки входных данных называются последовательностями).

Последовательность имеет следующие черты:

- число элементов неизвестно;

- последним элементов должен быть признак конца последовательности;

- признак конца последовательности не должен совпадать ни с одним из значащих элементов последовательности;

- нельзя напрямую обратиться к нужному элементу (по имени, по номеру);

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

Сигнальная метка (признак конца последовательности) – уникальное значение во входном потоке, которое обозначает конец входного потока, но само это значение не является частью потока - не может использоваться (обрабатываться) как данные. Например, если вводится и обрабатывается поток целых положительных чисел, то в качестве такой сигнальной метки можно принять число = -1. Начинать обработку любой последовательности надо с выбора значения «сигнальной метки» (исходя из типа элементов последовательности).

{подготовка цикла} исходя из типа элементов последовательности

Сигнальная_метка := Выбранное_значение

Считать текущее значение из входного потока и присвоить его входной_переменной

while входная_переменная ≠ Сигнальная_метка do

begin

Обработать текущее значение входной_переменной

Считать текущее значение из входного потока и присвоить его входной_переменной

end;

3. цикл, управляемый событием (флагом)

Чтобы не было зацикливания, следует делать следующее (при отладке):

- считать число шагов (и прерывать цикл, когда число шагов превышает некое максимальное, заранее заданное);

- считать время (и прерывать цикл, когда время превышает некое максимальное, заранее заданное).

Здесь используется булевская переменная, которая своим значением (True или False) указывает, наступило или нет определенное ожидаемое событие. Такая переменная часто называется флагом (фложком). Начальное значение флага обычно принимается равным False, а цикл должен (будет) продолжаться до тех пор, пока флаг не станет равным True (пока не наступит ожидаемое событие). При этом предполается, что в теле цикла выполняются действия, которые могут изменить значение флага.

{подготовка цикла} или Флаг = false

флаг := False; // инициализация флага

while (Not флаг) do

begin непрерывно гоняя цикл

...

<пусто> если ожидаем наступление внешнего события

или по отношению к программе (процессу)

<выполнение действий, влияющих на флаг>

end;

Рассмотрим более сложный пример:

На интервале [x1, x2] находится корень уравнения. надо найти этот корень.

NB: Пусть нам известно, что на интервале [x1,x2] существует лишь один корень. Иначе мы найдем лишь один корень из нескольких имеющихся



x1=A x0 x2=B

f(x0) = 0

Первую формулировку решения можно сразу записать следующим образом:

вычислить корень

При уточнении решения этой задачи возможны 2 ситуации: корень существует и корень не существует.

Ситуация

Действие

корень существует

найти корень

корень не существует

сообщить об ошибке и закончить

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

найти 1-е приближение

найти 2-е приближение можно свернуть в цикл (число повт. неизвестно заранее)

найти 3-е приближение

...

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

Структура цикла будет иметь вид:

{подготовка цикла} // подготовка нужна, так как в теле цикла используется рекурсия

установить начальное значение корня //найти 0-е приближение;

while продолжать do текущая левая граница

begin текущая правая граница

найти очередное приближение корня | х1 - х2| >

end заданная точность

Ситуацию "продолжать" можно описать следующим образом: | х1 - х2| > . Для поиска корня будем использовать метод половинного деления. Суть этого метода поиска корня (метод деления пополам) заключается в следующем: на каждой итерации интервал определения корня делится пополам и определяется середина интервала, т.е. точка x = (х1+х2)/2 . Далее определяется, какую половину интервала выбрать в качестве нового интервала определения корня. На следующей итерации выбранный интервал вновь делиться пополам. При каждом делении интервала текущие значения х1 и х2 как бы приближаются друг к другу. При бесконечном числе повторений х1 и х2 сольются в одну точку (с точностью до ).

Команду "найти очередное приближение" можно разбить на две:

1) выбрать нужную половину интервала (изменить левую или правую границу)

2) вычислить новое значение корня.

Команду "установить начальные значения" можно уточнить таким образом :

Вычислить х:= (x1+x2)/2.

Команда "вычислить новое значение корня" уточняется таким же образом:

вычислить х:= (x1+x2)/2.

Чтобы уточнить задачу «выбрать нужную половину интервала» надо следовать следующим правилам корректировки: в качестве нового интервала выбирается та половина начального интервала., на границе которой функция имеет разные знаки (если функция имеет разные знаки на границах, то это значит, что она через "0" перейдет). В данном случае в качестве нового интервала примем правую половину интервала (функция на границах правой половины имеет разные знаки).

Возможны две ситуации при уточнении команды "скорректировать границы":

Ситуация

Действие

Знаки на интервале [х1; (х1+х2)/2] (левая половина) разные

Принять х2 := (х1+х2)/2

Знаки на интервале [(х1+х2)/2; х2] (правая половина) разные

Принять х1 := (х1+х2)/2

Эти ситуации взаимоисключающие, поэтому анализировать (проверять) будем только одну из них.

В итоге уточнения цикла while получим:

Проверка: корень существует? ситуация «продолжать»

{подготовка цикла}

{установить начальное значение корня}

х:= (х1+х2)/2;

while (abs(x1 + x2)> eps) do

begin

{определение новой половины интервала}

if (f(x1) * f((x1 - x2)/2) < 0)

then

{корень в левой половине}

x2:= (x1 + x2)/2

else

{корень в правой половине}

x1:= (x1 + x2)/2;

{определение текущего значения корня на новом интервале}

x:= (x1 + x2)/2;

end;