Глава 3 Циклы Цикл является одной из важнейших алгоритмических 3. Увеличить на 1 значение знаменателя. структур и представляет собой последовательность Следовательно, задачу можно решить, например, так: операторов, которая выполняется неоднократно. В 1. Присвоить переменной Sum значение, равное 0 программах, связанных с обработкой данных или (Sum := 0). вычислениями, часто приходится выполнять циклически 2. Присвоить переменной i значение, равное 1 повторяющиеся действия. Циклы позволяют записать (i := 0) такие действия в компактной форме. 3. Добавить к сумме значение 1 / i Циклы принадлежат к числу управляющих опера- (Sum := Sum + 1 / i). торов. Внимательный читатель мог заметить, что до сих 4. Увеличить i на 1 (i := i + 1). пор мы использовали два вида операторов. Одни из них (Read, Write, оператор присваивания) 5. Повторить шаги 3 и 4. только Повторив операции 3 и 4 99 раз, мы получим тре- выполняли какие-либо действия, другие же управляли буемую сумму. Это пример алгоритмической конст- ходом выполнения программы (например, условный рукции «цикл». оператор). Последние и называются управляющими В языке программирования Паскаль имеется три операторами. разновидности цикла: Давайте познакомимся с примерами использования циклов в программах на Паскале. Рассмотрим задачу на цикл со счетчиком (цикл «для» — for ... to / downto); вычисление суммы большого числа слагаемых: 1+1/2+1/3+1/4+…+1/99+1/100 цикл с предусловием (цикл «пока» — whilе); Можно было бы выбрать простое решение и записать цикл с постусловием (цикл «до тех пор, пока» — вычисление данной суммы в строчку, употребив 99 repeat...until). операций деления и 99 операций сложения. Ну а если Каждая из трех разновидностей цикла имеет свои число элементов суммы равно 1000 или просто любому особенности, для каждой из них есть свой круг задач, целому наиболее естественно решаемых именно с ее помощью. числу? Представьте себе программу с оператором, который занимает несколько страниц и Цикл с предусловием содержит 999 сложений! Очевидно, простое решение здесь уже не подходит. Можно заметить, что при вы- Цикл с предусловием имеет вид: числении суммы повторяются всего три операции, while условие причем в определенном порядке: do {Эта часть называется заголовком цикла} 1. Разделить единицу на знаменатель. оператор; {Эта часть называется телом цикла} 2. Прибавить частное к ранее полученной сумме. 20-21 внутри цикла, и ее величина Телом цикла может быть и группа операторов, за- определяет очередное повторение ключенная в операторные скобки begin... end (то есть тела цикла} составной оператор). end; Цикл с предусловием выполняется до тех пор, пока W r i t e L n ( ' сумма ' , n , ' элементов= ' , истинно условие в заголовке цикла, причем оно s u m : 10:5 ) ; проверяется вначале, потом исполняется оператор. end. Переменным, входящим в условие, должны быть Цикл в этой программе работает следующим образом: присвоены определенные значения до входа в цикл. В теле цикла должны быть операторы, которые в какой-то момент изменят значение условия, сделав его вначале i = 1, s u m = 0; условие i < = 100 в заголовке оператора w h i le истинно, поэтому начинается выполнение цикла; ложным. Если этого не случится, цикл будет бес конечным. При возникновении в программе бесконечного цикла говорят, что программа «зациклилась». s u m = 0 + 1; Зациклившуюся программу приходится останавливать одновременным нажатием клавиш Ctrl+Break, иначе она будет выполняться вечно (точнее, до значение суммы увеличивается на единицу: i увеличивается на 1 i = 2; условие i < = 100 вновь истинно, поэтому тело первого цикла повторяется очередной раз; отключения компьютера). Задача о вычислении суммы может быть решена с значение суммы s u m = 0 + 1 + 1 / 2 , а переменной использованием цикла while... do следующим образом: i = 3; program summal; const после выполнения данной последовательности действий необходимое число раз получаем n = 100; {Так объявляется s u m = . . . + 1 / 100 , i = 101; именованная константа программы} var условие i <= 100 ложно, поэтому цикл i : Integer; sum : Real; завершается. Следующим действием будет вывод begin sum : = 0; результата. i : = 1; {Присвоим переменной sum Отметим, что в данном начальное значение 0, a i - начальное константа. Константа может иметь имя, тогда она значение 1} называется именованной решении использовалась константой. Объявляется именованная константа в предложении описания while i <= n do begin sum := sum + 1 / i; i := i + 1 {Переменная i меняется 22-23 констант, которое размещается в разделе описаний sum : Real; программы и имеет вид: begin const имя = значение; sum : = 0; Использование именованных констант преследует i : = 1; repeat две цели: сделать программу более удобной sum := sum + 1 / i; для i := i + 1; {i управляет повторением цикла } понимания. Если, допустим, в программе часто until i > n ; используется число 12, то иногда удобнее один WriteLn( 'сумма ' , n , ' элементов = ' , раз дать ему имя, например dozen, а затем sum:10:5 ) ; использовать это имя; end. облегчить изменение программы. Если, скажем, Здесь выполнение цикла происходит следующим нужно изменить количество элементов суммы, образом: то лучше изменить одну строку в предложении описания констант, чем вносить исправления по вначале i = 1 и sum = 0 + 1 ; всей программе. i увеличивается на 1: i = 2; условие i > 100 ложно, поэтому выполнение цикла повторяется; Цикл с постусловием Следующая разновидность цикла — цикл с постусловием. Рассмотрим эту разновидность: repeat группа операторов until условие; Здесь вначале выполняется группа операторов, а значение суммы изменяется: sum = 1 + 1 / 2; i увеличивается на 1: i = 2 + 1; условие i > 100 ложно, цикл повторяется; цикл повторяется, пока не окажется i = 100 + 1, а значение суммы sum = 1 + 1 / 2 +...+ 1 / 100; условие i > 100 истинно, цикл завершен. Слова repeat и until являются зарезервированными, потом производится проверка, следует ли вновь по- как, впрочем, и слово whi le. В отличие от цикла while вторить эту группу. Если условие ложно, выполнение операторы внутри цикла repeat выполняются хотя бы цикла повторяется, иначе — заканчивается. один раз, в то время как в цикле whi le они могут не Решение предыдущей задачи о суммировании с выполниться ни разу. использованием цикла repeat... until выглядит так: program summa2; Цикл со счетчиком const n = 106; var i : Integer; Цикл со счетчиком имеет следующий вид: 24-25 f.or i начальное_значение := Программа вычисления суммы с использованием to конечное_значение do . . . оператор; цикла со счетчиком дана далее в двух вариантах (вариант с to и вариант с downto). Здесь переменная i, называемая управляющей переменной цикла for (или его счетчиком), является program summa3; произвольным идентификатором, который объявляется const n = 100; как переменная целого (чаще всего) типа. Она может var i : Integer; быть также логической или символьной (о символьном sum : Real; типе речь пойдет дальше). Допускаются и некоторые begin другие типы, но этот случай мы рассматривать не будем. sum := 0; При выполнении оператора for сначала вычисляется значение выражения начальное_значение, вычисляется значение выражения for i := 1 to n do затем {При первом выполнении цикла i конечное_ равняется 1, к sum добавляется 1, затем i значение, далее управляющая переменная цикла по- = 2 , к sum добавляется 1/2} следовательно пробегает все значения от начального до sum := sum + 1 / i; конечного. В том случае, когда начальное значение {Затем i = 3 , к sum добавляется 1/3, и оказывается больше конечного значения, тело цикла не так продолжается до i = п} будет выполняться вовсе. Начальное и конечное WriteLn( 'сумма ' , n , ' элементов = ' , значения остаются неизменными в ходе выполнения sum:10:5); всего цикла for. end. Параметр цикла i, если он целого типа, пробегает все Второй вариант: значения с приращением 1, и его текущее значение не должно изменяться операторами внутри цикла. Такое program summa4; изменение не запрещено правилами языка, но его const n = 100; последствия будут непредсказуемы. После завершения var i : Integer; цикла параметр i считается неопределенным. sum : Real; В цикле: begin for i := начальное_значение sum := 0; downto конечное_значение for i := n downto 1 do do . . . sum := sum + 1 / i; оператор; параметр цикла меняется от начального значения до конечного с шагом -1. 26-27 Writeln('сумма ', n, 'э лементов = ' ,sum:10:5); 100 °С. Эти значения и используются для пересчета end. одних температур в другие. Формула для пересчета имеет вид: tf = 9 / 5 * tc + 32, где tf — температура по Самостоятельно разберите работу циклов f o r в Фаренгейту, a t c — температура по Цельсию. Следую- обоих вариантах программы. щая программа предназначена для вывода таблицы соответствия между температурными шкалами Цельсия Программы с циклами и Фаренгейта в интервале замерзания воды до точки ее кипения: Какую разновидность цикла лучше выбрать в каждом Program Celsius_to_Fahrenheit; конкретном случае? Вот наши советы. Используйте цикл f o r точно знаете, сколько var i, Celsius, Fahrenheit : Integer; в том случае, когда раз должно begin быть Writeln('Таблица соответствия между выполнено тело цикла. В противном случае температурными шкалами'); обратитесь к циклам r e p e a t или w h i l e . Writeln('Цельсия и Фаренгейта'); Используйте цикл r e p e a t , если необходимо, Writeln; чтобы тело цикла выполнялось по крайней мере for один раз. подзаголовке к роману 20 do Fahrenheit := 32 + Cel sius * 9 di v 5; выполняться тело цикла. Write (' С =' , Celsius) ; У писателя-фантаста Рэя «451° to Celsius := 5 * i; проверка была произведена прежде, чем будет Бредбери есть роман i := 0 begin Используйте цикл w h i l e , если хотите, чтобы 212° по Фаренгейту. температур от точки Write (' F =' , Fahrenheit) ; по Фаренгейту». В сказано, что «451° Writeln; по end; Фаренгейту — температура, при которой горит бумага». WriteLn( 'Нажмите <Enter>'); А что значит «по Фаренгейту»? ReadLn; Температурная шкала Фаренгейта была предложена end. немецким физиком Габриэлем Фаренгейтом и используется в настоящее время в ряде англоязычных В данной программе еще до ее выполнения точно стран. В этой шкале температура замерзания воды при известно число повторений цикла, поэтому ясно, что стандартном атмосферном давлении равна 32 "F, а лучше всего здесь использовать цикл со счетчиком. температура кипения составляет 212 °F. В более Так чему же равна температура горения бумаги по привычной для нас шкале Цельсия аналогичными шкале Цельсия? Измените программу так, чтобы найти «опорными» точками являются соответственно 0 °С и ответ на этот вопрос. 28-29 Паскаль-рулетка. В следующем примере число Randomize; повторений цикла заранее неизвестно, поэтому вместо number := Random(ll); цикла со счетчиком лучше использовать одну из W r i t e L n ( ' З а д у м а н о целое ч и с л о о т 0 до 1 0 . разновидностей цикла с проверкой условия. Предлагаем Угадайте! ' ) ; поиграть в простую, но азартную игру (крепко закройте WriteLn; дверь в своей комнате — родители могут увидеть!) на W r i t e L n ( " В в е д и т е целое ч и с л о от 0 до 1 0 ' ) ; угадывание целого числа от 1 до 10. Пусть программа ReadLn(guess); «загадает» такое предполагаемое число, а значение. пользователь Если число введет угадано, программа поздравит победителя, а если нет — while guess <> number do begin Dec(bonus); попросит его повторить попытку еще раз. Каждая WriteLn('Вы не угадали.'); безуспешная попытка снижает призовые баллы. В самом WriteLn; начале if игроку назначается 10 призовых баллов. Описание алгоритма: g u e s s < n u m b e r t h e n W r i t e L n ( ' Ваше число меньше задуманного' ) else выбрать случайное целое число от 1 до 10; W r i t e L n ( ' Ваше ч и с л о б о л ь ш е вывести приглашение на ввод целого значения; задуманного' ) ; если введенное число меньше задуманного, сооб- W r i t e L n ( ' П о п ы т а й т е с ь еще р а з ! ' ) ; щить об этом игроку, иначе сообщить ему о том, ReadLn(guess); end; что введенное число больше задуманного; W r i t e L n ( ' П о з д р а в л я ю ! Вы у г а д а л и и повторять ввод целого значения до тех пор, пока набрали ' , bonus, ' очков'); число не будет угадано; WriteLn('Нажмите <Enter>'); вывести поздравление победителю и сообщить ReadLn; ему о набранном числе баллов; end. завершить работу. В этой программе используются новые операторы. Не исключена возможность того, что число будет Это R a n d o m i z e — начальная установка специальной угадано сразу. В этом случае уже не надо выводить процедуры — подсказку игроку, поэтому следует использовать цикл с R a n d o m ( n ) , выдающей случайные целые числа от 0 предусловием w h i l e . . .do: до n - 1, а также Dec ( b o n u s ) — вызов процедуры, уменьшающей program roulette; «генератора» на единицу случайных значение чисел переменной bonus. var number, guess, bonus : Byte; Пробуем разбогатеть. Рассмотрим пример исполь- begin зования цикла с постусловием. Пусть некто (ну, bonus := 10; например, 30-31 вы, уважаемый читатель), обладая определенной interest := rate * balance; денежной суммой, открыл счет в банке. Банк ежегодно balance := balance + interest; начисляет определенный процент от вклада (это Inc(year); называется «учетной ставкой процента»), соот- W r i t e L n ( y e a r :6, ' ' , b a l a n c e ) ветственно увеличивается и сумма вклада. Считается, until balance > 2 * balance_initial; что этот процент не зависит от времени и от величины WriteLn('Нажмите <Enter>'); вклада. Такая схема называется «правилом сложных ReadLn; процентов». Необходимо написать программу, которая end. рассчитывает величину вклада и выводит эту величину В данном случае тело цикла выполняется по крайней для каждого года до тех пор, пока величина вклада не мере один раз, поэтому используется цикл с по- удвоится. Вот алгоритм решения данной задачи: стусловием. 1. Ввести первоначальную величину вклада, Процедура Inc(year) увеличивает на единицу значение переменной year. учетную ставку процента и год помещения денег в Игра Баше на 15 предметах. Игра Баше известна во банк. Франции. Интересно поиграть в нее, например, на уроке 2. Рассчитать новую величину вклада. химии (однако авторы не отвечают за последствия такой 3. Вывести год и величину вклада в этом году. игры!). Правила игры Баше таковы. Имеется 15 одинаковых 4. Повторять шаги 2 и 3 до тех пор, пока величина (обычно это деревянные палочки). В игре участвуют двое. Соперники ходят по вклада не удвоится. Текст предметов очереди, за каждый ход играющий может взять 1, 2 или программы: 3 предмета. Проигрывает тот, кто вынужден взять program rockafeller; последний var year : Word; предмет. Пропускать ход нельзя. Предполагая, что нашим соперником по игре Баше будет balance, balance_initial, rate, interest : компьютер, напишем для него программу. Real; Прежде всего придумаем алгоритм выигрышной begi n стратегии, при которой первый соперник начинает и WriteLn('Введите год помещения денег выигрывает. Легко видеть, что исходные 15 предметов в банк'); можно разбить на 5 групп, содержащих не более чем по ReadLn(year); 4 предмета так, как указано на рис. 3.1. WriteLn('Введите величину вклада'); При таком распределении начинающий игрок берет ReadLn(balance); первые 2 предмета, и далее, сколько бы ни взял WriteLn('Введите ставку процента (0.0-1.0)'); ReadLn(rate); balance_initial : = balance; WriteLn('Год Вклад'); WriteLn('============'); repeat 4 З а к 32-33 второй игрок (1, 2 или 3 предмета), первый будет до- W r i t e L n ( ' О с т а л с я ' , m , ' п р е д м е т . Вы бирать до 4 предметов так, чтобы вместе они за два проиграли' ) ; полухода выбрали одну группу. После четырех ходов end. первый игрок оставляет сопернику 1 предмет и выиг- Измените эту программу так, чтобы можно было рывает. играть вдвоем с человеком. Ищем квадратный корень. Квадратный корень из числа можно приближенно вычислить как предел a последовательности чисел: x0, x1, x2, …, xk, xk+1, …, где x0 — произвольное число, а каждое следующее приближение xk+1 получается из предыдущего xk по формуле xk+1 Рис. 3.1. Распределение предметов в игре Баше =1/2*( xk + а / xk ). Процесс заканчи- вается тогда, когда очередное xk+1 отличается от предыдущего xk program bashe; на величину, меньшую некоторого за- var a, b, m : Integer; данного и достаточно маленького числа. Это число begin называют точностью вычисления квадратного корня, и чем оно меньше, тем точнее будет определено искомое m := 15; значение. Данный алгоритм используется в программе a := 2; squareroot: m :=m – a; program squareroot; while m > 1 do const eps = le-15; begin { П о к а з а т е л ь н а я форма з а п и с и Write('я взял ' , a); вещественного числа, эквивалентна if a = 1 then Write('предмет') else 10-15} Write('предмета'); var WriteLn(' осталось ' , m); а , х0, xl : Real; WriteLn( ' Соперник, ваш ход:'); begin ReadLn(b); WriteLn('Введите число а ' ) ; а := 4 - b ; ReadLn(a); m := m - (a + b); end; 34-35 х0 := 0; смысла рассматривать, так как они не будут его xl := а; делителями. {задаем предыдущее и последующее program sover; приближения к корню } var repeat a , i , s : Integer; х0 := xl; begin xl := (х0 + а / х0) / 2; Write('Введите целое число a : ' ) ; {получаем из предыдущего последующее ReadLn(a); приближение} s := 0; until Abs(xl - х0) < eps; for i := 1 to a d i v 2 do WriteLn(' Sqrt(' , а , ' ) = ' , xl:10:5); {Подсчет суммы end. делителей } Определить заранее, сколько потребуется повторений i f a mod i = 0 then цикла для достижения заданной точности расчета квадратного корня из определенного begin значения, s:= s + i; практически невозможно, поэтому в данном примере Write('+', i) следует выбрать какой-нибудь цикл с условием. Цикл end; repeat в данном случае нагляднее, так как выход из i f s = a then Writeln( ' Число ' , a , ' цикла определен условием достижения заданной точ- совершенное ' ) else WriteLn( 'Число ' , a , ности расчета, а для того, чтобы проверить это условие, ' н е совершенное ' ) ; необходимо вычислить по крайней мере два элемента end. числовой последовательности, иначе нечего будет сравнивать. В поисках совершенства. Число называется вершенным, Итог со- если оно равно сумме всех своих делителей, Итак, мы познакомились с циклами, важнейшей со- включая 1, например 6 = 1 + 2 + 3, 28 = 1 + 2 + + 4 ставной частью большинства программ. Но как нам быть + 7 + 1 4 . Поставим задачу определить, является ли с героем древнегреческого мифа? Мы выяснили, что заданное число совершенным. Алгоритм решения этой циклическое действие, которое он выполняет, будет задачи довольно простой. Необходимо перебрать все конечным, если существует условие, при выполнении натуральные числа от 1 до половинки (или ее целой (или невыполнении!) которого цикл должен завершаться. части) от проверяемого числа и, если остаток от деления рассматриваемого числа на данное равен Ограничение может быть и по числу повторений цикла. нулю, От цикла, который выполняет Сизиф, немного пользы, добавить очередное значение к сумме. Значения, ведь каждый раз он повторяет одно и то же действие. большие половинки проверяемого числа, не имеет Цикл в программе при каждом 36-37 новом повторении выполняет действие, которое хотя бы 1 + 2 + 3 +...+ п = п * ( п + 1) / 2, немного отличается от предыдущего. Так, Сизиф мог 1 + 3 + 5 +...+ (2 * п - 1) = п2, бы, скажем, каждый раз вкатывать на гору камень 12 + 22 + З2 +...+ n2 = n * (n + 1) * (2 * п + 1) / 6, меньшего размера, чем предыдущий. Условием окон- 13 + 23 + З3 +...+ n3 = п2 * ( п + 1)2 / 4, 12 + З2 + 52 +...+ (2 * п - 1)2 = п * (4 * п2 - 1) / 3, чания нелегкого сизифова труда было бы в этом случае 13 + З3 + 53 +...+ (2 * п - 1)3 - п2 ( 2 * п 2 - 1). вкатывание на гору камня, например, весом в 1 грамм. Следовательно, помочь Сизифу можно, Задача 2. Пусть даны числа а,b (а> 1). Получить все изменив члены последовательности а, а2, а3, ... меньшие b. алгоритм его работы, введя в него «правильный» цикл. Задача 3. Пусть даны числа a, b (а > 1). Получить Напоследок, как обычно, предлагаем подумать над первый элемент последовательности а, а2, а3, ... боль- задачами. В каждой из задач необходимо вначале ший числа b. придумать алгоритм, а затем написать программу. Задача 1. Проверить тождества: 38-39