Учебник Информатика Алгоритмика 7 класс Ландо Семенов Вялый

На сайте Учебники-тетради-читать.ком ученик найдет электронные учебники ФГОС и рабочие тетради в формате pdf (пдф). Данные книги можно бесплатно скачать для ознакомления, а также читать онлайн с компьютера или планшета (смартфона, телефона).
Учебник Информатика Алгоритмика 7 класс Ландо Семенов Вялый - 2014-2015-2016-2017 год:


Читать онлайн (cкачать в формате PDF) - Щелкни!
<Вернуться> | <Пояснение: Как скачать?>

Текст из книги:
7 (О S н «5 ь / ^ /.,' - ■ [ , m ____®______ ПРОСВЕЩЕНИЕ ИЗДАТЕЛЬСТВО с. к. ЛАНДО А. Л. СЕМЕНОВ М. Н. ВЯЛЫЙ информатика алгоритмика Учебник для 7 класса общеобразовательных учреждений Москва «Просвещение» 2008 УДК 373.167.1:004 ББК 32.81я72 Л22 На учебник получено положительное заключение Российской Академии Наук. Ландо С. К. Л22 Информатика : алгоритмика : учеб, для 7 кл. общеобразоват. учреждений / С. К. Ландо, А. Л. Семенов, М. Н. Вялый. — М. : Просвещение, 2008. — 208 с. : ил. — ISBN 978-5-09-015963-0. Цель этого учебника — продолжить изучение основ алгоритмического мышления, начатое в учебнике «Информатика — 6. Алгоритмика*. Алгоритмические средства предыдущего учебника расширяются за счет введения переменных. Изучение переменных начинается на материале Исполнителей, уже известных из предыдущего курса. Далее рассматриваются новые задачи; восстановление алгоритмов, поиск, построение игровых стратегий, щифры. УДК 373.167.1:004 ББК 32.81 я72 Учебное издание Ландо Сергей Константинович Семенов Алексей Львович Вялый Михаил Николаевич информатика алгоритмика Учебник для 7 класса общеобразовательных учреждений Зав. редакцией Т.А. Бурмистрова. Редактор А. В. Желонкин. Младший редактор Н. И. Смирнова. Художник О. В. Попович. Художественный редактор О. П. Бого молова. Компьютерная графика: Г. М. Дмитриев, И. В. Губина, М. Е. Аксенова. Техническое редактирование и верстка Е. С. Юровой. Корректоры: Е. В. Баранов скал, Н. В. Бурдина, Л. С. Вайтман, И. В. Чернова Налоговая льгота — Общероссийский классификатор продукции ОК 005-93—953000. Изд. лиц. Серия ИД № 05824 от 12.09.01. Подписано в печать 24.04.2007. Формат 70x90Vi6- Бумага офсетная. Гарнитура Школьная. Печать офсетная. Уч.-изд. л. 12,88 + 0,46 форз. Тираж 10 000 экз. Заказ № 16977 (п-гз). Открытое акционерное общество «Издательство «Просвещение*. 127521, Москва, 3-й проезд Марьиной рощи, 41. Открытое акционерное общество «Смоленский полиграфический комбинат*. 214020, г. Смоленск, ул. Смольянинова, 1. ISBN 978-5-09-015963-0 )Издательство «Просвещение», 2008 ) Художественное оформление. Издательство «Просвещение*, 2008 Все права защищены Оглавление От авторов ................................................ 6 УЧЕБНИК Глава 1. Что такое переменные и как с ними работать 1. Лестница для Робота................................ 8 2. Что такое переменные.............................. 10 3. Применение переменных ............................ 11 4. Переменные в процедурах .......................... 12 5. Как выбирать переменные........................... 13 6. Условия с переменными............................. 14 7. Возвращение к Ханойским башням ................... 15 Глава 2. Переменные в графических Исполнителях и рисование графиков 1. Чертежник и Черепаха: напоминание ............... 18 2. Черепаха рисует многоугольники................... 19 3. Как Черепаха может нарисовать кривую........... 21 4. Изображение зависимостей на графиках — работа для Чертежника ...................................... 22 5. Массивы ......................................... 24 6. Откуда берутся формулы .......................... 26 7. Дискретизация.................................... 28 8. Рисование формул................................. 29 9. Процедуры рисования графиков..................... 31 10. Сложности в рисовании графиков................... 34 Глава 3. Работа с массивами 1. Среднее и максимум................................ 38 2. Эффективность работы с массивами.................. 41 3. Работа с упорядоченными массивами................. 42 4. Позиционная система счисления..................... 47 Глава 4. Угадай алгоритм! 1. Изучаем Робота ................................... 55 2. Генераторы числовых последовательностей........... 63 3 Глава 5. Числовые алгоритмы 1. Вычисление значения многочлена.................... 69 2. Алгоритмы и формулы............................... 78 3. Алгоритм Евклида.................................. 80 Глава 6. Перебор 1. Ищем клад......................................... 87 2. Проверка простоты числа........................... 89 3. Выбираем пароль................................... 92 Глава 7. Случайность и неопределенность в программах 1. Случайное и закономерное..........................100 2. Вероятность случайного события....................101 3. Комбинаторика и вероятность.......................102 4. Несовместимые события и правило суммы ............103 5. Независимые события и правило произведения.....; . 104 6. Как сделать монету с 86 сторонами.................106 7. Задача обмена новостями...........................108 8. Псевдослучайность, или Как научить компьютер бросать монету.............................................112 Глава 8. Игровые алгоритмы 1. Игра в конфеты....................................117 2. Игры и стратегии .................................120 3. Программирование стратегий........................123 4. Дерево игры.......................................124 5. Как искать выигрышные стратегии (использование симметрии) ..............................................130 6. Использование случайности в игровых алгоритмах....133 Глава 9. Работа с цепочками символов 1. Работа с символами................................135 2. Упорядочение символов и слов......................136 3. Понятие о кодировании и шифрах....................141 4. Простейшие алгоритмы шифрования...................143 5. Разгадывание алгоритмов шифрования и дешифровка 146 Словарь понятий алгоритмики.............................149 4 ЗАДАЧНИК Справочник по алгоритмическому языку 1. Программы.......................................152 2. Исполнители.....................................156 Задачи 1. Робот рисует. Замена рекурсии в программах для Робота работой с переменными..........................160 2. Чертежник рисует...............................168 3. Черепаха рисует................................173 4. Кривая дракона с переменными ..................178 5. Условия и выражения............................179 6. Массивы .......................................182 7. Угадай алгоритм ...............................188 8. Эффективные вычисления ........................193 9. Перебор и случайность..........................198 10. Вероятности и комбинаторика....................202 11. Игровые алгоритмы..............................204 12. Символьные переменные..........................206 От авторов Учебник, который вы держите в руках, служит продолжением учебника «Информатика — 6. Алгоритмика». А это значит, что основным предметом изучения будет по-прежнему алгоритм. Но теперь вы уже много знаете об алгоритмах, и мы можем опираться на эти знания. Алгоритмы, которые мы будем рассматривать, станут богаче и разнообразнее. Это обогащение достигается за счет введения переменных. Окружающий нас мир все время меняется. Люди, предметы, животные движутся. Ветер нагоняет облака, которые проливаются дождями. Земля вращается вокруг Солнца — это движение приводит к смене времен года — и вокруг своей оси, что мы замечаем по восходам и заходам Солнца. В процессе жизни человека клетки, составляющие его тело, все время обновляются и заменяются другими. Даже, казалось бы, вечно неизменные звезды постепенно уменьшаются за счет излучаемого ими света, а затем неожиданно схлопываются, превращаясь в белых карликов, или взрываются, образуя сверхновые. Записывая характеристики процессов в последовательные моменты времени, можно отследить происходящие изменения. Эти характеристики могут быть самыми разными. Например, числовыми — светимость звезды и ее масса. Другой пример числовых характеристик — стоимость акций той или иной компании или количество произведенных автомобилей. Характеристики могут быть и нечисловыми. Тогда они собираются в списки фамилий или библиотечные каталоги, даты и газетные заголовки. Когда человек работает с меняющимися величинами, для него важно каждой такой величине дать имя. Имя при этом как раз не меняется, меняется только значение величины. Переменные — это кусочки компьютерной памяти, предназначенные для хранения различных — как числовых, так и нечисловых — характеристик, которые изменяются в процессе работы программы. Соответственно меняется и содержимое этих кусочков памяти. Пока вы читаете эти строки, значения многих миллиардов переменных меняются в процессе одновременной работы миллионов программ. Но какими бы сложными ни были эти программы, основы их работы с переменными очень просты. Эти основы мы и будем изучать в курсе алгоритмики. И постараемся при этом сохранить и развить ту ясность мышления, которой, мы надеемся, вам удалось достичь при изучении первой части курса. За дело! 6 f алгоритмика _ - Ч L Jii'I • ^ Глава 1 ,* Что такое переменные и кок с ними работать В этой главе мы знакомимся с переменными. При работе с Ал-горитмикой — 6 нам приходилось писать много программ, которые были очень похожи друг на друга и отличались лишь числами, входившими в команды. Введение переменных позволяет заменить такие программы одной программой. Вместо того чтобы всякий раз переписывать программу заново, мы получаем возможность изменить лишь одну строку программы — задать новые значения переменных. Т]| Лестница для Робота Робот должен нарисовать на поле лестницу (рис. 1), начиная с левого нижнего угла. Это несложная задача, подобная тем, которые мы научились решать в Алгорит-мике — 6. Одно из возможных ее решений показано в программе справа. Рис. 1 ПОВТОРИТЬ 10 РАЗ закрась вправо КОНЕЦ ПОВТОРИТЬ 5 РАЗ закрась вверх КОНЕЦ ПОВТОРИТЬ 5 РАЗ закрась влево закрась влево закрась вниз КОНЕЦ Упражнение Убедитесь, что написанная программа действительно рисует лестницу с рисунка 1 и что Робот возвращается на свое исходное место. Обратите внимание на то, что высота лестницы б клеток, а Робот поднимается вверх 5 раз. 8 ГЛАВА 1 а) Рис. 2 б) Лестницы с рисунка 2 очень похожи на лестницу с рисунка 1, однако они другого размера. Поэтому, для того чтобы нарисовать любую из них, нам потребуется изменить уже написанную программу для Робота. Программа 1 — это новая программа для лестницы с рисунка 2, а. Программа 2 — это новая программа для лестницы с рисунка 2, б. 1) 2) ПОВТОРИТЬ 6 РАЗ закрась вправо КОНЕЦ ПОВТОРИТЬ 3 РАЗ закрась вверх КОНЕЦ ПОВТОРИТЬ 3 РАЗ закрась влево закрась влево закрась вниз КОНЕЦ ПОВТОРИТЬ 16 РАЗ закрась вправо КОНЕЦ ПОВТОРИТЬ 8 РАЗ закрась вверх КОНЕЦ ПОВТОРИТЬ 8 РАЗ закрась влево закрась влево закрась вниз КОНЕЦ Эти две новые программы очень похожи на исходную, но все равно их пришлось писать заново. К тому же даже в таком простом случае при переписывании программы ее пришлось менять в трех местах. В более сложной программе даже поиск мест, в которых ее нужно изменить, становится непростой задачей, а ведь надо еще понять, как именно следует изменить алгоритм. Но программирование призвано облегчить жизнь людям и автоматизировать выполнение Что такое переменные и кок с ними работать 9 монотонной, рутинной работы! Значит, должен существовать инструмент, который уменьшает количество изменений в программах, решающих похожие задачи. Такой инструмент действительно есть. Это переменные. Вот как можно упростить рисование 3) разных лестниц. Введем переменную s, которая будет обозначать число ступенек в лестнице. Тогда программу рисования лестницы с рисунка 1 можно записать так, как показано справа (программа 3). Первая команда в программе задает число ступенек, а остальная часть программы рисует лестницу с s ступеньками. Теперь, для того чтобы приспособить программу к рисованию лестницы с 3 или 8 ступеньками, нам достаточно изменить в ней одну строку — первую. Нам уже не нужно искать места, где программу нужно менять, и думать, как именно это следует делать. Все, что от нас требуется, — это задать переменной s значение, равное нужному числу ступенек. 8 = 5 ПОВТОРИТЬ 2*8 РАЗ закрась вправо КОНЕЦ ПОВТОРИТЬ 8 РАЗ закрась вверх КОНЕЦ ПОВТОРИТЬ 8 РАЗ закрась влево закрась влево закрась вниз КОНЕЦ 2. Что такое переменные Итак, число повторений в нашей программе указывает переменная S. Что же такое переменная? Переменная — это имя, которое может принимать различные значения. Пока мы ограничимся переменными, которые принимают только числовые значения. Значение переменной (в нашем случае переменной s) можно использовать при исполнении программы. В процессе исполнения программы значение переменной может меняться. Поэтому переменная и называется переменной. Именно возможность изменения значения и является главной причиной введения переменных в языках программирования. Переменную можно представить себе как ящичек в памяти компьютера. Вся память состоит из таких ящичков, но перед началом исполнения программы эти ящички пусты. Чтобы у переменной появилось значение, нужно ей значение присвоить. Команда присваивания записывается с помощью знака равенства. Например, команда / = 3 присваивает переменной i значение 3. В результате в ящичек i заносится значение 3. После этого им можно пользоваться в программе: если в очередной команде встречается переменная i, то она заменяется своим значением 3. Ящичек нельзя опять сделать пустым, но его значение можно поменять с помощью новой команды присваивания. Например, если 10 ГЛАВА 1 выполнить команду / = 6, то содержимое ящичка i заменится числом 6. После этого переменная i будет заменяться своим новым значением 6. К тому же результату, что и команда i = 6, привело бы исполнение команды i = i + 3. Эту команду нужно понимать так. Сначала мы вычисляем значение выражения г -1- 3, которое стоит справа от знака присваивания «=». Для этого мы берем значение переменной i (которое, как мы знаем, равно 3) и прибавляем к нему 3. Получается 6. Затем этот результат заносится в переменную i (указанную слева от знака присваивания). Другими словами, команда i = i + 3 увеличивает значение переменной i на 3. С переменными и равенствами вы уже встречались в курсе математики. В алгебраических выражениях переменная — это символ (или последовательность символов). Но знак равенства в команде присваивания и алгебраической формуле нужно понимать совершенно по-разному. В алгоритмике равенство i = i + 3 — это команда, а в алгебре равенство i = i + 3 — это утверждение (ложное для любого значения г). 3. Применение переменных Использование переменных делает программу гибкой, позволяя легко приспосабливать ее к новым требованиям и к новым задачам. Например, мы можем захотеть менять не только число ступенек в лестнице, но и ширину ступенек. В исходной программе эта ширина равна двум клеточкам, а мы хотим научить Робота рисовать лесенки, в которых ширина ступенек может быть равна 1 клеточке или 4 клеточкам. Проще всего этого достичь, используя для хранения ширины ступеньки какую-нибудь переменную. Пусть это будет переменная w. Что должно измениться в программе рисования лестницы? Ясно, что от ширины ступеньки зависит общая длина лестницы. В предыдущей программе длина основания лестницы определялась тем, что в первом цикле Робот закрашивает 2s клеточек. Коэффициент 2 в этом выражении не что иное, как ширина ступеньки. Поэтому при ширине ступеньки w мы можем заменить коэффициент 2 на la, и первый цикл примет такой вид, как показано справа. Уже из этого простого примера видно, что переменные можно перемножать: надо, как в алгебре, перемножать их значения. Вообще, над переменными можно производить все те же арифметические операции, что и над числами. При выполнении программы переменным whs нужно до начала выполнения цикла присвоить значения. После этого уже можно найти и значение выражения w*s, перемножив значения ПОВТОРИТЬ w*s РАЗ закрась вправо КОНЕЦ Что такое переменные и кок с ними работать 11 переменных whs. Мы видим, что в алгоритмике, как и в алгебре, значение может быть не только у переменных, но и у выражений. Второй цикл отвечает за высоту лестницы, поэтому при изменении ширины ступеньки он останется неизменным. В третьем цикле все ступеньки рисуются по очереди сверху вниз. Ширина ступеньки стала переменной, поэтому мы не можем написать одну последовательность команд Робота для рисования всех таких ступенек. Однако мы можем вместо этого написать цикл рисования одной ступеньки (программа 1). 1) ПОВТОРИТЬ W РАЗ закрась влево КОНЕЦ 2) 8 = 5 w = 2 ПОВТОРИТЬ w*s РАЗ закрась вправо КОНЕЦ ПОВТОРИТЬ 8 РАЗ закрась вверх КОНЕЦ ПОВТОРИТЬ 8 РАЗ ПОВТОРИТЬ W РАЗ закрась влево КОНЕЦ вниз КОНЕЦ Ош OOz ■О; inj: VJIb КЛщ О! r\JZ Сводя воедино все уже написанные кусочки, мы получаем программу 2 рисования лестницы с рисунка 1. Программа 2 не длиннее исходной программы, но может рисовать с небольшими изменениями очень много разных лестниц, в зависимости от того, какие значения мы присвоим переменным s и ш! Задача 1.1 Измените программу 2 еще раз, введя в нее дополнительно переменную h — высоту ступеньки и добившись того, чтобы Робот мог рисовать лестницы с переменными — числом, шириной и высотой ступенек. 4, Переменные в процедурах Lni Oi ooz Oz ui; rJs Ln; O! rvj- Ull oi OOz o= rvjS <Лш ui; O! fNJ- в начале написанной нами программы переменным s и w присваиваются значения. Оставшаяся часть программы не меняется, но она будет работать по-разному в зависимости от того, какие 12 ГЛАВА 1 именно значения были присвоены переменным. Если мы оформим эту часть программы в виде процедуры, то ее можно использовать в любых программах (не обязательно в нашей), где есть необходимость рисовать лестницу. Такая процедура показана справа. Теперь для рисования лестницы с рисунка 1 достаточно выполнить программу 1, а лестницы с рисунка 2 будут нарисованы программами 2 и 3. Несложно написать и программу, рисующую последовательно несколько лестниц, например две (см. программу 4). ПРОЦ лестница ПОВТОРИТЬ иг*8 РАЗ закрась вправо КОНЕЦ ПОВТОРИТЬ S РАЗ закрась вверх КОНЕЦ ПОВТОРИТЬ S РАЗ ПОВТОРИТЬ W РАЗ закрась влево КОНЕЦ вниз КОНЕЦ КОНЕЦ 1) 2) 3) 4) S = 5 W = 2 лестница 5 = 3 W-2 лестница 5 = 8 иг= 2 лестница 5 = 5 W = 2 лестница повторить 10 раз вправо конец 5 = 8 W = 3 лестница ^ Как выбирать переменные в обсуждавшихся выше программах нам встречались переменные S, W и h. Из каких соображений мы выбрали эти переменные? Как вообще выбирать переменные и какими они могут быть? Принципы выбора переменных очень похожи на принципы выбора имен процедур, которые мы обсуждали в Алгоритмике — 6. Прежде всего ясно, что не любые символы подходят для переменных. Например, использование переменной 13 может вызвать путаницу. Поэтому в языках программирования вводятся синтаксические правила для переменных. В нашем языке это правило выглядит так: СИНТАКСИЧЕСКОЕ ПРАВИЛО Переменная должна начинаться с буквы (латинского или русского алфавита) Что такое переменные и кок с ними работать 13 Кроме того, каждая переменная используется в программе с какой-то целью, и если она будет отражать эту цель, то программисту будет легче ее запомнить и использовать. Выбранные нами переменные — это первые буквы в английских словах step (ступенька), width (ширина) и height (высота). Если программа большая и переменных много, то нужно брать более длинные переменные. Использование одной и той же переменной в разных целях часто приводит к ошибкам в программе. 6. Условия с переменными Робот стоит здесь В Алгоритмике — 6 нам встречалась следующая задача: Робот стоит в горизонтальном прямоугольнике шириной 1, огороженном стенами, на некотором расстоянии от левой стены. Переведите Робота в клетку, находящуюся на таком же расстоянии от правой стены (рис. 3). Мы обсудили рекурсивное решение этой задачи. Переменные позволяют предложить другое решение. Вот как это делается. Сначала Робот считает расстояние до левой стены (группа команд 1). Робот должен оказаться здесь Рис. 3 1) d = О ПОКА слева свободно ДЕЛАТЬ влево d = d+ Л КОНЕЦ После выполнения этих команд в переменной d хранится расстояние от исходного положения Робота до левой стены. Если Робот стоял у левой стены, то значение переменной d будет равно О, если между ним и стеной была одна клетка, то d будет равно 1 и т. д. Теперь Робот должен дойти до правой стены и отойти от нее влево на d клеток. Это можно делать по-разному. Например, подойдет группа команд 2. 2) ПОКА справа свободно ДЕЛАТЬ вправо КОНЕЦ ПОКА d > О ДЕЛАТЬ влево d = d- 1 КОНЕЦ 14 ГЛАВА 1 Первый цикл ПОКА доводит Робота до правой стены, а второй цикл отводит его на нужное число клеток влево. В этом втором цикле ПОКА мы использовали условие d > 0. Такое условие истинно, если значение переменной d положительно, и оно ложно, если значение переменной d отрицательно или равно нулю. При каждом проходе второго цикла ПОКА значение переменной d уменьшается на единицу. Оно становится равным нулю, когда число проходов сравнивается с числом шагов Робота из исходного положения до левой стенки, что и дает требуемый результат. Условие d > о — типичный пример условия, использующего значения переменных. Мы можем сравнивать переменные с числами: i < 5, S = 3 и т. д. Можем сравнивать числовые выражения, содержащие переменные: а + Ь < S - s. Для сравнения используются знаки <, >, = (меньше, больше и равно). Кроме простых знаков сравнения, есть три двойных знака: <=, >=, <>. Они используются вместо привычных математических знаков: <= вместо <, >= вместо >, о вместо 5^. Знаки, используемые в алгоритмике, составные: они состоят из двух символов. Чтобы правильно их прочитать, нужно просто вставить «или» между этими символами: «меньше или равно», «больше или равно» и «меньше или больше», т. е. «не равно». Поэтому условие а <= Ь совпадает со сложным условием (а < Ь) ИЛИ (а = Ь), так что эти составные знаки можно вообще не использовать. Можно также обойтись без знака >, ведь условие а > Ь можно переписать как Ь < а. Но использование дополнительных знаков, если они имеют ясный смысл, может несколько облегчить процесс записи программы. Упражнение Напишите сложное условие, совпадающее с условием S + d >= 3*f, используя условия сравнения < и =. Использование переменных и условий с переменными позволяет теперь найти новые решения некоторых задач из Алгоритмики — 6. В следующем разделе мы обсудим алгоритм переноса Ханойских башен с точки зрения новых возможностей. 7^ Возвращение к Ханойским башням в Алгоритмике — 6 мы написали несколько процедур оптимального перекладывания Ханойских башен. Вот, например, как выглядит одна из процедур для башни из трех кружков (см. справа). Мы видим, что эта процедура вызывает две процедуры оптимального перекладывания башни из двух кружков. Процедура оптимального перекладывания башни из четырех кружков будет вызывать две процедуры оптимального перекладывания ПРОЦ 3-башня с А на В НАЧАЛО 2-башня с А на С перенеси с А на В(3) 2-башня с С на В КОНЕЦ Что такое переменные и как с ними работать 15 башни из трех кружков и т. д. Число процедур растет с ростом числа кружков. Если исходно в башне было 64 кружка, то понадобится довольно много времени и бумаги для написания всех этих процедур. Введение переменных позволяет заменить много разных процедур всего шестью. Этих шести процедур достаточно для перекладывания башни из любого числа кружков. Каждая из этих шести процедур будет отвечать за перекладывание башни с одного из стержней на другой. Процедуры будут, по существу, одинаковыми и будут различаться только именами стержней. Итак, задача состоит в том, чтобы переложить башню высотой h кружков со стержня А на стержень В по правилам Ханойской башни. Обсуждавшийся нами алгоритм оптимального переноса состоит в следующем: нужно сначала перенести все кружки, кроме самого большого, со стержня А на стержень С, затем перенести этот большой кружок со стержня А на стержень В, а потом перенести башню со стержня С на стержень В. Команду переноса кружка с одного стержня на другой (например, со стержня А на стержень В) будем записывать просто в виде перенеси с А на В. Такой команде не нужно указывать номер переносимого кружка, она просто берет верхний кружок со стержня А и устанавливает на стержень В. Если это действие невозможно (а такое бывает, если на стержне А нет кружков или переносимый кружок больше верхнего на стержне В), то наступает ОТКАЗ. Всего таких команд 6. Попробуем написать процедуру переноса. Мы предполагаем, что в переменной h хранится высота переносимой башни. Тогда процедура выглядит так, как показано справа. Здесь две процедуры перенеси башню с А на С и перенеси башню с С на В еще не написаны, но ясно, что они должны быть очень похожи на нашу первую процедуру. Каждая из них будет вызывать две другие процедуры. Ясно и то, почему мы обращаемся к ним, уменьшив предварительно значение переменной Л на 1: в переносимой ими башне на один кружок меньше, чем в исходной. Написанная нами процедура является рекурсивной — она вызывает саму себя через другие процедуры. Перед завершением процедуры мы должны увеличить значение переменной /г на 1: вызывающая процедура переносит более высокую башню. Казалось бы, все в порядке, но, присмотревшись к нашей процедуре внимательнее, мы обнаружим, что она не может правильно работать. Одним из важнейших признаков правильной работы рекурсивной процедуры является своевременная остановка — при каких-то условиях такая процедура должна перестать вызывать себя (в том числе и через другие процедуры). ПРОЦ перенеси башню с А на В ! НАЧАЛО ! /I = Л - 1 i перенеси башню с А на С перенеси с А на В перенеси башню с С на В /» = Л + 1 КОНЕЦ 16 ГЛАВА 1 А наша процедура никаких условий не проверяет! Что же это за необходимые условия? Если при входе в процедуру Л = О, то попытка ее выполнения приведет к обращению к следующим процедурам со значением переменной h, равным -1. Но это означает, что мы просим переставить башню из -1 кружка! Ясно, что такая операция бессмысленна. Если значение переменной h в процедуре перестановки башни равно О, то процедура просто не должна ничего делать. Вот к какой процедуре приводит это рассуждение (программа справа). Эта процедура не вызывает никаких процедур при Л = О (и при h отрицательном). Вот теперь все правильно, в чем вы легко убедитесь, дописав недостающие процедуры и проверив, как они работают, вручную или на компьютере. Например, вы можете выписать все исполняемые команды и значение переменной h после выполнения каждой из них при Л = 4 и вызове процедуры перенеси башню с А на В. ПРОЦ перенеси башню с А на В НАЧАЛО ЕСЛИ Л > О ТО /I = /I - 1 перенеси башню с А на С перенеси с А на В перенеси башню с С на В h = h+ Л КОНЕЦ КОНЕЦ Задача 1.2 Напишите недостающие процедуры перенеси башню с А на С и перенеси башню с С на В. Задача 1.3 Измените процедуры переноса башни таким образом, чтобы при Л = 1 они не вызывали другие процедуры, а выполняли команду переноса одного кружка. ИТОГИ в этой главе мы познакомились с переменными. Переменная — это имя, которое принимает числовые значения. В процессе работы программы значение переменной может меняться. Переменные — основной инструмент упрощения алгоритмов. Переменные можно использовать и в процедурах. Использование переменных делает программы универсальными и позволяет с помощью одной программы решать целую серию однотипных задач. Глава 2 Переменные в графических Исполнителях и рисование графиков Применение переменных в графических Исполнителях, рисующих на плоскости, расширяет их возможности еще значительнее, чем для Робота и Ханойских башен. Одна короткая программа или процедура теперь может справиться с работой, выполнение которой требовало большого числа разных программ. Особенно удобно использование переменных там, где поведение графических Исполнителей описывается с помощью формул. В прошлом году мы написали много программ для Чертежника и Черепахи. Использование переменных облегчает написание программ для этих Исполнителей и позволяет заменить большое число разных программ одной. В этой главе мы научимся пользоваться переменными при работе с Чертежником и Черепахой. 1. Чертежник и Черепаха: напоминание Напомним вкратце описание Исполнителей Чертежник и Черепаха из Алгоритмики — 6. Чертежник живет на координатной плоскости, на которой задана прямоугольная система координат. Он рисует пером. У него четыре команды. По команде подними перо Чертежник поднимает перо, а по команде опусти перо опускает его. Поднятое перо не оставляет следа при перемещении пера по плоскости, а опущенное перо прочерчивает отрезок при каждом перемещении пера из одного положения в другое. Команд перемещения две, и обе с параметрами. Команда переведи в точку(а, Ь) перемещает перо в точку с координатами (а, Ь), где бы оно до этого ни находилось. Команда сдвинь на вектор(а, Ь) сдвигает перо из текущего положения на вектор с координатами (а, Ь). В начале исполнения любой программы перо Чертежника поднято. В отличие от Чертежника, Черепаха живет на бескоординатной плоскости и рисует хвостом. У нее шесть команд. Две первые подними хвост и опусти хвост играют ту же роль, что и команды подними перо и опусти перо Чертежника. А вот команды с параметрами другие. Это команды поворота вправо(а) и влево(а) и команды движения вперед(с/) и назад(<У). При выполнении команды поворота Черепаха поворачивается вправо или влево на указанный в параметре команды 18 ГЛАВА 2 угол, выраженный в градусах. При выполнении команды движения Черепаха сдвигается на указанное в параметре число шагов в нужном направлении. Она оставляет след, если ее хвост опущен. В начале выполнения любой программы хвост Черепахи поднят. ^ Черепаха рисует многоугольники ^ Чертежник способен нарисовать квадрат. А вот если надо нарисовать правильный многоугольник с другим числом сторон, то Чертежник сталкивается с серьезными трудностями. Черепаха же рисует не только квадрат, но и другие правильные многоугольники легко и непринужденно. Для того чтобы написать процедуру рисования правильного многоугольника с любым числом сторон, нам нужно лишь вспомнить, что делала Черепаха в разных конкретных случаях, и заменить стоявшие в этой процедуре числа на переменные. Пусть в переменной п записано число сторон, а в переменной d — длина одной стороны. Тогда процедура рисования правильного многоугольника с п сторонами длины d будет выглядеть так, как показано справа. В этой процедуре собраны все те программы, которые мы писали отдельно для каждого правильного п-угольника. Теперь для того чтобы нарисовать, скажем, правильный семиугольник со стороной 20, достаточно написать в программе три команды. ПРОЦ правильный многоугольник опусти хвост ПОВТОРИТЬ л РАЗ вперед(= 0) ДЕЛАТЬ переведи в точку(1, 50 — 9.8 1*1/2) 1= 1+ 0.1 КОНЕЦ Условие в цикле ПОКА гарантирует, что камень находится над землей, а шаг мы уменьшили. Новый график гораздо лучше предыдущего описывает и математический закон и действительное положение вещей (рис. 10). 9. Процедуры рисования графиков При рисовании графиков зависимостей, как бы эти зависимости ни были заданы, нам приходится выбирать размер шага по каждой из осей координат. Вид графика зависит от выбранного размера. При уменьшении шага график растягивается, а при увеличении — сжимается. Нам хотелось бы, чтобы график помещался на экране Чертежника и на нем можно было разобрать все важные детали взаимозависимости. Очень часто оказывается, что с первой попытки не удается нарисовать понятный и полезный график. Приходится перепробовать несколько вариантов, прежде чем удастся подобрать подходящие шаги по осям. Это означает, что шаг по оси должен задаваться в одном месте, чтобы достаточно было сделать одно изменение. Другими словами, длина шага должна быть переменной. Некоторые другие данные тоже хочется сделать переменными, чтобы при их изменении не надо было искать по всей программе, где нужно внести поправки, рискуя при этом что-нибудь забыть. Так, если камень был брошен не с высоты 50 метров, а с высоты 75 метров, то в предыдущей программе надо поменять три строки: • условие в цикле ПОКА, где проверяется, долетел ли камень до земли; Переменные в графических Исполнителях 31 и рисование графиков • команда переведи в точку, в которой вычисляется у — координата очередной точки графика; • самая первая команда, которая переводит перо Чертежника в начальное положение. Это означает, что в изменении нуждается половина строк программы! Напомним, что две строки ПОКА и КОНЕЦ мы считаем за одну. А если в программе тысяча строк? Введем переменную xs для обозначения шага по оси х и переменную ys для обозначения шага по оси у. Переменная td будет обозначать приращение времени, а переменная h — начальную высоту камня. Вот как выглядит результат превращения программы в удобную процедуру: ПРОЦ график падения НАЧАЛО подними перо переведи в точку(0, h/ys) опусти перо д = 9.8 t = 0 ПОКА (Л - g*f*f/2 >= О) ДЕЛАТЬ переведи в T04Ky(f/xs, (Л - g*f*#/2)/ys) t=t + td КОНЕЦ подними перо переведи в точку(0, О) КОНЕЦ Мы сделали еще одно улучшение — обозначили через g ускоре- р ние свободного падения 9.8 м/с . Это значение не меняется от того, как мы кидаем камень. Но представьте себе, что нам понадобится изменить единицы измерения, например выразить длины не в метрах, а в сантиметрах; тогда ускорение свободного падения будет выражаться в сантиметрах на секунду в квадрате. В приведенной процедуре для внесения этого изменения достаточно исправить одну строчку, написав д = 980 вместо д = 9.8. Теперь мы можем попробовать, как наша процедура будет работать при различных значениях переменных. На рисунке 11 приведены соответствующие графики. Видно, что значения xs = 0.25 с, ys = 2.5 м, td = 0.01 с дают наиболее приемлемый результат: график на рисунке 11, а занимает все отведенное для него место, по нему легко найти высоту положения камня в различные моменты времени, изображенная на нем кривая, как и должно быть, гладкая. На рисунке 11, б график слишком узок — шаг xs выбран слишком большим. По такому графику невозможно определить, например, высоту положения камня в момент времени t = 0.5 с. График 32 ГЛАВА 2 hk a) xs = 0.25 c, ys = 2.5 M, fd = 0.01 c I 6) xs = 1 c, ys = 2.5 M, W = 0.01 c ys = 2.5 M, td - 0.75 c Рис. 1 1 на рисунке 11, в чересчур изломан. Это произошло из-за того, что шаг изменения времени ts выбран слишком большим. На протяжении этого шага график может очень сильно отклоняться от реального поведения функции. Обратите внимание, что по той же причине график на рисунке 11, в обрывается, не достигнув уровня земли. Задача 2.11 Напишите процедуры рисования графиков следующих функций: а) на отрезке от 0 до той точки, в которой значение функции впервые превысит 5; б) (л: - 1)^ на отрезке от О до той точки, в которой значение функции впервые превысит 3; в) на отрезке от 0 до той точки, в которой значение функции впервые станет меньше 0.1. Предложите Чертежнику выполнить эти процедуры при различных значениях переменных и подберите наиболее подходящие величины шагов по осям. Начало отрезка, на котором рисуется график функции, также удобно сделать переменной: не всегда изображение будет начинаться Переменные в графических Исполнителях 33 и рисование графиков с нуля. Кроме того, часто в качестве условия окончания рисования графика выбирается второй конец отрезка. Задача 2.12 Перепишите процедуры рисования графиков из вашего решения предыдущей задачи таким образом, чтобы они использовали переменные, в которых хранятся концы отрезка по оси х, а ограничения на значения функции отсутствовали. Нет сомнения, что при написании этих процедур вы обнаружите, что они отличаются только в одном месте — там, где вставляется формула для вычисления значения функции. Вот если бы эта формула также могла быть значением переменной! Тогда для рисования всех и всяческих функций, заданных формулой, годилась бы одна и та же процедура! Наш язык — учебный, и в нем таких возможностей нет. Но вы уже увидели, что простая процедура рисования графиков универсальна — переписав в ней одну строчку, ее можно настроить на рисование графика любой понравившейся функции. 10. Сложности в рисовании графиков Решение последней задачи предыдущего параграфа для графика функции 1/(х -1-1) может выглядеть приблизительно следующим образом: ПРОЦ график обратной пропорциональности НАЧАЛО X = х1 подними перо переведи в точку(х/ха, (1/(х-н 1))/ys) опусти перо ПОКА X < хг ДЕЛАТЬ X = X + xd переведи в точку(х/ха, (1/(х + 1))/ys) КОНЕЦ подними перо переведи в точку(0, 0) КОНЕЦ Здесь, как и раньше, переменные xs и ys задают величину шага по горизонтальной и вертикальной осям координат, xd — величина приращения переменной х при рисовании графика, х1 и хг — левый и правый концы отрезка, на котором график рисуется. Однако если мы попробуем, например, задать Чертежнику команды 34 ГЛАВА 2 xs = 2 ys = 0.1 xd = 0.1 xl = -4 xr = 5 график обратной пропорциональности то нас ждет разочарование. По нашему замыслу Чертежник должен нарисовать график функции 1/(лг + 1) на отрезке [-4, 5]. Этот график должен выглядеть, как на рисунке 12. Однако на самом деле произойдет следующее. На тридцатом выполнении тела цикла ПОКА значение переменной х станет равным -1 (сначала ее значение равнялось -4, и к ней 30 раз было прибавлено по 0.1). Теперь, когда в команде переведи в точку(х/ха, (1/(х+ 1))/ys) будет вычисляться значение 1/(х+ 1), окажется, что необходимо делить на 0. Такое деление невозможно, поэтому произойдет ОТКАЗ. Подобного рода ситуации встречаются довольно часто — формулы, задающие функции, могут работать не при всех значениях переменных. Если про это вовремя не подумать, то программы рисования графиков могут начать отказывать в самых разных ситуациях. Проще всего исправить положение, обойдя плохие точки. В нашей программе это можно сделать так. Плохой точкой является точка х = -1. При этом значении переменной знаменатель дроби, задающей функцию, обращается в нуль. Стоит исключить и значения, близкие к -1; действительно, если число дг -Ь 1 очень маленькое по абсолютной величине, то число 1/(дг -I- 1) будет очень большим по абсолютной величине и график может уйти далеко за пределы экрана. Поэтому интервал вокруг -1 надо обойти. Рис. 12. Желаемый график функции 1 /(х + 1) на отрезке [-4, 5] Рис. 13. Неудачная попытка нарисовать график функции 1/(х + + 1): прямолинейный отрезок на графике не имеет к функции никакого отношения Переменные в графических Исполнителях 35 и рисование графиков Можно сделать это, например, так: ПРОЦ график обратной пропорциональности НАЧАЛО X = х/ подними перо переведи в точку(х/ха, (1/(х+ 1))/ys) опусти перо ПОКА X < хг ДЕЛАТЬ X = X + xd ЕСЛИ (X + 1 < 0.2) И (X + 1> -0.2) ТО подними перо ПОКА X + 1 < 0.2 ДЕЛАТЬ X = X + xd КОНЕЦ переведи в точку(х/х5, (1/(х+ 1))/ys) опусти перо ИНАЧЕ переведи в точку(х/ха, (1/(х+ 1))/ys) КОНЕЦ КОНЕЦ подними перо переведи в точку(0, 0) КОНЕЦ Здесь мы обходим опасный отрезок [—1.2, -0.8] вокруг точки д: = -1. В результате и получается график, как на рисунке 12. Обратите внимание на то, что перед обходом опасного участка мы подняли перо Чертежника — иначе на графике появился бы лишний отрезок, как на рисунке 13. Этот отрезок соединяет значения функции по разные стороны от опасной точки и не имеет никакого отношения к настоящим значениям функции на отрезке [—1.2, -0.8]. Задача 2.13 Исправьте следующую процедуру таким образом, р чтобы при рисовании графика функции у = 1/х Чертежник обошел опасный отрезок [-0.3, 0.3]. ПРОЦ график обратной квадратичной пропорциональности НАЧАЛО X = х/ подними перо переведи в точку(х/х5, (1/(x*x))/ys) опусти перо ПОКА X < хг ДЕЛАТЬ X = X + xd переведи в точку(х/х5, (1/(x*x))/ys) КОНЕЦ подними перо переведи в точку(0, 0) КОНЕЦ 36 ГЛАВА 2 итоги в этой главе мы использовали переменные при работе с графическими исполнителями. Переменные очень упрощают решение многих задач, позволяя пользоваться одной процедурой для изображения большого числа рисунков. Изменяя значения небольшого числа переменных, мы можем рисовать похожие друг на друга рисунки. Мы познакомились со специальным видом переменных — массивами. Массивы предназначены для хранения большого количества данных одинаковой природы. Такими являются, например, результаты измерений одной величины в течение некоторого времени. Данные, заданные массивом, легко изображать графически. Иногда такое графическое представление подсказывает простую формулу, задающую те же значения. Переменные — самый подходящий инструмент для рисования графиков функций, заданных формулами. Мы узнали, какие сложности могут возникать при рисовании таких графиков и как их можно преодолевать. Глава 3 Работа с массивами Введенные нами в предыдущей главе массивы предназначены для работы с большим количеством однородных элементов. Массивы могут быть очень большими — в тысячи и даже миллионы элементов длиной. Поэтому нужно уметь эффективно работать с ними. Простые примеры такой работы мы и рассмотрим в этой главе. Т!| Среднее и максимум Пусть массив а длины 365 содержит данные о том, сколько автомобилей было произведено заводом ежедневно в течение года. Как сосчитать, сколько всего автомобилей произвел завод за год? Конечно, это несложно. Нужно всего лишь сложить все элементы массива. Для подсчета суммы элементов введем переменную s, в которую будем заносить сумму первых нескольких элементов. Постоянную 365 обозначим через гг, за счет этого нам будет легче менять наш алгоритм, приспосабливая его к другим ситуациям. Программа подсчета количества произведенных автомобилей приведена справа. В конце выполнения этой программы переменная s будет содержать сумму элементов массива о. Этот алгоритм несложно модифицировать для подсчета среднего числа автомобилей, производившихся заводом ежедневно. Для этого нужно только добавить в конце команду п = 365 1 = 0 S = О ПОКА I < п ДЕЛАТЬ i = i + 1 s = s + a[i] КОНЕЦ m = s/n После выполнения программы переменная т будет содержать среднюю дневную производительность завода. Интереснее выглядит подсчет среднего значения элементов массива, длина которого неизвестна. Что это значит? Рассмотрим пример с автомобилями. Пусть для хранения количества ежедневно производимых автомобилей используется массив а, как и выше. Мы предполагаем, что присвоены значения 365 последовательным элементам массива а (в таком случае говорят, что длина массива равна 365), начиная от а[1] и до а[365]. В начальной части массива запи- 38 ГЛАВА 3 саны данные по производству автомобилей, а начиная с некоторого места его элементы равны 0. (Такую запись нужно понимать как отсутствие данных по производству автомобилей за соответствующие дни года.) Мы хотим подсчитать, сколько автомобилей было произведено и каково среднее количество ежедневно производимых автомобилей за ту часть года, о которой есть данные. Признаком, указывающим, что все данные обработаны, будет для нас служить то, что в очередной день завод не произвел ни одного автомобиля, т. е. что очередной элемент массива равен нулю. В предыдущей программе для подсчета среднего числа производимых в день автомобилей общее число произведенных автомобилей делилось на число дней в году. Сейчас же требуется разделить его на количество дней, о которых есть данные по производству автомобилей. Но это количество будет подсчитываться автоматически, поэтому никаких проблем перед нами не возникнет. В результате получается программа, показанная справа. Если использовать такую программу, то в некоторых случаях произойдет ОТКАЗ. Например, пусть год еще не начался и в первый день года ни одного автомобиля не произвели. Тогда в последней команде присваивания нужно вычислить результат деления 0 на 0. По правилам нашего алгоритмического языка в этом случае наступает ОТКАЗ. Еще одна неприятность поджидает нас, если мы захотим использовать эту программу с данными из предыдущей задачи, когда было известно количество произведенных автомобилей за весь год. В этом случае значение переменной i будет увеличиваться до тех пор, пока не станет равным 366. Переменной а[366] не присвоено никакого значения, поэтому результат сравнения a[i -I- 1] <> 0 не определен. По правилам нашего алгоритмического языка в этот момент также произойдет ОТКАЗ. /■= о 5 = 0 ПОКА а[/ 1] О о ДЕЛАТЬ I = / + 1 5 = 5 + а[/] КОНЕЦ т = s/i Задача 3.1 а) Измените программу так, чтобы она правильно работала и тогда, когда количество дней, за которые есть данные, равно 0 или 365. Проверьте, что именно в этих случаях и происходит ОТКАЗ в программе, которая приведена выше. б) В приведенной программе предполагается, что работающий завод ежедневно выпускает какое-то количество автомобилей. Однако на практике бывает так, что в некоторые дни завод может ничего не производить (например, конвейер остановили на ремонт). Что нужно изменить в .заполнении массива айв программе, чтобы дни с нулевым производством не путались с условием окончания содержательной части массива? Работа с массивами 39 Подобно суммарному количеству произведенных автомобилей и их среднему количеству, несложно найти и максимальное количество автомобилей, произведенных в какой-то день года. Сначала решим задачу о выборе максимума из двух чисел. Ее решение очевидно: нужно сравнить числа и выбрать большее из них. Пусть два числа содержатся в переменных а и Ь; их максимум мы занесем в переменную с. На нашем языке программирования эти действия собираются в процедуру I. Теперь решим задачу про максимальное количество автомобилей (обозначим его через max). Решением служит программа 2. 1) ПРОЦ максимум из двух НАЧАЛО С ” 3 ЕСЛИ Ь > с то с = Ь КОНЕЦ КОНЕЦ 2) п = 365 1 = 0 max = О ПОКА / < п ДЕЛАТЬ i=i + ^ ЕСЛИ а[|] > max ТО max = а[/] КОНЕЦ КОНЕЦ Мы сравниваем значение каждого очередного элемента массива с текущим значением переменной max. Если при этом оказывается, что очередной элемент больше значения переменной max, то мы заносим его значение в эту переменную. Если массив а, с которым мы работаем, описывает не результаты работы завода в течение года, а какие-то другие величины (расстояния между городами, производительность различных компьютеров, скорости средств передвижения и т. д.), то программа поиска максимального элемента в нем не изменится: достаточно лишь заменить присваивание п = 365 заданием нужной длины массива. Упражнение Запишите в таблицу значения переменной max после каждого увеличения переменной i на единицу, если массив а выглядит следующим образом: О, 5, 7, 9, 1, 8, 12, 4, 36, 6, 5. Задача 3.2 Задача 3.3 Измените программу таким образом, чтобы в конце ее выполнения, помимо максимального дневного производства автомобилей, в переменной k хранился номер первого дня, когда этот максимум был достигнут. Как нужно изменить программу из предыдущей задачи, чтобы в конце ее выполнения в переменной k хранился номер последнего дня, когда был достигнут максимум производительности? 40 ГЛАВА 3 Задача 3.4 Измените программу поиска максимума так, чтобы она искала максимум в массиве неотрицательных чисел, длина которого неизвестна, а признаком окончания массива служит элемент, равный 0. ^ Эффективность работы с массивами Решая в предыдущем параграфе задачу о поиске максимального элемента в массиве, мы последовательно шли по массиву, изучая каждый его элемент поочередно. Мы сравнивали очередной элемент с текущим значением максимума и делали его новым значением максимума, если он оказывался больше. К уже пройденным элементам мы не возвращались. Это очень важное свойство алгоритма поиска максимума. Дело в том, что массивы могут быть очень длинными. Настолько длинными, что они не помещаются целиком в память компьютера. И тогда уже обработанная часть массива выбрасывается из памяти, и возвращение к ней может занять много времени. Поэтому надо стараться придумывать алгоритмы, которые решают поставленную задачу за один просмотр массива. Такие алгоритмы существуют не для всех задач. Однако если они есть, то они оказываются очень полезными и эффективными. Рассмотрим, например, следующую задачу. Задача 3.5 Найти значение максимальной дневной производительности завода и количество дней в году, когда эта производительность достигалась. На первый взгляд кажется, что нам придется просмотреть массив дважды. Действительно, при первом просмотре мы не можем подсчитывать количество повторений максимума, поскольку мы не знаем, чему этот максимум равен. Поэтому сначала нужно найти максимум элементов в массиве, а затем сравнить его с каждым элементом в массиве, чтобы подсчитать количество таких элементов. Нельзя ли, однако, обойтись без второго просмотра? Оказывается, такая возможность есть. Давайте заведем еще одну переменную days, в которой будем хранить количество дней, когда встречается текущий максимум. Будем придавать этой переменной значение 1 всякий раз, когда текущее значение максимума заменяется другим, и увеличивать ее на 1 при всяком повторении текущего максимума. Такое предложение приводит к программе, показанной справа. п = 365 /• = О max = о days = 1 ПОКА i < п ДЕЛАТЬ i = Л- 1 ЕСЛИ а[/] > max ТО max = а[/]] days = 1 ИНАЧЕ ЕСЛИ a[i] = max ТО days = days + 1 КОНЕЦ КОНЕЦ КОНЕЦ Работа с массивами 41 в конце выполнения программы переменная days будет содержать количество элементов массива а, равных максимальному значению max элементов массива. И мы обошлись всего одним просмотром массива! Упражнение Запишите в таблицу значения переменных max и days после каждого увеличения переменной i на единицу, если массив а выглядит следующим образом: О, 5, 7, 9, 1, 8, 12, 4, 36, 6, 5, О, 5, 7, 9, 1, 8, 12, 4, 36, 6, 5. Вот начало этой таблицы: max 0 5 7 days 1 1 1 Задача 3.6 Напишите алгоритм поиска двух наибольших элементов в массиве из неотрицательных чисел за один просмотр массива. Для простоты можно считать, что все элементы массива попарно различны. ^ Работа с упорядоченными массивами Решим задачу на поиск элемента в массиве. Задача 3.7 Узнать, выпускал ли завод в какой-нибудь из дней 12 автомобилей. п = 365 ;• = О answer = О term =12 ПОКА / < п ДЕЛАТЬ I = / + 1 ЕСЛИ а[|] = term ТО answer = 1 КОНЕЦ КОНЕЦ Эта задача имеет простое решение. Давайте брать элементы массива по очереди и сравнивать их с числом 12. Если какой-то элемент окажется равным 12, то ответ на вопрос — «да», иначе — «нет». Для хранения ответа предусмотрим переменную answer. Значение этой переменной будет равно О, если ответ отрицателен, и 1, если ответ положителен. Сначала мы присвоим переменной answer значение О, поскольку интересующий нас элемент еще не найден (программа справа). Значение переменной answer может измениться при выполнении конструкции повторения только в том случае, когда выполнено условие в конструкции ветвления (седьмая строка программы). Поэтому если в массиве есть элемент, значение которого равно значению переменной term (т. е. 12), то значение переменной answer станет равным 1. А если такого элемента не встретилось, то оно так и останется равным О. Мы получили довольно короткую программу, которая справля- 42 ГЛАВА 3 n = 365 1 = 0 answer = 0 term =12 ПОКА (/ < n) И (answer = 0) ДЕЛАТЬ I = i + 1 ЕСЛИ a[i] = term TO answer = 1 КОНЕЦ КОНЕЦ ется с поставленной задачей за один просмотр массива. Посмотрим, нельзя ли ее улучшить. Ясно, что нет необходимости в просмотре оставшейся части массива, если интересующий нас элемент уже найден. В написанной программе просмотр массива продолжается до конца, даже если искомый элемент стоит в массиве первым. Конечно, такой лишней работы лучше избежать. Этого нетрудно добиться, усложнив условие в конструкции повторения. Вот как выглядит улучшенная программа (см. справа). После обнаружения искомого элемента значение переменной answer равно 1, поэтому составное условие становится ложным (напомним, что ИСТИНА И ЛОЖЬ = ЛОЖЬ) и повторения заканчиваются. Вторая возможность более важна. Вам, наверное, не раз случалось искать книгу в библиотеке, слово в словаре, видеофильм на полке, игрушку в ящике с игрушками. И несомненно, вы убеждались, что гораздо легче найти то, что вы ищете, если среди предметов наведен порядок. На поиск слова в словаре, слова в котором расположены в алфавитном порядке, требуется гораздо меньше времени, чем на поиск игрушки в ящике, куда все игрушки свалены в кучу. А ведь в словаре может быть 100 000 слов — куда больше, чем игрушек в ящике. Но расположение слов по алфавиту — упорядочение — сильно облегчает поиск. С точки зрения алгоритмики это означает, что алгоритмы поиска в упорядоченном массиве гораздо эффективнее алгоритмов поиска в неупорядоченном массиве. Разумеется, поддержание порядка тоже требует усилий. Итак, теперь мы будем решать задачу поиска в упорядоченном массиве. Упорядоченный массив может быть неубывающим, если каждое следующее число в нем не меньше предыдущего, или невозрастающим, если каждое следующее число не больше предыдущего. Задача 3.8 Имеется неубывающий массив чисел а. Количество элементов в массиве известно и равно п. Нужно узнать, встречается ли среди элементов этого массива некоторое заданное число term. Для решения этой задачи годится тот же алгоритм, которым мы решили задачу 3.7. Однако сейчас наша цель — разработать более быстрый алгоритм. Давайте разберем несколько примеров. Во всех этих примерах будем считать, что ищется число 10, т. е. term = 10. Пусть а[1] =11. Все остальные элементы массива не меньше первого. Но 10 < 11, так что среди элементов массива число 10 заведомо не встречается. Работа с массивами 43 Теперь предположим, что а[п] = 9. Все остальные элементы массива не больше последнего. Но 10 > 9, так что среди элементов массива число 10 заведомо не встречается. Мы рассмотрели сравнения с первым и последним элементами массива. Но применить такое сравнение можно и для любого другого элемента. Например, возьмем какой-нибудь элемент массива, скажем, а[100]. Если а[100] = 11, то можно .заключить, что если term и встречается в массиве, то он должен находиться среди первых 99 элементов (элементы массива, которые следуют за сотым, не меньше сотого, значит, все они больше 10). Если а[100] = 9, то первые 100 элементов массива не больше 9. Значит, если term и встречается в массиве, то он должен находиться после 100-го элемента. Если же а(100]= 10, то задача решена и ответ положительный: мы нашли элемент массива, который совпадает с числом term. Из этих примеров следует важный вывод: сравнив число term с элементом массива о[/г], мы либо решили задачу, либо сузили область возможного положения числа term в массиве. Именно в дальнейшем поиске можно отбросить либо часть массива от 1 до k-ro элемента, либо часть массива от k до л-го элемента. Таким образом, каждое сравнение уменьшает область поиска, как это показано на рисунке 14. Это уменьшение может быть значительным, если нам дальше придется искать в меньшем из двух отрезков, и не таким уж существенным, если придется искать в большем. Можно, конечно, надеяться, что нам повезет и что ответ в меньшем отрезке. Можно было бы надеяться и на то, что мы очень быстро, даже с первого раза, найдем член последовательности, равный 10. Мы, однако, попробуем не надеяться на авось и рассмотреть худший случай — когда отрезок с ответом не короче другого отрезка. Какую длину оставшегося отрезка мы можем себе обеспечить? Давайте разберемся. Из отрезка натурального ряда длины t мы выбираем число. Получаем два отрезка, длины которых в сумме дают t — Поэтому хотя бы один из них имеет длину не меньше чем (< — 1)/2. Наша цель — осуществить выбор так, чтобы длина этого отрезка была как можно меньше, т. е. как можно ближе к {t - 1)/2. Будем выбирать длину первого отрезка равной частному от деления < на 2 с остатком. Если t четное, то второй отрезок будет на еди- DDDDDDDDDDDDDDDDDDDDD term < а [/(] DDDDDD нашли term > а [/< ] DDDDDDDDDDDDDD Рис. 14 44 ГЛАВА 3 ницу короче первого. Если t нечетное, то второй отрезок будет такой же длины, как и первый. В любом случае длина любого из отрезков не больше чем t/2. Мы доказали, что на каждом шаге длина отрезка, где мы ищем нужное число, уменьшается по крайней мере в 2 раза. Например, если вначале длина отрезка была 100, то на первом шаге получились отрезки не длиннее 50, на втором шаге — не длиннее 25, на третьем — не длиннее 12, затем не длиннее 6, потом 3 и, наконец, 1. Если дело дойдет до отрезка длины 1, то в нем, конечно, мы сразу найдем нужное нам число, если оно там есть, или узнаем, что его нет. Теперь запишем найденное нами правило выбора элемента массива на алгоритмическом языке. Пусть мы ищем на отрезке массива от номера I до номера г. Число элементов массива в этом отрезке равно (г — i) Ч- 1. Выбираем номер к = / + частное((г - /) + 11 2) Здесь использована операция частное(а, Ь), которая дает частное от деления а на 5 с остатком (подробнее о делении с остатком написано ниже, см. раздел 4.2 главы 3). При таком выборе длина отрезка от / до /г - 1 равна как раз (Аг - 1 - () + 1. Описанный выше алгоритм называется двоичным поиском. Теперь все готово для написания программы двоичного поиска. answer = 0 /= 1 г = п ПОКА (г - О > о ДЕЛАТЬ * = / -ь частное((г - I) + 1, 2) ЕСЛИ term = а[к] ТО answer = 1 1 = к г = к ИНАЧЕ ЕСЛИ term > а[к] ТО / = fc + 1 ИНАЧЕ r = k-t КОНЕЦ КОНЕЦ КОНЕЦ ЕСЛИ term = а[г] ТО answer = 1 КОНЕЦ Последняя проверка производится тогда, когда г > I и единственным возможным решением остался элемент массива а[г]. Работа с массивами 45 в программе двоичного поиска есть несколько тонкостей, которые вы почувствуете, выполнив следующие упражнения. Упражнение Уберем восьмую и девятую строки в программе двоичного поиска (присваивания I = к и г = к). Новая программа иногда будет работать неограниченно долго (как говорят, будет зацикливаться). Приведите пример массива и числа term, для которых новая программа зацикливается. Упражнение Уберем последнюю конструкцию ветвления из программы двоичного поиска. Новая программа будет работать не всегда правильно. Приведите пример массива и числа term, для которых новая программа дает неверный ответ. Упражнение Заменим условие в последней конструкции ветвления из программы двоичного поиска на term = а[/]. При работе новой программы будет иногда наступать ОТКАЗ. Приведите пример массива и числа term, для которых выполнение новой программы заканчивается ОТКАЗом. Упражнение Написанную выше программу можно улучшить, если не делать никаких проверок после того, как term найден среди элементов массива. Напишите такую улучшенную версию программы. Алгоритм двоичного поиска понять труднее, чем алгоритм простого поиска, приведенный в начале этого раздела. Каких преимуществ удается достичь за счет такого усложнения алгоритма? Преимущест во, как вы уже понимаете, состоит в том, что обычно он работает намного быстрее. Простой алгоритм, перебирающий элементы массива один за другим, либо находит ответ, либо сокращает область возможных решений на один элемент за выполнение одной конструкции повторения. Алгоритм двоичного поиска при выполнении одной конструкции повторения либо находит ответ, либо сокращает область возможных решений по крайней мере вдвое. Выше мы уже считали, сколько раз нам нужно разбивать отрезок, если сначала его длина была равна 100. Вот таблица, где мы приводим максимальное число шагов двоичного поиска, включая последний (когда выбирается одно число). Длина массива Максимальное число шагов двоичного поиска 10 4 100 7 1 000 10 10 000 14 100 000 17 1 000 000 20 46 ГЛАВА 3 Комментарий Двоичный поиск и более сложные алгоритмы, лежащие в его основе, постоянно используются в реальных компьютерных вычислениях. Каждый раз, когда вы ищете что-нибудь в Интернете, программа поиска проверяет содержимое миллиардов сайтов. Вы никогда не удивлялись тому, как быстро это происходит? (Миллиард — это ОЧЕНЬ много. Чтобы досчитать до миллиарда, произнося одно слово в секунду, потребуется более тридцати лет.) Такая чудесная скорость достигается благодаря тому, что поиск в Интернете опирается на идею двоичного поиска (и, конечно, на многие другие идеи). Задача 3.9 Предложите основанный на двоичном поиске алгоритм для решения такой задачи: имеется упорядоченный массив чисел, нужно узнать, сколько раз в нем встречается число term. (Числа в упорядоченном массиве могут повторяться, так что ответом может быть любое целое неотрицательное число.) Задача 3.10 Двое играют в такую игру. Полина задумывает семизначное число (номер телефона), а Володя пытается отгадать это число. Он может задавать Полине любые вопросы, на которые можно ответить «да» или «нет», и Полина отвечает честно. За сколько вопросов Володя гарантированно может узнать число? 4^1 Позиционная система счисления Как уже говорилось, переменная — это место для хранения числа (пока мы используем только числовые переменные). В нашем учебном языке программирования для простоты принято правило: значение переменной может быть любым числом, даже очень большим. Память компьютеров устроена иначе. Заранее заданная область памяти (ячейка) может хранить лишь одно число из некоторого конечного набора, скажем, 256 различных чисел, или 65 536 различных чисел, или даже 4 294 967 296 различных чисел. Это, конечно, много. Но чисел бесконечно много, так что записать их все таким способом невозможно, даже если еще больше увеличить размер ячейки, отводимой под переменную. Как же работать с очень большими числами? Для этого нужно уметь «разрезать» числа на куски и хранить куски в разных ячейках памяти, которые обычно организованы в массив. Способов «разрезания» — систем счисления — много. Мы познакомимся с самой важной и распространенной, и называется она позиционной системой счисления. Робота с массивами 47 4.1. Идея позиционной системы счисления Обычно мы записываем числа в десятичной системе счисления. Давайте вспомним, что это такое. Запись числа в десятичной системе счисления получается как результат пересчета предметов следующим способом. Прежде всего нужно научиться считать до 9: 1, 2, 3, 4, 5, 6, 7, 8, 9. Потом добавить к этим цифрам еще одну цифру: 0. Теперь можно считать предметы не только единицами, но и десятками, сотнями, тысячами и т. д. Например, число 1007 — это 7 единиц, нуль десятков, нуль сотен и 1 тысяча. Из этого примера видно, зачем нужна цифра 0: она обозначает отсутствие предметов (или десятков предметов, сотен и т. д.). В десятичной системе количество предметов, которое обозначает цифра, зависит от места (или позиции), на котором она стоит. Именно поэтому системы счисления, похожие на десятичную, называются позиционными. Скорее всего, число 10 используется в нашей системе счисления только потому, что у людей на руках по 10 пальцев. Его можно заменить на любое другое натуральное число, большее 1. При этом правила выполнения арифметических операций над числами изменяются незначительно. Например, давайте использовать вместо числа 10 число 12. Мы должны обозначить числа от 0 до 11 какими-то символами— «две-надцатеричными цифрами». Пусть это будут о, 1, 2, 3, 4, 5, 6, 7, 8, 9, а, Ь. Мы используем для чисел от 0 до 9 их обычные обозначения, число 10 обозначаем буквой а, число 11 обозначаем буквой Ь. Затем мы будем считать предметы единицами, дюжинами (дюжина — число 12), гроссами (гросс — дюжина дюжин, число 144) и т. д. В результате мы получим двенадцатеричную систему счисления. Одно и то же число в десятичной и двенадцатеричной системах счисления будет записываться по-разному. Попробуем записать в двенадцатеричной системе число 1007. Проще всего начать с конца. Как определить последнюю цифру двенадцатеричной записи? Это остаток от деления числа 1007 на 12, поскольку дальше мы считаем целыми дюжинами. А остальные двенадцатеричные цифры будут двенадцатеричной записью частного от деления 1007 на 12, т. е. они будут записывать то целое число дюжин, которое можно набрать из 1007. Выполнив вычисления, получим 1007 = = 83 ■ 12 + 11 ...Ь 83 = = 6 • 12 -Ь 11 ...ьь 6 = = 0 • 12 -Ь 6 бЬЬ 48 ГЛАВА 3 Итак, 1007io = 6fe&i2* В этой последней записи слева и справа от знака равенства стоят записи одного и того же числа, но в разных системах счисления. Чтобы не запутаться, мы указали справа внизу основание системы счисления. Для десятичной системы это число 10, для двенадцатеричной — 12. При записи числа в десятичной системе основание обычно не указывают, чтобы не загромождать запись. Упражнение Сложите в двенадцатеричной системе числа 15Ь^2 и а6112- Запишите ответ в десятичной системе счисления. Другой пример позиционной системы — шестеричная. Для записи числа в ней нужно всего шесть цифр: о, 1, 2, 3, 4, 5, обозначающих первые пять чисел и нуль. Записывать остальные числа в шестеричной системе нужно так же, как в разобранном выше примере двенадцатеричной системы счисления. Например, число 6 будет записано как lOg, число 36 — как lOOg. Упражнение Запишите в шестеричной системе число: а) 100; б) 216; в) 1007. Интересно отметить, что шестеричная система тоже удобна для счета на пальцах. В самом деле, на руке у человека 5 пальцев. Поэтому одной рукой можно показать 6 разных чисел: от 1 до 5 (пальцами), сжатый кулак будет изображать 0. Двумя руками можно показать любое двузначное шестеричное число, т. е. число, которое записывается в шестеричной системе двумя цифрами. Задача 3.11 Научитесь показывать на пальцах числа от 0 до 35. Позиционная система счисления с основанием 2 называется двоичной. Эта система простейшая из всех позиционных систем. В ней используются всего две цифры: 0 и 1. В двоичной системе счет идет единицами, парами, четверками и т. д. Задача 3.12 Запишите число 1007 в двоичной системе счисления. Прежде всего ясно, чему равна последняя цифра в двоичной записи числа 1007. Как мы уже говорили выше, это остаток от деления числа на 2. Число 1007 нечетное, поэтому 1007 = ...I2. На месте точек стоит двоичная запись числа (1007 - 1)/2 = 503. Число 503 нечетное, поэтому 1007 = ...11а. Работа с массивами 49 Последнее равенство как раз означает, что двоичная запись числа 1007 получается приписыванием справа единицы к двоичной записи числа 503. Дальше нам нужно найти двоичную запись количества полных четверок в числе 1007, которое равно (503 - 1)/2 = 251. Продолжая вычисления, получаем полную двоичную запись числа 1007. Все эти вычисления можно записать вместе следующим образом (сравните с разложением в двенадцатеричной системе): 1007 = 503 = 251 = 125 = 62 = 31 = 15 = 7 = 503 251 125 62 • 31 • 15 • 7 3 3 = 1 1 = 0 2 -Ь 1 2 -Ь 1 2 -ь 1 • 2 -Ь 1 ■ 2 -Ь о ■2+1 2 + 1 2 -I- 1 2 + 1 2 -h 1 ...1 ...11 ...111 ...1111 ...01111 ...101111 ...1101111 ...11101111 ...111101111 1111101111 Итак, мы нашли двоичную запись числа 1007. Она, по сути, является сокращенной записью равенства 1007 = 1 • 2® + 1 • 2® -Ы • 2^ -Ы • 2® -h 1 • 2^ -I- о • 2^ -Ь -I- 1 • 2^-t- 1 • 2^-I- 1 • 2^ + 1 • 2". А процесс поиска двоичной записи дает выражение 1007 = 2(2(2(2(2(2(2(2(2 ■ 1 -Ы) -Ы) -И) -Ы) -h 0) -Ы) -Ы) -Ы) -Ы. Упражнение Найдите двоичную запись числа 2015. Подсказка 2015 = 2 • 1007 •+• 1. 4.2. Математика позиционной системы счисления Из решений предыдущих задач было ясно, какая математика нужна для работы в позиционной системе счисления. Главное, что для нее требуется, — это деление с остатком и возведение в степень. Напомним коротко, что это такое. Результат нескольких умножений числа на себя называется степенью этого числа. Количество сомножителей записывается в показатель степени. Например: 22 = 2 • 2 = 4; 3^ = 3 • 3 = 9; 3^ = 3 • 3 • 3 = 27; 2^ = 2 • 2 • 2 • 2 • 2 = 32. Ясно, что при умножении степеней переменных показатели степеней складываются: 2^ • 2^ = (2 • 2) • (2 • 2 • 2) = 2 • 2 • 2 • 2 • 2 = 2^ = 2^ ^ 50 ГЛАВА 3 Поэтому мы должны положить = 1 для любого числа п (умножение на 1 не меняет числа и прибавление О не меняет числа). Целые числа можно делить с остатком. Например, при делении 5 на 3 получается частное 1 и остаток 2: 5 = 1 • 3 + 2. Точное определение деления с остатком: частное от деления целого числа а на целое число Ь с остатком равно такому числу q, что разность а — qb неотрицательна, но меньше Ь. Остаток от деления а на Ь равен а — qb. По этому правилу можно делить и отрицательные числа. Упражнение Проверьте согласно определению, что частное от деления -12 на 5 равно -3, а остаток равен 3. В нашем алгоритмическом языке имеются операции вычисления частного и остатка при делении с остатком. Выражение частное(<числовое выражение>, <числовое выражение>) дает значение частного от деления значения первого выражения на второе с остатком, а выражение остаток(<числовое выражение>, <числовое выражение>) дает значение остатка. Приведем примеры применения таких операций: Выражение Значение выражения частное(5, 3) 1 остаток(5, 3) 2 частное(-11, -5) 3 остаток(-11, -5) 4 Упражнение Проверьте, что в результате выполнения команд с = частное(а, Ь) d = остаток(а, Ь) а = с*Ь + d значение переменной а не изменится. 4.3. Алгоритмика позиционной системы счисления Начнем с алгоритма нахождения двоичных цифр целого положительного числа. Вернемся к решению задачи 3.12 и посмотрим на числа, которые в ней получались. Каждое следующее число получается из пре- Работа с массивами 51 дыдущего по правилу: если число нечетное (очередная двоичная цифра равна 1), то из него нужно вычесть 1 и разделить на 2, а если число четное (очередная двоичная цифра равна О), то его нужно разделить на 2. Почти такой алгоритм встречался в Алгоритми-ке — 6. Там рассматривался исполнитель Раздвоитель, умеющий делить на 2 и вычитать 1. Для Раздвоителя был составлен эффективный алгоритм получения нуля из заданного натурального числа: если число ненулевое и четное, то его нужно делить на 2, а если нечетное, то вычитать из него 1. Точно так же мы действовали выше, находя двоичную запись числа. Поэтому Раздвоитель на самом деле находит двоичные цифры числа. Обратите также внимание на то, что при этом способе вычисления двоичные цифры появляются в обратном порядке относительно привычного нам порядка записи цифр, как если бы мы читали запись числа справа налево. Чтобы увидеть это наглядно, составим процедуру, которая печатает двоичные цифры по мере их появления. Пусть в переменной п хранится значение числа, двоичную запись которого мы хотим получить. ПРОЦ двоичная запись наоборот НАЧАЛО ПОКА НЕ (л = 0) ДЕЛАТЬ печатьподряд(остаток(л, 2)) л = частное(л, 2) КОНЕЦ КОНЕЦ В этой процедуре встречается команда печатьподряд. Это команда для особого Исполнителя Вывод. Вывод занимается тем, что печатает указанные ему данные на устройстве вывода. Два самых популярных устройства вывода — принтер и монитор. Выполнение команды печатьподряд приводит к тому, что указанные в ней данные печатаются без пробелов между последовательными числами. Упражнение Проверьте, что при л = 138 вызов процедуры двоичная запись наоборот приведет к тому, что будет напечатано 01010001, тогда как двоичная запись числа 138 имеет вид 10001010. Записывать цифры числа справа налево неудобно. Чтобы напечатать двоичную запись числа в привычном виде, нужно поступить немного иначе. Вместо печати каждой вновь найденной двоичной цифры будем записывать ее в массив. После того как найдены все двоичные цифры числа, их можно уже напечатать в любом порядке. Запишем тот же алгоритм Раздвоителя в виде процедуры двоичные цифры, которая находит двоичную запись числа п и сохраняет ее для дальнейшего использования. Массив d предназначен для хране- 52 ГЛАВА 3 ния двоичных цифр числа п, а в переменной I мы будем хранить количество двоичных цифр в двоичной записи. ПРОЦ двоичные цифры НАЧАЛО 1 = 0 ПОКА НЕ (л = О) ДЕЛАТЬ d[/] = остаток(л, 2) л = частное(л, 2) / = /+ 1 КОНЕЦ КОНЕЦ Самая младшая цифра числа записывается в d[0]. Упражнение Составьте таблицу состояний переменных при вызове процедуры двоичные цифры, если п = 138. Упражнение При л = 17 после вызова процедуры двоичные цифры значение переменной I оказывается равным 5. Какое значение имеет переменная d[5]? Задача 3.13 Напишите процедуру запись числа, результатом работы которой является массив цифр числа п в системе счисления с основанием g. Чтобы не придумывать символы для цифр в каждой системе счисления, записывайте в массив числа от О до я “ 1- Теперь рассмотрим обратную задачу: по записи числа в позиционной системе найти это число. Алгоритм решения этой задачи состоит в том, чтобы умножать цифры записи числа на последовательные степени основания системы счисления. Давайте составим процедуру значение числа. Эта процедура пользуется следующими переменными — массив d, длина которого записана в переменной I, и основание системы счисления g. Так же как и в задаче 3.13, мы предполагаем, что каждый элемент массива d является числом от О до g — 1. Процедура (см. справа) состоит из двух циклов: вначале вычисляются степени числа g, а затем число, которое мы хотим подсчитать. Это число будет записано в переменную п. В .этой процедуре используется вспомогательный массив р, элементы которого хранят последовательные степени числа g — основания системы счисления. ПРОЦ значение числа р[0] = 1 У = 1 ПОКА у < / ДЕЛАТЬ рШ = д*р[/- 1] у = у + 1 КОНЕЦ п = d[0] к = 1 ПОКА к < I ДЕЛАТЬ п = п + d[kyp[k] fc = Л + 1 КОНЕЦ КОНЕЦ Работа с массивами 53 Убедитесь, что при проверке условия в первом цикле ПОКА значение переменной j равно количеству вычисленных к этому моменту степеней основания g. Упражнение Если предыдущее упражнение вызвало у вас затруднения, то рассмотрите вызов процедуры значение числа при g = 10, если элементы массива d длины Z = 3 равны: d[0] = 2 d[l] = 4 d[2] = 1 Составьте таблицу состояний переменных при этом вызове. Проверьте выполнение условия, сформулированного в предыдущем упражнении. Чему будет равно вычисленное значение п? Комментарий Процедура значение числа не лучший способ вычислять значение числа по его g-ичной записи. В главе 5 мы рассмотрим другой, более эффективный способ. Вы можете и сами его придумать, если поймете, как привлечь к работе над позиционной системой не только Раздвоителя, но и Удвоителя. ИТОГИ в этой главе мы разобрали основные способы работы с массивами и научились находить: сумму элементов массива, максимальный и средний элементы массива за один проход по массиву; элемент в упорядоченном массиве двоичным поиском; запись числа в позиционной системе; значение числа по его записи в позиционной системе. Мы поняли, что поиск в упорядоченном массиве может быть гораздо эффективнее поиска в неупорядоченном массиве. Глава 4 Угадай алгоритм! L 5 i 1 До сих пор мы решали задачи, в которых нужно было построить алгоритм, выполняющий указанные действия: «нарисовать лестницу», «найти наибольший элемент в массиве» и т. п. В этой главе мы рассмотрим задачи другого типа. В них мы можем наблюдать последовательность действий Исполнителя на некоторых входах. Нужно построить алгоритм, который на этих входах совершает именно такую последовательность действий. Исполнитель в данном случае — это необязательно компьютерная программа, может быть, мы наблюдаем поведение живого существа, сложного механизма, компании на бирже или противника в военных действиях. Мы можем ставить эксперименты, т. е. запускать программу на некоторых начальных данных и наблюдать поведение Исполнителя. Одно и то же поведение Исполнителя может обеспечиваться различными программами. Поэтому у задач на поиск программы с заданным поведением в экспериментах много решений. Разные решения можно сравнивать между собой. Лучшим мы будем считать более простое, т. е. более короткое, решение. Короткие решения для задач на поиск программы с заданным поведением можно найти, если обнаружить закономерности в поведении программы, выполняющиеся во всех проведенных экспериментах. Изучаем Робота Начнем с того, что покажем на примере, в чем состоит задача поиска программы с заданным поведением в экспериментах. Вот задача, похожая на те, которые мы решали в Алгоритми-ке — 6. Задача 4.1 Поле Робота — прямоугольник, огороженный стенами (пример на рисунке 15, а). Робот находится в левом нижнем углу. Напишите программу для Робота, после исполнения которой он окажется в правом верхнем углу. Это очень простая задача. Добиться поставленной цели можно многими способами. Вот два решения (программы 1 и 2): Угадай алгоритм! 55 1) 2) ПОКА справа свободно ДЕЛАТЬ ПОКА сверху свободно ДЕЛАТЬ вправо вверх КОНЕЦ КОНЕЦ ПОКА сверху свободно ДЕЛАТЬ ПОКА справа свободно ДЕЛАТЬ вверх вправо КОНЕЦ КОНЕЦ Движение Робота при выполнении программы 1 изображено на рисунке 15, б, а движение Робота при выполнении программы 2 — на рисунке 15, в. А теперь будем решать такую задачу: построить программу для Робота, которая обеспечивает поведение, изображенное на рисунке 15, б. Программа 1 является решением этой новой задачи, а программа 2 нет: хотя конечное положение Робота такое же, как на рисунке 15, б, маршрут движения отличается от заданного. Мы будем называть поведение Исполнителя при заданных начальных данных экспериментом и говорить, что программа согласована с экспериментом, если она обеспечивает указанное в эксперименте поведение Исполнителя. Задачи, которые мы рассматриваем в этой главе, состоят в том, чтобы найти программу, согласованную с заданными экспериментами. Результаты нескольких таких экспериментов изображены на рисунке 16. Упражнение Проверьте, что программа 1 согласована с экспериментами, изображенными на рисунке 16. Рис. 15 а) б) в) 56 ГЛАВА 4 Рис. 16 а) б) в) Задача 4.2 Напишите две другие программы, которые согласованы с экспериментами на рисунках 15, б и 16. Какая из трех программ самая короткая? 1.1. Память Робота Чем больше экспериментов, тем труднее найти программу, которая с ними всеми согласована. Давайте добавим к экспериментам на рисунках 15, б и 16 эксперименты, изображенные на рисунке 17. 4 1 1 1 1 1 1 1 1 1 1 1 ~f“ 1 1 Рис. 17 а) б) в) Угадай алгоритм! 57 Будем искать программу, которая согласована со всеми этими экспериментами. Прежде всего проверим, является ли решением этой задачи программа 1. Ответ «нет*. Это видно из эксперимента на рисунке 17, а. Путь Робота, исполняющего программу 1, изображен на этом рисунке штриховой линией. Вместо этого Робот остается на месте. Упражнение Проверьте, что поведение Робота, изображенное на рисунке 17, б, в, отличается от поведения программы 1. Сравнивая все эксперименты, можно подметить простую закономерность; Робот идет вверх, если он сделал по горизонтали нечетное число шагов (1,3 или 5 в проведенных экспериментах). Если же число шагов по горизонтали четное, как на рисунке 17, Робот вверх не идет, а останавливается. Чтобы обеспечить такое поведение, программа для Робота должна как-то «запоминать* четность количества шагов по горизонтали. Как устроить память для Робота? Проще всего воспользоваться переменными. Предположим, что в программе Робота есть переменная л, в которую записывается четность числа сделанных по горизонтали шагов. Вместо «четное» (Ч) будем писать О, вместо «нечетное* (Н) будем писать 1. В начале работы программы эта переменная должна быть равна О, после первого шага — 1, затем снова О и т. д. Изменить программу I, добавив к ней такую память четности, очень просто. Получим программу 3: 3) п = О ПОКА справа свободно ДЕЛАТЬ вправо п = 1 - п КОНЕЦ ЕСЛИ п = 1 ТО ПОКА сверху свободно ДЕЛАТЬ вверх КОНЕЦ КОНЕЦ Упражнение Проверьте, что поведение Робота при выполнении программы 3 согласуется с экспериментами на рисунках 15, б, 16 и 17. Задача 4.3 Мы использовали для организации памяти Робота переменные. Это самый простой и естественный способ. Но в случае программы 3 можно было бы обойтись без переменных, воспользовавшись вместо этого рекурсией. Напишите программу без переменных, которая всегда ведет себя так же, как программа 3. 58 ГЛАВА 4 1.2. Изощренный Робот Теперь решим более трудную задачу, добавив к заданным экспериментам те, которые изображены на рисунке 18. Наша цель — найти программу, которая ведет себя предписанным образом во всех 13 случаях. Можно заметить, что во всех рассмотренных случаях Робот движется прямо, пока не встретит препятствие. После этого он иногда поворачивает налево, а иногда останавливается. Как сформулировать правило, которое различает эти случаи? а) б) в) Рис. 18 г) Д) е) Угадай алгоритм! 59 Сравнивая рисунки а, б и в—е (рис. 18), можно заподозрить, что поведение Робота зависит от четности числа сделанных ходов. Чтобы понять правило, которым руководствуется Робот, попробуем собрать результаты экспериментов в таблицу. В первом столбце будем отмечать четность числа сделанных к моменту встречи с препятствием ходов. В остальных столбцах таблицы буквой Л пометим те случаи, когда Робот идет влево, а буквой О — те случаи, когда Робот останавливается. Таблица получается такой: Четность числа ходов \Ъб 16а 166 16в 17а 176 17в 18а 186 18в 18г 18^ 18е ч 0 0 0 0 ч Л л л Л н л л л л Л л л л л н о о о о о О О о О Из таблицы видно, что одной четностью числа сделанных ходов обойтись не удается: Робот может и остановиться, и повернуть налево как после четного, так и после нечетного числа ходов. Тем не менее эта таблица подсказывает, что четность имеет значение. Нужно учесть еще что-то. Мы пока учитывали только четность ходов. Можно дополнительно привлечь четность числа препятствий, которые уже встретились Роботу. Чтобы проверить эту гипотезу, расширим таблицу. Добавим в нее столбец, в котором будем отмечать четность числа препятствий, которые уже повстречал Робот. При встрече с первым препятствием число предыдущих препятствий равно нулю, т. е. оно четно, далее четность меняется при прохождении каждого препятствия. Получаем таблицу: Четность 156 16а 166 16в 17а 176 17в 18а 186 18в 18г 185 18с се п 4 о « п 5 о 3* Е- ое в; is Ч П S 3* Н о ч н О О О О ч н л л л л н ч л Л л л Л л л л л н н о О О о о О О О О Из этой таблицы видно, что Роботу достаточно помнить четность числа сделанных ходов и четность числа встреченных препятствий. 60 ГЛАВА 4 Каждому возможному состоянию памяти Робота (первые два столбца таблицы) отвечает ровно одно возможное действие: поворот налево или остановка. Значит, можно написать программу, которая ведет себя по такому правилу. Прежде чем писать программу, полезно сформулировать это правило как можно проще. Вот одна из возможных его формулировок: «если общее количество сделанных поворотов и сделанных ходов нечетно, то поверни налево и иди до встречи с препятствием, в противном случае остановись». Упражнение Может ли программа, работающая по такому правилу, зациклиться (никогда не остановиться)? Если да, приведите пример поля, на котором происходит зацикливание. Чтобы написать программу, работающую по сформулированному правилу, нужно справиться еще с одной трудностью. Правило требует от Робота поворачивать налево. Но у Робота нет такой команды! Справиться с этой трудностью можно различными способами. Вот один из них, использующий рекурсию: ПРОЦ иди вправо КОНЕЦ ПРОЦ иди вверх КОНЕЦ ПРОЦ иди влево КОНЕЦ ПРОЦ иди вниз КОНЕЦ п = О иди вправо Эта программа использует четыре похожих друг на друга процедуры, тексты которых мы заменили многоточиями. Переменная п содержит значение четности общего количества поворотов и шагов. Она будет принимать значения О и 1. Значение равно О, если это общее количество четно, и равно 1, если оно нечетно. Приведем текст процедуры иди вправо (см. справа). Процедура иди вправо вызывает процедуру иди вверх и т. д. ПРОЦ иди вправо НАЧАТЬ ПОКА справа свободно ДЕЛАТЬ вправо п = 1 - п КОНЕЦ ЕСЛИ л = 1 ТО /1 = 1 - п иди вверх КОНЕЦ КОНЕЦ Угадай алгоритм! 61 Задача 4.4 Напишите тексты остальных процедур из приведенной выше программы. Проверьте, что получившаяся в результате программа работает так, как показано на рисунке 22. 1.3. Отступление: можно ли понять работу программы до конца? Вопрос, вынесенный в заголовок раздела, не математический, но очень интересный. Как обычно и бывает с такими вопросами, на него нельзя дать категоричный ответ «да» или «нет». Давайте посмотрим с этой точки зрения на решенные в предыдущих разделах задачи. В каждой из них было задано некоторое множество экспериментов и нужно было найти программу, которая была бы согласована со всеми указанными экспериментами. От задачи к задаче множество указанных экспериментов расширялось: мы добавляли новые эксперименты и ничего не выбрасывали. Но даже в последней задаче, где нужно было обеспечить согласование с 13 экспериментами, нам удалось придумать довольно простое правило поведения Робота. Мы работаем с программой для Робота, текст которой нам недоступен. Все, что мы можем, — это задать окружающую среду: поместить Робота на поле, расставить стены, закрасить некоторые клетки. После этого мы можем запустить программу Робота на исполнение и посмотреть на результат. Проведя некоторое количество таких экспериментов (например, тех, результаты которых приведены на рисунках 15, б и 16—18), мы можем сформулировать гипотезу о поведении программы: правила, по которым ведет себя Робот. Эти правила должны быть соблюдены во всех рассмотренных случаях. Однако возможных вариантов начальных условий для работы программы бесконечно много. Поэтому мы никогда не сможем быть уверены, что наша гипотеза верна. Это не более чем правдоподобная догадка. Тем не менее до тех пор пока все эксперименты дают результаты, согласованные с нашей гипотезой, мы можем считать, что «понимаем» работу программы Робота. Можно сказать и иначе: мы построили модель поведения Робота. Похожим образом обстоят дела в физике и других науках, изучающих окружающую нас природу. В описанной выше ситуации важны такие два вопроса: 1. Можно ли придумать более простое объяснение имеющимся экспериментам? 2. Какие еще эксперименты можно поставить, чтобы проверить имеющуюся гипотезу? Для Рис. 19. Шахматная раскраска помогает понять Робота 62 ГЛАВА 4 того чтобы правильно отвечать на поставленные выше вопросы, одних знаний недостаточно. Нужны и такие качества, как фантазия, здравый смысл, наблюдательность, умение формулировать простые правила. Вот пример. Когда мы искали правило поведения Робота в предыдущем разделе, нужно было подсчитывать четность числа ходов. Можно подумать над тем, как сделать подсчет четности числа ходов наглядным. Тут помогает шахматная раскраска. На рисунке 19 мы перерисовали поле с рисунка 18, е, раскрасив поля доски. Теперь придуманное нами правило поведения Робота становится более наглядным; Робот останавливается, когда встречает новое препятствие на поле такого же цвета, что и предыдущее. В противном случае он поворачивает налево. Упражнение Проверьте, что это правило всегда приводит к тому же поведению, что и правило, которое мы нашли в предыдущем разделе. Комментарий Конечно, подход к программам как к законам природы не всегда оправдан. Программы пишут люди, иногда у этих людей есть чувство юмора. Например, программа может вести себя как текстовый редактор. Вы можете долгие годы изучать эту программу и работать с ней, ни разу не столкнувшись с тем, что она делает не только то, что обещано, т. е. обработку текстов. Но в один прекрасный момент вы (случайно или по подсказке друзей) набираете короткую строчку в этой программе, и она неожиданно заполняет 257 страниц фразой «съешь еще этих мягких французских булочек». Или, нажав некоторую комбинацию клавиш, вы превращаете эту программу в игру Flight Simulator. ^Генераторы числовых последовательностей^ = _ _ _ _ ^ Вот еще одна задача. Задача 4.5 Напишите программу, которая печатает последовательность чисел О, 3, 8, 15, 24, 35, 48, 63, 80. Разумеется, у этой задачи много решений. Одно из них — генератор А. В этой программе мы просто заполняем массив теми значениями, которые надо напечатать, а затем печатаем их по порядку. Для печати мы пользуемся командой печать Исполнителя Вывод. Угадай алгоритм! ^3 Генератор А: (Этот Исполнитель нам уже встречался в главе 3. Там он печатал двоичную запись числа.) Мы назвали программу генерато ром, поскольку она создает, т. е. генериру ет, числовую последовательность. Предложенный нами подход к решению годится для любой числовой последовательности из конечного числа членов: мы просто заносим все нужные нам числа в массив, а затем печатаем их по порядку. У него, однако, есть и недостаток: если последовательность очень длинная, то нам придется написать много команд присваивания элементов массива. Иногда, впрочем, удается придумать простое правило, по которому образована последовательность, и тогда программу можно значительно сократить. Упражнение Проверьте, что для всех элементов a(i] массива а с / > О в предыдущем решении выполняется равенство а[| + 1 ] = а[|] + 2/ + 1. Например, а[8] = 63 = 48 + 2*7 + 1 = а[7] + 2*7 + 1. а[1] = 0 а[2] = 3 а[3] = 8 а[4]= 15 а[5] = 24 а[6] = 35 а [7] = 48 а[8]= 63 а[9] = 80 ;■= 1 ПОКА /< 10 ДЕЛАТЬ печать(а[|]) /■ = I + 1 КОНЕЦ Генератор Б: /•= 1 а = о п = 9 ПОКА / < л ДЕЛАТЬ печать а а = а + 2*/ + 1 1 = 1+1 КОНЕЦ Угаданное нами правило позволяет написать новый генератор для нашей последовательности (см. справа). Эта программа заметно короче генератора А. Если бы генератор должен был напечатать последовательность из 100 или 1000 членов, образованную по тому же правилу, то разница в длине была бы еще разительнее: каждый дополнительный член последовательности добавляет одну строчку в генератор А, а длина генератора Б не меняется. При удлинении последовательности в этом генераторе надо поменять лишь строчку, в которой присваивается значение переменной п. Кроме того, генератор Б не использует массива и поэтому занимает гораздо меньше памяти компьютера. Есть и другой способ описать интересующую нас последовательность. Упражнение Проверьте, что для всех элементов a[i] массива а выполняется равенство а[/] = /*/ - 1. Например, а[8] = 63 = 8*8 - 1. 64 ГЛАВА 4 /= 1 n = 9 ПОКА i <= n ДЕЛАТЬ печать (/*/ - 1) #■ = / + 1 КОНЕЦ Другими словами, мы можем задать Генератор В: элементы нашей последовательности простой формулой, которая вычисляет значение элемента по его номеру. Вот к какой программе приводит это правило (см. справа). Генератор В — самый короткий из всех трех. Увеличив значение, присваиваемое переменной л, в обоих генераторах Б и В, мы увеличим длины генерируемых ими последовательностей. Нетрудно проверить, что десятые члены этих двух последовательностей совпадают. Действительно, генератор Б должен напечатать результат прибавления к девятому члену последовательности, т. е. к числу 80, значения 2 • 9 -Ь 1; получится 99. А генера- р тор в должен напечатать 10 - 1, т. е. тоже 99. Докажем, что и все члены в них совпадают. Это доказательство похоже на доказательство совпадения двух последовательностей, подсчитывающих число ходов в оптимальном алгоритме переноса Ханойских башен из Алгоритмики — 6. Пусть оба генератора Б и В сгенерировали один и тот же элемент а[/]; этот элемент равен - 1. Тогда следующий элемент, напечатанный генератором Б, будет равен a[t] -I- 2/ -Ь 1 = (t^ - 1) -Ь 2i -f 1 = (/^ -Ь 2i -Ь 1) - 1 = (/ -f- 1)^ - 1, р т. е. будет в точности совпадать с элементом (i -I- 1) — 1, напечатанным генератором В. Задача 4.6 Пусть Л[п] — это число шагов в оптимальном алгоритме переноса Ханойской башни из п кружков, Л[1]=1, Л[2] = 3, Л[3] = 7, Л[4]=15, Л[5] = 31, Л[6] = 63, Л[7] = 127. Напишите два генератора последовательности h[n], первый из которых вычисляет значение Л[п] по предыдущему значению h[n - 1], а второй — по значению п. Поиск правила, по которому образована та или иная последовательность, — одна из наиболее часто встречающихся в алгоритмике задач. Нередко оказывается, что нам удается вычислить начальные члены последовательности, не зная для них простого правила, но вот вычисление далеких членов не получается. В таких случаях мы можем попробовать угадать правило по начальным членам. Угаданное правило можно проверить, вычисляя по нему очередные члены и сравнивая их с членами последовательности. Если имеет место совпадение, то можно попробовать правило доказать. В электронной энциклопедии целочисленных последовательностей, составленной Н. Дж. А. Слоаном, собрано много таких последовательностей. Зайдя на сайт https://www.research.att.com/~njas/sequences/index.html вы получите доступ к этой энциклопедии. В ответ на ввод первых Угадай алгоритм! 65 нескольких членов последовательности программа выдает все последовательности, хранящиеся в энциклопедии и содержащие эти члены. Энциклопедия описывает эти последовательности, приводит формулы для них и сообщает, где они встречались. Она может служить очень полезным и эффективным инструментом распознавания последовательностей. Если мы не только видим первые несколько членов последовательности, но и знаем что-нибудь про правило ее образования или решили ограничить себя только последовательностями, образованными по некоторым правилам, то иногда это правило можно узнать точно. Некоторые правила образования последовательностей имеют специ-£1льные названия. В арифметической прогрессии всякий член, начиная со второго, получается прибавлением к предыдущему одного и того же числа — разности прогрессии. Например, последовательные нечетные числа 1, 3, 5, 7, 9, 11, 13, ... образуют арифметическую прогрессию с разностью 2: каждое число в этой последовательности на 2 больше предыдущего. Вот начало некоторой арифметической прогрессии с разностью -3: 7, 4, 1, -2, -5, -8, -11, ... . Первый элемент а и разность d арифметической прогрессии полностью ее определяют. Генератор арифметической прогрессии легко написать. Вот, например: а = 1 d = 2 п = 100 /= 1 ПОКА / <= п ДЕЛАТЬ печать(а) / = /-(■ 1 а = а + d КОНЕЦ Эта программа печатает первые 100 нечетных чисел. Заменив подходящим образом правые части в первых трех командах присваивания, мы получим программу, печатающую первые л членов арифметической прогрессии с первым элементом а и разностью d. Упражнение Измените команды присваивания в генераторе арифметической прогрессии таким образом, чтобы он печатал первые 47 членов арифметической прогрессии 7, 4, 1, —2, —5, ... . 66 ГЛАВА 4 Задача 4.7 Член арифметической прогрессии с номером / + 1 можно вычислять непосредственно, зная только начальный член а и разность d, а не прибавляя разность к предыдущему члену. Придумайте формулу для (i + 1)-го члена и напишите генератор арифметической прогрессии, основанный на этой формуле. Проверьте, что оба генератора дают одинаковые результаты при одинаковых начальном члене прогрессии и ее разности. Задача 4.8 Последние два числа, напечатанные генератором арифметической прогрессии, равны 191, 182. Какое число будет напечатано следующим? В геометрической прогрессии всякий член, начиная со второго, получается умножением предыдущего члена на одно и то же число — знаменатель прогрессии. Так, геометрическую прогрессию образуют степени двойки: 1, 2, 4, 8, 16, 32, ... . Вот еще две геометрические прогрессии: —3, 3, —3, 3, “3, 3, ... , 1 625 ’ ■ 25 5 1 — — — , 5 ’ 25 ’ 125 ’ Упражнение Какие знаменатели у этих прогрессий? Задача 4.9 Задача 4.10 Задача 4.11 Напишите генератор геометрических прогрессий, который вычисляет очередной член прогрессии по предыдущему. Первое число, напечатанное генератором геометрической прогрессии, равно 2, третье число равно 6. Каким будет пятое число? Первое число, напечатанное генератором геометрической прогрессии, равно 2, четвертое число равно -3. Каким будет седьмое число? Правила образования последовательностей могут быть сложнее тех, которые используются в прогрессиях. Например, знаменитая последовательность Фибоначчи 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, ... образована по такому правилу: первые два члена в ней — это единицы, а каждый член, начиная с третьего, равен сумме двух предыдущих (5 = 2-1-3, 8 = 3-1-5ит. д.). Здесь очередной член последова- Угадай алгоритм! ^7 тельности зависит уже от двух предыдущих, а не от одного, как это было для прогрессий. Последовательность Фибоначчи можно задать и формулой, однако придумать эту формулу уже гораздо труднее. Задача 4.12 Напишите генератор последовательности Фибоначчи. Задача 4.13 в последовательности с начальными членами О, 1, 1, 3, 5, 11, 21, ... каждый член, начиная с третьего, выражается по некоторой формуле через два предыдущих. Напишите генератор этой последовательности. Постарайтесь придумать несколько решений. ИТОГИ в этой главе мы решали задачи построения алгоритма, согласованного с заданными экспериментами. Поиск простых решений в задачах такого типа требует умения формулировать простые гипотезы о поведении программ. Мы познакомились с новым видом программ — числовыми генераторами, результатом работы которых являются числовые последовательности. Мы также обсудили две простые разновидности таких генераторов, которые генерируют арифметические и геометрические прогрессии. Если мы знаем, что работает генератор прогрессии, то печатаемую им последовательность можно полностью восстановить по ее началу. Глава 5 Числовые алгоритмы Раньше мы определяли сложность программы по количеству выполняемых команд Исполнителя. Количество команд Компьютера не принималось во внимание. Такое определение сложности программы связано с тем, что обычно команды Компьютера выполняются быстро, а команды Исполнителя — гораздо медленнее. Но иногда нужно учитывать и количество команд Компьютера. Оно может быть настолько велико, что время работы программы станет очень большим. Так происходит в задачах, решение которых требует больших вычислений. Определение оптимальной формы крыла самолета или конструкции двигателя внутреннего сгорания — вот пара примеров таких задач. При их решении нужны алгоритмы быстрого исполнения элементарных операций, каждая из которых выполняется миллиарды раз. В этой главе мы рассмотрим примеры таких алгоритмов: вычисление значения многочлена и нахождение наибольшего общего делителя. Кроме того, мы покажем, как лучшее понимание задачи может ускорять вычисления, на примере задачи подсчета сумм. 1. Вычисление значения многочлена Вспомним сначала, что такое многочлен. Многочлены изучаются в школьном курсе алгебры. У нас они встречались в главе 2 при рисовании графиков. 1.1. Одночлены и многочлены Степень переменной получается в результате нескольких умножений этой переменной на себя: = д:; = X • х; х^ = X • X • х; ... . Вообще х" = X • X ■ ■ X, где справа от знака равенства стоит п сомножителей х. Так же как и для чисел, при перемножении степеней перемен- О Е О ных их показатели складываются (например, д: • дг = д: ), а нулевая степень любого числа равна Ь дг® = 1. Числовые алгоритмы 69 Умножая степени переменной на числа, мы получаем одночлены. Вот примеры одночленов: гx^^, 7х^°/2, -3.1415х‘*. Складывая, вычитая и перемножая одночлены, мы получаем многочлены. Вот примеры многочленов: + 1, 2х^ - 12л:“^, - (1 + 2х)^, - дс)^ + 1, (д:^ - 2дс^)(д: + 5). Пользуясь свойствами арифметических операций, можно всякий многочлен привести к стандартному виду. Многочлен в стандартном виде — это сумма одночленов вида ах*, где а — число, причем слагаемые идут в порядке убывания степени. Первые два многочлена из приведенных выше имеют стандартный вид, а остальные нет. Числа мы тоже считаем многочленами. Значения некоторых многочленов совпадают при любых значениях переменных, подставляемых в эти многочлены. В таком случае говорят, что многочлены тождественно равны. Например, тождественно равны многочлены (X + 1)(х - 1) и - 1. Тождественно равные многочлены имеют одинаковый стандартный вид. Напомним, что порядок выполнения арифметических действий определяется старшинством операций. Операции умножения и деления старше сложения и вычитания, поэтому они выполняются в первую очередь, например: 2 + 3*2 = 2 + 6 = 8, 3/2-1 = 1.5-1 = 0.5. Операции одинакового старшинства выполняются по очереди слева направо, например: 2 + 5- 7 = 7- 7=0, 3/7/8*14 = 3/56*14 = 3/4. Чтобы изменить порядок выполнения действий, применяются скобки. Правило использования скобок состоит в том, что вначале вычисляется значение в скобках. Например: но 2*5 /(1-6)/ 3 = 2*5 / (-5) / 3 = 10 / (-5) / 3 = -2 / 3, 2*5/1-6/3 = 10/1-6/3 = 10- 6/ 3 = 10- 2 = 8. Мы рассмотрим задачу вычисления значения многочлена при заданном значении переменной. При этом мы будем учитывать количество выполненных арифметических действий. Упражнение Проверьте, что вычисление значения многочлена (х + 1)(х-1) по обычным правилам требует трех арифметических действий, а вычисление тождественно равного ему многочлена х^ - 1 — двух. 70 ГЛАВА 5 в дальнейшем в этой главе мы будем использовать в каждой команде присваивания только по одной вычислительной операции, чтобы их легче было подсчитывать. 1.2. Возведение в степень ПРОЦ степень НАЧАЛО г= 1 ПОВТОРИТЬ п РАЗ г = г*х КОНЕЦ КОНЕЦ Начнем с задачи возведения в степень. Нужно вычислить значение выражения jc” при заданном целом положительном п. Это означает, что у нас есть переменная д:, которой присвоено некоторое числовое значение, и переменная п — целое неотрицательное значение степени. Нужно составить процедуру степень, вычисляющую значение х" и заносящую его в переменную г. Вспомнив определение степени, можно написать такую процедуру (см. справа). Сколько команд выполняет эта процедура? Первая команда присваивает ответу значение 1. Затем в цикле выполняется п команд умножения. Всего п Ч- 1 команд. Можно ли вычислить значение степени быстрее? Давайте посмотрим на примеры. Ясно, что команда присваивания единицы должна присутствовать, так как процедура должна уметь вычислять нулевую степень. Поэтому сэкономить можно только на умножениях. Для вычисления первой степени нужно одно умножение (которое можно заменить командой присваивания г = х, но она не дает выигрыша). Для вычисления квадрата (второй степени) хотя бы одно ум- ty ножение сделать необходимо. Этого и достаточно: х = х • х. Вычисление третьей степени написанной выше программой требует трех умножений: = ((1 ■ х) • х) ■ х. Меньшим количеством действий обойтись не удается. Вычисление значения х“* требует четырех умножений, если использовать написанную выше простую программу. Но здесь появляется новая возможность, основанная на формулах х^ = X 4 2 2 X, X = X • X . Если вычислить квадрат числа х, а затем квадрат квадрата, то получим как раз четвертую степень. И нужно для этого только три умножения. Эту же идею можно использовать еще раз: возведение числа х^ в квадрат дает х^. Поэтому восьмую степень числа можно вычислить всего за четыре умножения. После выполнения программы (см. справа) в переменной г будет храниться восьмая степень числа х. Аналогично вычисляются 16-я и 32-я степени. Итак, некоторые степени можно вычислить намного быстрее, чем с процедурой степень. Это степени, показатели которых — степени г = 1 г = г*х г = г*г г = г* г Числовые алгоритмы 71 двойки. Следующие два упражнения показывают, что для уменьшения количества умножений при вычислении значения д:" число п необязательно должно быть степенью двойки. Упражнение Напишите программу, которая вычисляет число х®: а) за 4 умножения; б) за 3 умножения. Упражнение Напишите программу, которая вычисляет число а) за 6 умножений; б) за 5 умножений; в) за 4 умножения. Главный недостаток написанных нами сейчас программ в том, что для каждой степени приходится писать свою программу ее вычисления. Нельзя ли, как и раньше, написать одну общую процедуру для вычисления всех степеней, которая была бы почти такой же эффективной, как рассмотренные выше примеры? Давайте подумаем, каким свойством степеней мы пользуемся для уменьшения вычислений. Конечно же, тем, что при умножении степеней показатели складываются. Поэтому возведение в квадрат удваивает степень. А умножение на х добавляет к показателю степени 1. Другими словами, нам нужно получить заданную степень п из нуля, удваивая числа и прибавляя к ним 1. А это значит, что мы приходим к задаче об Удвоителе из Алгоритмики — б! Удвоитель умеет прибавлять к числу на экране 1 и умножать это число на 2. Но мы знаем про Удвоителя главное — мы умеем получать заданное число из О самым эффективным способом! Напомним, как это делается. Построить оптимального Удвоителя нам помог обратный ему Исполнитель — Раздвоитель. Мы уже использовали эффективный алгоритм получения нуля из за- ^ ^ __. данного натурального числа для Раздвоителя, когда находили двоичные цифры числа в главе 3. Напомним, что этот алгоритм устроен очень просто: если число ненулевое и четное, то его нужно делить на 2, а если нечетное, то вычитать из него 1. Превратим теперь этот алгоритм в процедуру возведения в степень (см. справа). Процедура получилась рекурсивной: Раздвоитель начинает действовать с конца. ПРОЦ эффектстепень НАЧАЛО г = 1 ЕСЛИ л > О ТО ЕСЛИ остаток(л, 2) = О ТО л = л/2 эффектстепень г = г*г ИНАЧЕ л = л - 1 эффектстепень г = х*г КОНЕЦ КОНЕЦ КОНЕЦ Упражнение Запишите, как меняется значение переменной г при вызове процедуры эффектстепень, если х = 3, ал = 10. Подсчитайте число сделанных при этом умножений. 72 ГЛАВА 5 Задача 5.1 Посмотрите еще раз на то, как мы печатали двоичную запись числа (гл. 3, с. 52—53). Там мы встретились с похожей проблемой — из-за того, что Раздвоитель действует с конца, поначалу запись двоичного числа получилась в обратном порядке. Мы справились с этой проблемой, записав двоичные цифры числа в массив. Воспользуйтесь этой возможностью и напишите нерекурсивный вариант процедуры эффектстепень. 1.3. Схема Горнера Попытаемся построить быструю программу вычисления значения многочлена. Задача 5.2 Напишите программу, которая вычисляет значение многочлена 2х^ -I- Зл: -I- 4. Решение Проще всего написать г = 2*х*х + 3*х + 4 Сколько арифметических действий выполняет такая программа? Это несложный вопрос. Давайте, как и договаривались выше, перепишем программу так, чтобы в каждой команде выполнялось не более одного арифметического действия. Значение 2х^ вычисляется командами г= 2 г = г*х г = г*х Теперь нам нужно найти Зх (поскольку по правилам вычисления значения выражения нужно выполнить вначале все умножения, а лишь затем выполнять сложения). Но для хранения значения Зх нам нужна новая переменная — переменная г занята, а мы договорились выполнять только по одной вычислительной операции в каждой команде присваивания. Поэтому следующие операции будут выглядеть так: г1 = 3 г1 = г1*х Числовые алгоритмы 73 Теперь нужно сложить значения всех вычисленных одночленов, не забыв про свободный член: г = г + г1 г = г + 4 Итак, наша программа состоит из семи команд, пять из которых содержат вычисления (а две оставшиеся являются командами присваивания констант). Чтобы сократить число команд, запишем наш многочлен в другом виде: -Ь Зх -I- 4 = (2х + 3)х + 4. Правая часть этого тождества подсказывает последовательность команд г = 2 г = г*х г = г + 3 г = г*х г = г+ 4 На этот раз получилось всего пять команд, лишь четыре из которых содержат вычисления. И при этом мы смогли обойтись без дополнительных переменных! Способ, которым мы сократили количество вычислений в задаче 5.2, годится и для многочленов более высоких степеней. Скажем, для о о вычисления значения многочлена 2х - Зх 4- 5х -ь 6 можно использовать такое тождество: 2х^ - Зх^ -ь 5х -Ь 6 = ((2х - 3)х + 5)х + 6. Упражнение Напишите программу вычисления о Q 2х - Зх -Ь 5х -Ь 6 за семь команд. многочлена Если вы правильно выполнили это упражнение, то наверняка заметили получающиеся закономерности: для хранения результата используется только одна переменная г, первая команда в программе — это команда присваивания этой переменной старшего коэффициента многочлена, а затем команды идут парами: сначала умножение на значение переменной х, затем прибавление следующего коэффициента. В конце вычисления значение переменной г равно значению многочлена. Напишем процедуру, которая вычисляет значение многочлена в общем виде по указанному выше правилу. Это правило называется схемой Горнера, поэтому процедуру мы назовем горнер. 74 ГЛАВА 5 Многочлен + а„ jX" ^ + ... + OiX + Oq ПРОЦ горнер НАЧАЛО I = п г = а[п] ПОКА I > О ДЕЛАТЬ г = г*х / = I - 1 г = г + а[/] КОНЕЦ КОНЕЦ задается в программе массивом а[0], а[1],..., а[п\ своих коэффициентов. Степень многочлена будем хранить в переменной п. Процедура горнер (см. справа) будет вычислять значение г многочлена в точке, которая хранится в переменной х. Обратите внимание на порядок действий внутри конструкции повторения. Мы вначале уменьшаем значение переменной / и лишь затем производим сложение (со следующим коэффициентом многочлена). Сколько команд выполняется при вычислении по схеме Горнера? В конструкции повторения значение переменной i уменьшается каждый раз на 1, так что всего она будет выполнена п раз. И каждый раз выполняются 3 команды. Так что общее число команд равно Зл + 1. При этом умножение выполняется всего л раз. Задача 5.3 Напишите две программы построения графика квадратного трехчлена, одна из которых использует первый способ вычисления значения квадратного трехчлена из задачи 5.2, а другая — схему Горнера. Изучите работу этих программ. Заметна ли разница во времени исполнения? Сделайте то же самое с многочленом третьей степени. 1.4. Схема Горнера и позиционная система счисления Теперь мы построим эффективную процедуру вычисления значения числа по его записи в позиционной системе. Мы обещали сделать это в конце главы 3. Между записью чисел в позиционной системе и многочленами есть важная связь. Стандартный вид многочлена степени л -1- С„ _ “' + ...+ С^Х + Cq, по существу, совпадает с записью ^nS" + а„ _ хя" " ^ + «о (л + 1)-значного числа в позиционной системе счисления с основанием g. В первом случае Cq, Cj, ... — числовые коэффициенты, х — переменная. Во втором Oq, Ui, ... — цифры, а я — основание системы счисления. Поэтому для вычисления числа по его Я’ичной записи можно пользоваться той же схемой Горнера. Покажем сначала, как она вы- Числовые алгоритмы 75 а„х числяет число по его двоичной записи, т. е. как работает Эффективный Удвоитель. Составим процедуру двоичное число. При работе процедуры предполагается, что массив а содержит двоичную запись числа, а в переменной I хранится длина массива — количество цифр в числе. Ответ записывается в переменную р. Эта процедура (см. справа) получается из схемы Горнера заменой л: на 2. По сравнению со схемой Горнера в этой процедуре еще изменены имена некоторых переменных. Задача 5.4 Напишите процедуру восстановления числа по его записи в системе счисления с основанием g. ПРОЦ двоичное число НАЧАЛО 1 = 1-А р = а[/- 1] ПОКА I > О ДЕЛАТЬ р=р*2 / = / - 1 р = р + а[|] КОНЕЦ КОНЕЦ 1.5. Особые случаи Схема Горнера очень удобна для вычисления значений многочленов. Однако для некоторых многочленов может найтись и более быстрый способ вычисления. Мы уже видели, что можно быстро вычислять некоторые степени. Рассмотрим еще несколько примеров. Значение многочлена + X + X + \ (все коэффициенты равны 1) вычисляется по схеме Горнера за 46 команд. Но можно воспользоваться тождеством -I- x^‘^ -I- ... -Ь -Ь д: -Ь 1 = (х° + 1)(х^ -I- 1){х^ + 1)(х + 1). Доказать это тождество вы можете двумя способами: либо раскрыть скобки и привести подобные, либо применить для доказательства двоичную систему счисления — надо воспользоваться тем, что любое натуральное число единственным образом представляется в виде суммы различных степеней двойки. Запишем программу, которая реализует алгоритм вычисления значения многочлена по этой формуле (см. справа). Эта программа требует исполнения 4 • 3 = 12 арифметических операций. Это намного меньше, чем 3 • 15 + 1 = 46 операций, необходимых по схеме Горнера. .14 = /^8 г= 1 г1 =х ПОВТОРИТЬ 4 РАЗА г = (г1 + 1)*г г1 = г1*г1 КОНЕЦ Задача 5.5 Вычислите значение многочлена 4- 2х^ -I- 2х -Ь 1 за 6 команд (схема Горнера требует 10 команд). 76 ГЛАВА 5 Задача 5.6 Вычислите значение многочлена - 4х“* + 4х^ за 4 команды (схема Горнера требует 19 команд). Теперь рассмотрим многочлен + х^^ + ... + + X + 1. Его уже нельзя так удачно разложить на множители, чтобы стала ясна простая схема вычисления. По схеме Горнера вычисление значения этого многочлена требует 3 • 13 -Н 1 = 40 команд. Однако это вычисление можно выполнить и быстрее, если использовать не только операции сложения, вычитания и умножения, но и операцию деления. Чтобы понять, зачем здесь нужно деление, посмотрим на тождество (X - 1)(х^^ + х^'^ + ... -ь X + 1) = х1^ + х13 + -х^з- Ч-Х-1-Х- -х^-х-1= (5.1) = X 14 1. Чтобы легче было понять это тождество, мы разместили друг под другом подобные слагаемые, которые получаются после раскрытия скобок. Разделив левую и правую части этого тождества на х - 1, получаем новое тождество Х13 + х12 + -I- х'^ -Ь X -I- 1 = X - 1 Ясно, что при заданном х правую часть этого тождества можно вычислить быстрее, чем за 40 команд. Вычислить число х^“* можно за 8 команд (см. раздел 1.2). Осталось вычесть из него 1 и разделить на значение х - 1. Это еще 3 команды, а всего получаем 11 команд. Комментарий Применять описанное выше вычисление можно только при х ^ Если мы попробуем выполнить указанные действия при х=1, то придется делить на 0, так что мы получим ОТКАЗ. Тождество (5.1) можно обобщить: х" -I- ах” ^ + а^х" ^ + + а” ^х + а + 1 ..п + 1 (5.2) Проверка этого тождества ничем не отличается от разобранного выше частного случая: нужно точно так же раскрывать скобки, сокращать подобные члены и делить на х - а. Значит, и значения многочленов такого вида можно вычислять быстрее, чем по схеме Горнера. Числовые алгоритмы 77 Вот более трудная задача на эту тему. Задача 5.7 Напишите процедуру вычисления значения многочлена за 10 команд: + 2х^ + Зх^ + 4х^^ + 5х^ + -Ь 7л: 4- 8. Подсказка Запишите многочлен в виде суммы левых частей тождества (5.2), после чего дважды примените это тождество. 2. Алгоритмы и формулы Эффективные алгоритмы удается иногда построить при вычислении сумм. Задача 5.8 Напишите процедуру вычисления суммы первых п натуральных чисел. Решение 1 Нам нужно вычислить сумму 1 -Ь ... -Ь (п - 1) ч- п. Вид этой формулы подсказывает, что нужно использовать конструкцию повторения. Будем подсчитывать сумму в переменной s: ПРОЦ сумма НАЧАЛО / = о 5 = 0 ПОКА / < л ДЕЛАТЬ 1 = 1+1 5 = 5 + / КОНЕЦ КОНЕЦ Решение 2 Запишем искомую сумму S„ в прямом и в обратном порядке: = л ■+ (л — 1) + ... 4- 2 +- 1, Sfj = 14-24-...4-(л~1)4-л и сложим эти два выражения. Каждая пара слагаемых, записанная в одном столбце, дает вклад л 4- 1, а левые части дают 2S„. Поэтому 2S„ = л(л 4- 1) 78 ГЛАВА 5 (справа п слагаемых). Значит, S„ = п{п + 1)/2. Поэтому другое решение задачи дает такая процедура: ПРОЦ сумма по формуле НАЧАЛО S = п*(п -I- 1)/2 КОНЕЦ Задача 5.9 Вторая процедура гораздо короче, и она будет работать гораздо быстрее первой. Попробуйте вычислить сумму чисел от 1 до 20, непосредственно складывая числа и по найденной формуле. Возьмите калькулятор, заметьте время начала работы и найдите сумму нечетных чисел от 1 до 2007. Запишите время, которое вам потребовалось на это вычисление. А теперь отложите калькулятор в сторону и попробуйте найти формулу для суммы нечетных чисел (она получается аналогично предыдущей формуле). Снова возьмите калькулятор и выполните вычисления по формуле. Сравните затраченное время. Два способа подсчета суммы чисел демонстрируют два разных подхода к решению алгоритмических задач: можно написать программу первым пришедшим в голову способом, а можно подумать подольше и написать более качественную программу. Тем не менее нельзя сказать, что первое решение никуда не годится. Его легко приспособить для подсчета суммы квадратов, кубов и т. д. Что же касается второго решения, то неясно, можно ли найти короткую формулу для нахождения суммы квадратов или более высоких степеней. Математика помогает быстро находить удобные формулы для сумм во многих случаях. В частности, короткие формулы есть для сумм квадратов, кубов и любых других степеней. В этих формулах суммы представляются многочленами от п, причем степень многочлена на 1 больше степени, в которую возводятся слагаемые. Например, для суммы квадратов формула имеет вид + 2^ + + = п(п + 1)(2л -ь 1) 6 Числовые алгоритмы 79 3. Алгоритм Евклида в этом разделе мы будем решать задачу нахождения наибольшего общего делителя двух чисел. Общий делитель чисел а и Ь — это такое число d, которое делит нацело и а, и &. Уже по названию можно догадаться, что наибольший общий делитель чисел а и Ь — это самый большой из общих делителей чисел а и Ь. Обозначать наибольший общий делитель мы будем НОД(а, Ь). Пример Чему равен НОД(10, 15)? Ответ 5. Проверим ответ. Действительно, 10 : 5 = 2, 15 ; 5 = 3, поэтому 5 является общим делителем чисел 10 и 15. Нужно еще проверить, что никакое большее число не является общим делителем чисел 10 и 15. Как это сделать? Можно заметить, что раз 10 : 5 = 2, то следующий по величине после 5 делитель числа 10 — это само число 10. В самом деле, между 2 и 1 натуральных чисел нет, а увеличение делителя приводит к уменьшению частного (если делить пряники между большим числом людей, то каждому достанется меньше пряников). Но 10 не является делителем числа 15. 12 Дробь — легко упростить. Ее числитель и знаменатель делятся на 6, поэтому мы можем сократить дробь на это число: 12 ^ 2 • g ^ 2 18 “ 3 • (1 “ 3 ■ А вот что делать с дробью 1917 1988 ? Чтобы провести в ней все со- кращения, нужно найти НОД(1917, 1988). Проще всего это сделать с помощью алгоритма Евклида, который мы рассмотрим ниже. При работе с дробями иногда полезна и еще одна операция, связанная с делимостью натуральных чисел, — нахождение наименьшего общего кратного. Начнем с примера. На рисунке 20 показано, как сложить четверть торта и его шестую часть. Чтобы выразить результат в виде обыкновенной дроби, нужно представить четверть торта как три двенадцатых, а одну шестую как две двенадцатых. Сразу становится ясно, что в сумме получится пять двенадцатых. Рис. 20 80 ГЛАВА 5 Итак, при сложении дробей нужно найти число, которое делится на знаменатели обоих слагаемых. Конечно, в качестве такого числа можно взять произведение знаменателей. Но выполнять вычисления проще, если знаменатели дробей не очень большие. Проверить 1,1 3,2 5 правильность вычисления выражения ^ + ^ = ^^^ + ^^ = ^3^ можно в уме. Наименьшее натуральное число, которое делится и на число а, и на число Ъ, называется наименьшим общим кратным этих чисел (обозначается НОК(а, б)). Найти наименьшее общее кратное можно, разделив произведение чисел на их наибольший делитель: НОЩа, Ь) = а ■ Ь НОД(а, Ь) • Как искать НОД в общем случае? Самое простое — превратить определение в алгоритм. Чтобы найти НОД, будем перебирать все числа и проверять, является ли число общим делителем, запоминая максимальный из найденных общих делителей. В конце такой процедуры мы и получим НОД. У описанной идеи много недостатков. Прежде всего тот, что она еще не является алгоритмом. Чисел ведь бесконечно много, и все их не перебрать. Поэтому нужно придумать, в какой момент прервать перебор. Это нетрудно: НОД не превосходит каждое из чисел а, Ь (ненулевое число не может делиться нацело на большее число). Значит, перебор достаточно вести до любого из чисел а или Ь. Проверить делимость одного числа на другое можно, применяя команду деления с остатком. Число с делится нацело на число х тогда и только тогда, когда остаток от деления с на дс равен 0. Окончательно получаем такую процедуру вычисления наибольшего общего делителя: ПРОЦ НОД НАЧАЛО d= 1 х= 1 ПОКА х< а ДЕЛАТЬ X = X + 1 ЕСЛИ (остаток(а, х) = 0) И (остаток(Ь, х) = 0) ТО d = x КОНЕЦ КОНЕЦ КОНЕЦ Убедимся, что эта процедура работает правильно. В переменной d мы храним наибольший из найденных на текущий момент общих делителей чисел а и &. Сначала мы присваиваем этой переменной значение 1. Это наименьшее из возможных значений НОД (число 1 делит нацело любые натуральные числа). Числовые алгоритмы 81 а ь X d 4 6 1 1 4 6 2 2 4 6 3 2 4 6 4 2 Переменная х пробегает в цикле все натуральные числа от 2 до а. Для каждого из этих чисел мы проверяем, является ли оно делителем обоих чисел а и Ь. Именно об этом говорит сложное условие в конструкции ветвления. Если это так, то мы присваиваем переменной d значение этого делителя — самого большого из найденных на текущий момент. А если не так, то ничего делать не надо. В конце работы процедуры в переменной d содержится наибольший общий делитель чисел а и Ь. Проследим работу процедуры нод при а = = 4 и Ь = 6. Для этого составим таблицу состояний, в которой укажем значения переменных перед проверкой условия в конструкции повторения (пятая строка). Когда значение переменной х равно 2, сложное условие выполнено и значение переменной d заменяется значением 2. После этого изменяется только значение переменной х, потому что условие в конструкции ветвления всегда оказывается ложным. Задача 5.10 Примените процедуру нод к числам а = 15 и ft = 10. Составьте таблицу состояний переменных х и d. Какие значения принимает переменная d? Задача 5.11 Измените процедуру нод таким образом, чтобы она проверяла делители не до первого числа а, а до меньшего из чисел а, Ь. Задача 5.12 Процедура нод находит наибольший общий делитель целых положительных чисел. А деление нацело определено для любых целых чисел, в том числе и отрицательных. Напишите процедуру нод целых, которая находит НОД любых двух целых чисел. Итак, мы написали процедуру, которая находит НОД. Процедура эта имеет один существенный недостаток: она работает долго. Что это означает? Конструкция повторения в этой процедуре будет исполнена столько раз, каково значение меньшего из чисел. Пока числа маленькие, это не страшно. Как уже говорилось, компьютер работает очень быстро. А выполнение всех команд в процедуре нод происходит внутри компьютера, никаких обращений к внешним (медленным) Исполнителям не нужно. Но как бы быстро ни работал компьютер, исполнение команд все равно занимает время. Давайте сделаем примерный подсчет. Пусть компьютер выполняет за секунду 10 миллиардов команд. Тогда процедура нод для 10-значных чисел будет выполняться не дольше 5 секунд. Не мгновенно, но все-таки терпимо. Но что случится, если мы применим процедуру нод к 100-значным числам? Компьютер будет работать не меньше 82 ГЛАВА 5 10®° = 1 0000000000 0000000000 0000000000 0000000000 0000000000 0000000000 0000000000 0000000000 0000000000 секунд. Это очень большое число. Оно больше числа атомов в наблюдаемой нами части Вселенной. По современным научным данным, столько времени не просуществуют Солнце и другие звезды. Поэтому с практической точки зрения можно считать, что такое вычисление не закончится никогда. Комментарий Кто-нибудь может спросить: а зачем нужно вычислять НОД таких чудовищно больших чисел? На практике необходимость обращаться с очень большими числами возникает не при пересчете предметов (даже атомов во Вселенной меньше), а в задачах шифрования. Что же делать, если хочется найти НОД 100-значных чисел? Оказывается, НОД можно вычислять гораздо эффективнее. Такой алгоритм был известен уже Евклиду более двух тысяч лет назад. Мы сейчас его опишем. Идея, лежащая в основе алгоритма Евклида, выражается равенством НОД(д:, у) = НОД(л: - у, у). Это очень простое утверждение: НОД двух чисел равен НОД одного из этих чисел и их разности. Действительно, любой общий делитель чисел хну является делителем числа X - у, а любой общий делитель чисел х - у и. у является делителем числа х. Значит, НОД этих пар чисел также совпадает. Это равенство позволяет вычислять НОД, переходя ко все меньшим числам. Например: НОД(192, 144) = НОД(144, 48) = НОД(96, 48) = НОД(48, 48) = 48. Упражнение Найдите НОД(1917, 1988) и сократите дробь 1917 1988 Последнее наблюдение, которое нужно для алгоритма Евклида: вместо того чтобы вычитать несколько раз число у, можно вычесть любое его кратное, не меняя значения НОД. Другими словами, если разделить х на у с остатком: X = qy + г (г обозначает остаток), то НОД(д:, у) = НОД(£/, г). (5.3) Последнее равенство выполняется и в том случае, когда х делится на у, потому что любое число является делителем нуля. Числовые алгоритмы 83 в результате мы приходим к следующему алгоритму: чтобы найти наибольший общий делитель чисел а и Ь, нужно заменить пару (а, Ь) парой (Ь, г), где i-остаток от деления числа а на число Ь, и повторять то же действие с полученной парой чисел, пока остаток не станет равным нулю. Теперь запишем все приведенные выше соображения в виде алгоритма ПРОЦ Евклид НАЧАЛО ПОКА НЕ (Ь = О) ДЕЛАТЬ г = остаток(а, Ь) а = Ь Ь = г КОНЕЦ КОНЕЦ После выполнения процедуры в переменной а будет храниться НОД чисел а и 6. Присваивания в пятой и шестой строчках процедуры Евклид осуществляют переход от левой части равенства (5.3) к его правой части. Приведем пример выполнения процедуры Евклид при начальных значениях переменных а = = 144, Ь = 192 (см. справа). Как и в примере работы процедуры нод, составим таблицу состояний, в которой приведем значения переменных перед проверкой условия в конструкции повторения (третья строка). Упражнение Докажите, что если начальные значения переменных а и Ь поменять местами, то количество команд, выполняемых процедурой Евклид, изменится на 3. Долго ли работает процедура Евклид? Это зависит от того, насколько быстро уменьшаются значения переменных а и Ь. Будем рассматривать один шаг алгоритма Евклида, т. е. выполнение одной конструкции повторения. Вначале посмотрим на примеры. а ь Г 144 192 - 192 144 144 144 48 48 48 0 0 а Ь было 13 12 стало 12 1 а ь было 13 7 стало 7 6 Первый пример показывает, что большее число в паре может уменьшиться за один шаг всего на 1. Второй пример показывает, что 84 ГЛАВА 5 меньшее число в паре также может уменьшиться за один шаг всего на 1. Этого мало. Но оба примера показывают, что сумма чисел в паре уменьшается гораздо быстрее. В первом примере она уменьшилась в 25/13 раза, т. е. почти вдвое. Во втором примере она уменьшилась в 20/13 раза, т. е. больше чем в 1,5 раза. Упражнение Проверьте, что сумма чисел в паре уменьшается за один шаг не меньше чем в 1,5 раза для пар: а) 21, 14; б) 34, 20; в) 55, 33. Вы можете взять и другие пары чисел и проверить для них, что сумма чисе. уменьшается за один шаг по крайней мере в 1,5 раза. Но это означает, что после двух шагов алгоритма Евклида сумма уменьшится в (3/2) • (3/2) = 9/4 раза, т. е. более чем в 2 раза. После выполнения четырех шагов сумма уменьшится в (9/4)‘(9/4) = = 81/16 раза, т. е. больше чем в 5 раз. После выполнения шести шагов сумма уменьшится по крайней мере в 10 раз. Тем самым количество цифр в десятичной записи этой суммы уменьшится на 1. Давайте теперь оценим, сколько команд выполнит алгоритм Евклида, примененный к 100-значным числам. Сумма 100-значных чисел записывается не более чем 101 десятичным знаком. Значит, после не более чем 6 • 101 = 606 шагов алгоритм заведомо закончит работу. Каждый шаг состоит из выполнения трех команд. Нужно еще учесть возможные 3 команды из-за неудачного порядка чисел. Итак, количество команд не превосходит 606 • 3 -Ь 3 = 1821. Это НАМНОГО меньше, чем количество команд, которое требовалось при выполнении процедуры нод. Любой современный компьютер выполнит такую процедуру практически мгновенно. Комментарий У нашего рассуждения есть недостаток. Мы проверили на нескольких примерах, что сумма а + Ь уменьшается в 1,5 раза за один шаг, и заключили, что так будет всегда для любых пар чисел. В этом месте возможна ошибка. Для полной уверенности в справедливости нашей оценки нужно рассуждать иначе. Пусть у нас есть пара натуральных чисел а, Ь, причем а > Ь. После шага алгоритма Евклида из этой пары получилась пара Ь, с. При делении с остатком всегда остаток меньше частного, т. е. с < 6, а делимое не меньше суммы остатка и частного, т. е. а > Ь + с. Исходя из этих условий нам нужно доказать неравенство а + Ь > ^(Ь + с). Числовые алгоритмы 85 Получим правую часть этого неравенства из левой, заменяя числа на меньшие или равные: а + 6>(6 + с) + Ь = (6 + с) + |-ь|>(Ь + с) + |-(-| = = (Ь + с) + ^ (Ь -Ь с) = I (6 -Ь с). Алгоритм Евклида удобен и для вычислений вручную. Задача 5.13 Воспользовавшись алгоритмом Евклида, вычислите: а) НОД(42, 24); б) НОД(21, 34); в) НОД(111, 1111); г) НОД(12007, 95). Задача 5.14 Найдите наибольшие общие делители пар чисел в предыдущей задаче, раскладывая числа на простые множители. Какой метод удобнее? ИТОГИ в этой главе мы занимались анализом эффективности числовых алгоритмов; придумывали алгоритмы, выполнение которых требует как можно меньшего числа команд; оценивали количество этих команд; сравнивали разные алгоритмы между собой. Мы научились быстро вычислять значение многочлена общего вида по схеме Горнера. Мы обнаружили, что вычисление значения числа по его записи в позиционной системе способом Удвоителя является, по сути, вычислением по схеме Горнера. Мы узнали, что иногда длинные вычисления можно заменить короткой формулой. Наибольший общий делитель чисел можно быстро находить алгоритмом Евклида. Глава 6 • «х*]* • • и* т 1* • Перебор Решение задачи часто требует изучения большого числа разных, хотя и похожих вариантов. Перебор вариантов — утомительная и скучная работа, которую лучше предоставить компьютеру. Алгоритмы, основанные на переборе, использовались нами в главе 3 при решении задач о массивах. В этой главе мы решим еще несколько подобных задач. Перебор вариантов нужно организовать таким образом, чтобы ни один из вариантов не был пропущен. При этом число перебираемых вариантов может оказаться таким большим, что ни один самый современный компьютер не сможет решить поставленную задачу. Поэтому нужно уметь сокращать перебор — уменьшать число вариантов. Мы рассмотрим некоторые способы сокращения перебора. Ищем клад Вернемся к Исполнителю Робот. В Алго-ритмике — 6 рассматривалась такая задача: Поле Робота — прямоугольник без внутренних стен. Где-то на поле имеется клад (закрашенная клетка). Нужно его найти. Эту задачу можно решить без использования переменных. Но бывают случаи, когда без переменных не обойтись. Например, если Робот находится на поле, бесконечном во все стороны (рис. 21). Нужно написать процедуру для Робота, которая приводит его в клетку с кладом. Клад по-прежнему обозначен закрашенной клеткой. При указанных ограничениях решить задачу поиска без использования переменных невозможно. Рис. 21. Клад на бесконечном поле Робота Задача 6.1 (Трудная.) Докажите, что для Робота, который может только проверять условие закрашена и выполнять команды влево, вправо, вверх, вниз, нельзя написать программу, которая успешно ищет клад на плоскости. А теперь решим задачу поиска клада на бесконечном поле, применяя в нашем алгоритме переменные. Ясно, что Робот должен об- Перебор 87 И| а) б) Рис. 22. Порядок обхода бесконечного поля по спирали ходить по очереди все клетки плоскости, пока не наткнется на клад. Поэтому нужно сформулировать правило обхода плоскости. Мы выбираем порядок обхода «по спирали», изображенный на рисунке 22, а. Такой обход плоскости разбивается на участки четырех типов: «шаг вправо и п шагов вверх», «шаг вверх и п шагов влево», «шаг влево и п шагов вниз», «шаг вниз и п шагов вправо». Число п будем называть длиной участка. Эти типы участков встречаются по очереди в указанном порядке. На рисунке 22, б показаны положения Робота в начале каждого участка. Упражнение Найдите длину каждого участка обхода, показанного на рис. 22, б. Выпишите полученные числа в порядке обхода. Структура процедуры поиска приводится ниже. ПРОЦ поиск на плоскости НАЧАЛО п = О вправо и вверх /1=1 ПОКА НЕ закрашена ДЕЛАТЬ вверх и влево влево и вниз п = л + 1 вниз и вправо вправо и вверх л = п + 1 КОНЕЦ КОНЕЦ 88 ГЛАВА 6 Процедура поиск на плоскости использует четыре процедуры, каждая из которых отвечает за движение по участку обхода одного из указанных выше типов. Вот как выглядит процедура вправо и вверх: ПРОЦ вправо и вверх НАЧАЛО ЕСЛИ НЕ закрашена ТО вправо 1=0 ПОКА (НЕ закрашена) И (/ < л) ДЕЛАТЬ] вверх 1 = 1+1 КОНЕЦ КОНЕЦ КОНЕЦ Перед началом выполнения конструкции повторения значение переменной I равно числу уже сделанных шагов вверх. Условие в конструкции повторения выполняется, если клад еще не найден и не все шаги сделаны (их меньше п). Упражнение Напишите процедуры вверх и влево, влево и вниз, вниз и вправо по аналогии с процедурой вправо и вверх. Упражнение Сколько раз процедура поиск на плоскости проверяет условие закрашена, когда Робот стоит на клетке, содержащей клад? Задача 6.2 Задача 6.3 Задача 6.4 Напишите процедуру поиска клада на плоскости, которая в каждой клетке проверяет условие закрашена ровно один раз. Придумайте правило обхода плоскости и напишите процедуру поиска клада, основанную на вашем правиле. Решите задачу поиска клада на бесконечном поле без использования переменных, если Робот может выполнять команду закрась и проверять условие закрашена. Робот должен остановиться на той клетке, которая была закрашена до начала работы программы. 'Щ Проверка простоты числа ^ Программа поиска клада из предыдущего раздела фактически проверяла все возможные положения клада на плоскости. Такой метод решения задач называется методом перебора. Вариантов, как Перебор 89 правило, оказывается много, нужно действовать очень аккуратно, чтобы не пропустить нужный, и такая работа занимает много времени. Поэтому помощь компьютера при переборе очень важна. Не менее важно при написании программ перебора следить за тем, чтобы не делать лишней работы и сокращать перебор насколько возможно. Так, в предыдущей главе мы решили задачу поиска НОД методом перебора, а потом нашли более быстрый способ — алгоритм Евклида. В этом разделе мы рассмотрим задачу, которую тоже можно решать перебором, — задачу проверки простоты числа. Целое положительное число называется простым, если оно не равно 1 и у него нет других делителей, кроме 1 и самого числа. Вот несколько первых простых чисел: 2, 3, 5, 7, 11, 13, 17, 19. Число, которое не равно 1 и не является простым, называется составным. Вот примеры составных чисел: 4 = 2-2, 6 = 2-3, 8 = 2 - 2 - 2, 9 = 3-3, 123 = 3 - 41, 235 = 5 - 47. Единица не является ни простым, ни составным числом. Составим алгоритм, который проверяет простоту числа. Точнее говоря, мы хотим написать процедуру, которая заносит в переменную а значение 1, если число п простое, а во всех остальных случаях заносит в эту переменную 0. При этом предполагается, что в переменной п записано целое положительное число. Как обычно, легкий способ решения — превратить определение в алгоритм. Если число л > 1 делится хотя бы на одно из чисел от 2 до ц - 1, то оно составное. В противном случае оно простое. Вот текст процедуры, которая это делает: ПРОЦ простое 1 НАЧАЛО ЕСЛИ п = 1 ТО а = о ИНАЧЕ к = 2 а = 1 ПОКА {к < п) ДЕЛАТЬ ЕСЛИ остаток(л, fc) = О ТО а = о КОНЕЦ к = к+ А КОНЕЦ КОНЕЦ КОНЕЦ Результат выполнения процедуры простое 1 содержится в переменной а. В начале процедуры проверяется условие п = 1. Если оно 90 ГЛАВА 6 истинно, то переменной а присваивается значение О (поскольку число 1 не простое). Если же оно ложно, то алгоритм перебирает все возможные значения делителя числа л от 2 до п - 1. Перед началом перебора переменной k присваивается значение 2 (с него начинается перебор), а переменной а — значение 1. В дальнейшем значение переменной а изменяется лишь в том случае, если для некоторого k истинно условие остаток(п, к) = О. Истинность этого условия означает, что п делится на Л, т. е. является составным числом. Если у числа л несколько делителей, то значение О будет присвоено переменной а несколько раз. Упражнение Сколько раз выполняется присваивание значения переменной а при вызове простое1, если л = 8? Чем меньше вариантов приходится перебирать программе, тем быстрее она работает. Поэтому перебор стараются по возможности сократить. Например, если мы уже нашли делитель числа л, то оно составное и дальнейший перебор возможных делителей не нужен. Задача 6.5 Напишите процедуру проверки простоты числа прос-, тое2, которая заканчивает работу, как только найден делитель числа л, отличный от 1 и л. Перебор можно еще больше сократить, если отвлечься от программирования и немного поразмышлять. Пусть у числа л есть делитель р. Тогда частное q = п \ р также является делителем числа л. Выберем из чисел р, q меньшее, пусть это будет р. Тогда р < q = = п : р, или, что то же самое, р • р < л. Задача 6.6 Приведите пример трехзначного числа л, для которого в использованных выше обозначениях р = q. Наши размышления привели к такому выводу: у любого составного числа п есть делитель, больший 1, квадрат которого не превосходит п. Этот факт позволяет значительно уменьшить количество кандидатов в наименьшие делители числа и еще больше сократить перебор. В нашем случае получаем процедуру, изображенную справа. ПРОЦ простоеЗ НАЧАЛО ЕСЛИ п = 1 ТО а = О ИНАЧЕ к = 2 а = 1 ПОКА к‘к <= п ДЕЛАТЬ ЕСЛИ остаток(л, 1с) = О ТО а = О КОНЕЦ * = + 1 КОНЕЦ КОНЕЦ КОНЕЦ Перебор 91 ЗаДОЧа Ш-iW' Улучшите процедуру простоеЗ, научив ее прекращать работу, как только у числа найден первый делитель. Комментарий Процедура простоеЗ работает не очень быстро. Проверить с ее помощью простоту 100-значного числа нельзя: если значение п — простое 100-значное число, вы не дождетесь окончания работы процедуры. Есть гораздо более быстрые процедуры проверки простоты, которые проверяют простоту 100-значного числа за несколько секунд. А самое большое известное простое число равно - 1 (данные на 15 декабря 2005 г.). Печать десятичной записи этого числа потребовала бы тысяч страниц. Эффективные процедуры проверки простоты используют намного более изощренную математику, чем те переборные алгоритмы, которые мы обсудили. Давным-давно было замечено: знание — сила. В нашем веке, пронизанном информацией и компьютерными технологиями, можно добавить: знание — скорость. Щ Выбираем пароль Компьютерный пароль — это ключ, открывающий доступ к компьютеру. Как человек, у которого есть ключ от дверного замка, может открыть дверь в квартиру, так и человек, знающий пароль, может получить доступ к информации в компьютере. Но между дверями и компьютерами есть большая разница: пробовать разные варианты пароля гораздо проще, чем пробовать разные ключи. Взломщики подбирают пароли с помощью компьютерных программ, способных проверять миллионы паролей в секунду. В этом разделе мы попробуем понять, как нужно выбирать пароль, чтобы он обеспечивал достаточно надежную защиту. Оценить надежность защиты проще всего, рассматривая ее с точки зрения того, кто пытается эту защиту преодолеть. Начнем с очень простого примера. Пусть пароль состоит всего из трех символов, каждый из которых — это 0 или 1. Такие простые пароли в реальной жизни не встречаются. Почему? Давайте представим, что такой пароль защищает доступ к компьютеру. Мы не знаем пароля, но хотим использовать компьютер. Как нам поступить? Начнем с того, что выпишем все возможные варианты пароля. Вот они: 000, 001, 010, 011, 100, 101, 110, 111. Их всего 8. Теперь, имея список всех вариантов, можно перебирать их один за другим, пока не найдем нужный. В худшем случае нам потребу- 92 ГЛАВА 6 ется всего 8 попыток (если очень не повезет и именно последний вариант в списке будет искомым паролем). Как улучшить наш пароль? Есть два способа добиться этого: увеличить длину пароля или расширить набор возможных символов. В результате количество возможных вариантов станет больше, и можно надеяться, что необходимое количество попыток подбора пароля тоже сильно увеличится. 3.1. Полный перебор вариантов Для поиска пароля нужно уметь выписывать все возможные пароли. Чтобы ничего не пропустить и не повторять один и тот же пароль несколько раз, нужно выписывать их в каком-нибудь определенном порядке. Если мы придумаем простое правило для выписывания всех паролей, то его будет несложно объяснить и компьютеру. Таким образом, мы сможем написать программу, которая сама будет выписывать все возможные пароли. Заодно она сможет подсчитать и их число. Задача 6.8 Пусть пароль состоит всего из трех символов, каждый символ — это О или 1. Придумать способ выписывания всех возможных паролей. Хотя эта задача и похожа на задачу, которую мы уже решили, она отличается тем, что на сей раз нас интересует не сам список паролей, а способ их выписывания. Мы решим эту простую задачу двумя разными способами. Прежде чем читать наши решения, попробуйте придумать свое! Первое Как получить нужный список? Посмотрим на него решение как на список чисел, записанных в двоичной системе “ счисления. Окажется, что числа в списке идут в по- рядке возрастания: ОООа = О, OOI2 = 1, OIO2 = 2, OII2 = 3, 1002 = 4, 1012 = 5, 1102 = 6, 1112 = 7. Один из способов научиться выписывать последовательности в таком порядке — это научиться писать следующее число, зная предыдущее. Для этого нужно научиться прибавлять 1 к числу, записанному в двоичной системе. Как, например, узнать, какая последовательность должна следовать за 011? Будем смотреть на элементы последовательности поочередно справа налево. Если очередной элемент равен 1, то мы должны заменить его нулем и перейти влево (это просто перенос цифры в старший разряд Перебор 93 при сложении). А если этот элемент равен О, то мы должны заменить его на 1 и остановиться. Запишем теперь этот алгоритм на языке программирования. Используем для первой цифры переменную а, для второй — Ь, для третьей — с. Тогда алгоритм записывается в виде процедуры так: ПРОЦ следующий НАЧАЛО ЕСЛИ с = О ТО с = 1 ИНАЧЕ с = О ЕСЛИ Ь = О ТО Ь = 1 ИНАЧЕ Ь = О ЕСЛИ а = О ТО а = 1 КОНЕЦ КОНЕЦ КОНЕЦ печать(а, Ь, с) КОНЕЦ Эта процедура вызывается в цикле. Теперь нам нужно решить, какие значения должны иметь переменные в начале цикла и при каком условии цикл должен закончиться. Но это просто. Мы начинаем с последовательности 000, поэтому в начале цикла все переменные должны равняться нулю. А последним возможным результатом является строка 111; это и будет условием окончания цикла. Итак, полная программа выписывания всех возможных паролей из трех нулей и единиц будет выглядеть так: а = о Ь = о с = о печать (а, Ь, с) ПОКА НЕ ((а = 1) И (6 = 1) И (с = 1)) ДЕЛАТЬ следующий КОНЕЦ Обратите внимание на то, что после присваивания переменным а, Ь, с начальных значений в программе стоит команда печати — иначе строка 000 окажется ненапечатанной! 94 ГЛАВА 6 Второе ^Шё ни е Ту же самую последовательность паролей можно вывести с помощью другого алгоритма. Для этого нужно просто по очереди менять цифры а, Ь, с так, чтобы каждая из них принимала значения О и 1: а = О Ь = 0 с = О ПОКА а < 2 ДЕЛАТЬ ПОКА Ь < 2 ДЕЛАТЬ ПОКА с < 2 ДЕЛАТЬ печать(а, Ь, с) с = с + 1 КОНЕЦ с = О Ь = Ь + 1 КОНЕЦ Ь = О а = а -I- 1 КОНЕЦ Упражнение Упражнение Задача 6.9 Обратите внимание на то, что можно подсчитать, сколько вариантов найдет эта программа, даже не запуская ее. Каждый раз, когда программа доходит до выполнения самой внутренней конструкции повторения ПОКА с < 2 ДЕЛАТЬ, она повторяет ее два раза и каждый раз находит один из вариантов. Аналогично при каждом выполнении конструкции повторения ПОКА Ь < 2 ДЕЛАТЬ она повторяет исполнение самой внутренней конструкции повторения два раза. Это означает, что будут найдены 2-2 = 4 варианта. И наконец, самая внешняя конструкция повторения исполняет конструкцию ПОКА а < 2 ДЕЛАТЬ также два раза. Всего получаем 2 • 2 • 2 = 8 вариантов. Проверьте, совпадают ли результаты работы программ, найденных в первом и втором решениях. Придумайте, как выводить пароли последовательно, переводя последовательные числа в двоичную запись. Рассмотрим теперь пароль, который по-прежнему состоит из трех символов, но каждый из них может быть любой из десятичных цифр О, 1, 2, 3, 4, 5, 6, 7, 8, 9 (такие пароли встречаются на кодовых замках в подъездах домов). Придумайте способ выписывания всех возможных паролей. Перебор 95 Решение Попробуйте самостоятельно приспособить к этому случаю первое из найденных нами решений. Вот как изменяется второе решение. а = О Ь = О с = О ПОКА а < 10 ДЕЛАТЬ ПОКА Ь < 10 ДЕЛАТЬ ПОКА с < 10 ДЕЛАТЬ печать(а, Ь, с) с = с + 1 КОНЕЦ с = О Ь = Ь + 1 КОНЕЦ Ь = О а = а + 1 КОНЕЦ Так же как в предыдущем способе, можно подсчитать общее число вариантов, не запуская этой программы. Получится 10 • 10 • 10 = 1000 вариантов. Это в точности количество целых чисел от 0 до 999. А как найти все варианты пароля, если символов в нем больше трех? Нужно для каждого символа добавлять свою переменную и вставлять свою конструкцию повторения. Задача 6.10 Напишите программу порождения всех вариантов пароля, который состоит из 4 десятичных цифр. Сколько всего таких паролей? Теперь можно попробовать решить общую задачу. Пароль имеет некоторую заданную длину п (т. е. указано количество символов в пароле), и в нем могут использоваться символы из некоторого данного набора. Мы пока рассмотрели две возможности: набор двоичных цифр о, 1 или набор десятичных цифр 0, 1, 2, 3, 4, 5, 6, 7, 8, 9. Но в качестве символов для пароля можно использовать русские буквы, или латинские буквы, или их комбинации с цифрами, или даже произвольные знаки, которые можно набирать на компьютерной клавиатуре. Наборы символов могут очень сильно отличаться по виду друг от друга. Но для порождения всех возможных вариантов важно только их общее кол)шество к. Почему? Можно любой набор символов перенумеровать числами от 0 до /е - 1 и использовать в программе перебора не сами символы, а их номера. Когда же нам потребуются сами символы, мы сможем подставить их вместо номеров. Сейчас мы этого делать не будем, а решим задачу порождения всех вариантов 96 ГЛАВА 6 пароля, в котором п символов, а каждый символ будем считать числом от О до ft - 1. Алгоритм, по существу, остается тем же самым: мы перебираем возможные значения каждого символа при неизменных значениях всех остальных. Поскольку длина пароля теперь переменная, мы не можем дать свое имя каждой переменной, которая хранит значение символа в пароле. Вместо этого мы используем массив длины п, в котором будут храниться все символы. Пусть d — имя этого массива. Есть еще одна трудность при записи этого алгоритма на нашем алгоритмическом языке. Если число символов в пароле известно заранее, то мы можем написать не-________________ сколько вложенных конструкций по- ~ вторения, как это делалось выше. Но если п может принимать различные значения, то такой способ не годится: мы не можем менять текст программы в процессе ее выполнения. Поэтому нужно добавить еще какую-нибудь идею. Первый способ справиться с этой трудностью вы найдете, если внимательно перечитаете первое решение задачи 6.8. Мы разберем другой способ, который использует рекурсию. Процедура пароли (см. справа) рекурсивно вызывает саму себя. Мы предполагаем, что значения присвоены только элементам массива d с номерами от 1 до п. Тогда команда печать(Ц) печатает их по порядку номеров. Мы предполагаем, что есть возможность вызвать команду печать( О ДЕЛАТЬ первый игрок ЕСЛИ л > о ТО р = 2 второй игрок ЕСЛИ л > о ТО] Р = 1 КОНЕЦ КОНЕЦ КОНЕЦ печать (р) ПРОЦ первый игрок НАЧАЛО л = л - 1 КОНЕЦ 1 ПРОЦ второй игрок ! НАЧАЛО ЕСЛИ л > 1 ТО t л = л - 2 ' ИНАЧЕ л = л - 1 I КОНЕЦ КОНЕЦ Видно, что простая стратегия легко переписывается в программу. Задача 8.8 Напишите процедуры, реализующие стратегии В и Г игры в конфеты. Игровые алгоритмы 123 Задача 8.9 Измените программу игры таким образом, чтобы игрок, после хода которого значение переменной п обратилось в О, был проигравшим. 4. Дерево игры Мы нашли выигрышную стратегию при игре в конфеты, проанализировав все варианты поведения игроков в случаях, когда число конфет на столе было невелико. Эта выигрышная стратегия оказалась простой — нужно всякий раз оставлять на столе такое число конфет, которое делится на 3. Однако в других играх поиск выигрышной стратегии может оказаться не столь простым. Построение дерева игры — полезный общий метод анализа игр. В дерево записываются все возможные позиции, которые могут возникнуть в игре, и все возможные ходы игроков в каждой позиции. Во многих задачах алгоритмики речь идет о состояниях чего-то. Например, электрическая лампочка может находиться в состоянии «Горит» и «Не горит», выключатель — в состоянии «Включено» и «Выключено», ячейка памяти компьютера — в одном из 2'^^ состояний. Мы будем рассматривать состояния игры в конфеты (и других игр). Состоянием нашей игры будет пара, состоящая из числа конфет и номера игрока, который должен сделать ход. Вот примеры состояний игры: <5,1>, <0,П>. Среди состояний бывают начальные, в нашем случае это <15,1> — на столе 15 конфет и ход за первым игроком. Часто, говоря о состояниях, мы говорим и о переходах из одного состояния в другое. Возможность перехода можно изображать стрелкой: А —► В. В играх переход соответствует ходу одного из игроков. В случае нашей игры возможны, например, такие переходы (ходы): <5,1> ^ <3,П>, <8,11> - <7,1>. Из некоторых состояний никакой переход невозможен, они называются конечными. В случае нашей игры это <0,1> и <0,П> — на столе нет конфет. Нарисуем начальное состояние. Нарисуем все состояния, куда мы можем перейти из уже нарисованного, ниже начального. Проведем из начального состояния стрелки, отвечающие всем возможным переходам. В случае нашей игры получим такую картинку: С каждым из нарисованных в нижней строке состояний сделаем то же самое. При этом, если у нас в ходе рисования возникнет необ- 124 ГЛАВА 8 ходимость нарисовать состояние, которое уже встречалось, мы тем не менее нарисуем его еще раз: Продолжим этот процесс. Когда он закончится, на бумаге окажется изображенным то, что математики называют деревом. В случае игры это дерево называется деревом игры. На рисунке 26 приведено дерево игры в конфеты для начального состояния <5,1>. Отметим в дереве начальное состояние, затем некоторое (одно) состояние, в которое, сделав ход, можно перейти из начального, затем еще одно состояние, куда можно перейти из второго, и т. д., пока не дойдем до конечного состояния (рис. 27). В дереве окажется отмеченной некоторая последовательность (цепочка) состояний и соответствующих переходов. Ее можно называть путем в дереве. В случае игры всякий путь соответствует партии и всякой партии соответствует некоторый путь в дереве игры. По пути видно, кто выиграл в соответствующей партии — надо посмотреть на конечное состояние в этом пути. Рис. 26. Дерево игры Игровые алгоритмы 125 Рис. 27. Изображение партии в игре на ее дереве Сейчас мы расскажем, как раскрасить состояния в дереве так, чтобы из раскраски было видно, как выигрывать в игре. Начнем с того, что закрасим все конечные состояния, в которых выиграл первый. В случае нашей игры закрашенными окажутся все состояния <0,1>. Двигаясь вверх по дереву, будем раскрашивать состояния по следующему Правилу: 1. Состояние, в котором ход первого, закрашиваем, если из него есть ход, ведущий в закрашенное состояние. 2. Состояние, в котором ход второго, закрашиваем, если все ходы из него ведут в закрашенные состояния. Поднявшись до начального состояния, мы получим раскраску дерева игры. Объясним теперь, как эта закраска позволяет найти выигрышную стратегию. Возможны два варианта: начальное состояние закрашено и начальное состояние не закрашено. Мы выясним сейчас, какое это имеет отношение к выигрышу в игре. 1. Начальное состояние закрашено. Дадим такой '^овет первому игроку: Из закрашенного состояния всегда переходи в любое закра шенное. Мы утверждаем, что, пользуясь нашим Советом, первый игрок обязательно выиграет в игре. Начнем играть. Начальное состояние закрашено. В силу нашего Правила 1 это возможно, если из него есть ход в закрашенное состояние. Сделаем этот ход (в соответствии с Советом). Игра снова в закрашенном состоянии, но ход у второго игрока. В силу нашего 126 ГЛАВА 8 Правила 2 оно закрашено, если все ходы из него ведут в закрашенные состояния. Второй игрок делает какой-то ход в этом состоянии. Состояние игры теперь опять закрашенное, и ход первого игрока. Дальше все повторяется: первый игрок следует Совету, второй игрок поступает как хочет (мы даем совет только первому). При этом все состояния, через которые мы проходим, закрашенные. Игра заканчивается в закрашенном состоянии, т. е. в таком, в котором первый игрок выиграл. При этом само это состояние может находиться в разных местах дерева, партии могут быть разными, но первый выигрывает, следуя нашему Совету, всегда. 2. Начальное состояние не закрашено. Тогда мы дадим Совет второму игроку: Из незакрашенного состояния всегда переходи в любое незакрашенное. Мы утверждаем, что, пользуясь нашим Советом, второй игрок обязательно выиграет в игре. Начнем играть. Начальное состояние не закрашено. В силу нашего Правила 1 это возможно, если из него нет хода в закрашенное. Первый игрок делает любой ход. Игра снова в незакрашенном состоянии. В силу нашего Правила 2 оно не закрашено, поскольку из него не все ходы ведут в закрашенные состояния. Значит, второй игрок может сделать ход в незакрашенное состояние и в соответствии с Советом делает его. Игра опять в незакрашенном состоянии, ход первого игрока. Все его ходы ведут в незакрашенное состояние, и он делает какой-нибудь. Дальше все повторяется: второй игрок следует Совету, первый игрок поступает как хочет (мы даем совет только второму). При этом все состояния, через которые мы проходим, незакрашенные. Игра заканчивается в незакрашенном состоянии, т. е. в таком, в котором второй игрок выиграл. При этом само это состояние может находиться в разных местах дерева, партии могут быть разными, но второй выигрывает, следуя нашему Совету, всегда. Наши Советы определяют стратегии игроков. Это выигрышные стратегии — следуя им, игрок выигрывает. Но дать такой совет мы можем лишь одному из игроков — первому, если начальное состояние закрашено, и второму, если оно не закрашено. Перерисуйте в тетрадь дерево игры с рисунка 26 и Упражнение закрасьте его в соответствии с Правилами. Закрашено ли начальное положение? Кто из игроков выигрывает при правильной игре? Как он должен играть? По сути дела, анализ дерева игры есть не что иное, как перебор всех возможных партий, которые могут сыграть два противника. Полное дерево игры может быть очень большим. Его размер увеличивается с ростом числа возможных позиций и количества ходов в них. Анализ полного дерева игры, например в шахматах, лежит далеко за пределами возможностей современных компьютеров. Даже в игре в конфеты, если мы начнем не с 5, а, скажем, с 30 конфет. Игровые алгоритмы 127 для рисования дерева игры потребуется несколько тысяч тетрадных страниц. Как и в других переборных задачах, при анализе игр нужно стремиться уменьшить количество перебираемых вариантов. Возможности уменьшения количества вариантов действительно есть, и одну из них мы сейчас изучим. Если мы внимательно посмотрим на дерево игры в конфеты на рисунке 26, то увидим, что некоторые позиции встречаются в нем по нескольку раз. Так, ситуация, в которой на столе лежит одна конфета, а ход у второго игрока, присутствует в дереве трижды; с ходом первого игрока такая ситуация возникает два раза (найдите эти ситуации на рисунке). Есть в дереве и другие повторяющиеся ситуации. Если бы мы начинали не с 5, а с большего числа конфет, то повторений было бы еще больше. Можно надеяться, что если мы будем следить за возникающими ситуациями без повторений, то число исследуемых вариантов сильно уменьшится. Так оно и есть. Ситуация в игре в конфеты полностью определяется количеством конфет на столе и номером игрока, делающего очередной ход. Поэтому все возможные позиции можно представить себе в виде двух клетчатых столбиков, клетки в каждом из которых пронумерованы неотрицательными целыми числами снизу вверх. Первый столбик отвечает ситуациям, в которых должен ходить первый игрок, а второй — тем, где ход второго игрока. Число в клеточке — это количество конфет на столе. Высоту столбиков можно ограничить начальным числом конфет на столе (рис. 28, а). Ход первого игрока изображается стрелкой, идущей из клетки левого столбика в клетку правого столбика; ход второго игрока — стрелкой, идущей из клетки правого столбика в клетку левого. Стрелки всегда смотрят вниз — при б) 5 5 каждом ходе число конфет на столе уменьшается. Нижние клеточки, содержащие число О, отвечают завершающим позициям: если игра заканчивается в нижней клеточке правого столбца, то выиграл первый игрок, а если в нижней клеточке левого столбца, то второй. Если мы попытаемся изобразить на диаграмме все возможные партии в игре, то на ней будет очень много стрелок и мы не сможем в них разобраться. Но нам и не нужно перебирать все партии. У нас другая цель — найти выигрышную стратегию. А вот для этой цели диаграмма Рис. 28. Диаграмма позиций: а) на позиций как раз очень удобна, диаграмме; б) выигрышные пози- Будем искать выигрышную ции для первого игрока стратегию для первого игрока. Boc- al 5 5 4N \ ч h 1J 0 'о 5 ^ 1 зЛ % ч h V 'о 128 ГЛАВА 8 5 5 4^ 3 3 / 2'' \ 2 Л \’ 0 Рис. 29. Изображение партии на диаграмме позиций пользуемся тем же приемом, с помощью которого мы искали выигрышную стратегию в первом параграфе, — будем действовать с конца, т. е. искать выигрышную стратегию начиная с маленького числа конфет. Если при ходе первого игрока на столе одна или две конфеты, то он выигрывает одним ходом — берет эти конфеты. Давайте закрасим соответствующие клетки первого столбика — это выигрышные позиции для первого игрока. Заодно закрасим и нижнюю клетку второго столбика: она тоже обозначает выигрышную позицию для первого игрока (рис. 28, б). Если же ход второго игрока, то он выигрывает, если на столе одна или две конфеты, но проигрывает, если конфет три. Действительно, при трех конфетах на столе любой ход второго игрока неминуемо приводит в одну из двух закрашенных клеток, где, как мы уже знаем, выигрывает первый игрок. Значит, клетку 3 во втором столбике надо закрасить: это выигрышная позиция для первого игрока (см. рис. 28, б). Вернемся теперь к первому столбику. В клетку 3 второго столбика можно попасть из клеток 4 и 5 первого столбика. Значит, это выигрышные позиции для первого игрока и их надо закрасить (см. рис. 28, б). Это рассуждение легко продолжается дальше на столбики любой высоты: мы поочередно закрашиваем те клетки в правом столбике, ход из которых обязательно приводит в уже закрашенные клетки слева, а затем те клетки в левом столбике, из которых можно попасть в закрашенную клетку справа. Подчеркнем еще раз эту разницу: в правом столбике мы закрашиваем клетки, любой ход из которых приводит в закрашенную клетку левого столбца. Действительно, противник играет так, как нужно ему, и может делать любой ход. А вот в левом столбце мы закрашиваем те клетки, из которых можно сделать ход в закрашенную клетку. Действительно, ход из позиции в левом столбце мы выбираем сами и можем выбрать его так, чтобы попасть в закрашенную клетку. В итоге мы заключаем, что если начальная позиция в игре оказалась закрашенной, то первый игрок выигрывает при правильной игре. Для этого он должен ходить так, чтобы после каждого его хода позиция соответствовала закрашенной клетке. Закрашенные клетки — выигрышные позиции для первого игрока. А вот если начальная позиция оказалась незакрашенной, то тогда при правильной игре выигрывает второй игрок — ему нужно лишь следить за тем, чтобы после его хода позиция оставалась незакрашенной. На рисунке 29 показано изображение партии на диаграмме позиций. Задача 8.10 Докажите, что клетка с любым номером закрашена либо в левом, либо в правом столбце, но только в одном из них. Игровые алгоритмы 129 Задача 8.11 Нарисуйте диаграммы позиций и закрасьте выигрышные позиции для первого игрока при игре в конфеты, если игрок, взявший последнюю конфету, проигрывает. Задача 8.12 Нарисуйте диаграммы позиций и закрасьте выигрышные позиции для первого игрока для игр в конфеты из задач 8.3, 8.4. 5, Как искать выигрышные стратегии (использование симметрии) в некоторых играх не только выписывание полного дерева игры, но и просто выписывание всех позиций — достаточно трудоемкое дело. Значит, и поиск выигрышной стратегии таким способом затруднен. Рассмотрим такую игру — игру в ромашку. Задача 8.13 у ромашки 12 лепестков (рис. 30, о). Ход состоит в том, что игрок обрывает один или два растущих рядом лепестка. Выигрывает тот, кто обрывает последний лепесток. Кто выигрывает при правильной игре — тот, кто начинает, или его противник? Набор всех возможных позиций в этой игре довольно велик. Некоторые из них приведены на рисунке 30, б. Такая позиция определяется тем, какие лепестки у ромашки уже оторваны (и номером игрока, делающего очередной ход). Попробуем найти выигрышную стратегию, не выписывая всех позиций. Снова число 12 слишком велико, чтобы можно было надеяться сразу решить задачу. Начнем с меньшего количества лепестков. Если число лепестков равно 1 или 2, то выигрывает первый игрок — он просто срывает все имеющиеся лепестки. При 3 лепестках выигрыш у второго игрока — при любом ходе первого он срывает один или два оставшихся лепестка. Рис. 30. а) Ромашка для игры в ромашку; б) некоторые позиции в этой игре 130 ГЛАВА 8 а) Рис. 31. Позиции в игре в ромашку: а) с 4 лепестками после хода первого игрока, сорвавшего 2 лепестка; б) с 6 лепестками после хода первого игрока, сорвавшего 1 лепесток Интересная ситуация возникает, когда лепестков 4. Если первый игрок сорвет 2 лепестка, то второму останется только сорвать 2 других и выиграть (рис. 31, а). А что делать второму игроку, если первый сорвал 1 лепесток? Ясно, что 2 лепестка срывать нельзя, — это приведет к проигрышу. А вот если сорвать лепесток, противоположный сорванному первым, то тогда на ромашке останется 2 разрозненных лепестка. Первый игрок может сорвать только один из них, и тогда второй игрок срывает второй. Итак, при 4 лепестках второй игрок выигрывает при любой игре первого. Кроме того, мы установили, что два разрозненных (не соседних) лепестка — выигрышная позиция для второго игрока. Это первое звено выигрышной стратегии. Если лепестков на ромашке 5, то при любом ходе первого игрока второй игрок может добиться выигрышной для себя позиции — оставить два разрозненных лепестка. Поэтому при 5-лепестковой ромашке у второго игрока также есть выигрышная стратегия. Рассмотрим еще случай, когда на ромашке 6 лепестков. Если первый игрок своим ходом срывает 2 лепестка, то второй срывает два противоположных им лепестка и получает выигрышную позицию. А что ему делать, если первый игрок сорвал 1 лепесток и на ромашке осталось 5 лепестков (рис. 31, б)? Ответ: и в этом случае нужно сорвать лепесток, противоположный сорванному первым. Такой ход приводит к успеху потому, что в результате мы получаем симметричную ромашку, и на любой последующий ход первого игрока второй игрок может дать симметричный ответ: сорвать столько же лепестков, что и первый, расположенных симметрично относительно оси ромашки. У ромашки, все лепестки которой целы, много осей симметрии. Но после того как лепестки с одной стороны оторваны, у нее появляется одна выделенная ось симметрии. Симметрию относительно этой оси и нужно поддерживать. Упражнение Проверьте, что при любом другом ответе второго игрока на отрывание одного лепестка у 6-лепестковой ромашки первый игрок может выиграть. Итак, мы уже можем сформулировать выигрышную стратегию для второго игрока: при любом ходе первого игрока нужно оторвать один или два лепестка с противоположного конца диаметра таким образом, чтобы оставшаяся часть лепестков была симметрична относительно этого диаметра. Затем на любой ход противника второй игрок Игровые алгоритмы 131 Рис. 32. Симметричный ответ при игре в ромашку Рис. 33. Отрывание одного лепестка: а) на 12-лепестковой ромашке; б) на 13-лепестковой ромашке должен давать ответ, симметричным относительно того же диаметра (рис. 32). Эта стратегия приводит к выигрышу второго игрока при любом количестве лепестков у ромашки, большем 2. Упражнение Первый игрок оторвал один лепесток на 12-лепестковой ромашке (рис. 33, о). Какой ход должен сделать второй игрок, следуя выигрышной стратегии? А если лепестков было 13 и первый игрок оторвал один лепесток (рис. 33, 6)1 Упражнение Сколько лепестков должно остаться у 37-лепестковой ромашки после первого хода второго игрока, следующего выигрышной стратегии, если первый игрок своим первым ходом сорвал: а) 1 лепесток; б) 2 лепестка? Одной симметрии ромашки для успеха выигрышной стратегии недостаточно. Действительно, ромашка на рисунке 34 симметрична относительно вертикального диаметра, однако если первый игрок своим ходом сорвет верхний лепесток, то она останется симметричной и проиграет уже второй игрок. Помимо симметрии, нужно еще, чтобы любой ход первого игрока лежал либо по одну, либо по другую сторону оси симметрии. Тогда второй игрок сможет сделать симметричный ход в любой ситуации, независимо от действий первого игрока. Этого и добивается второй игрок, срывая своим первым ходом лепестки на противоположном конце диаметра от сорванных первым игроком. Важно понимать, что предложенная выигрышная стратегия не единственная. Могут быть и другие. Например, если число лепестков на ромашке четно, то можно использовать симметрию относительно центра ромашки, а не относительно ее диаметра. Задача 8.14 Опишите выигрышную стратегию второго игрока, опирающуюся на симметрию ромашки с четным числом лепестков относительно ее центра. 132 ГЛАВА 8 Рис. 34. Симметричная ромашка, на которой первый игрок выигрывает Рис. 35. Различные ромашки для построения дерева игры Однако, как правило, перед нами не стоит задача поиска всех выигрышных стратегий. Если хотя бы одна такая стратегия найдена, то ее достаточно, чтобы всегда выигрывать. При этом многие возможные позиции вообще не попадают в поле нашего зрения, за счет чего и уменьшается число возможных позиций. Скажем, при игре в ромашку мы можем ограничиться только симметричными позициями и теми позициями, из которых в симметричные можно попасть за один ход. А вот позиции другого вида приходится анализировать отдельно. Задача 8.15 Постройте полное дерево игры в ромашку на каждой из позиций, изображенных на рисунке 35, укажите, кто выигрывает в каждой из них при правильной игре, и разработайте какую-нибудь выигрышную стратегию для этого игрока. Подсказка Позицию можно записывать последовательностью чисел непрерывно идущих лепестков ромашки. При этом все равно, с какой группы непрерывно идущих лепестков начинать (и даже в каком порядке записывать эти числа). Например, для ромашек с рисунка 35 записи позиций будут выглядеть так: а) 2, 1, 1, 1; б) 4, 1; в) 5. 6. Использование случайности в игровых алгоритмах Представьте себе, что вы играете с компьютером. В компьютер заложен какой-то алгоритм игры. Если этот алгоритм всегда делает одни и те же ходы в ответ на ваши ходы, то очень скоро игра станет неинтересной: скорее всего, вы научитесь выигрывать у такого компьютера, повторяя все время одну и ту же партию. Игровые алгоритмы 133 Чтобы ответ алгоритма нельзя было предсказать, разработчики игровых алгоритмов вводят в них элемент случайности. В результате оказывается, что ход алгоритма выбирается случайным образом из нескольких возможных в данной ситуации ходов, и играть против такого алгоритма становится интереснее. Конечно, выигрышной стратегии такой алгоритм проиграет, но если цель состоит не в том, чтобы выиграть, а в том, чтобы просто получить удовольствие от игры, или выигрышная стратегия неизвестна, то введение случайности очень оживляет игру. _______________________ Справа показана простейшая стратегия игры в конфеты с использованием случайности. Этот игровой алгоритм ходит всякий раз случайным образом, забирая одну или две конфеты. Количество забираемых конфет дается датчиком псевдослучайных чисел случай. По существу, такой способ игры совпадает с тем, как если бы мы перед каждым ходом подбрасывали монетку и забирали одну конфету, если выпадет решка, и две, если выпадет орел. Стратегии, использующие случайность, также можно сравнивать с другими стратегиями и между собой. Задача 8.16 Предложите алгоритм, использующий случайность при игре в ромашку. ПРОЦ первый игрок НАЧАЛО ЕСЛИ п = 1 ТО п = п - 1 ИНАЧЕ случай(а) п = п - 1 - а КОНЕЦ КОНЕЦ ИТОГИ В этой главе мы рассказали о некоторых играх и о некоторых игровых алгоритмах. Играют два игрока, которые ходят по очереди и знают все о положении в игре. Игра обязательно заканчивается выигрышем одного из игроков. Игровые алгоритмы предназначены для задания поведения игрока в игре — его стратегии. Если игра не очень сложная, то в ней удается найти выигрышную стратегию. Выигрышная стратегия выигрывает у любой другой стратегии, если такая возможность существует. Для поиска выигрышной стратегии можно использовать полное дерево игры, однако для большинства игр это дерево оказывается слишком большим. Диаграмма позиций меньше полного дерева, и она пригодна для поиска выигрышной стратегии в большем количестве игр. Иногда полный анализ игры можно заменить другими соображениями, например воспользоваться симметрией. Глава 9 Работа с цепочками символов До сих пор мы работали с числами и массивами чисел. Однако на практике этого недостаточно: фамилии людей и названия книг, слова в словарях и названия химических элементов, названия футбольных команд и банковских операций — все это нечисловые данные, а компьютер должен уметь обращаться и с ними. Да и сами команды в языках программирования тоже могут служить значениями переменных в специальных программах, которые переводят программы с языка программирования на внутренний язык компьютера (такие программы называются трансляторами). Вот поэтому все языки программирования имеют средства для работы с отдельными символами и с цепочками символов — словами. Мы расскажем вам, как устроена работа с символами в нашем алгоритмическом языке. В большинстве языков программирования она выглядит похожим образом. Т]| Работа с символами Каждая символьная переменная предназначена для хранения последовательности символов. Цепочка символов может состоять из одного или нескольких символов. Чтобы отличить символьные переменные от числовых, они будут начинаться символом $. Вот примеры символьных переменных: $буква, $symbol, $i, $answer, $вопрос, $ответ. Как и числовым переменным, символьным переменным можно присваивать значения командой присваивания. Вот как это происходит: $буква = "М" $ответ="нет" $буква = "Т" $вопрос= "?" $имя = "Андрей" Справа от знака присваивания может стоять любая последовательность символов, которые есть на клавиатуре компьютера, заключенная в кавычки. Вы, наверное, уже поняли, зачем заключать цепочки символов в кавычки — чтобы не перепутать их с именем переменной. Если написать просто $буква = м, то мы могли бы решить, что символьной переменной $буква нужно присвоить значение числовой переменной м. А если м заключено в кавычки, то никаких проблем не возникает. Работа с цепочками символов 135 Как и числовым переменным, одной символьной переменной можно присвоить значение другой символьной переменной. Например, команда $буква = $letter присваивает переменной $буква значение переменной $letter. Если перед выполнением этой операции переменная $буква имела значение "ВВВ", а переменная $lettet-значение "W", то после ее вы- полнения обе переменные будут иметь значение "W". Символьную переменную можно представлять себе как массив переменных, в каждой из которых записан отдельный символ. Как и отдельные элементы числовых массивов, буквы слова можно брать по отдельности. Так, если в переменной $имя записано слово "Андрей", то элемент массива $ыжя[2] — это буква "д", третья буква в имени "Андрей", (Нумеровать символы мы начинаем с 0.) 'Щ Упорядочение символов и слов ^ Складывать, умножать и делить буквы незачем. Поэтому арифметические операции не распространяются на символьные переменные. А вот сравнивать буквы очень полезно: это может пригодиться для составления разных словарей и списков и работы с ними. Действительно, в словарях слова упорядочены по алфавиту. Упорядочивание очень облегчает поиск в них. Так же упорядочивают имена в телефонных книгах, станции назначения в железнодорожном расписании, книги в библиотеках по имени автора и много чего другого. Вот фрагменты словаря и библиотечного каталога: Фрагмент русско-английского словаря: албанский язык Albanian, the Albanian language алгебра ж. algebra алгоритм м. мат. algorithm алгоритмический м. мат. algorithmic алебарда ж. halberd алебастр м. alabaster Фрагмент библиотечного каталога: Абелевы многообразия. Пер. с англ, библ. : 33. Автор: Мамфорд Д. Абракадабры. Декодировка смысла. 160 с. Автор: Вашкевич Н. Н. М. : Мир, 1971.— 300 с. • М. : Белые альвы, 1998. 136 ГЛАВА 9 Абсолютная величина: пособие для учителя. — М. : Просвещение, 1968. — 96 с. Автор: Гайдуков И. И. Условия сравнения символов такие же, как и условия сравнения чисел. Понятно, как проверять символьные переменные на равенство. Условие $/ ="Я" истинно, если значение переменной $/ равно букве "Я", и ложно, если это не так. Аналогично, условие $а = $Ь истинно, если значение переменной $а равно значению переменной $Ь, и ложно, если они не равны. Истинность условия О противоположна истинности условия =. Например, после выполнения команд $/ = "Яма" $к="Канава" условие $/[1] о "Я" ложно, а условие $/ <> $к истинно. Сложнее сказать, какое из слов больше: "Яма" или "Канава". Мы научимся сравнивать слова в два этапа. Сначала договоримся, как сравнивать отдельные буквы. Сравнивать числа просто. Дело в том, что у чисел есть естественный порядок: 3 > 2, потому что среди трех любых предметов обязательно есть два предмета. А вот естественных причин для того, чтобы сказать, что буква "К" больше или меньше буквы "С", нет. Но договоренность о порядке букв в алфавите существует много веков. Мы будем говорить, что одна буква больше другой, если эта буква стоит в алфавите позже. Например, "С" > "К", "Э" > "Ь", "В" < "Г", "С" < "Т". Самая маленькая буква в алфавите — это "А", а самая большая — это "Я". Прописные буквы русского алфавита АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЬЭЮЯ Упражнение Напишите для каждого из утверждений, истинно оно или ложно: а) "М" > "Ы"; б) "У" > "О"; в) "С" < "К"; г) "П" > "Й". Перейдем к упорядочению слов. В словарях все слова, которые начинаются с буквы "Б", идут за словами, которые начинаются с буквы "А". Значит, слова, начинающиеся с буквы "Б", больше, чем слова, начинающиеся с буквы "А". А как сравнить два слова, начинающиеся с одинаковой буквы? Вы знаете, как это делается. Посмотрим на вторую букву. То слово, в котором она больше, и идет в словаре дальше. А если и вторые буквы одинаковы, посмотрим на третьи буквы, и так далее, пока не найдем различающиеся буквы или пока одно из слов не кончится. Слово, которое кончилось первым, мень- Работа с цепочками символов 137 ше. Например, слово "ДОМ" идет в словаре раньше слова ’ДОМОВОЙ": первые три буквы в них совпадают, но слово "ДОМОВОЙ" продолжается, тогда как слово "ДОМ" уже кончилось. А слово "СЕРЕБРО" идет раньше слова "СЕРЕБРЯНЫЙ": первые шесть букв в обоих словах одинаковы, но седьмая буква в слове "СЕРЕБРО" — это "О", которая идет по алфавиту раньше седьмой буквы в слове "СЕРЕБРЯНЫЙ" — буквы "Я". Чтобы лучше понять это правило, давайте составим процедуру, которая сравнивает два слова, используя сравнение символов. Пусть первое слово хранится в переменной $слово1, его длина — в переменной Л, второе слово — в переменной $слово2, а его длина — в переменной 12. Часть процедуры выглядит так: ПРОЦ сравни слова НАЧАЛО ЕСЛИ /1 < 12 ТО п = 1 /= 1 ПОКА /• <= /1 И $слово1[/] = $слово2[/] ДЕЛАТЬ I = / + 1 КОНЕЦ ЕСЛИ / <= Л ТО ЕСЛИ $слово1[/] > $слово2[/] ТО п = 2 КОНЕЦ КОНЕЦ КОНЕЦ КОНЕЦ Многоточием обозначены команды, сравнивающие два слова, длина первого из которых больше либо равна длине второго. По окончании работы процедуры переменная п должна содержать номер меньшего слова, т. е. 1 или 2. Посмотрим, что делает эта процедура. Написанная часть работает в случае, когда длина первого слова меньше длины второго слова. В переменную п заносим 1. Если второе слово окажется меньше первого, то мы занесем в нее значение 2. В переменной / хранится номер сравниваемых в словах букв. Цикл ПОКА сравнивает слова буква за буквой. Он работает до тех пор, пока либо не кончится первое — более короткое — слово, либо в словах не обнаружатся две различные буквы. В следующей команде ЕСЛИ мы начинаем с проверки того, по какой причине остановилось выполнение цикла ПОКА. Если #>/1, то это произошло из-за того, что первое слово кончилось. Это означает, что начало второго слова совпадает с первым. Но второе слово длиннее, значит, оно больше, и мы можем ничего не делать — оставить значение переменной п равным 1. 138 ГЛАВА 9 Задача 9.2 Если же / <= /1, то это означает, что мы наткнулись на несовпадающие буквы. Это буквы с номером i. Их надо сравнить, и если буква в первом слове оказалась больше буквы второго слова, то меньшим является второе слово и мы должны присвоить переменной п значение 2. Упражнение Чему равны значения переменных i и п в конце выполнения процедуры сравни слова, если входные слова — это: а) "ДОМ" и "ДОМОВОЙ"; б) "СЕРЕБРО" и "СЕРЕБРЯНЫЙ"; в) "ОГОРОД" и "ОГИБАЮЩАЯ"? Задача 9.1 Допишите процедуру сравни слова, вставив в нее: а) команды, сравнивающие слова в случае, когда длина второго слова меньше длины первого; б) команды, сравнивающие слова с равными длинами; если при этом слова оказываются равными, то по окончании работы процедуры значение переменной п должно быть равным 0. Воспользовавшись процедурой сравни слова, напишите процедуру упорядочи три слова, которая печатает значения трех символьных переменных $слово1, $слово2, $словоЗ в алфавитном порядке. Например, результатом вызова процедуры упорядочи три слова со значениями переменных $слово1 = "УЛЙЦА", $слово2 = "ФОНАРЬ", $словоЗ = "АПТЕКА" должны стать три строчки АПТЕКА УЛИЦА ФОНАРЬ Порядок совпадающих слов неважен. В этой главе нам достаточно будет сравнивать только такие цепочки символов, значения которых состоят из прописных (заглавных) русских букв. Но цепочки символов, как уже говорилось, могут содержать и другие символы. Поэтому скажем коротко о правилах сравнения таких символов (а сравнение цепочек, в которые входят такие символы, происходит в том же словарном порядке, который описан выше). Помимо русского алфавита, часто используется латинский алфавит. Это алфавит многих языков (например, английского). Латин- Работа с цепочками символов ]39 ский алфавит тоже упорядочен. В нем самая маленькая буква — это "А", а самая большая — "Z". Прописные буквы латинского алфавита ABCDEFGHIJKLMNOPQRSTUVWXYZ А что делать, если нам придется сравнивать буквы латинского и русского алфавитов? Или строчные и прописные буквы? Давайте постараемся этого не делать. Но если придется — договоримся на всякий случай, что все буквы латинского алфавита меньше всех букв русского, а в каждом из алфавитов все прописные буквы меньше (раньше) всех строчных. Например, русское "А" следует за латинским "Z", но перед русским "б", русское "Я" стоит перед русским "а ". Обратите внимание на то, что процедура сравни слова пригодна и для сравнения таких символьных переменных, в которых смешаны буквы разных алфавитов, а также строчные и прописные буквы. Упра жнение Какие из условий: а) "БОш”>”БцШ"; б) "Radio">”Pa-дио"; в) "Hst"<"nA"; г) "я"<"кп1Ге" — истинны? В компьютерах используется двоичная, битовая система представления информации, поэтому при занесении в компьютер данных любого вида — чисел, текстов, изображений — они автоматически переводятся в последовательности из нулей и единиц. При кодировании текстов каждый символ (буква, цифра, знак препинания и т. д.), как правило, представляется последовательностью из восьми нулей и единиц — одним байтом памяти. Поскольку в байте 8 битов и каждый из них может принимать два значения — О и 1, каждый байт может кодировать один из 2^ = 256 символов. Набора из 256 символов хватает для удобной работы с большинством видов текстов. Сюда укладываются 64 буквы русского алфавита (по 32 строчных и прописных букв) и 52 буквы латинского алфавита (по 26 строчных и прописных букв), 10 цифр, знаки препинания и многие вспомогательные символы. Коды символов можно выбирать по-разному. При этом было бы разумно следовать такому общему принципу: порядок кодов букв совпадает с их порядком в алфавите. При соблюдении этого принципа с точки зрения компьютера сравнение символов ничем не отличалось бы от сравнения чисел. По разным причинам это правило не всегда соблюдается. На форзаце приведены десятичные и двоичные значения кодов заглавных русских и латинских букв в одной из наиболее распространенных систем кодирования, системе КОИ8-Р. Для полноты картины добавим сюда же коды некоторых строчных букв и небуквенных символов и коды некоторых букв в системе Windows-1251 (см. форзац). 140 ГЛАВА 9 'Щ Понятие о кодировании и шифрах Что вы подумаете, получив на уроке следующую записку? ЖЗИЗСФХГХЯЕСОИМДСОЮРЮМПВЪФГЯГ Вы можете подумать, что кто-то из друзей хочет сообщить вам что-то важное, но не хочет при этом, чтобы, попав в чужие руки, сообщение могло быть прочитано. Для этого отправитель зашифровал записку, сделав ее смысл недоступным для всякого, кто не знает секрета. Но и вы не можете ее прочитать, если не знаете этого секрета. А вот если секрет известен вам обоим и никому больше, то вы можете спокойно обмениваться информацией, не опасаясь, что кто-нибудь чужой сможет ее прочитать. Что именно написано в записке, мы узнаем позже. А пока обсудим, для чего может понадобиться шифрование. Все вы смотрели фильмы или читали книжки про разведчиков и шпионов. Их главная задача — раздобыть сведения о противнике. Но добытые сведения надо передать своему руководству. И это нужно сделать так, чтобы противник не знал, кто именно передал сведения и что именно стало известно другой стороне. Да и руководители разведчиков должны иметь возможность передать им указания таким образом, чтобы противник не узнал, в чем эти указания состоят. Конечно, добыча военных секретов не входит в повседневные обязанности большинства жителей современного мира. Но шифрование все активнее вторгается в нашу жизнь, и в этом процессе все активнее используются компьютеры. Компьютерные системы, пересылающие письма по электронной почте, шифруют их, чтобы их содержание нельзя было подменить. Банковские компьютерные системы шифруют сообщения, передающиеся между банком и клиентами, чтобы никто не смог снять со счета или перевести на другой счет чужие деньги. Государственные организации шифруют собираемую ими информацию о гражданах с тем, чтобы она не попала в руки людей, не имеющих на это права. Итак, задача шифрования заключается в том, чтобы защитить передаваемую информацию от тех, кто не имеет права ею владеть. Кодирование, в отличие от шифрования, не преследует своей целью скрытие информации. Его задача — представить информацию в более удобной для обработки форме. Общепринятые системы кодирования символов, о которых шла речь в предыдущем разделе, широко известны, и их использование не предполагает сокрытия информации. А вот кодирование с помощью системы, известной немногим, уже может рассматриваться как — хотя и очень простой — способ шифрования. Работа с цепочками символов 141 Один из таких способов и был использован для того, чтобы зашифровать полученную вами записку. При ее шифровании каждую букву заменили третьей от нее буквой в алфавите: букву ”А" заменили буквой "Г" (между ними стоят буквы "Б” и "В”), букву "Б" — буквой ”Д” (между ними стоят буквы "В" и ”Г") и т. д. Буквы, стоящие в конце алфавита ("Э", "Ю", "Я"), заменяются на начальные буквы алфавита ("Э" на "А", "Ю" на "Б", "Я" на "В"). Другими словами, мы считаем, что буквы русского алфавита идут по кругу, так что за "Я" следует "А". Посмотрите на рисунок 36. Буквы на этом рисунке расставлены в двух концентрических кольцах. Шифрование состоит в том, что мы заменяем букву внутреннего кольца на соответствующую ей букву внешнего кольца. Для восстановления исходного текста записки нужно заменить каждую букву в ней третьей буквой в алфавите впереди нее (переходить от буквы на внешнем кольце к соответствующей букве на внутреннем кольце). Поэтому начало записки выглядит так: ГДЕДОСТАТЬВО Обратите внимание на то, что все слова в записке идут сплошь, без пробелов. Это позволяет не придумывать специальный способ кодирования пробелов и затрудняет расшифровку записки тем, кто не знает шифра. Рис. 36. Кольца для шифра замены Задача 9.3 Расшифруйте записку до конца, исправив при этом ошибки в шифровке. Задача 9.4 Зашифруйте тем же шифром ответ НЕВОЛНУЙСЯЯПРИНЕСУСВОЙАНДРЕЙ Напишем программу, кодирующую тексты в соответствии с этим способом кодирования. Для этого нам пригодится операция но-мер(<буква>), которая вычисляет номер буквы в алфавите. Например, номерС'А") = О, номерС'Б") = 1, номер("Э") = 30 и т. д. Заведем массив 142 ГЛАВА 9 $ТаблицаШифра из 33 символов алфавита, сдвинутых по кругу на 3 позиции, и предположим, что шифруемый текст хранится в переменной $Текст, а его длина хранится в переменной I. Шифровать теперь становится легко: ПРОЦ замш*а1 НАЧАЛО ; $ТаблицаШифра = ТДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯАБВ / = О ПОКА / <= / ДЕЛАТЬ $Гехсг[/] = $ТаблмцаШифра[номер{$Текст[1])] i = i + ^ КОНЕЦ КОНЕЦ Эта процедура заменяет каждую букву в шифруемом тексте буквой с тем же номером в таблице шифра. В результате каждая буква сдвигается на 3 позиции по алфавиту, что нам и требуется. Упражнение Что нужно изменить в процедуре шифрования, чтобы исходный текст не менялся, а зашифрованный текст хранился в другой переменной? Задача 9.5 Напишите процедуру расшифровывания, восстанавливающую исходный текст по шифровке. Комментарий Операцию номер(<буква>), вычисляющую номер буквы в алфавите, несложно написать самостоятельно. Самая простая процедура для этого ищет нужную букву в массиве, содержащем все буквы в алфавитном порядке, подсчитывая при этом номер буквы. Однако такая процедура не очень эффективна: поиск букв, особенно стоящих в конце алфавита, требует большого количества операций. А ведь при шифровании операция номер выполняется очень часто — для каждой буквы входного текста. Если мы знаем, как буквы закодированы в компьютере, то поиск можно заменить более простыми операциями. ^ Простейшие алгоритмы шифрования Как мы уже говорили, кодирование можно рассматривать как самый простой способ шифрования. При кодировании каждый символ алфавита заменяется другим символом (того же или другого алфавита). Одну из таких замен мы и использовали. Обратите внимание на Работа с цепочками символов 143 то, что единственное место в нашей процедуре шифрования, где использовался сдвиг на три символа, — это способ составления таблицы $ТаблицаШифра. Мы могли бы заменить эту таблицу любой другой перестановкой букв алфавита. Работа процедуры от этого не изменилась бы, а вот результат был бы другим. Чем же сдвиг на три позиции (или другое их количество) в алфавите выгоднее других замен? Для компьютера разницы нет — число выполняемых им операций не зависит от выбора таблицы. А вот человеку это правило легче усвоить — нужно запомнить всего одно число. Вот более сложный вариант того способа кодирования, который мы использовали в шифрованной записке в предыдущем разделе. В этом варианте вместо сдвига на постоянную величину используется сдвиг, величина которого зависит от положения буквы во фразе. Например, мы можем сдвигать все буквы с нечетным номером на две позиции, а все буквы с четным номером — на пять позиций. Соответствующая программа будет выглядеть так: ПРОЦ замена2 НАЧАЛО $ТаблицаШифраНечет = "ВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯАБ" $ТаблицаШифраЧет = "ЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯАБВГД" /= 1 ПОКА i <= I ДЕЛАТЬ $Teifcr[/] = $ТаблицаШифраНечет[номер{$Текст[1])] /= /+ 1 $Техст[/] = $ТаблицаШифраЧет[номер{$Текст[!])] /■ = / + 1 КОНЕЦ КОНЕЦ Задача 9.6 Процедура заменаЗ хорошо шифрует фразы из четного числа букв, а вот фразы с нечетным количеством букв обрабатывает не так хорошо. Почему это так? Исправьте ошибку. Вот начало шифровки, в которую процедура заменаЗ превратит шифровавшуюся нами фразу: ЕКЗКРЦФЕФБ Задача 9.7 Завершите шифрование. Задача 9.8 Напишите процедуру расшифровки текстов, зашифрованных процедурой замена2. Будем записывать величины сдвига в виде числа. Так, сдвигу букв с нечетными номерами на две позиции, а с четными на пять соответствует двузначное число 25. Можно шифровать и с помощью 144 ГЛАВА 9 более длинных чисел. Например, в романе Жюля Верна «Жангада» признание преступника было зашифровано с помощью шестизначного числа 432513. Это означает, что первая буква фразы заменяется отстоящей от нее на 4 позиции, вторая — на 3, третья — на 2 и так до шестой, седьмая опять на 4, восьмая — на 3, и все повторяется. Такое число называется ключом шифра. Знание ключа позволяет зашифровать сообщение и расшифровать его. А вот, не имея ключа, расшифровать сообщение сложно, а если оно короткое, то и невозможно. Шифры, которые заменяют каждую букву сообщения другой буквой, сохраняя их порядок, называются шифрами замены. Но это не единственный вид шифров. Шифры другого вида не меняют буквы, но переставляют их внутри сообщения. Такие шифры называются шифрами перестановки. Давайте, например, разобьем буквы сообщения на пары и поменяем местами буквы внутри каждой пары. Вот во что превратится при этом наше сообщение: ДГДЕСОАТЬТОВЕЛБЙЕОБЬЛЫНМЙЧЯАСАШ Можно ли догадаться, что здесь написано? А вот и процедура такого шифрования: ПРОЦ перестановка! НАЧАЛО /= 1 ПОКА /<= / ДЕЛАТЬ %Буква = $Гекст[/] $Текст[/] = $Гекст[/ +1] $Текст[| + 1 ] = $Буква 1 = i + 2 КОНЕЦ КОНЕЦ Обратите внимание на то, как мы меняем две буквы местами. Для этого нам приходится пользоваться дополнительной переменной $Буква. Задача 9.9 Здесь мы наталкиваемся на ту же ошибку, что и в процедуре замена2: процедура перестановка! хорошо обрабатывает тексты из четного числа букв и ошибается на текстах нечетной длины. Исправьте эту ошибку. Буквы можно переставлять и более сложным образом — разбивая сообщение не на пары букв, а, скажем, на пятерки и переставляя буквы внутри каждой пятерки. Способ перестановки букв и является ключом такого шифра. Вот что получается при применении одного из таких алгоритмов шифрования к нашему сообщению: ДДОЕГТТЬАСОЕЙЛВОЬНЛБЙЯЧМЫААШС Работа с цепочками символов 145 Задача 9.10 Какая перестановка пяти букв применялась при этом шифровании? В букву с каким номером переходит первая буква? вторая буква? третья буква? четвертая буква? пятая буква? Обратите внимание на то, что последняя группа букв в нашем сообщении состоит из четырех, а не из пяти букв. Переставляя буквы этой группы, мы пропускаем те из них, номер которых больше количества букв в последней группе. Задача 9.11 Напишите процедуру, реализующую этот алгоритм шифрования. Задача 9.12 Напишите процедуру расшифровки зашифрованных таким образом текстов. Шифры замены и перестановки можно комбинировать. Можно, например, сначала произвести замену каждой буквы в соответствии с ключом замены, а затем еще переставить буквы с помощью некоторого ключа перестановки. Задача 9.13 Зашифруем сообщение с помощью шифра замены с ключом 3, а затем результат зашифруем с помощью шифра перестановки, переставляющего буквы внутри пары. Будет ли полученное сообщение совпадать с результатом шифрования, при котором мы сначала выполняем перестановку, а потом замену? А если мы воспользовались шифром замены с ключом 25? Если у нас есть процедуры для выполнения замены и перестановки, то составить процедуру, выполняющую сначала замену, а потом перестановку, очень легко — нужно просто применить процедуру перестановки к результату работы процедуры замены. Задача 9.14 Напишите процедуру шифрования, в которой сначала выполняется перестановка, а потом замена. Задача 9.15 Как должна выглядеть процедура расшифровки текстов, зашифрованных процедурой из задачи 9.14? Разгадывание алгоритмов шифрования и дешифровка Разработчик шифра стремится сделать его таким, чтобы человек, не знающий ключа, не смог прочитать зашифрованное сообщение. Но противник (контрразведка или преступник) прилагает все усилия к расшифровке. Поэтому разработчик должен представлять себе мето- 146 ГЛАВА 9 ды, которыми можно взламывать шифры, чтобы предотвратить возможный взлом. Ведь взломанный шифр служит источником бед для того, кто им пользуется: вся секретная информация становится тут же известной противнику. Самые простые методы в.злома шифра напоминают игру по отгадыванию алгоритма, которой мы уже занимались, — надо найти алгоритм, зная результат его работы при тех или иных входных данных. Например, если мы знаем сообщение и результат шифрования этого сообщения с помощью шифра перестановки, то ключ шифра — саму перестановку — найти несложно. Задача 9.16 Чему равен ключ шифра перестановки для следующих пар сообщение — результат его шифрования: а) "ШИФРПЕРЕСТАНОВКИНЕНАДЁЖЕН" и "ФШИЕРПСРЕНТАКОВЕИНДНАЕЖЁН"; б) "ИЩИИСТИНУВТЕКСТАХ" и "ИИИЩИНСТТЕУВТАКСХ"; в) "ЕСТЬМНОГОРАЗНЫХПЕРЕСТАНОВОК" и " НМЬТСЕЗАРОГОРЕПХЫНОН АТСЕКОВ" ? А вот если мы знаем только результат шифрования, то для поиска ключа приходится искать совсем другие методы. Вообще говоря, шифры перестановки относительно просты, а шифры замены — если ключ достаточно длинный — довольно сложны для взламывания. Причина этого в следующем. Допустим, мы знаем, что длина ключа равна 6. Существует всего 6! = 6 • 5 • 4 • 3 • 2 • 1 = 120 перестановок такой длины. Проверив их все поочередно, мы найдем ту из них, которая приводит к осмысленному тексту. Для компьютера это несложная работа, да и вручную она вполне выполнима. А вот ключей сдвига той же длины уже 10® = 1 000 000, т. е. один миллион. Проверка их всех чрезвычайно трудоемка. А если мы разрешаем сдвиги не только в пределах десятка, но и в пределах всех 33 букв алфавита, то вариантов становится еще больше: 33® = 1 291 467 969 > 1 000 000 000, и вручную их никак не переберешь. Нетрудно догадаться, что и современные компьютеры не способны найти такой ключ за разумное время. Задача 9.17 Каждое из следующих сообщений представляет собой результат шифровки с помощью двузначного ключа замены. Найдите этот ключ в каждом из случаев и расшифруйте сообщение: а) "СЧСЩРЙХТТИЙЛВМВЫЮЁФЧФБКЭТ"; б) "ФШНТХФЧУИЙЧНЩБШЧХПТГЮЙР"; в) "ЭЖЫЖТМЮППЛВКЭХЦПКЛЪННЩБЁСКТВ”. Работа с цепочками символов 147 При использовании сложных шифров одного зашифрованного сообщения не хватает для выяснения ключа. Даже если сообщений много, их может оказаться недостаточно. Многие шифры использовались годами, несмотря на предпринимаемые для их взлома значительные усилия. Идею «идеального» шифра можно объяснить, если вспомнить о случайности. Представьте, что у вас и у вашего корреспондента есть общий секрет: длинный файл, заполненный случайными числами. Тогда этот файл можно использовать для сверхстойкого шифрования. Поясним эту идею на примере разобранных нами шифров замены. Пусть в файл записаны равновероятно и независимо числа от О до 32. Шифруя сообщение, будем делать сдвиг на очередное число, прочитанное из файла. Тогда полученная после такого шифрования последовательность тоже будет состоять из столь же случайной последовательности букв. Не зная секретного файла, расшифровать эту последовательность можно лишь наугад. Так как буквы в разных позициях появляются независимо, то даже если при расшифровке повезет и станут известны некоторые буквы, это не поможет в расшифровке остальной части сообщения. Чтобы такая идея давала абсолютно «стойкий» шифр, использовать каждое случайное число можно только один раз. Поэтому для шифрованной переписки при помощи такого метода нужно постоянно обновлять запасы общих секретных файлов. Но если можно безопасно передавать длинные файлы, то к чему вообще шифрование? Так что стойкость шифра вступает в противоречие с удобством его использования. ИТОГИ в этой главе мы научились работать с символьными переменными. Значение такой переменной — буква, слово или даже фраза, в которую могут входить произвольные знаки. Мы также познакомились с самыми простыми алгоритмами шифрования и написали несколько процедур, реализующих эти алгоритмы. Цель шифрования — помешать прочитать информацию людям, которым она не предназначена. Мы обсудили два класса шифров — шифры замены и шифры перестановки. Шифр замены задается своим ключом, в котором записаны величины сдвига каждой буквы сообщения. Шифр перестановки также задается ключом — это перестановка, определяющая правило перестановки букв внутри каждой группы. Человек, знающий ключ шифра, легко расшифрует зашифрованное сообщение, а вот, не зная ключа, с расшифровкой придется повозиться. Словарь понятий алгоритмики Алгоритм — инструкция, точное описание способа действия через отдельные простые общепонятные элементы. Благоприятный исход (для данного события) — такой исход испытания, при котором событие произошло. Вероятность события — число между О и 1, характеризующее возможность того, что событие произойдет. При равенстве вероятностей всех возможных исходов вероятность события равна отношению числа благоприятных исходов к числу всех возможных исходов. Выигрышная стратегия — стратегия игрока, позволяющая ему выиграть при любых действиях противника. Генератор чисел — программа, печатающая последовательность чисел. Дерево игры — совокупность всех возможных позиций в игре и всех возможных ходов игрока в каждой позиции. Дешифровка — восстановление исходного текста по результату его шифрования. Диаграмма позиций — совокупность всех возможных позиций и ходов в игре. Исполнитель — устройство для выполнения определенного набора команд. Код — таблица или правило, сопоставляющее каждому символу из некоторого алфавита некоторый символ другого алфавита. Команда присваивания — команда вида <переменная> = <выражение>. Слева от знака равенства должна стоять переменная, справа — некоторое выражение. Выполнение команды присваивания состоит в том, что выражение вычисляется и результат вычисления присваивается переменной. Конструкция — средство для построения программы в алгоритмическом языке. Массив — совокупность переменных, различающихся номерами — целыми неотрицательными числами. Независимые события — два события, вероятность наступления каждого из которых не зависит от того, наступило ли второе событие. Основание позиционной системы счисления — количество используемых в такой системе цифр. ОТКАЗ — состояние, в котором Исполнитель не может выполнить очередную команду программы. Словарь понятий алгоритмики 149 Пароль — последовательность символов, открывающая доступ к некоторому ресурсу (компьютеру, программе, банковскому счету и т. д.). Переменная — имя, которое может иметь значение. Значение переменной может изменяться. Программа — алгоритм действия Исполнителя, записанный на алгоритмическом языке или на другом языке программирования. Процедура — специальным образом оформленная программа с именем, которое можно использовать в записи других программ. Псевдослучайная последовательность — последовательность чисел или символов, полученная в результате работы алгоритма, используемая как случайная последовательность. Рекурсия — вызов процедурой самой себя непосредственно или через другие, промежуточные процедуры. Случайная последовательность — последовательность чисел или символов, полученная в результате случайного процесса (подбрасывания монетки, бросания кубика и т. д.). Случайность — характеристика событий, которые невозможно предсказать заранее. Синтаксические правила — правила записи алгоритмов (программ) на алгоритмическом языке. Состояние Исполнителя — полный набор сведений об Исполнителе в некоторый момент выполнения программы. Стратегия в игре — правило, определяющее ход игрока в зависимости от ситуации в игре. Числовое выражение — синтаксически правильная последовательность чисел, переменных, знаков операций и скобок, значение которой можно вычислить, если каждую переменную заменить ее значением. Шифр — алгоритм преобразования текста, предназначенный для того, чтобы скрыть содержание текста от лиц, не имеющих права его знать. Шифрование — преобразование текста согласно выбранному шифру. Элементарный исход — возможный результат испытания. Эффективность программы — число шагов работы Исполнителя при выполнении данной программы. г г-:^^ ■ I й=-'-' ■■ . 4- V ■ - ' '' fi; Щ ■ - ■ 'Г ^ А - задачник Справочник по алгоритмическому языку 'ч ^ ' 1. Программы Программа состоит из команд. Как правило, команда занимает одну строку, а выполняются команды в том порядке, в каком они записаны в программе. Исключениями из этого правила являются конструкции, которые определяют особый порядок выполнения. В конструкциях для управления порядком выполнения команд используются условия для чисел и символов. 1.1. Числа Числа задаются в программе числовыми (арифметическими) выражениями. Числовое выражение может быть: • записью числа; • значением переменной или элемента массива; • результатом операции. Запись числа Целые числа записываются в десятичной позиционной системе. Дробное число записывается десятичной или обыкновенной дробью. При записи десятичных дробей в качестве разделителя используется точка, при записи обыкновенных дробей — косая черта (/). Операции с числовыми (арифметическими) выражениями: Сложение <арифметическое выражение> + <арифметическое выражение> Вычитание <арифметическое выражение> - <арифметическое выражение> Умножение <арифметическое выражение> * <арифметическое выражение> Деление <арифметическое выражение> / <арифметическое выражение> Частное от деления с остатком частное(<арифметическое выражение>, <арифметическое выражение>) 152 СПРАВОЧНИК ПО АЛГОРИТМИЧЕСКОМУ ЯЗЫКУ Остаток остаток(<арифметическое выражение>, <арифметическое выражение>) Взятие целой части арифметического выражения целая часть(<арифметическое выражение>) Взятие дробной части арифметического выражения дробная часть(<арифметическое выражение>) При делении (обычном или с остатком) на нуль возникает ОТКАЗ. Есть операция, сопоставляющая символу число: номер(<символ>) Эта операция дает номер буквы в алфавитном порядке или числовое значение цифры. Если символ не является буквой или цифрой, возникает ОТКАЗ. Правила вычисления значения числового выражения Если числовое выражение содержит несколько арифметических операций, то порядок их выполнения определяется старшинством операций. Старшими являются операции умножения и деления, а младшими — операции сложения и вычитания. Операции одинакового старшинства выполняются по очереди слева направо. Чтобы изменить порядок выполнения действий, применяются скобки. Всегда вначале вычисляется значение выражения, стоящего в скобках. 1.2. Символы Символ — буква, цифра, пробел или любой другой знак, который можно ввести с клавиатуры. В программе можно указать последовательность символов, записав ее в кавычках. 1.3. Переменные Программа может использовать вместо чисел значения переменных. Переменная — это последовательность строчных букв, цифр и пробелов, начинающаяся с буквы (латинского или русского алфавита) и заканчивающаяся буквой или цифрой. Любое количество пробелов считается за один пробел. Массив — это совокупность переменных (элементов массива), пронумерованных неотрицательными целыми числами. Элемент массива записывается в программе в виде <массив>[<номер элемента>] Программа может использовать вместо последовательностей символов значения символьных переменных. Символьная переменная СПРАВОЧНИК ПО АЛГОРИТМИЧЕСКОМУ ЯЗЫКУ 153 начинается со знака $, за которым следует последовательность строчных букв, цифр и пробелов, начинающаяся с буквы (латинского или русского алфавита) и заканчивающаяся буквой или цифрой. Любое количество пробелов считается за один пробел. Одиночный символ из значения символьной переменной записывается в виде <символьная переменная>[<номер символа>] В начале работы программы у переменных значений нет. Если использовать в программе переменную, которой не присвоено значение, то возникает ОТКАЗ. 1.4. Команды Помимо команд конкретных Исполнителей, в программе могут использоваться команды присваивания и порождения псевдослучайного числа. Команды присваивания Есть четыре вида команд присваивания. Присваивание переменной < переменная> = <арифметическое выражение> Присваивание элементу массива <массив>[<арифметическое выражение>] = <арифметическое выражение> Присваивание символьной переменной <символьная переменная> - ”<последовательность символов>" Присваивание символа в символьной переменной <символьная переменная >[<арифметическое выражение>] = <символ> Если указанное число больше длины символьной переменной на единицу, то длина символьной переменной увеличивается на единицу, а ее значение становится равным прежнему значению, к которому приписан указанный в правой части присваивания символ. Если указанное число превышает длину переменной больше чем на единицу, то возникает ОТКАЗ. Если значение правой части не является символом, то также возникает ОТКАЗ. Команда получения псевдослучайного числа Команда _______________________________ случай(<переменная>) присваивает переменной одно из двух чисел — О или 1. Эти числа являются псевдослучайными, т. е. имитируют независимые подбрасывания монеты. 154 СПРАВОЧНИК ПО АЛГОРИТМИЧЕСКОМУ ЯЗЫКУ 1.5. Условия Условия имеют два возможных значения: ИСТИНА и ЛОЖЬ. Условия бывают простые и сложные. Сложные условия составляются из простых с помощью логических операций И, ИЛИ, НЕ. Правила вычисления истинности сложного условия: ИСТИНА И ИСТИНА = ИСТИНА ИСТИНА И ЛОЖЬ = ЛОЖЬ ЛОЖЬ и ИСТИНА = ЛОЖЬ ЛОЖЬ и ложь = ложь ИСТИНА или ИСТИНА = ИСТИНА ИСТИНА ИЛИ ЛОЖЬ = ИСТИНА ЛОЖЬ или ИСТИНА = ИСТИНА ЛОЖЬ или ложь = ложь НЕ ИСТИНА = ЛОЖЬ НЕ ЛОЖЬ = ИСТИНА Простые условия бывают трех видов: сравнение чисел, сравнение символов и условия Исполнителей. Сравнение чисел Проверка равенства <арифметическое выражение> = <арифметическое выражение> Проверка неравенства <арифметическое выражение> О <арифметическое выражение> Строгие неравенства <арифметическое выражение> > <арифметическое выражение> < <арифметическое выражение> <арифметическое выражение> Нестрогие неравенства <арифметическое выражение> >= <арифметическое выражение> <= <арифметическое выражение> <арифметическое выражение> Сравнение символов Проверка равенства <символ> = <символ> Проверка неравенства <СИМВОЛ> о <символ> Строгие неравенства <символ> > <символ> <символ> < <символ> Нестрогие неравенства <символ> >= <символ> <символ> <= <символ> СПРАВОЧНИК ПО АЛГОРИТМИЧЕСКОМУ ЯЗЫКУ 155 1.6. Конструкции Конструкция процедуры Команды, описывающие работу процедуры, выполняются каждый раз, когда в программе встречается строка с именем процедуры (см. справа). Конструкция простого цикла Указанное число раз повторяется выполнение расположенной между словами НАЧАЛО и КОНЕЦ группы команд (см. справа). Конструкция ветвления Первая форма: Если условие имеет значение ИСТИНА, то выполняется указанная группа команд. В противном случае не выполняется ни одной команды. Вторая форма: Если условие имеет значение ИСТИНА, то выполняется первая группа команд. Если условие имеет значение ЛОЖЬ, то выполняется вторая группа команд. ПРОЦ <имя процедуры> НАЧАЛО <группа команд> КОНЕЦ ПОВТОРИТЬ <число раз> РАЗ НАЧАЛО <группа команд> КОНЕЦ ЕСЛИ <условие> ТО <группа команд> КОНЕЦ ЕСЛИ <условие> ТО <первая группа команд> ИНАЧЕ <вторая группа команд> КОНЕЦ Конструкция повторения Если условие имеет значение ИСТИНА, то выполняется группа команд, стоящая между словами НАЧАЛО и КОНЕЦ, после чего заново определяется истинность условия (см. справа). Эти действия повторяются, пока условие не примет значение ЛОЖЬ. В этом случае не выполняется ни одной команды. ПОКА <условие> ДЕЛАТЬ НАЧАЛО <группа команд> КОНЕЦ 2. Исполнители 2.1. Робот Среда представляет собой поле, разбитое на квадратные клетки. Между клетками могут быть стены, некоторые клетки могут быть закрашены. Робот находится на одной из клеток поля. 156 СПРАВОЧНИК ПО АЛГОРИТМИЧЕСКОМУ ЯЗЫКУ вверх вниз вправо влево Система команд Команды движения (см. справа). __ При выполнении этих команд Робот пере- ___ двигается соответственно на одну клетку вверх, вниз, вправо или влево. Если на пути Робота --- расположена стена, то возникает ОТКАЗ. — Команда закрась заставляет Робота закрасить клетку, на которой он находится. Если клетка уже была закрашена, то Робот просто ничего не делает. ОТКАЗ при этом не возни- _ кает. Условия вверху свободно снизу свободно справа свободно слева свободно Каждое из этих условий истинно, если в соответствующем направлении нет стены. закрашена Это условие истинно, если клетка, на которой стоит Робот, закрашена. 2.2. Чертежник Среда Чертежник живет на координатной плоскости, на которой задана прямоугольная система координат. Он рисует пером. Перо в каждый момент находится в одной из точек плоскости. Перо либо поднято, либо опущено. Система команд Чертежника: две команды движения переведи в точку(<арифметическое выражение>, <арифметическое выражение>) сдвинь на вектор(<арифметическое выражение>, <арифметическое выражение>) Первая команда переводит перо в точку с указанными координатами. Вторая сдвигает перо из текущего положения на вектор с указанными координатами. Поднятое перо не оставляет следа при движении, а опущенное прочерчивает отрезок, соединяющий начальное и конечное положения. Команды управления пером подними перо опусти перо СПРАВОЧНИК ПО АЛГОРИТМИЧЕСКОМУ ЯЗЫКУ 157 переводят перо в указанное положение. Если перо уже было в этом положении, ничего не происходит. Условия Отсутствуют. 2.3. Черепаха Среда Черепаха живет на бескоординатной плоскости. У нее есть направление, в котором она смотрит. Черепаха оставляет или не оставляет след, в зависимости от того, опущен или поднят ее хвост. Система команд Команды движения: вперед(<арифметическое выражение>) назад(<арифметическое выражение>) При выполнении команды движения Черепаха сдвигается на указанное в параметре расстояние в нужном направлении. Команды поворота: вправо(<арифметическое выражение>) влево(<арифметическое выражение>) При выполнении команды поворота Черепаха поворачивается вправо или влево на указанный в параметре команды угол, выраженный в градусах. Команды управления хвостом: подними хвост опусти хвост переводят хвост в указанное положение. Условия Отсутствуют. 2.4. Вывод Среда Вывод управляет устройством вывода. Два типичных устройства вывода: окно на экране монитора и принтер. Система команд По команде печать(<список выражений>) 158 СПРАВОЧНИК ПО АЛГОРИТМИЧЕСКОМУ ЯЗЫКУ Вывод печатает значения выражений в указанном порядке через пробелы. Если устройство вывода — экран, то эти значения появятся на экране, если принтер — будут напечатаны на бумаге. В любом случае если результат вывода не умещается на одной строке печатающего устройства, то он продолжается на следующей строке. По команде печатьподряд(<список выражений>) Вывод печатает значения выражений в указанном порядке без пробелов. Если одним из параметров команд печать или печатьпод-ряд является массив или символьная переменная, то на печать выводятся все элементы массива, которым присвоены значения (аналогично для символьной переменной). Условия Отсутствуют. 2.5. Ввод Среда Ввод управляет устройством ввода текстовой информации. Типичный пример такого устройства — клавиатура. Система команд При выполнении команды читать(<список переменных>) Ввод ожидает, что на клавиатуре будут набраны символы, которые задают значения переменных, указанных в списке. Ввод символьной переменной начинается с кавычки и заканчивается кавычкой. После имени символьной переменной в списке переменных должна идти числовая переменная, которой присваивается длина введенного значения символьной переменной. На начало ввода числа указывает ввод знака «-f» или «-» или цифры. На окончание ввода числа указывает ввод символа пробела или кавычки. Условия Отсутствуют. ' тг ! 1 1 1 1 1 1 i t ,1 _ 1 Задачи Задачи собраны по темам, которые идут примерно в том же порядке, в каком излагаются в учебнике. Уровень трудности задач сильно различается. Чтобы облегчить пользование задачником, номера некоторых задач написаны по-разному. Понимать это нужно следующим образом: Номер задачи написан со знаком * (звездочка) — сложная задача, ее решение может потребовать сообразительности и находчивости. Номер задачи написан другой краской (не черной) — трудоемкая задача, ее решение может потребовать значительных затрат времени. Номер задачи подчеркнут — важная задача, решение такой задачи и размышления над ней весьма полезны для понимания алгоритмики. Указанные метки носят оценочный характер. Количество времени, необходимое для написания одной и той же программы, меняется от человека к человеку в десятки раз. Сложная для одного человека задача может оказаться пустяковой для другого, сразу обнаружившего правильный путь решения. Важность задач тоже относительна. Робот рисует. Замена рекурсии в программах для Робота работой с переменными 1.1. На рисунке 37 изображены различные башни. Напишите одну процедуру, выполняя которую Робот рисует все эти башни. Укажите значения переменных, при которых ваша процедура рисует каждый из рисунков. Рис. 37 160 ЗАДАЧИ 1.2. На рисунке 38 изображены различные флаги. Напишите одну процедуру, выполняя которую Робот рисует все эти флаги. Укажите значения переменных, при которых получается каждый из рисунков. 1.3. На рисунке 39 изображены различные меандры. Напишите одну процедуру, выполняя которую Робот рисует все эти меандры. Укажите значения переменных, при которых получается каждый из рисунков. Рис. 38 Рис. 39 ЗАДАЧИ 161 1.4. Робот перемещается по полю, показанному на рисунке 40, а. Начальное положение Робота отмечено кружком. Исполняется программа d = о ПОКА d < 6 ДЕЛАТЬ вправо d= d + 1 КОНЕЦ а) в) Рис. 40 А • В Г • б) • I 1 г) а) Сколько шагов сделает Робот, исполняя эту программу? б) Отметьте клетку, в которой окажется Робот после исполнения этой программы. 1.5. Робот перемещается по полю, нарисованному на рисунке 40, б. Начальное положение Робота отмечено кружком. Исполняется программа (см. справа). а) Сколько шагов сделает Робот, исполняя эту программу? б) Отметьте клетку, в которой окажется Робот после исполнения этой программы. 1.6. Робот перемещается по полю, показанному на рисунке 40, в. Начальное положение Робота отмечено кружком. Исполняется программа (см. справа). а) В какой из клеток — А или В — Робот окажется раньше? б) Отметьте клетку, в которой Робот окажется после исполнения программы. d = о ПОКА d < 20 ДЕЛАТЬ ЕСЛИ слева свободно ТО влево ИНАЧЕ вправо КОНЕЦ d = d + 1 КОНЕЦ 1 = 0 ПОКА / < 20 ДЕЛАТЬ ПОВТОРИТЬ / РАЗ влево КОНЕЦ ПОВТОРИТЬ / -ь 1 РАЗ вправо КОНЕЦ 1 = 1+2 КОНЕЦ 162 ЗАДАЧИ 1.7. Робот перемещается по полю, нарисованному на рисунке 40, г. Начальное положение Робота отмечено кружком. Исполняется программа 1 = 0 ПОКА / < 9 ДЕЛАТЬ ПОВТОРИТЬ / РАЗ влево КОНЕЦ ЕСЛИ закрашена ТО ПОВТОРИТЬ/+ 1 РАЗ вправо КОНЕЦ ИНАЧЕ ПОВТОРИТЬ I РАЗ КОНЕЦ / = /+ 1 КОНЕЦ вправо КОНЕЦ Отметьте клетку, в которой окажется Робот. 1.8. Робот стоит в горизонтальном прямоугольнике шириной 1, ограниченном стенами. Не используя рекурсии, напишите для Робота программу, исполняя которую он закрасит все клетки слева от своего начального положения, столько же клеток в правом конце прямоугольника, после чего вернется в начальное положение. 1.9. Робот стоит в бесконечном горизонтальном коридоре шириной 1, ограниченном сверху и снизу стенами. Одна из клеток справа от Робота закрашена. Напишите процедуру, исполняя которую Робот дойдет до закрашенной клетки, пройдет ее и остановится с другой стороны. Место остановки зависит от значения переменной f. Нужно предусмотреть возможность задания значения переменной f таким образом, чтобы Робот остановился: а) на таком же расстоянии от закрашенной клетки, что и вначале; б) на расстоянии, вдвое большем исходного; в) на расстоянии, втрое большем исходного. 1.10. Напишите для Робота процедуру полоска, исполняя которую Робот закрасит п клеток справа от своего исходного положения и по завершении работы вернется в исходное положение. Число п записано в переменной п. 1.11. Напишите для Робота процедуру квадрат, исполняя которую Робот закрасит клетки в квадрате размером п х п, в верхнем левом углу которого он находится. По завершении работы Робот должен возвращаться в исходное положение. Число п записано в переменной п. ЗАДАЧИ ' 163 1.12. Процедура ход конем использует переменные а и Ь следующим образом: ПРОЦ ход конем НАЧАЛО ЕСЛИ а = О ТО ЕСЛИ Ь = О ТО вправо вправо вверх ИНАЧЕ влево влево вниз КОНЕЦ ИНАЧЕ ЕСЛИ Ь = О ТО вверх вверх влево ИНАЧЕ вниз вниз вправо КОНЕЦ КОНЕЦ КОНЕЦ Составьте процедуру конь, заменив в процедуре квадрат из предыдущей задачи команды движения Робота вызовами процедуры ход конем и присваиваниями по правилу: Команда Заменяется на вверх а = 1 Ь = 0 ход конем вниз а = 1 ь = 1 ход конем влево а = 0 Ь = 1 ход конем вправо а = 0 Ь = 0 ход конем Какую картинку нарисует Робот, исполнив процедуру конь, если значение переменной п равно: а) 2; б) 3; в) 4? 164 ЗАДАЧИ 1.13. Даны две процедуры. ПРОЦ зигзаг НАЧАЛО ПОВТОРИТЬ Ь РАЗ закрась ход закрась вниз КОНЕЦ КОНЕЦ Нарисуйте картинки, которые получатся после исполнения Роботом процедуры зигзаг при значениях переменных: а) а = 1, Ь = 5; б) а = О, Ь = 5. Сформулируйте словами, чем отличаются эти картинки. 1.14. Напишите для Робота процедуру, которая в зависимости от выбора значений переменных рисует любую из картинок, изображенных на рисунке 41. Совет. Чтобы процедура была короче, используйте вспомогательную процедуру, аналогичную процедуре хода из предыдущей задачи. а) б) Д) е) ж) 3) Рис. 41 1.15. Поле Робота имеет вид горизонтального прямоугольника шириной 1, ограниченного стенами. Напишите программу, подсчитывающую длину поля в клетках. ЗАДАЧИ 165 1.16. На поле Робота стен нет. Напишите процедуру, выполняя которую Робот подсчитает количество закрашенных клеток в квадрате размером п X п, в верхнем левом углу которого он находится. По завершении работы Робот должен возвращаться в исходное положение, а количество закрашенных клеток должно быть записано в переменной с. Размер квадрата задан переменной п. 1.17. Измените процедуру из предыдущей задачи таким образом, чтобы Робот подсчитывал количество закрашенных клеток в прямоугольнике размером т х п. В задачах 1.18—1.26 начальное положение Робота не задано. Программы, которые требуется написать в этих задачах, должны правильно работать при любом начальном положении Робота на поле. Ответ в этих задачах должен быть записан в переменной с. 1.18. Поле Робота имеет вид прямоугольника, огороженного стеной. Напишите программу, подсчитывающую длину стены в клетках. 1.19. Поле Робота имеет вид прямоугольника, огороженного стеной. Напишите программу, подсчитывающую площадь поля в клетках. 1.20. Поле Робота имеет вид прямоугольника, огороженного стеной, внутри которого есть некоторое количество вертикальных стен, причем на каждой вертикальной прямой есть не более одной стены (рис. 42) и ни одна вертикальная стена не проходит от самого верха до самого низа. Напишите для Робота программу, которая подсчитывает число клеток в прямоугольнике. 1.21. Поле Робота огорожено одной стеной неизвестной формы (рис. 43). Напишите программу, подсчитывающую длину стены в клетках. Рис. 42 166 ЗАДАЧИ Рис. 43 Рис. 44 1.22. Поле Робота огорожено одной стеной неизвестной формы, которая выпукла в вертикальном направлении (рис. 44). Это означает, что все вертикальные кусочки поля шириной в одну клетку являются столбиками (а не состоят из нескольких столбиков, как на рисунке 43). Напишите программу, подсчитывающую площадь поля (в квадратных клетках). 1.23*. Поле Робота ограничено одной стеной неизвестной формы. Напишите программу, которая проверяет, выпукло ли поле в вертикальном направлении. (Определение см. в задаче 1.22.) Если поле выпукло, значение переменной с в конце работы программы должно равняться 1. В противном случае оно должно равняться 0. 1.24. Поле Робота имеет вид прямоугольника, огороженного стеной. Напишите программу без переменных, исполняя которую Робот закрасит все клетки прямоугольника, граничащие со стеной. 1.25. Поле Робота имеет вид прямоугольника, огороженного стеной. Напишите программу без переменных, исполняя которую Робот закрасит все клетки прямоугольника, не граничащие со стеной. Подсказка: используйте рекурсию. ЗАДАЧИ 167 2. Чертежник рисует 2.1. Рисование графиков Общее требование к решению задач на рисование графиков: размер шага по каждой из осей координат нужно выбирать так, чтобы весь рисунок помещался на листе бумаги или экране. Обратите на это требование особое внимание при решении задач с параметрами. 2.1. Напишите для Чертежника программу, которая рисует график функции у = = 1000л: + 350 на промежутке [-1, 1]. Шаг по осям X и у должен быть одинаковый. 2.2. Напишите программу, выполняя которую Чертежник рисует график функции t/ = |лг - 1| + |x -Ь 1| -f |лг + 2| на промежутке [-3, 3]. Шаг по осям х и у должен быть одинаковый. 2.3. Напишите процедуру, выполняя которую Чертежник рисует р графики функций у = Ах + Вх -Ь С. 2.4. Напишите процедуру, выполняя которую Чертежник рисует графики функций у = А + В|х - С|. 2.5. Напишите процедуру, выполняя которую Чертежник рисует графики функций у = |лг - А| -I- |д: - В\. 2.6. Напишите процедуру, выполняя которую Чертежник рисует В графики функций у =А + х - С ' Позаботьтесь об обходе опасной точки X = С. 2.7. Напишите для Чертежника программу, которая рисует график функции у = |дг| на промежутке [-2, 2] и ту часть графика одной из следующих функций: а) 2 - лг; б) 1.25х + 0.5; в) 2 - х^, которая лежит выше графика функции у = |л:|. 2.8. Напишите для Чертежника программу, которая на одном чертеже рисует графики функций у(х) = 2tx - на промежутке [-1, 1]. Параметр t пробегает значения от -1 до 1 с шагом 0.2. Шаг по осям X и у должен быть одинаковый. 168 ЗАДАЧИ 2.9. Измените программу из предыдущей задачи так, чтобы на том О же чертеже был изображен и график функции г/ = х на промежутке [-1, 1]. 2.10. Задайте формулой функцию, значения которой близки к приведенным в следующей таблице: 0 1 2 3 4 5 6 7 8 9 3.2 4.9 6.9 9.15 10.8 12.7 15.1 17.3 18.95 21 Пусть табличные значения занесены в массив f в программе. Напишите программу для Чертежника, которая рисует график функции по вашей формуле и изображает точками табличные значения. Сравните результаты с теми, которые получили ваши одноклассники. Чья формула проще? Чья формула дает наилучшее приближение? 2.11. Пусть функция задана своими табличными значениями при постоянном шаге аргумента. Таблица значений функции записана в массиве f подряд, начиная от /[0] до f[n - 1] (таким образом, в переменной п записана длина массива). Напишите процедуру, изображающую график отклонения функции от ее среднего значения. В следующих задачах предполагается, что шаг по осям х и г/ одинаковый. Чертежник должен рисовать только в окне, которое задается условиями -1<х<1,-1<у<1. На рисунке 45, а это окно указано штриховой линией. На этом же рисунке изображена также сетка координатных линий, которая помогает определять координаты точки. Эта сетка нарисована с шагом 0.1. Рис. 45 а) б) ЗАДАЧИ 169 2.12. Напишите процедуру сетка, которая рисует сетку координатных линий и координатные оси, изображенные на рисунке 45. Конечно, наш Чертежник не может рисовать линии разной толщины, поэтому рисуйте все линии одинаковой толщины. Стрелки нарисовать нужно. В дальнейшем вы можете добавлять вызов процедуры сетка к программам рисования графиков для того, чтобы было легче проверять правильность построения. 2.13. Определите, график какой функции изображен на рисунке 45, б. 2.14. Напишите для Чертежника программу, которая рисует в окне графики функций: а) у = -2х -Ь 0.5; б) у = 0.5х + 0.1; в) у = -1.5л: - 0.5; г) у = Зл: - 2. 2.15. Напишите для Чертежника процедуру прямая, которая рисует в окне график функции у = ах + Ь. Числа а и Ь записаны в переменных а и Ь соответственно. 2.16. Решите задачу 2.8, рисуя графики в окне. 2.17. Нарисуйте в окне графики функций: а) у = 1.5 - 2х^-, б) у = 0.5 -(- 0.25/(л: - 0.5); в) у = -0.5 -ь 1/(л: - 0.25); г) у = -0.25 + 1/(х + 1); д) у = |л: - 0.2| -Ь |л: -1- 0.21; е) у = |л: - 0.51 + |х - 0.21 + 1^^^ + 0.2| + |д: + 0.51. 2.2. Построение столбиковых диаграмм Иногда вместо рисования графиков таблично заданных функций оказывается удобно представлять такие функции столбиковыми диаграммами. Такое представление особенно часто используется в статистике — науке о сборе и анализе больших массивов данных. На рисунке 46 приведена столбиковая диаграмма, построенная по массиву данных о производстве электроэнергии в России за период 1995—2003 гг., выраженных в млрд кВт/ч. Год 1995 1996 1997 1998 1999 2000 2001 2002 2003 Энергия 860 847 834 827 846 878 891 891 915 Каждый столбик этой диаграммы соответствует одному году. Ширина столбика равна 1 по оси х. Высота столбика пропорциональна 170 ЗАДАЧИ количеству электроэнергии, произведенной в соответствующем году. Мы считаем, что единица по оси у соответствует 100 млрд кВт/ч, поэтому высота первого столбика равна 8.6 единиц, высота второго столбика равна 8.47 единиц и т. д. Чертежник рисует столбиковые диаграммы почти так же легко, как и графики функций, заданных табличными значениями. 2.18. Не используя переменных, напишите для Чертежника программы рисования столбиковых диаграмм, заданных следующими табличными значениями: Рис. 46 а) 5 2 4 7 4 б) 5 -2 0 3 -1 Считайте, что ширина каждого столбика равна 1, а единица значения соответствует единице высоты. 2.19. Составьте список переменных, которые полезно использовать для рисования столбиковых диаграмм. Напишите процедуру, которая использует эти переменные. Постройте варианты столбиковой диаграммы с рисунка 46 при других значениях переменных. Выберите самый красивый вариант. 2.20. Отмечайте ежедневно в течение недели, сколько времени вы смотрите телевизор. Напишите программу, которая строит столбиковую диаграмму, изображающую результаты этого эксперимента. 2.3. Разное 2.21. Чертежник исполняет программу (см. справа). а) Нарисуйте картинку, которая получится в результате работы этой программы, не исполняя самой программы. б) Поменяйте местами девятую и десятую строчки программы и нарисуйте картинку, которая получается в результате работы такой программы. подними перо переведи в точку (0,0) у = 5 х = 0 ПОКА у > о ДЕЛАТЬ опусти перо переведи в точку(х, у) подними перо X = X +1 переведи в точку(х, 0) у = у_ 1 КОНЕЦ ЗАДАЧИ 171 (0.0) (0,0) а) б) (0,0) в) Рис. 47 2.22. Напишите процедуру штрихгор для Чертежника, которая рисует квадрат, заштрихованный горизонтально, как на рисунке 47, а. Длина стороны квадрата записана в переменной а, количество линий штриховки — в переменной п. (0, 2.23. Напишите процедуру штрихверт для Чертежника, которая рисует прямоугольник, заштрихованный вертикально, как на рисунке 47, б. Длина горизонтальной стороны прямоугольника записана в переменной а, длина вертикальной — в переменной 6, количество линий штриховки — в переменной п. 2.24. Напишите процедуру штрихнаклон для Чертежника, которая рисует квадрат, заштрихованный косыми линиями, как на рисунке 47, в. Длина стороны квадрата записана в переменной а, количество линий штриховки — в переменной п. 2.25. Напишите процедуру штрихнаклон2 для Чертежника, которая рисует прямоугольник, заштрихованный косыми линиями, как на рисунке 47, г. Длина сторон прямоугольника записана в переменных а, Ь, количество линий штриховки — в переменной п. 2.26*. Заштрихуйте квадрат с «дыркой» с помощью Чертежника (рис. 48). 2.27. Придумайте программу для Чертежника, которая рисует картинку, как на рисунке 49, а. 2.28. Придумайте программу для Чертежника, которая рисует картинку, как на рисунке 49, б. 2.29. Придумайте программу для Чертежника, которая рисует картинку, как на рисунке 49, в. 172 ЗАДАЧИ б) Рис. 49 3. Черепаха рисует 3.1. Рисование геометрических фигур 3.1. Напишите процедуры для Черепахи, которые рисуют заштрихованные квадраты, как в задачах 2.22 и 2.23. 3.2. Напишите программу для Черепахи, которая рисует квадрат, заштрихованный какими-нибудь п наклонными линиями, как в задаче 2.24. 3.3. Напишите для Черепахи процедуру, которая рисует пятиугольные звезды, как на рисунке 50. Сколько переменных необходимо исполызовать в такой процедуре? в) Рис. 50 ЗАДАЧИ 173 а) Рис. 52 в) 3.4. Напишите для Черепахи процедуру, которая рисует многоугольные звезды, как на рисунке 51. 3.5. Напишите процедуру для Черепахи, которая рисует параллелепипед. Укажите значения переменных, при которых получаются параллелепипеды, аналогичные изображенным на рисунках а — в (рис. 52). 3.6. Подберите значения переменных в процедуре рисования параллелепипеда из предыдущей задачи так, чтобы полученный рисунок выглядел как куб. 3.7*. Составьте процедуру для Черепахи, которая рисует пирамиду, как на рисунке 53. Количество этажей в пирамиде записано в переменной п. 3.8. Напишите процедуру для Черепахи, которая рисует «волосатые» спирали, как на рисунке 54. Рис. 54 174 ЗАДАЧИ Рис. 55 3.9. Напишите процедуру для Черепахи, которая рисует картинку, как на рисунке 55. 3.10. Напишите процедуру для Черепахи, которая рисует картинку, изображенную на рисунке 56, а. Длина стороны маленького квадрата записана в переменной I, расстояние между сторонами соседних квадратов записано в переменной d, количество квадратов в ряду — в переменной п. 3.11*. Напишите процедуру для Черепахи, которая рисует картинку, изображенную на рисунке 56, б. Длина стороны маленького треугольника записана в переменной I, расстояние между вершинами соседних треугольников записано в переменной d, количество треугольников в самом нижнем ряду — в переменной п. 3.12*. Напишите процедуру для Черепахи, которая рисует картинку, изображенную на рисунке 56, в. Длина стороны маленького шестиугольника записана в переменной I, расстояние между вершинами соседних шестиугольников записано в переменной d, количество шестиугольников в самом нижнем ряду — в переменной п. 3.13. Напишите для Черепахи процедуру штрих, которая рисует штриховую линию, как на рисунке 57, а. Длина линии (расстояние, на которое перемещается Черепаха) задана переменной Z, длина штриха — переменной d, расстояние между штрихами — переменной s. 3.14. Напишите для Черепахи процедуру штрихквадрат, которая рисует квадрат штриховыми линиями, используя процедуру штрих, написанную в предыдущей задаче. Данные записаны в □ □ □ □ □ □ А □ □ □ □ □ □ А А ООО □ □ □ □ □ □ А А А ОО ОО □ □ □ □ □ □ А А А А ООООО □ □ □ □ □ □ А А А А А оооо □ □ □ □ □ □ А А А А А А ООО Рис. 56 а) б) в) ЗАДАЧИ 175 Рис. 57 а) в) следующих переменных: сторона квадрата — в переменной I, длина штриха — в переменной а, расстояние между штрихами — в переменной Ь. Исполните программу, написанную справа. У вас получится рисунок, похожий на рисунок 57, б. Это не очень красивый рисунок, потому что линии штриховки не проходят через углы квадрата. Напишите процедуру красивыйштрих, которая всегда рисует квадрат штриховыми линиями так, как показано на рисунке 57, в. Длина стороны квадрата и длина штриха записаны в тех же переменных, что и раньше. Расстояние между штрихами должно быть не больше значения переменной Ь и как можно ближе к нему. 3.15. Напишите процедуры, аналогичные процедурам правильный многоугольник и правильная звезда (они описаны в учебнике), которые рисуют правильные многоугольники и правильные звезды штриховыми линиями. 3.2. Построение секторных диаграмм не подвергались наказаниям в детстве Рис. 58 Секторная диаграмма показывает, как целое делится на части. Части изображаются в виде секторов круга. Весь круг соответствует целому (если измерять доли в процентах, то 100%). Каждой части соответствует сектор круга, угол которого пропорционален доле данной части. Например, половине (50%) соответствует угол 360° : 2 = 360° • 50 : 100 = 180°, четверти — 90°, одной пятой — 72° и т. д. На рисунке 58 приведена секторная диаграмма, изображающая результаты опроса, проведенного фондом «Общественное мнение». У респондентов (людей, участвующих в опросе) спросили, каким наказаниям 176 ЗАДАЧИ они подвергались в детстве. Результаты опроса (в процентах от общего числа опрошенных) приведены в таблице: № п/п Варианты наказания % 1 наказывали физически 22 2 ругали, отчитывали, кричали 19 3 ставили в угол 13 4 лишали удовольствий, развлечений, временно ограничивали свободу 12 5 вели воспитательные беседы, назидательные разговоры 8 6 оказывали психологическое, моральное воздействие 2 7 бойкотировали, прекращали общаться 1 8 заставляли делать какую-либо работу по дому, на огороде 1 3.16. Определите, какой процент опрошенных не подвергался наказаниям в детстве. 3.17. Напишите процедуру для Черепахи, которая рисует сектор круга. Процедура должна использовать радиус круга, угловую величину сектора и угол наклона к горизонтали (рис. 59). ^ угол наклона к горизонтали 3.18. Напишите программу для Черепахи, которая рисует секторную диаграмму, 59 изображающую результаты опроса, приведенные в таблице выше. Сравните полученный результат с рисунком 58. 3.19. Напишите программу для Черепахи, которая рисует секторную диаграмму по данным о знаках кодировки КОИ8-Р, приведенным в следующей таблице: № п/п Вид знаков Количество знаков 1 буквы латинского алфавита 52 2 буквы русского алфавита 66 3 цифры 10 4 знаки препинания, специальные символы 95 5 управляющие символы 33 3.20. В течение недели отмечайте время, которое вы тратите на сон, учебу, поездки в транспорте, отдых и развлечения. Подведите ЗАДАЧИ 177 итоги, вычислив долю времени, которая тратится на каждое из указанных занятий. Напишите программу для Черепахи, которая рисует секторную диаграмму, изображающую результаты вашего исследования. 4. Кривая дракона с переменными Напомним, как строится кривая дракона. Кривая дракона порядка О — это просто отрезок, концы которого отмечены как начало (голова дракона) и конец (хвост дракона). Если мы уже построили кривую дракона порядка п, то кривая дракона порядка /г Ч- 1 строится следующим образом: нужно сначала нарисовать кривую дракона порядка п и затем добавить к рисунку ту же самую кривую, повернутую на угол 90° по часовой стрелке относительно хвоста. Голова кривой дракона порядка п -I- 1 совпадает с головой кривой дракона порядка п, а хвост получается из головы кривой дракона порядка п поворотом на угол 90° по часовой стрелке относительно хвоста кривой дракона порядка п. На рисунке 60 изображено несколько кривых дракона и указаны их порядки. (Голова дракона изображена на этом рисунке пустым кружком, а хвост — заполненным.) 4.1. Напишите процедуру, которая рисует кривую дракона порядка п. Порядок кривой дракона записан в переменной п, а длина отрезка кривой дракона — в переменной а. Указание. Используйте рекурсию. При этом полезно научиться рисовать дракона не только от головы к хвосту, но и от хвоста к голове. (Подробно об этом рассказывалось в Алгорит-мике — 6.) 0 О—• 1J 'Ь ' q Рис. 60 178 ЗАДАЧИ 3^ L? 4.2*. Напишите процедуру, которая рисует кривую дракона порядка п, ширина которой не больше w, высота — не больше Л. Эти числа записаны в переменных п, w, h соответственно. Длина отрезка кривой не задана — ее нужно подобрать, чтобы удовлетворить требованию задачи. 4.3*. Напишите программу, которая проверяет, проходит ли кривая дракона какой-нибудь свой отрезок дважды. Изучите результаты работы программы. Можно ли так изменить программу рисования кривой дракона, чтобы было ясно из картинки, проходит ли кривая через какой-нибудь отрезок дважды? Можете ли вы придумать объяснение этим результатам? 5. Условия и выражения 5.1. Найдите значение переменной j после исполнения программы 1 5.2 Найдите значения переменных а, Ь после исполнения программы 2. Составьте таблицу состояний переменных при исполнении программы. Как изменятся значения переменных в конце работы программы, если заменить вторую строчку на Ь = -1? 1) 2) J = 0 а = 1 i=j+^ Ь = 2 У=У +/• + 1 а = а + Ь Ь = а - Ь а = а - Ь 5.3. Известно, что значения переменных хну — целые однозначные числа. После выполнения команды а = 10*х -(■ у значение переменной а равно 27. Чему равны значения переменных X и у? 5.4*. Известно, что значения переменных х и у — целые неотрицательные числа. После выполнения команды а = (X + у)*(х + у + \ )12 + у значение переменной а равно 27. Чему равны значения переменных хну! ЗАДАЧИ I 179 5.5. Найдите значение переменной х после исполнения команд а = целая часть(х) Ь = целая часть(-х) X = а + Ь (считайте, что перед началом исполнения этих команд переменной X присвоено значение, так что ОТКАЗа не происходит). 5.6. Найдите значение переменной д: после исполнения программ: а) б) X = 1.01234567 а = дробная часть(х) Ь = дробная часть(-х) ж = а + Ь X= 123456789 а = остаток(х, 134) Ь = остаток(-х, 134) ж = а + Ь 5.7. Значения переменных даны в таблице: Имя переменной а Ь i j Значение переменной 2/3 -1.5 2 3 Найдите значения следующих числовых выражений: а) 2*а; б) i - j; в) а*Ь; г) а + Ь; д) j/i + Ь; е) {j'a - 1)*(/*Ь +1); ж) Л/а + Ь; з) а/{/ + Ь); и) a'{j - Ь) - а*Ь. 5.8. Значения переменных такие же, как в предыдущей задаче. Найдите значения следующих числовых выражений: а) целая часть(а); б) /*дробная часть(Ь); в) целая часть(а*у)> г) дробная часть(Ь/а); д) дробная часть(э + Ь); е) целая часть(Ъ) + /. 5.9. Значения переменных даны в таблице: Имя переменной а Ь X У Значение переменной 14 10 7 4 Найдите значения следующих числовых выражений: а) частное(э, х); б) остаток(Ь, у); в) частное(х, Ь - а); г) остаток(а, х + у - Ь); д) частное(х*у, а) + 1; е) остаток(а, целая часть (х/у)). 180 ЗАДАЧИ 5.10. Известно, что условие а + Ь > а + с имеет значение ИСТИНА. Найдите значение условия Ь > с. 5.11. Укажите такие значения переменных, при которых условие а*Ь > а*с имеет значение ЛОЖЬ, а условие Ь > с имеет значение ИСТИНА. 5.12. Известно, что условие (а = 3) ИЛИ (а = 4) ИЛИ (а = 5) имеет значение ИСТИНА. Объясните, почему условие а <= 6 имеет значение ИСТИНА. 5.13. Известно, что условие (а > 5) ИЛИ (-а = -5) имеет значение ИСТИНА. Объясните, почему условие а < 5 имеет значение ЛОЖЬ. 5.14*. В переменных Я1, Я2, Ты1, Ты2 записаны номера дней недели, когда дежурю я (Я1, Я2) и ты (Ты1, Ты2). Напишите условие с одним сравнением, которое истинно тогда и только тогда, когда мы дежурим по очереди. 5.15. Значения переменных даны в таблице: Имя переменной а б В Г Значение переменной -5 2 9 16 Найдите значения истинности следующих условий: а) частное(17, г) = частное(в, -а); б) остаток(в, б) <> частное(г, в); в) целая часть(а/б) <= целая часть((г - а)/в); г) остаток(а + б, в) >= остаток(г, б + в); д) частное(а*в, г) > 2; е) остаток(а*в, 5) < 6. 5.16. При написании программ часто требуется сделать выбор из нескольких вариантов. Предположим, что номер варианта (1, 2, ...) записан в переменной г. Проверьте, что выбор из двух вариантов можно сделать конструкцией ветвления, показанной справа. ЕСЛИ I = 1 ТО <первый вариант> ИНАЧЕ <второй вариант> КОНЕЦ В нашем алгоритмическом языке нет специальной конструкции выбора из нескольких вариантов. Поэтому выбор из трех вариантов нужно делать, используя несколько конструкций ветвления. Найдите среди написанных ниже фрагментов программ те, которые позволяют выбрать один вариант действий из трех, и объясните, почему не годятся остальные фрагменты. ЗАДАЧИ 181 а) ЕСЛИ / = 1 ТО <первый вариант> КОНЕЦ ЕСЛИ I = 2 ТО <второй вариант> КОНЕЦ ЕСЛИ / = 3 ТО <третий вариант> КОНЕЦ б) ЕСЛИ / = 1 ТО <первый вариант> ИНАЧЕ ЕСЛИ /• = 2 ТО <второй вариант> ИНАЧЕ <третий вариант> КОНЕЦ КОНЕЦ в) ЕСЛИ I = 1 ТО <первый вариант> ИНАЧЕ <второй вариант> КОНЕЦ ЕСЛИ ; = 3 ТО <третий вариант> КОНЕЦ 5.17. Напишите фрагмент программы, который позволяет выбирать один из четырех вариантов в виде, аналогичном предыдущему примеру. Номер нужного варианта записан в переменной г. ^ Массивы При решении всех задач из этого раздела нужно считать, что в любом массиве длины п записаны элементы с номерами от 1 до п. Переменной можно присвоить значение одной командой, например л: = 0. С массивом одной командой обойтись нельзя. Но и использовать переменные, которым не присвоено значение, тоже нельзя. Поэтому при работе с массивами часто бывает полезна процедура, описанная в следующих двух задачах. 6.1. Напишите процедуру иниц, которая присваивает первым п элементам массива array значение 0. Число п записано в переменной п. 6.2. Напишите процедуру копия, которая присваивает первым п элементам массива Ih соответственные значения элементов массива rh так, что выполняется равенство lh[i] = г/г[г] при всех i от 1 до п. Число п записано в переменной л, 6.3. Напишите процедуру номера, которая присваивает первым л элементам массива а значения так, что выполняется равенство а[/] = i при всех i от 1 до л. Число л записано в переменной л. 182 ЗАДАЧИ 6.4. Для решения задачи нахождения максимального элемента в массиве а длины 10 предложены две программы. В конце работы программы в переменной max должен быть записан максимальный элемент массива. Правильно ли работают эти программы? а) п = 10 б) п = 10 /= 1 /= 1 max = а[1] max = а[1] ПОКА / < п ДЕЛАТЬ ПОКА / < п ДЕЛАТЬ / = ; + 1 ' ЕСЛИ а[|] >тах ТО ЕСЛИ а[;] >тах ТО ■ max = а[|] max = а[/] КОНЕЦ КОНЕЦ / = /+ 1 КОНЕЦ КОНЕЦ 6.5. Дана программа: п = 20 #п = 30 1= 1 ПОКА /• < п + 1 ДЕЛАТЬ с[/] = a[i] / = /-(- 1 КОНЕЦ /= 1 ПОКА / < m + 1 ДЕЛАТЬ с[/ -I- 20] = b[i] /• = /-(- 1 КОНЕЦ а) Объясните, что делает эта программа, б) Сформулируйте условия, при которых выполнение этой программы не приведет к ОТКАЗу. Указание. Найдите в тексте программы команды, исполнение которых может привести к ОТКАЗу. 6.6. Даны два неубывающих (см. определение на с. 43) массива а, Ь, их длины записаны в переменных 1а и lb соответственно. Напишите программы, которые образуют новый неубывающий массив с, состоящий из элементов массивов 1а и lb, при условии, что каждое значение, которое встречается в массивах а и Ь, входит в массив с: а) столько раз, сколько оно входит в массивы а и Ь; б) только один раз. В конце работы программ длина массива с должна быть записана в переменной 1с. 6.7. Напишите программу, которая переписывает массив а задом наперед (последний элемент становится первым, второй с конца — вторым и т. д.). Длина массива записана в переменной п. 6.8. Напишите программу поиска в массиве целых чисел о минимальной по абсолютной величине (модулю) разности между соседними элементами. Длина массива записана в переменной п. ЗАДАЧИ 183 6.9. В условие предыдущей задачи добавьте печать номера первого из двух элементов, на которых достигается минимальная разность. 6.10. Напишите программу поиска в массиве целых положительных чисел а такой пары соседних чисел, что отношение большего из них к меньшему максимально. Длина массива а не меньше 2, она записана в переменной п. 6.11. В массиве а записаны результаты выступлений спортсменов, всего я результатов (число я записано в переменной я). Напишите программу, которая составляет таблицу рекордов — массив г, в котором записаны в порядке возрастания рекордные результаты, т. е. те результаты из массива а, которые больше всех предыдущих. 6.12. Напишите программу, которая по заданному массиву целых чисел а составляет массив таха, содержащий номера элементов массива а, принимающих наибольшее значение среди первых я элементов массива. Число я записано в переменной я. Например, если элементы массива а равны 2, 6, 1, 4, 6, 6, 3 и я = 7, то массив таха будет содержать три элемента 2, 5, 6. 6.13. В массиве s записаны результаты измерений координаты тела, движущегося по прямой вправо. Всего проведено 100 наблюдений через равные промежутки времени. Напишите программу, которая за один просмотр массива s находит начальный отрезок наблюдений (от первого до /г-го), на котором средняя скорость тела наибольшая. 6.14. В массиве t записаны моменты времени, когда проводилось наблюдение за положением тела, движущегося по прямой вправо, а в массиве s записаны координаты тела в эти моменты времени. Всего проведено 100 наблюдений. Напишите программу, которая определяет промежуток времени между двумя наблюдениями, на котором средняя скорость тела наибольшая. 6.15. Напишите процедуру поиска в двух массивах а (длина записана в переменной п) и h (длина записана в переменной т) номера первого по порядку элемента, которым они различаются. Этот номер в конце работы процедуры должен быть записан в переменной I. Например, если а начинается с элементов 5, 3, 8, 27, а Ь начинается с элементов 5, 3, 2, 27, то в конце работы процедуры переменная I должна иметь значение 3, поскольку на третьем месте в а и Ь стоят различные элементы, а до этого — попарно одинаковые. 184 ЗАДАЧИ 6.16. в двух возрастающих массивах а (длина записана в переменной п) VI Ь (длина записана в переменной т) записаны целые числа. Напишите процедуру подсчета числа одинаковых элементов в а и 6, которое в конце работы процедуры должно быть записано в переменной answer. Например, если а состоит из элементов 1, 3, 8, 27, а Ь — из элементов 3, 12, 20, 27, 35, то в конце работы процедуры значение переменной answer должно быть равно 2, поскольку ровно два элемента 3 и 27 встречаются в обоих массивах. 6.17*. В двух возрастающих массивах а (длина записана в переменной п) и Ь (длина записана в переменной т) записаны целые числа. Напишите процедуру подсчета длины максимальных одинаковых кусков подряд идущих элементов в а и Ь, которая в конце работы процедуры должна быть записана в переменной answer. Например, если а состоит из элементов 1, 3, 8, 27, 42, аЬ — из элементов 3, 5, 7, 8, 27, 35, 42, то процедура должна возвращать значение 2, поскольку кусок 8, 27 встречается в обоих массивах, а другие повторяющиеся куски 3 и 42 короче. 6.18. Каждое утро делайте как можно больше отжиманий от пола. Записывайте результаты. Напишите программу, которая содержит массив с вашими результатами и строит столбиковую диаграмму по этому массиву. Для каждого дня занятий программа должна рисовать столбик, высота которого пропорциональна числу отжиманий. 6.19. Проведите измерение роста всех учеников в вашем классе. Составьте программу, которая наглядно изображает данные эксперимента. Исходные данные эксперимента занесите в массив рост, элемент массива рост[г] равен росту i-ro ученика в классе. Если вы затрудняетесь перенумеровать одноклассников, загляните в классный журнал или прочитайте главу 9. Чтобы изобразить полученные данные наглядно, примените следующий прием. Разбейте возможные значения роста на интервалы по 5 см и подсчитайте таблицу частот, в которой для каждого интервала указано, сколько человек имеет рост, попадающий в этот интервал. Программа должна определить наибольший и наименьший рост, подсчитать, сколько нужно интервалов по 5 см, чтобы покрыть все возможные значения роста, подсчитать таблицу частот и нарисовать столбиковую диаграмму, изображающую эту таблицу. В задачах 6.20—6.23, 6.30, 6.33—6.37 считайте, что длина массива а записана в переменной п. Ответ на вопрос каждой задачи должен быть записан в переменной answer, значение которой в конце работы процедуры должно равняться 1, если ответ «да», и 0, если ответ «нет». ЗАДАЧИ 185 6.20. Напишите процедуру одинаковые, которая проверяет, верно ли, что все элементы массива одинаковы. 6.21. Напишите процедуру неубывающий, которая проверяет, верно ли, что каждый следующий элемент массива не меньше предыдущего, т. е. что массив неубывающий. 6.22. Напишите процедуру всецелые, которая проверяет, верно ли, что все элементы массива имеют целые значения. 6.23. Напишите процедуру всеразные, которая проверяет, верно ли, что среди элементов массива нет одинаковых. 6.24*. а) Найдите максимальное количество сравнений чисел, которое выполняется при вызове процедуры всеразные, когда длина массива равна 5. б) Измените процедуру всеразные так, чтобы она подсчитывала количество выполненных сравнений чисел. в) Определите, сколько сравнений чисел выполняется при работе программы для каждого из значений п от 1 до 100. Используйте модифицированную процедуру из пункта б и запишите результат в массив. Процедура номера описана в задаче 6.3. г) С помощью Чертежника постройте график зависимости числа сравнений от значения п. 6.25. Напишите процедуру всеразныеупор, которая проверяет, среди элементов неубывающего массива нет одинаковых. что 6.26*. а) Найдите максимальное количество сравнений чисел, которое выполняется при вызове всеразныеупор, когда длина массива равна 5. б) Измените процедуру всеразныеупор так, чтобы она подсчитывала количество выполненных сравнений чисел. в) Определите, сколько сравнений чисел выполняется при работе программы для каждого из значений « от 1 до 100. Используйте модифицированную процедуру из пункта б и запишите результат в массив, г) С помощью Чертежника постройте график зависимости числа сравнений от значения п. 186 ЗАДАЧИ 6.27. В массиве а длины п содержатся по одному разу все числа от О до п, кроме одного. Напишите процедуру, которая ищет это единственное число. Подсказка. Чем может помочь вычисление суммы всех элементов в массиве? Перестановка чисел от 1 до п — это запись этих чисел в некотором порядке, необязательно в порядке возрастания или убывания. Вот, например, несколько перестановок чисел от 1 до 8: (1, 2, 3, 4, 5, 6, 7, 8); (2, 7, 8, 4, 5, 6, 1, 3); (3, 5, 7, 1, 2, 8, 6, 4). Перестановку можно задать массивом длины п, элементы которого содержат элементы перестановки. 6.28. В массивах а, Ь, с записаны три перестановки чисел от 1 до 8, указанные выше. Найдите, чему равны а[2], а[6], Ь[3], Ь[5], с[1], с[7]. 6.29. Напишите процедуру промежуток, которая проверяет, верно ли, что значение переменной х — целое положительное число, которое не превосходит значения переменной п. 6.30. Напишите процедуру этоперестановка, которая проверяет, верно ли, что первые п элементов массива а образуют перестановку чисел от 1 до п. Используйте построенные выше процедуры всеразные и промежуток. 6.31. Каждый элемент массива а является целым числом от 1 до 33. Напишите программу, которая составляет таблицу частот, т. е. массив /, в котором для каждого числа от 1 до 33 записано, сколько раз это число встречается в массиве а. (Длина массива а записана в переменной п.) 6.32. Напишите процедуру сколькоразных, которая определяет, сколько различных целых чисел от 1 до /г содержится среди элементов массива а. Длина массива записана в переменной п, число k — в переменной к. Указание. Используйте вспомогательный массив — таблицу частот, как в предыдущей задаче. Как вы видели при решении задачи 6.24, процедура всеразные делает много сравнений чисел. Если п = 40, то в худшем случае нужно сделать 780 сравнений. Поэтому и процедура этоперестановка делает много сравнений чисел. Количество сравнений чисел при проверке того, что массив задает перестановку, можно значительно уменьшить, если применить метод вспомогательного массива. Идея этого метода состоит в том, чтобы расставить числа массива а по порядку, помещая их в другой массив Ь. Если предварительно присвоить элементам массива Ь нулевые значения, то после одного прохода по массиву а в массиве Ь будут записаны все числа, которые встре- ЗАДАЧИ 187 чаются в массиве а. Теперь проверить, что элементы массива а образуют перестановку, можно, проверив, что в массиве Ь нет нулей. 6.33. Напишите процедуру, которая реализует описанную выше идею. 6.34. Как только в массиве найдены два одинаковых числа, можно утверждать, что этот массив не образует перестановку. Измените процедуру из предыдущей задачи так, чтобы учесть это соображение. 6.35. Напишите процедуру, которая проверяет, является ли массив перестановкой, составляя таблицу частот этого массива. 6.36. Напишите программу, которая по массиву целых чисел а проверяет, есть ли такая пара элементов массива, что сумма их значений равна 100. 6.37. Решите предыдущую задачу в предположении, что массив неубывающий. Используйте двоичный поиск. 6.38. Приведите пример данных (массив и число, которое ищется в этом массиве), на котором алгоритм поиска перебором элементов массива, описанный на странице 43, делает меньше сравнений, чем описанный на странице 45 алгоритм двоичного поиска. 7^ Угадай алгоритм 7.1. Предложите простое правило, объясняющее поведение Робота, изображенное на рисунке 61. Напишите программу для Робота, выполняя которую он будет следовать предложенному правилу. а) б) в) Рис. 61 188 ЗАДАЧИ а) б) в) Рис. 62 7.2. Предложите простое правило, объясняющее поведение Робота, изображенное на рисунке 62. Напишите программу для Робота, выполняя которую он будет следовать предложенному правилу. 7.3. Пусть Робот следует правилу, придуманному вами в предыдущей задаче. Можно ли так выбрать начальное положение Робота на изображенном на рисунке 62 поле, чтобы Робот двигался бесконечно долго? 7.4. Предложите простое правило, объясняющее поведение Робота, изображенное на рисунке 63. Напишите программу для Робота, исполняя которую он будет следовать предложенному правилу. 7.5. Работает генератор арифметической прогрессии. Вы подошли к компьютеру в тот момент, когда от последовательности результатов на экране остались лишь два последних числа 15/4, 18/11. Какое следующее число выдаст программа? а) 6) в) Рис. 63 ЗАДАЧИ 189 7.6. Работает генератор геометрической прогрессии. Вы подошли к компьютеру в тот момент, когда от последовательности результатов на экране остались лишь два последних числа 9, 6. Какое следующее число выдаст программа? 7.7. В каждой из следующих последовательностей очередной член, начиная с третьего, определяется предыдущими. Найдите соответствующие формулы и напишите программы, порождающие эти последовательности. а) 1, 4, -3, 7, -10, 17, -27, ... ; б) 2, 2, 3, 4, 6, 9, 14, ... ; в) 3, 3, 4, 5, 7, 10, 15, 23, ... . 7.8. Объясните, почему две написанные ниже программы 1 и 2 выдают одни и те же результаты. 1) г = о ПОКА ИСТИНА ДЕЛАТЬ ЕСЛИ г < 2 ТО г= 2 ИНАЧЕ г = г- 2 КОНЕЦ печать(г) КОНЕЦ 2) г= о ПОКА ИСТИНА ДЕЛАТЬ г= 2-г печать(г) КОНЕЦ 7.9. Для каждой из приведенных ниже последовательностей напишите программу, ее порождающую. Сравните вашу программу с программами других учеников в классе. Чья программа самая простая? а) 1, 1, 2, 1, 3, 1, 4, 1, 5; б) 2, 3, 4, 5, 4, 5, 6, 7, 10, 11; в) 1, 1, 1, 1, 2, 2, 2, 3, 3, 4, 5; г) 2, 1, 3, 1, 1, 2, 1, 3, 1, 1; д) 9, 4, -1, 1, -4, -2, о, 2, -3; е) 1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4; ж) 1, 1, 2, 1, 2, 3, 1, 2, 3, 4, 1. Какие из последовательностей, порождаемых написанными вами программами, вы уверены, будут периодическими? Почему? 7.10. Укажите правило, по которому образована последовательность 2, 3, 5, 7, 11, 13, 17, 19, 23. 7.11. Проверьте, что таблица Человек 0 1 1 2 3 5 2 1 1 1 4 Программа 0 1 2 4 7 12 14 15 16 17 21 190 ЗАДАЧИ правильно описывает историю общения с программой г= О ПОКА ИСТИНА ДЕЛАТЬ читать(а) г = г + а печать(г) КОНЕЦ 7.12. Заполните таблицу истории общения с программой г= О ПОКА ИСТИНА ДЕЛАТЬ читать(а) ЕСЛИ г < а ТО г = а КОНЕЦ печать(г) КОНЕЦ В верхней строке записаны числа, которые вводил человек, в нижней вы должны записать ответы программы. Человек 0 1 1/5 2 -1 -5 20 15 13 16 4 Программа Сформулируйте правило, по которому программа выдает ответ. 7.13. Придумайте, какие числа должен вводить человек, чтобы программа г = О ПОКА ИСТИНА ДЕЛАТЬ печать(г) читать(а) г = г + а КОНЕЦ выдавала бы следующие числа: Человек Программа 0 1 2 4 7 12 14 15 16 17 21 ЗАДАЧИ 191 7.14. В таблице приведена история общения с некоторой программой. В верхней строке записаны числа, которые вводил человек, в нижней строке — числа, которые выдавала в ответ программа. Человек 1 1 1 1/2 10 -100 -200 5 1/3 80 100 Программа 1 1 0 0 0 1 1 1 1 1 0 Напишите программу, которая ведет себя точно так же. Сравните вашу программу с программами других учеников в классе. Чья программа проще? Сформулируйте правило, по которому программа выдает ответ. 7.15*. В таблице приведена история общения с некоторой программой. В левой колонке указано слово, которое вводил человек, в правой — ответ программы. Человек Программа Привет В превед В зануда а еще что-нибудь б Я понял л Напишите программу, которая ведет себя так же. Сравните вашу программу с программами других учеников в классе. Чья программа проще? Сформулируйте правило, по которому программа выдает ответ. 7.16. После ввода чисел 1, 2, 3 программа выдала последовательность 3, 5, 8. После ввода чисел 2, 2, 2 — последовательность 4, 6. А после ввода чисел 3, 4, 5 — последовательность 7, 11, 18, 29, 47. Напишите программу, которая ведет себя таким образом. Сравните вашу программу с программами других учеников в классе. Чья программа проще? 192 ЗАДАЧИ 8. Эффективные вычисления 8.1. Напишите процедуру остатокстепени, которая вычисляет остаток от деления числа х" на d. Число х записано в переменной X, число п — в переменной п, а число d — в переменной d. Проверьте, дает ли ответ ваша процедура при вызове остаток-степени, если X = 3, л = 10000, d = 10001. 8.2. Один из способов ускорить вычисление остатка от степени числа — использовать стратегию Раздвоителя. Напишите процедуру нахождения остатка от степени, которая использует эту идею. Проверьте ее работу на нескольких вызовах с большими числами. Сравните ее работу с процедурой из предыдущей задачи. 8.3. Другой способ вычисления остатка от степени числа получается, если заметить, что множество возможных значений остатка конечно и остатки рано или поздно начнут повторяться. Напишите процедуру нахождения остатка от степени, которая использует эту идею. Проверьте ее работу на нескольких вызовах с большими числами. Сравните ее работу с работой процедур из предыдущих задач. 8.4. Напишите процедуру поиска наименьшего простого делителя числа п. Примените вашу процедуру к числам 10, 45, 129, 100 018, 743 271 385. Какой результат она дает? 8.5. Напишите процедуру поиска наибольшего простого делителя числа п. Попробуйте применить вашу процедуру к числам, указанным в предыдущей задаче. Во всех ли случаях вы дождались ответа? 8.6. Напишите процедуру поиска первого числа, которое больше заданного числа а и является степенью 2. Например, если а = 8, то процедура должна найти число 16. 8.7*. Напишите процедуру поиска первого числа, которое больше заданного числа а и все простые делители которого — это 2 и 3. Например, если а = 8, то процедура должна найти число 9. 8.8. Напишите процедуру поиска первого простого числа, которое больше заданного числа а. Например, если а = 8, то процедура должна найти число 11. 8.9. Напишите программу, вычисляющую значение многочлена х^'^ - - ... -х^ + X - 1 быстрее, чем по схеме Горнера. ЗАДАЧИ 193 8.1. Алгоритмы и формулы 8.10. Составьте таблицу состояний переменных i и s в момент проверки условия в конструкции повторения при работе программы 1. 8.11. Докажите, что в каждый момент проверки условия в конструкции повторения программы 2 значение переменной s равно удвоенному значению переменной i. 1) .i) \ ' 1 = 0 1 = 0 ■8 = 0 8 = 0 п = 6 п = 10 ПОКА / < п ДЕЛАТЬ ПОКА / < л ДЕЛАТЬ 8 = 8 -1- 3 5 8=8+2 i = i+ 1 /■ = / + 1 КОНЕЦ КОНЕЦ 8.12. Составьте таблицу состояний переменных i и s в момент проверки условия в конструкции повторения при работе программы 3. 8.13. Докажите, что при каждой проверке условия в конструкции повторения программы 4 значение переменной s равно i(i - 1)/2, где i— значение переменной i. 3) /= 0 4) / = 0 8 = 0 8 = 0 П = 6 п = 10 ПОКА / < п ДЕЛАТЬ ПОКА i < л ДЕЛАТЬ 8 = 8 + / / = / + 1 S = S + i : 1 = / + 1 КОНЕЦ КОНЕЦ 8.14. Составьте таблицу состояний переменных / и s в момент проверки условия в конструкции повторения при работе программы 5. 8.15. а) Докажите, что при каждой проверке условия в конструкции повторения программы 6 значение s равно ((г - 1)/2)^. б) Какое число напечатает программа? 5) 6) /=0 / = 0 8 = 0 ' 8 = 0 л = 7 Л = 10 ПОКА i < л ДЕЛАТЬ ПОКА / < л ДЕЛАТЬ , 8 = 8 + 2*/+ 1 8 = 8 + /■ ] I = / + 1 / = /■ + 2 КОНЕЦ КОНЕЦ печать(8) 194 ЗАДАЧИ 8.16. Напишите процедуру разность, которая по массиву а длины п строит массив Ь длины п - 1, элементы которого являются разностями соседних элементов массива а, т. е. выполняются равенства Ь[/] = а[/+ 1] -а[/]. 8.17. Заполните массив а длины 10 по правилу а[/] = 13*/- 5. Примените к массиву а процедуру разность. Сформулируйте свойство элементов полученного в результате массива Ь. 8.18. Решите задачу, аналогичную предыдущей, с массивом а длины 10, элементы которого заданы правилом а[/] = 2*/*/ - 3*/ - 5. Примените к массиву а процедуру разность, получив массив Ь. Сформулируйте свойство элементов массива Ь. 8.19. Составьте таблицу состояний переменных а, 6 и s в момент проверки условия в конструкции повторения при работе программы 7. 8.20. Не исполняя программы 8, определите, какие числа она напечатает. 7) 8) 1=2 / = 2 а = 0 а = 1 Ь = 0 Ь = 4 5 = 0 5 = 0 п = 8 л = 10000 ПОКА / < л ДЕЛАТЬ ПОКА 1 < л ДЕЛАТЬ 5 = 2*Ь - а + 1 5 = 2*Ь - а + 2 а = Ь а = Ь Ь = S Ь = 5 / = / + 1 1= I + ^ печать(5) печать(5) КОНЕЦ КОНЕЦ 8.2. Позиционная система счисления в программировании часто используется шестнадцатеричная система счисления. Будем обозначать цифру 10 буквой А, 11 —В, 12 — С, 13 — D, 14 — Е, 15 — F. 8.21. Запишите в шестнадцатеричной системе счисления числа: а) 16; б) 20; в) 32; г) 100; д) 120; е) 195; ж) 256. ЗАДАЧИ 195 8.22. Сравните числа, заданные в шестнадцатеричной системе счисления: а) 110 и IFF; б) FA и FC; в) 25 и 15; г) 1000 и FFF. В каждой паре укажите, какое из чисел больше и на сколько. 8.23. Найдите десятичную запись чисел, которые являются результатами действий с числами в шестнадцатеричной записи: а) FE -- EF; б) 12 ч- А; в) ВС*АС; г) АА - 11*В; д) FFF -Ь 1; е) DE -AF. 8.24. На какие цифры может заканчиваться шестнадцатеричная запись числа, кратного 8? 8.25. Напишите процедуру шестнадцатеричная, которая печатает шестнадцатеричную запись целого положительного числа х (для этого вам понадобятся символьные переменные). Учтите, что самый младший разряд должен быть самым правым. 8.26. Напишите процедуру, которая сравнивает два положительных целых числа, записанные в шестнадцатеричной системе счисления. Цифры чисел а и Ь записаны в массивах а, Ь, а длины этих массивов — в переменных п, k соответственно. Цифры записаны начиная с младшего разряда. В конце работы процедуры значение переменной answer должно равняться 1, если число, записанное в массиве а, больше; -1, если число, записанное в массиве а, меньше; и 0, если числа равны. 8.27. Шестизначное число называется счастливым, если сумма первых его трех цифр равняется сумме последних трех цифр. Напишите процедуру счастливое, которая определяет, является ли число п счастливым. Если в числе меньше шести знаков, нужно добавить недостающие нули слева. Процедура должна присваивать переменной happy значение 1, если п — счастливое число, и о во всех остальных случаях. 8.28. Напишите процедуру перевода записи числа из одной системы счисления в другую. 8.29. Напишите программу, которая печатает все числа из промежутка от 1 до 100, все цифры которых в двоичной записи равны 1. 8.30. Напишите программу, которая печатает все числа из промежутка от 1 до 100, у которых в двоичной записи вторая слева цифра равна 1. Например, число 1 печатать не нужно (у него всего одна цифра), 2 — не нужно (вторая слева цифра равна 0), 3 — нужно (3 = II2) и т. д. 8.31*. Напишите программы, которые решают две предыдущие задачи и не используют процедуры записи числа в двоичной системе. 8.32. Напишите программу, печатающую все числа от 1 до 100, в двоичной записи которых нет рядом: а) двух единиц; б) двух нулей. 196 ЗАДАЧИ 8.33. При построении псевдослучайных последовательностей иногда используется такое правило получения следующего числа: возведем текущее л-значное число в квадрат и оставим средние цифры этого квадрата — они и будут давать следующее число. Пример в десятичной системе: если текущее число равно 28, р то следующее равно 78, так как 28 = 0784 (ноль слева добавлен, чтобы количество знаков у квадрата было в два раза больше, чем у числа). Пример в двоичной системе: после числа IOIO2 следует число IOOI2, так как IOIO2 = 10, 10^ = 100, 100 = IIOOIOO2. Напишите процедуру, которая строит последовательность чисел по этому правилу. Обыкновенную дробь а/Ъ можно представлять в виде десятичной. Для этого нужно делить а на & столбиком и записывать получающиеся цифры. Для некоторых дробей алгоритм деления столбиком заканчивается (например, 2/5 = 0.4), а для некоторых — нет (например, 1/3 = 0.33333...). Можно считать, что в первом случае все цифры дроби, начиная с некоторой, равны 0. 8.34. Напишите процедуру цифры-дроби, которая находит первые п десятичных цифр дроби а/Ъ. Числа п, а, Ь записаны в одноименных переменных. 8.35*. Цифры десятичной записи любой обыкновенной дроби начинают повторяться с некоторого места, например 61/495 = = 0.123232323... . Повторяющийся набор цифр называют периодом:. Напишите процедуру период, которая находит период дроби а/Ь. Найдите с ее помощью периоды всех дробей 1/д, в которых п принимает значения от 1 до 1000. 8.36. Измените процедуру период из предыдущей задачи так, чтобы она находила период в последовательности семеричных цифр обыкновенной дроби. Найдите семеричные периоды всех дробей \/п, в которых п принимает значения от 1 до 1000. Сравните полученные результаты с предыдущей задачей. Есть ли связь между периодами дробей в разных системах счисления? 8.3. Алгоритм Евклида 8.37. Упрощать дроби можно двумя способами: разложением числителя и знаменателя на простые множители и вычислением НОД. Сравните эти два способа на примерах дробей: а) jg , , 899 ч 111 123 ’ ж1^- Ж) , , 3431 1241 лч 21 . 13 ’ , 629 600 ’ , 1956 1975 ’ 10807 10708 ’ ЗАДАЧИ 197 8.38. Найдите такую пару шестизначных чисел, на которой выполнение алгоритма Евклида требует меньше 10 команд. 8.40. Найдите такую пару трехзначных чисел, на которой выполнение алгоритма Евклида требует больше всего команд. 8.41. Напишите программу подсчета числа несократимых дробей среди всех дробей вида а/6, где а и Ь принимают значения от 1 до 100. ^ Перебор и случайность 9.1. На поле Робота имеется одна бесконечная стена, неизвестно, горизонтальная или вертикальная. Напишите программу, выполняя которую Робот найдет стену, двигаясь по раскручивающейся спирали (рис. 64). 9.2. Напишите для Робота процедуру естьстены, выполняя которую Робот определяет, может ли он за п ходов достичь стены (оказаться в клетке, соседней со стеной), и возвращается в исходное положение. Процедура естьстены должна присваивать переменной answer значение 1, если за п ходов Робот может достичь стены, и о в противном случае. 9.3. Напишите для Робота программу поиска клада в полуплоскости. На рисунке 65 указано начальное положение Робота. 9.4. Напишите для Робота программу поиска клада в квадранте. На рисунке 66 указано начальное положение Робота. Рис. 64 Рис. 65 198 ЗАДАЧИ Рис. 66 Г !• 1 Рис. 67 9.5. Напишите процедуру поиска клада в повернутом квадрате, изображенном на рисунке 67. Потребуются ли вам переменные? 9.6. Напишите процедуры поиска клада в квадрате, если известно, что клад находится в одной из отмеченных точек, изображенных на рисунке 68. Длина стороны квадрата записана в переменной IV. Процедуры должны быть как можно более эффективными. В случаях а, б и г клад может находиться и в клетке, соответствующей начальному положению Робота. Задачи 9.7—9.9 нужно решать в пессимистическом предположении, что героям этих задач не везет. 9.7. Даша оставила Саше свой номер телефона, но одну цифру в нем назвала неправильно. Сколько номеров телефонов придется перебрать Саше, чтобы дозвониться до Даши? 9.8. Гена идет в гости. На двери подъезда цифровой замок с трехзначным цифровым кодом. Гена кода не знает, но уверен, что среди цифр кода есть цифра 5. Сколько попыток придется сделать Гене, прежде чем он попадет в гости? ЗАДАЧИ 199 9.9. Вася забыл номер телефона Пети. Он точно знает, что в номере телефона семь цифр, среди которых есть две цифры 5, две цифры 9 и одна цифра 0. Сколько номеров телефонов придется перебрать Васе, чтобы дозвониться до Пети? 9.10. Значение переменной п равно 9. Сколько команд будет исполнено при вызове процедур: а) простое!; б) простоеЗ? (Тексты этих процедур см. в учебнике на с. 91 и 92 соответственно.) 9.11. Числа 1 = 1^, 4 = 2^, 9 = 3^, 16 = 4^ и т. д. называются полными квадратами, поскольку они являются квадратами целых чисел. Напишите процедуру квадрат, которая определяет, является ли число п полным квадратом. а) б) в) Рис. 68. Возможные положения клада в квадрате г) 200 ЗАДАЧИ 9.12. Напишите процедуру дваквадрата, которая проверяет, является ли число п суммой двух квадратов целых чисел. (Скажем, число 10 является суммой двух квадратов; 10 = 3^ -Ь 1^, а число 11 нет.) 9.13*. Даны возрастающий массив а целых неотрицательных чисел и его длина п. Напишите процедуру, которая ищет наименьшее натуральное число, непредставимое в виде суммы двух различных элементов этого массива. 9.14*. Напишите процедуру, которая печатает все целые числа от 1 до 10 000, которые не представляются в виде суммы трех квадратов. Выполните программу на компьютере. 9.15*. Напишите процедуру, которая печатает все целые числа от 1 до 10 000, которые не представляются в виде суммы четырех квадратов. Выполните программу на компьютере и убедитесь, что таких чисел нет. 9.16. Число называется треугольным, если его можно записать как 1 + 2 + ... -Ь п = п(п + 1)/2, где п — натуральное. Напишите процедуру проверки того, что число является треугольным. Можно ли применить в этой процедуре двоичный поиск? 9.17*. Найдите все целые числа от 1 до 10 000, которые не являются суммой двух треугольных чисел. 9.18. Напишите программу, которая составляет таблицу простых чисел из промежутка от 1 до п. Указание. Используйте прием, который называется решето Эратосфена-, из списка чисел от 1 до п вычеркиваем все четные числа, большие 2, все числа, которые делятся на 3 и больше 3, и т. д. 9.19. Напишите программу, которая печатает все слова длины п, состоящие из о и 1. 9.20. Напишите программу, которая печатает все слова длины п, состоящие из о и 1, в которых ровно k единиц. 9.21. Напишите программу, которая печатает все подмножества множества чисел от 1 до п. 9.22. Напишите программу, которая печатает все перестановки чисел от 1 до п. 9.23*. Напишите процедуру подсчета числа счастливых билетов (сумма первой половины цифр равна сумме второй половины цифр). В переменной п записана половина числа цифр в номере билета, а в переменной р — основание системы счисления, в которой записаны цифры номера билета. ЗАДАЧИ 201 lO.j Вероятности и комбинаторика 10.1. Изучите программу, которая использует команду случай для порождения случайного бита в предположении, что 0 или 1 возникают равновероятно, а последовательные применения команды случай независимы: ПРОЦ кость! НАЧАЛО случай(с/) случай(е) случайно d = d+ 2*е d = d + 4*f ЕСЛИ d > 5 ТО d = о ИНАЧЕ d = d + 1 КОНЕЦ КОНЕЦ Докажите, что: а) программа возвращает числа от 0 до 6; б) вероятности появления чисел от 1 до 6 одинаковы и равны 1/8; в) вероятность появления 0 равна 1/4. 10.2. Процедуру из предыдущей задачи можно рассматривать как имитацию подбрасывания кубика. Если она возвращает 0, то мы считаем, что имитация не удалась. Таким образом, из предыдущей задачи следует, что процедура кость! имитирует подбрасывание кубика с вероятностью ошибки 1/4 = 0.25, или 25 %. Определите, с какой вероятностью ошибки имитирует подбрасывание кубика следующая процедура: ПРОЦ кость2 НАЧАЛО е = ! f = о ПОКА (е > О) И (t < 4) ДЕЛАТЬ d = кость! ЕСЛИ d > о ТО е = о КОНЕЦ 1=1+! КОНЕЦ КОНЕЦ 202 ЗАДАЧИ 10.3. Иногда советуют выбирать пароль так, чтобы все символы в нем были различны. Исследуйте этот вопрос экспериментально: напишите процедуру, которая порождает случайный пароль, и программу, которая определяет, сколько из выбранных случайно паролей содержат одинаковые символы. Посмотрите на результаты работы такой программы при разных длинах пароля. Попробуйте дать обоснованную оценку предложенного выше совета. 10.4. Напишите процедуру, которая печатает все пароли, состоящие из различных символов, причем по одному разу каждый. Совет. Для начала сформулируйте правило, по которому вы будете строить такие пароли. 10.5. /1 = 0 п= 10000 i = о ПОКА / < п ДЕЛАТЬ серия КОНЕЦ печать(/?/л) ____ Насколько велики шансы, что после 1000 подбрасываний монеты герб выпадет ровно 500 раз? Исследуйте этот вопрос экспериментально. Напишите процедуру серия, которая имитирует 1000 подбрасываний монеты и, если ровно 500 раз команда случай выдавала 1, увеличивает на 1 значение переменной Л. Выполните программу, показанную справа. Число, которое напечатает программа, будет близко к вероятности выпадения 500 гербов в серии из 1000 подбрасываний. Запустите программу несколько раз при том же значении переменной п. Обратите внимание на то, насколько различаются результаты нескольких запусков этой программы. 10.6*. Есть ли у вас в классе ученики с одним и тем же днем рождения? Попробуйте экспериментально оценить вероятность того, что в наугад выбранном классе есть ученики с общим днем рождения, написав программу, которая для заданного количества учеников выбирает случайные дни рождения. Чтобы получить достаточно точную оценку вероятности, нужно повторить работу этой программы много раз и подсчитать долю тех случаев, когда встретились одинаковые дни рождения. 10.7*. Два игрока подбрасывают монетку. Первый выигрывает, если выпадают три герба подряд. Второй — если выпадает серия «герб — решетка — герб». Подбрасывания продолжаются до тех пор, пока не выпадет одна из указанных серий. Чьи шансы более благоприятны? Исследуйте вопрос, написав программу, имитирующую эту игру. ЗАДАЧИ 203 11.| Игровые алгоритмы 11.1. На доске в ряд записано 10 единиц: 1111111111. Ход состоит в том, что играющий ставит между двумя соседними единицами знак сложения « + » или знак умножения «•». После того как девять знаков поставлены, т. е. между любыми двумя соседними единицами стоит какой-нибудь знак, вычисляется значение полученного выражения. Если оно четно, то выигрывает первый игрок. Если нечетно, то второй. Например, если знаки стоят так: 1 • 1 • 1 + 1 • 1 -Ь 1 -Ь 1 -М • 1 ■ 1, то значение полученного выражения равно 5, оно нечетно, значит, выиграл второй игрок. Придумайте выигрышную стратегию для первого игрока. 11.2. Кто выигрывает в игре из предыдущей задачи, если на доске 11 единиц? 11.3. Вот стратегии в игре с единицами из двух предыдущих задач: A. Ставить знак «-Ь» на самое левое свободное место. Б. Ставить знак «•» на самое левое свободное место. B. Ставить знак «-Ь» на самое правое свободное место. Г. Ставить знак «•» на самое правое свободное место. Кто выигрывает при игре с десятью единицами, если: а) первый игрок придерживается стратегии А, а второй — стратегии Г; б) первый игрок придерживается стратегии В, а второй — стратегии Б; в) первый игрок придерживается стратегии А, а второй — стратегии В? Ответьте на те же самые вопросы при игре с 11 единицами. 11.4. На столе лежат несколько конфет. Своим ходом играющий может взять либо число конфет, строго меньшее половины, либо ровно одну конфету. Выигрывает тот, кто взял последнюю конфету. Кто выигрывает при правильной игре, начинающий или его партнер, если на столе: а) 5 конфет; б) 8 конфет; в) 9 конфет? 11.5. Вот стратегии для игры в конфеты из предыдущей задачи: A. Брать всякий раз наибольшее разрешенное число конфет. Б. Брать всякий раз одну конфету. B. Брать всякий раз две конфеты, а если это невозможно, то одну конфету. Сравнивая стратегии попарно, выясните, какая из них лучшая для первого игрока, если на столе 8 конфет. 204 ЗАДАЧИ 11.6. Запрограммируйте стратегии игры в конфеты из предыдущей задачи. 11.7. Есть кусочек шоколадки размером 1 X 10, разделенный бороздками на квадратные дольки размером 1X1. Первый игрок своим ходом разламывает кусочек на две части вдоль бороздки. Второй игрок выбирает одну из двух получившихся частей и также разламывает ее на две части вдоль бороздки. Затем первый делает то же самое с одной из трех получившихся частей и т. д. Тот, после хода которого отломилась одна долька, проигрывает. Найдите выигрышную стратегию для первого игрока. Подсказка. Позиции, в которых одна из частей — долька, можно не рисовать. 11.8. Нарисуйте диаграмму позиций и закрасьте выигрышные позиции для первого игрока при игре в шоколадку из предыдущей задачи. 11.9. Достроив диаграмму позиций из предыдущей задачи, выясните, кто из игроков выигрывает, если начальный размер кусочка шоколадки: а) 1 х Ц; б) 1 X 12. Как он должен играть? 11.10. На доске выписаны числа от 1 до 26. Ход состоит в том, что играющий зачеркивает одно из выписанных чисел. После 25 ходов на доске остается одно число. Если последнее оставшееся на доске число нечетное, то выигрывает первый игрок, если четное, то выигрывает второй. Найдите выигрышную стратегию для первого игрока. 11.11. На доске выписаны числа от 1 до 26. Ход состоит в том, что играющий зачеркивает одно из выписанных чисел. После 25 ходов на доске остается одно число. Если последнее оставшееся на доске число делится на 3, то выигрывает первый игрок, если не делится, то выигрывает второй. Найдите выигрышную стратегию для второго игрока. 11.12. Вот три стратегии игрока в игре из предыдущей задачи: A. Вычеркивать самое маленькое число. Б. Вычеркивать самое большое число. B. Вычеркивать самое маленькое число — такое, чтобы сумма оставшихся на доске чисел делилась на 3; если это невозможно, то вычеркивать самое маленькое число. Кто из игроков победит, если: а) первый игрок придерживается стратегии А, а второй — стратегии Б; б) первый игрок придерживается стратегии Б, а второй — стратегии В; в) первый игрок придерживается стратегии В, а второй — стратегии А? ЗАДАЧИ 205 к _ > ? : Рис. 69 _ > Рис. 70 11.13. Двое играют в такую игру. На блюдце лежит а батончиков и Ь ирисок. Игроки по очереди съедают по одной конфете. Съевший последний батончик выигрывает. Составьте диаграмму позиций для этой игры, если а = 5, 6 = 7. Заштрихуйте выигрышные позиции для первого игрока. Кто выигрывает при правильной игре? 11.14. В горизонтальном прямоугольнике 2 X 10 разрешается за один ход вычеркнуть либо 1 квадратик, либо столбик из двух квадратиков (рис. 69). Вычеркнувший последний квадратик выигрывает. Придумайте выигрышную стратегию для второго игрока, основанную на симметрии. Придумайте выигрышную стратегию для первого игрока при игре на прямоугольнике 2 X Ц. 11.15. В горизонтальном прямоугольнике 2 X Ю разрешается за один ход вычеркнуть либо 1 квадратик, либо столбик из двух квадратиков, либо два соседних квадратика по горизонтали (рис. 70) . Вычеркнувший последний квадратик выигрывает. Придумайте выигрышную стратегию для второго игрока, основанную на симметрии. Придумайте выигрышную стратегию для первого игрока при игре на прямоугольнике 2 х Ц. 11.16. Игра в диагонали происходит следующим образом. В начале игры отмечены вершины правильного многоугольника (рис. 71) . Игроки по очереди проводят отрезки по диагоналям этого многоугольника так, чтобы проведенные отрезки не имели общих точек (даже концов). Нельзя проводить отрезки по сторонам многоугольника (штриховая линия на рисунке 71). Выигрывает сделавший последний ход. Для какого игрока есть выигрышная стратегия при игре в диагонали 2006-угольника? Рис. 71 12. Символьные переменные 12.1. Потренируйтесь в определении словарного порядка. Не заглядывая в словарь, попробуйте определить, есть ли слова между: а) «СЛОВНО» и «СЛОВО»; б) «ЧАША» и «ЧА1ЦА»; в) «СЮРПРИЗ» и «ТАБОР»; г) «ИЮНЬ» и «ЙОГ». 206 ЗАДАЧИ 12.2. Напишите процедуру этобуква, которая по символьной переменной $а проверяет, является ли ее значение буквой (строчной или прописной) русского алфавита. Если ответ положительный (в переменной записана буква), процедура присваивает переменной б значение 1, в противном случае — значение 0. 12.3. Напишите процедуру этогласная, которая по символьной переменной $а проверяет, является ли ее значение гласной буквой (строчной или прописной) русского алфавита. Если ответ положительный (в переменной записана буква), процедура присваивает переменной г значение 1, в противном случае — значение 0. 12.4. В программе есть строки 12.5. этобуква этогласная Напишите сложное условие, которое истинно в том и только в том случае, когда значение переменной $а — согласная буква русского алфавита. В процедуре замена1 замены: используется такая таблица шифра ЕЗРЛЪУЁАИБНЧОВЙГПКЦЬДХСЫМЩЭФЯЮШЖТ Напишите процедуру расшифровывания, восстанавливающую исходный текст по шифровке. Расшифруйте зашифрованное этим шифром сообщение: ГЗКЕЬЙЕТПУКУЦЬЕЙГРЧЕ 12.6. Пусть в процедуре замена2 используется такая таблица шифра замены: ГДЕЁЖЗИЙКЛМНОПРИТУФХЦЧШАЪЫЬЭЮЯАБВ Можете ли вы написать процедуру расшифровывания этого шифра? 12.7. Как нам уже известно, шифр замены может основываться на довольно длинном ключе (приводился пример ключа длины 6 из романа Жюля Верна). Напишите процедуру заменапоключу, которая использует такие данные: массив целых чисел ключ, задающий ключ сдвига, переменную длина, задающую длину массива ключ, символьную переменную $текст, задающую сообщение, которое надо зашифровать, переменную длинатекста, в которой записана длина переменной $текст. Процедура шифрует текст и оставляет его в той же символьной переменной $текст. ЗАДАЧИ 207 12.8. Есть ли необходимость писать специальную процедуру расшифровки для шифра замены, описанного в предыдущей задаче? Если вы затрудняетесь ответить на этот вопрос, то зашифруйте процедурой заменапоключу сообщение УКОЛЬЦАНЕТКОНЦАВЛЕВОПОЙДЁШЬСПРАВАПРИДЁШЬ с ключом сдвига 1, 4, 16. Полученную шифровку зашифруйте еще раз с ключом сдвига 32, 29, 17. В следующих задачах мы будем считать, что символьные переменные могут содержать целые тексты, состоящие из слов. Слова по определению состоят из букв русского алфавита. В тексте слова разделены пробелами и, быть может, еще какими-то знаками. 12.9. Напишите процедуру, которая проверяет, входит ли в символьную переменную $текст слово «мама». Если входит, процедура должна присвоить переменной answer значение 1, в противном случае — значение 0. Длина переменной $текст записана в переменной длинатекста. 12.10. Напишите пропедуру числослов, которая подсчитывает число слов в символьной переменной $текст и записывает это число в переменную w. Длина переменной $пгекспг записана в переменной длинатекста. В анаграмме слова или фразы для каждой буквы указывается, сколько раз она входит в слово или во фразу. Отсутствующие буквы пропускаются. Пропускаются также пробелы и знаки препинания. Строчные и прописные буквы не различаются. Например, анаграммой слова «анаграмма» будет «4аг2мнр», анаграммой фразы «анаграммы использовались учёными эпохи Возрождения для защиты приоритета» будет «6а2вг2д2еёжЗз8иЗлЗмЗн6оЗп4р2сЗтухчщ2ьЗыэ2я». 12.11. Расшифруйте анаграмму «вд5её2ин4р2пс4т2у». 12.12. Напишите процедуру, которая по слову или фразе, записанным в символьной переменной $фраза, строит анаграмму этой фразы и записывает ее в переменную ^анаграмма. Длина переменной ^фраза записана в переменной длинафразы. 12.13. Расшифруйте сообщение: ЫДРЛЕЯШО ШЁНМЕПИТ НЬФКРАУЮ АЖТНЕЫРК В качестве подсказки используйте рисунок 72. Д Л Я ш и Ф Р А Рис. 72 оОООГ ^ Ф 9 ф ^ Ф Ф Ф Ф Ф Ф Ф Ф Ф i0 Ф J Ф • • Ф ф <9 ф ) ) 9 Ф ^ Ф Ф Ф f ф О Ф Ф Ф Ф Ф Ф ф ф Ф 9 iO < 9 } « • « « О • ;9 о 9 ф • • ё' t) 9 • • • •