Text
                    Э.	3. Любимский
В. В. Мартынюк
Н. П. Трифонов
ПРОГРАММИРОВАНИЕ

'Э: 3. ЛЮБИМСКИЙ В. В. МАРТЫНЮК Н. П. ТРИФОНОВ ПРОГРАММИРОВАНИЕ Под редакцией а з. любимского Допущено Министерством высшего и среднего специального образования СССР в качестве учебного пособия для студентов вузов, обучающихся по специальности «Прикладная математика» МОСКВА «НАУКА» ГЛАВНАЯ РЕДАКЦИЯ ФИЗИКО-МАТЕМАТИЧЕСКОЙ ЛИТЕРАТУРЫ 1980
22.18 Л 93 УДК 519.6 Программирование. Л ю б и м с к и й Э. 3.» Мартынюк В.В.,Трифонов Н. П.— М.: Наука. Главная редакция физико- математической литературы, 1980 Книга содержит изложение двухгодич- ного курса лекций для студентов факуль- тета вычислительной математики и кибер- нетики МГУ. Курс соответствует новой программе для университетов. В книге нашли свое отражение те существенные изменения, которые произошли за послед- нее время как в практике использования ЭВМ, так и в общих подходах к вопросам программирования. Книга предназначена в качестве учеб- ного пособия для студентов факультетов прикладной математики университетов и вузов с повышенной математической под- готовкой. - 20204—141 Л ~053(02)-80 13-*°- 1702070000 © Издательство «Наука». Главная редакция физико-математической литература,
ОГЛАВЛЕНИЕ Предисловие........................................................... 7 Введение ...»......................................................... 9 Глава 1. Элементы теории алгоритмов................................ 13 1.1. Интуитивное понятие алгоритма и необходимость его уточнения . 13 1.2. Машины Тьюринга............................................. 17 1.2.1. Определение машины Тьюринга (17). 1.2.2. Примеры машин Тьюринга (22), 1.2.3. Возможности машин Тьюринга. Основная гипотеза теории алгоритмов (25). 1.3. Нормальные алгоритмы Маркова . . ........................... 29 1.4. Сравнение различных алгоритмических схем.................... 34 1.5. Понятие алгоритмической неразрешимости...................... 36 Г л а в а 2. Программирование на алгоритмических языках. Алгол-60 . . 40 2.1. Общая характеристика алгоритмических языков.............. 2.2. Способ описания алгоритмических языков . ,............... 2.2.1. Нормальная форма Бэкуса (45). 2.3. Алфавит алгола ......................................’ . . 2.4. Неформальное рассмотрение фрагментов алгол-программ . ... 2.5. Операнды................................................. 2.5.1. Классификация операндов (57). 2.5.2. Числа (59), 2.5.3. Переменные (61). 2.5.4. Операнды-функции (63). 2.6. Арифметические и логические выражения............... . . 2.6.1. Простые арифметические выражения (65). 2.6.2. Простые логические выражения (68). 2.6.3. Условные выражения (72). 2.7. Основные операторы....................................... 2.7.1. Оператор присваивания (76). 2.7.2. Оператор перехода и . именующие выражения (78). 2.7.3. Оператор процедуры. Опера- торы ввода и вывода (83). 2.7.4. Пустой оператор (86). 2.8. Условные операторы ... .................................. 2.9. Составные операторы...................................... 2.9.1. Примеры с использованием составных операторов, содержа- щие анализ фрагментов алгольного текста (90). 2.10. Операторы цикла.......................................... 2.10.1. Некоторые примеры с использованием операторов цикла. Оптимизация циклов (96). 2.11. Описания в алголе........................................ 2.12. Блоки и локализация...................................... 2.13. Процедуры и функции...................................... 2.13.1. Простейшие процедуры (112). 2.13.2. Локализация про- цедур (114). 2.13.3. Ограничения на фактические параметры (115). 2.13.4. Спецификации (116). 2.13.5. Параметры-значения (118). 2.13.6. Процедуры-функции (120). 2,13.7. Полный синтак- сис описания процедуры (122). 2.14. Рекурсивные процедуры.................................... 2.15. Использование комментариев............................... 2.16. Методы разработки программ............................... 2.16.1. Программирование по принципу сверху вниз (126). 2.16.2. Блок-схемы (130). 2.16.3. Программирование по принципу снизу (131), 2.16.4, Принципы структурного программирования 40 44 48 52 57 65 76 86 88 93 101 106 111 122 125 126 вверх (132),
4 ОГЛАВЛЕНИЕ Г л а в а 3. Структуры данных..................................... 133 3.1. Очереди.................................................. 3.2. Стеки . •.....................................‘........... 138 3.3. Строки.................................................... 144 3.3.1. Векторное представление строк (144). 3.3.2. Представ- ление строки в виде цепочки (145). 3.3.3. Включение симво- ла в строку и исключение символа (147). 3.3.4. Более сложные операции над строками (149). 3.3.5. Реализация универсаль- ного нормального алгоритма (152). 3.4. . Списки.................................................. 156 3.4.1. Однонаправленные списки (157). 3.4.2. Двунаправленные списки (160). 3.4.3. Кольцевые списки (162). 3.4.4. Иерархиче- ские списки (164). 3.4.5. Ассоциативные списки (Ц>5). 3.4.6. При- мер информационной сети с иерархическими и ассоциативными связями (166). 3.5. Таблицы................................................... 173 3.5.1. Простая цепочка (174). 3.5.2. Цепочка с упорядоченными записями (175). 3.5.3. Хранение ключей в отдельном массиве (176). 3.5.4. Дихотомический поиск по таблице в векторном пред- ставлении (177). 3.5.5. Двоичное дерево (180). 3.5.6. Перемешан- ные таблицы (186). 3.6. Отображение многомерных массивов на вектор................. 192 Глава 4. Электронно-вычислительные машины.......................... 196 4.1. Общая характеристика ЭВМ................................... 198 4. 1.1. Принципы фон Неймана (199). 4.1.2. Машинные операции (200 ). 4.1.3. Ячейки памяти (201). 4.2. Структура ЭВМ и ее работа.................................. 203 4.2.1. Формализованное описание работы ЭВМ (209). 4,3. Характеристика реальных ЭВМ................................ 212 4.3.1. Системы счисления (212). 4.3.2. Представление чисел (218). 4.3.3. Характеристика набора машинных операций (225). 4.3.4. Форматы команд (227). 4.4. Разнообразие систем команд ЭВМ. Примеры программ........... 228 4.4.1. Четырехадресная машина (УМ-4) (228). 4.4.2. Трехадресная машина (УМ-3) (238). 4.4.3. Двухадресная машина (УМ-2) (240). 4.4.4. Одноадресная машина (УМ-1) (245). 4.4.5. Дробно-адресная машина (УМ-Д) (249). 4.4.6. Машина с переменным форматом ко- манд (УМ-П) (251). 4.4.7. Безадресная (стековая) машина (УМ-С) (255). 4,4.8. Машины с индексными регистрами (УМИР) (263). Глава 5. Основные приемы программирования для ЭВМ............... 274- 5.1. Символическое программирование........................... 275 5.2. Характеристика УМИР-3.................................... 281 5.2.1. Общая характеристика УМИР-3 (281). 5.2.2. Основные арифметические операции (284). 5.2.3, Операции над порядками (285). 5.2.4. Операции над индексным регистром (286). 5.3. Программирование вычислений по формулам.................. 287 5.3.1. Экономия команд (289). 5.3.2. Выбор машинных операций (290). 5.3.3. Экономия рабочих ячеек и констант (291). 5.4. Программирование простых логических выражений............ 293 5.4.1. Представление логических значений (293). 5.4.2. Операции УМИР-3 логического типа (294). 5.4.3. Реализация логических операций (295). 5.4.4. Программирование простых логических выражений (296). 5.5, Программирование условных выражений и операторов......... 300
ОГЛАВЛЕНИЕ 5 5.6. Действия над кодами....................................... 301 5.6.1. Операции над кодами (302). 5.6.2. Преобразования кодов (303). 5.7. Программирование циклов................................... 307 5.7.1. Общие вопросы программирования циклов (307). 5.7.2. Цик- лы итерационного типа (309). 5.7.3. Циклы с известным числом повторений (312). 5.7.4. Управление переменными адресами (315). 5.8. Подпрограммы.............................................. 326 5.8.1. Понятие и назначение подпрограммы (326). 5.8.2. Переда- ча подпрограмме значений (328). 5.8.3. Передача подпрограмме адресов (331). 5.9. Стандартные подпрограммы.................................. 336 5.9.1. Интерпретирующая система ИС-2 (339). Г л а в а 6. Системы программирования............................. 344 .6.1. Модульное программирование.................................. 348 6.1.1. Понятие и назначение модуля (348). 6.1.2. Составление мо- дулей (354). 6.1.3. Использование модулей (359). 6.1.4. Общие объекты и их использование] (362). 6.1.5. Схемы трансляции и за- грузки (365). 6.2. Загрузчики и редакторы связей............................. 367 6.3. Трансляторы............................................... 373 6.4. Отладчики программ........................................ 378 6.4.1. Отладка программы (378). 6.4.2. Реализация отладчика. Прокрутка (385). 6.5. Редакторы текстов программ................................ 393 6.5.1. Назначение редактора текстов (393). 6.5.2. Пример работы с редактором текстов (397). 6.6. Схема функционирования системы программирования.......... 399 Глава 7. Язык программирования фортран............................ 402 7.1. Общая характеристика фортран-программы . ♦................ 403 7.2. Основные понятия.......................................... 406 7.2.1. Алфавит (406). 7.2.2. Типы данных (407). 7.2.3. Констан- ты (407). 7.2.4. Переменные (409). 7.2.5. Выражения (409). 7.3. Операторы................................................. 410 7.3.1. Операторы присваивания (410). 7.3.2. Операторы управле- ния (411). 7.4. Объявления................................................ 417 7.4.1. Объявления спецификаций (418). 7.4.2. Объявление началь- ных данных (422). 7.5. Процедуры и модули........................................ 423 7.5.1. Встроенные функции (423). 7.5.2. Внутренние функции (424). 7.5.3. Внешние функции (424). 7.5.4. Внешние подпрограм- мы (426). 7.5.5. Модуль-блок данных (427). . , 7.6. Ввод/вывод .... .......................................... 428 7.6.1. Оператор форматного вывода (429). 7.6.2. Объявление фор- мата (430). 7.6.3. Взаимодействие форматного управления со спис- ком вывода (435). 7.7. Структура программы и модулей............................ 437 7.8. Пример программирования на фортране....................... 438 Глава 8. Автокоды . . ............................................ 447 8.1. Общие сведения............................................ 447 8.2. Основные понятия.......................................... 449 8.2.1. Алфавит (449). 8.2.2. Идентификаторы (450). 8.2.3. Формат предложений (450), 8,2,4, Имена (452), 8,2.5, Счетчик размеще-
6 ОГЛАВЛЕНИЕ * ния (454). 8.2.6. Самоопределенные величины (455). 8.2.7. Адрес- ные выражения (456). 8.3. Задание команд............................................ 457 8.4z Задание констант.......................................... 460 8.5. Команды транслятору....................................... 464 8.5.1. Команды организационного характера (465). 8.5.2. Резер- вирование памяти (465). 8.5.3. Объявление эквивалентности (466). 8.5.4. Управление размещением литералов (467). 8.5.5. Дублиро- вание команд (468). 8.5.6. Управление счетчиком размещения (469). 8.5.7. Определение межмодульных связей (471). 8.5.8. Управ- ление листингом (473). 8.6. Условное ассемблирование.................................. 474 8.7. Макросредства............:................................ 478 . 8.7.1. Макроопределения (479). 8.7.2. Макрокоманды (480). 8.7.3. Способы задания параметров (480). 8.7.4. Макроопределения и условное ассемблирование (482). 8.7.5. Локализация объектов макроопределений (485). 8.7.6. Макрооперации в макроопределе- ниях (48/). 8.7.7. Системные макроопределения (488). 8.7.8. Мак- . ро в языках высокого уровня (489). 8.8. Ассемблер............................................... 492 8.8.1. Первая фаза работы ассемблера (495). 8.8.2. Вторая фаза работы ассемблера (497). Глава 9. Мультипрограммный режим работы ЭВМ....................... 502 9.1. Причины возникновения..................................... 502 9.1.1. Быстродействие и производительность ЭВМ (502). 9.1.2. . Виды внешней памяти (503). 9.1.3. Внешняя память и языки прог- раммирования (508). 9.1.4. Эффективность использования обору- дования (510). 9.2. Совмещение работы устройств ЭВМ. Мультипрограммный режим 511 9.3. Требования к аппаратуре................................... 516 9.3.1. Прерывания (516). 9.3.2. Привилегированный режим (520). ’ 9.3.3. Защита памяти (521). 9.3.4. Перемещение (релокация) про- грамм (524). 9.3.5. Машинные часы (528). 9.4, Появление операционных систем............................. 528 Глава 10. Операционные системы.................................... 531 10.1. Работа с данными.......................................... 531 10.1.1. Виртуальная оперативная память (531). 10.1,2. Виртуаль- ные устройства ввода/вывода (532). 10.1.3. Виртуальная внеш- няя память (534). 10.1.4. Архивная служба (539). 10.2. Управление процессами..................................... 540 10.3. Управление взаимодействием задач.......................... 548 10.3.1. Множество задач (549). 10.3.2. Последовательное взаимо- действие (550). 10.3.3. Параллельное взаимодействие (564). 10.4. Обеспечение диалога с ЭВМ................................. 568 10.4. L Две формы общения человека с машиной (568). 10.4.2. Терминалы (571). 10.4.3. Простой пример диалога с ЭВМ (573). 10.4.4. Основные виды услуг при диалоговой форме общения (578). 10.4.5. Развернутый пример диалога с ЭВМ (583). 10.5. Организационные функции................................... 593 " 10.5.1. Режимы использования ЭВМ (593). 10.5.2. Административ- ные функции (598). 10.5.3. Обеспечёние Надежности функциони- рования вычислительной системы (598). Предметный указатель ........................................... 603
ПРЕДИСЛОВИЕ В основу настоящего пособия положен курс лекций, который его авторы читают на факультете Вычислительной математики и кибер- нетики МГУ им. М. В. Ломоносова на протяжёнии последних че- тырех лет. После того, как была утверждена новая программа курса «ЭВМ и программирование», рассчитанная на два учебных года и пре- дусматривающая совместное изучение вычислительных машин, программирования и математического обеспечения, стала особенно очевидной потребность в создании единого учебного пособия, со- держащего связное изложение всех этих вопросов с достаточно сов- ременной точки зрения. Такое пособие тем более нужно, что, ввиду большого значения, придаваемого нашим государством развитию и применению ЭВМ, за последнее время резко увеличилось количе- ство вузов, готовящих специалистов по вычислительной технике и прикладной математике во всех концах страны. Поставленная зада- ча обусловила некоторые характерные особенности в отборе и из- ложении материала. Прежде всего, в качестве первичного понятия выбрана не вы- числительная машина, а программирование как процесс разработки алгоритма. В соответствии с этим сначала излагаются абстрактные алгоритмические языки (машины Тьюринга и нормальные алгорит- мы), затем язык алгол и наконец языки вычислительных машин. При этом подчеркивается, что разработка программ на различных языках проводится в принципе одинаково, хотя она, безусловно, су- щественно зависит и от специфики языков. Основные приемы и методы разработки программ объясняются по ходу изучения * языка алгол. Затем алгол становится инстру- ментом для изложения остального материала: на нем записывается большинство алгоритмов (как вычислительных, так и невычисли- тельных), с его помощью описываются устройство и работа различ- ных типов вычислительных машин и выполнение различных ма- шинных операций. При этом знание алгола для изучения ЭВМ оказалось значительно полезнее, чем оказывалось знание ЭВМ для изучения алгола. В порядке иллюстрации различных методов раз- работки программ составляются алгол-программы, описывающие ра- боту универсальной машины Тьюринга и универсального нормаль- ного алгоритма. Конечно, использование алгола в качестве инстру-
8 ПРЕДИСЛОВИЕ ментального языка вызвало ряд вполне понятных трудностей. Од- нако для авторов решающим было то обстоятельство, что алгол — это широко распространенный язык, который хорошо знают все преподаватели прикладной математики. Примерно по таким же соображениям авторами был сделан толь- ко первый небольшой шаг в направлении структурного программи- рования. Главное внимание при изучении математического обеспечения уделено возможностям, которые оно предоставляет программисту. Некоторые сведения о структуре математического обеспечения и алгоритмы работы отдельных его компонентов приводятся только для лучшего понимания его функций или как иллюстрация к материалу предшествующей части курса. В основу описания системы программирования положено поня- тие модуля. Таким образом, система программирования рассматрива- ется как комплекс программ, под управлением которого ЭВМ ис- пользуется для изготовления, хранения и редактирования модулей, для их объединения в рабочие программы и для отладки. Для описа- ния операционной системы используется понятие виртуальной ма- шины, представляющей собой объединение физической вычислитель- ной машины и программ операционной системы. Возможности, пре- доставляемые программисту операционной системой, описываются через свойства такой виртуальной машины. Вполне понятно, что пособие по базовому курсу программиро- вания не могло быть привязано к какой-либо одной вычислитель- ной машине, системе программирования или операционной систе- ме — оно должно было отражать типичные или перспективные черты различных машин и систем. Авторы выражают глубокую благодарность декану факультета ВМиК МГУ академику А. Н. Тихонову, который был инициатором создания такого пособия и оказывал авторам поддержку на разных этапах, его разработки; коллективу преподавателей программиро- вания факультета ВМиК МГУ, чей творческий труд способствовал становлению и совершенствованию курса, положенного в основу этой книги; чл-корр. АН УССР профессору Е. Л. Ющенко, профессору А. Н. Костовскому, доценту А. И. Кардашу и доценту И. В. Людкевичу, которые ознакомились с рукописью и сделали ряд ценных замечаний. Главы 1—3 подготовлены Э. 3. Любимским и В. В. Мартынюком, главы 4—10 — Э. 3. Любимским и Н. П. Трифоновым. Э. 3. Любимский, В. В. Мартынюк, И. П. Трифонов
ВВЕДЕНИЕ Что такое программирование. Предмет нашего изучения — про- граммирование для электронных вычислительных машин, или ЭВМ, как их принято называть сокращенно. ЭВМ — это устройства для решения задач. Не обязательно задач по алгебре или геометрии, к которым мы привыкли в школе. И даже вообще не обязательно задач чисто математического характера. Это могут быть и шахмат- ные задачи, и задачи управления станками или ракетами, и задачи планирования производства или информационно-справочного об- служивания. Чтобы решить какую-либо задачу на некоторой ЭВМ, необходимо сначала придумать, как вообще можно решить эту задачу, т. е. придумать алгоритм ее решения. Затем следует представить этот алгоритм в таком виде, чтобы данная ЭВМ могла его выполнить. Для этого нужно, во-первых, разбить алгоритм на элементарные операции, которые умеет выполнять эта ЭВМ, и, во-вторых, записать каждую такую операцию на языке, понятном ЭВМ. Такая запись алгоритма на языке некоторой ЭВМ называется программой для этой ЭВМ. Описанный выше процесс разработки программы представляет собой процесс программирования, а чело- век, его выполняющий, называется программистом. Почему программирование — это научная дисциплина. Если бы процессы программирования решения разных задач на разных ЭВМ не имели между собой ничего общего, то программирование, как таковое, не было бы научной дисциплиной, и наш учебник можно было бы на этом закончить. Однако, дело обстоит совсем не так. Существуют общие методы, которые” позволяют, постепенно расчленяя задачи на подзадачи, сводить их решение, в конечном сче- те, к некоторым типовым фрагментам алгоритмов, подобно тому как, разбирая совершенно непохожие сложные механизмы, мы обнаруживаем, что они состоят из одинаковых деталей и узлов, только по-разному соединенных. Из этого, конечно, не следует, что процесс программирования не содержит в себе элементов твор- чества. Составление программы — это такой же творческий процесс, как и конструирование механизма с заданными свойствами из за- данного наббра деталей. Что же касается ЭВМ, то, при всем их разнообразии, с точки Зрения разработки алгоритмов решения задач все они очень похожи
10 ВВЕДЕНИЕ друг на друга. Каждая из них имеет процессор и память. Все про- цессоры выполняют примерно одинаковые наборы операций, а память любой ЭВМ состоит из перенумерованных ячеек, способных хранить числа или слова. Таким образом, тип выбранной ЭВМ в принципе сказывается только на последней стадии разработки про- граммы. Более того, те же самые методы программирования приме- няются и в том случае, когда предполагаемый исполнитель алгорит- ма вообще не есть ЭВМ и даже совсем на нее не похож. Мы убедимся в этом, когда рассмотрим два абстрактных исполнителя алгоритмов и напишем для них несколько программ. Общность методов разработки программ для самых разных ис- полнителей не случайна. Она вытекает из того фундаментального свойства, что все эти. исполнители практически сводимы друг к другу. Иными словами, если есть два исполнителя И1 и И2, то можно, например, составить для И1 алгоритм, который будет моде- лировать И2, т. е. выполнять на И1 программы, составленные для И2. И наоборот. Правда, при этом теоретически возникает одно ограничение. Если И1 — физический, реально существующий ис- полнитель (например, какая-нибудь ЭВМ), а И2 — абстрактный исполнитель, то можно представить себе программу для И2, при выполнении которой исполнителю И1 не хватит памяти для хране- ния промежуточных данных. Ведь абстрактный исполнитель мы мо- жем «снабдить» неограниченной памятью (как «снабжаем» прямую неограниченной длиной), а физический имеет столько ячеек, сколько их на самом деле изготовили на заводе. Однако, если какую-либо задачу можно решить при помощи исполнителя И1, то это можно сделать двумя способами: или разработать соответствующую про- грамму непосредственно для И1, или разработать программу для И2 и выполнить ее на И1 при помощи моделирующего алгоритма. А сле- довательно, методы, пригодные для решения задачи на И2, в прин- ципе пригодны и для ее решения на И1. Какое значение имеет исполнитель алгоритма. Предположим теперь, что И1 и И2 оба есть физические исполнители (например, разные ЭВМ). Если мы разрабатываем программу на языке испол- нителя И2, то какая нам разница, будет она выполняться непосред- ственно на И2 или на И1 под управлением моделирующего алгорит- ма? В принципе — никакой разницы нет. Чтобы это подчеркнуть, говорят, что И2 виртуально реализован на И1, или что на базе фи- вичёского исполнителя И1 реализован виртуальный исполнитель И2. ; Однако с практической точки зрения нам совсем не безразлично, где будет выполняться наша программа. Ведь физический исполни- тель И2 й виртуальный исполнитель И2 могут иметь совершенно разные эксплуатационные характеристики. Например, они могут иметь разные соотношения скоростей выполнения различных опе- раций. А поскольку мы всегда заинтересованы, чтобы задачи ре- шались быстрее, нужно постараться на каждом исполнителе пре-
ВВЕДЕНИЕ 11 имущественно использовать те операции, которые он выполняет с наибольшей скоростью. Таким образом, может оказаться, что для эффективного решения одной и той же задачи на физическом и на виртуальном исполнителях И2 потребуется составить разные про- граммы. И вообще, наше утверждение о существовании общих методов разработки программ вовсе не означает, что разработка программы должна проводиться независимо от ее будущего исполнителя. Мето- ды разработки программ — общие, а сами программы могут корен- ным образом отличаться друг от друга, даже если они предназначены для решения одной и той же задачи, но на разных исполнителях. И если мы хотим составлять эффективные программы, а исполнители существенно различаются, то и программы обязательно будут от- личаться друг от друга. Таким образом, нас должны интересовать не только общие ме- тоды разработки программ, но и свойства ЭВМ как исполнителей алгоритмов и то, как нужно учитывать эти свойства, чтобы сос- тавлять эффективные программы. О программном обеспечении. Одна из важнейших особенностей ЭВМ состоит в том, что они способны воспринимать и накапливать знания. В самом деле, написать для некоторой ЭВМ, например, программу решения системы линейных уравнений — это значит научить эту ЭВМ решать системы линейных уравнений. Теперь можно сколько угодно раз предлагать этой ЭВМ системы линейных уравнений, и она будет их решать. Истинное богатство современной ЭВМ — это не столько аппаратура, из которой она состоит, сколько совокупность заложенных в нее знаний, т. е. написанных для нее программ. Все программы можно, хотя и несколько условно, разделить на четыре группы. К первой группе относятся программы для решения отдельных, самостоятельных задач. Эти программы выполняются независимо друг от друга и представляют собой набор разрозненных, не свя- занных между собой знаний. Из этого, конечно, не следует, что отдельные конкретные задачи, которые может решать ЭВМ при по- мощи таких программ, не могут быть весьма сложными или,очень нужными задачами. Ко второй группе относятся системы программ для решения классов задач из различных специализированных областей науки, техники, промышленности и т. д. Часто такие системы называют пакетами прикладных программ. Пакеты представляют собой уже систематизированные знания в узких предметных областях. Про4 граммы, входящие в пакет, выполняются не отдельно друг от друга, а совместно, в различных комбинациях, в зависимости от конкрет- ной решаемой задачи. Разрабатывать программы, составляющие па- кет, т. е. передавать ЭВМ систематизированные знания, труднее,
12 ВВЕДЕНИЕ чем разрабатывать отдельные, независимые программы. Но и поль- зы от таких программ гораздо больше — число задач, которые можно решить при помощи пакета, как правило, намного превосхо- дит число программ, входящих в этот пакет. Третья группа — это система программ, предназначенная для автоматизации самого процесса разработки программ. Эта система представляет собой квалификацию ЭВМ в области программирова- ния и называется системой программирования. По существу, она тоже есть пакет, предметная область которого — разработка про- грамм. Все программы первой и второй групп разрабатываются с помощью программ третьей группы, и в этом смысле система про- граммирования является системой общего пользования. Употреб- ляя промышленную терминологию, можно сказать, что назначение системы программирования состоит в производстве средств произ- водства. Современная ЭВМ — это комплекс, состоящий из большого числа различных взаимодействующих устройств, и управление им представляет собой весьма сложную задачу. Решением этой задачи занимается специальная система программ, называемая операцион- ной системой. Программы, входящие в эту систему, и образуют чет- вертую группу программ. Помимо управления работой ЭВМ, one- ' рационная система выполняет и ряд других функций. Из них наи- более для нас интересная — это пополнение набора операций, которые умеет выполнять ЭВМ, новыми операциями, весьма удобны- ми для программистов, но слишком сложными для непосредственной реализации на ЭВМ. Таким образом, операционная система вместе с ЭВМ образуют новую, виртуальную машину, существенно более удобную для программирования. Все программы первой, второй и третьей групп пишутся уже для этой виртуальной машины (оче- видно, что программы самой операционной системы должны писать- ся непосредственно для ЭВМ). Программы первых двух групп образуют специализированное, программное обеспечение ЭВМ, а последних двух — ее стандарт- ное программное обеспечение. Поскольку первые ЭВМ применялись Почти исключительно для математических расчетов, за программ- ным обеспечением закрепилось также название математическое обеспечение. Для того чтобы грамотно использовать современную ЭВМ, про- граммист должен хорошо знать структуру ее стандартного програм- много обеспечения и возможности, которые оно предоставляет для разработки программ.
Глава 1 ЭЛЕМЕНТЫ ТЕОРИИ АЛГОРИТМОВ 1.1. Интуитивное понятие алгоритма и необходимость его уточнения Работа вычислительной машины состоит в выполнении алгорит- ма, поэтому общие возможности применения вычислительных ма- шин определяются тем, что можно и что нельзя представить в виде алгоритма. Понятие алгоритма, являющееся одним из основных понятий математики, возникло задолго до появления вычислительных машин. На протяжении многих веков люди пользовались интуитивным поня- тием алгоритма, которое можно выразить так: Алгоритм — это строгая и четкая конечная система правил, которая определяет последовательность действий над некоторыми объектами и после конечного числа шагов приводит к достижению поставленной цели. В частности, система правил является алгоритмом, если ее можно вручить в качестве инструкции разным людям, не знакомым с сутью дела, и они, следуя этой системе правил, будут действовать одина- ково. Например, можно предложить такой простейший алгоритм подсчета людей в зрительном зале: пройди по всем рядам и для каж^ дого присутствующего человека прибавляй единицу к общему счет- чику, в котором сначала был нуль. Объекты этого алгоритма — люди в зале и числа. Над людьми выполняется действие «найти следующего», а над числами — дей- ствие «прибавить единицу». Древнегреческий математик Евклид предложил алгоритм вычис- ления общего наибольшего делителя двух натуральных чисел А и В. Суть этого алгоритма в том, чтобы вычитать из большего числа меньшее, занося результат на место большего, и действовать так до тех пор, пока числа не станут равны между собой. Эти равные числа и будут общим наибольшим делителем исходных двух чисел. В алгоритме Евклида используется тот факт, что общий наибольший делитель чисел А и В является также общим наибольшим делителем их разности и любого из чисел А, В. Поэтому можно в паре чисел. (А, В) заменить большее число на эту разность, а затем искать об- щий наибольший делитель для новой пары, в которой одно число уменьшилось. Мы описали идею алгоритма Евклида, но этому описанию не- хватает четкости, и поэтому его нужно уточнить. Настоящий алго- ритм Евклида выглядит так:
14 ЭЛЕМЕНТЫ ТЕОРИИ АЛГОРИТМОВ [Гл. I Г. Рассмотреть А как первое число и В как второе число. Перей- ти к п. 2°. 2°. Сравнить первое и второе числа. Если они равны, то перейти к п. 5°. Если нет, то перейти к п. 3°. 3°. Если первое число меньше второго, то переставить их. Пе- рейти к п. 4°. 4°. Вычесть из первого числа второе и рассмотреть полученную разность как новое первое число. Перейти к п. 2°. 5°. Рассмотреть первое число как результат. СТОП. _ Этот набор правил является алгоритмом, потому что, следуя ему, любой человек, умеющий вычитать, может получить общий наи- больший делитель для любой пары чисел. Математики долго пользовались такими описаниями алгоритмов и довольствовались интуитивным понятием алгоритма. В этих рам- ках формулировались и успешно применялись на практике различ- ные вычислительные алгоритмы. Например, были получены алго- ритмы поиска корней квадратных и кубических уравнений. Посте- пенно математики увлекались все более трудными задачами. Напри- мер, стали искать алгоритм вычисления корней алгебраических уравнений любых степеней. Г. Лейбниц в семнадцатом веке даже пытался найти общий алго- ритм решения любых математических задач. Уже в нашем веке этот грандиозный замысел приобрел более конкретную форму: найти ал- горитм проверки правильности любой теоремы при любой системе аксиом, т. е. алгоритм, который отвечал бы на вопрос, верна ли тео- рема, и в случае положительного ответа давал бы вывод ее доказа- тельства. Построит^.такие алгоритмы не удавалось, и постепенно возникло мнение, что это вообще невозможно, т. е. что рассматри- ваемые задачи алгоритмически неразрешимы. Но нельзя было дока- зывать невозможность алгоритмического решения задач, пока не было строго определено понятие алгоритма. Поэтому возникла на- сущная проблема: построить формальное определение алгоритма, аналогичное известному интуитивному понятию. Одна из причин расплывчатости интуитивного понятия алгорит- ма состоит в том, что объектом алгоритма может оказаться все, что угодно. Поэтому естественно было начать с формализации понятия объекта. В вычислительных алгоритмах объектами работы являются чис- ла. В алгоритме шахматной игры объекты — это фигуры и позиции, и нужно выбрать очередной ход. При алгоритмизации производст- венных процессов объектами служат показания приборов и управ- ляющие клавиши, и нужно найти такое нажатие клавишей, при ко- тором процесс пошел бы лучшим образом. Это только несколько при- меров разнообразия алгоритмов. Однако во всех случаях можно счи- тать, что алгоритм имеет дело не с объектами реального мира, а а изображениями этих объектов. •
t.l] ИНТУИТИВНОЕ ПОНЯТИЕ АЛГОРИТМА 13 Например, когда алгоритм сложения работает над числовыми объектами 26 и 57, он вырабатывает числовой результат 83. Но мы можем считать, что объектом алгоритма является изображение, со- стоящее из пяти знаков: 26+57 а результат — это изображение, состоящее из двух знаков: 83 При этом мы исходим из того, что имеется набор из одиннадцати различных знаков: {О, 1, 2, 3, 4, 5, 6, 7, 8, 9, +} Знаки мы называем буквами, а их набор — алфавитом. В общем случае буквами могут служить любые знаки. Требуется только, что- бы они были различимы между собой и чтобы в алфавите не было бесконечного разнообразия различных букв. Итак, буквы — это любые знаки; алфавит — это конечная совокупность различимых букв. Любая конечная последовательность букв из некоторого алфа- вита называется словом в этом алфавите. Например, можно сказать, что алгоритм сложения перерабатывает слово, которое состоит из изображений слагаемых, разделенных знаком +, в слово, изобра- жающее сумму. Заметим, что важен порядок букв в слове, но не имеет значения порядок букв в алфавите. Алфавит {А, Б}ничем не отличается от алфавита {Б, А}, но слова А Б и БА разные. Алфавит русского языка удовлетворяет нашему определению алфавита, а русские слова — нашему определению слов. Однако, любые бессмысленныё комбинации русских букв также являются словами в этом алфавите. Если к русскому алфавиту добавить знак пробела, а также знаки препинания (кроме точки, вопросительного и восклицательного знака), то любые предложения русского языка становятся .словами в этом алфавите. А если добавить и эти знаки препинания и цифры, то любая книга станет словом в нашем алфавите (при условии, что в ней не встречаются иностранные буквы и другие специальные зна- ки). Ведь каждая книга — это последовательность букв, цифр и знаков препинания. Количество букв в слове называется длиной слова. Иногда нужно рассматривать так называемое пустое слово, в котором нет букв; мы будем обозначать его символом А. Итак, объекты реального мира можно изображать словами в раз- личных алфавитах. Эго позволяет считать, что объектами работы алгоритмов могут быть только слова. Получается такое уточненное (но еще не окончательное) определение: Алгоритм есть четкая конечная система правил для преобразова- ния слов из некоторого алфавита в слова из этого же алфавита,-
16 ЭЛЕМЕНТЫ ТЕОРИИ АЛГОРИТМОВ [Гл. I Слово, к которому применяется алгоритм, называется входным, а слово, вырабатываемое в результате применения алгоритма, назы- вается еыходяели. Совсем не обязательно, чтобы удавалось применить алгоритм ко всем словам из алфавита. Некоторые слова нельзя использовать в качестве входных слов алгоритма, и мы говорим, что к этим сло- вам алгоритм не применим. Совокупность слов, к которым алгоритм применим, называется областью применимости этого алгоритма. Мы еще вернемся к понятию области применимости; теперь для нас важно убедиться в том, что мы ничего не потеряли, сузив поня- тие алгоритма и перейдя от любых объектов к словам. К сожале- нию, нельзя доказать, что все возможные объекты удается описать словами, так как само понятие объект^ не было формально (т. е. строго) определено. Но это можно проверить, если брать наугад различные алгоритмы не над словами и каждый раз выражать их так, чтобы объектами становились слова, а суть дела при этом не менялась. Для алгоритма сложения мы уже продемонстрировали, что мож- но работать со словами. Точно так же, можно считать, что алгоритм шахматной игры имеет дело не с реальным расположением фигур на доске, а с записью этого расположения в привычных шахматистам обозначениях, например: Белые: Кре5, Фс12, Ла7, КП. Черные: КрЬЗ, Лс4, КЬ2, КЬ4, пс2. При этом результатом применения шахматного алгоритма будет не физическое перемещение фигуры на доске, а слово, представляющее собой запись выбранного хода: Ф(12—сЗ+. Известно, что можно в таких обозначениях описывать любые шахматные ситуации, а потом по имеющимся записям безошибочно воспроизводить эти ситуации на шахматной доске. Эти й другие многочисленные примеры позволяют считать, что мы ничего не потеряем, выбирая для каждой задачи подходящий алфавит и ограничиваясь рассмотрением алгоритмов, действующих со словами из выбираемых алфавитов. (Если алгоритм работает со словами в русском алфавите, то может оказаться, что входным словом служит исходная постановка задачи на русском языке, а выходное слово представляет собой описание того, как решается эта задача.) Любой алфавит можно заменить другим. Такая замена называется кодировкой. Каждой букве из первого алфавита ставится в соответ- ствие ее код, представляющий собой слово во втором алфавите. На самом деле, всегда достаточно иметь дело с алфавитом из двух букв, потому что любое слово из любого алфавита можно закодировать в алфавите из двух букв. Например, телеграммы на русском языке
1.21 МАШИНЫ ТЬЮРИНГА 17 передаютс'я азбукой Морзе, где алфавит состоит только из знаков «точка» и «тире». Проще всего перейти от произвольного алфавита к алфавиту ив двух букв, закодировав все буквы исходного алфавита различными словами в двухбуквенном алфавите. Вот один из самых простых способов такой кодировки в алфавите {0, 1}: нумеруем все буквы, исходного алфавита и кодируем первую букву словом 101, вторую — словом 1001, третью — словом 10001 и т. д. При этом номер каждой буквы равен числу нулей между единицами в ее коде. Если так закодировать русский алфавит, то слово А Б будет кодироваться словом 1011001; слово ГА будет кодироваться как 100001101, а слово БАВ как 100110110001. Конечно, для кодировки последних букв алфавита потребуется много нулей. Но в принципе: можно закодировать таким образом любое русское слово, а когда понадобится, то восстановить исходное слово, отделяя в имеющейся комбинации нулей и единиц одну за другой группы нулей, заклю- ченные между единицами, и заменяя их на соответствующие буквы русского языка. Поскольку от любого алфавита можно перейти к алфавиту из двух букв с гарантией однозначного обратного восстановления за- кодированных слов, то без потери общности можно-свести любой алгоритм к алгоритму над словами в алфавите из знаков 0 и 1. Для этого достаточно в правилах алгоритма заменить обрабатываемые слова в исходном алфавите на их коды. Перед применением алго- ритма потребуется закодировать входное слово, а после выполнения алгоритма нужно будет раскодировать выходное слово. 1.2. Машины Тьюринга 1.2.1. Определение машины Тьюринга. Теперь, когда мы формально описали объекты применения алго- ритмов как слова в некотором алфавите, осталось формализовать действия над объектами-словами и порядок этих действий. Формальные определения алгоритма появились в тридцатых — сороковых годах нашего века. Одним из первых было определение, английского математика А. Тьюринга, который в 1936 году описал схему некоторой гипотетической (абстрактной) машины и предложил' называть алгоритмами то, что умеет делать такая машина. При этом' определении, если что-то не может быть сделано машиной Тьюрин-. га, это уже не алгоритм. Иначе говоря, Тьюринг формализовал пра- вила выполнения действий при помощи описания работы некоторой конструкции.
ЭЛЕМЕНТЫ ТЕОРИИ АЛГОРИТМОВ [Гл. I Вычислительные машины — это тоже конструкции для выпол- нения алгоритмов, но это реальные устройства, тогда как машина Тьюринга является абстракцией, которая никогда не была реали- зована. (Как мы увидим дальше, ее и нельзя реализовать.) Поэтому алгоритмы Для машины Тьюринга должны выполняться другими средствами. Например, за нее должен работать человек, действуя так как действовала бы машина Тьюринга, если бы она существо- вала. Польза от машины Тьюринга в том, что, говоря об этой во- ображаемой конструкции, мы можем доказывать существование или несуществование алгоритмов решения различных задач. Исходя из этого, Тьюринг искал как можно более простую, «бедную» алгоритмическую схему, лишь бы она была универсальной. Важно чтобы было удобно доказывать, что она не может решить задачу, которая, как нам кажется, вообще неразрешима. Когда же Речь идет о вычислительной машине, то, наоборот, мы больше всего ценим ее удобство, богатство ее возможностей; требуем, чтобы человеку было легко с ней работать. В этом нас сдерживает только цена желаемых удобств и трудность разработки обору- дования. Принципиальное отличие машины Тьюринга от вычислительных машин состоит в том, что ее запоминающее устройство представля- ет собой бесконечную ленту, тогда как у вычислительной машины может быть очень большое, но уж во всяком случае не бесконечное запоминающее устройство. Машину Тьюринга нельзя физически реализовать именно из-за ее бесконечной ленты. В этом смысле она мощнее любой вычислительной машины. Лента машины Тьюринга разделена на ячейки. В каждой ячей- ке может находиться одна из букв какого-нибудь алфавита. Если ячейка пустая, то мы говорим, что в ней находится специальная буква А. Алфавиты могут быть разные, но для каждой конкретной машины Тьюринга выбирается какой-либо один алфавит. Кроме лен- ты, у машины Тьюринга есть автомат, который может двигаться вдоль ленты и по очереди «обозревать» содержимое ячеек. Входное слово размещается на ленте по одной букве в расположенных подряд ячейках и занимает конечное число ячеек. Слева и справа от вход- ного слова на ленте находятся только пустые ячейки. Ниже нари- сована машина Тьюринга, у которой на ленте занято буквами пять ячеек, прячем автомат находится против ячейки, содержащей бук- ву С.’ лента... А А С Л О в 0 л л •.. автомат
1.2] МАШИНЫ ТЬЮРИНГА ф Итак, автомат каждый раз «видит» одну ячейку. Кроме того, он может находиться в одном из нескольких состояний qu q2, . . . .. ,, qk. В зависимости от того, какую букву s( автомат «видит» в- очередной ячейке, а также в зависимости от своего состояния qj, т. е. в зависимости от пары ($г, qj), автомат может выполйять раз* личные действия: запись новой буквы в обозреваемую ячейку, сдвиг по ленте на одну ячейку влево или вправо; переход в новое состояние. Это три вида операций машины Тьюринга, и каждый раз для очередной пары («г, qj) машина Тьюринга может выполнить по од- ной операции каждого вида. Все задание для ее работы можно изо- бразить программой, приведенной в табл. 1.1. В каждой клетке программы нужно указать, какие операции должен выполнить автомат, если, находясь в данном состоянии. Таблица 1.1 Что «видит» автомат Л «1 • • ♦ si ... Состояние автомата <71 Яг 4J (Л) Н (Ят 1/7 J * Як [Л] Запись < Н > означает, что вместо нее может 1/7 J стоять одна из букв Л, Н, П.
20 ЭЛЕМЕНТЫ ТЕОРИИ АЛГОРИТМОВ [Гл. 1 он «видит» данную букву. В общем случае, находясь в состоянии и видя букву sh он записывает в ту же ячейку заданную букву (которая может в частном случае совпасть с буквой sf); затем он сдвигается по ленте на шаг влево, или остается неподвижным, или сдвигается на шаг вправо; чтобы различать эти три возможности, мы записываем в клетку одну из трех букв: Л (означает шаг влево) Н (означает неподвижность) П (означает шаг вправо). После этого автомат переходит в состояние qm (которое может в частном случае совпасть с прежним состоянием </,). Теперь следую- щее действие автомата нужно искать в /n-й строке программы (эта строка соответствует состоянию на пересечении со столбцом, соответствующим той букве, которую автомат «увидит» после сдвига. (Если автомат остается неподвижным, он «увидит» ту самую букву которую только что записал, и в этом случае следующее действие нужно искать на пересечении со столбцом, соответствующим букве $Р) Итак, автомат движется по ленте то вправо, то влево, иногда ос- тается неподвижным и каждый раз принимает три решения: что писать, куда двигаться и в какое состояние переходить. Если сле- дить только за лентой, не обращая внимания на автомат, то мы уви- дим, что каждый раз одна буква заменяется на другую. В частности, какие-то буквы стираются и вместо них остаются пустые ячейки, а в каких-то пустых ячейках появляются новые буквы. Иначе говоря, укорачиваются или удлиняются слова, записанные на ленте. Несмотря на свое простое устройство, машина Тьюринга может выполнять самые сложные преобразования слов. Сначала, когда лента содержит входное слово, автомат находится против какой-то ячейки и в каком-то состоянии. Необходимо договориться, какая это будет ячейка: например, самая левая непустая, третья от конца слова или какая-нибудь еще. В зависимости от выбора начальной ячейки, получатся разные результаты работы машины Тьюринга. Что касается начального состояния, то всегда для удобства можно считать, что это состояние № 1, т. е. qr. Ведь строку программы, соответствующую начальному состоянию, мы всегда можем написать первой. В процессе своей работы машина Тьюринга будет как бы перескакивать из одной клетки программы в другую в соответствии с информацией на ленте и указаниями в клетках программы, пока не дойдет до клетки, в которой будет написано, что автомат должен не менять в очередной ячейке находящуюся там букву, остаться не- подвижным и сохранить свое прежнее состояние. Если, например, это клетка на пересечении строки для q* и столбца для буквы s7, то это значит, что в ней написано s7, Н, q*. Дойдя до такой клетки, машина Тьюринга уже никогда никуда из нее не уйдет и ничего но- вого на ленту не запишет. Считается, что на этом она завершит свою работу, т. е. остановится.
1.21 МАШИНЫ ТЬЮРИНГА 21 Входным является то слово, которое имелось первоначально на ленте (от самой левой непустой ячейки до самой правой). То, что получилось на ленте к моменту останова,— выходное слово. Таких клеток останова в программе может не быть; тогда машина Тьюринга никогда не остановится. Даже, если такие клетки есть, машина может никогда до них не дойти (ведь ее переходы зависят от программы и от входного слова). Если машина Тьюринга никогда не остановится, то считается, что она неприменима к данному входному слову. Она применима к слову только в том случае, если, начав работу над этим входным словом, рано или поздно дойдет до клетки останова. Прослеживая работу машины Тьюринга, мы узнаем, что она применима к данному слову, но если она неприменима, то такое прослеживание нам ничего не докажет, так как в любой: момент мож- но надеяться на то, что она скоро остановится. Итак, неприменимость не может быть выяснена прямым способом; в ней можно убедиться только косвенными рассуждениями. Например, если в имеющейся программе нет клетки останова, то данная машина Тьюринга не применима ни к одному слову. Даже при наличии клеток остано- ва в нижних строках программы, машина не применима ни к одному слову, если первая строка имеет, например, такой вид Л 0 1 <71 Л, П, <7, о, п, 1, п, В этом случае, если автомат в состоянии qx видит пустую ячейку, он оставляет ее пустой и сдвигается вправо, сохраняя состояние qt. Аналогично, видя в обозреваемой ячейке знак 0 или 1, он оставляет в ячейке тот же знак и тоже сдвигается вправо, сохраняя состояние 4i. Итак, что бы автомат ни «увидел» на ленте, он ничего не меняет и сдвигается по ленте вправо, оставаясь всегда в состоянии qu а поскольку лента бесконечна, он никогда не остановится. Может оказаться, что машина Тьюринга применима ко всем словам из своего алфавита. Например, если программа имеет вид Л 0 1 Qi Л, Н, <7, Л, П, Л, П, 9i то автомат, увидев пустую ячейку, оставляет ее пустей и не сдвига- ется, а, увидев знак 0 или 1, стирает этот знак и сдвигается вправо. Таким образом, получается, что автомат будет шаг за шагом стия
22 ЭЛЕМЕНТЫ ТЕОРИИ АЛГОРИТМОВ [Гл. t рать содержимое непустых ячеек и двигаться вправо, пока не дой- дет до пустой ячейки, после чего машина остановится. Эта машина Тьюринга применима ко всем словам, потому что каждое слово име- ет конечную длину, а после него следуют пустые ячейки. Она будет стирать это слово, если условиться, что когда машина начинает ра- ботать, автомат находится против самой левой непустой ячейки. 1.2.2. Примеры машин Тьюринга. Начнем с рассмотрения машины Тьюринга, которая прибавляет единицу к числу на ленте. Входное слово состоит из цифр этого числа, записанных в после- довательные ячейки ленты. В начальный момент автомат находится против самой правой цифры числа. Машина должна прибавить еди- ницу к последней цифре, а если это была цифра 9, то заменить ее на О и аналогично поступить с предыдущей цифрой. Предлагается про- грамма, представленная в табл. 1.2. Таблица 1.2 Состо- яние л 0 1 8 9 Qi 1, Н, qt 1, н, qt 2, Н, q2 9, Н, q2 0, Л, <?! Qi Л, Н, q2 0, Н, q2 1. Н, q2 •. • 8, Н, q2 9, Н, q2 Здесь qi— состояние изменения цифр, a q2— состояние оста- нова. Вся вторая строка схемы заполнена клетками останова. Если в состоянии qt автомат видит цифру 0, или 1.или 8, он заменяет ее на 1 или 2, . . . или 9 соответственно и переходит в состояние q2, т. е. машина останавливается. Если же он видит цифру 9, то заме- няет ее на 0 и сдвигается к предыдущей цифре числа, оставаясь в том же состоянии qlt т. е. продолжая руководствоваться первой строкой программы. Так продолжается до тех пор, пока не встре- тится цифра, которая меньше, чем 9. Если все цифры были девят- ками, то автомат последовательно заменит их нулями; записав же О на место самой старшей цифры, он сдвинется влево, увидит пустую ячейку, запишет туда 1 (как это указано в клетке на пересечении строки (/1 и столбца Л) и перейдет в состояние qa, т. е. остановится. В результате, например, число 999 будет заменено на число 1000. Чтобы короче и нагляднее записывать программы машин Тьюрин- га, договоримся о следующем: 1) Вместо указания перехода в состояние останова, пишем в клетках программы знак !. 2) Опускаем в программах букву Н, т. е. пишем Л или П, или же ничего не пишем.
1.2] МАШИНЫ ТЬЮРИНГА 25' 3) Если согласно данной клетке нужно сохранять букву в ячейке, то не пишем в клетку эту букву. С учетом этих поправок та же программа будет записана так: А 0 1 •. • 8 9 Qi 1, I 1, I 2, I 9, ! 0» Л, Теперь рассмотрим несколько более сложную машину Тьюринга для подсчета на ленте штрихов, которые располагаются подряд и образуют входное слово. Нужно стереть все штрихи и написать на ленту их число, представленное в десятичной системе. Будем формировать это число на ленте слева от штрихов. В на* чальный момент машина Тьюринга обозревает любой из штрихов и находится в состоянии q^ В программировании принято прежде чем приступать к состав- лению алгоритма, фиксировать его план в виде схемы, состоящей из пунктов, или в виде графической блок-схемы. (Подробно об этом говорится в главе 2.) Для рассматриваемой задачи схема программы может выглядеть так: Г. Найти правый конец слова на ленте. 2° . Если слово оканчивается штрихом, то стереть этот штрих, иначе остановить машину. 3° . Прибавить к числу единицу и перейти к п. 1°. Каждый раз стирается самый правый штрих и к числу прибавля- ется единица. Выполнение этих трех пунктов повторяется до тех пор, пока не будет стерт последний штрих, после чего, согласно условию из п. 2°, машина Тьюринга остановится. Каждый из этих пунктов может быть реализован одним состоя- нием машины Тьюринга. Итак, нам понадобятся три состояния ма- шины Тьюринга. В состоянии <71 автомат будет искать правый ко- нец слова; qa будет состоянием стирания штрихов; q, будет состоя- нием прибавления к числу единицы. В табл. 1.3 приводится программа предлагаемой машины Тью- ринга. Машина «видит» на ленте цифры, которые она писала сама, и штрихи, находящиеся там с самого начала. В состоянии признаком Достижения правого конца слова служит пустая обозреваемая ячей- ка; при этом автомат сдвигается по ленте на шаг влево (т. е.начинает обозревать самый правый непустой символ) и переходит в состоя- ние qa. Находясь в состоянии qa и увидев штрих, автомат стирает «го, сдвигается натпаг влево и переходит в состояние qa прибавления единицы. Если же автомат в состоянии qa видит цифру, то машина
24 ЭЛЕМЕНТЫ ТЕОРИИ АЛГОРИТМОВ [Гл. I Таблица 1.Э Л 0 1 2 ... 8 9 / <71 Л, q3 п, qi п, qi п, <71 п, qi п, п, <71 <72 ! ! ! ! 1 ! Л, л, q3 <7з 1.П, <z, 1. П, qt 2, П, 3, П, <7! 9, П, <7, 0, Л, q3 /> Л, q3 останавливается, так как это означает, что все штрихи уже стерты. В состоянии q3 автомат двигается по ленте влево, минуя оставшиеся штрихи, пока не дойдет до числа, и прибавляет к числу единицу аналогично тому, как это делалось в предыдущем примере. В начальный момент автомат в состоянии может находиться против любого штриха из входного слова. Например, пусть входное слово состоит из трех штрихов и автомат первоначально находится против среднего штриха: Л / / / Л t <7i Начав работать, автомат сдвинется два раза вправо в состоянии q19 после чего возникнет ситуация: Л / / / Л t <71 В этот момент автомат сдвигается влево и переходит в состояние Л / / / Л t <h Затем обозреваемый штрих стирается, автомат сдвигается влево и переходит в состояние q9: Л / / Л Л t <7з Затем он движется влево, оставаясь в состоянии q39 пока не уви- дит пустую ячейку, после чего записывает туда цифру 1, сдвигается вправо и переходит в состояние ft:
j' 2] МАШИНЫ ТЬЮРИНГА 25 1 / / Л Л t <?1 Далее в состоянии ft автомат движется вправо до первой пустой ячейки, увидев которую, сдвигается влево и переходит в состояние ft: 1 / / Л Л t Очередной штрих стирается, автомат сдвигается влево и пере- ходит в состояние q3. 1 / Л Л Л t Яз Еще один сдвиг влево в состоянии qs, и автомат заменяет цифру 1 на 2, сдвигается вправо и переходит в состояние qi. 2 / Л Л Л t Я1 Снова сдвиг вправо, и следующая смена состояния со сдвигом влево: 2 / Л Л Л t Й Стирание штриха (последнего), сдвиг влево и переход в состоя- ние q3: 2 АЛЛА t Яз После этого цифра 2 заменяется на 3, и автомат сдвигается впра- во, переходя в состояние qi'. 3 Л Л Л Л t Я1 Далее следует сдвиг влево с переходом в состояние q2, и машина останавливается, оставив на ленте выходное слово. 1.2.3. Возможности машин Тьюринга. Основная гипотеза тео- рии алгоритмов. Богатство возможностей конструкции Тьюринга проявляется в том, что если какие-то алгоритмы А и В реализуются машинами Тьюринга, то можно строить программы машин Тьюринга, реализу- ющие различные композиции алгоритмов А и В, например, «выпол-
26 ЭЛЕМЕНТЫ ТЕОРИИ АЛГОРИТМОВ [Гл. t нить А, затем выполнить В» или «Выполнить А. Если в результате получилось слово да, то выполнить В. В противном случае не выполнять В» или «Выполнять поочередно А, В, пока В не даст ответ О». В интуитивном смысле такие композиции являются.алгоритмами. Поэтому их реализация посредством машины Тьюринга служит од- ним из способов обоснования универсальности конструкции Тью- ринга. Реализуемость таких композиций доказывается в общем виде, независимо от особенностей конкретных алгоритмов А и В. Дока- зательство состоит в том, что указывается способ построения из программ А и В программы нужной композиции. Пусть, например, нужно построить машину А-В, эквивалентную последовательному выполнёнию алгоритмовЛ и В. Машина А имеет т состояний ..., qm\ машина В имеет k состояний ft....qk. Переименовываем сос- тояния машины В, заменяя qx на qm+i, Яг на qm+2, . . ., qk на qm+k. Соответственно заменяем и все ссылки на состояния в клетках про- граммы В. В программе А всюду знак 1 заменяем на указание состоя- ния qm+t. Записываем полученную программу А, а под ней програм- му В с переименованными состояниями. Вместе они образуют ис- комую программу А’В. Пока выполняется алгоритм А, в программе А-В работает часть А без учета части В. Когда алгоритм А дойдет до конца, то вместо останова произойдет переход в первое состояние части В, и затем часть В будет работать обычным образом, как будто части Л и не было. Например, если Л — алгоритм подсчета штрихов на ленте, а В — алгоритм прибавления единицы к числу на ленте, то мы можем воспользоваться уже рассмотренными программами машин Тью- Еинга, реализующими эти алгоритмы. В данном случае т=3 и k=\. [3 программы Л получаем первые три строки программы Л-В (табл. 1.4). Таблица 1.4 Л 0 1 2 ... 8 9 / Я1 Л, q2 п, 91 п, 9г п, 91 п, 91 П.91 п, 91 Яг 1 4 Я* я^ <74 <?4 <74 А, Л, q3 Яг 1.П.С 1. п, 91 2, П, 9, з, п, 91 9, П, 9, 0, Л, q2 /» Л, <73 Я* 1» 1 1, 1 2, 1 3, I 9, 1 0, Л, q^
12] МАШИНЫ ТЫрРИНГА 27 Последняя, четвертая строка программы Л-В получается из программы В. Полученная программа А-В описывает машину Тьюринга, ко- торая сначала подсчитывает число штрихов на ленте, стирая их, а затем прибавляет к этому числу единицу. Заметим, что в програм- ме Л-В осталась незаполненной клетка на пересечении строки и столбца /, но эта клетка никогда не будет использоваться, так как алгоритм В не имеет дела со знаком /. Аналогично конструируются и другие композиции машин Тью- ринга; каждый раз строятся общие правила: что на что менять в исходных программах. Описывая различные алгоритмы для машин Тьюринга и дока- зывая реализуемость всевозможных композиций алгоритмов, Тью- ринг убедительно показал разнообразие возможностей предложен- ной им конструкции, что позволило ему выступить со следующим тезисом: Всякий алгоритм может быть реализован соответствующей ма- шиной Т ьюринга. Это основная гипотеза теории алгоритмов в форме Тьюринга (здесь не оговаривается «всякий алгоритм преобразования слов», . так как мы уже ранее договорились, что действие любого алгорит- ма сводится к преобразованию слов). Одновременно этот тезис является формальным определением алгоритма. Теперь можно дока- зывать существование или несуществование алгоритмов, описывая соответствующие машины Тьюринга или доказывая невозможность их построения. Этим для нас открывается общий подход к поиску алгоритмических решений. Если поиск решения наталкивается на препятствие, то мы пытаемся использовать это препятствие для до- казательства невозможности решения, опираясь на основную ги- потезу теории алгоритмов. Если же при доказательстве невозмож- ности возникает свое препятствие, то оно может помочь нам продви- нуться в поиске решения, хотя бы частично устранив прежнее пре- пятствие. Так, поочередно пытаясь доказать то существование, то несуществование решения, мы можем постепенно приблизиться к пониманию существа стоящей перед нами задачи. Доказать тезис Тьюринга нельзя, так как в его формулировке не определено понятие «всякий алгоритм», т. е. левая часть тождества. Его можно только обосновать, представляя различные известные ал- горитмы в виде машин Тьюринга. Дополнительное обоснование этого тезиса состоит в том, что позднее было предложено еще не- сколько общих определений понятия алгоритма и каждый раз уда- валось доказать, что, хотя' новые алгоритмические схемы и выгля- дят иначе, они в действительности эквивалентны машинам Тью- ринга: все, что реализуемо в одной из этих конструкций, можно сделать и в других. Эти утверледения доказываются строго, так как в них речь идет уже о тождественности формальных схем. Даже ког-
28 ЭЛЕМЕНТЫ ТЕОРИИ АЛГОРИТМОВ [Гл. t да предпринимались специальные попытки выйти за рамки машин Тьюринга, строя алгоритмические схемы, казалось бы, противоре- чащие понятию машины Тьюринга, в конечном итоге все же оказы- валось, что эти схемы сводятся к машинам Тьюринга. До сих пор мы имели дело со специализированными машинами Тьюринга, предназначенными для решения конкретных задач. Од- нако, рассмотренный нами общий способ интерпретации работы машин Тьюринга сам является алгоритмом, а стало быть и ему долж- на соответствовать некоторая машина Тьюринга, в которой входное слово состоит из изображения программы и входного слова интер- претируемой машины. Такая машина называется универсальной^ так как она способна выполнять задания для любой машины Тью- ринга. Мы не будем описывать здесь программу универсальной ма- шины Тьюринга, а рассмотрим только, как можно представлять ин- формацию на ее ленте. Чтобы получить изображение программы интерпретируемой машины, нужно закодировать эту программу в алфавите универсаль- ной машины. Программа кодируется и записывается на ленту уни- версальной машины последовательно, строка за строкой. Алфавит универсальной машины Тьюринга содержит буквы, используемые при записи программ, включая и алфавит интерпре- тируемой машины. Кроме того, в него входят знаки препинания запятая (,) двоето- чие (:) и точка(.) для разделения частей линейного изображения программы; знак * отделяет изображение программы от входного слова для интерпретируемой машины, а знак | служит для указа- ния положения автомата интерпретируемой машины. Например, рассмотрим, как интерпретируется на универсаль- ной машине Тьюринга применение к числу 199 рассмотренной нами машины для прибавления единицы. Входное слово на ленте универ- сальной машины будет выглядеть так: Л', 0,1, - ..,8, 9. Ч1. 1, !. 1, !. 2, 1. .... 9, !. 0, Л, ^*1991 Здесь левее знака * изображена программа машины Тьюринга для прибавления единицы, причем сначала перечислены буквы алфавита этой машины. (Мы заменили Л на Л', чтобы этот знак пу- стой ячейки на ленте интерпретируемой машины не путать с обоз- начением пустой ячейки на ленте универсальной машины.) Предполагается, что автомат универсальной машины предвари- тельно установлен против самой левой буквы ее входного слова (в нашем случае против знака Л'). Мы не указываем, с какого состоя- ния начинается работа интерпретируемой машины, так как ранее договорились, что это всегда состояние На самом деле нет необходимости в том, чтобы алфавит уни- версальной машины Тьюринга включал все знаки алфавита интер- претируемой машины; вместо этого знаки интерпретируемого алфа-
I.3J НОРМАЛЬНЫЕ АЛГОРИТМЫ МАРКОВА 2» вита можно кодировать небольшим числом знаков алфавита универ- сальной машины. (Как мы уже знаем, для такой кодировки в прин- ципе достаточно двух знаков.) После завершения работы универсальной машины на ее ленте должно остаться то слово, которое получилось бы в результате работы интерпретируемой машины; в нашем случае это слово 200 Если интерпретируемая машина не применима к какому-то сло- ву, то универсальная машина тоже должна быть неприменимой, т. е. должна работать над кодировкой этого слова бесконечно долго. Такая интерпретация различных алгоритмов (в данном случае различных машин Тьюринга) посредством одного специально для этого предназначенного алгоритма (в данном случае посредством универсальной машины Тьюринга) называется в программирова- нии прокруткой. 1.3. Нормальные алгоритмы Маркова В 1954 г. советский математик А. А. Марков предложил алго- ритмическую схему, в которой, как и в машине Тьюринга, преобра- зуются слова, но на основе других принципов. В алгоритмической схеме Маркова нет понятия ленты и подразумевается непосредст- венный доступ к различным частям преобразуемого слова. А. А. Мар- ков назвал эту алгоритмическую схему нормальным алгоритмом. Обозначая большими буквами слова в некотором алфавите, можно записать нормальный алгоритм в таком виде: A/t -®n Запись {} означает, что вместо нее должна стоять одна из стрелок -> или t—>. Таким образом, нормальный алгоритм представляет собой упо- рядоченный набор пар слов, соединенных между собой стрелками Двух видов: -> и ь-». Каждая пара представляет собой формулу подстановки для за- мены подслов в преобразуемом слове. Выполнение нормального алгоритма распадается на такты. Каждый такт включает в себя поиск первой по порядку применимой Формулы подстановки и применение этой формулы. Первый такт на- чинается с проверки, является ли слово Ai частью входного слова. Иначе говоря, ищется вхождение слова в исходное слово. На- пример, в слове МАКАР есть вхождение слова МА (но нет вхожде-
30 ' ЭЛЕМЕНТЫ ТЕОРИИ АЛГОРИТМОВ [Гл. 1 ния слова МК9 так как буквы вхождения должны располагаться подряд). Если вхождение имеется, то оно заменяется на правую часть пары, т. е. на слово Таким образом, производится изме- нение входного слова путем подстановки вместо одного подслова другого подслова. На следующем такте снова ищется вхождение левой части первой пары уже в измененное слово. Если нет вхожде- ния слова Лх, то первый такт продолжается аналогичным рассмо- трением второй пары и т. д. Если при попытке применить формулу подстановки оказывается, что имеется несколько вхождений ее левой части, то всегда заменяется первое (самое левое) вхождение. Если удалось применить какую-то формулу подстановки, найдя в преобразуемом слове левую часть формулы и заменив ее на пра- вую часть, то всегда на следующем такте возвращаются к самому на- чалу нормального алгоритма и снова ищут вхождение левой части первой формулы в измененное слово. Если же какую-то формулу не удалось применить, то происходит попытка применить следующую за ней формулу. Процесс выполнения нормального алгоритма за- канчивается в одном из двух случаев: либо все формулы оказались неприменимыми, т. е. в обрабаты- ваемом слове нет вхождений ни одной левой части какой-либо фор- мулы подстановки; либо только что применилась завершающая формула, в которой левую и правую части разделяет знак н-». В любом из этих случаев считается, что нормальный алгоритм применим к данному входному слову. Если же в процессе выполне- ния нормального алгоритма бесконечное число раз применяются не завершающие формулы, то алгоритм неприменим к данному вход- ному слову. В левых и правых частях формул подстановок могут содержаться пустые слова. Для записи нормальных алгоритмов не требуется специального обозначения пустого слова, так как в этой конст- рукции нет ячеек, не зафиксирован носитель информации, а преоб- разуемое слово свободно раздвигается и сужается. Переход от иных способов описания алгоритмов к эквивалент- ным нормальным алгоритмам называется представлением в нормаль- ной форме, или нормализацией. В качестве примера мы опишем нормальную форму алгоритма кодировки знаками 0 и 1 слов из алфавита {а, Ь, с}: а->101 ft->1001 с-> 10001 Рассмотрим применение этого алгоритма к входному слову caab. Входное слово содержит букву а два раза. Поскольку всегда заме- няется первое вхождение, то в нашем случае первая буква а заме- нится на 101 и мы получим измененное слово clOlaft
1.3] НОРМАЛЬНЫЕ АЛГОРИТМЫ МАРКОВА 31 На следующем такте выполнения нормального алгоритма снова ищется вхождение левой части первой формулы и, найдя и заме- нив его на правую часть, получаем С1011016 Теперь первая формула оказывается неприменимой, применяется вторая формула и получается сЮ11011001 Опять ищется вхождение левой части первой формулы (вообще гово- ря, оно могло бы появиться в результате произведенного изменения, если бы буква а содержалась в правой части примененной формулы). Затем пытаемся применить вторую формулу, но в данном случае оказывается применимой только третья формула. После ее приме- нения получается слово 100011011011001 к которому нельзя применить ни одну формулу. Работа алгоритма завершилась, и мы получили искомую кодировку. Рассмотрим еще несколько простых нормальных алгоритмов. Алгоритм а^- Ь-+ с->- стирает во входном слове буквы а, Ь, с. По определению, вхождения пустого слова имеются слева и справа от каждой буквы в преобразуемом слове. Первое из них пред- шествует первой букве слова. Поэтому алгоритм и который заменяет вхождение пустого слова на букву а, будет бе- сконечно приписывать слева букву а к исходному слову, а это озна- чает, что он не применим ни к какому слову. Алгоритм ►—» а применим к любому слову, так как состоящие из одной завершающей формулы алгоритмы останавливаются (т. е. заканчивают свое вы- полнение) либо сразу, если формула неприменима к входному слову, либо, как в нашем случае, после однократного применения формулы, - хотя полученное слово и будет снова содержать вхождение левой части формулы. Алгоритм 101-*a 1001-*& 10001-*с тоже применим к любому слову. Легко видеть, что он выполняет обратную задачу по сравнению с рассмотренным выше алгоритмом Двоичной кодировки, так как позволяет по слову из знаков 0 и 1 получить слово из букв.
32 ЭЛЕМЕНТЫ ТЕОРИИ АЛГОРИТМОВ [Гл. 1 Сформулируем два простых достаточных признака применимо- сти нормального алгоритма ко всем входным словам: 1) во всех формулах подстановок левые части не пустые, а в правых частях нет тех букв, которые входят в левые части; 2) в каждом правиле подстановки правая часть короче, чем ле- вая. Первый признак гарантирует от «зацикливания», поскольку при < замене вхождений левых частей на правые алгоритм с разными ал- фавитами левых и правых частей не может сам создавать для себя новые возможности выполнения. (Заметим, что этому признаку удов- летворяет рассмотренный нами алгоритм двоичной кодировки и алгоритм, решающий обратную задачу.) Если выполняется второй признак, то после каждого применения формулы подстановки длина слова уменьшается, поскольку боль- шее подслово заменяется на меньшее. Поэтому число замен не может превысить длину исходного слова. (Этому признаку удовлетворяет, например, алгоритм стирания букв а, Ь, с.) Заметим, что алгоритм, удовлетворяющий второму признаку, не может содержать формул подстановки с пустыми левыми частями, так как не бывает слов ко- роче пустого. Перейдем теперь к несколько более сложным примерам. Попро- буем построить нормальный алгоритм приписывания к любому •слову в алфавите {а, Ь, с} справа буквы а. В отличие от машины Тьюринга, свободно передвигающей свой автомат вдоль ленты, нор- мальный алгоритм не имеет непосредственного доступа к правому концу входного слова. Однако мы будем моделировать такой доступ, введя в алфавит специальную букву * для отметки интересующего нас места в слове. Будем реализовывать искомый нормальный ал- горитм по следующей схеме. Г. Приписать букву * к входному слову слева. 2°. Если буква * не последняя в слове, то поменять ее местами со следующей буквой и снова выполнить п. 2°. 3°. Заменить букву * на букву а и остановить алгоритм. Нормальный алгоритм имеет непосредственный доступ к левому концу слова: для того чтобы приписать букву * к входному слову слева, достаточно применить формулу подстановки -*-* Для выполнения п. 2° нужны три формулы подстановок: *с—»-с* Для замены знака * на букву а нужна одна формула подстановки Эта формула содержит стрелку •—>, потому что ее применением завершается выполнение алгоритма. Теперь, чтобы получить нуж- ный нам нормальный алгоритм, остается установить порядок этих
1.3] НОРМАЛЬНЫЕ АЛГОРИТМЫ МАРКОВА 33 пяти формул подстановок. Нельзя начинать запись нормального алгоритма формулой —► * так как эта формула оказывается применимой всегда, и поэтому она применялась бы снова и снова бесконечно долго, причем к сло- ву приписывались бы слева все новые и новые знаки *. Чтобы из- бежать такого бесконечного применения, поместим эту формулу в конец нормального алгоритма после завершающей формулы *<—>а: *а-*-а* *b—>b* *с-*-с* * 1-» а —>* (Для удобства рассмотрения алгоритма формулы подстановки про- нумерованы.) Если входное слово состоит из букв а, Ь, с, то к нему не приме-. нимы первые четыре формулы, левые части которых содержав знак *. Выполнение алгоритма начинается с применения формулы (5), что приводит к приписыванию знака * к слову слева. Затем, в за- висимости от порядка букв во входном слове, применяются форму- лы (1) — (3), и каждый раз знак * сдвигается на одну позицию вправо. Так продолжается до тех пбр, пока знак * не достигнет пра- вого конца слова. Сигналом об этом будет неприменимость формул (1) — (3) из-за того, что правее знака * нет буквы. Тогда применя- ется завершающая формула (4), в результате чего на правом конце слова знак * заменяется на букву а и выполнение алгоритма за- канчивается. Например, если входным словом было aabca то алгоритм будет выполняться в такой последовательности: Преобразованное слово Примененная формула подстановки (5) (1) (1) (2) (3) (1) (4) *aabca a*abca аа*Ьса aab*ca aabc*a aabca* aabcaa х., Можно доказать, что данный алгоритм применим к любому слову, хотя он и не удовлетворяет сформулированным нами достаточным признакам применимости. Если, имея этот алгоритм, мы захотим приписывать справа не букву, а люббе слово, то нам достаточно изменить только заверша- ющую формулу (4). 2 3. Любимский и др.
34 ЭЛЕМЕНТЫ ТЕОРИИ АЛГОРИТМОВ [Гл. I Например, если заменить ее на *»—>Ьас, мы будем выполнять ту же последовательность действий, но на последнем этапе перемещен- ный в правый конец слова знак * заменится на Ьас. Теперь продемонстрируем возможность применения нормальных алгоритмов для арифметических вычислений. В принципе, нормаль- ные алгоритмы могут работать с любой формой записи чисел, но для простоты мы будем изображать числа наборами вертикальных палочек, разделяя их знаком *. Например, число 5 изображается как |||||, азапись |||||*||| является изображением двух чисел 5 иЗ. Алгоритм вычисления модуля разности двух чисел а—b описы- вается двумя формулами подстановки: 1*1 -+* * —> При его выполнении последовательно применяется первая фор- мула, и каждый раз слева и справа от знака * исчезает по одной палочке. Когда меньшее из двух чисел исчерпается, первая формула окажется неприменимой и алгоритм завершится применением вто- рой формулы, которая сотрет знак *. Можно было бы снабдить вторую формулу признаком завершаемости i—>, но в этом нет не- обходимости, так как после ее применения обе формулы оказыва- ются неприменимыми из-за исчезновения знака *. Алгоритм получения остатка от деления числа на 5 реализу- ется одной формулой * НИН При каждом применении этой формулы число уменьшается на 5, и так продолжается до получения остатка, меньшего, чем 5. Если нужно получать не только остаток, но и частное, то можно воспользоваться таким нормальным алгоритмом: * —>* Входным словом является набор палочек без знака *. Поэтому сначала первая и вторая формулы не применимы, и мы применяем третью формулу, приписывая к числу слева знак *. Затем после- довательно применяется первая формула, и каждый раз остаток уменьшается на 5, а частное увеличивается на 1. Когда первая фор- мула оказывается неприменимой из-за того, что справа от знака * не нашлось пяти палочек, применяется вторая, завершающая фор- мула, которая введена только для того, чтобы алгоритм прекратил выполняться, а не «проскочил» на выполнение последней формулы. 1.4. Сравнение различных алгоритмических схем Доказано, что алгоритмические схемы Маркова и Тьюринга эквивалентны в том смысле, что все алгоритмы, описываемые в одной из этих схем, могут быть описаны и в другой. Основная ги- потеза теории алгоритмов в форме Маркова приобретает такой вид;
1.41 СРАВНЕНИЕ РАЗЛИЧНЫХ АЛГОРИТМИЧЕСКИХ СХЕМ 35 Всякий алгоритм нормализуем. Как и машина Тьюринга, алгоритмическая схема Маркова в общем случае не может быть физически реализована, так как она допускает неограниченно большую длину входных слов и слов, воз- никающих в процессе подстановок. Сравнивая алгоритмические схемы Тьюринга и Маркова, мы можем отметить, что у машины Тью- ринга сравнительно развито управление при слабых возможностях преобразования информации, тогда как в алгоритмической схеме Маркова богаче возможности преобразования при менее развитом управлении. В машине Тьюринга предусматривается управление последова- тельностью доступа к различным элементам обрабатываемого слова (сдвиг и влево и вправо). А алгоритмическая схема Маркова жёст- ко закрепляет последовательность доступа (каждый раз ищется первое вхождение левой части очередной формулы подстановки). Аналогично и последовательность действий в машине Тьюринга управляемая (за счет смены состояний), а в алгоритмической схеме Маркова жесткая (последовательный перебор формул подстановки, а после каждого применения незавершающей формулы возврат к самой первой формуле). В то же время элементарная операция по преобразованию ин- формации в машине Тьюринга очень проста (замена буквы в ячей- ке ленты), а в алгоритмической схеме Маркова весьма мощная (за- мена любого слова на любое другое слово). Авторы обеих алгоритмических схем старались простыми сред- ствами обеспечить возможность описания любых алгоритмов. Тью- ринг достиг этой цели, в первую очередь, за счет упрощения дей- ствий, а Марков за счет упрощения логики управления. Примерно одновременно с А. Тьюрингом английский математик Э. Пост предложил иную алгоритмическую схему, которая харак- теризуется еще большей простотой. В машине Поста можно записы- вать в ячейки бесконечной ленты всего два знака 0 и 1. Это ограни- чение не влияет на ее универсальность, потому что любой алфавит, как известно, может быть закодирован двумя знаками. Как и ма- шина Тьюринга, машина Поста может находиться в различных со- стояниях, но каждому состоянию соответствует не строка програм- мы с клетками, а некоторая команда одного из следующих шести типов: Первый тип: записать 1, перейти к I. Второй: записать 0, перейти к i. < Третий: сдвиг влево, перейти к i. Четвертый: сдвиг вправо, перейти к I. Пятый: останов. Шестой: если 1, то перейти к i9 иначе перейти к /. Как и в машине Тьюринга, все состояния машины Поста про- нумерованы. Указания «перейти к Ь> или «перейти к /> означают, 2*
36 ЭЛЕМЕНТЫ ТЕОРИИ АЛГОРИТМОВ [Гл. I что после выполнения данной команды машина должна перейти в i-e или /-е состояние, которому соответствует своя команда одного из перечисленных шести типов. Команды первых двух типов уста- навливают новое содержимое обозреваемой ячейки ленты, остав- ляя эту ячейку в «поле зрения» машины. Третий и четвертый типы команд обеспечивают сдвиг на одну позицию, т. е. перенесение поля зрения на соседнюю слева или справа ячейку. Первые пять типов команд не зависят от информации на ленте; поэтому, если бы алгоритм был составлен только из команд этих типов, то он запи- сывал бы на ленту всегда одно и то же слово, зависящее только от вида алгоритма, но не от входного слова. Однако добавление ше- стого типа команд делает алгоритмическую схему Поста такой же универсальной, как алгоритмические схемы Тьюринга и Маркова. Доказательство возможности перехода от любой машины Тьюринга к машине Поста основывается на том, что после кодировки исход- ного алфавита знаками 0 и 1 каждое состояние машины Тьюринга, работающей с этими знаками, описывается несколькими состояния- ми машины Поста. 1.5. Понятие алгоритмической неразрешимости В теории алгоритмов известны некоторые задачи, о которых доказано, что для их решения не существует алгоритма. Такие за- дачи называются алгоритмически неразрешимыми. Обычно алгорит- мическая неразрешимость новых задач доказывается методом све- дения к этим задачам известных алгоритмически неразрешимых задач. Тем самым доказывается, что если бы была разрешима но- вая задача, то можно было бы решить и заведомо неразрешимую задачу. В самом деле, если удается свести к новой задаче неразре- шимую задачу, то любой алгоритм решения новой задачи решал бы и ту задачу, что противоречило бы ее неразрешимости. Применяя метод сведения, обычно ссылаются на искусственные задачи, кото- рые не представляют самостоятельного интереса, но для которых легко непосредственно доказать их неразрешимость. К числу та- ких задач относится проблема распознавания самоприменимости. Говоря об универсальной машине Тьюринга, мы показали, как любую схему машины Тьюринга можно записать на ленту в закоди- рованном виде. Аналогично можно закодировать и любой нормаль- ный алгоритм, использовав для разделения формул подстановки какой-нибудь знак, заведомо не встречающийся в формулах (на- пример, запятую). Тогда сам нормальный алгоритм становится сло- вом и может служить входным словом для любого нормального ал- горитма и, в частности, для самого себя. Например, нормальный алгоритм c-+de ab*->k
1.5] ПОНЯТИЕ АЛГОРИТМИЧЕСКОЙ НЕРАЗРЕШИМОСТИ 37 будучи примененным к представляющему его запись слову a-*-b, c->de, ab*—>k станет выполняться так, как показано в табл. 1.5. Таблица 1.5 Преобразованное слово Примененная формула подстановки Ь —► Ь, с —► de, ab k b —► Ь, с —► de, bb н-» k b —► b, de —* de, bb»—> k a —► b a —► b c~+ de Работа закончена, так как ни од- на формула не применима к по- лученному слову. Сначала алгоритм заменит а на b согласно первой формуле, затем снова заменит а на b в другом месте, потом подставит de вместо с и остановится. Однако могло бы оказаться, что алго- ритм не применим к описывающему его слову в том смысле, что ни- когда не остановится, порождая для себя все новые и новые условия для применения формул подстановки. Простейшим примером этого может служить нормальный алгоритм ->а. Таким образом, все алгоритмы делятся на два класса. Само- применимыми. называются алгоритмы, которые, начав работу над собственным описанием, рано или поздно останавливаются. Если же алгоритм в таком случае зацикливается, он называется несамо- применимым. Аналогично можно говорить о самоприменимости машин Тьюринга, имея в виду их применение к своим линейным изображениям. И вообще, для любой формы записи алгоритмов мож- но ставить проблему такой классификации. Таким образом, возникает задача: как узнать, является ли са- моприменимым тот или иной алгоритм, т. е. остановится ли когда- нибудь заданный алгоритм, будучи примененным к собственному описанию. Выражаясь более точно, нужно найти общий алгоритм, который для любого алгоритма отвечал бы на вопрос, является ли он самоприменимым. Мы докажем, что такого общего алгоритма рас- познавания самоприменимости не существует. Доказательство будем вести от противного. Предположим, что искомый алгоритм сущест- вует и назовем его А. Алгоритм А применяется к записи любого алгоритма Р и вырабатывает букву С или букву Н, в зависимости °т того, самоприменим или нет алгоритм Р: С, если Р самоприменим, Н, если Р несамоприменим. итм А может быть применен к записи любого алгоритма, словом для него служит запись алгоритма Р, а выходное А (запись Р) =4> Алгор Входным
38 ЭЛЕМЕНТЫ ТЕОРИИ АЛГОРИТМОВ (Гл. I слово состоит из одной буквы. Построим еще один алгоритм В, ко- торый должен, увидев букву С, зациклиться, а увидев букву Н, остановиться. Ему соответствует машина Тьюринга с программой: А С н Qi С* <h A, gi 1 В самом деле, эта машина, увидев букву С, будет работать без конца, поочередно то стирая, то вновь записывая букву С в обозре- ваемой «мигающей» ячейке. Увидев же букву Н, она сразу останав- ливается. Таким образом, факт существования алгоритма В не вы- зывает сомнения, он доказан описанием этой машины Тьюринга. Мы знаем из предыдущего, что, если есть два алгоритма Л и В, то существует и их произведение К.-А-В, т. е. алгоритм, которой осуществляет последовательную работу этих алгоритмов, выполняя сначала Л, затем В. В разд. 1.2.3 мы показали, как строится про- грамма машины Тьюринга, реализующей такой алгоритм. Если мы докажем, что алгоритм К не может существовать, то либо не существует Л, либо не существует В, либо нельзя построить их произведение. Но В заведомо существует, а если бы существовал алгоритм Л, то можно было бы построить и произведение А-В. Тем самым мы опровергнем предположение о существовании алгорит- ма Л. Мы докажем, что алгоритм К не может существовать, доказав, что он не может быть ни самоприменимым, ни несамоприменимым. Действительно, рассмотрим применение алгоритма Л к его соб- ственной записи. По определению, сначала к ней применится ал- горитм Л, затем к полученному результату применится алгоритм В. Предположим, что алгоритм К самоприменим. Тогда Л (запись К)=>С. Но, получив на входе букву С, алгоритм В должен зациклиться, а это означает, что зацикливается сочетание Л и В. Значит, алгоритм К. оказывается несамоприменимым, что противоречит предположению. Если же К несамоприменим, то Л (запись и, получив на входе букву Н, алгоритм В остановится, т. е. алгоритм К оказывается самоприменимым, и мы снова приходим к противо- речию. Итак, алгоритм # не может быть ни самоприменимым, ни не- самоприменимым, а, следовательно, его вообще не существует. Поскольку он формально построен из алгоритмов Л и В, то не должно существовать какого-нибудь из этих двух алгоритмов.
16J ' ПОНЯТИЕ АЛГОРИТМИЧЕСКОЙ НЕРАЗРЕШИМОСТИ 39 Алгоритм В заведомо существует, значит, не может быть алго- ритма А. Тем самым мы доказали, что проблема распознавания са- моприменимости алгоритмически неразрешима. Отсюда вовсе не следует, что, какой бы мы ни взяли алгоритм, невозможно распоз- нать, самоприменим ли он. Например, очевидно, что нормальный алгоритм а->а несамоприменим, так как он никогда не останавливается, тогда как алгоритм а*-+а самоприменим, потому что он в любом случае остановится либо сразу, либо после одной замены буквы а на нее же. В принципе, может даже оказаться, что для любого отдельно взятого алгоритма удастся доказать, самоприменим ли он. Утверждается только, что нет массового алгоритма, который решал бы весь класс задач рас- познавания самоприменимости любых алгоритмов. Аналогично, этому, алгоритмически неразрешима сформулированная Лейб- ницем проблема проверки правильности любых математических утверждений, но из этого не следует, что есть теоремы, которые ни- когда не смогут быть доказаны. Алгоритмическая неразрешимость проблемы Лейбница означает, что бесполезно искать общий способ для доказательства всех теорем.
Глава 2 ПРОГРАММИРОВАНИЕ НА АЛГОРИТМИЧЕСКИХ ЯЗЫКАХ, АЛ ГОЛ-60 2.1. Общая характеристика алгоритмических языков Алгоритмические языки (называемые также языками програм* , мирования) — подобно машинам Тьюринга, Поста и нормальным , алгоритмам Маркова — также предназначены для представления различных алгоритмов. Однако в рамках этого общего назначения они преследуют существенно иные цели. Дело в том, что рассмотренные в главе 1 алгоритмические схемы преследовали прежде всего цели теоретического характера — обес- печить принципиальную возможность представления с их помощью любых алгоритмов, позволить теоретически исследовать те или иные алгоритмы (например, для установления эквивалентности различ- ных алгоритмов), служить для доказательства алгоритмической не- разрешимости отдельных проблем и т. п. Для достижения этих це- лей важно, чтобы сам. аппарат представления алгоритмов был мак- симально простым (этим объясняется элементарность действий на каждом шаге работы машины Тьюринга и Поста, элементарность управления в нормальных алгоритмах и т. п.). Основная же цель, которая ставится при разработке алгорит- мического языка, состоит как раз в том, чтобы предложить некое фор- мализованное средство общения между людьми, а также и главным образом между человеком и вычислительной машиной, предназна- ченное для изложения алгоритмов. Такой язык должен быть формальным, чтобы любая запись на нем однозначно определяла алгоритм. Например, приведенное в разд. 1.1 описание алгоритма Евклида нельзя считать вполне точ- ным, так как в нем почти каждую фразу можно понимать по-разному и получается, что оно не обеспечивает строго однозначной последо- вательности действий. Если тот же алгоритм записать на алгоритми- ческом языке, то эта запись однозначно определит все действия по применению алгоритма. Второе требование к алгоритмическому языку состоит в том, что он должен быть настолько простым, чтобы не возникало чрезмерных затруднений при разработке специальной программы-транслятора, осуществляющей автоматический перевод любых текстов с этого языка на язык вычислительной машины. В отличие от прежних алгоритмических схем, от которых тре- бовалась только универсальность и простота, такой язык должен
t l] ОБЩАЯ ХАРАКТЕРИСТИКА АЛГОРИТМИЧЕСКИХ ЯЗЫКОВ 41 быть и достаточно гибким, чтобы обеспечивать компактную и на- глядную запись алгоритмов. Он должен быть легко доступен для освоения широким кругом лиц, заинтересованных в решении задач определенного класса, а также достаточно удобен для его исполь- зования в публикациях. Иначе говоря, он должен быть настолько богат выразительными средствами, чтобы люди не испытывали не- удобств, передавая с его помощью алгоритмы друг другу или же вычислительной машине. Для этого нужно, -чтобы способ запи- си алгоритмов на таком языке был достаточно близок к традицион- ным способам описания алгоритмов в книгах, статьях и инструк- циях. К настоящему времени разработано много различных алгорит- мических языков, несколько из них получили широкое международ- ное признание. Каждый алгоритмический язык предназначается для достаточно широкой, но все же, в какой-то степени, ограни- ченной области приложения, для определенного круга задач. Подобно тому как профессиональные языки физиков, медиков, экономистов различаются своей терминологией, отражающей ос- новные понятия, характерные для той или иной области знания, каждый алгоритмический язык характеризуется своей ориентацией на определенные типы и структуры данных, а также на определенные действия над данными. При описании алгоритмов численной математики определенный тип указывает, являются ли элементы данных целыми, веществен- ными или комплексными числами, логическими значениями. Для описания алгоритмов аналитических преобразований математиче- ских формул требуются также такие типы, как буквы алфавита, спе- циальные математические знаки и т. д. Для каждого круга задач, отражающих некоторый круг реаль- ных процессов, характерны определенные структуры данных, т. е. формы связи между элементами данных. Например, в задачах ли- нейной алгебры элементы данных (числа) объединяются в векторы и матрицы; при автоматической обработке документации приходится иметь дело со структурой списка с заданным порядком следова- ния элементов, а также со структурой таблицы, к которой можно обратиться, указав, какая требуется строка; при составлении же- лезнодорожных расписаний нужно работать со структурой графа, в котором каждый элемент (вершина) может быть связан (ду- гами) с любым числом других элементов. Действия над элементами структур и целыми структурами опи- сываются операторами алгоритмического языка. Каждый алго- ритмический язык характеризуется своим набором основных видов операторов, ориентированных на конкретный круг задач. Как правило, один оператор алгоритмического языка позволяет выпол- вить существенно более крупные действия, чем, например, одна операция машины Тьюринга.
42 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 Для обозначения объектов алгоритмического языка использу- ется широко распространенное в математике понятие переменной ве- личины. Однако по существу своему переменные алгоритмических языков отличаются от переменных, которыми мы оперируем, на- пример, в алгебре. Когда мы употребляем переменную в алгебре, то имеем в виду, что она будет иметь определенное значение, которо- го мы еще не знаем. В то же время в алгоритмическом языке каждая переменная в процессе выполнения алгоритма может- последова- тельно принимать разные значения. Скорее следовало бы сравнить переменную алгоритмического языка с ячейкой на ленте машины Тьюринга. Аналогично занесению новых значений в ячейки машины Тьюринга, можно в процессе выполнения алгоритма присвоить но- вое значение любой переменной и использовать его впоследствии сколько угодно раз, причем после каждого использования перемен- ная сохраняет свое прежнее значение. Забывается старое значение переменной только тогда, когда в эту переменную пишется новое значение. Однако в отличие от безымянных ячеек машины Тью- ринга, каждая переменная имеет свое имя, по которому к ней можно непосредственно обратиться. Это создает существенные удобства для программиста, так как избавляет его от необходимости орга- низовывать доступ к нужной переменной последовательным перебо- ром, как это приходится делать при доступе к ячейке на машине Тьюринга. Таким образом, фундаментальным действием в любом алгорит- мическом языке, как и в рассмотренных нами алгоритмических схемах, является присваивание, которое изменяет значение некото- рой переменной. В алгоритмических языках, предназначаемых для математических расчетов, оператор присваивания обеспечивает счет по формуле и присваивание некоторой переменной вычисленною значения. Выполнение операторов присваивания может включать в себя поиск по структуре и преобразование структуры данных. Например, возможны операторы «вставить новую строку в таблицу» или «умножить две матрицы и присвоить третьей матрице значение полученного результата». В алгоритмических языках предусматриваются определенные правила построения композиций операторов, т. е. объединения их в более крупные операторы. Каждое правило построения компо- зиции предписывает свой порядок выполнения операторов, входя- щих в композицию. Способы описания таких композиций в алгорит- мических языках более удобны и естественны для человека, чем управление посредством перехода в следующее состояние в машине Тьюринга или посредством обязательного перехода к следующей или возврата к первой формуле подстановки в нормальном ал- горитме. В основе управления алгоритмических языков лежат три ти- повых операторных конструкции.
J.1] ОБЩАЯ ХАРАКТЕРИСТИКА АЛГОРИТМИЧЕСКИХ ЯЗЫКОВ 43 Следование: начало 51; S2; . . .; Sk конец. Выбор: если В то 51 иначе 52. Повторение (цикл): пока В выполнять 5. Первая из этих конструкций означает, что нужно выполнить сначала оператор 51, потом 52 и т. д. и закончить выполнением опе- ратора Sk. Вторая конструкция означает, что нужно проверить условие В и, если оно верно, то выполнить оператор 51, а если не верно — оператор 52. Третья конструкция означает, что нужно по- вторять выполнение оператора 5, пока верно условие В. Условие В включает в себя одну или несколько переменных и является верным или нет на каждом этапе выполнения алгоритма в зависимости от того, каковы текущие значения этих переменных. Например, условие В: х>у верно при значениях х=5 и t/=3, но если эти значения изменятся так: х=4 и у=1, то условие В станет неверным. При выполнении конструкции цикла пока В выполнять 5 проверка верности условия В производится перед каждым очеред- ным выполнением оператора 5. Если при очередной проверке ока- зывается, что условие В не верно, то на этом заканчивается выпол- нение цикла. Каждая из рассмотренных трех операторных конструкций в свою очередь объявляется оператором и может быть подставлена в другую конструкцию любого типа. Например, возможна такая композиция: начало 51; 52; если 51 то 53 иначе пока В2 выпол- нять 54; 55 конец, которая тоже является оператором. Здесь задан следующий порядок действий: выполнить оператор 51, затем вы- полнить 52, потом, в зависимости от верности условия В1, выбрать (и выполнить) либо оператор 53, либо цикл пока В2 выполнять 54, а затем выполнить оператор 55. Помимо рассмотренных основных конструкций управления в алгоритмических языках обычно предусматривается также возмож- ность прямой передачи управления на любой оператор. С этой целью операторам даются имена (метки). Существуют и другие средства управления, с которыми мы познакомимся в последующих разделах этой главы. Первым развитым алгоритмическим языком, который получил общее признание и широкое применение, был язык алгол-60 *). Он удовлетворяет всем необходимым требованиям к алгоритмиче- ским языкам, и его структура продумана очень удачно. Поэтому он успешно выдержал испытание временем и до сих пор является од- ним из наиболее популярных языков программирования. За про- шедшие два десятилетия в научной литературе было опубликовано много алгоритмов на этом языке, причем часто такие публикации . *) Алгол (ALGOL) — сокращение английских слов Algorithmic Language (алгоритмический язык), а 60 означает год (1960) его официальной публикации.
44 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-бО (Гл. 2 предназначались просто для того, чтобы точно сообщить другим людям, как нужно решать те или иные задачи. Дело в том» что алгол достаточно удобен для описания определенного, весьма важ- ного класса так называемых вычислительных алгоритмов, т. е. ал- горитмов выполнения всевозможных вычислений. При изложении методов программирования на алгоритмических языках мы будем основываться на языке алгол. 2.2. Способ описания алгоритмических языков Именно потому, что алгоритмический язык имеет богатые выра- зительные возможности, четкое, недвусмысленное определение этого языка представляет собой сложную проблему. Развитым аппаратом для таких целей служат формальные грамматики, исследованием и разработкой которых занимаются многие-ученые. Для описания ал- гола и некоторых других алгоритмических языков была использо- вана грамматика, предложенная американским математиком Дж. Бэкусом (нормальная форма Бэкуса). Грамматическое описание любого языка (в том числе и языка, на котором мы говорим) включает в себя его алфавит, синтаксис и семантику. Алфавит языка представляет собой набор основных сим- волов («букв» языка), которые могут быть использованы при записи алгоритма. Синтаксис — это правила построения фраз, позволяю- щие определить, правильно или неправильно написана та или иная фраза. Точнее говоря, синтаксис языка представляет собой набор правил, устанавливающих, какие комбинации символов являются текстами на этом языке. Такой набор правил позволяет для любого текста получить ответ на вопрос, написан ли он на рассматриваемом языке. (Заметим, что привычная нам грамматика русского языка не формальна, как и грамматики других естественных языков. Имен- но поэтому лингвисты иногда не могут прийти к единому толкова- нию некоторых спорных грамматических проблем.) Метод Бэкуса позволяет’описать при помощи формул весь синтаксис алгоритми- ческого языка. Семантика определяет смысловое значение фраз языка. В част- ности, семантика алгола устанавливает, какие последовательности действий описываются теми или иными фразами этого языка, а в конечном итоге, какой алгоритм определен данным текстом на ал- голе. Алгоритмические схемы Тьюринга, Маркова и Поста тоже можно рассматривать как алгоритмические языки и они тоже имеют свои синтаксис и семантику. Например, у Тьюринга синтаксисом является форма задания программы в виде матрицы, каждая клетка которой содержит три знака (или менее). Этот синтаксис описыва- ется не формально потому, что он очень прост и не допускает дву- смысленных толкований даже в словесном изложении. Семантика машины Тьюринга — это слова о том, что автомат «видит» каждый
2.2] , СПОСОБ ОПИСАНИЯ АЛГОРИТМИЧЕСКИХ ЯЗЫКОВ 45' раз одну ячейку, может находиться в различных состояниях, ищет в матрице клетку, изменяет содержимое одной ячейки и т. д. Аналогично для алгоритмического языка А. А. Маркова син- таксис состоит в том, что нормальным алгоритмом является после- довательность формул подстановок, в каждой из которых содержится одна стрелка. Семантика здесь — это описание способа поиска вхождений, замены подслов по формулам подстановки с последую- щим возвратом к началу алгоритма и т. д. В отличие от этих алгоритмических схем развитый алгоритми- ческий язык весьма богат по изобразительным средствам. Например, синтаксис алгола настолько развит и многообразен, что допускает построение очень сложных и длинных фраз. Поэтому серьезную проблему представляет собой формальное определение правиль- ности тех или иных алгольных фраз. Для этого используется спе- циальный формализм Бэкуса, основанный на том, что язык описы- вается при помощи формул. 2.2.1. Нормальная форма Бэкуса. В нормальной форме Бэкуса (НФБ) каждая формула представ- ляет собой определение некоего термина языка. Название опреде-. ляемого термина, взятое в угловые скобки ( ), записывается в ле- вой части формулы; левая часть отделяется от правой служебным знаком :: = (что означает «это есть»); справа от знака :: = сообща- ется, какие конструкции могут являться определяемым термином. Правая часть формулы может содержать знаки алфавита описывае- мого языка или другие термины, входящие в определяемый термин как составные элементы. В качестве примера рассмотрим формулу, определяющую термин «цифра»: (цифра) ::=0|112|3|4|5|6|7|8|9 В этой формуле употреблен еще один служебный знак | (пря- мая черта), который читается как «или». В правой части сообщается, какие знаки являются цифрами. В данном случае 0, 1, . . ., 9 есть «значения» понятия «цифра». В правой части формулы знаками | (или) разделяются так называемые альтернативы, которыми описываются различные варианты значения определяемого термина. Аналогично, формула . (буква) ::^|B|C|D|£|F|G|^|/|J|K|£|M|A(|O1P|Q|^|S|TH7|F| W\X\Y\Z означает, что понятие «буква» может принимать 26 различных зна- чений, каждым из которых является заглавная буква латинского алфавита. Заметим, что служебные знаки (, ),::=* и | не входят в алфавит языка, описываемого с помощью НФБ. Рассмотрим теперь формулу (пара ):: = (буква) (цифра)
46 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 Здесь правая часть содержит не знаки, а названия терминов, ко- торые также заключаются в угловые скобки ( ) и означают, что раз- решается подставлять вместо этих терминов любые их значения. Например, подставляя вместо (буква) любую заглавную латинскую букву, а вместо (цифра > любую цифру, мы можем получать любые комбинации Л О, Л1, . . ., Z8, Z9. Каждая из этих комбинаций будет, согласно данной формуле, значением понятия «пара». Формула (знак):: = (буква )| (цифра) отличается в своей правой части от предыдущей формулы наличием знака |. Согласно этой формуле, значением понятия (знак) может быть или значение понятия (буква) или значение понятия (цифра). Поэтому она определяет другое понятие, значениями которого яв- ляются одиночные знаки Л, В, . . ., Z, 0,1, . . ., 9. Если предыду- щая формула обеспечивала 26-10=260 различных вариантов «пары», то эта формула обеспечивает 26+10=36 различных вариантов «знака». Формула (номер):: = N (цифра) устанавливает, что конкретными примерами понятия «номер» яв- ляются комбинации NO, N1, . . ., N9. Если мы захотим предусмо- треть не 10, а 11 номеров, то для этого достаточно дописать в конец правой части |N10. Получится формула (номер )::=N (цифра )|N 10 согласно которой допускаются 10 вариантов номера из двух знаков и один вариант из трех знаков. Такие формулы, служащие для формального описания языка, называются металингвистическими. Рассмотренные. нами до сих пор металингвистические формулы четко предопределяют число знаков в возможных значениях описываемых терминов. Однако можно определить формулой и термин из произвольного числа зна- ков. Это вполне естественно, так как нашей конечной целью явля- ется формальное определение любого алгоритма на алголе, а алго- ритмы могут иметь произвольную длину. Примером такого опреде- ления может служить следующая формула: (слово):: = (буква)| (слово) (буква) Согласно своей левой части, эта формула определяет термин «сло- во». Из правой части формулы следует, что, если нам известно ка- кое-то слово (например, из одной буквы), то, добавав к нему букву, мы снова получим слово. Мы можем взять любое значение термина «буква», например, В (которое является словом, согласно первой альтернативе правой части формулы) и приписать к этому значению
2.21 СПОСОБ ОПИСАНИЯ АЛГОРИТМИЧЕСКИХ ЯЗЫКОВ 47 любую букву, скажем, А. Согласно второй альтернативе правой части формулы, полученная комбинация ВА является словом, а, следовательно, опять-таки согласно второй альтернативе правой части формулы, к нему можно приписать еще букву Р, и при этом тоже получится слово, на этот раз ВАР. Так можно получить слово любой длины. Например, легко проследить буква за буквой, что комбинация букв TEMPERATURE также является словом в силу этого определения. Вообще, согласно второй альтернативе из пра- вой части этой формулы, если любая комбинация из п букв является словом, то и любая комбинация из (п+1) букв тоже является словом. Рассмотренная формула представляет собой пример рекурсив- ного определения, когда сам термин определяемого понятия исполь- зуется для получения некоторых своих собственных значений. При этом он может непосредственно встречаться в правой части формулы своего определения. Тем самым уже известные значения понятия служат основой для получения новых значений того же понятия. Аналогично можно строить более общие понятия, например, список слов. (Заметим, что различные списки часто используются в различных конструкциях языка алгол.) Списком слов мы будем на- зывать любую последовательность слов, разделенных запятыми. Металингвистической формулой это выражается так: (список слов):: = (слово)|(список слов), (слово) Согласно этой формуле, любое слово представляет собой частный случай списка слов. Приписывая после него запятую и еще одно слово, мы получаем снова список слов, состоящий уже из двух слов. Например, В — слово, а значит это список слов; ВАР — тоже слово, поэтому В, ВАР — список слов; присоединив к этому списку TEMPERATURE, мы получим новый список слов В, ВАР, TEMPERATURE Аппарат металингвистических формул позволяет нам опреде- лять язык алгол, последовательно. усложняя вводимые понятия и переходя ко все более крупным понятиям до тех пор, пока мы не придем к понятию алгол-программы. Если бы мы формально описы- вали этим же методом русский язык, то в конце концов могли бы прийти к определению термина «книга», например, по формуле (книга):: = (титульный лист) (текст) (выходные данные) Титульный лист можно было бы определить так: , (титульный лист):: = (автор) (название) Текст определялся бы как набор слов и т. д. Однако такой метод не поможет нам формально определить Понятие «хорошая книга» или «правильный алгоритм». Он пригоден
48 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 (Гл. Э только для того, чтобы проверять, правилен ли синтаксически тот или иной текст. С его помощью нам не удастся узнать, какие вычис- ления предусматривает данный алгоритм, или же, является ли ос- мысленным данное слово. Альтернативы в правой части металингвистической формулы мож- но менять местами: совокупность значений, определяемых форму- лой, от этого не изменяется. В частности, приведенную выше фор- мулу для «слова» можно записать и так: (слово): :=(слово) (буква) | (буква) Такое определение ничем не отличается от данного выше, посколь- ку совсем не обязательно первое значение термина получать по са- мой левой альтернативе. Только из эстетических соображений в фор- муле для «цифры» мы перечисляем в обычном порядке альтернатив- ные значения этого термина 0 | 1 | 2 | 3 ... С таким же успехом мы могли бы начать этот список и так: 7 | 3... Важно только, чтобы бы- ли упомянуты все альтернативы. Однако, как правило, нельзя про- изводить перестановки внутри одной альтернативы. Например, фор- мулы N (цифра) и (цифра) N задают разные виды комбинаций знаков. Таким образом, металингвистическая формула не только указывает знаки, входящие в определяемый термин, но и устанав- , ливает их порядок. Впрочем, иногда можно переставлять части не- кой альтернативы, если окажется, что значениями определяемого термина являются и такие переставленные комбинации знаков. На- пример, поскольку слово — это произвольная последовательность букв, то совершенно неважно, дописываем ли мы буквы справа или слева. Поэтому допустимо и такое определение: (слово) ::= (буква) | (буква )(слово) Теперь к исходному слову можно приписывать буквы только сле- ва, и вывод слова TEMPERATURE нужно начинать не с началь- ной буквы Т, как мы сделали бы при прежнем определении, а с ко- нечной буквы Е. Такое изменение формулы возможно потому, что термин «слово» строится из одинаковых терминов «буква». Авторам алгола не удалось дать обозримое формальное описание семантики этого языка, и поэтому, в отличие от формализованного синтаксиса, вся семантическая информация об алголе была изложе- на ими в виде пояснений на английском языке, переведенных впо- следствии на русский и другие языки. 2.3. Алфавит алгола Мы приступаем к систематическому описанию алгола и начнем с того, что определим набор символов, т. е. знаков, из которых мож- но строить запись алгоритма: (символ) ::= (буква) | (цифра) | (ограничитель)
АЛФАВИТ АЛГОЛА 4» 3.3) В сущности, символ — это буква алфавита языка алгол, но мы оставляем термин «буква» за русскими и английскими буквами, ко* торые входят в алфавит алгола, но не исчерпывают его. Теперь поясним использованные в этом определении альтерна- тивы. При записи алгоритмов на языке алгол разрешается использо- вать любые строчные и заглавные буквы английского и русского языков. <буква>:: = a|b|c|d|e|f|g|ft|/|/|^|/|m|n|o|p|^|r|s|/|u| Q| R |S|T | (/1 V| IF | Х| У |2|б|е|д|ж|з|л |м|н|п|т|ф|ц| ч|ш|и<|ь|'&|ы|э|ю|я|£> |Г|Д|.Ж|3|Я|Л|Л|У|ФЩ| Ч\Ш\Щ[ Ь | Ъ\Ы | Э\ю\я В правой части этой формулы перечислены по порядку буквы латинского алфавита и отличающиеся от них по начертанию русские буквы. Такой широкий выбор букв облегчает использование алго- ла как средства общения между людьми. Однако при общении с вы- числительной машиной возникает необходимость кодировать любую букву пробивками на перфокартах или использовать другие сред- ства механического ввода информации. Чем больше выбор букв, тем сложнее оказывается аппаратура ввода. Как мы уже знаем, в принципе было бы достаточно двух знаков, что обеспечило бы край- нюю простоту аппаратуры, но затруднило бы восприятие информа- ции человеком. На практике выбирается компромисс, приемлемый для людей и пригодный для машинной реализации. Например, в алгоритмах, предназначенных для машин, обычно допускается ис- пользование только заглавных букв латинского алфавита. (Пред- почтение отдается латинскому алфавиту потому, что он привычен для написания математических формул, а это основная цель языка' алгол.) Для определения термина «цифра» используется уже знакомая нам формула <цифра)::=0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 Понятие ограничителя в алголе примерно соответствует понятию знака препинания в русском языке. Элементы фраз ограничиваются запятой, точкой с запятой или точкой. Элементы алгольного текста ограничиваются знаками операций, скобками, знаком := и некото- рыми другими служебными знаками, а также служебными словами (*f»then, do и др.), для которых не удалось подобрать подходящих - знаков. При выборе специальных знаков учитывались установив- шиеся традиции. В частности, элементы списков разделяются запя- тыми (по аналогии с естественными языками).
60 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 Итак, ограничитель — это либо служебный знак, либо служеб- ное слово: (ограничитель) (служебный знак) | (служебное слово) (служебный знак) ::== (знакоперации) ((скобка) | (разделитель) Все алгольные знаки операций исчерпываются следующей металинг- вистической формулой: (знак операции)::= ||Х|/|4-|+|—|<|С1=|>1>1¥=|—11 Л |V | =>1= Здесь перечислены 17 знаков операций. Из них первые 6 знаков от- носятся к арифметическим операциям, следующие 6 соответствуют операциям отношения, а последние 5 знаков являются знаками ло- гических операций. Большинство из перечисленных знаков привыч- ны читателям, знакомым с элементарной математикой. Заметим, что знак f служит для обозначения возведения в степень. Знак 4- употребляется для «деления нацело», т. е. для деления целых чи- сел с получением целой части результата. Что касается знаков ло- гических операций, то знак —1 служит для операции отрицания, знак Д обозначает конъюнкцию (операцию, называемую также ло- гическим умножением), V — знак дизъюнкции (логического сложе- ния), z> — знак операции импликации (следования), = служит зна- ком операции эквивалентности. В дальнейшем мы подробнее оста- новимся на этих операциях. В алголе употребляются следующие виды скобок: (скобка) ::= ( | ) | I | 1 | „ | ” Круглые скобки применяются в формулах для выделения под- выражений или аргументов функций. Квадратные скобки служат для выделения индексов (которые обычно в математическом тексте пишутся внизу строки, тогда как в алголе все символы пишутся в - одну строку). Что касается кавычек, то употребление этого вида скобок связано с тем, что в некоторых случаях алгол позволяет оперировать не только числами, но и особыми строками символов, которые отделяются кавычками от остального алгольного текста. Разделители перечисляются в следующей формуле (разделитель) ::=, | . | ; | := | : Запятая разделяет элементы списков. Точка используется для разделения целой и дробной части чисел. Точка с запятой разде- ляет предложения языка. Знак : = отделяет имя переменной от вы- ражения, по которому вычисляется ее значение. Двоеточие отделяет от оператора его имя (метку), а также используется при описа- нии массивов. Служебное слово представляет собой один символ и употреб- ляется Для наглядности, чтобы не выдумывать новые служебные знаки, которых нет в традиционной математической символике и
2.3] АЛФАВИТ АЛГОЛА 5> которые поэтому могли бы запутать читателей. Для каждого слу- жебного слова из нескольких значений, которые соответствующее слово имеет в английском языке, выбрано одно определенное зна- чение, соответствующее служебной роли этого слова в алголе. В табл. 2.1 приводится полный список этих служебных слов с пе- реводом их смыслового значения и с краткими пояснениями. Не- которые из служебных слов будут объяснены в дальнейшем. Работая с языком алгол, необходимо знать эти слова наизусть» чтобы каждый раз понимать их смысл, не обращаясь к словарю. Таблица 2.1 Служебное слово Русский перевод Пояснение true false go to if then else for do step until while comment begin end boolean integer real array switch procedure string label value 10 Помимо 24 ан жебное слово 10, ] истина ложь перейти к если то иначе для выполнить шаг ДО пока комментарий начало конец логический целый вещественный массив переключатель процедура строка метка значение глийских служебны которое также игра Обозначения двух возможных логи- ческих значений в алгебре логики Является начальным символом опе- ратора перехода, который служит для изменения порядка выполнения алгоритма Эти слова употребляются при выборе пути по алгоритму с учетом условий Слова, служащие для задания цикли- ческого выполнения действий Служит для внесения в алгольный текст примечаний, не влияющих на выполнение алгоритма Служебные слова, играющие роль скобок для выделения частей алго- ритма Служат для описания типов данных Служит для удобства записи чисел, начинающихся или заканчивающихся многими нулями х слов, этот список содержит слу- ет роль единого знака.
52 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 (Гл. S 2.4. Неформальное рассмотрение фрагментов алгол-программ Прежде чем продолжить изложение формальных правил записи алгоритмов на алголе, мы рассмотрим несколько простых приме- ров. Эти примеры берутся из области вычислений, так как язык алгол предназначен именно для описания вычислительных алго- ритмов. Любой вычислительный алгоритм состоит из действий, в ходе выполнения которых значения формул присваиваются переменным, а переменные используются для вычисления новых значений, пока не получатся окончательные результаты. Например, на алголе мож- но записать действие х'.—а означающее присваивание переменной х того значения, которое имеет переменная а. Здесь вместо обычного знака равенства упот- реблен особый знак := . Дело в том, что знак = может пониматься неоднозначно, так как в разных ситуациях он имеет три различных смысла. Запись х=а могла бы означать: 1) высказывание, что уже сейчас переменные х и а имеют равные Значения; нужно сравнить эти значения, чтобы выяснить, истинное это высказывание или нет; 2) присваивание переменной х того значения, которое имеет переменная а; 3) присваивание в противоположном смысле, т. е. установка нового значения переменной а, равного значению переменной х. Во избежание таких недоразумений, в алголе знак = исполь- зуется только для записи высказываний, а для присваивания зна- чений применяется знак :=. В этом знаке двоеточие обращено в сто- рону той переменной, которая получает новое значение. Запись х:=а означает, что значение х изменяется и становится равным значению а, причем переменная а сохраняет свое прежнее значение. Если мы запишем х:=а+12 Это будет означать, что к значению а нужно прибавить число 12 И присвоить переменной х значение полученной суммы. Рассмотрим следующую запись на алголе a:—(V/(OH—VHA4)/t Здесь переменной а присваивается значение дроби, у которой в знаменателе t, а в числителе разность значений двух переменных ч VKOH и VHA4. Тем самым вычисляется ускорение тела при равно- мерно ускоренном движении. Обычно мы записываем такое действие
2.41. \ НЕФОРМАЛЬНОЕ РАССМОТРЕНИЕ ФРАГМЕНТОВ ПРОГРАММ S3 с помощью формулы д V кон — 1^иач На алголе нельзя прямо воспроизвести эту формулу по двум причинам. Во-первых, в ней употреблен знак = в смысле присваи- вания, а во-вторых, в языке есть специфическое требование, линей- ности текста, вызванное тем, что алгол должен быть понятен вычис- лительным машинам. Обычно в устройствах ввода информации зна- ки поступают один за другим и поэтому желательно, чтобы вводи- мый текст был линейной цепочкой символов. Именно поэтому в алголе нет понятий «нижний символ» (индекс), «верхний символ» (степень), «над чертой» (делимое), «под чертой» (делитель) и т. д. В частности, алгол не позволяет называть переменные буквами с подстрочными знаками (VK0H, VHa4), но зато разрешает обозначать переменную не обязательно одной буквой. Например, мы можем на- писать VKOH вместо Укои и VHA4 вместо VHa4. Вместо горизонтальной дробной черты используется знак деле- ния /, что позволяет записывать делимое и делитель в одной строке, заключая каждое из них в случае надобности в круглые скобки. Та- ким образом, мы приходим к записи на алголе a:=(WW —V/L4V)// Заметим, что круглые скобки для делимого необходимы, так как без них мы получили бы запись: a.=VKOH— VHA4/t, означающую применение другой формулы: a=V _____^2 В алголе не используется для обозначения деления двоеточие, так как этот знак применяется для других целей. Рассмотрим теперь несколько более сложный пример вычисле- ния двух сторон треугольника, когда заданы третья сторона и си- нусы углов треугольника. В обычном математическом тексте мы на- писали бы __a sin fl _a sin у sin а ’ sin а * На алголе это можно записать так: b:=axsin (beta)/sin (alpha)-, с: =axsin (gamma)/sin (alpha). Язык алгол допускает любые заглавные и строчные буквы ла- тинского и русского языка, но в нем нет греческих букв. Поэтому вместо греческих букв мы написали на алголе соответствующие ком-
64 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. » бинации латинских букв. В обычном математическом тексте нам ясно, что sin х —это значение функции sin от аргумента х, но алгол позволяет трактовать такую запись как обозначение переменной, состоящее из четырех букв: s, i, п, х. Если же мы хотим обозна- чить sin от х, то необходимо заключить аргумент в круглые скобки, написав sin(x). И вообще, в записи на алголе аргументы любой функции заключаются в круглые скобки. По аналогичной причине, нельзя пропускать знак умножения между сомножителями. Например, запись ab воспримется не как произведение, а как название переменной, состоящее из двух букв. Точно так же запись a sin(alpha) была бы воспринята как приме- нение функции под названием asin к аргументу alpha. Поставив между двумя последовательно записанными алголь- ными фразами точку с занятой, мы сообщили, что нужно выпол- нить сначала первое из этих вычислений, а затем второе. Дело в том, что алгол предусматривает последовательное управление, при котором действия выполняются подряд слева направо, а все пере- дачи управления с нарушением линейного порядка действий долж- ны специально оговариваться. Рассмотрим теперь пример вычисления значения i/=max (и, v). В математическом тексте мы написали бы {и, если и > v, V, если и <1 v. На алголе эти вычисления можно записать так: if u>uthen t/:=uelse yt=v Здесь используются служебные слова if (если), then (то) и else (иначе). Эта запись на алголе буквально означает следующее: «Если t£>v, то у присвоить значение и, в противном случае у присвоить значение V». В принципе, можно было бы пользоваться русской версией ал- гола, в которой вместо английских служебных слов употреблялись бы соответствующие русские слова, но это затруднило бы обмен алгоритмами на алголе с зарубежными учеными и вычислительными центрами, а также ввод в. машину. В настоящее время имеется об- ширный международный фонд алгоритмов на алголе, опубликован- ных в книгах, научных журналах или размноженных другими спо- собами. Одно из важных преимуществ языка алгол состоит именно в том, что он широко используется в разных странах в одном и том же варианте, так что отпадает необходимость дополнительных уси- лий по переводу алгоритмов с одной национальной версии алгола на другую. Служебные слова нужно выделять специальным образом, что- бы отличать их от тех слов, которые вводятся авторами алгольных
S.4] НЕФОРМАЛЬНОЕ РАССМОТРЕНИЕ ФРАГМЕНТОВ ПРОГРАММ 65 алгоритмов для обозначения переменных и функций. Для выделе* ния служебных слов в печатных изданиях принято использовать полужирный шрифт. В рукописном тексте служебные слова, под* черкиваются. Рассмотрим еще один пример. Имеется последовательность из п чисел Xi, х2,... ,хп; нужно вычислить их сумму и присвоить полу- ченное значение переменной S. В математике существует стандарт- ная форма записи такого вычисления в виде п S = xi- Однако алгол не предусматривает такой операции суммирования п чисел. Чтобы описать это вычисление средствами алгола, нужно установить четкий порядок выполнения арифметических действий. Мы можем решить, что сначала присвоим переменной S значение О, а потом будем по очереди прибавлять к значению S значения х2, х2, .... хп, получая последовательно 0,0+xi,0+xi-|-xa и т. д. На алголе это записывается так: S:=0; for t: = l step 1 until n do S:=S+x(i] Знак ; разделяет два предложения, являющиеся операторами ал- гола. Во втором из них употреблены четыре служебных слова for (для), step (шаг), until (до) и do (выполнить). Слово for служит для задания циклического вычисления. Управление циклом осущест- вляется в данном случае с помощью переменной i, которая прини- мает последовательно значения от 1 до гас шагом 1. В алголе эле- менты последовательностей вместо х< обозначаются х [il. Квадрат- ными скобками выделен индекс i. Обычно в математических текстах квадратные скобки применяются, как и круглые, для выделения подвыражений в формулах. Язык алгол более скуп в использовании изобразительных средств и проводит четкое разграничение между круглыми скобками и квадратными, которые соответствуют записи индексов внизу строки. Рассматриваемый Текст на алголе подразумевает, что х пред- ставляет собой набор значений и нас интересует i-e из них. (При i=l это будет первое значение из набора х, т. е. хъ при 1=2 возь- мется ха, при i=3 будет взято х3 и т. д.) Если нужно просуммировать 10 первых квадратов натуральных чисел и присвоить переменной у полученное значение, то вместо формулы ю </= 2 k=\ мы пишем на алголе: ^•==0; for Л: = 1 step 1 until 10 do y:—y+k\ 2
S6 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 Этот текст также состоит из двух предложений, разделенных точ- кой с запятой. Запись k f 2 означает возведение значения k в квад- рат. В алгольном тексте вслед за знаком f, обозначающим дейст- вие возведения в степень, пишется показатель степени, который при необходимости может быть заключен в круглые скобки. Напри- мер, запись х f (a+b) означает ха+ь, а запись х f a-\-b означает х“4-&. Рассмотрим еще один пример. Пусть имеется последовательность вещественных чисел, назовем ее у. Нужно найти произведение квад- ратов четных членов этой последовательности. Допустим, что по- следовательность очень велика и нужно прекратить перемножение, не дожидаясь исчерпания последовательности, в тот момент, когда натуральный логарифм произведения станет больше числа 10 (т. е. когда само произведение превысит е10). Рассмотрим следующий алгольный текст, который предлагается в качестве алгоритма ре- шения этой задачи. S: = l; i:=0; for i:=i-|-2 while Zn(S)^10 do S:=Sxt/[i]t2 Этот текст состоит из трех операторов. Последним является опе- ратор цикла с использованием служебного слова while (пока); In является обозначением стандартной функции вычисления натураль- ного логарифма. Произведение накапливается в переменной 3. Сначала 3 при- сваивается значение 1, а затем 3 последовательно умножается на очередные квадраты. Переменная i каждый раз содержит номер очередного элемента последовательности, на квадрат которого мы множим значение 3 в текущий момент. Перед каждым очередным перемножением проверяется верность условия /n(S)^10. В пер- вый же раз, когда это условие оказывается неверным, т. е. когда /n(S)>10, прекращается выполнение оператора цикла. Все приведенные выше фразы языка алгол содержат указания о требуемых вычислениях и состоят из операторов. Их можно срав- нить с повелительными предложениями естественного языка. В рас- сматриваемом примере сначала написаны два оператора присваи- вания, затем следует один большой оператор циклического дейст- вия, называемый оператором цикла. Внутри него есть еще один оператор присваивания S:=SX#li]f2 . выполняемый циклически. Оператор присваивания обеспечивает вычисление некоторого вначения и присваивание этого значения переменной, указанной в левой части. Для этого в правой части оператора присваивания при- ведена формула, по которой нужно вычислить значение. В алголе такие формулы называются выражениями. В простейшем случае
2.51 ОПЕРАНДЫ БТ выражение может быть константой, как в двух первых операторах присваивания из нашего примера. В последнем операторе присваи- вания арифметическое выражение представляет собой более слож- ную формулу. Выражения встречаются и в других операторах из этого примера. Конструкция t:=H-2 после символа for не является оператором присваивания, а представляет собой часть заголовка цикла. Согласно этому заголовку, при каждом новом выполнении цикла переменной I присваивается значение выражения i+2, т. е. она увеличивается на 2. Логическое выражение In (S)^10 указы- вает, при каком условии нужно продолжать цикл. Выполнение цик- ла прекратится, как только это условие окажется невыполненным. Заметим^ что хотя нужны определенные навыки для того, чтобы правильно писать на алголе, но читать и правильно понимать ал- гольный текст может без специальной подготовки человек, знако- мый с обычной математической символикой. 2.5. Операнды Величины, которые встречаются в выражениях, называются операндами. Подобно тому, как объектами машины Тьюринга являются зна- ки, записываемые в ячейках ленты, а объектами нормального алго- ритма служат слова и их части, алгоритмы на алголе имеют дело с операндами, принимающими числовое или логическое значения. Для вычисления новых значений используются выражения, кото- рые строятся из знаков операций, скобок и операндов. Операнда- ми задаются те значения, над которыми могут выполняться опера- ции. Например, в выражении SXt/hl f 2 операндами являются S, и 2. 2.5.1. Классификация операндов. Одним из основных понятий алгола является понятие типа, определяющего свойства операндов. Предусмотрены следующие три типа значений, над которыми можно производить действия: — вещественный, — целый, — логический. Вещественный тип используется в тех случаях, когда допус- каются любые (дробные и целые) числовые значения. Заметим, что при счете на вычислительной машине значения ве- щественного типа могут округляться, а это приводит к тому, что ре- зультаты получаются не абсолютно точными. Целый тип используется при работе с целочисленными значени- ями. Как мы увидим в дальнейшем, при выполнении вычислений
58 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 могут потребоваться специальные действия по преобразованию зна- чений вещественного типа в целый или наоборот. Логический тип используется, если соответствующий операнд должен принимать не числовые, а логические значения true или false. В формулах операнды каждого типа могут встречаться в сле- дующих четырех различных видах: 1) Постоянное одиночное значение. Напри- мер, целое число 0, вещественное число 10.2, логические значения true и false. В примерё алгольного текста S:=l; к=0; for i:=i+2 while In (S)^10 do S:=SXt/[Z]f2 этот вид представлен целыми числами 1, 0, 2, 10. 2) Простая переменная (т. е. переменное одиночное значение). Это переменная, которая может принимать различные значения по ходу выполнения алгоритма. Например, переменная S сначала получает значение 1, а потом последовательно домножает- ся, каждый раз меняя свое значение. 3) Переменная с индексами (т. е. элемент массива). В алголе разрешено определять массивы, т. е. последовательности значений, и работать с элементами этих массивов. В нашем примере y[i\ — это элемент массива у. 4) Вычисляемое значение. Примером вычисляемо- го значения является значение функции Zn(S), которое вычисляет- ся заново при каждом употреблении его в формуле (в отличие от переменных значений S, i, которые вычисляются заново только а соответствующих операторах присваивания или цикла). Заметим, что третий вид подразумевает, что значения элементов массива изменяются. Однако массив с постоянными значениями эле- ментов не выделяется в языке алгол в специальный вид, а рассмат- ривается как частный случай того же третьего вида. Таким образом, каждый операнд может относиться к одному из- трех типов и к одному из четырех видов, т. е. всего возможны 3x4= «=12 вариантов. Тип операнда характеризует принимаемые им зна- чения, а вид операнда характеризует способ организации работы с этим операндом в языке алгол. В частности, вид позволяет судить, - является ли операнд одиночным, или же он должен рассматриваться как элемент упорядоченного массива значений. Кроме того, вид операнда определяет, может ли значение операнда меняться в про- цессе выполнения алгоритма и если да, то нужны ли для этого- специальные операторы перевычисления (при втором и третьем видах), или же операнд вычисляется по ходу дела, при каждом вхождении в формулу (четвертый вид). Возможные операнды арифметических выражений описываются следующей металингвистической формулой:
2.5] ОПЕРАНДЫ 59 (арифметический операнд) ::= (число без знака) ] (простая пе- ременная) | (переменная с индексами) | (функция) | ((ариф- метическое выражение)) Здесь первые четыре альтернативы соответствуют перечисленным выше четырем видам. Последняя альтернатива означает, что опе- рандом может служить арифметическое выражение в скобках. Она не добавляет новых видов и введена формально, чтобы удобно было строить сложные выражения, отвлекаясь от существа тех «кирпи- чей», из которых они составляются. Говоря, что операнд представ- ляет собой арифметическое выражение, мы подразумеваем, что, вычислив данное выражение, мы получим одно значение, которое будет затем использоваться при вычислении. 2.5.2. Числа. На языке алгол можно писать обычные десятичные числа, при- чем вместо десятичной запятой употребляется точка. Впрочем, де- сятичная точка не является особенностью алгола, а соответствует традиционной математической символике на западе. Интуитивное представление о форме написания чисел в алголе дают следующие примеры: О 177 .5374 (т. е. допускаются числа без целой части). Число может начинаться со знака — или +: —200.084 +177 В начале числа может быть один или несколько нулей: +0.731 —00801.304 .00073 Чтобы не писать много нулей в начале или в конце целого чис- ла, можно указывать десятичный порядок. Например, можно на- писать 1.2ю — 4 вместо записи 0.00012 или 73.03ю8 вместо записи 7303000000 Для сокращения разрешается писать один порядок, например, 103 вместо записи 1юЗ, что означает 1000. Аналогично ю—4 озна- чает то же, что и 0.0001, а —ю—2 означает —0.01 Мы имеем теперь некоторое интуитивное представление о поня- тии числа в алголе. Однако это понятие еще не вполне определи- лось. В частности, из сказанного выше не ясно, можно ли писать точку в конце числа: 101. ? Чтобы исчерпывающе определить понятие числа, рассмотрим соответствующие металингвистические формулы. Для лучшей обо- зримости будет введено шесть промежуточных понятий. а) (целое без знака) ::= (цифра) | (целое без знака) (цифра). Согласно этой формуле, целое без знака — это последователь- ность цифр. Примерами могут служить
60 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 1 23 0157 б) (правильная дробь) :: = .(целое без знака) Примеры правильной дроби: .17 .0031 в) (десятичное число):: = (целое без знака) | (правильная дробь) | (целое без знака) (правильная дробь) Примеры десятичного числа: 1 0 173 .1 .03 13.771 01.10 Десятичное число — это промежуточное понятие; в отличие от произвольного числа, оно не может содержать порядка и знака. г) (целое) ::= (целое без знака) | + (целое без знака) | — (целое без знака) Примеры целого: 1 +1 23 —23 д) (порядок) ::=10(целое) Примеры порядка : 1в01 10+3 щ—371 е) (число без знака) ::= (десятичное число) | (порядок) | (деся- тичное число) (порядок) Примеры числа без знака: 177 10—4 9.34]08 Заметим, что порядок числа без знака может содержать знак. Теперь мы окончательно определим понятие числа: (число) ::=(число без знака) | +(число без знака) | — (число без знака) Из этого определения, в частности, следует, что нельзя писать на алголе такие числа: 101. 12.10—2 2ю3.1 Например комбинацию 101. никак нельзя вывести из наших ме- талингвистических формул, потому что точка может попасть в кон- струкцию числа только через конструкцию правильной дроби, а там после точки обязательно должна быть хотя бы одна цифра. Комбинация 2103.1 не годится потому, что после знака ю должно быть только целое число, представляющее порядок. Итак, мы оп- рёделили синтаксис чисел в алголе. Семантика понятия числа состоит в следующем. Числа служат для изображения постоянных арифметических значений. В соответствии с классификацией типов, рассмотренной в разделе 2.5.1, «целые» числа представляют значения целого типа; все осталь- ные числа в алголе изображают вещественные значения. Например, число ю5 имеет не целый, а вещественный тип, тогда как числу 100000 соответствует целый тип. Соответственно этому, при машин- ной реализации алгола число 100000 будет обязательно представ- лено точно, тогда как ю5 может быть представлено приближенно.
2.5J ОПЕРАНДЫ < 2.5.3. Переменные. В алголе переменная — это одиночное переменное значение или элемент массива. Изображение переменной не зависит от типа зна- чений и не показывает его. Переменные (и некоторые другие син- таксические единицы) изображаются с помощью идентификаторов. Вместо термина «[идентификатор» можно было бы употреблять <имя» или «название». Идентификатор — это то, что идентифицирует, т. е. выделяет объект из других.сходных объектов. Синтаксически идентификатор представляет собой про- извольную последовательность букв и цифр, начинающуюся с буквы: (идентификатор) ::=(буква) | (идентификатор) (буква) | (иденти- фикатор) (цифра) Примеры идентификаторов: q х ml if ВЕС sin МАША vlla Семантика. Идентификатор служит для идентификации объектов языка. Заметим, что для облегчения чтения текст на языке алгол может содержать между символами пробелы, которые не влияют на вы- полнение алгоритма. Поэтому можно написать идентификатор из нескольких слов, например, случайное число ШАГ по х Переменные подразделяются на простые переменные и перемен- ные с индексами: (переменная) ::== (простая переменная) | (переменная с индексами) Простые переменные служат для идентификации одиночных зна- чений, а переменные с индексами для идентификации элементов массивов переменных. Они различаются и синтаксически. Простая переменная описывается формулой: (простая переменная) ::= (идентификатор) Другими словами, любой идентификатор может быть использо- ван для обозначения простой переменной. (Как правило, разные объекты должны обозначаться разными идентификаторами.) Переменные с индексами используются в тех случаях, когда не- обходимо выполнить некую циклическую работу, проводя однооб- разные вычисления с наборами однотипных величин. Например, если нужно вычислить скалярное произведение двух векторов, в каждом из которых 6 координат, то можно было бы обозначить все эти координаты разными буквами и записать оператор присваи- вания с использованием простых переменных: х:=а х g-H X Л-Н X H-d X/+е X Л+f X / Однако этот способ имеет два недостатка. Во-первых, он требует Долгой и утомительной работы по написанию (особенно, если ела-
«2 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 (Гл. 2 гаемых не 6, а, например, 60). Во-вторых, таким способом нельзя задать алгоритм вычисления скалярного произведения векторов произвольной длины. Поэтому необходимо иметь в алгоритмичес- ком языке возможность употребления последовательности значе- ний. В нашем случае удобно ввести в рассмотрение для каждого вектора массив его элементов, имеющий свое имя. Например, для первого вектора можно рассмотреть массив Лив нем величины (элементы) Als Л2, . . ., Лв, а для второго вектора — соответствен- но массив В ив нем величины Ви Вг.Вв. В алголе эти величи- ны выглядят так: Л'[1], А [21.........................Л[61, В[1 ], В[2], . . ., В[6]. Можно выделить в нашем вычислении общую часть, которая многократно выполняется. Это добавление к общей сумме очередного произве- дения i-x элементов двух векторов. Тогда наш алгоритм может быть построен из двух операторов: занесения нуля в качестве на- чального значения суммы х:=0; и очередного добавления произведения x:=x-Mli]XB[il причем второй оператор нужно выполнить в цикле при t=l,..., 6. В общем случае синтаксис переменной с индексами опи- сывается следующими формулами: (переменная с индексами) ::= (идентификатор) [(список индек- сов)] (список индексов) ::= (индекс) | (список индексов), (индекс) (индекс) ::=(арифметическое выражение) Примеры переменных с индексами: Q[7,2] ali] cli] d[i, j] temp[A+\,t f 2,01 В алголе массивы состоят из переменных значений, которые могут (но не обязаны) изменяться в процессе счета. Эти значения назы- ваются элементами массива. Для идентификации элемента употреб- ляется имя массива перед квадратными скобками и список индек- сов элемента внутри скобок. Программист сам выбирает способ ну- мерации элементов. Точнее говоря, элементы по каждому измерению нумеруются подряд, но номер первого элемента выбирается произ- вольно. В определенном месте алгол-программы должно быть опи- сание каждого используемого массива. Описание массива содержит идентификатор этого массива и информацию о границах изменения индексов элементов. Если в списке индексов только один индекс, то соответствующая структура даннйх представляет собой одно- мерный массив, т. е. перенумерованное множество значений. Например, если в описании указано all: 100], то массив а со- держит 100 элементов all], a[2], . . ., allOOl. Если же указано 2>[0: 99], то массив b содержит тоже 100 элементов, но они нуме- руются от нуля: МО], Ml], . . ., 6[99]. При указании с[—7, 82] мае-
2 5Г ОПЕРАНДЫ ' 6Э сив с состоит из 90 элементов d—7], с [—6], . . d—1], d0], dl), • • d82]. Иногда постановка задачи предполагает более сложную струк- туру данных в виде матрицы, где возможен перебор в различных направлениях (по строкам или столбцам). Чтобы было удобно ре- шать такие задачи более сложного перебора, в алголе разрешено наряду с одномерными массивами пользоваться и многомерными (размерности 2,3 и т. д.). Например, если описание массива содер- жит указание dll : 10, 1 : 10], то в этом массиве содержатся эле- менты dll,1], dll,2], ..., dll,10], d[2,l], ...,d[2,10], ..., dllO.l], ... . . ., dllO, 10] и мы имеем дело с матрицей, в которой 10 строк и 101 столбцов. Задавая переменную dli, /], мы тем самым обращаемся к. элементу матрицы d на пересечении i-й строки и /-го столбца. Можно описать прямоугольную матрицу, например dl : 2, 1 : 50] или'/[1 : 2, —3 : 46]. В обеих этих матрицах по две строки и по пятьдесят столбцов, но столбцы нумеруются в них по-разному (от 1 или от —3). При указании §10 : 1, 1:2, 1 : 25] трехмерный массив g содержит тоже 100 элементов и состоит из двух матриц, (с номерами 0 и 1), в каждой из которых по две строки и по 25 столб- цов. Элементы массива g будут идентифицироваться тройками ин- дексов, т. е. арифметических выражений. Например, §11,1,17] — это элемент № 17 строки № 1 матрицы № 1 из массива g. (Числа являются частным случаем арифметических выражений и поэтому могут употребляться в качестве индексов.) Семантика. Переменные с индексами служат для обозна- чения элементов массивов переменных значений. Идентификатор' такой переменной обозначает массив, а индексы служат для иден- тификации элементов внутри массива. Число индексов должно со- ответствовать размерности массива, а значения индексов должны быть заключены между левой и правой границами индекса из соот- ветствующего описания (и могут равняться этим границам). Значение индекса может получаться не целым. В таком случае выбирается ближайшее к нему целое число. Точнее говоря, вычис- ляется целая часть от / +0.5, где / — вычисленное значение ариф- метического выражения, указанного в качестве индекса. Например, при i=2.5 переменная xli] обозначает элемент х[3]. 2.5.4. Операнды-функции. В выражение могут входить функции, которые представляют собой вычисляемые операнды. Примерами функций могут служить sin(x) cos(a+b\2) f(x, г/+1) Здесь sin, cos, f — идентификаторы функций; содержимое скобок, т. е. х, a+b f 2 и х, §+1 — это списки параметров, называемых фак- тическими параметрами. Для каждой функции должен быть задан способ ее вычисления. Для этого служат описания процедур, которые снабжаются теми же
64 ' АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл.» идентификаторами и включаются в ту же программу на алголе. Когда в процессе выполнения программы встречается операнд- функция, нужно найти описание процедуры с тем же именем и вы- полнить вычисления в соответствии с этим описанием, используя значения заданных фактических параметров. Можно многократно ссылаться в программе на одно и то же описание процедуры. В прин- ципе, можно было бы обойтись без функций, если всякий раз запи- сывать в программе нужную последовательность действий, однако применение функций обычно позволяет существенно сократить текст программы и сэкономить усилия, затрачиваемые на ее изго- товление. Не для всякой функции необходимо задавать алгоритм, описы- вая процедуру ее вычисления. Имеется общая договоренность о некоторых стандартных функциях, которым соответствуют стан- дартные идентификаторы abs, sqrt, sin, cos, arctg, In, exp, sign К этому обязательному списку обычно добавляют еще функции entier, tg, cig, arcsin, arccos и некоторые другие. Функция abs(x) — абсолютное значение (модуль) величины х, sqrt(x) — квадратный корень из х, 1п(х) — натуральный лога- рифм значения х, ехр(х)=ех при основании е=2,71828 . . ., 1 при х > О, sign(x) =< О при х = 0, —1 при х < 0. Параметры этих стандартных функций могут быть целого или вещественного типа. Для функции sqrt(x) параметр х должен удов- летворять отношению х^О, а для 1п(х) — отношению х>0. Для функций arcsin(x) и arccos(x) нужно, чтрбы выполнялось отношение |х|<1. Для остальных функций допускаются любые значения пара- метра. Функция sign(x) принимает значения целого типа. Остальные перечисленные стандартные функции дают значения вещественного типа. Синтаксис функций задается следующими формулами: (функция) ::= (идентификатор) (совокупность фактических па- раметров) (совокупность фактических параметров) ::= (пусто) | ((список фактических параметров)) (пусто) ::= (1) (список фактических параметров) ::= (фактический параметр)!
2.6] АРИФМЕТИЧЕСКИЕ И ЛОГИЧЕСКИЕ ВЫРАЖЕНИЯ 65 (список фактических параметров) (ограничитель параметра)- (фактический параметр) (ограничитель параметра) ::=.|) (строка букв) :( (2) (фактический параметр) ::= (идентификатор) | (выражение) | (строка) (строка букв) (буква) | (строка букв) (буква) Таким образом, примерами функций являются ctg(a+b) Случайное число Скалпр(А, В) Нам потребовалось ввести, помимо понятия списка, понятие совокупности параметров, так как возможны функции без парамет- ров, например, очередное случайное число. Отсутствие парамет- ров обозначается в этом синтаксисе термином (пусто), который определяется формулой (1), не содержащей никаких знаков справа от знака Если нет параметров, то не нужно и скобок после идентификатора. Встретив оператор У‘ =f +1 мы ищем описание для f и, если оказывается, что это описание про- цедуры, то выполняем вычисления по этому описанию, берем резуль- тат, прибавляем к нему единицу и присваиваем значение получен- ной суммы переменной у. Согласно металингвистической формуле (2), разрешено отделять параметры в списке не только запятой, но и конструкцией, в кото- рой при помощи слов для наглядности поясняется, что означает очередной параметр. Таким образом, например, конструкция ) температура' ( эквивалентна запятой, и поэтому можно функцию F{x, у, t, р) записать в таком виде - F(x, у) температура: (0 давление: (р) Эти две записи совершенно эквивалентны, причем закрывающая скобка после у не завершает список параметров. Такие конструк- ции между параметрами не влияют на выполнение алгоритма и употребляются только для повышения наглядности программы. Строка — это любая комбинация символов из алфавита алго- ла, заключенная в кавычки. Например, для функции код (,/’) па- раметром является строка В наших металингвистических формулах осталось одно еще не определенное формально понятие (выражение) (см. предпослед- нюю формулу). Это понятие будет определено позднее, в разд. 2.7.3. 2.6. Арифметические и логические выражения 2.6.1. Простые арифметические выражения. Вычисления над операндами задаются с помощью выражений. Операнды объединяются в выражения при помощи знаков операций и скобок. Простейший случай выражения — это один операнд, ®- 3. Люб имений и др.
66 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 - например: а 10 ml xti+11 sin (a+b) (a—x 12+3) Последнее из этих выражений-операндов получено взятием некото- рого выражения в скобки. В арифметических выражениях употребляются следующие опе- рации (знаки операций): (операция) ::=+ |—|X|/|-i-| f Чтобы облегчить поэтапное понимание семантики арифметичес- ких выражений, авторы алгола разделили операции на ранги в - соответствии с общепринятыми правилами: (операция 1-го ранга) ::= f (операция 2-го ранга) ::=Х |/| 4- (операция 3-го ранга) ::=+ | — Выражения строятся по рангам нарастающей сложности: (арифметическое выражение 0-го ранга) ::=(арифметический операнд) (арифметическое выражение 1-го ранга) ::=(арифметическое выражение 0-го ранга) | (арифметическое выражение 1-го ранга) (операция 1-го ранга) (арифметическое выражение 0-го ранга) Понятие (арифметический операнд) определено металингви- стической формулой в разд. 2.5.1. Примеры арифметических выражений 1-го ранга: а 3.0 (c+d+e) xt«/t 12 f(x)txf0.2 2f«/[i] Первые три примера — это операнды, т. е. выражения 0-го ранга, а следовательно и выражения 1-го ранга. (Напомним, что любое арифметическое выражение в круглых скобках — операнд.) Вооб- ще же, арифметическое выражение 1-го ранга — это набор операн- дов, задающий последовательные возведения в степень. Эти возве- дения производятся слева направо. В частности, a f b f с=(аь)е, а не аьС. Если мы хотим записать на алголе аьС, то изображаем это так: a f (b | с). В алголе всегда все операции одного ранга выполняются строго слева направо. Арифметические выражения 2-го ранга определяются так: (арифметическое выражение 2-го ранга) ::= (арифметическое выражение 1-го ранга) | (арифметическое выражение 2-го ранга) (операция 2-го ранга) (арифметическое выражение 1-го ранга) Например, выражениями 2-го ранга являются а 3.0 xf у\ 12 ах2 ахЗ/с
2.61 АРИФМЕТИЧЕСКИЕ И ЛОГИЧЕСКИЕ ВЫРАЖЕНИЯ 67 При этом, поскольку операции одного ранга выполняются слева направо, то а1Ь1С^с Из выражений 2-го ранга строятся арифметические выражения 3-го ранга: (арифметическое выражение 3-го ранга): := (арифметическое вы- ражение 2-го ранга) | (арифметическое выражение 3-го ранга) (операция 3-го ранга) (арифметическое выражение 2-го ранга) | (операция 3-го ранга) (арифметическое выра- жение 2-го ранга) Таким образом, арифметическое выражение 3-го ранга состоит из арифметических выражений 2-го ранга, разделенных операция- ми 3-го ранга, причем перед первым (или единственным) выраже- нием 2-го ранга может стоять знак + или —. Примеры арифметических выражений 3-го ранга: —3 а+Ь —(а-Н>)Хс Сложная конструкция такого определения арифметических вы- ражений оправдана тем, что последовательно разбирая синтаксис выражений различных рангов, начиная с нулевого, мы тем самым устанавливаем порядок выполнения действий. В частности, сначала вычисляются все выражения 0-го ранга (операнды), затем выраже- ния 1-го ранга, потом 2-го ранга и, наконец, 3-го ранга. Например, рассматривая текст и—y+omega X sum f cos (y+z X 3)/7.310—2 f Ы»4-2.8] f (a—3/y+ vu f 8) мы сначала выделяем в нем операнды и у omega sum cos(y+z*3) 7.3i0—2 tt»[i+2.8] (a—3/y+vu f 8) затем выделяем арифметические выражения 1-го ранга и у omega sum\cos(y-\-z*3) 7.310—2 f w[i+2.8] f (a—3/y+ vu f 8) потом арифметические выражения 2-го ранга и у omega*, sum ] cos (у+г*3)/7.31в—2 f ш[г-|-2.81 f (a—3/y+ vu f 8) и, наконец, приходим к выводу, что исходный текст является ариф- метическим выражением 3-го ранга, так как состоит из выражений 2-го ранга, разделенных операциями 3-го ранга. Аналогично можно разобрать арифметические выражения, со- держащиеся в круглых и квадратных скобках. Теперь определим понятие простого арифметического выраже- «ия, объединяющее все структуры выражений, рассмотренные в этом разделе: (простое арифметическое выражение): := (арифметическое вы- ражение 3-го ранга) 3*
68 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 Семантика. Простое арифметическое выражение служит для вычисления значений (целых и вещественных). При этом сна- чала вычисляются значения всех операндов (слева направо), затем выполняются все операции 1-го ранга (слева направо) и т. д. Если - операнд — это заключенное в скобки выражение, то оно вычисля- ется аналогичным способом. Тип выражения (целый или вещественный) определяется типом операндов и операциями, которые над ними выполняются. Тип опе- ранда определяется либо его написанием (например, константа 9 имеет целый тип, а константы 11.7 или 9.0 имеют вещественный тип), либо его описанием. Так, тип выражений а+b, а—b, aXb зависит от того, какие типы указаны в описаниях переменных а и Ь. Если оба типа целые, то выражение тоже имеет целый тип. Если же хотя бы один из них вещественный, то тип всего выражения вещест- венный. Выражение alb всегда имеет вещественный тип, независи- мо от того, какие типы имеют операнды а и Ь. Операция 4- опреде- лена только для того случая, когда оба операнда имеют целый тип и дает результат тоже целого типа. Операция | возведения в степень характеризуется следующей зависимостью типа результата от типов операндов. Если значение b имеет целый тип, причем К>0, то a f b имеет тот же тип, что и а. В остальных случаях a f b имеет вещественный тип. Простые арифметические выражения употребляются в операто- рах присваивания, в индексных выражениях, в качестве фактиче- ских параметров и в других местах алгольного текста. 2.6.2. Простые логические выражения. Часто бывает нужно организовать выбор нужных формул в за- висимости от тех или иных условий. Например, для В граммов воды при температуре t объем V опре- деляется следующим образом: ' B/dj при t О, V = B/dt при t > 100, B/dw в остальных случаях. Здесь dt — удельный вес льда, dw — удельный вес пара, ds — удельный вес (жидкой) воды. Условия /^0 и £>100 представляют собой высказывания, о ко- торых можно утверждать, что они истинны или ложны (в зависи- мости от значения переменной t на данном этапе вычислений). На алголе вычисление объема воды можно записать так: V:=if fcCO then B/di else if £>100 then B/ds else B/dw Такая запись указывает, какие условия нужно проверять и что нужно делать в зависимости от их выполнения. Между if («если») и then («то») заключаются логические выражения, которые при-
- 2.6) АРИФМЕТИЧЕСКИЕ И ЛОГИЧЕСКИЕ ВЫРАЖЕНИЯ 69 нимают значения true («истина») или false («ложь»). Обычно их опе- рандами являются отношения. Два простых арифметических выражения, соединенные знаком операции отношения, образуют отношение, которое принимает зна- чение true или false. (отношение) ::= (простое арифметическое выражение) (знак операции отношения) (простое арифметическое выражение) (знак операции отношения) : :=<|^|=|^|>|:т^ В рассмотренном нами примере логические выражения /^0 и />100 представляли собой отношения. Другим примером отноше- ния является выражение а>Ь9 которое принимает значение, true, если значение а больше значения Ь, и значение false, если а^Ь. Значение отношения определяется по значениям простых арифме- тически^^выражений, записанных слева и справа от знака опера- ции отношения. Эти выражения вычисляются, а затем проверяется выполнение отношения между их значениями. Например, если для отношения а+1 —sin(alpha) левая часть принимает значение 1, а правая часть равна нулю, то проверяется отношение 1^0, и его значение false рассматривается как значение отношения а+1^—sin(alpha). Условия для выбора варианта счета не всегда определяются од- ним отношением. Отношения могут комбинироваться при помощи знаков логических операций в логические выражения, подобно тому как арифметические выражения комбинируются из операндов и знаков арифметических операций. Всего в языке алгол разрешается использовать пять логических операций, каждая из которых имеет свой ранг старшинства. Самой старшей является операция отрицания «не» со знаком —i. Далее в порядке убывания старшинства следует конъюнкция (со знаком Д), дизъюнкция (со знаком V), операция следования (со знаком о) и операция эквивалентности (со знаком =). Операция «не» приме- няется к одному логическому операнду, а остальные логические операции выполняются над двумя операндами. Каждая логическая операция вырабатывает результат со значением true или false По соответствующему ей правилу в зависимости от значений (true или false) ее операндов. Обозначим первый (или единственный) операнд через 4, а второй операнд через В. Тогда правила выполнения ло- гических операций можно задать таблицей 2.2. Операция —14 читается «не А». Операции А/\В, А\/В, AzdB и А=В читаются соответственно «4 и В», «4 или В», «из 4 следует В», «4 эквивалентно В». Операция “14 вырабатывает значение, противоположное зна- чению своего операнда. Например, логическое выражение “if<0
70 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 Таблица 2.2 Значения операндов Результаты операции А в “М A A В A V В А В A = В true false true true false true true true true true true false false true false false false true False true true false false false false false true true будет истинным, когда конкретное значение t есть неотрицательное число. Операция А/\В вырабатывает значение true только в том слу- чае, если и Л, и В имеют значение true. Например, логическое выра- жение х=0А*+у>1 будет истинным, если х=0 и #>1. Операция A\JВ вырабатывает значение true, если или А или В (хотя бы один из этих операндов) имеет значение true. Например, логическое выражение x=0Vf/=0 будет истинным, если хотя бы од- но из значений х и у (или оба они) равно нулю. Операция Лг)В вырабатывает значение true, если верно хотя бы одно из двух условий: В имеет значение true или А имеет значение false. Например, логическое выражение x>lot/<5 истинно, если у<5 или если х^1. Наконец, операция Л=В вырабатывает значение true, если А и В имеют одинаковые значения (оба true или оба false). Например, логическое выражение x>l=z/<5 истинно, если значения х и у удовлетворяют одному из условий: а) х>1 и у<5; б) х^1 и f/^5. В любом другом случае (например, при х—2 и г/=6) это выражение будет ложным. Кроме отношений в логическое выражение могут входить в ка- честве операндов постоянные значения true или false (как числа в арифметических выражениях) и переменные, имеющие логические значения. (Например, в результате выполнения оператора р\=а>Ь переменной р присваивается логическое значение, которое может быть использовано в логическом выражении рA#>0Vq-) Операн- дами логических выражений могут быть также функции, принимаю- щие логические значения, и логические выражения, заключенные в скобки. Итак:
2.61 АРИФМЕТИЧЕСКИЕ И ЛОГИЧЕСКИЕ ВЫРАЖЕНИЯ 71 (логический операнд) : := (отношение) | (логическое постоян- ное значение) | (переменная) | (функция) | ((логическое выражение)) (логическое постоянное значение): :=true | false Логические переменные и функции имеют такой же вид, как арифметические, но принимают другие (логические) значения. Аналогично синтаксису арифметических выражений, синтаксис логических выражений описывается металингвистическими форму- лами, в которых вводятся ранги операций и выражений: (логическая операция 1-го ранга): :=~“1 (логическая операция 2-го ранга): : = Л (логическая операция 3-го ранга): : = V (логическая операция 4-го ранга): :==z) (логическая операция 5-го ранга): :=н= (логическое выражение 0-го ранга) : := (логический операнд) (логическое выражение 1-го ранга) : := (логическое выражение 0-го ранга) ((логическая операция 1-го ранга) (логическое выражение 0-го ранга) Например, а>Ь — логическое выражение 0-го ранга, поэтому —\а>Ь — логическое выражение 1-го ранга. Однако —v~}a>b не является логическим выражением 1-го ранга (и вообще это не логи- ческое выражение), так как знак —i можно ставить только перед выражением 0-го ранга, но не 1-го ранга. Если же заключить выра- жение в скобки, то i(~irC>b) становится логическим выраже- нием 1-го ранга, так как любое логическое выражение в скобках — это операнд, а следовательно, логическое выражение 0-го ранга. Остальные синтаксические формулы: (логическое выражение 2-го ранга) ::= (логическое выраже- ние 1-го ранга) | (логическое выражение 2-го ранга) (ло- гическая операция 2-го ранга) (логическое выражение 1-го ранга*) (логическое выражение 3-го ранга) ::= (логическое выраже- ние 2-го ранга) | (логическое выражение 3-го ранга) (ло- гическая операция 3-го ранга) (логическое выражение 2-го ранга) (логическое выражение 4-го ранга) : := (логическое выраже- ние 3-го ранга) | (логическое выражение 4-го ранга) (ло- гическая операция 4-го ранга) (логическое выражение 3-го ранга) (логическое выражение 5-го ранга) : := (логическое выражение 4-го ранга) ((логическое выражение 5-го ранга) (логическая операция 5-го ранга) (логическое выражение 4-го ранга) (простое логическое выражение) ::= (логическое выражение 5-го ранга)
72 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 Заметим, что формула для логического выражения 1-го ранга имеет особенность: в ней употребляется не двуместная, а одномест- ная (т. е. применяемая к одному операнду) операция. Аналогичную особенность имела формула для арифметического выражения 3-го ранга, поскольку в ней плюс и минус могли употребляться как одноместные операции. Однако в синтаксисе арифметических выра- жений эта формула с особенностью была не в начале, а в конце, т. е. допускался знак плюс или минус перед арифметическим выра- жением 2-го ранга. Для логических же выражений особенность воз- никает в начале синтаксиса; поэтому знак отрицания может стоять перед любым операндом. Например, если мы хотим образовать отри- цание логического выражения 2-го ранга, а/\Ь/\с, то нельзя поста- вить знак отрицания перед этим выражением, так как тогда отрица- ние будет отнесено к а, поскольку ранг операции выше всех. Од- нако можно превратить это выражение в операнд, взяв его в скобки, после чего допустимо отрицание: —у(а/\Ь/\с). Примеры простых логических выраже- ний: х——2 — отношение: два простых арифметических выражения, соединенные знаком отношения; y>v\/z>q — два отношения соединены операцией логического сложения V; а-\-Ь>—5ДгДс|2>3— логическое выражение 2-го ранга с тремя операндами, из которых два — отношения и один — про- стая переменная г; §г=—шДбД—\c\/d\J— в этом логическом выражении все -переменные должны иметь логические значения; х:=г<СО — это оператор присваивания, который вычисляет зна- чение логического выражения z<0 и присваивает это значение пе- ременной х логического типа. Семантика. Простое логическое выражение служит для задания правил вычисления логического значения. Для вычисления простого логического выражения сначала определяются значения всех логических операндов, потом выполняются слева направо все операции 0-го ранга, затем слева направо все операции 1-го ранга и так далее. Получаемое в результате значение называется значением простого логического выражения. Простые логические выражения употребляются в операторах присваивания, в условиях, а также в качестве фактических параметров. 2.6.3. Условные выражения. Условные выражения позволяют осуществлять выбор одной из нескольких формул счета. Например, условное выражение в правой части оператора присваивания x:=if a>b then а—b else a+b
2.61 АРИФМЕТИЧЕСКИЕ И ЛОГИЧЕСКИЕ ВЫРАЖЕНИЯ 73 ' обеспечивает вычисление значения переменной х либо по формуле а—Ь, либо по формуле а+b, в зависимости от логического значе- ния отношения а>Ь. Другие примеры условных выражений: a) if c^d/\x then a f 2 else 0.1 (Здесь x — логическая переменная.) б) if а>1 then х\/у else true Это условное выражение является логическим. Оно может быть использовано в условии другого выражения, например: в) if if й>1 then х\/у else true then 0 else 1 Итак, условные выражения образуются при помощи служебных слов if (если), then (то), else (иначе). Любое условное выражение начинается с условия, которое определяется следующей формулой: (условие) : :=if (логическое выражение) then Примеры условий: if a>b then if true then if xVa>l then if if x then у else z then Синтаксис произвольных (и в частности, условных) ариф- метических и логических выражений задается следующими форму- лами: (арифметическое выражение) : := (простое арифметическое вы- ражение) | (условие) (простое арифметическое выраже- ние) else (арифметическое выражение) ’ (логическое выражение) : := (простое логическое выражение) | (условие) (простое логическое выражение) else (логиче- ское выражение) Согласно этим формулам, между then и else всегда должно быть простое (арифметическое или логическое) выражение, т. е. выраже- ние без условий. Точнее говоря, условия допускаются, но содер- жащие их условные выражения должны быть заключены в скобки. За словом else может следовать любое (т. е. простое или условное) выражение. Семантика. Вычисление выражения, содержащего усло- вие, сводится к вычислению одного из входящих в него простых вы- ражений следующим (рекурсивным) образом: Вычисляется значение логического выражения, содержащегося в условии. Если оно true (истина), то значением всего выражения является значение (простого) выражения, написанного между then и else. Если же это значение false, то значением всего выражения яв- ляется значение того выражения, которое написано после else. Иначе говоря, условное выражение разбивается на две части, и дело
74 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [IX 2 сводится к тому, что нужно вычислять первую или вторую часть. Если она оказывается простым выражением, то вычисляется по правилам, описанным в разд. 2.6.1 и 2.6.2. Если же это выражение тоже содержит условие, то оно опять разбивается на две части, и в зависимости от истинности условия выбирается одна из этих час- тей и т. д. Рассмотрим некоторые примеры условных выражений. a) if x<Zy then х else у Это арифметическое выражение, определяющее вычисление min (х, у). Оно содержит условие х<у (так мы сокращенно обозна- чаем условие if x<Zy then). Если х<у, то нужно взять значение х, а если х^>у, то нужно взять значение у. б) if а<0 then A/В else if fe=0 then В/A else z Здесь проверяется условие a<0. Если оно выполнено, то зна- чение всего выражения равно А/В. Если же о>0, то следует перейти к рассмотрению выражения после else, которое оказывается услов- ным. Аналогично рассматривается новое условие: если fe=0, то нужно вычислять В/А, если нет, то значение всего выражения рав- но г. в) if q then п—1 else п В этом примере условием является переменная у, которая может принимать значение true или false. Если она true, то значением все- го выражения является значение п—1, а если она false, то—зна- чение п. г) if k<l then s>w else h^c Это логическое выражение, принимающее значение одного из отношений s>w или h^c. Если &<1, берется значение отношения s>w, в противном случае берется значение отношения h^Zc. д) if if if a then b else c then d else f then g else h<k Это выражение также является логическим. Оно принимает значение логической переменной g или отношения h<Zk в зависи- мости от истинности условия (обозначим его через р), которое, в свою очередь, принимает значение логической переменной d или f в зависимости от истинности содержащегося в нем условия if a then feelsec. Последнее условие мы опишем логической формулой р1 = =aA&V"n^A^« Соответственно полное условие, проверяемое при вычислении данного выражения, описывается формулой р=р1А dV~iplAA Итак, значение выражения есть g, если (а/\Ь\/—]а/\с)/\ dV^aA^V—iaA^AA и h<Zk в противном случае. Например, если a=fe=d=true, то значение выражения есть g, так как полное проверяемое условие в этом случае истинно.
2.6] АРИФМЕТИЧЕСКИЕ И ЛОГИЧЕСКИЕ ВЫРАЖЕНИЯ 75 Не всегда по виду выражения можно с уверенностью заключить, является ли оно арифметическим или логическим. Например, в вы- ражении if a then с else d переменная а обязательно должна быть логической, а тип перемен- ных с и d не известен. Если они логические, то и все выражение ло- гическое; если они арифметические, то все выражение тоже явля- ется арифметическим. Однако переменные с. и d не могут быть раз- ных типов, поскольку рассматриваемое выражение должно соот- ветствовать либо формуле (условие) (простое арифметическое выражение else (ариф- метическое выражение) либо формуле (условие) (простое логическое выражение) else (логическое выражение) Приведем еще несколько примеров и продемонстрируем разно- образие выразительных средств языка, позволяющих варьировать способ описания алгоритма. Пусть нужно произвести вычисление | а+b при а>6 и c>d, x~\a—b в противном случае. Один из способов реализации этого вычисления основывается на том, что выбор в зависимости от условия можно производить не толь- ко между частными формулами, но и между целыми операторами: if a>b/\Od then x:=a+b else x:=a—b (Такие условные операторы мы будем рассматривать в разд. 2.8.) Приведем еще три способа того же вычисления с использованием условных выражений х: = if a>b/'\c>d then a+b else a—b x:=a+(if a>b/\c>d then b else—b) 1 x:= if a>b then(if c~>d then a+b else a—b) else a—b Последний способ записи внешне выглядит громоздким, но в случае а^Ь он обеспечит наиболее быстрое вычисление, так как при этом не потребуется проверять второе условие. (Заметим, что здесь условное выражение берется в круглые скобки и тем самым<стано- вится операндом.) Пусть требуется вычислить (а+хг при а>0, + при а^О. Иначе говоря, к значению а прибавляется в зависимости от знака числа а либо i-й, либо (i-H)-fl элемент массива х. Тогда возможны.
76 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 например, такие способы записи: у.= if а>0 then a+x[i] else a+x[i+l] z/:=a+(if aX) then x[i] else x[i+1 ]) y:=a+xtit aX) then i else i+ll z/:=a-i-x[t'+(if a>0 then 0 else 1)1 В последних двух примерах проверка условия осуществляется при вычислении самого индекса. 2.7. Основные операторы С участием выражений составляются алгольные операторы — повелительные фразы для исполнителя алгоритма. Эти операторы подразделяются на простые (называемые основными) и сложные, представляющие собой композиции простых. Предусмотрены четыре вида основных операторов. (основной оператор) : := (оператор присваивания) | (оператор перехода) | (оператор процедуры) | (пустой оператор) Понятие пустого оператора не имеет самостоятельного значе- ния; оно вводится для удобства описания более сложных синтакси- ческих конструкций, означая, что на том- месте, где согласно мета- лингвистической формуле должен находиться (оператор), может в частном случае ничего не быть. 2.7.1. Оператор присваивания. Операторы присваивания служат для присваивания значений переменным. Оператор присваивания состоит из двух частей, раз- деленных знаком присваиваниям (оператор присваивания) ::= (список левой части):= (ариф- метическое выражение) | (список левой части):= (логиче- ское выражение) В списке левой части указывается одна или несколько переменных, которым присваивается значение, а правая часть содержит арифмети- ческое или логическое выражение для вычисления этого значения. Ниже приводятся примеры операторов присваивания с одной пе- ременной в левой части d-.—aXb e-.=c+f(d) a:=if 6>0 then a-H else a x '.—a~>b f :=(xa=£) V(xs=true) g[3xt+2] :=xV“УУ В первых трех операторах вычисляется и присваивается пере- менной числовое значение, а в последующих трех — логическое зна-
2.7 J ОСНОВНЫЕ ОПЕРАТОРЫ 77 чение. Если список левой части содержит несколько переменных, они разделяются таким же знаком присваивания (список левой части): := (переменная) | (список левой части) := (переменная) Например, d :=е :=aXb. Семантика. Оператор присваивания выполняется следую- щим образом. Сначала вычисляются значения всех индексных выра- жений, имеющихся в левой части, в порядке слева направо. Потом вычисляется значение выражения, представляющего собой правую часть. А затем это значение (одно и то же) присваивается всем пере- менным, перечисленным в левой части. Переменные из списка левой части должны быть одинакового типа: либо целого, либо вещественного, либо логического. Выра- жение из правой части имеет свой тип, который определяется по типам аргументов и применяемых к ним операций. В простейшем случае тип выражения должен совпадать с типом левой части, одна- ко алгол допускает отличие этих типов при условии, что один -из них вещественный, а другой целый. Если переменные в левой части целого типа, а в результате вычисления получается значение ве- щественного типа, то алгол предусматривает автоматическое ок- ругление этого значения до ближайшего целого. Например, если а — переменная вещественного типа, a i — переменная целого ти- па, то можно написать операторы присваивания: а : = 1 а :=0.б i : = 1 i :=0.6 (переменной i в обоих последних операторах будет присвоено целое значение 1), но нельзя написать a :=i :=0.6 или а:=/ :=1, так как нельзя объединять переменные разных типов в одном списке ле- вой части. Сформулированный выше четкий порядок выполнения опера- тора присваивания определен авторами алгола не из любви к фор- мализму, а по необходимости, чтобы избежать возможности неодно- значного толкования вычислений, предписываемых оператором присваивания. Например, то обстоятельство, что выполнение опе- ратора присваивания начинается с вычисления индексов, сказыва- ется в том, что оператор i :=х[Л:=2 дает результаты, отличные от результатов выполнения пары операторов i:=2; х[Л: = 2. В частно- сти, при начальном значении 1=1 в первом случае 1=2 и х[1]=2, а во втором случае i=2 и х[21=2. Поскольку всем переменным, перечисленным в левой части опе- ратора присваивания, присваивается одно и то же значение, опера- тор а: =Ь: =а+Ь не эквивалентен паре операторов а: —а+Ь\ Ь: =а+Ь. Например, если имелись начальные значения а=1 и 6=3, первый оператор присваивает значения а=4 и 6=4, тогда как пара опера- торов дает значения а=4 и 6=7.
78 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 2.7.2. Оператор перехода и именующие выражения. Как правило, операторы алгол-программы выполняются под- ряд. Однако имеется возможность отклониться от естественной пос- ледовательности выполнения операторов. Для реализации такого отклонения служат операторы перехода. Если в каком-то месте программы нужно перейти не к следую- щему оператору, а к некоторому другому, то в этом месте помеща- ется оператор перехода, в котором указывается, к выполнению ка- кого оператора следует перейти. Для того чтобы обеспечить возмож- ность таких указаний, в алголе разрешено приписывать отдельным операторам имена, которые называются метками: (метка): := (идентификатор) Согласно этому синтаксическому определению, меткой опера- тора может служить любой идентификатор. Если мы хотим назвать оператор некоторым именем (идентификатором), то пишем перед этим оператором данный идентификатор и отделяем его от операто- ра двоеточием. Например, в записи СЧЕТ: х:=а+И2 оператор присваивания оказывается помеченным меткой СЧЕТ. Если в каком-то месте программы стоит оператор перехода go to СЧЕТ, то после выполнения этого оператора перехода будет выпол- няться указанный оператор присваивания и следующие за ним опе- раторы (до тех пор, пока естественный порядок выполнения опера- торов не будет снова прерван каким-нибудь оператором перехода). Разрешается помечать один и тот же оператор несколькими различ- ными метками, разделяемыми двоеточиями; например, допустима запись: СЧЕТ: ЛИ : х:=а+&|2 В таком случае оператор перехода, ссылающийся на данный опера- тор, может содержать указание любой из меток этого оператора. Оператор перехода может быть безусловным (например, go to All), но может и содержать условие, в зависимости от которого вы- бирается тот или иной оператор для дальнейшего продолжения сче- та, например go to if a>b then All else М2 Выражение, стоящее в операторе перехода после символа go to, называется именующим. Синтаксис оператора перехода и именующего выражения задается следующими формулами: (оператор перехода): :=go to (именующее выражение) (простое именующее выражение) : := (метка) | (указатель пе- реключателя) | ((именующее выражение)) (именующее выражение): := (простое именующее выражение) | (условие) (простое именующее выражение) else (именующее выражение) Семантика. Именующее выражение является правилом для определения метки оператора.
2.7] ОСНОВНЫЕ ОПЕРАТОРЫ! 79 Для иллюстрации применения оператора перехода рассмотрим задачу вычисления последовательности значений хг при i=l, 2, .... п по формуле (У,-4-1 при yz<0, Xl~\yi — 1 при t/z>0. С использованием условного именующего выражения в опера- торе перехода это вычисление записывается на алголе следующим образом: i: = l; S:x[i]:=if y[i]<0 then t/[il+l else y[i]—1; go to if i^n then S else /<; K‘. Оператор с меткой S производит вычисление очередного значе- ния xlil Далее i увеличивается на 1. Затем выполняется оператор перехода с условным именующим выражением. Если i^n, то этот оператор передает управление на метку S (т. е. осуществляет пере- ход к оператору с меткой S) для вычисления следующего значения xlil. Если же t>n, то все нужные значения х(Л уже вычислены; тог- да управление передается на метку К, в конец фрагмента алгол- программы. (При i>n ничего вычислять не требуется, но оператор перехода должен содержать указание метки и на этот случай. За- метим, что метка может стоять только перед оператором; в данном случае можно считать, что после метки К находится пустой опера- тор.) В этом примере условное именующее выражение использо- вано для организации цикла. Более удобен для этой цели оператор цикла: for i:=l step 1 until n do x[i]:=if r/Ul<<0 then «ДЛ+1 else y[i] —1 При определении простого именующего выражения мы ввели понятие указателя переключателя. С этим понятием связаны сле- дующие формулы: (указатель переключателя) ::= (идентификатор переключате- ля) ((индекс)] (описание переключателя) : := switch (идентификатор пере- ключателя) := (переключательный список) (переключательный список) ::= (именующее выражение)! (переключательный список), (именующее выражение) Переключатели в алголе служат для удобной записи перехода, «переключаемого» в зависимости от значения некоторой переменной. Например, в алгол-программе можно описать такой переклю- чатель с идентификатором х: switch x:-=alpha, beta, gamma, delta и тогда, если переменная k принимает значение 1, 2, 3 или 4, то опе- ратор go to xl/г] означает то же самое, что и рассмотренный нами one-
80 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 ратор go to if &=1 then alpha else if &=2 then beta else if fe=3 then gamma else delta. Переключательный список может содержать не только метки, но и любые именующие выражения. Например, switch у.=Л41, if a>b then М1 else М2, М3, М4, х[а+6] В этом случае при i=l оператор go to #[i] означает, что нужно перейти к оператору с меткой М1; при 1=2 нужно проверить выпол- нение условия а>Ь и в зависимости от его истинности перейти либо к метке Ml либо к метке М2; при 1=3 или /=4 нужно перейти со- ответственно к метке М3 или М4. При i=5 нужна вычислить значе- ние а+b и обратиться к переключательному списку х, т. е. пере- дать управление на одну из меток alpha, beta, gamma или delta в зависимости от значения а-^Ь. Для того чтобы произошла передача управления на некоторую метку, индексное выражение в указателе переключателя должно принимать положительные значения 1, 2, 3, ..., п, где и — число именующих выражений в соответствующем переключательном спис- ке. Поэтому в нашем примере переменная i может принимать только значения 1, 2, 3, 4 и 5, причем при i=5 значение а+b должно быть 1 или 2 или 3 или 4. Если I или а+b типа real, то они округляются и допустимы значения 0.5^j<5.5 и 0.5^а+Ь<4.5. Заметим, что в одном именующем выражении могут содержать- ся и метки и указатели переключателей, например, if a>ylk] then All else g[i+1 ] Здесь y[k] — переменная с индексом, g[i+l] — указатель переклю- чателя. Семантика оператора перехода. Оператор перехода слу- жит для изменения естественной последовательности выполнения операторов. Его действие состоит в том, что сначала вычисляется значение именующего выражения, написанного после go to, а по- том происходит переход к оператору, помеченному меткой, полу- ченной в результате этого вычисления. Целью вычисления именую- щего выражения является определение метки, являющейся его зна- чением. Если именующее выражение начинается с условия, то при вычислении этого выражения сначала проверяется данное условие и в зависимости от того, выполнено оно или нет, значением всего именующего выражения будет либо значение выражения, написан- ного между then и else, либо значение выражения, написанного пос- ле else. Таким образом, аналогично вычислению арифметического или логического выражения, мы сводим вычисление именующего выра- жения, содержащего условие, к вычислению простого именующего выражения. Если оно оказывается меткой, это и есть значение ис- ходного именующего выражения и нужно передать управление опе- ратору с этой меткой. Если же мы пришли к указателю переключа- теля, то поиск метки продолжается следующим образом. Отыскива-
2.7] ОСНОВНЫЕ ОПЕРАТОРЫ 81 ется переключательный список с тем же идентификатором, какой имеется у этого указателя. Затем в этом переключательном списке выбирается именующее выражение, номер которого равен значению индекса из указателя переключателя. (Нецелые значения индексов округляются, как и в случае переменных с индексами.) Далее вы- числяется значение этого найденного именующего выражения, при- чем снова применяются все те же правила, и такой процесс продол- жается до тех пор, пока не будет найдена метка. Если на каком-то этапе вычисления именующего выражения встречается указатель переключателя, ссылающийся на несущест- вующую позицию переключательного списка, это делает весь опера- тор перехода эквивалентным пустому оператору, т. е. происходит переход к следующему оператору программы. В частности, может оказаться, что в каком-то переключательном списке содержится именующее выражение, в котором указатель пе- реключателя ссылается на тот же список, но с другим индексом. Например, при описании переключателя switch A:=beta, Л[Л; оператор перехода go to Л [2] корректен только в том случае, если i#=2; если же i=2, то, согласно данной семантике, мы никогда не выйдем из зациклившегося процесса вычисления именующего вы- ражения. При i=l оператор go to Л [2] означает переход на оператор, по- меченный меткой beta. При других целых значениях I, например при 1=0, этот оператор перехода эквивалентен пустому, так же как и операторы go to Л[0], go to Л [2.6] и т. д. Переход по переключательному списку можно использовать Для организации многократного использования один раз описанного вычисления. Рассмотрим пример такого использования. Пусть имеются два набора троек длин отрезков, причем каждый набор оформлен в виде двумерного алгольного массива Л[1 : п, 1 : 3] и В[1 : п, 1:3]. Каждую тройку Л[*, 1], АН, 2], АН, 3] и B[i, 1], ВН, 2], ВН, 3] можно отнести к одному из трех классов: 1) из данных трех отрезков нельзя составить треугольник; 2) можно составить тупоугольный треугольник; 3) можно составить остроугольный или прямоугольный тре- угольник. Требуется посчитать число таких значений i, при которых клас- сы троек Л [i, 1], АН, 2], АН, 3] и B[t, 1], B[i, 2], B[t, 3] совпадают. Составим схему нужных вычислений: 1°. Присвоить начальное значение 0 переменной 3, в которой будет накапливаться результат. 2°. Для каждого значения *=1,2, ..., п выполнить пункты 3°, 4° и 5°. 3°. Определить номер класса, к которому принадлежит тройка АН, 1], АН, 2], АН, 3].
«2 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 ЧГл. 2 4е. Определить номер класса, к которому принадлежит тройка Ж 1), ВЦ, 2], BU, 31. 5°. Если эти классы одинаковые, то прибавить единицу к зна- чению S. Мы видим, что в двух разных местах счета производится вычис- ление номера класса, к которому относится очередная тройка длин отрезков. Чтобы не программировать это вычисление дважды, мож- но завести внутри нашей алгол-программы подпрограмму F, ко- торая будет получать длины отрезков в переменных х, у, г и выдавать номер соответствующего класса в переменной и. Выходить из под- программы F нужно каждый раз так, чтобы возвращаться в то мес- то счета, которое следует за соответствующим оператором обраще- ния к подпрограмме go to F. Для организации таких возвратов мы используем управляющую переменную k, которой будем присваи- вать значение 1 при выполнении пункта 3° и значение 2 при выпол- нении пункта 4°. Выход из подпрограммы F, обеспечивающий воз- врат в нужное место счета, будет осуществляться оператором пере- хода по переключателю go to key [Л]. Программа может выглядеть так: switch кеу:=Ф1, Ф2; s:=0; i: = l; Ml : x:=AU, 1]; y:=AU, 21; z:=AU, 3]; Л:=1; go toF; Ф1 : v:—u; x:=BU, U; y:=BU, 2]; z:—BU, 3]; k:=2; go to F; Ф2 : s:=s-|-(if u=v then 1 else 0); i:—i+l; go to if then Ail else СТОП', F : m:=if x>y/\x>z then x else if y>z then у else z; n:=if 2 then 1 else if 2Xm f 2>x f 2+y f 2-f* zf2 then 2 else 3; go to key [Л]; СТОП: Подпрограмма F начинается с метки F и состоит из двух опера- торов присваивания и одного оператора перехода (возврата). Пер- вый оператор подпрограммы F присваивает переменной т значение длины наибольшего из трех отрезков х, у, г. Следующий оператор вычисляет номер класса, к которой относится эта тройка отрезков. (Если 2т>х+у+г, то наибольший отрезок не меньше суммы двух других, т. е. из данных трех отрезков нельзя составить треугольник. Иначе, если 2m2>x2+z/2+z2, то квадрат наибольшего отрезка боль- ше суммы квадратов двух других отрезков, т. е. из данной тройки можно составить тупоугольный треугольник. В противном случае данная тройка образует остроугольный или прямоугольный тре- угольник.) В последние годы широкое распространение получило понятие структурного программирования, введенное голландским ученым Э. Дейкстрой. В рамках теории структурного программирования
2.71 ОСНОВНЫЕ ОПЕРАТОРЫ 83 формируются и развиваются определенные принципы составления программ с «хорошей» структурой. Мы обсудим такие принципы в разд. 2.16, а теперь отметим, что структурное программирование только в трех случаях предполагает необходимым нарушение ес- тественного порядка выполнения операторов в программе: 1) для выбора одного оператора из нескольких в зависимости от выполнения логических условий; 2) для организации повторения (цикла); 3) для обращения к функции (иначе говоря, к процедуре или подпрограмме). Для этих трех случаев в алголе (как и в других развитых язы- ках программирования) введены специальные средства записи, бо- лее удобные и наглядные, чем операторы перехода. Такими сред- ствами записи являются условный оператор, оператор цикла и опи- сание процедуры. С использованием этих средств рассмотренное нами вычисле- ние можно оформить без операторов перехода: integer procedure КЛАСС (х, у, z); begin m:=if x>y/\x>z then x else if y>z then у else г; КЛАСС-.=\\ 2Xtn^x+y+z then 1 else if 2X/nf 2>xf 2+i/t2+ z f 2 then 2 else 3; end кончилось описание процедуры КЛАСС', НАЧАЛО ВЫЧИСЛЕНИЯ' s:=0; for r. = l step 1 until п do if КЛАСС (A[i, 1], A[i, 2], A[i, 3])== КЛАСС (B[i, 1], B[i, 2], B[i, 3]) then s: =$+1; Процесс вычисления начинается с метки НАЧАЛО ВЫЧИСЛЕ- НИЯ и состоит в выполнении двух операторов: оператора присваи- вания s:=0, а затем оператора цикла, внутри которого находится условный оператор (см. разд. 2.8) и используется процедура- функция КЛАСС, описанием которой начинается программа. Употребление операторов перехода, с точки зрения сторонни- ков структурного программирования, неоправдано и приводит к потере наглядности программы. Поэтому иногда структурное про- граммирование называют «программированием без go to». Опреде- ленная потеря наглядности в программах, изобилующих операто- рами перехода, не вызывает сомнений, и, не вдаваясь в крайности, можно рекомендовать программистам не злоупотреблять операто- рами go to. 2.7.3. Оператор процедуры. Операторы ввода и вывода. Операторы процедуры служат для обращения к фрагментам про- граммы, описанным отдельно и предназначенным для неоднократного- использования. Синтаксически оператор процедуры эк- вивалентен понятию функции, с которым мы познакомились в разд. 2.5;4:
.84 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 (оператор процедуры): := (идентификатор процедуры) (сово- купность фактических параметров) (совокупность фактических параметров): :=(пусто) | ((список фактических параметров)) (фактический параметр): := (идентификатор) | (выражение) | (строка) (выражение): := (арифметическое выражение)! (логическое выражение) | (именующее выражение) Различие состоит в том, что функция представляет собой опе- ранд выражения, заключаемый, например, между знаками опера- ций в правой части оператора присваивания, тогда как оператор процедуры является самостоятельным оператором и помещается, например, между знаками ; в последовательности операторов. В обо- их случаях производится обращение к фрагменту программы. Од- нако в случае функции целью обращения является вычисление зна- чения, которое присваивается в данном фрагменте программы иден- тификатору процедуры, причем прочие вычисления, которые могут производиться в этом фрагменте, хотя и выполняются, но рассмат- риваются как «побочный эффект», а в случае оператора процедуры такой специальной цели нет и просто нужно выполнить этот фраг- мент программы. Подробное рассмотрение операторов процедуры будет проведено в разд. 2.13, а здесь мы остановимся только на двух частных случаях. В языке алгол предусмотрены операторы процедуры, которые не требуют отдельного описания соответствующего фрагмента про- граммы (подобно тому как такого описания не требуют стандартные функции sin., In и др.). Такими стандартными операторами являют- ся операторы ввода значений простых переменных и массивов из внешней среды и вывода таких значений во внешнюю среду. Обыч- но вводятся значения начальных данных, а выводятся результаты работы алгоритма. Однако официальное описание алгола не пре- дусматривает достаточно развитых средств для задания операторов ввода и вывода. В различных конкретных реализациях алгола эти операторы формулируются по-разному. Мы выберем некоторый учебный вариант задания операторов ввода и вывода в соответст- вии со следующими металингвистическими формулами: (оператор ввода): :=ввод ((список объектов)) (оператор вывода): :=выеод ((список объектов)) (список объектов): := (объект) | (список объектов), (объект) (объект): := (простая переменная) | (идентификатор массива) Примеры: ввод (а, Ь, с, D) вывод (х, Y, z, U) Поскольку объектом может служить простая переменная или идентификатор массива, операторы ввода и вывода позволяют про-
2.7] ОСНОВНЫЕ ОПЕРАТОРЫ 85 изводить ввод/вывод одиночных значений или массивов целиком. Массив может содержать любое количество значений, что позволя- ет заготовлять для последующей циклической обработки любое количество исходной информации, а также накапливать и выводить наружу большими порциями результаты счета. Благодаря наличию операторов ввода мы получаем возможность писать на алголе программы многократного использования, кото- рые можно применять к различным начальным данным, присваи- вая переменным новые значения, вводимые извне. Без этих опера- торов программа при каждом ее использовании применяется к од- ним и тем же начальным данным (задаваемым числами в тексте ал- гол-программы) и поэтому выполняет одну и ту же работу, так что, использовав один раз, ее можно было бы выбрасывать за ненадоб- ностью. Ввод данных обеспечивает возможность многократного использования алгол-программы подобно тому, как многократное использование машин Тьюринга и нормальных алгоритмов Маркова обеспечивается возможностью применять эти алгоритмические схе- мы к различным словам. (Эти слова задаются извне до начала рабо- ты алгоритма и в преобразованном виде остаются после завершения работы алгоритма.) Отличие алгола состоит в том, что он позволя- ет производить ввод и вывод в процессе выполнения алгоритма. Когда вычислительная машина должна вводить извне данные, ей необходимо задать, откуда (с какого внешнего устройства) дан- ные нужно взять, и в каком виде они там представлены. Данные мо- гут браться, например, с перфокарт, перфоленты, магнитной ленты, магнитного барабана, сменных магнитных дисков и других внеш- них устройств. Аналогично при выводе результаты можно напеча- тать на узкой бумажной ленте или широких листах бумаги, пробить на перфокартах, показать на видеоэкране и др. Относительно вида представления данных можно, например, при выводе массива троек А[1:п, 1:3] указать, что в каждой строке бумажного листа следует печатать очередную тройку чисел из массива А, причем во всех строках целые части чисел нужно печатать под целыми, а дробные под дробными: 1.89 2 1 3 4.5 7 8.05 13.01 2.7 19.4 0.007 4.501 В некоторых современных языках программирования, напри- мер в фортране, имеются развитые специальные средства для указа- ния номера канала ввода/вывода (т. е. конкретного внешнего уст- ройства), а также формата, определяющего вид представления вво- димых или выводимых данных. Однако в рамках нашего изло- жения алгола мы рассматриваем операторы ввода/вывода только с указанием объектов. Что касается каналов и форматов, то они за-
86 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 (Гл. г даются по-разному в различных конкретных машинных реализа- циях алгола. Семантика. Оператор ввода служит для присваивания пе- ременным значений, которые берутся извне. Оператор вывода обес- печивает копирование значений во внешнюю среду, которые, таким образом, становятся результатами работы алгоритма. 2.7.4. Пустой оператор. В примерах с операторами перехода из раздела 2.7.2 мы уже сталкивались с употреблением пустого оператора. (пустой оператор): := Пустые операторы не задают никаких действий. Они, как пра- вило, служат для размещения меток. Ссылаясь в операторе перехо- да go to L на метку L, мы тем самым указываем, что нужно выпол- нить оператор, следующий за меткой L и двоеточием; но иногда ни- каких дополнительных вычислений не требуется, и ссылка на мет- ку L означает, например, что нужно перейти к концу программы. В таком случае метка L и знак : помещаются в конце программы не- посредственно перед символом end, а синтаксическое требование, чтобы после каждой метки следовал оператор, удовлетворяется, поскольку мы считаем, что между соседними символами L: и end находится пустой оператор. 2.8. Условные операторы Условный оператор является композицией из основных опера- торов и позволяет осуществлять выбор между выполнением различ- ных операторов в зависимости от истинности некоторого условия, например: if х>у then х:=0 else у:=0 В этом случае, подобно уже знакомой нам структуре условного выражения, в зависимости от значения логического выражения х>у, заключенного между if и then, выбирается либо оператор х:=0, заключенный между then и else, либо оператор y.—Q, который следует за else. Иначе говоря, той из переменных х и у, значение которой больше, присваивается новое значение. Однако, в отличие от единообразной синтаксической структуры условного выражения, обязательно предписывающей наличие слу- жебного слова else и следом за ним второй альтернативы, синтаксис условных операторов разрешает употреблять и неполный условный оператор (без else и второй альтернативы). Например, допускается запись а: =0; if х>0 then а: = 1 согласно которой при выполнении условного оператора перемен- ная а либо получает новое значение 1, либо сохраняет свое преж- нее значение 0.
•2.81 УСЛОВНЫЕ ОПЕРАТОРЫ 87 В более общем виде две основные конструкции условного опе- ратора можно представить следующим образом: if В then SI else S2 и if В then S Из-за того, что в конструкции условного оператора может от- сутствовать else, не разрешается после then писать условный опера- тор. Иначе могли бы возникать двусмысленности, как, например, в следующем случае: <г.=0; if fc>0 then if c>0 then a: = 1 else a: =2 Здесь в зависимости от того, какому из двух символов then соот- ветствует единственный символ else, могли бы возникнуть две сле- дующие трактовки: 1) Если else соответствует первому then, то >0 и с^О, О при b 1 при b > 0 и с > 0, k 2 при 6^0. 2) Если else соответствует второму then, то: ' 0 при а= 1 при b > 0 и с > 0, 2 при д>0 и с^О. Поэтому на алголе такая запись не разрешается; а если между then и else нужно поместить условный оператор, то он заключается в операторные скобки begin и end, становясь при этом безусловным составным оператором: 1) а:=0; if b>6 then begin if £>0 then a: = l end else a: =2 или 2) a:=0; if bX) then begin if £>0 then a: = l else a: =2 end Синтаксис условного оператора (условный оператор): := (условие) (безусловный оператор) else (оператор) | (условие) (безусловный оператор) | (условие) (оператор цикла) | (метка): (условный оператор) (безусловный оператор): := (основной оператор) | (составной оператор) | (блок) | (метка):(безусловный оператор) В этих металингвистических формулах употребляются понятия оператора цикла, составного оператора и блока, которые будут оп- ределены позднее. Согласно данному синтаксису, внутри условного оператора мо- гут содержаться метки, так что допускается, например, запись: if а>ЬД£>0 then V:q:—n+m else W:if а>0 then l/:go to M В этом случае извне можно передать управление «внутрь» ус- ловного оператора на метку V, W или U. После оператора go to V выполнится,оператор присваивания q:=n-\-m и далее будет выпол-
88 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 пяться оператор, следующий за условным. После оператора go to W выполнится «короткий» условный оператор if а>0 then go to М. Если же выполнить оператор go to U, то мы сразу же попадем на оператор перехода, который отошлет нас к метке М. Семантика условного оператора. Полный условный опе- ратор служит для выбора одного из двух операторов. При его вы- полнении вычисляется логическое выражение, находящееся в ус- ловии. Если оно истинно, то выполняется тот оператор, который написан между then и else, и не выполняется оператор, найисанный после else. В противном случае выполняется тот оператор, который написан после else, и не выполняется оператор, написанный между then и else. Неполный условный оператор служит для выбора между выпол- нением и невыполнением содержащегося в нем оператора. Если ус- ловие истинно, то выполняется оператор, написанный после then, в противном случае он не выполняется. Поскольку после else может следовать любой (и в частности сно- ва условный) оператор, то конструкция условных операторов позво- ляет производить выбор одного из любого числа операторов, напри- мер, if а<0 then х:=1 else if а<2 then х:=2 else if а=4 then х:=3 - Рассмотрим еще один пример. Пусть имеется одномерный мас- сив с идентификатором а, состоящий из п. элементов, пронумерован- ных от 1 до п, причем элементы массива могут принимать логиче- ские значения (true и false). Нужно посчитать, сколько элементов принимают значение true, и вывести полученный результат. С по- мощью условного оператора это записывается следующим образом: ввод (a)-, s:=0; i: = l; цикл: if alt] then s:=s-M; t:=i+l; if t^n then go to цикл-, вывод (s) Здесь использованы два неполных условных оператора, при- чем в первом из них в качестве условия выступает логическая пе- ременная ali]: если значение ali] есть true, то выполняется оператор s:=s-H. Второй неполный условный оператор обеспечивает п вы- полнений цикла, после чего пропускает нас на оператор вывода ре- зультата. Заметим, что второй условный оператор в данном случае не по существу, так как он используется только в качестве средства замены еще не рассмотренной нами конструкции оператора цикла. 2.9. Составные операторы Часто в программировании возникает естественная задача выбо- ра между группами операторов. В таком случае конструкция ус- ловного оператора непосредственно не применима, так как она обес- печивает только выбор между отдельными операторами. Однако в алголе предусмотрена возможность формального объединения не- скольких операторов в один составной, который к тому же стано-
? 9] СОСТАВНЫЕ ОПЕРАТОРЫ 89 вится безусловным, даже если в него входит условие. Подобно тому, как любое арифметическое выражение, взятое в скобки, тем самым становится операндом, составной оператор образуется как набор операторов, заключенный в скобки, но не круглые, а специальные операторные begin и end. Между этими служебными символами мож- но вписать любое число операторов, отделенных один от другого символом ; . Тем самым мы получаем, например, возможность про- изводить в зависимости от истинности условия выбор между пара- ми операторов: if a>b then begin а: = 1; b:=0 end else begin а:=0; b:=l end Синтаксис составного оператора (составной оператор): :=begin (список операторов) end (список операторов): :=(оператор) | (список операторов); (оператор) (оператор): := (безусловный оператор) | (условный оператор) | (оператор цикла) Напомним, что в синтаксическом определении безусловного опе- ратора одной из альтернатив было понятие составного оператора. Подобная рекурсия в металингвистических формулах нам уже встре- чалась, и в данном случае она означает только, что составной опе- ратор может в частности содержать внутри себя составные опера- торы. В качестве примера работы с составными операторами рассмот- рим вычисление вектора с, являющегося произведением матрицы а на вектор b по формуле п Cj = 2 aJk-bk при 1 < / С П. Поскольку нужны п значений Cj, то естественно организовать счет как цикл с управляющей переменной /, которая последователь- но принимает значения 1,2, .. .,п. При каждом значении / вычис- ляется очередной элемент cj. Внутри этого цикла требуется сложить п произведений a/ftXbA. Поэтому нужно организовать внутрен- ний цикл с управляющей переменной k, которая при каждом кон- кретном значении / тоже последовательно принимает значения от J до п. Программа может выглядеть так: ввод (а, Ь)\ /: = 1; элемент: if /^n then begin fe: = l; с[/]:=0; шаг: if k^n then begin d/]:=d/l+e[/, Л]хЫЛ]; k:—k+l', go to шаг end; go to элемент end; вывод (c)
90 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2: Для удобства зрительного восприятия мы расположили со сдви- гом вправо символы begin, с которых начинаются вложенные сос- тавные операторы, причем каждый символ end помещен под соот- ветствующим ему символом begin. В данной программе организуются два вложенных цикла, каж- дый из которых оформлен как неполный условный оператор, при- меняемый к составному оператору. Во внутреннем составном опе- раторе к накапливаемому значению Cj прибавляется очередное про- изведение ajk-bk, продвигается на единицу значение управляю- щей переменной k и управление передается на метку шаг (для выпол- нения следующего шага цикла вычисления очередного элемента вектора с). Во внешнем составном операторе устанавливается на- чальное значение k—\ для перебора элементов очередной строки матрицы а, которая будет скалярно умножаться на вектор Ь, при- чем скалярное произведение будет накапливаться в очередном эле- менте с/ вектора с. Затем производится начальное присваивание с[/]=0 и выполняется внутренний цикл вычисления очередного скалярного произведения, т. е. очередного элемента с[/1. После этого к счетчику / прибавляется 1 и управление передается на метку элемент для вычисления следующего элемента с[/J или для выхода на оператор вывод (с), если j>n, т. е. если формирование вектора с уже завершено. 2.9.1. Примеры с использованием составных операторов, содер- жащие анализ фРагментов алгольного текста. Рассмотренные нами средства языка алгол позволяют, в част- ности, писать на алголе программы анализа алгольных текстов. Предназначенный для анализа текст на алголе будем представлять в виде массива чисел, каждое из которых изображает очередной сим- вол этого текста. Нужно установить кодировку целыми числами всех символов из алфавита языка алгол. В принципе возможна любая кодировка со взаимно однозначным соответствием. Для боль- шей наглядности мы выбираем кодировку, которая разделяет сим- волы на несколько классов, причем каждый класс кодируется под- ряд, начиная с круглого числа. Такими классами являются цифры (коды от 0 до 9), строчные латинские буквы (коды от 10 до 35), про- писные латинские буквы (коды от 40 до 65), строчные и прописные буквы русского алфавита, не совпадающие по написанию с латин- скими (коды от 70 до ПО), служебные знаки и служебные слова алфавита алгола (коды от 200 до 252). Заметим, что в связи с этим в кодировке имеются пропуски чисел. Например, число 36 не ко- дирует никакого алгольного символа. Коды служебных знаков и слов представлены в таблице 2.3. Теперь будем анализируемый алгольный текст кодировать последовательностью элементов массива Т, начиная с Т[0]. В конце ч закодированного тексту будем помещать специальный код конца —
2.91 СОСТАВНЫЕ ОПЕРАТОРЫ 91 Таблица 2.3 Сим- вол Ч ИСЛОВОЙ код Сим- вол Ч ИСЛОВОЙ код Символ Числовой код Символ Число- вой код t 200 V 214 true 228 own 242 1 x 201 ZD 215 False 229 boolean 243 / 202 = 216 go to 230 integer 244 203 217 if 231 real 245 + 204 218 then 232 array 246 205 • 219 else 233 switch 247’ 206 220 for 234 procedure 248 207 •= * 221 do 235 string 249 208 ( 222 step 236 label 250 209 ) 223 until 237 value 251 210 [ 224 while 238 10 252 211 ] 225 comment 239 212 n 226 begin 240 /? 213 n 227 end 241 число 999. Например, текст x:=al + S; кодируется последователь- ностью целых чисел 33, 221, 10, 1, 204, 41, 220, 999, располагаемых в элементах ПО], ЛИ, ..., Л7]. Формирование нужных значений этих элементов может быть выполнено оператором ввод (Т) или опе- раторами присваивания: Л0]:=33; Т[\}:=22\\ Л21: = 10; Л3]: = 1; Л4]:=204; Л5]:=41; Л61: =220; Л7]: =999 Рассмотрим примеры анализа алгольного текста, закодирован- ного в массиве Т. Пусть нужно узнать, является ли текст в массиве Т идентифи- катором, и присвоить логической переменной Р значение true, если да, и false, если нет. Требуемый анализ можно выполнить следующими действиями: 1°. Ввести текст. 2°. Если первый символ не буква, то выполнить присваивание P:=false и закончить работу выводом результата Р. 3°. Для 1=1, 2, ..., пока Л/] есть буква или цифра, перебирать символы. 4°. Если символ, оказавшийся не буквой и не цифрой, есть при- знак конца (999), то P;=true, иначе P:=false. s 5°. Вывести Р. На алголе это записывается так: ввод (Г); if ЛО]<1ОУЛО]>11О then P:=false else begin i: = l; ЦИКЛЫ ЛЛ^НО then begin f:=i+l; go to ЦИКЛ end; Р:=ЛЛ=999 end; вывод (P)
92 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 (Гл. 2 Переменная i служит счетчиком элементов 71 Л, в которых коди- руются последовательные символы текста. Если текст в Т начина- ется нес буквы, т. е. 710] < 10 или 710] > 110, то условный оператор присваивает значение P:=false, после чего мы переходим к опера- тору вывод (Р). (Мы предполагаем, что, пока не встретим кода 999, будем встречать в массиве Т только коды алгольных символов, а числами от 10 до НО кодируются только буквы.) Если текст начи- нается с буквы, то содержащийся в составном операторе цикл пере- бирает последовательные элементы массива Т, являющиеся кодами чисел или букв. Такие коды 7li] характеризуются отношением TH 110. Мы выходим из этого цикла, встретив код не буквы и не цифры. Если это код конца 999, то текст, закодированный в масси- ве Т, является идентификатором, и тогда оператор присваивания P:=71i]=999 выдает ответ P=true. В противном случае это код алгольного символа, отличного от буквы или цифры, и значение отношения 71i]=999 есть false. Рассмотрим теперь такую задачу. Пусть в массиве Т содержится кодировка целого числа без знака из (п+1) цифры в элементах от 710] до Tin], причем цифра младшего разряда записана в Tin}. (В данном случае мы можем вместо «код цифры младшего разряда» употребить слова «цифра младшего разряда», поскольку в нашей ко- дировке коды цифр совпадают с ними самими.) Нужно записать в массив Т кодировку числа на единицу большего. Предполагается, что закодированное в Т число состоит не из одних девяток. (Сход- ную задачу мы решали применительно к машине Тьюринга.) Нуж- но, если цифра младшего разряда не 9, прибавить к ней 1, а если 9, то написать вместо нее 0 и прибавить 1 к предыдущей цифре, если та не 9 и т. д. Составим схему требуемого вычисления: Г. Ввести текст. 2°. Присвоить начальное значение i:=n. 3°. Если 7’[i]#=9, то выполнить присваивание 7li]:=71t]+I и перейти к п. 5° иначе 4°. Выполнить присваивание ТЦ]: =0, вычесть 1 из значения i и перейти к п. 3°. 5°. Вывести полученный текст. На алголе это записывается так: ввод (7'); 1’:=л; Af:if then 71i]:=7li]+l else begin T[i]:=0; i:=i—1; go to M end; 1 вывод (T) В конструкцию условного оператора мы включили после else составной оператор, который целиком пропускается, если очеред- ная цифра не равна 9, так что после прибавления к ней 1 произой- дет переход к выводу массива Т. Если же эта цифра равна 9, то вы- полняется составной оператор, который заносит в нее 0, измёняет
, 2.10] ОПЕРАТОРЫ ЦИКЛА 93 * / счетчик i для перехода к рассмотрению предыдущей цифры и пере- дает управление в начало условного оператора. Поскольку исход- ное число состоит не из одних девяток, то когда-нибудь встретите? ' значение T[t]=/=9, к нему прибавится 1 и произойдет вывод масси- ва Т. 2.10. Операторы цикла В примерах из предыдущих разделов мы часто сталкивались с задачей описания циклического счета. Многократное последова- тельное выполнение одинаковых действий является основой про- граммирования, так'как обеспечивает возможность широкого при- менения программ. Как правило, однотипные действия применяют- ся многократно либо к одним и тем же переменным в итерационном процессе, пока не выполнится определенное условие, либо к разным величинам, являющимся элементами одного массива. Продвиже- ние циклического счета обычно определяется перебором последова- тельных значений переменной, называющейся управляющей пере- менной или параметром цикла. Оператор цикла состоит из заголовка и одного оператора. Этот оператор представляет собой тело цикла, выполнение которого бу- дет повторяться. (Поскольку за заголовком цикла может следовать любой оператор, такая структура позволяет нам включать в цикл любое количество операторов, объединяя их в составной оператор.) Заголовок задает закон изменения параметра, при каждом значе- нии которого один раз выполняется тело цикла. Авторы алгола пре- дусмотрели три способа задания такого закона: перечисление, про- грессию и пересчет и соответственно три структуры элементов спис- ка цикла, которые могут содержаться в заголовке цикла. Первым способом явно указываются значения параметра, при которых нужно выполнять тело цикла. Каждое такое значение за- дается элементом, состоящим из одного арифметического выра- жения. Прогрессия задается тремя арифметическими выражениями по схеме £1 step Е2 until £3. Сначала значение параметра цикла уста-, навливается равным £1; в дальнейшем на каждом шаге цикла к не- му прибавляется £2, и этот процесс продолжается до тех пор, пока параметр не достигнет значения £3 или не перейдет за это зна- чение. Пересчет задается элементом вида £ while В, где £ — арифме- тическое, В — логическое выражение. Эта структура означает, что на каждом шаге цикла нужно вычислять значение £ и присваивать его параметру цикла, действуя так, пока выражение В, также вы-' числяемое на каждом шаге, сохраняет значение true; процесс закан- чивается, как только при очередной проверке оказывается, что зна- чением В является false. Обычно при этом в формулу £1 входит сам
94 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 (Гл. 2 параметр цикла, что позволяет вычислять его следующее значение по предыдущему. Например, закон геометрической прогрессии со знаменателем а можно задать заголовком цикла for s:=sxa while s^4 do. Если предварительно было присвоено значение s=l, то данный элемент списка цикла обеспечивает выполнение тела цикла при значениях s=a, а2, а3 и так далее, пока параметр s не перейдет через верхнюю границу А. Заголовок цикла начинается с записи for К:=(где V — перемен- ная, являющаяся параметром цикла) и заканчивается служебным словом do, например, for i:=0 step 1 until 100 do В заголовке цикла могут содержаться несколько элементов спис- ка цикла, разделенные запятыми. В этом случае элементы управ- ляют перебором значений параметра по очереди. Так, тот же цик- лический счет при значениях параметра i—6, 1, ..., 100 можно за- дать заголовком fori:=0, i-H while iXlOO do где первый элемент — типа перечисления, а второй — типа пере- счета. Синтаксис операторов цикла (элемент списка цикла ):: = (арифметическое выражение)! (арифметическое выражение) step (арифметическое выраже- ние) until (арифметическое выражение) | (арифметическое выражение) while (логическое выражение) (список цикла): := (элемент списка цикла) | (список цикла), (элемент списка цикла) (заголовок цикла): :=for (переменная): = (список цикла) do (оператор цикла): := (заголовок цикла) (оператор) Семантика. Оператор цикла служит для задания повто- ряемого выполнения оператора, являющегося телом цикла, при пос- ледовательных значениях параметра, определяемых заголовком цикла. Входящие в заголовок элементы списка цикла управляют последовательностью значений параметра по очереди слева направо до исчерпания каждого очередного элемента (без возвратов к уже исчерпанным элементам). Чтобы строго определить порядок дейст- вия предусмотренных типов элемента списка цикла, напишем на алголе программы, эквивалентные каждому такому типу. Будем обозначать параметр цикла через V, тело цикла через S, арифмети- ческие выражения через El, Е2, ЕЗ, Е, логическое выражение через В. Когда рассматриваемый элемент списка исчерпан, мы уходим на метку ЭЛИСЧ. Предполагается, что после этого произой- дет переход либо к следующему элементу списка цикла (если он есть), либо к оператору, следующему за оператором цикла. Вычисление, управляемое элементом типа перечисления Е1, эквивалентно программе V:=E1; S; go to ЭЛИСЧ
2.10) ОПЕРАТОРЫ ЦИКЛА 95 Вычисление, управляемое элементом типа прогрессии £1 step £2 until £3, эквивалентно программе У:=£1; Ll:if(V—£3)Xsign(£2)>0 then go to ЭЛИСЧ-, S; y:=V4-E2; go to LI Вычисление, управляемое элементом типа пересчета £ while В, эквивалентно программе L: V:=£; if-1£ then go to ЭЛИСЧ-, S; go to L В программном представлении элемента типа прогрессии уч- тено, что шаг £2 может быть как положительным, так и отрица- тельным. Например, при заголовке for i: = 10 step—1 until 2 do вы- полнение тела цикла происходит при значениях i=10, 9, ..., 2. Затем при i=l оказывается истинным условие (V—£3) Xsign(£2)>0, поскольку V=l, £3=2 и sign. (£2)=—1, а это значит, что элемент списка цикла исчерпан.^ Поскольку проверка на исчерпание элемента производится до выполнения тела цикла, может оказаться, что элемент списка цикла не задает ни одного выполнения. Например, элемент 10 step 1 until 9 сразу же оказывается исчерпанным, так как при V—10, £3=9, sign(£2)=l верно, что (V—£3)Xsign(£2)>0. ( Теперь мы можем рассмотреть следующий пример, в котором представлены различные типы элементов списка цикла. fori: = l, 5, 6, 7 step 2 until 13, i4-3 while i>0, 35 step— 5 until 20 do вывод (i) Тело цикла состоит из оператора вывода очередного значения параметра цикла. В заголовке данного цикла 6 элементов, разде- ленных запятыми. Первые три элемента перечисляют последова- тельные одиночные значения 1=1, 5, 6. Далее элемент типа про- грессии задает выполнение цикла при i=7, 9, 11, 13. Этот элемент будет исчерпан при значении 1=15. Затем элемент типа пересчета каждый раз делит на 3 операцией 4- предыдущее значение i. По- скольку перед этим мы имели 1=15, то получаем выполнения цикла при i=5 и 1 (так как 54-3=1). И, наконец, последний элемент типа прогрессии задает выполнения тела цикла при i=35, 30, 25, 20. Таким образом, всего тело цикла выполняется 13 раз и будут напе- чатаны числа 1, 5, 6, 7, 9, 11, 13, 5, 1, 35, 30, 25, 20 Параметр цикла может изменяться не только управлением эле- ментов из заголовка, но и в теле цикла. Так, в результате выпол- нения оператора for i:=l step 1 until 20 do begin вывод (i); i:=i+lend будут выведены числа 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, так как на каждом шаге происходит прибавление 1 к параметру i два раза: в заголовке цикла и в операторе присваивания i:=i+l. Согласно правилам алгола, значение параметра цикла поело окончания этого цикла по исчерпанию элементов его списка не оп-
96 (АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 (Гл. 2 ределено, так что им нельзя пользоваться как аргументом при вы- полнении последующих операторов. (Однако если выход из цикла осуществляется посредством какого-либо оператора перехода, то значение параметра цикла сохраняется таким, каким оно было не- посредственно перед выполнением оператора перехода.) Входить в цикл извне разрешается только через его заголовок, хотя в теле цикла и могут быть метки, предназначенные, например, для пере- дач управления между различными операторами, содержащимися в теле цикла. 2.10.1. Некоторые примеры с использованием операторов цикла. Оптимизация циклов. Пусть требуется вычислить значение s целого числа без знака, состоящего из (п+1) цифр, которые хранятся в элементах ПО], ... . .., Т[п] массива Т, как описано в разд. 2.9.1. (Напомним, что элемент ПпЧ-И содержит признак конца 999.) Это вычисление можно записать на алголе так: s:=0, for k:=0 step 1 until n do s:=s+T[/s]x 10 f (n—k) На каждом шаге цикла происходит прибавление к накапливаемой сумме очередной цифры, умноженной на значение соответствую- щего разряда. Самая старшая цифра ПО] соответствует (п-Ы)-му разряду и поэтому умножается на 10", последующие цифры умно- жаются на 10"“*, ..., 10°=1. Можно предложить другую программную реализацию решения данной задачи, избавившись от многократного выполнения сравни- тельно дорогостоящей операции возведения в степень 10|(п—k). Для этого воспользуемся соотношением xfc=xft_j/10, где хк — коэф- фициент при T[k\ в вычисляемой нами сумме. Мы вычислим только х<>=10", а каждое следующее значение х будем получать делением на 10 предыдущего значения: s:=0; х: = 10 f п; for k‘.=0 step 1 until n do begin s:=s+Hfe] Xx; x:=x/10end Можно обойтись и без начального вычисления 10 f п, если про- изводить суммирование в обратном порядке, от Пп] к ПО]: s:=0; х: = 1; for kt =п step—1 until О do begin s: =s4-H£] X x; x: =x X10 end При каждом выполнении цикла мы производим два умножения и одно сложение. Можно сократить число действий в цикле до од- ного умножения и одного сложения, если воспользоваться выраже- нием для искомого значения s в так называемой схеме Горнера: s=(.. .((ПО] X10.-4-7411) X10+И2]) X10+ПЗ])...) X 10+Пп] Соответственно наша программа получает следующий вид: 8:=П01; for kt=\ step 1 until n do s:=sX 10+ПЛ] Одно из преимуществ схемы Горнера состоит в том, что она поз- воляет написать программу, пригодную для вычисления значения s при любом количестве цифр:
2.10] . ' ОПЕРАТОРЫ ЦИКЛА 97 s:=710]; fe=0; for /г:=/г+1 while 7W=999 dos—sXlO+Ш] В этой программе мы вне цикла выбираем первую цифру Т[0], а затем циклически перебираем последовательные цифры, пока не встретим признак конца 999, который приведет к невыполнению проверяемого в заголовке цикла условия. Заметим, что хотя формально можно сделать величину s пара- метром цикла: 6:=0; for s: =7101, sx10+Ш1 while Ш+И=/=999 do A:=A:+1 для нас такая программа не годится, так как s является искомой ве- личиной, значение которой должно использоваться в дальнейшем, а мы знаем, что значение параметра цикла неопределено после окончания цикла. Здесь в условии написано ТЧб-НЬ^ЭЭЭ, а не 7W¥=999 потому, что в теле цикла производится «настройка» на следующую цифру Tlfc+1], которая добавится к значению s на сле- дующем шаге при вычислении параметра (раньше проверки усло- вия, т. е. независимо от результата этой проверки). Теперь рассмотрим обратную задачу. Пусть дано положитель- ное значение переменной s целого типа, и нужно найти последова- тельные цифры этого числа и переписать их в массив Т. Число s выражается через свои цифры по формуле ss=STr10»4 г=о Самая младшая цифра Тп — коэффициент при 10® получается как остаток от деления s на 10. Целая часть результата деления s на 10 определяется формулой Л-1 s' = 2 1=0 Следующая цифра TB_j получается как остаток от деления на 10 этого числа s' и так далее. В алголе нет операции взятия остатка от деления, но есть операция -j- получения целой части частного. Целую часть результата деления s на 10 мы будем заносить на место той же переменной s оператором s:=s4-10. Соответственно каждая цифра (т. е. остаток от деления очередного значения s на 10) будет определяться выражением s—(s-i-10)X10. Нужные вычисления можно выполнить следующей программой: for k: =0, k+1 while s^0 do begin a[6]:=s—(s-i-10) X10; s:=s-4-10; m:—k end; for k:=0 step 1 until tn do T[k]:=alm—k\\ T(/n+l]:=999 Первый цикл обеспечивает вычисление и занесение в массив а всех цифр исходного числа s. Самая младшая цифра заносится в 40], следующая в all] и так далее до получения в некоем элемента alm] самой старшей цифры. Легко заметить, что цифры накапли- ваются в массиве а в порядке, противоположном требуемому. По- Э. 3. Любнмскнй и др.
98 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 1Гл. 2 этому в следующем цикле эти цифры переписываются в массив Т в обратном порядке: alm] попадает в ПО], alm—1] в ЛИ и так да- лее. В заголовке этого цикла используется значение /и, вычисляе- мое в теле предыдущего цикла. (Оно равно последнему значению параметра, при котором выполнялось тело предыдущего цикла. Сам параметр, как мы знаем, недоступен для непосредственного дальнейшего использования после окончания цикла.) Желательно избежать употребления вспомогательного массива а (так как при реализации алгоритма на ЭВМ этот массив потребует дополнительной машинной памяти, ресурсы которой бывают ог- раничены). Другой недостаток этой программы в том, что в первом цикле два раза вычисляется один и тот же результат s-r-10. Ниже приводится вариант программы, в котором это выражение вычис- ляется в цикле однократно, а вместо массива а употреблена одна вспомогательная переменная: for fe:=0, k+l while s=/=0 do begin i:=s4-10; T[k]:=s—fXlO; s:=Z; m:=kend\ for &:=0 step 1 until (m+1)4-2 do begin a:—Tlk]\ Tlk]:=Tlm—k]; Tim—k]:=a end В первом цикле значения цифр накапливаются сразу в массиве Т, хотя и в обратном порядке. Во втором цикле последовательно ме- няются местами сначала крайние цифры ПО] и Tim], затем соседние с ними ЛИ и Tim—1], и этот процесс продолжается в направлении от краев к центру, пока все цифры не займут нужные места в мас- сиве Т. Поскольку в алголе нет операции, которая сразу меняла бы местами соответствующие значения П&] и Tim—k], эта перестановка выполняется тремя операторами присваивания в теле второго цикла, с использованием простой переменной а для промежуточного хране- ния очередного значения ПАП, на место которого пишется Tim—k]. При каждом выполнении второго цикла в процессе проверки ус- ловия его окончания будет вычисляться одно и то же значение (т+1)4-2. Желательно вынести это инвариантное вычисление из цикла, тогда программа приобретает следующий вид: for&:=0, АН-1 while s#=0 do begin t:=s4-10; Tlk]:=s—ix 10; s:=t; m:=k end; r.=(/n-H)4-2; for k:=0 step 1 until i do begin а:=ЛАП; Tlk]~Tlm—k]\ Tim—AH:=aend Обычно следует особенно тщательно программировать вычисле- ния внутри цикла, так как ускорение этих вычислений дает особый эффект благодаря их многократному выполнению. Существуют не- которые общие приемы оптимизации циклов. К ним относятся: 1) вынесение инвариантных частей счета за цикл; 2) использование для ускорения результатов предыдущего шага цикла; 3) замена более сложных операций на более простые; 4) однократное вычисление одинаковых подвыражений;
2.10] ОПЕРАТОРЫ ЦИКЛА 99 5) исключение операций над константами. Первый прием состоит в том, что если в циклически выполняе- мой последовательности операторов имеются выражения, значения которых не изменяются внутри цикла, то вычисление этих выраже- ний выносится за пределы цикла для того, чтобы эти выражения вычислялись только один раз, а не при каждом выполнении цикла. Рассмотрим, например, оператор цикла for f: = l step 2х£ until п—£+1 do a[i]:=x f 2 X alii На каждом шаге выполнения этого цикла будут заново вычисляться значения выражений п—£+1, х f 2 и 2Х&. Однако видно, что в дан- ном случае эти значения в цикле не изменяются, поэтому нет ни- какой необходимости вычислять их каждый раз заново — доста- точно это сделать один раз, до входа в цикл: Н:=п—&+1; г2:=х|2; гЗ:=2х/?; for r. = l step гЗ until rl do a[i]:=r2Xa[i] Этот же прием был применен, когда в предыдущей программе мы вынесли из цикла вычисление (т+1)4-2. Второй прием позволяет уменьшить число операций на каждом шаге выполнения цикла за счет использования результатов, полу- ченных на предыдущем шаге. Мы применили этот прием в начале данного раздела, когда воспользовались соотношением хй=хА-1/10 между результатом xkt получаемым на шаге й, и результатом xk~lt получаемым на шаге k—1. Третий прием состоит в сведении операции возведения в степень к умножению, умножения — к сложению и т. д., так как a f п=аХа.. .Ха (п раз), аХп=а+а+ .. .+а (п раз). В частности рекомендуется использовать сложение вместо умноже- ния на два, умножение вместо возведения в квадрат. Четвертым приемом мы воспользовались в программе получе- ния цифр по числу, когда избавились от двукратного вычисления выражения S4-10. Пятый прием исключает использование таких операндов, как, например, 1/4 или 10 f 5. (Вместо этого нужно писать соответст- венно .25 или 105.) Последние три приема необходимо применять не только в циклах. Применим теперь рассмотренные приемы оптимизации циклов к решению следующей задачи. Пусть требуется вычислить sin (х) по формуле ^« = £(-1).^ Разумеется, мы не можем суммировать бесконечный ряд, и по- этому задается значение требуемой точности е. Суммирование пре- кращается, как только модуль очередного члена оказывается мень- 4*
100 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 (Гл. 2 ше, чем е. (Известно, что весь отбрасываемый остаток по модулю меньше этого члена.) Поскольку в алголе нет операции вычисления факториала, нам 2п+1 придется вычислять (2п+1)! по формуле i, которая реализует- 1=1 ся операторами Л—1; for i:=l step 1 until 2xn-H do f:==fyi Сформируем сначала схему нашей программы. Iе. Установить начальное значение 0 переменной s, где будет на- капливаться сумма. 2°. Прибавлять к s очередной член ряда ип, пока |ип|^е. Пункт Г вполне ясен и не нуждается в дальнейшей детализа- ции. Что касается пункта 2°, то он все еще довольно сложен, и по- этому мы напишем для него более детальный подплан, пользуясь дробными «номерами» пунктов: 2.1 е. Для д=1, 2, 3, ... пока |ып|^8 выполнить: 2.2 °. {Вычислить (2п+1)1 2.3 °. Вычислить «„ = (— 1)В^7)Г 2.4 °. Прибавить ип к s) Фигурные скобки, объединяющие пункты 2.2°, 2.3° и 2.4°, оз- начают, что все эти три подпункта нужно выполнять в цикле (пока |un|^e). Составленной нами схеме соответствует такая программа: s:=0; forn:=0, п+1 while abs(un)^eps do begin a:=l; for f:=l step 1 until 2Xn-H do a:=aXi; un:=(—1) f nXxf (2xn-f-l)/a; s:=s+un end Здесь внутри цикла, перебирающего члены ряда, содержится другой цикл вычисления факториала, который выполняется (2п-Н) раз при каждом n-м выполнении внешнего цикла. Еще до составления этой программы мы могли путем рассмотре- ния схемы заметить, что делаем лишние действия, так как в данном случае применим второй прием оптимизации циклов: каждый очеред- ной член un+t может вычисляться с использованием значения пре- дыдущего члена ип. В самом деле, =~' 2. п (2* n -f-'i) ’ ПоэтомУ __ X2 U«+l ~~и* 2.л.(2.л-Н) ’ С учетом этого изменяем детализацию пункта 2 схемы вычисле- ния sin х следующим образом: 2.1°. Присвоить значение и:=х. 2.2°. Для п=1, 2, 3, ... пока |м|^е выполнить s: = s + «; «: = — 2.п.(2.« + 1) • Эта схема также может быть улучшена, поскольку в ней при каж- дом выполнении цикла вычисляется не зависящая от п величина х\ а также дважды производится умножение п на 2. Применим первый
2.11) ОПИСАНИЯ В АЛГОЛЕ 101 прием оптимизации циклов, вынеся вычисление — хг/2 из тела цикла, и получится следующий вариант детализации схемы: 2.1 °. Присвоить значения и:=х, о:=—№/2. 2.2 °. Для п=1, 2, 3, ... пока |ы|^8 выполнить , U'V s^s+u; ---------•-(2.n+ij t Наконец, при вычислении an=n-(2'n+l) можно сэкономить одно умножение, если воспользоваться значением этого выражения, полученным при предыдущем выполнении цикла: а„+1=ап+4п4-3. Полагаем аг=3 и выбираем в качестве параметра цикла величину У=4*п+3. Тогда наша детализированная схема приобретает сле- дующий окончательный вид: 2.1 °. Присвоить значения и‘.—х\ v:=—х2/2; а:—3. 2.2 °. Для N=l, 11, 15,... пока |«|>в выполнить s: = s4-u; = a: = a-f-A(. Соответственно мы получаем программу в виде: s:=0; и:=х; v:~—хХх/2; а:=3; for N'.=l, N+4 while abs (u)^eps do begin s:=s+«; u:=«X»/a; ai=a+N end 2.11. Описания в алголе В алголе принято общее правило: все объекты, употребляемые в программе, должны быть описаны. Поэтому, наряду с операторами, предписывающими определенные действия, в алголе предусмотрена еще одна форма предложений — описания. Каждое описание объяв- ляет объект, вводит его имя, сообщает его вид и сообщает его до- полнительные свойства (для простых переменных это тип значений, а для массивов — тип и структура). Кроме того, каждое описание определяет область существования объекта; это будет рассмотрено в следующем разделе. В каждом месте программы каждое имя долж- но обозначать только один объект. Такими описываемыми объектами могут быть простые перемен- ные (описывается тип одиночных значений), массивы, переключате- ли, процедуры или функции. (описание): := (описание типа) | (описание массивов)! (бпи- сание переключателя) | (описание процедуры) В описании типа определяются простые переменные, а в описа- нии массивов — массивы, элементами которых являются перемен- ные с индексами. С описаниями переключателей мы уже знакоми- лись в разделе 2.7.2, а описания процедур (и функций) рассмотрим позднее. Перечисленным четырем вариантам описаний соответствует че- тыре различных вида имен. Еще один, пятый, вид имен — это мет-
102 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл 2 ::и, которые не описываются отдельными предложениями, посколь- ку сам факт появления идентификатора перед оператором и знаком : считается описанием метки. Например, запись х231:а:=0 означает, что х231 — метка, причем соответствующая именно это- му оператору присваивания. Поэтому если где-то в программе встре- тится оператор перехода go to х231, то следующим будет выполнять- ся этот оператор присваивания. Если в программе метка не описана (например, нет оператора, которому предшествует ALFA:), то нельзя употреблять оператор go to ALFA согласно общему правилу, запрещающему употребление имен неописанных объектов. В описании типа перечисляются идентификаторы определяемых тем самым простых переменных с указанием типа значений этих переменных, т. е. (описание типа): :=(тип) (список идентификаторов) (тип): :=real | integer | boolean (список идентификаторов): := (идентификатор) | (список иден- тификаторов ), (идентификатор) Семантика. Описание типа объявляет о существовании простых переменных указанного типа и закрепляет за ними иденти- фикаторы, перечисленные в списке. Примеры описаний типов: real х integer а, Л, k boolean В, сог2\ Первое из этих описаний означает, что вводится простая пере- менная с именем х, которая будет принимать вещественные значе- ния. Во втором примере описываются три простых переменных, ко- торые будут принимать только целые значения. В третьем описы- ваются две простые переменные, которые могут принимать логиче- ские значения true и false. Если при наличии описаний real х и integer а в программе встре- тится оператор а:=х, то должно быть обеспечено автоматическое округление вещественного значения х до ближайшего целого значе- ния и присваивание этого значения переменной а. Описание массива определяет не только имя и тип значений эле- ментов массива, но и структуру самого массива: его размерность, ко- личество элементов и наборы индексов для нумерации его значений. Тип массива задается так же, как и для простых переменных; ука- занием, что описывается массив, является служебное слово array, а структура массива задается списком граничных пар. Например, граничная пара 0 : 10 означает, что имеется 11 элементов с индек- сами 0,1,2, ..., 10; граничная пара —3 : 5 означает 9 элементов с ин- дексами —3, —2, —1,0, 1,2, 3, 4, 5. Сочетание из двух граничных пар 1 : 7, 0: 8 означает, что в массиве 7 строк по 9 элементов в каж- дой, причем элементы характеризуются парами индексов 1,0; 1,1;... ..., 1,8; 2,0; ...; 7,8. Описание массива может содержать список из
ОПИСАНИЯ В АЛГОЛЕ 103 21 li любого количества граничных пар, т. е. массив может быть любо?, размерности. Например, при описании real array х[—1 : 1, 2:3, 3:6] х представляет собой трехмерный массив в трех параллельных плос- костях (с индексами —1, 0 и 1), причем в каждой плоскости 2 стро- ки (с индексами 2 и 3) по 4 элемента (с индексами 3, 4, 5, 6). Всего в этом массиве 3x2x4=24 элемента. Внутри одного описания допускаются различные структуры мас- сивов. Например, описание integer array х, t/[l, 5], a, X, Y [0:10, От] сразу вводит пять массивов с целочисленными значениями элемен- тов, причем первые два — одномерные массивы из 5 элементов каж- дый, а последующие три — двумерные из 11 Х(п-Н) элементов каж- дый. Мы видим из этого примера, что в граничных парах употреб- ляются не только числа, но и переменные. Вообще в граничных па- рах могут употребляться любые арифметические выражения; эти выражения вычисляются, и вместо них подставляются их числовые значения. В нашем примере х, у, а также а, X, Y — это списки мас- сивов. Части описания х, у[1 : 5] и а, X, У[0: 10, 0: п] называются сегментами массивов. Синтаксис описаний массивов задается так: (граничная пара): ; = (арифметическое выражение): (арифме- тическое выражение) (список граничных пар): ;= (граничная пара) | (список гранич- ных пар), (граничная пара) (список массивов): := (идентификатор массива) | (список мас- сивов), (идентификатор массива) (сегмент массивов): := (список массивов) [(список граничных пар)] (описание массивов): :=аггау (сегмент цдссивов) | (тип) array (сегмент массивов) | (описание массивов), (сегмент мас- сивов ) Семантика. Описание массивов объявляет о существовании массивов переменных значений указанного типа и закрепляет за ними идентификаторы, перечисленные в списках. Граничные пары определяют структуру массивов. К моменту рассмотрения описания массивов значения всех переменных, входящих в выражения гра- ничных пар, должны быть известны. Если в описании массивов тип не указан, то по умолчанию под- разумевается тип real. (Это одно из проявлений принятой в боль- шинстве современных языков программирования так называемой концепции умолчания, которая означает, что для достижения лако- ничности можно опускать некоторые указания, и тогда вместо них автоматически будут подставлены некие стандартные варианты. По- скольку языки программирования рассчитаны на формальное ис-
104 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 полнение алгоритмов, все возможности применения концепции умол- чания явно оговариваются в описаниях языков.) В качестве примера работы с описаниями запрограммируем на алголе универсальную машину Тьюринга. Как мы знаем, в машине Тьюринга имеется бесконечная лента, вдоль которой передвигается автомат. Лента разбита на ячейки, в каждой из которых находится какой-нибудь символ. Автомат может поменять символ в обозреваемой ячейке, руководствуясь при этом программой, заданной в виде таблицы и определяющей (в зависимости от состояния автомата и от обозреваемого символа) три результата: новый символ в этой ячейке, шаг (направление сдвига автомата вдоль ленты) и новое состояние автомата. Все эти три действия кодируются в клетке на пересечении строки, соответствующей состоянию, и столбца, соответствующего обозре- ваемому символу. В отличие от машины Тьюринга, алгол позволяет работать не с символами, а с числами. Однако мы можем пронумеровать воз- можные символы Si на ленте и состояния qj автомата и оперировать вместо них соответствующими им номерами i и /. Нам нужно запрограммировать ввод закодированной ленты и программы машины Тьюринга, а также выполнение одного оче- редного такта работы этой машины (т. е. выполнение действий со- гласно очередной клетке программы), после чего выполнение тактов будет повторяться до появления в очередной клетке программы знака останова !, который мы будем кодировать числом 999. После этого нужно будет вывести кодировку ленты, преобразованной в результате работы нашей машины Тьюринга. Схема нашей алгол- программы выглядит так: 1°. Ввести ленту, программу, номер начального состояния и начальный номер обозреваемой ячейки. 2°. Пока номер состояния </^999, выполнять п. 3°. 3°. Выполнить действия согласно очередной клетке программы. 4°. Вывести ленту. Ленту будем кодировать в виде одномерного массива целых чисел о идентификатором ЛЕНТА. Его описание на алголе имеет вид integer array ЛЕНТА [1: N] Каждому элементу этого массива будет присваиваться значение номера символа, содержащегося в соответствующей ячейке ленты. Таким образом, записи в k-ю ячейку ленты символа s, будет соот- ветствовать оператор ЛЕНТА [fc]:=i. Программа машины Тьюринга — это двумерная решетка, в каждой клетке которой хранятся три числа — коды символа, направления движения и состояния автомата. Ее можно было бы описать при помощи трех двумерных массивов: integer array s 11 : п, 1 : ml integer array h 11 : n, 1 : ml
2.111 ОПИСАНИЯ В АЛГОЛЕ 105' integer array q [1 : п, 1 : т] или, что то же самое, integer array s, h, q [1 : n, 1 : m}, где n — число возможных состояний и т — число различных возможных символов на ленте. Тогда для каждого текущего состояния q{ и обозреваемого символа sj соответствующая клетка программы кодировалась бы значениями s[i, Д, hli, /] и q{i, /]. При такой организации эти три числа оказываются разнесенными по трем различным массивам, в одном из которых выделены все состояния, в другом все направ- ления сдвигов, а в третьем все заносимые символы для данной про- граммы. Однако, для разнообразия мы выберем другую форму и будем описывать программу в виде трехмерного массива ПРОГг integer array ПРОГ [1 : n, 1 : т, 1 : 3] Элемент ПРОГ [t, /, 1] — это числовой код символа, храняще- гося в клетке (i, /) программы. ПРОГ U, /, 2] — код шага (направления сдвига). ПРОГ h', j, 3] — код состояния, в которое нужно перейти со- гласно этой клетке. Сдвиг по ленте влево мы будем кодировать числом — 1, сдвиг вправо — числом +1 и неподвижность — числом 0. Если сопоставить трехмерный массив ПРОГ с рассмотренными нами выше двумерными массивами s, h и q, то ПРОГ [t, /, U=s[t, /] ПРОГ [i, /, 2]—hit, /] ПРОГ [i, /, 31=<7(1, /] Нам потребуются три целых переменных: СОСТОЯНИЕ — номер текущего состояния автомата. АДРЕС — индекс обозреваемой ячейки в массиве ЛЕНТА. СИМВОЛ — код (номер) символа, содержащегося в обозрева- емой ячейке. Теперь мы можем составить схему для пункта 3°, описывающую выполнение действий согласно очередной клетке программы машины Тьюринга. 3.1 °. Присвоить переменной СИМВОЛ значение символа из обозреваемой ячейки ленты. 3.2 °. Записать новый символ в обозреваемую ячейку (присвоить новое значение переменной ЛЕНТА {АДРЕС}). 3.3 °. Сделать шаг (изменить АДРЕС). 3.4 °. Установить новое состояние (изменить значение йеремен- ной СОСТОЯНИЕ). В соответствии с нашей схемой, алгольная реализация универ- сальной машины Тьюринга выглядит следующим образом: integer array ЛЕНТА 11 : М, ПРОГ (1 : п, 1 : т, 1 i 31j integer СОСТОЯНИЕ, АДРЕС, СИМВОЛ; ввод (ЛЕНТА, ПРОГ, СОСТОЯНИЕ, АДРЕС); for СИМВОЛ1.—ЛЕНТА {АДРЕС} while СОСТОЯНИЕ =H=999do begin ЛЕНТА {АДРЕС]:=ПРОГ {СОСТОЯНИЕ, СИМВОЛ, llj
ICG АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 АДРЕС:=АДРЕС+ПРОГ [СОСТОЯНИЕ, СИМВОЛ, 21 СОСТОЯНИЕ:=17РОТ [СОСТОЯНИЕ, СИМВОЛ, 3] end; вывод (ЛЕНТА) В начало алгольного текста мы поместили описания использу- емых величин, а далее следуют операторы, реализующие схему. (Пункт 3.1° мы совместили с заголовком цикла, так как синтаксис алгола все равно требует, чтобы начало заголовка цикла выглядело как оператор присваивания.) Ограниченность алгольной реализации по сравнению с абст- рактной универсальной машиной Тьюринга состоит в том, что массив ЛЕНТА не является бесконечным и его длина ограничена некоторым значением специально введенной переменной АЛ Ана- логично нам пришлось ограничиться определенными значениями п и ш. 2.12. Блоки и локализация В примерах из предыдущего параграфа мы описывали сначала все употребляемые объекты (массивы, простые переменные), а затем писали все операторы, в которых эти объекты используются. Такая конструкция, заключенная в операторные скобки begin и end, называется блоком. Блок — это один из видов операторов языка алгол. Подобно составному и условному операторам, блок явля- ется композиционным оператором, т. е. содержит в себе другие операторы. Отличие блока в том, что он представляет собой неко- торый замкнутый фрагмент программы, использующий внутри себя новые объекты; как правило, так оформляется некое достаточно самостоятельное вычисление. В программе может быть много бло- ков, если она подразделяется на большое количество таких подвы- числений. Синтаксис блока: ^0K>::=begin (список описаний); (список операторов) end (список описаний)::= (описание) | (список описаний); (опи- сание ) Принципиальное отличие блока от составного оператора со- стоит в том, что блок содержит не только операторы, но и описания, причем описания предшествуют операторам. Внешне отличить описание от оператора можно по первому символу: описание может начинаться с символов real, integer, boolean, array, procedure, switch, а оператор — с символов for, go to, if, begin, а также с начальной буквы метки или другого идентификатора (в случае операторов присваивания и процедуры, .в которых за идентификатором следует соответственно знак := или открывающая скобка). Как известно, описания вводят объекты и закрепляют за ними обозначающие их идентификаторы. Блок является областью су-
2.121 БЛОКИ И ЛОКАЛИЗАЦИЯ 107 шествования описанных в нем объектов. Эти объекты определены в пределах того блока, в котором они описаны. Их называют локаль- ными объектами блока, или объектами, локализованными в блоке. Такое закрепление объектов за блоками впервые было введено ав- торами алгола и получило название принципа локализации. Этот принцип в том или ином виде применяется во всех языках про- граммирования. Поскольку идентификаторы закрепляются за объектами, то в пределах блока один идентификатор может обозначать только один объект. Поэтому нельзя одинаково обозначать в одном блоке, например, простую переменную и массив, или переменную и метку. (Закрепление идентификаторов относится и к меткам, хотя они описываются не в начале блока; описанием метки является запись этой метки и двоеточия перед оператором.) Определим синтаксическое понятие алгол-программы'. (алгол-программа ):: = (блок) т. е. все программы, которые мы рассматривали до сих пор, фор- мально не были правильными, так как не представляли собой блоки (Они начинались не с begin, либо в них использовались неописанные идентификаторы.) Теперь мы в первый раз можем написать на алголе полностью правильную программу: begin real х; ввод (х); х:=х+1, вывод (х) end Это блок, в котором описывается простая переменная х и содер- жится три оператора: ввод х, присваивание х нового значения и вывод х. Поскольку блок — это частный случай оператора, то, согласно синтаксису, блоки могут быть вложены один в другой. Объекты, описанные в каждом блоке, могут употребляться только внутри него и становятся недоступными при выходе из этого блока. (По- этому локальные объекты вложенного блока недоступны во внешнем блоке.) Что касается описаний из внешнего блока, то их действие, как правило, распространяется и на внутренний блок; в частности, во внутреннем блоке можно употреблять все те же переменные, что и во внешнем блоке. Так, программа begin integer k\ £:=5; tn\ begin integer f, s; s:=0; for /: = ! step 1 until k do s:=s+Z; вывод (s) end end представляет собой блок, в который вложен внутренний блок с меткой tn. Во внутреннем блоке описываются переменные i и 6 и производятся вычисления с этими переменными и с переменной k, которая описана во внешнем блоке. Заметим, что оператор выводу может находиться только во внутреннем блоке, так как вне его переменная s не определена.
108 . АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 (Гл. 2 Итак, в каждом блоке могут употребляться объекты, локализо- ванные в нем самом или в одном из объемлющих его блоков. Опи- санные в объемлющих блоках объекты называются глобальными по отношению к этому блоку. В нашем примере глобальной яв- ляется переменная k по отношению к блоку с меткой т. Бывают исключения, когда глобальный объект не доступен во внутреннем блоке, потому что в нем тот же идентификатор описан заново, и тем самым этому идентификатору придан другой смысл. Например, в программе begin integer k; real s; s:=2.5; kt=10; mt begin integer i, s; s:=0; for i:=l step 1 until k do s:=s-H; вывод (s) end; вывод (s) end описаны две разные переменные с одинаковыми идентификаторами s, одна типа real во внешнем, а другая типа integer во внутреннем блоке. Тем самым глобальная переменная s становится недоступной в блоке с меткой т, где вычисляется как сумма арифметической прогрессии значение локальной переменной s. Это значение (равное 55) будет напечатано оператором вывод (s), находящимся в конце внутреннего блока. Оператор вывод (s) из внешнего блока напеча- тает значение глобальной переменной s (равное 2.5), которое было вычислено до входа во внутренний блок, являлось недоступным во время работы этого блока и снова стало доступным, когда мы вер- нулись во внешний блок. Таким образом, случайно совпадающие по имени с локальными глобальные переменные становятся недоступными до выхода из блока. Объекты, локализованные в блоке, порождаются при входе в него и исчезают при выходе. Поэтому результаты, полученные во внутреннем блоке, можно использовать во внешнем блоке, только если присвоить их значения глобальным переменным. В нашем примере мы могли бы в конце внутреннего блока написать k:=s и тогда сохранили бы в переменной k значение локальной переменной s. Локальные переменные — это внутренние, рабочие величины блока. Вся информация, которую он получает или от- дает во внешние блоки, проходит через глобальные переменные. Принцип локализации оказывается весьма полезным при созда- нии больших программ, написание которых не под силу одному человеку. В таком случае организуется группа из нескольких программистов. Руководитель разбивает задание на примерно одинаковые части и составляет схему, определяя при, этом интер- фейс, т. е. глобальные переменные всей программы, через которые ее. части будут взаимодействовать между собой. Каждая частичная программа, которую пишет человек, представляет собой большой блок, в котором описаны свои переменные. При этом обычно нет оснований бояться совпадения локальных идентификаторов в раз-
2.12] БЛОКИ И ЛОКАЛИЗАЦИЯ 109' личных блоках, так как эти блоки не будут вкладываться один в другой, а «параллельно» войдут в один общий блок всей программы, в кагором в соответствии с блок-схемой будут описаны глобальные переменные, обеспечивающие взаимодействие блоков, написанных различными исполнителями. Таким образом, каждый исполнитель может работать независимо, как бы отгородившись от остальных частей программы барьером из операторных скобок begin и end. Если какая-то часть оказывается слишком большой, ее можно снова поделить, определив с помощью более детальной схемы ин- терфейс между ее подчастями, которые тоже становятся блоками. Другое достоинство принципа локализации состоит в том, что блочная структура помогает экономно распределять машинную память при трансляции, т. е. при автоматическое преобразовании алгол-программы в машинную программу. Для каждого массива обычно отводится столько ячеек памяти, сколько элементов преду- смотрено описанием этого массива. В пределах одного блока каж- дый массив и каждая простая переменная помещаются на отдельное место в памяти (в принципе можно применять специальные алго- ритмы экономии памяти, которые иногда позволяют совмещать в памяти разные переменные; описанные в одном и том же блоке, но в большинстве трансляторов этого не делается). Если же имеется несколько независимых (параллельных) блоков, то переменные, локализованные в этих блоках, можно помещать на одно место. Рассмотрим пример. Пусть имеется программа, состоящая иа блоков с описаниями массивов: begin array А, В, С ... begin array х, Y ... begin array и, v, х ... end begin array v, w ... end end; begin array P, Q, R ... end end Для наглядности изобразим блочную структуру этой программы по отношению к описываемым массивам в такдм виде: {Л, В, С {х, У {и, v, х} {о, u»)}{P, Q, R}} 1 а з « а (Нам уже известно, что во втором и третьем блоках описыва- ются разные массивы х, в третьем и четвертом — разные массивы о.) ' Возможна схема размещения этих массивов в памяти, изобра- женная на рис. 2,1. Глобальным массивам А, В, С отводятся отдельные места на все время вычисления программу, так как в любой момент должна иметься возможность присвоить их элементам новые значения или воспользоваться их имеющимися значениями. Массивам из второго
по АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-бО [Гл. 2 и пятого блоков можно отвести одни и те же места, потому что эти блоки, а значит, и области существования их массивов не пересе- каются (т. е. эти массивы существуют не в одно и то же время). Аналогично совмещаются в памяти массивы из блоков 3 и 4. Хотя глобальный массив х из второго блока недоступен в третьем блоке, где имеется локальный массив с тем же именем, тем не менее 1 2 3 Ч 5 Номер блока. Рис. 2.1. Схема размещения массивов в памяти. третий блок не может использовать это место для своих массивов, так как он находится внутри второго блока и, вый- дя из третьего блока, мы «вспомним» массив к и сможем воспользоваться его ранее вычисленными значениями в самом втором блоке, а также во вложенном в него четвертом блоке. Таким образом, свойство недоступности одноименных глобальных переменных не позволяет экономить память, а только создает удобство свободного использования имен. Граничные пары для массивов вы- числяются хпри входе в блок, где эти массивы описаны. Поэтому, если гранич- ные пары являются не числами, а выраже- ниями, то переменные из этих выражений должны быть глобальными и к моменту входа в блок этим глобаль- ным переменным должны быть присвоены значения. (При входе в блок локальные переменные этого блока только возникают и еще не имеют определенных значений, так как не могло быть соответ- ствующих операторов ввода или присваивания.) Например, допустима запись begin integer /и, п; ввод (т, п)\ begin array а [1 : т, 1 : л|; ввод (а) ... но нельзя писать begin integer tn, п\ array а (I : т, 1 : п]. Вычис- ленные при входе в блок значения граничных пар остаются постоян- ными до выхода из этого бдока, даже если внутри него меняются значения глобальных переменных входящих в выражения для граничных пар. Но* при каждом новом входе в блок граничные пары вычисляются заново, с учетом происшедших изменений зна- чений глобальных переменных. Рассмотрим пример программы: begin integer i\ for i:=l step 1 until 10 do begin array all: Л, Ы1 : 2хЛ; integer k\ for &:=1 step 1 until i do begin a[k]:=kXi*> b[2xk—l]:=fe[2xfc]:=2x&+f end; вывод (a, b) > end end
2 Л 31 ПРОЦЕДУРЫ И ФУНКЦИИ П1 Во внутреннем блоке массивы а и b имеют переменные границы. При каждом из десяти последовательных выполнений внешнего цикла происходит выполнение внутреннего блока с новыми гра- ницами для а и Ь\ каждый раз массив а увеличивается на один элемент и массив b увеличивается на два элемента. Таким образом, выполняемый в этом блоке оператор вывод (а, Ь) будет с каждым разом выдавать все более длинные последовательности элементов массивов а и Ь. При ( = 1 печатаются числа 1 3, 3 При i=2 печатаются числа 2, 4 4, 4, 6, 6 При /=3 печатаются числа 3, 6, 9 5, 5, 7, 7, 9, 9 и т. д. При t=10 будут напечатаны следующие две группы чисел: 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 и 12, 12, 14, 14, 16, 16, 18, 18, 20, 20, 22, 22, 24, 24, 26, 26, 28, 28, 30, 30 Хотя метки не описываются при входе в блок, они локализу- ются в ближайшем объемлющем их блоке. Вне блока не может быть выполнен оператор перехода на метку из этого блока, поскольку эта метка (как и все локальные объекты блока) доступна только операторам, находящимся в самом этом блоке. Поэтому нельзя войти в блок иначе, чем через его начало; однако выйти из блока можно не только через его конец, но и с помощью оператора пере- хода (на метку из внешнего блока). 2.13. Процедуры и функции Процедура — это средство, позволяющее многократно исполь- зовать в разных местах программы один раз описанный фрагмент алгоритма. В математике (и в других областях науки) принято ссылаться на один раз описанные объекты. Например, введя функцию г/ \ х2+#2 f (Х> У) ~ sjn (Х — у) ’ мы можем потом в математическом тексте употреблять f(l, 3), /(7, 83), f(a, b+c). Здесь f — имя функции, которое вводится, чтобы отличать разные функции друг от друга. Величины х, у — формальные параметры (аргументы), они используются только для обозначений, чтобы описать данную функциональную зависимость. Пары аргументов (1, 3), (7, 83), (а, Ь+с) представляют собой списки фактических параметров, по которым фактически будет вычисляться функция. Количество формальных и фактических параметров
112 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 1Гл. 2 должно быть одинаковым. Соответствие между ними устанавлива- ется порядком написания в списке: первый фактический параметр 1, 7 или а подставляется вместо первого формального параметра х, второй фактический 3, 83 или b+с подставляется вместо второго формального параметра у. В алголе сохранен тот же принцип работы с функциями, но он сформулирован более четко и широко развит, благодаря чему алгольные процедуры дают пользователю гораздо больше возмож- ностей, чем обычная математическая символика функциональной зависимости. 2.13.1. Простейшие процедуры. Мы не будем сразу излагать все возможности процедур, а дадим сначала определение простейшего вида процедуры, и затем будем постепенно дополнять и расширять его, пока не придем к окон- чательному определению. Итак, (описание процедуры) ::=procedure (идентификатор проце- дуры > ((список формальных параметров)); (тело процедуры) (идентификатор процедуры):: = (идентификатор) (формальный параметр )::= (идентификатор) (тело процедуры):: = (оператор) Напомним также определение оператора процедуры (см. раздел 2.7.3): (оператор процедуры )::= (идентификатор процедуры) ((список фактических параметров)) Как и в списке фактических параметров оператора процедуры, разрешается, чтобы элементы списка формальных параметров в описании процедуры разделялись либо запятыми, либо конструк- циями вида ) (строка букв):(. Употребление любого такого огра- ничителя параметра никак не сказывается на выполнении проце- дуры. Нет необходимости воспроизводить в списке фактических параметров разделители из соответствующего списка формальных параметров. Например, при описании procedure f(x) высота: (у) ... можно написать оператор процедуры f(a, 5). Семантика описания и оператора процедуры задается следующими правилами выполнения оператора процедуры. 1. Отыскивается описание процедуры с этим идентификатором. При этом учитывается принцип локализации идентификаторов: сначала выясняется, не описан ли этот идентификатор в том же блоке, где содержится оператор процедуры, если нет, то проверяется объемлющий блок, затем следующий объемлющий блок и так далее. Если ни в одном из объемлющих блоков не окажется искомого описания, то это будет свидетельствовать об ошибке в программе.
? I3J ' ПРОЦЕДУРЫ И ФУНКЦИИ 113, 2. В найденном описании процедуры все вхождения формальных параметров в оператор, являющийся телом процедуры, заменяются нан соответствующие им фактические параметры из данного опера- тора процедуры. (Исключением являются параметры-значения, которые будут рассмотрены в разд. 2.13.5.) 3. Затем получившийся в результате замены оператор выпол- няется так, как если бы он был написан на том месте, где написан оператор процедуры. Когда вместо формальных подставляются фактические пара- метры, они там, где нужно, берутся в скобки. Так, в рассмотренном примере при подстановке вместо у значения Ъ+с (и вместо х значе- ния а), мы получаем выражение ' sinfx—(6-f-c)) Если тело процедуры представляет собой блок и в нем описаны объекты с такими же именами, как и в фактических параметрах выполняемого оператора процедуры, то, чтобы не возникало пу- таницы при подстановке фактических параметров, локальные объ- екты тела процедуры автоматически переименовываются, т. е. их идентификаторы заменяются на другие. Из третьего правила выполнения оператора процедуры следует,, что после выполнения тела будет выполняться оператор, следую- щий за оператором процедуры. В целом соответствие фактических и формальных параметров, должно быть таким, чтобы в результате подстановки тело проце- дуры осталось правильным оператором языка алгол. Рассмотрим пример. Пусть требуется вычислить выражения 10 В*-1 10 k = s а = S sin7, 6= s ^[/]- 1=1 /=5 /=6 Все три выражения представляют собой результаты сумми- рования, но в первом складываются квадраты последовательных целых чисел, во втором — синусы, а в третьем последовательные- элементы некоторого вектора. Алгол позволяет написать одну процедуру суммирования, которую можно использовать во всех трех случаях: procedure СУМ(/) по: (т) от: (х) до: (у) рез: (s); begin s:=0; for m:=x step 1 until у do s:—s+f end Формальными параметрами этого описания- процедуры являются суммируемая величина f, параметр цикла суммирования т, началь- ное и конечное значения этого параметра х и у и переменная $, которой нужно присвоить значение результата суммирования. Программа вычисления трех искомых выражений может выгля- деть так:
114 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл 2 begin real k, a, b, В-, integer i, j; array P [6 : 10]; procedure СУМ (f) no: (m) от: (x) до: (у) рез: (s); begin s:=0; for m:=x step 1 until у do s:=s+f end; ввод (В, P): СУМ (if 2, i, 1, JO, k); СУМ (sin(j), j, 5, B|2—1, а); СУМ(Р1)], j, 6, 10, b); вывод (k, a, b) end При выполнении первого оператора процедуры СУМ (i f 2, i, 1, 10, k) на его место подставляется составной оператор, являющийся телом процедуры СУМ, с заменой параметров: if2 вместо /, i вместо т, I вместо х, 10 вместо у и k вместо s. Этот оператор при- обретает вид: begin й:=0; for i: = l step 1 until 10 do k:=k+i]2 end В результате его выполнения значением k становится искомая ю сумма X i 12. Аналогично при выполнении второго оператора процедуры тело процедуры приобретает вид: begin а:=0; for /:=5 step 1 until Bf2—1 do a:=a+sin(j) end При выполнении третьего оператора процедуры вместо него подставляется составной оператор: begin b:—0-, for 6 step 1 until 10 do b:=b+Plj] end Таким образом, изменяя фактические параметры, мы можем менять не только исходные значения параметров, но и формулы выражений, по которым будет вестись счет. 2.13.2. Локализация процедур. Как и все алгольные описания, описание процедуры локализу- ется в том блоке, где оно написано. Иначе говоря, описанной в блоке процедурой можно пользоваться в самом этом блоке и в со- держащихся в нем блоках, но нельзя обращаться к ней из других блоков. В теле процедуры могут встречаться формальные параметры, локальные переменные блоков, входящих в тело процедуры (если такие блоки имеются), и глобальные переменные из объемлющих блоков. Рассмотрим пример: begin real d; procedure P(x, n)\ begin integer i; for i: = l step 1 until n do d:=d+xli] end; d:=0; begin array b П : 21; real a; a:=3; bl 1 J: =£121:=1; P(b, 2); вывод(а) end; вывод(а) end Здесь имеются три блока: блок программы, тело процедуры и внутренний блок программы. В теле процедуры описана локальная
2.13) ПРОЦЕДУРЫ И ФУНКЦИИ 115- переменная I, в общем и внутреннем блоках описаны одноименные переменные а. (Чтобы читателю было удобнее их отличать, мы специально ввели обозначение &.) Одним из параметров процедуры, является идентификатор массива, элементы которого нужно про- суммировать и прибавить к исходному значению глобальной пере- менной а. Возникает вопрос, какая из двух одноименных перемен- ных а имеется в виду. Одна из них описана в блоке, объемлющем описание процедуры, а другая — в блоке, объемлющем оператор процедуры. Согласно правилам алгола, глобальными по отношению к телу процедуры считаются переменные из блоков объемлющих описание процедуры. Поэтому в нашем случае оператор Р[Ь, 21 в результате подстановки заменяется на блок begin integer i; for t: = l step 1 until 2 do d:=d+b[i] end где a — переменная а, описанная в общем блоке. При выполнении- этого оператора будет взято исходное значение d, равное 0, и к нему прибавятся Ml 1=1 и М2]=1. Затем произойдет вывод зна- чения локальной переменной а из внутреннего блока. Это значение- было вычислено оператором присваивания а:=3 и больше не из- менялось. Следующий оператор вывода выдаст значение глобальной; переменной а=2. Таким образом, напечатается сначала число Зг а потом 2. 2.13.3. Ограничения на фактические параметры. Как правило, описание процедуры накладывает определенные- ограничения на фактические параметры, которые могут подстав- ляться вместо употребляемых в теле процедуры формальных пара- метров. Эти ограничения вытекают из общего правила: в результате- подстановки должен получиться оператор, правильный с точки зрения языка алгол. (Заметим, что и само тело процедуры по оп- ределению должно быть правильным оператором.) Например, пусть процедура F описана следующим образом: procedure F(a, b, с, d, е, f, g; h, i, k)\ begin ai=b:=c+dli\-, e(fy, go to if g then h else М2] end Из этой записи можно вывести следующие ограничения на фак- тические параметры данной процедуры. Фактические параметры, соответствующие формальным пара- метрам а и Ь, должны быть переменными, причем либо обе типа integer, либо обе типа real, поскольку они употребляются в левой части одного оператора присваивания, правой частью которого^ является арифметическое выражение. Фактический параметр, со- ответствующий с, должен быть арифметическим выражением; в. частном случае это может быть число или переменная. Формаль- ному параметру d должен соответствовать фактический параметр,, являющийся идентификатором массива, причем обязательно од-
116 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 номерного. (Нельзя, чтобы этот фактический параметр был пере- менной с индексами. Если бы мы подставили г[1] вместо d, то в теле процедуры получилась бы неправильная запись г[1 ] [Л.) Фор- мальному параметру I должно соответствовать арифметическое выражение. Фактический параметр, подставляемый вместо пара- метра е, должен быть идентификатором процедуры. Что касается формального параметра f, то мы не можем наложить никаких ог- раничений на соответствующий ему фактический параметр, если не знаем, какой конкретный фактический параметр соответствует формальному параметру е. (При разных заменах параметра е воз- никают разные ограничения на замену параметра f.) Фактический параметр, соответствующий формальному параметру g, должен быть логическим выражением. Параметру h должно соответствовать именующее выражение. Параметр k должен заменяться на иденти- фикатор переключателя, причем в соответствующем переключа- тельном списке должно быть не менее двух элементов. 2.13.4. Спецификации. На предыдущем примере мы увидели, что анализируя тело про- цедуры, можно узнать об ее возможных фактических параметрах многое, но не все. Например, мы ничего не выяснили о том, каким может оказаться фактический параметр, подставляемый вместо /. Мы узнали, что параметры а и b должны быть одинакового типа, но не знаем, какого именно: integer или real. Между тем от этого может существенно зависеть выполнение оператора процедуры. Например, в описании процедуры procedure f(a); а: =0.6; тело является оператором присваивания, в правой части которого на- ходится вещественное число, а в левой — формальный параметр. При выполнении оператора f(x), если фактический параметр х будет переменной типа integer, то потребуется округлить результат и присвоить .переменной х значение 1. Если же переменная х типа real, то ей будет присвоено без округления значение 0.6. Таким образом, присваивание выполняется с округлением или без него в зависимости от типа фактического параметра. В алголе разре- шается писать такие неопределенные описания процедур, так как в момент выполнения соответствующего оператора процедуры, после подстановки фактических параметров, в любом случае полу- чится правильный оператор и тогда будет ясно, нужно ли выпол- нять округление результата. Однако, если автор программы за- ранее знает, какого типа должен быть фактический параметр дан- ной процедуры, то, чтобы облегчить трансляцию и понимание программы, он может указать этот тип в описании процедуры в виде так называемой спецификации. В нашем случае с включением спецификации описание процедуры может получиться в виде: procedure f(a); integer а; а-. =0.6;
2.13] ПРОЦЕДУРЫ И ФУНКЦИИ 117 Посредством этой спецификации мы сообщили, что вместо формаль- ного параметра а можно задавать фактический параметр только типа integer. Теперь, если переменная х типа real, то оператор f(x) становится незаконным. В общем случае спецификации помещаются в описании про- цедуры перед ее телом и в них сообщаются типы формальных пара- метров, т. е. типы фактических параметров, которые могут под- ставляться вместо этих формальных. Если процедура рассчитана на фактический параметр разных типов, то для соответствующего формального параметра спецификация не указывается. С учетом спецификаций синтаксис описания процедуры можно несколько уточнить: (описание процедуры) ::=procedure (идентификатор проце- дуры) ((список формальных'параметров)); (совокупность спецификаций) (оператор) Совокупность — это список, который может быть и пустым: (совокупность спецификаций)::= (пусто) | (список специфи- каций ); (список спецификаций)::= (спецификация) | (список специ- фикаций ); (спецификация) (спецификация):: = (спецификатор) (список идентификаторов) (спецификатор )::=real | integer | boolean | array | real airay | integer array | boolean array | switch | string | procedure j real procedure j integer procedure | boolean procedure | label (список идентификаторов)::= (идентификатор) | (список иден- тификаторов ), (идентификатор) — Спецификаторами выражаются свойства, которые можно ука- зывать при формальных параметрах как характеристику тех фак- тических параметров, которые должны соответствовать этим фор- мальным. Мы видим, что большинство спецификаторов совпадает со служебными словами описаний, с которыми мы познакомились в разд. 2.11. Кроме того, допускаются спецификаторы string (строка) и label (метка). Отличие спецификации от описания состоит в том, что спецификация не вводит нового объекта, а только сообщает о свойствах уже введенного формального параметра. Например, спецификация array х (или, что то же самое, real array х) означает, что формальному параметру должен соответст- вовать в качестве фактического параметра идентификатор вещест- венного массива; спецификация real у означает, что соответствую- щий фактический параметр должен быть арифметическим выраже- нием. При спецификации label г соответствующим фактическим параметром должно являться именующее выражение, а при специ- фикации string и соответствующий фактический параметр должен
118 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 (Гл. 2 быть строкой (т. е. заключенной в кавычки последовательностью символов алгола). 2.13.5. Параметры-значения. t В разделе 2.13.1 мы рассмотрели обращение к процедуре СУМ оператором CyM(sin(j), j, 5, Bf 2—1, а). В результате подстановки фактических параметров тело процедуры приобрело вид: begin а:=0; for /:=5 step 1 until В\2—1 do a:=a+sm(j) end Недостаток этой процедуры состоит в том, что при каждом вы- полнении цикла заново вычисляется одна и та же величина В f 2—1. Лучше было бы в самом начале узнать, чему она равна, и в даль- нейшем использовать это значение. С такой точки зрения параметры естественно разделяются на два класса. Для одних параметров важно, как они написаны, потому что в таком виде их необходимо подставить в тело процедуры. (В нашем примере это фактические параметры sin(j), j, а, подставляемые вместо формальных пара- метров /, /и, s.) Для других параметров не имеет значения форма их записи, о таком параметре нужно только знать, чему равно его значение перед выполнением процедуры, так как именно это зна- чение фактически требуется подставить в тело процедуры. Таковы в нашем примере параметры 5 и В|2—1, подставляемые вместо формальных параметров х и у. В алголе есть возможность выде- лить при описании процедуры формальные параметры, для которых важно знать только значения фактических. Такие формальные параметры называются параметрами, вызываемыми по значению или просто параметрами-значениями. Остальные формальные пара- метры называются параметрами, вызываемыми по наименованию. или заменяемыми параметрами. Параметры-значения перечисляются в специальном списке зна- чений, который начинается служебным словом value (значение) и пишется сразу после списка формальных параметров. С исполь- зованием такого списка описание процедуры СУМ может выгля- детьтак: procedure СУМ(/, т, х, у. s); value х, у\ integer т. х, у\ real /, s; begin s:=0; for /n:=x step 1 until у do s:=s+f end Включенные в список значений формальные параметры х и у не будут заменяться в теле процедуры на соответствующие им фак- тические. Вместо этого, в начало тела будут добавлены описания переменных х и у типа integer (в соответствии с их спецификацией). Тем самым тело данной процедуры превратится из составного опе- ратора в блок. Кроме того, при обращении к процедуре в начало этого блока будут автоматически включены операторы присваи- вания переменным х и у значений соответствующих фактических
2.13] . ПРОЦЕДУРЫ И ФУНКЦИИ 119 параметров. Параметры Д т и s, не включенные в список значений, будут обычным образом заменяться на соответствующие фактические. В частности, при выполнении оператора процедуры СУМ {sin (j), j, 5, 73 f 2 — 1, a) па его место будет подставляться соответственно блок begin integer х, у\ х:=5; у.=В f 2—1; for j:=x step 1 until у do a:=a+sm(j) end Аналогично будут подставляться блоки с описанием integer х, у и при других обращениях к процедуре СУМ. Параметры, входящие в список значений, обязательно должны входить и в список спецификаций. Неправильное включение параметра в список значений приво- дит к ошибочной программе. Так, в нашем примере, если бы мы включили в список значений параметр /, то, присвоив ему началь- ное значение, мы потом в цикле все время прибавляли бы это зна- чение, не изменяя его. Например, оператор СУМ (i 12, Д 1, 10, k) заменился бы на блок begin real Д integer х, у\ х: = 1; у: = 10; for к=х step 1 until у do k\=k+f end Аналогично, если включить в список значений параметр т, то, например, при обращении СУМ (Plj], j, 6, 10, b) при подстановке фактического параметра j произойдет начальное присваивание m:=j, после чего в теле процедуры не произойдет подстановки / вместо имени т. В результате при выполнении цикла будет ме- няться значение/n, a j останется неизменным, так что подставляемый вместо f параметр P1/J будет при каждом суммировании §+/ браться с одним и тем же значением /: begin integer т, х, у\ х:=6; у: = 10; for т:=х step 1 until у do b:=b+P[j] end Если в список значений некоторой процедуры включен формаль- ный параметр х со спецификацией array х (или real array х, или integer array х, или boolean array х), то при обращении к этой про- цедуре в начало ее тела будет автоматически включено описание массива х с такими же граничными парами, как в описании массива, выступающего в роли фактического параметра, а также будет включен цикл присваивания всем элементам массива х значений соответствующих элементов массива — фактического параметра. Таким образом, возникают дополнительные расходы времени и места, и поэтому включение таких фактических параметров в спи- сок значений не всегда целесообразно (особенно, если массив со- держит много элементов).
120 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 Формальный параметр, специфицированный как переключатель (switch х), или как процедура (procedures) или как строка (strings), нельзя включать в список значений. 2.13.6. Процедуры-функции. К описанию обычной процедуры можно обращаться только опе- ратором процедуры. Однако в алголе имеется возможность описать процедуру, к которой можно обращаться и как к функции, являю- щейся операндом выражения. После выполнения функции должно остаться значение, которое будет использоваться как операнд. Для этого разрешается писать в теле процедуры не совсем обычный оператор присваивания, у которого в левой части находится само имя этой процедуры. Например, можно описать: real procedure F(s, у)', F-.—x+y, Это присваиваемое значение считается значением функции. Вычисленное значение функции используется как мгновенное зна- чение операнда и не сохраняется для дальнейшего использования. Если оно требуется повторно, то его нужно вычислять заново, например: fe:=(a+F(:, c+6))xF(i, c+b) либо при первом вычислении указывать как правую часть опера- тора присваивания, например: + =F(j, с+6); 6:=(a+d)Xd; В теле процедуры-функции может выполняться большая про- грамма. Если в ней несколько раз присваиваются значения иден- тификатору процедуры, то в качестве значения функции берется результат последнего присваивания. Идентификатор процедуры нельзя использовать в ее теле как операнд, а можно употреблять только в левой части оператора присваивания. Бывает так, что имеет смысл обращаться к одной и той же про- цедуре попеременно: иногда как к функции, а иногда как к опе- ратору. Такая возможность обеспечивается тем, что к процедуре, допускающей обращение в виде функции, можно обратиться и как к оператору: ...; F(i, с+6); ... При этом происходит обычное выполнение оператора процедуры. В теле процедуры производится присваивание значения иденти- фикатору F процедуры, но это значение не может использоваться и сразу же пропадет. (Однако могут сохраниться результаты других вычислений, выполненных в теле процедуры, если в этом теле про- изводятся присваивания значений глобальным переменным.) Итак, первая особенность процедур, допускающих обращение в виде функций, состоит в присваивании значения идентификатору процедуры. Вторая особенность (логически вытекающая из первой) состоит в том, что требуется снабжать описание такой процедуры
2.13] ПРОЦЕДУРЫ И ФУНКЦИИ; 4 121 типом, который приписывается вырабатываемому этой процедурой операнду — функции. Например, при описаниях integer d\ real procedure F... если встречается оператор d‘.=F(i, сА-Ь"), то алгол предписывает округление вещественного значения функции F, чтобы получить целое значение d; при описании integer procedure F... такое округ- ление не потребуется. Чтобы еще раз продемонстрировать влияние фактических па- раметров на алгоритм, описываемый телом процедуры, рассмотрим следующий пример процедуры-функции, вычисляющей скалярное произведение двух векторов: real procedure СКАЛ ПР (х, у, i, n); real х, у, integer i, n; begin real s; s:=0; for t: = l step 1 until n do s:—s+xXy, СКАЛПР-.—s end (Нам пришлось ввести переменную s для накопления суммы, по- тому что нельзя употребить оператор СКАЛПР-.=СКАЛПР-\-хху, содержащий идентификатор процедуры СКАЛПР в своей правой части.) Пусть эта функция используется в выражении из правой части в следующем операторе присваивания: Ь-.==\+СКАЛПР(А[(\, Bill, i, 10)XC7GW7P(D[l, /], Dl/, 1], /, Ю)+СКАЛПР(АЦ— 1], x\(i— 1), i, n+1); При вычислении выражения мы сначала находим значения всех операндов в порядке слева направо, а затем выполняем операции над ними. В нашем случае заранее как бы заготавляются места для четырех операндов: единицы и трех значений функции СКАЛПР. Получив от процедуры значение СКАЛПР, мы запоминаем его как значение очередного операнда, и оно тут же забывается как СКАЛПР.- Иначе говоря, как значение идентификатора СКАЛПР оно существует только в момент непосредственно после обращения, а в дальнейшем не может быть использовано. При первом обращении из нашегр выражения тело процедуры после подстановки будет содержать оператор цикла: for t:=l step 1 until 10 do s:=s-MUlxB[i] Здесь для двух векторов А и В вычисляется их скалярное про- изведение (сумма попарных произведений элементов этих векторов). Во второй раз мы скалярио.умножаем первую строку матрицы D на первый столбец той же матрицы: for /:=1 step 1 until 10 do s:=s+D [1, j]XD[j, 1] В последний раз оператор цикла приобретает вид: for j:=l step 1 until n+1 do s:=s+AU—1]хх|(/—1)
122 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 Тем самым вычисляется многочлен причем массив А представляет собой вектор коэффициентов данного многочлена. 2.13.7. Полный синтаксис описания процедуры. Теперь мы можем систематизировать те элементы синтаксиса описания процедуры, с которыми познакомились в разд. 2.13. (описание процедуры):: = (заголовок процедуры) (оператор) (заголовок процедуры):: =procedure (идентификатор процеду- ры) (совокупность формальных параметров); (совокупность значений) (совокупность спецификаций) | (тип) procedure (идентификатор процедуры) (совокупность формальных параметров); (совокупность значений) (совокупность спе- цификаций ) (совокупность формальных параметров)::= (пусто) | ((список формальных параметров)) (совокупность значений)::= (пусто) | value (список иденти- фикаторов ); (совокупность спецификаций)::= (пусто); | (список специфи- каций ); Заметим, что в силу нашего синтаксиса допускаются процедуры не только без списка значений и списка спецификаций, но и без списка формальных параметров, например: real procedure /; /: = 1; В данном случае процедура-функция f присваивает своему идентификатору всегда значение 1. Возможны более содержатель- ные процедуры без параметров, производящие вычисления над глобальными переменными и над локальными переменными тела процедуры. 2.14. Рекурсивные процедуры Мы знаем, что телом процедуры может служить любой опера- тор. В нем могут встречаться операторы процедуры, и тогда во время выполнения данной процедуры выполняются те процедуры, которые соответствуют этим операторам. Если при выполнении некоторой процедуры возникает необходимость обратиться к ней же самой, то такая процедура называется рекурсивной. Рекурсив- ная процедура/может обращаться самак себе или непосредственно» или косвенно; в последнем случае гфм^едура f обращается к другой процедуре Д, которая непосредственно или косвенно обращается к процедуре f. Случай, когда каждое обращение к процедуре f влечет за собой еще одно обращение к той же процедуре, не представляет прак- тического интереса, так как сводится к бесконечному циклу. В ре- альных алгоритмах обращения из рекурсивной процедуры к ней
2.141 РЕКУРСИВНЫЕ ПРОЦЕДУРЫ 123 самой происходят не бесконечно, а при выполнении некоторых -условий, которые когда-нибудь исчерпываются. Ключом к пони- манию рекурсии является правило выполнения оператора проце- дуры, состоящее в замене формальных параметров в теле проце- дуры на фактические и в подстановке преобразованного таким образом тела на место оператора процедуры. В качестве примера рассмотрим рекурсивную процедуру вы- числения факториала: procedure ФАКТ (п) РЕЗУЛЬТАТ1, (f)-, integer п, f\ If п=1 then f: = l else begin ФАКТ (п—1, /); f:=nxf end Проследим, как будет выполняться эта рекурсивная процедура в программе begin integer k\ procedure ФАКТ (n, f)...; ФАКТ (3, k)\ вывод (k) end (Мы обозначили многоточием описание процедуры ФАКТ, приве- денное выше.) Подставляя вместо оператора ФАКТ (3, k) тело процедуры с заменой параметра f на k и параметра п на 3, мы получаем: if 3=1 then k: — l else begin ФАКТ (3—1, k); k:=3xk end В этом условном операторе, поскольку 3=/=1, нужно выполнять оператор процедуры ФАКТ (3—1, к). Аналогично подставляем вместо него тело процедуры с заменой п на 3—1 и заменой f на к и получаем следующий расширенный оператор: if 3=1 then k: = \ else begin if 3—1 = 1 then k: = \ else begin ФАКТ (3—1—1, k)-, k:=(3— l)Xk end; k:=3xk end Выполнять этот оператор нужно с того места, куда только что подставлено тело процедуры ФАКТ, т. е. с первой операторной скобки begin. Мы снова получили текст с оператором процедуры ФАКТ, ко- торый нужно выполнять, так как 3—1#=1. Подставляем на место этого оператора тело процедуры ФАКТ на этот раз с заменой п на 3—1—1 и f на k. Получаем if 3=1 then /г. = 1 else begin if 3—1=1 then k: = \ else begin if 3—1—1=1 then /г:=1 else begin ФАКТ (3—1—1—1, *); fc:=(3—1—l)x£ end; fe:=(3—l)Xfe end; ki=3xk end Программа снова содержит оператор процедуры ФАКТ, однако на этот раз не нужно выполнять этот оператор, потому что условие 3—1—1 = 1 является истиной. В содержащем это условие условном операторе выполняется присваивание Л:=1. Затем выполняется
!24 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 £Гл. 3 оператор й:=(3—l)Xfe, который дает результат k=2, после чего выполнение оператора k:=3xk приводит к получению окончатель- ного результата k=6. На этом выполнение оператора ФАКТ (3, k) завершено, и следующим выполняется оператор вывод (k). Рекурсивное выполнение процедур возможно и при обращении к функциям. Так, нашу процедуру вычисления факториала можно переписать в виде: integer procedure ФАК(п)\ integer п; if n=l then ФЛК: = 1 else ФАК:=пХФАК(п— 1); По сравнению с предыдущим описанием процедуры ФАКТ, это описание немного укоротилось, так как отпала необходимость выполнять оператор процедуры ФАКТ (п—1, k), а вместо этого непосредственно в выражении используется как множитель функ- ция ФАК(п—1). Последовательность рекурсивных обращений в этом случае будет аналогична предыдущей. Пусть исходным был - оператор 1г:=ФАК(3). Чтобы вычислить значение функции, выпол- няется тело: if 3=1 then ФАК: = 1 else ФА К: =ЗхФА Я(3—1) Условие 3=1 не верно, и поэтому вычисляется ФАК(3—1) посред- ством выполнения тела: if 3—1 = 1 then ФЛК:=1 else ФЛК:=(3— \)ХФАК (3—1—1). Снова условие 3—1 = 1 не верно, и мы вычисляем ФАК (3—1—1), выполняя тело if з_1—1 = 1 then ФАК‘.=\ else ФАК:=(3— 1—1)ХФЛК(3— 1 — 1-D На этот раз условие верно, производим присваивание ФАК=1 и это значение подставляем вместо ФАК(3—1—1) в выражение для вычисления ФАК(3—1). Получаем ФАК=(3—1)’Х1=2 и под- ставляем это значение в выражение для вычисления ФАК (3), что дает нам ФЛЛ=ЗХ2=6. Это окончательное значение ФАК присваивается переменной k из левой части оператора k: =ФАК(3). Заметим, что можно (а для ускорения выполнения процедуры и нужно) включить параметр п в список значений: value п. Иначе, если соответствующим фактическим параметром является ариф- метическое выражение, оно каждый раз будет вычисляться заново. Мы этого не сделали, чтобы не усложнять изложение выполняемых подстановок тела процедуры. Преимущество рекурсивных процедур состоит в том, что они позволяют обеспечить хорошую наглядность. Например, наше описание рекурсивной процедуры ФАК похоже на обычное мате- матическое определение факториала: 1!=1, nl=nX(n—1)!. Однако с точки зрения скорости вычислений такая процедура менее эффективна, чем вычисление факториала при помощи обык- новенного цикла: /: = 1; for t: = l step 1 until n do f:=fxi
2 15J ИСПОЛЬЗОВАНИЕ КОММЕНТАРИЕВ 12J Рекурсивными процедурами нужно пользоваться не в таких тривиальных случаях, когда ясно, как обойтись без рекурсии. Она всегда замедляет счет, но приносит облегчение в некоторых слу- чаях, когда без нее трудно описать вычислительный процесс. , Еще одним примером употребления рекурсии может служить следующая процедура вычисления чисел Фибоначчи: integer procedure ФИБ(п); value п; integer n; ФИБ: =if n—Q then 0 else if n=l then 1 else ФИБ (п—1)+ФИБ(п—2) В описании этой процедуры выражен тот факт, что каждое число Фибоначчи, начиная с третьего, является суммой двух пре- дыдущих. Потеря эффективности за счет рекурсии проявляется в данном случае отчетливо: на каждом шаге мы выполняем, как правило, два новых обращения к процедуре ФИБ, а всего при вычислении ФИБ(п) число рекурсивных обращений при увеличении значения п возрастает по закону геометрической прогрессии. То же самое вычисление можно выполнить, не прибегая к ре- курсии, если воспользоваться следующей процедурой: integer procedure ФИБ1 (п); value и; integer и; begin integer i, ФИБ, ФИБп, ФИБпп: if n=0 then ФИБ\:=0 else if n=l then ФИБ\:~1 else begin ФИБп: = 1; ,ФИБпп: =0; for i:=2 step 1 until n do begin ФИБ:=ФИБп+ФИБпп: ФИБпп:=ФИБп; ФИБп:—ФИБ end; ФИБ\:=ФИБ end end В принципе, любую алгол-программу с рекурсией можно за- менить на эквивалентную ей алгол-программу без рекурсии. 2.15, Использование комментариев Язык алгол позволяет включать в программу любые поясни- тельные тексты в виде комментариев, которые предназначаются только для людей и игнорируются при машинном исполнении про- граммы. Синтаксис алгола предусматривает специальные правила оформления таких комментариев, позволяющие формально усга- овить, где начинается и где кончается текст комментария. Допускаются комментарии двух типов: (комментарий первого типа)::=соттеп1 (любой текст, не содержащий точку с запятой);
426 ' АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 (комментарий второго типа )::= (любой текст, не содержащий символов ; или end или else) Комментарий первого типа можно писать в алгол-программе после любого символа begin или после любой точки с запятой. Комментарий второго типа можно писать после end. Признаком того, что такой комментарий закончился, служит появление любого из трех символов: точки с запятой, end или else. (По синтаксису алгола после end в программе может следовать только точка с за- пятой или end или else, это позволяет распознать комментарий второго типа.) Так, в примере begin comment это легкий пример; г.=г-|-2; if г < q then <?:=? end пример кончился; текст это легкий пример является комментарием первого типа, а текст пример кончился — комментарий второго типа. 2.16. Методы разработки программ 2.16.1. Программирование по принципу сверху вниз. Когда поставленная задача требует составления сложной про- граммы, обычно человеку не удается сразу представить себе деталь- ный вид этой программы в ее эффективной реализации, так как для этого пришлось бы удерживать в голове непомерно большой объем информации. Сделать объем осмысливаемой информации посиль- ным позволяет метод программирования сверху вниз, который основывается на том, что задача разбивается на несколько более простых частей и при дальнейшей детализации мы думаем о каждой части отдельно. Части задачи выделяются таким образом, чтобы их можно было программировать независимо. При этом состав- ляется план решения задачи, пунктами которого и являются вы- деленные части. План оформляется либо в виде линейной схемы, как это будет сделано ниже, либо в виде блок-схемы, которая будет описана в 2.16.2. Затем аналогично производится детали- зация выделенных частей. В принципе допускается любое количе- ство таких уровней детализации, которые вводятся до тех пор, пока не станет полностью ясно, как программировать все выделенные части задачи в виде отдельных фрагментов алгоритма. Для того, чтобы можно было программировать фрагменты алго- ритма независимо, необходимо четко определить связи между этими «фрагментами, строго сформулировать, какие данные получает и какие результаты выдает каждый фрагмент. Эти связи называются интерфейсом. Фактически мы уже пользовались методом программирования сверху вниз, например, в разд. 2.10.1, когда разрабатывали про- грамму для вычисления sm(x). Теперь мы применим этот метод к разработке еще одной программы.
2 1б] МЕТОДЫ РАЗРАБОТКИ ПРОГРАММ 127 Пусть дана прямоугольная матрица, состоящая из т строк и п- столбцов. Требуется найти строку, сумма элементов которой мак- симальна, найти в этой строке ее минимальный элемент и вычислить, сумму элементов того столбца, которому принадлежит этот эле- мент. Начнем с того, что введем необходимые обозначения для на- чальных данных и результатов вычисления и уточним постанофсу задачи. Матрицу обозначим через а, ее элементы будем обозначать через ац при и Номер строки с максимальной сум- мой элементов обозначим через k. Номер столбца, в котором нахо- дится минимальный элемент этой строки, обозначим через /. Сумму элементов этого /-го столбца обозначим через s. Теперь нужно уточнить постановку задачи. В матрице может оказаться несколько строк с одинаковой максимальной суммой элементов, а в каждой такой строке может оказаться несколько равных минимальных элементов. Поэтому для определенности будем искать самую первую из строк с максимальной суммой эле- ментов, а в ней самый первый из минимальных элементов. Теперь мы можем составить исходную схему требуемого вы- числения: 1°. Ввести значения /и, п и матрицу а. 2°. Вычислить номер k первой из строк с максимальной суммой элементов. 3°. Вычислить номер I первого минимального из элементов akJ^ т 4°. Вычислить s = 2 аи- 5°. Вывести значения A, Z, s. Согласно правилам алгола (см. раздел 2.12) значения /и, п долж- ны быть введены не в том блоке, в котором вводится матрица а- Поэтому мы разделяем пункт 1° на два подпункта: 1.1°. Ввести значения т, п. 1.2°. Ввести матрицу а. Пункт 2° можно детализировать следующим образом: 2.1 °. Присвоить значение k: = l. п 2.2 °. Вычислить /: = S аи. / = i 2.3 °. Для i=2, 3, .... т выполнить: п 2.4 °. {Вычислить иг — 2 2.5 °. Если u>t, то присвоить значения &:=/; Пункт 3° детализируется так: З .Г. Присвоить значения /:=1; 3.2 °. Для /=2, 3...п выполнить:
128 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 1Гл. 2 3.3 °, {Если akj<Zt, то присвоить значения l:=j; t:=akJ}. Пункт 4° детализируется следующим образом: 4 . Г. Присвоить значение s:=0. 4.2 °. Для i=l, 2, ..т выполнить: 4.3 °. {s:=s+a/z} Пункт 5° не нуждается в детализации, так как он может быть реализован одним оператором вывода. Не обязательно доводить детализацию схемы до уровня отдель- ных операторов алгола, однако в этом примере мы проведем такую детализацию. Для этого уточним суммирование элементов строки для пунктов 2.2° и 2.4°. 2.2.1°. /:=0 2.2.2°. Для /=1, 2, ..., п выполнить: 2.2.3°. {/:=Н-а1у} и аналогично: 2.4.1°. ы:=0 2.4.2°. Для /=1, 2....п выполнить: 2.4.3°. {и:=и+йц} Теперь детализация схемы завершена. Для большей наглядности можно выписать окончательную схему в таком виде: 1.1°. Ввести значения т, п. 1.2°. Ввести матрицу а. 2.1°. Л:=1. 2.2.1°. /:=0. * 2.2.2°. Для / = 1, 2..п выполнить: 2.2.3°. {/:=/4-<zv}. 2.3°. Для i=2, 3, .... т выполнить: 2.4.1°. {«:=0 2.4.2°. Для /=1, 2, ..., п выполнить: 2.4.3°. {м:=«4-а7} 2.5°. Если u>t, то присвоить значения k:=i-, /:=«}. З.Г. Присвоить значения /: = 1; t:—akl. 3.2 °. Для /=2, 3, ..., п выполнить: 3.3 °. {Если akf<.t, то присвоить значения /:=/; 4.1 °. Присвоить значение s:=0. 4.2 °. Для i=l, 2, ..., т выполнить: 4.3 °. {s:=s+afz}. 5°. Вывести значения k, I, s. Схемы представляют собой важный и широко употребимый инструмент разработки алгоритмов. Они позволяют делить алго- ритм на части и затем вести работу с каждой частью в отдельности, зная, что если правильно запрограммировать каждую часть, то получится и правильная программа в целом. После завершения построения схемы производится переход от схемы к программе на формальном языке, например, на алголе.
216j МЕТОДЫ РАЗРАБОТКИ ПРОГРАММ 129 - Таким образом, использование схем позволяет не только по* степенно осмысливать план программы, разрабатывая на каждом этапе алгоритм с удобной степенью детализации (обычно каждая схема содержит не более, чем 10—15 пунктов), но и отделить раз- работку алгоритма от его кодирования. Чтобы продемонстрировать универсальность и удобство при- менения схем, изобразим в виде схемы сам процесс программиро- вания на алголе. Г. Составить детальную схему. 2°. Кодировать все элементы схемы на алголе. Интерфейсом между частями Г и 2° этой работы служит схема программируемого алгоритма. Теперь детализируем часть 1°, выписав для нее более подробную схему: 1.1°. Составить крупную схему. 1.2°. Пока все элементы не станут простыми для кодирования, детализировать отдельные элементы схемы. Применительно к нашей задаче поиска минимального элемента в строке с максимальной суммой элементов мы выполнили работы 1.Г и 1.2° из этой схемы и теперь переходим к работе 2°, т. е. к кодированию на алголе разработанной схемы алгоритма. В резуль- тате получаем алгол-программу: begin integer т, л; ввод (т, п)-, begin array а [1 : tn, 1 : л[; integer i, j, k, l\ real s, t, w, ввод (о); ft: = l; /:=0; for /: = 1 step 1 until л do /:=/+а [1, /]; for i:=2 step 1 until tn do begin u:==0; for /: = ] step 1 until л do м:=ы+а [i, /]; if u>t then begin kt — i, t:=u end end; /:==!; t:=a [ft, 1]; for j:=2 step 1 until n do if a [ft, j]<i then begin t:=a [ft, /] end; s:=0; for t: = l step 1 until m do s:=s+a [i, /1; вывод (k, I, s) end end Обозревая составленную таким способом программу, мы можем при желании отвлечься от подробностей ее окончательного вида и перейти к рассмотрению того промежуточного уровня ее описа- ния, на котором уже присутствуют интересующие нас детали, но Э. 3. Любинский и др
130 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. » I еще нет более частных деталей, которые при данном рассмотрении для нас несущественны. Иногда разработка алгоритма по принципу сверху вниз оказы- вается неудачной из-за того, что при попытке дальнейшей детали- зации одного из пунктов общей схемы выясняется, что эта общая схема в имеющемся виде не годится или нуждается в серьезных изменениях. Например, детализация алгоритма анализа матрицы могла бы показать, что следует перебирать строки не от первой к последней, как задумано в первоначальной общей схеме, а от последней к первой. В таком случае приходится корректировать общую схему с учетом ее детализации. 2.16.2. Блок-схемы. Приводившиеся до сих пор схемы легко обозримы, поскольку представляемые ими структуры алгоритмов линейны. В случае структур со сложными переходами используются блок-схемы. Блок-схемой называется такое графическое изображение струк- туры алгоритма, в котором каждый этап процесса переработки данных представляется в виде прямоугольника, ромба, овала или другой геометрической фигуры («блока»). Эти фигуры соединяются между собой линиями со стрелками, отображающими последова- тельность выполнения алгоритма. Внутри каждой фигуры разре- шается писать (как и в схеме) произвольный текст, в котором на понятном человеку языке сообщается о нужных вычислениях в соответствующей части программы. Приняты определенные стандарты графических обозначений. Так, прямоугольник обозначает вычислительные действия, в ре- зультате которых изменяются значения данных. Ромбом обозна- чается этап разветвления вычислительного процесса. Выбор одного из двух возможных направлений дальнейшего счета производится в зависимости от выполнения условия, записанного в ромбе. Если это условие выполнено, то нужно перейти по стрелке с надписью «да», если же условие не выполнено, то нужно перейти по стрелке с надписью «нет». Например: Овалом обозначается начало или окончание выполнения алгоритма. На рис. 2.2 в качестве примера приведена блок-схема алгоритма извлечения квадратного корня x = а итерационным методом Ньютона x»==x»-i+ Т — x«-i) ’ п== Ь 2, ...
2.161 МЕТОДЫ РАЗРАБОТКИ ПРОГРАМЛ1 131 Исходными данными являются значение а, из которого нужно извлечь корень, значение х», являющееся некоторым грубым при- ближением корня, и значение eps>0, определяющее требуемую точность вычисления. Более сложная блок-схема приводится, например, в раз- деле 3.3.4 на рисунке 3.3. Рис. 2.2. Блок-схема вычисления а. 2.16.3. Программирование по принципу снизу вверх. Программирование сверху вниз может быть затруднено, если неясно, на какие части де- лить задачу. При этом часто бы- вает видно, что заведомо пона- добятся определенные фрагмен- ты вычислений. (Обычно это типовые вычисления, которые уже использовались при про- граммировании других задач.) В таких случаях можно попы- таться оформить эти фрагменты и, опираясь на них, конструиро- вать нужную программу. Такой способ программирования называ- ется программированием снизу вверх. Принцип программирования снизу вверх состоит в том, что сначала выбираются или строятся детали алгоритма, а потом из них наращиваются более крупные части, пока не будет построен искомый алгоритм Программирование снизу вверх оказывается особенно эффективным, если имеется готовый набор фрагментов алгоритмов, оформленных, например, в виде процедур алгола. Тогда мы можем выбрать из этого набора те процедуры, которые, как нам кажется, пригодятся для выполнения отдельных этапов требуемого вычисления, затем из этих процедур мы конструируем более крупные фрагменты, нацеленные на частичное решение по- ставленной задачи, реализуем эти фрагменты и, постепенно нара- щивая и уточняя получаемые конструкции, пытаемся прийти в конце концов к решению нашей задачи. Например, при решении рассмотренной в разд. 2.16.1 задачи поиска минимального элемента в строке с максимальной суммой элементов мы могли бы сначала выделить процедуры вычисления минимума в строке, суммирования элементов строки и суммиро- вания элементов столбца, а потом построить из них нужный ал- горитм. Возможны смешанные стратегии программирования, когда при разработке алгоритма совместно используются принципы про-
132 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 граммирования сверху вниз и снизу вверх. Примеры такой раз- работки мы будем встречать в следующей главе. 2.16.4. Принципы структурного программирования. В разделе 2.7.2 мы уже говорили о понятии структурного про- граммирования. Теперь мы знаем структуры условного оператора, оператора цикла и оператора процедуры, которые позволяют ор- ганизовать алгольными средствами без использования операторов перехода выбор, повторение и обращение к функции. Однако такая стандартизация структур управления последовательностью счета служит в структурном программировании и средством для дости- жения гораздо более важной цели: писать удобочитаемые программы. Занимающая хотя бы несколько страниц печатного текста про- грамма со сложной логикой часто становится необозримой для человека, если заранее не предусмотреть строгую дисциплину ее написания. Между тем, одно из назначений алгола состоит в пуб- ликации алгоритмов для ознакомления с ними людей. Даже если алгольная программа предназначается только для машинного исполнения, ее неудобочитаемость часто является источником многочисленных скрытых ошибок, выявлять и исправлять которые должен опять-таки человек. Структурное программирование основывается на рассмотренном нами в разделе 2.16.1 принципе разработки программы сверху вниз с постепенной детализацией. В рамках структурного программирования на каждом этапе детализация очередного промежуточного действия может произ- водиться только следующими четырьмя способами: 1) последовательное перечисление нескольких более мелких действий; 2) выбор одного из нескольких более мелких действий в зави- симости от определенных условий; 3) циклическое повторение более мелкого действия при опреде- ленных значениях параметров. 4) использование более мелкого действия как процедуры или подпрограммы. Последовательное применение принципов структурного про- граммирования позволяет, как правило, получать удобные и легкие для чтения программы.
Глава 3 СТРУКТУРЫ ДАННЫХ Как мы уже говорили, для каждого круга задач характерны определенные структуры данных, над которыми выполняются алгоритмические действия. В реальной среде, в которой протекают процессы, отображаемые алгоритмом, элементы связаны некоторыми формами взаимного влияния или взаимной зависимости. Например, если среда пред- ставляет собой металлическую пластинку, то между ее элементами, являющимися пространственными соседями, передается тепло. Если это телефонная сеть, то связаны аппараты с подстанциями, подстанции со станциями, станции между собой и т. д. Разумеется, обычно нельзя отобразить все связи реальной среды. Поэтому нужно выбирать связи, существенные для данного процесса, от- брасывая остальные связи и получая таким образом упрощенную модель среды. Связанные между собой элементы данных образуют структуру данных. В алголе предусмотрены две структуры данных: одиночное значение (простая переменная) и прямоугольный массив значений, элементы которого задаются наборами индексов. Однако эти две алгольные структуры не всегда оказываются удобными для решения реальных задач, в которых имеются свои структуры данных, от- ражающие структуры исследуемых объектов. Например, возникают определенные трудности, если описывать на алголе телефонную сеть, схемы железнодорожного сообщения, шахматные позиции, очереди. В принципе любые специфические структуры удается отобразить на массивы языка алгол, но такое отображение требует изобретательности и может существенно затруднить формулировку алгоритма. В этом одна из причин того, что программисты не до- вольствуются одним языком (например, алголом), а используют разные языки программирования, ориентированные на различные классы решаемых задач, причем в первую очередь ориентирован- ность языка определяется принятыми в нем структурами данных. В частности, существуют языки программирования, специально предназначенные для работы с очередями, строками, таблицами. В этой главе мы введем типичные структуры, характерные для данных из реальных задач, и опишем реализацию этих структур на алголе.
134 СТРУКТУРЫ ДАННЫХ 1Гл. 3 Каждая структура данных характеризуется, во-первых, взаи- мосвязью элементов и, во-вторых, набором типовых операций над этой структурой. Как правило, такой набор в основном состоит из операций выборки, записи и поиска. Для реализации некоторой структуры на алголе необходимо: 1) отобразить ее на одну из структур алгола, 2) написать на алголе процедуры выполнения типовых операций. Оформляя типовые операции над структурами в виде алгольных процедур, мы получаем возможность в дальнейшем писать на ал- голе программу, оперирующую с новыми структурами, не задумы- ваясь над тонкостями алгольной реализации этих структур, а просто включив в свою программу описания этих процедур и об- ращаясь к ним в тех местах, где возникает потребность в операциях над такими структурами. Все рассматриваемые структуры мы будем отображать на од- номерный массив. Этот выбор объясняется тем, что в последующих главах мы будем изучать еще один язык программирования — язык ЭВМ, в котором имеются только две структуры данных: одиночное вначение и вектор (т. е. одномерный массив). Таким образом, ото- бражая некоторую структуру на одномерный массив, мы сразу решаем, как отображать ее и на языке алгол, и на языке ЭВМ. 3.1. Очереди Очереди — это одна из простейших и в то же время достаточно Типичных структур данных, потребность в которых возникает при программировании реальных процессов и ситуаций. Проблема Очереди возникает, когда имеется некоторый механизм обслужи- вания, который может выполнять заказы последовательно, один за другим. Если при поступлении нового заказа обслуживающее устройство свободно, оно может немедленно приступить к его выполнению, но если оно уже выполняет некоторый ранее получен- ный заказ, то новый заказ должен поступить в конец очереди за- казов, ожидающих выполнения. Каждый раз, когда обслуживаю- щее устройство завершает выполнение текущего заказа, оно при- ступает к выполнению заказа из начала очереди, причем этот заказ удаляется из очереди и первым в очереди становится следу- ющий за ним заказ. Если заказы поступают нерегулярно, их оче- редь то удлиняется, то укорачивается, и время от времени может становиться пустой, когда все имеющиеся заказы выполнены, а новых еще не поступило. Для простоты предположим, что каждый заказ кодируется не- которым целым числом. В таком случае очередь организуется из чисел, представляющих собой коды заказов. Например, исходная очередь могла бы состоять из чисел 59 и 17. Потом из очереди вы- бирается первое число 59, и в ней остается единственный элемент 17.
8.1 J ОЧЕРЕДИ 135 Если далее в очередь поступают последовательно числа 31 и 7, то теперь очередь имеет вид: 17; 31; 7. Над структурой очереди могут выполняться операции двух видов: — выборка и одновременное удаление из очереди первого ее элемента; — занесение нового элемента в конец очереди. Опишем реализацию очереди на алголе. Для отображения оче- реди будем использовать одномерный массив S, в последовательных элементах которого будут храниться последовательные элементы очереди. Кроме того, будем использовать две целочисленные пере- менные п и k, которые будут служить для указания соответственно индексов в массиве S начала очереди и первого свободного места в конце очереди. Так, если очередь из чисел 59 и 17 занимает эле- менты Sill и SI2], то п=1 и k—З. Занесение нового элемента х в очередь S реализуется следующей процедурой: procedure В ОЧЕРЕДЬ (S, х) конец: (k); value х; integer array S; integer x, k; begin SlAl:=x; k:—k+l end При выполнении этой процедуры значение х заносится в позицию, являющуюся концом очереди, и указатель конца очереди k про- двигается на единицу вперед. Удаление из очереди первого элемента с присваиванием его значения переменной х реализуется процедурой: procedure ИЗ ОЧЕРЕДИ (S, х) начало: (п); integer array S; integer x, n; begin x:=S[/d; n:=n+l end При выполнении этой процедуры указатель начала очереди п про- двигается на единицу вперед. Если очередь пустая, то n=k. В программе, использующей структуру очереди, должен быть описан массив S, содержащий некоторое количество т элементов*, integer array S[0: tn—1 ]. Можно считать, что значение т достаточно велико и заведомо больше, чем количество чисел, которые могут одновременно находиться в очереди. Однако, если числа в очереди обновляются часто, то состоящая даже из немногих чисел очередь будет постепенно исползать» по направлению к концу массива S. Когда-нибудь последний элемент очереди попадет в Sim—11, между тем как элементы SIO], S[l], S12], ... окажутся не используемыми. Таким образом, наши процедуры В ОЧЕРЕДЬ и ИЗ ОЧЕРЕДИ нуждаются в корректировке. Один из способов такой корректи- ровки состоит в том, чтобы после занесения элемента очереди в Sim—1] перенести всю очередь из конца массива S в его начало (установив значение п—Q и соответственно изменив значение А).
136 СТРУКТУРЫ ДАННЫХ (Гл 3 При этом процедура занесения нового элемента х в очередь не- сколько усложняется: procedure В ОЧЕРЕДЬ 1 (5, х) конец-, (k) длина массива: (т); value х, т: integer array S; integer x, k, nr, begin integer i; S(£l:=x; if k=£m—1 then k:=k+\ else begin for i:—n step 1 until m—1 do Sli—n]:=S[»l; k: =m—n; n: =0 end end В этом варианте процедуры одним из ее параметров является длина т массива 5. Тело процедуры теперь представляет собой не составной оператор, а блок, в котором описывается переменная i, используемая как параметр цикла. После присваивания значения 5[£]:=х, если k=£m—1 (т. е. пока не достигнут конец массива), выполняется присваивание k: =k+1, как и в процедуре В ОЧЕРЕДЬ. Если же k—m—1, то выполняются составной оператор, включаю- щий в себя цикл перемещения всех элементов очереди в начало массива 5 (с сохранением порядка следования элементов очереди), и операторы присваивания новых значений п и k, причем значение k указывает номер первого свободного места, но теперь уже в конце перенесенной очереди. Например, если т=90, л=80 и £=89, то в результате выполнения процедуры В ОЧЕРЕДЬ 1 десять эле- ментов очереди будут перенесены из 51801, 5181 ], ..., <S[89] в 510), 5111, ..., 5191 и получатся новые значения п=0 и £=10. При такой процедуре В ОЧЕРЕДЬ 1 процедура ИЗ ОЧЕРЕДИ не нуждается в корректировке. Чтобы сократить число параметров процедур, условимся, что для хранения значений п и k отводятся два первых элемента мас- сива 5: значение п будем хранить в 5101, а значение k — в 51П- Тогда элементы очереди могут размещаться, начиная с элемента 5121. Более простой способ избежать «сползания» очереди основы- вается на том, что очередь в массиве 5 «замыкается по кольцу», т. е. мы считаем, что элемент 5[т—1] не является последним, а за ним следует первый из отведенных для размещения очереди эле- мент 5121. Тогда при 511 }—т—1 (т. е. при k=m—1) новая проце- дура В ОЧЕРЕДЬ 2 занесет значение х в элемент 51/п—1 ] и уста- новит соответственно новое значение конца очереди 5111=2. Теперь процедура В ОЧЕРЕДЬ 2 имеет вид: procedure В ОЧЕРЕДЬ 2 (5, х) длина массива: (/п); value х, m; integer array 5; integer x, m-, begin 5 151111: =x; if S[ll=m—1 then S[l]:=2 else 5[ll:=Slll-H end
ОЧЕРЕДИ 137 3.1] Здесь запись SISI 1И означает то же, что в процедуре В ОЧЕ- РЕДЬ означала запись 3lfcl. Аналогично изменяется и процедура ИЗ ОЧЕРЕДИ'. procedure ИЗ ОЧЕРЕДИ 2 (S, х) длина массива', (т); value nr, integer array S; integer x, nv, begin x:=S(5(0]|; if S(01=m—1 then 3(01: =2 else SIOJ:=3(01+1 end Запись 3(3(0]] означает то же, что S(n] в процедуре ИЗ ОЧЕ- РЕДИ. Отличие этой процедуры в том, что если начало очереди совпадает с последним элементом массива 3, то после удаления этого элемента из очереди считается, что новым началом очереди является «следующий по кольцу» элемент S[2]. Приведенные процедуры не содержат контроля правильности обращения к ним. В частности, если очередь пустая, то выполнение процедуры ИЗ ОЧЕРЕДИ 2 (3, х, т) приведет к присваиванию переменной х постороннего значения. Если же очередь настолько большая, что заняты все элементы массива S, то выполнение про- цедуры В ОЧЕРЕДЬ 2 (S, х, т) приведет к наложению нового элемента на элемент, находящийся в начале очереди с уничтоже- нием этого начального элемента. Ответственность за правильные обращения к таким процедурам полностью ложится на человека, пользующегося ими. Однако можно несколько усложнить эти процедуры, введя в них контроль правильности обращения. Мы знаем, что если очередь пустая, то 3[1]=3(0), т. е. начало очереди совпадает с началом свободного места после очереди. Если очередь замкнута по кольцу, такое же равенство S[1]=S[O] возни- кает, когда заняты все элементы массива 3. Чтобы не путать эти две ситуации, мы договоримся реагировать на полное заполнение массива $ (сразу же) как на ошибку переполнения. Тогда наши процедуры могут выглядеть следующим образом: procedure В ОЧЕРЕДЬ 3 (3, х) длина массива', (/и); value х, nr, integer array 3; integer х, т\ begin S [S[l)]:=x; if 3(1 ]=m— 1 then S[l]:=2 else SI 11: =3111+1; if S[11=3(0] then ОШИБКА („переполнение", 3) end procedure ИЗ ОЧЕРЕДИ 3 (S, x) длина массива:(т)', value nv, integer array S; integer x, m; begin if 3(11=3(01 then ОШИБКА („нет элемента”, 3) else begin x:=S[S(011; if S[0]=/n—1 then S[0]:=2 else 3(01:=3(01+1 end end
138 СТРУКТУРЫ ДАННЫХ (Гл. 3 В этих процедурах в некорректных ситуациях производится обращение к специальной процедуре ОШИБКА, которая обеспе- чивает нужную реакцию на эти ситуации. При переполнении мас- сива S такой реакцией может быть, например, увеличение длины массива. Первым параметром процедуры ОШИБКА является строка с диагнозом ошибки. При машинной реализации процедура ОШИБКА может довести эту диагностику до сведения человека, напечатав ее на бумаге или выведя на экран, имеющийся в составе аппаратуры ЭВМ. В последующих процедурах алгольной реализации операций над структурами, мы не будем предусматривать контроля правиль- ности ’обращения к ним. (Контроль в процедурах не вводится, во-первых, чтобы не усложнять эти процедуры, и, во-вторых, по- тому, что выполнение операций контроля замедляет работу про- цедур.) 3.2. Стеки В предыдущем разделе мы рассмотрели очередь, в которой элементы (заказы) обслуживаются в порядке их поступления. В про- граммировании часто используется и другой тип очереди, в которой каждый раз предпочтение в обслуживании отдается последнему по времени поступления элементу. Потребность в такой органи- зации очереди возникает, например, при решении следующей задачи. Пусть массив Т содержит в элементах Т 10], Т [1], ... запись ал- гол-программы в кодировке, описанной в разделе 2.9.1. Каждому символу этой алгол-программы ставится в соответствие его коор- дината в массиве Т (индекс того элемента массива Т, в котором содержится код этого символа). Задача состоит в том, чтобы найти все пары операторных скобок begin — end и построить таблицу, в которой для каждой такой пары должна содержаться строка с ука- занием координат символа begin и соответствующего ему символа end. Продвигаясь по записи алгол-программы последовательно от начала к концу, мы можем образовать очередь координат открываю- щих скобок begin, ожидающих соответствующих закрывающих скобок end. Особенность этой очереди состоит в том, что каждый Еаз, когда встретится символ end, он будет соответствовать тому egin из очереди, который появился там самым последним. Пусть, например, в алгол-программе последовательность операторных ско- бок имеет вид: ,, m begin.. .begin.. .end.. .begin.. .end.. .end Координаты В T: О 17 42 61 84 85 Продвигаясь по записи этой алгол-программы, мы дойдем до первого end (с координатой 42), который соответствует второму (т. е. последнему в состоянии очереди на данный момент) символу
3.21 * ' СТЕКИ ' 139 begin (с координатой 17). Удалим из очереди этот begin и занесем в таблицу его координату 17 вместе с координатой 42 первого end. После этого в очереди останется один (первый) begin, с,которого начинается программа. Двигаясь по записи алгол-программы да- лее, мы встретим третий begin (с координатой 61) и занесем его в очередь нового типа, которая состоит теперь из первого и третьего символов begin Встретив второй end, мы удалим из этой очереди последний (в данном случае третий) begin и занесем строку, содер- жащую координаты 61 и 84 символов begin и end, в таблицу. Теперь в очереди опять останется один первый begin. Дойдя до соответст- вующего ему последнего end, мы удалим из очереди этот begin и занесем координаты 0 и 95 в таблицу. В результате очередь оста- нется пустой, а таблица координат begin — end будет завершена и примет вид: 17 42 61 84 О 95 Для реализации такой очереди, организованной по принципу «последним пришел — первым ушел», употребляется специальная структура данных, которая называется стек. (В литературе встре- чается и другое название — магазин.) Стек представляет собой последовательность элементов, упорядоченных по времени их по* ступления, причем извне доступна только вершина стека, т. е. место, в котором находится последний по времени поступления элемент. Обратиться к стеку можно для того, чтобы забрать элемент с вер- шины или добавить в него новый элемент. Стек можно сравнить со стопкой бумаги, накапливающейся на письменном столе. Когда мы забираем элемент с вершины, то новой вершиной стека стано- вится предыдущий по времени поступления элемент. Когда мы до- бавляем новый элемент, то он как бы ложится поверх прежней вер- шины, и теперь уже он становится вершиной стека. В простейшем случае каждый элемент стека является одиноч- ным числовым значением. Такой стек легко отобразить на одномер- ный алгольный массив S. Операция занесения В СТЕК (т. е. в вершину стека) значения х будет реализовываться процедурой В СТЕК. (S, х)\ операция извлечения из стека (т. е. из его верши- ны) хранящегося там значения будет реализовываться процедурой ИЗ СТЕКА (S, х), где х — имя переменной, в которую занесется значение, извлекаемое (и одновременно удаляемое) из стека. Мас- сив S будет начинаться с элемента S[0], который служит указате- лем очередного свободного места в стеке, т. е. содержит индекс того элемента массива S, который следует за текущей вершиной стека. Самый давний по времени поступления элемент стека хранится в массиве S в элементе S[1 ], а поступившие позднее элементы стека хранятся в массиве S соответственно в его элементах S12], S[3], ... ..., SIS101—1]. Если стек пуст, то S[0]=l.
140 СТРУКТУРЫ ДАННЫХ (Гл 3 Процедуры В СТЕК и ИЗ СТЕКА описываются на алголе сле- дующим образом: procedure В СТЕК (3, х); value х, integer array 3; integer x; begin S[S[0]]:=x, S[0]:=S[0]-H end procedure ИЗ СТЕКА (S, x); integer array 3; integer x; begin x:=S[S(0]—l]; S[O]:=S[OJ— 1 end Процедура В СТЕК начинает работу с того, что заносит значе- ние х в новую вершину стека, индекс которой в массиве 3 указыва- ется значением 3[0]. После этого первым свободным становится сле- дующий элемент массива S с индексом S[0]-H. Это новое значение индекса первого свободного элемента заносится в S[OJ. Процедура ИЗ СТЕКА берет значение из вершины стека и за- носит его в х. Вершина стека предшествует первому свободному эле- менту массива 3, и поэтому ее индекс в массиве 3 равен 3101—1. Те- перь бывшая вершина стека становится первым свободным элемен- том массива S, и поэтому значение 3[0]—1 заносится в S101. Как отмечалось в конце разд. 3.1, эти и последующие процеду- ры не содержат контроля правильности обращения к ним. Напри- мер, при пустом стеке (т. е. при 3[0]=1) процедура ИЗ СТЕКА будет работать некорректно. Уточним теперь постановку задачи построения таблицы соот- ветствия операторных скобок begin — end. В массиве Т последова- тельно, символ за символом, закодирована алгол-программа. Тре- буется построить двумерный массив В, в котором столько строк, сколько пар begin — end в этой алгол-программе, причем каждая строка соответствует очередной паре и содержит два числа: индек- сы элементов массива Т, в которых закодированы символы begin и end из этой пары. Признаком конца закодированной алгол-про- граммы является число 999 в очередном элементе массива Т. Для наглядности, чтобы не воспроизводить конкретные числовые коды, мы введем в употребление функцию КОД с одним аргументом КОД („(символ алгола)”). Например, КОД („begin”)=240. КОД (,,end”)= =241. Приведем сначала неформальное описание алгоритма. 1. Образовать пустой стек 3 и подготовиться к формированию первой строки массива В. 2. Перебирать последовательно от начала все элементы массива Т, пока не встретится признак конца алгол-программы. Если в очеред- ном элементе массива Т встретится код символа begin, то ин- декс этого элемента занести в стек. Если же в очередном элементе 7W встретится код символа end, то сформировать очередную t-ю строку массива В, в которую занести, во-первых, значение из вер- шины стека (индекс элемента массива Т, содержащего код символа
8.2] СТЕКИ 141 begin), й, во-вторых, индекс k. Тем самым получаются координаты очередной пары begin — end Запишем соответствующую схему: Г. 3(01: = 1 (образуется пустой стек 3); (:=1 (номер первой сво- бодной строки в массиве В). 2°. При k—0, 1, ... пока TUhM)99 выполнить 3°. {Если Т[1г]=КОД („begin”), то В СТЕК (3, k) 4°. Если Т{к}=КОД („end”), то ИЗ СТЕКА (S, В\1, 1]); В(/, 2]:=й; i:=H-l) 5°. Вывести полученную таблицу. Ниже приводится блок алгол-программы, реализующий этот алгоритм. (Предельное число строк в таблице В и элементов в сте- ке 3 задается глобальными переменными N и п, процедуры В СТЕК и ИЗ СТЕКА также считаются описанными во внешнем блоке.) begin integer array B[\:N, 1:2), 3)0:n); integer i, S[0J: = l; i: = l; for£:=0 Л+l while 7W/=999 do if Т[к}=КОД („begin”) then В СТЕК (3, k) else if Т[к]=КОД („end") then begin ИЗ СТЕКА (S, B[i, 1]); B[i, 2]:=Л; i:=t+l end; вывод (В, i) end Выводимое значение i указывает число строк в полученной таблице. Например, если применить этот алгоритм к программе с опера- торными скобками begin ... begin ... begin ... end ... begin ... T [О] 7(73] 7(117] 7(215) 7(310) ... end ... end ... begin ... end ... end 7(470) 7 (501) 7 (605] 7(700) 7(1800) то по мере возрастания k изменение содержимого стека и формирова- ние последовательных строк таблицы В будет происходить так, как показано в табл. 3.1. Таблица 3.1 k Содержимое стека Очередная строка таблицы В 0 0 73 0; 73 117 0; 73; 117 215 0; 73 117; 215 310 0; 73; 310 470 0; 73 310; 470 501 0 73; 501 605 0; 605 700 0 605; 700 1800 Пусто 0; 1800
142 СТРУКТУРЫ ДАННЫХ (Гл. 3 Таким образом, в массиве В перечисляются все пары b^gin — end, но в порядке следования символов end. Если нам нужно пере- числить их в порядке следования begin, то для «решения этой зада- чи удобно употребить стек, в котором элементами являются не оди- ночные значения, а пары значений: k — координата символа begin и i — его порядковый номер в тексте. Очевидно, что i — это и есть номер строки в массиве В, в-которую нужно будет записать коорди- наты символов begin и end, когда встретится соответствующий сим- вол end. При отображении такого стека на одномерный алгольный мас- сив S операция занесения в стек значений х и у будет реализовы- ваться процедурой В СТЕК 1 (S, х, у)\ операция извлечения из стека будет реализовываться процедурой ИЗ СТЕКА 1 (S, х, у), где х и у — имена переменных, в которые занесутся первое и второе значения, извлекаемые (и одновременно удаляемые) из вершины. Как и в случае простого стека, элемент S10] служит указателем оче- редного свободного места в стеке. Теперь процедуры В СТЕК 1 и ИЗ СТЕКА 1 описываются на алголе так: procedure В СТЕК 1 (S, х, у)-, value х, у, integer array S; integer x, у, begin SIS]0]]:=x; SLS[O]+11: =y, SlO]:=SlOl+2 end procedure ИЗ СТЕКА 1 (S, x, y)\ integer array S; integer x, y, begin x:=S[S[0]—2]; i/:=SLS]O]—11; S[0]:=S[01—2 end Вернемся теперь к решению нашей задачи построения табли- цы пар begin — end. Продвигаясь по записи Т алгольной программы, мы будем для каждого встреченного символа begin заносить в вершину стека две величины: координату k этого символа в массиве Т и порядковый номер i этого begin (т. е. номер, который должна занять соответ- ствующая строка в таблице результатов В). Неформально наш алгоритм можно описать следующим об- разом: 1. Образовать пустой стек S и занести 0 в счетчик i номеров сим- волов begin. 2. Перебирать все элементы массива Т, пока не встретится при- знак конца алгол-программы. Если в очередном элементе ПЛ] встретится код символа begin, то занести в стек индекс k этого эле- мента и порядковый номер i символа begin. Если же в очередном эле- менте ПЛ] встретится код символа end, то взять из вершины стека координату I и порядковый номер / символа begin (соответствующе- го данному end) и занести в /-ю строку таблицы В значения I и Л (координаты символов begin и end).
а.2]-. СТЕКИ М3 Соответствующая схема записывается так: 1°. i:=0 2°. При k=0, пока Т[£]=/=999 выполнить 3°. {Если Т[к]=КОД („begin”), то »:=Ж; В СТЕК 1 (S, k, О 4°. Если Т[к]=КОД („end”), то ИЗ СТЕКА 1 (S, I, fr, B[j, 1]:« Z; B[j, 2]:=Л) 5°. Вывести полученную таблицу. Описанный алгоритм реализуется следующим блоком алгол* программы: begin integer array Bl 1 :N, 1:2], S[0:2Xn); integer i, k, I, j; S[Oh=l; i:=0; for/г:=0, A+l while ШИ999 do if T\k]=KOE („begin”) then begin i:=i+l; В СТЕК I (S, k, i) end else if Т[к]=КОД („end”) then begin ИЗ СТЕКА 1 (S, t, ]), B\j, 1]: = /; B[/, 2]:=£end; вывод (B, i) end Если применить этот алгоритм к рассмотренному выше примеру, то по мере возрастания значений k изменение содержимого стека и формирование строк таблицы В будут происходить в последователь- ности, показанной в табл. 3.2. Таблица 3.2 k Содержимое стека Формируе- мая 1-Я строка таблицы Значе- ние i 0 (0,1) 73 (0,1); (73,2) 117 (0,1); (73,2); (117,3) 215 (0,1); (73,2) 117; 215 3 310 (0,1); (73,2); (310,4) 470 (0,1); (73,2) 310; 470 4 501 (0,1) 73; 501 2 605 (0,1); (605,5) 700 (0,1) 605; 700 5 1800 Пусто 0; 1800 1 В результате таблица В примет вид: 0; 1800 73; 501 117; 215 310; 470 605; 700
144 СТРУКТУРЫ ДАННЫХ (Гл. 3 3.3. Строки Мы уже убедились в главе 1, что все алгоритмы могут быть пред- ставлены как алгоритмы преобразования слов. Для довольно ши- рокого класса алгоритмов, называемых алгоритмами символьной обработки, объекты естественно и удобно представлять в виде слов. Например, алгоритм преобразования алгебраических формул пре- образует слово а Xb+aXc в слово ах (b+с), асловоа| 2+2хахЬ+ Ь f 2 в слово (a+b) | 2 и т. д. Алгоритм определения пар символов begin и end также имеет одним из своих объектов слово, представ- ляющее собой текст на языке алгол, но не изменяет этого слова, а только просматривает его. В программировании объекты, являющиеся последовательно- стями символов из некоторого алфавита, принято называть не сло- вами, а строками. Основная операция, которую применяют к стро- кам, это подстановка одной подстроки вместо другой, аналогичная применению формулы нормального алгоритма. В этом смысле нор- мальные алгоритмы предлагают аппарат, удобный не только для тео- ретических рассмотрений, но и для решения определенного класса практических задач. Однако обычно эта операция разбивается на ряд более простых операций, таких как последовательный перебор символов строки; включение заданного символа в указанное место строки; исключение символа из указанного места строки; поиск вхождения в строку заданной подстроки и определение места этого вхождения. Наряду с включением и исключением одного символа, употреб- ляются операции включения и исключения подстроки. 3.3.1. Векторное представление строк. С этим простейшим способом представления строк мы уже по- знакомились в предыдущем разделе. Каждый символ кодируется целым числом, и эти коды располагаются в одномерном массиве под- ряд друг за другом. Именно так мы представляли строку символов алгол-программы в массиве Т: располагали соседние символы про- граммы в элементах массива Т с подряд идущими значениями ин- декса, а в конце записывали код 999 конца строки. В векторном представлении последовательный перебор символов строки обеспечивается тем, что индекс следующего символа является простой функцией от индекса предыдущей, а именно след(0=> = t’+l. Чтобы вставить в такую строку символ, нужно освободить для него место, а для этого требуется «раздвинуть» строку, т. е. все сим- волы, которые должны следовать за вставляемым, нужно перенести в следующие элементы массива. Например, если исходная строка с17548 размещалась в массиве S:
8.3) СТРОКИ 145 символы: а 1 7 5 4 8 координаты в S: 0 1 2 3 4 5 и если после первого символа а вставляется символ f, то все после- дующие символы сдвигаются на одну позицию вправо, т. е. коорди- ната каждого из этих символов увеличивается на единицу: символы: a f 1 7 5 4 8 координаты в S: 0123456 При удалении из такой строки одного символа нужно сдвинуть все последующие символы влево, чтобы не оставалось свободных мест. Если из строки S удаляется символ после i-ro, то на алголе это реа- лизуется, например, следующей процедурой: procedure ИЗ СТР (S) после', (i); value t; integer array S; integer i; begin integer n; for n:=i+l, n+1 while S[n]=/=999 do S[n]:=S[n+l] end Если размеры строк невелики или строки редко изменяются, то можно пойти на затраты времени, связанные с перемещением сим- волов, следующих за вставляемым или удаляемым. Иногда символы удаляются из строки для того, чтобы на их место вставить такое же количество новых символов, тогда нет надобности сдвигать осталь- ные символы. Вообще же тот факт, что локальное изменение строки, касающееся одного ее символа, может повлечь за собой необходи- мость перемещения многочисленных последующих символов, являет- ся основным недостатком векторного способа представления строк. Причина этого состоит в том, что в векторе понятие «следующий эле- мент» связывается с местом расположения предыдущего элемента. Такое представление, как правило, плохо пригодно для структур, которые меняются в середине, хотя оно вполне удовлетворительно для структур, меняющихся только по краям (таким, как очередь или стек) или вовсе не меняющихся (как строка алгольного текста из предыдущего раздела). 3.3.2. Представление строки в виде цепочки. Между тем совсем не обязательно жестко связывать место распо- ложения (т. е. индекс) следующего символа с индексом предыду- щего. Можно располагать символы в любом порядке, но зато каж- дый символ сопровождать указанием места расположения следую- щего. При таком отображении строки на одномерный массив каж- дому символу строки ставится в соответствие пара соседних между собой элементов массива: в одном записывается код самого символа, а в другом — ссылка на следующую пару, т. е. индекс первого эле- мента пары, в которой записан следующий символ. Каждая такая пара элементов массива называется звеном. По- следовательные ссылки друг на друга сцепляют звенья в одну це-
СТРУКТУРЫ ДАННЫХ [Гл. » ) А* почку. Такой способ отображения структуры данных называется сцеплением. Как мы увидим, он применяется не только для пред- ставления строк. Но при представлении любой структуры данных звено всегда состоит из двух частей. В первой части (мы договоримся всегда располагать ее в начале звена) находится ссылка (или не- сколько ссылок) на соседние звенья. Эту часть мы будем называть справочной частью звена или просто справкой. Во второй части зве- на изображается сам элемент структуры данных (в нашем случае кодируется символ). Эту часть мы будем называть информационной частью или телом звена. При отображении разных структур под справку и тело звена может отводиться разное число элементов мас- сива. Мы будем называть индекс первого входящего в звено эле- мента массива индексом этого звена. Если информационные части звеньев цепочки содержат коды символов, то вся цепочка в целом является представлением строки, состоящей из этих символов. Итак, каждое звено в цепочке, которая соответствует строке, содержит два значения: ссылку и код символа. Звенья цепочки мы можем раз- мещать в одномерном массиве 3, каждое звено занимает два сосед- них между собой элемента массива S, но допускается любой разброс , этих звеньев по массиву, так как следование определяется не по- рядком индексов, а значениями ссылок. Мы уже решили, что само значение кода символа занимает вторую позицию звена, а в первой позиции хранится ссылка на следующее звено. Примем соглашение, что последнее звено содержит ссылку нуль. Например строка al5 представляется цепочкой, состоящей из трех звеньев. Если первое звено находится в элементах 3121 и 3131, второе звено — в элементах S141 и 3151 и третье звено — в элемен- тах 3161 и 3171, то мы имеем следующее заполнение элементов мас- сива с «S[21 по 3171: 3121=4 — ссылка на второе звено (т. е. индекс первого элемен- та второго звена). 313]=Л0Д („а”) Sl41=6 — ссылка на третье звено. 3151=КОД („1”) 3[6]=0 — ссылка 0 означает, что это звено последнее. 3171=Д0Д („5") Однако мы можем разместить звенья совсем по-другому: напри- мер, первое звено в элементах 31751 и 31761, второе в 31311 и 3132), а третье в 311061 и S11071. Тогда эти элементы массива S заполнят- ся следующим образом: 31311=106 — ссылка на третье звено. 31321=/<ОД („1”) 31751=31 — ссылка на второе звено. 3176]=ДОД („а”)
3.3) СТРОКИ £[1061==0— признак, что звено последнее. £[1071=КОД („5”) С точки зрения этой цепочки, совершенно не важно, чем запол- нены остальные элементы массива, и в частности, находящиеся меж- ду нашими звеньями элементы £[331, ..., £[74], £[77), ..., £[105]. Теперь, чтобы включить или исключить символ в строке, ничего не нужно сдвигать, а достаточно поменять некоторые ссылки. Для того, чтобы избежать нестандартной работы с первым зве- ном цепочки, удобно ввести фиктивное заглавное звено и хранить его всегда в постоянном месте. В нашем отображе- нии строки на алгольный массив £ заглавное звено цепочки, представляющей строку, будет занимать на- чальные элементы £[0) и £[1]. В £[01 мы поместим ссылку на первый элемент первого звена цепочки, а в £[1] для удобства вклю- чения новых символов по- местим значение индекса S[o>z $U]=S 5[з]-кодс,д”) sV/\=6 ^—индекс первого звена ' *—индекс первого свободного -J зпемента $[5}=К0Д(„1") 5И=0 $17^К0Д(„5”) S[fl Рис. 3.1. Представление строки а15. первого свободного элемен- та массива £. Например, представление в виде цепочки строки а15 отображается на массив £, как показано на рис. 3.1. Легко увидеть, что если индекс звена с символом х равен i, то в элементе £[<] находится индекс звена с символом, следующим за х, а в соседнем элементе £[£Ч-1 ] находится код символа х. В нашем примере индекс звена с символом а равен 2, индекс звена с символом I равен 4 и индекс звена с символом 5 равен 6. В частности, строка £ может оказаться пустой. В таком случае, ее заглавное звено состоит из элементов £[01—0 и £[11—2. 3.3.3. Включение символа в строку и исключение символа. Рассмотрим теперь операцию включения символа. Пусть I — индекс звена, после которого нужно вставить новый символ. Новое звено помещается на свободное место, указываемое значением £[1 ]. Код включаемого символа записывается во вторую позицию нового звена и становится значением элемента £[£[ 1 ]-Н ]. Перед ним нужно записать в элемент £[£[1]] ссылку на следующее звено. Раньше это звено следовало за звеном с индексом I. Поэтому нужная нам ссыл- ка хранится в первой позиции звена с индексом i, т. е. в элементе £[i]. Итак, нужно выполнить присваивание £[£[111: =£[»]. Предшествующее разрыву звено, которое занимает элементы £[»] и £[»+!], должно теперь содержать в своей первой позиции ссылку на вновь включенное звено, которое мы поместили в элемен-
148 СТРУКТУРЫ ДАННЫХ 1Гл 3 ты массива S с индексами 5(11 и S[ll+1. Поэтому выполняется при- сваивание S[il:=5lll. Поскольку мы заняли два новых элемента в массиве S, то соответственно изменяется указатель первого свободного элемента: 5(11:=5(11+2. Например, пусть в строку а!5 нужно вставить 2 так, чтобы получилась строка в!25. В схематическом изображении представление строки до включения символа было после включения символа 2 стало I I а —1 5 2 ♦ I Символ 2 вставляется после символа I, т. е. после звена, индекс которого ра- вен 4. Итак, i=4, код символа 2 занесет- ся в 5(9], и массив 5 будет содержать элементы, изображенные на рис. 3.2. Напишем на алголе процедуру вклю- чения символа. procedure В СТРОКУ (5) символ-, (х) после-. (t); value х, i; integer array 5; integer*, i; begin 51511 ]+!]:=*; comment символ x заносится в новое звено-, 5[S[1 U:=S[j]; comment в первую позицию нового звена зано- сится ссылка на звено после разрыва-, Sit]:—5(11; comment в звено перед разрывом заносится ссыл- ка на новое звено-, 5(1]: =5(11+2; comment указатель первого свободного эле- мента продвигается вперед-, end Можно ввести для наглядности вспомогательную переменную нов и помещать в нее индекс 511 ] начала нового звена. Тогда проце- дура В СТРОКУ будет выглядеть так: procedure В СТРОКУ (S) символах) после:(1); value х, i; integer array 5; integer x, i; begin integer нов; woe: =5(1]; 5(нов+1]:=х; 5l«oe]:=Sltl; S(tl:= нов; 5(11:=5(11+2 end Теперь рассмотрим задачу исключения символа из строки. Пусть задано значение i и нужно исключить из строки символ, который содержится в звене, следующем за звеном с индексом I.
8.31 z СТРОКИ '''мэ Тогда индекс звена с исключаемым символом равен значению 5Ь‘1. Иначе говоря, исключаемое звено цепочки занимает в массиве S элементы с индексами Sh'l и 5[«]-Н. В элементе с индексом 51Л, т. е. в 515U']], находится ссылка на звено, следующее за исключае- мым. После исключения это звено должно следовать за звеном с ин- дексом t. Поэтому операция исключения символа состоит в том, что значение 5h’] заменяется на значение 5lS[i]]. Изменение ссылок при вычеркивании символа 1 из строки а15 можно пояснить следующей схемой: было о —► 1 —> 5 стало ауИ ->5 Процедура исключения символа: procedure ИЗ СТРОКИ (5) после:(Г); value i; integer array 5; integer i; 5[tl:=S[5(i]] Исключенное звено остается в массиве 5, однако с точки зрения стро- ки оно перестало существовать, так как на него теперь нет ссылок из цепочки. Замечание. Если мы включаем или исключаем символ в начале строки, то при обращении к процедуре В СТРОКУ или ИЗ СТРОКИ указывается параметр 1=0, означающий, что символ вклю- чается или исключается после заглавного звена цепочки. 3.3.4. Более сложные операции над строками. 3.3.4.1. Включение строки в строку. Нужно включить в строку S после звена с индексом i группу символов, представленную в виде строки 51. Например, если включить в строку а!5 после символа 1 строку &3г, то схема ссылок в полученной строке будет такой: I I а—► 1-т* 5 Ь—*3—»г t I Будем считать, что при включении строки 51 в строку 5 строка 51 не должна изменяться, и следовательно, ее элементы нужно ско- пировать в строку 5. Будем вставлять строку S1 «посимвольно», перебирая последо-’ вательно содержащиеся в ней символы. Первый символ строки S1 вставляется после символа строки 5, находящегося в звене с индек- сом i, а каждый следующий — после только что вставленного. Ниже приводится описацие соответствующей процедуры, в котором пере- менная k служит указателем в строке 51 очередного ее звена. procedure ВКЛСТР (51) в строку.(8) после-.Ц)-, value i; integer array 5, 51; integer i;
150Г СТРУКТУРЫ ДАННЫХ 1Гл. 3 begin integer k;k:—O-, for k: =31 [61 while 6=#0 do begin В СТРОКУ (3, SI [6+11, i); K=S[i] end end ВКЛСТР Предполагается, что каждая строка 3 и 31 хранится в соответ- ствующем массиве стандартным образом: заглавное звено занимает элементы с индексами 0 и 1. В частности, в элементе 3110] находится указатель первого звена строки 31. При первом выполнении цикла в его заголовке производится присваивание k: =31 [01, т. е. значе- нием k становится индекс первого звена. Во второй позиции этого звена (в элементе 31 [6+11) находится код первого символа строки 31, который заносится в строку 3 оператором процедуры В СТ РОКУ (S, 31 [6+П, <)• Поскольку новое звено включается вслед за звеном с индексом I, то его индекс оказывается в S[i]. Поэтому, выполнив присваивание i:=S[t], мы тем самым занесли в i индекс нового зве- на в строке 3. (В рассмотренном примере включения строки ЬЗг в строку «15 на этом этапе строка 3 имеет уже вид al Ь5, а перемен- ная i содержит индекс звена с символом b в строке 3). Таким обра- зом,- следующий, второй, символ из строки 31 нужно опять заносить в строку 3 после звена с индексом i. Индекс второго звена в цепочке символов строки 31 находится в 31 [6]. При втором выполнении цик- ла в его заголовке производится присваивание 6: =31 [6], т. е. зна- чением 6 становится индекс второго звена строки 31. (В нашем при- мере это индекс звена строки ЬЗг, содержащего символ 3.) Теперь оператор В СТРОКУ занесет в строку 3 второй символ из S1. (В при- мере строка S примет вид al 635.) Таким образом, в строку 3 будут последовательно включены все символы строки 31. Когда будет включен последний символ из 31, то в первой позиции соответст- вующего звена цепочки 31 окажется значение 31 [61=0, так что после присваивания 6: =31 [6] условие заголовка цикла while 6=#0 не вы- полнится, и на этом выполнение цикла (и всей процедуры ВКЛСТР) завершится. В конце процедуры после символа end написан комментарий ВКЛСТР, который показывает, какая процедура кончилась. Такие комментарии часто используются для большей наглядности алго- - ритма. 3.3.4.2. Исключение подстроки из строки. Нужно исключить из строки 3 идущие подряд п символов, следующие за звеном с индек- сом L Будем исключать по одному символу. Тогда очевидно, что каждый раз нужно исключать очередной символ, следующий за авеном с индексом i. Это реализуется следующей процедурой: procedure ИСКЛСТР (3) после-.(i) количестео-.(п)-, value i, п-, integer array 3; integer i, n; begin integer 6; for 6: = 1 step 1 until n do ИЗ CTPOKH(S, i) end ИСКЛСТР
3.3) СТРОКИ После каждого очередного исключения символа звено, предшествую- щее месту разрыва цепочки, будет содержать в своей справке 5h’l индекс нового звена, следовавшего в цепочке за только что исклю- ченным. 3.3.4.3. Поиск вхождения одной строки в другую. Пусть имеются строки 5 и 51. Нужно определить, является ли 51 подстрокой стро- ки 5, и если да, то после какого элемента в 5 расположена подстро- ка, совпадающая со строкой 51. Ответ будет состоять из двух значений: 1) логического (истина, если S1 входит в 5); 2) числового (индекса i предшествующего подстроке звена в це- почке 5). Заметим, что поиск вхождения одной строки в другую — это «базовая операция» нормального алгоритма. Опишем процедуру ПОДСТРОКА решения этой задачи. Снача- ла дадим неформальное описание процедуры. Будем последовательно проверять, не входит ли строка 51 в строку 5 с самого начала строки 5, после первого ее символа, после Рис. 3.3. Блок-схема процедуры ПОДСТРОКА, второго символа и т. д. В соответствии с этим будем присваивать пе- ременной i значения индексов заглавного звена цепочки 5, первого звена цепочки 5 и т. д. При каждом новом значении i будем сравни- вать посимвольно строку 51 с очередным участком строки 5 (сле- дующим за звеном с индексом 0. Индексы участвующих в сравнении звеньев будем указывать соответственно значениями переменных / и k. Начальные значения этих переменных должны быть /==5110] и
152 СТРУКТУРЫ ДАННЫХ 1Гл. » k=S[t 1. Коды символов, которые нужно сравнивать, находятся со* ответственно в элементах Sil j+1J и 1 ], а в SI [/1 и 3[&] находятся ссылки на следующие звенья. Последующие сравниваемые символы также будут браться из звеньев, индексы которых являются значени- ями переменных / и k; для этого после каждого удачного сравнения символов из 31 [/+1 ] и S[£+11 мы выполняем присваивания /:=31[/] и k: — SIZsJ. Если в процессе сравнений оказывается, что строка 31 исчерпана (т. е. j=0), то тем самым мы нашли вхождение строки 31 .в строку S. (Если строка 31 пустая, то вхождение обнаружива- ется сразу.) Если же строка 31 еще не исчерпана, а строка 3 кон- чилась (т. е. Л=0), то дальнейший поиск бесполезен, вхождения строки 31 в 3 нет. Если же до исчерпания строк 31 и 3 обнаружи- вается несовпадение очередных сравниваемых символов, то в дан- ном месте строки 3 нет вхождения строки 31 и мы переходим к следующему начальному месту сравнения в строке 3, выполняя при- сваивание t:=SUJ. Введем логическую переменную входит, которой будем при- сваивать значение true, если строка 51 входит в строку 3, или зна- чение false, если такого вхождения нет. На рис. 3.3 изображена блок-схема процедуры ПОДСТРОКА. На алголе эта процедура может быть записана так: procedure ПОДСТРОКА (S1) в строке. (S) после'. (О признаку (входит)-, integer array 31, 3; integer i; boolean входит; begin i:=0; след: begin integer /, k; /:=S1[O]; £:=S[t|; npod: if /=0 then begin входит:—true; go to выход end; if k=Q then begin входит: =false; go to выход end; if 31 [/+1]=S^+1] then begin /:=S1 (/]; fe:=S[&]; go to npod end else begin i:=S [i]; go to след end end; выход: end ПОДСТРОКА Блок с меткой след будет выполняться для каждого значения i, т. е. при каждом изменении начального места сравнения в иссле- дуемой строке 3. В i остается информация о том, какая часть строки 3 подошла при сравнении. Если строка S1 входит в 3 несколько раз, то наша процедура обнаружит первое вхождение. 3.3.5. Реализация универсального нормального алгоритма. В качестве примера работы со строками рассмотрим реализацию на алголе универсального нормального алгоритма. Как мы уже знаем (см. раздел 1.3), запись нормального алго- ритма— это последовательность формул вида Л;{^}Вг. Применяя очередную формулу, мы ищем в слове, к которому применяется ал-
СТРОКИ 153 3.3J горитм, вхождение ее левой части. Если такое вхождение найдено, то оно исключается, а вместо него подставляется правая часть фор- мулы. Если данная формула применилась успешно (вхождение най- дено), то начинается проверка на вхождение снова с первой форму- лы, в противном случае происходит переход к следующей формуле. Выполнение алгоритма заканчивается, если после некоторого действия не применилась ни одна формула или если успешно при- менилась завершающая формула (формула, в которой левая и пра- вая части разделены стрелкой >-*). Пусть исходное слово хранится и преобразуется в массиве слово как строка, представленная в виде цепочки (со ссылками). Нор- мальный алгоритм будем хранить в массиве алг, в векторном пред- ставлении без ссылок. (Сцеплять символы ссылками нет необхо- димости, так как эту строку не придется изменять.) Можно счи- тать, что при записи исходного слова и нормального алгоритма ис- пользуются символы из алфавита языка алгол. Для этого будем кодировать стрелку—»• как разделитель f, а стрелку •—> как символ :=. Между формулами будем ставить запятую, а за последней фор- мулой поставим код 999. Левую и правую части формулы, которая применяется в данный момент, будем хранить в специальных рабочих массивах лев и прав в виде цепочек, чтобы удобно было сопоставлять их с цепочкой пре- образуемого слова в массиве слово. При выделении из массива алг очередной формулы используем процедуру ЧАСТЬ ФОРМУЛ Ы(Х), которая при подстановке вместо X фактического параметра лев или прав будет выделять соответствующую левую или правую часть формулы и формировать из нее цепочку символов в массиве лев или прав. Перед обращением к процедуре ЧАСТЬ ФОРМУЛЫ в глобаль- ную переменную п заносится индекс в массиве алг того элемента, вслед за которым начинается нужная часть формулы. Эта часть выделяется символ за символом, пока не встретится разделитель стрелка или запятая или код 999. (Стрелка означает, что кончилась левая часть формулы, а запятая или 999 означает конец правой час- ти, т. е. всей формулы.) При перенесении очередной части формулы из массива алг в массив X (т. е. в массив лев или прав) процедура ЧАСТЬ ФОРМУЛЫ одновременно преобразует эту информацию из векторного представления в представление в виде цепочки. После своей работы процедура ЧАСТЬ ФОРМУЛЫ оставляет измененное значение л: теперь оно равно индексу элемента масси- ва алг, содержащего тот разделитель, который следует за выделен- ной частью формулы. Работа процедуры ЧАСТЬ ФОРМУЛЫ состоит в следующем: 1°. Образуется пустая цепочка в массиве X. Для этого в заглав- ное звено заносятся значения Л10]=0 и Х[ 11=2. Указателю k зве- на в цепочке X присваивается начальное значение £:=0. (Тем са-
154 СТРУКТУРЫ ДАННЫХ 1Гл. а мым мы направляем этот указатель на заглавное звено цепочки.) К значению п прибавляется 1. (Теперь указатель п направлен на первый символ выделяемой части формулы в массиве алг.) 2°. Из массива алг выбирается код очередного символа алгШ, и если это не КОД („!”)> или КОД („:=”), или КОД („,”), или 999, то он заносится в очередное свободное звено цепочки X оператором В СТРОКУ (X, алгМ, fe). После этого указатель п направляется на следующий символ в векторе алг (присваивается значение n:=n-H) и указатель k продвигается на одно звено впе- ред присваиванием k'.=X\k\. Таким образом, указатель k всегда направлен на последнее заполненное звено цепочки X. Выполнение пункта 2° повторяется до тех Нор, пока очередной символ алгЬг] не окажется разделителем. Если же алг(п) — это КОД („f”), КОД („:=”), КОД („,”) или 999, то выполнение процедуры ЧАСТЬ ФОРМУЛЫ заканчивается. Запишем это на алголе: procedure ЧАСТЬ ФОРМУЛЫ(Х)\ integer array X; begin integer k, p\ X[01:=0; XUJ:=2; Л:=0; n:=n-|-l; for p:=0 while алг\п\=£КОД(„ f ”)Д<мгЫ «/=ДОД(„:=”)Далг 1п^К0Д(„, ")Г\алг [nl¥=999 do begin В СТРОКУ (X, алг1п],к)-, n:=n+l; k:=Xlk] end end Переменная p введена для использования ее в качестве фиктив- ного параметра оператора цикла (в соответствии с требованием син- таксиса). Переменные п и k нельзя использовать для этой цели, по- тому что после выхода из цикла значение его параметра становится неопределенным. Фактически мы, идя снизу вверх, уже выполнили большую часть разработки процедуры УНА (слово, алг) применения нормального алгоритма из массива алг к слову из массива слово. Мы последова- тельно вводили и реализовывали все более крупные базовые опе- рации: сначала В СТРОКУ и ИЗ СТРОКИ, а затем ВКЛСТР, ИСКЛСТР, ПОДСТРОКА и, наконец, ЧАСТЬ ФОРМУЛЫ. Те- перь, опираясь на эти операции, будем разрабатывать процедуру УНА по принципу сверху вниз. Естественно начать с составления общей блок-схемы разрабатываемой процедуры. Такая блок-схема приведена на рис. 3.4. Основываясь на этой блок-схеме, можно разработать более де- тальную схему процедуры УНА. Такая схема приводится ниже. В ней пункт Г соответствует первому прямоугольнику блок-схемы, пункты 2°—6° являются детализацией выбора очередной фор- мулы. Пункты 7° и 8° соответствуют первому ромбу блок-схемы, пункт 9° — последнему прямоугольнику (т. е. замене Лг->Вг). Проверка, была ли формула завершающей, реализуется пунк-
3.3] СТРОКИ 155 том 10°. Проверка, была ли формула последней, выполняется в пункте 11°. Схема процедуры УНА. Г. Присвоить значение п:=0. (Чтобы выделять левую часть первой формулы, которая хранится в массиве алг, начиная с эле- мента алг [1], направляем указатель п на предыдущий элемент.) Рис. 3.4. Блок-схема процедуры УНА. 2°. Запомнить значение п присваиванием j:=n. 3°. Выделить левую часть очередной формулы оператором ЧАСТЬ ФОРМУЛЫ (дев). (Теперь значение п указывает индекс эле- мента в массиве алг, содержащего разделительную стрелку после левой части.) 4°. Вычислить длину левой части /:=п—/—1. (Значение / нам понадобится как параметр для обращения к процедуре ИЗ СТРОКИ, - если мы найдем вхождение этой левой части в слово и будем исклю- чать это вхождение, чтобы заменить его на правую часть формулы.) 5°. Если алг [п]=КОД („:=”), то формула является завершающей. В этом случае присвоить переменной зав значение true. В против- ном случае присвоить зав значение false. (Этот пункт выполняется одним оператором присваивания зав: =алг[п]=КОД(„:
156 СТРУКТУРЫ ДАННЫХ (Гл. 3 6°. Выделить правую часть формулы оператором ЧАСТЬ ФОР- МУЛЫ (прав). (Теперь значение п указывает на разделитель после формулы.) 7°. Искать вхождение левой части формулы в слово оператором ПОДСТРОКА (лев, слово, вхож, т). 8°. Если вхождение не найдено (вхож—false), то перейти к пунк- ту 11°. (Если же вхож=true, то в результате работы процедуры ПОДСТРОКА мы получили в переменной т индекс звена в цепочке слово, предшествующего этому вхождению.) 9°.Выполнить процедуру исключения левой части оператором ИСКЛСТР (слово, tn, I) и процедуру включения на то же место пра- вой части формулы оператором ВКЛСТР (прав, слово, т). 10°. Если данная формула завершающая (sae=true), то окончить выполнение процедуры УНА. В противном случае перейти к п. Г. ' 11 °. Если алг [п]=999 (т. е. очередная формула оказалась послед- ней), то окончить выполнение процедуры УНА. В противном слу- чае перейти к п. 2°. Теперь можно написать процедуру УНА на алголе: procedure УНА(слово, алг)-, integer array слово, алг-, begin integer j, I, m, n; boolean вхож, зав-, integer array лев, прав (0:100]; нач:п:—0: npod:j:=n-, ЧАСТЬ ФОРМУЛЫ (лев)-, 1-.—п—j—1; зав:=алг(п]=К0Д („:=”)', ЧАСТЬ ФОРМУЛЫ (прав)-, ПОДСТРОКА (лев, слово, вхож, т); if вхож then begin ИСКЛСТР(слово, т, /); ВКЛСТР (прав, слово, т); go to if зав then конец else нач end else if алг[/г|=/=999 then go to npod-, конец: end УНА Мы не включили в процедуру УНА описания используемых в ней процедур, предполагая, что эти описания будут включены в программу, в которой применяется процедуру УНА. (Процедуры ВКЛСТР, ИСКЛСТР, ПОДСТРОКА, ЧАСТЬ ФОРМУЛЫ и КОД используются в процедуре УНА явно, а описания процедур В СТРО- КУ и ИЗ СТРОКИ должны быть включены потому, что эти процеду- ры используются в процедурах ВКЛСТР и ИСКЛСТР.) 3.4. Списки Список — это упорядоченный набор объектов произвольных размера и природы. Эти объекты называются записями. Упорядо- ченность записей определяется программистом и зависит от назна-
«.4) СПИСКИ - чения списка. Понятие списка естественно обобщает привычные нам по повседневной жизни такие структуры, как список абонентов телефонной сети, список (каталог) книг в библиотеке, список со- трудников учреждения, список городов в стране и т. д. Основными операциями над списками являются — переход к соседней записи, — включение новой записи, — исключение записи. В определенном смысле рассмотренные нами структуры очереди, стека и строки представляют собой частные случаи списков. Однако в очереди и стеке доступны только краевые записи (первая или по- следняя по времени поступления), а в строке объектами (записями) являются только отдельные символы (точнее, их коды). Общая структура списка не предусматривает подобных ограничений. Как правило, списки представляются в виде цепочек, состоя- щих из звеньев со ссылками. Такими цепочками мы пользовались для представления строк, но там каждое звено имело одинаковую жесткую структуру и состояло из двух значений: ссылки на следую- щее звено и кода символа. Когда мы имеем дело с произвольным списком, может возникнуть потребность снабжать каждую его за- пись различной справочной информацией, характеризующей как саму эту запись (например, ее размер), так и взаимосвязи данной записи с другими записями списка. Сама запись становится телом очередного звена, а соответствующая ей справочная информация образует справку звена. Первая запись списка хранится в первом звене цепочки, которому предшествует специальное заглавное звено, содержащее некоторые начальные данные для работы с данной це- почкой. Для единообразия это звено условно тоже разделяется на две части: справку и тело. Существует много разных типов списков. Мы разберем наиболее распространенные типы и рассмотрим их реализацию на алго- ле. Каждый тип списка характеризуется особенностями инфор- мации, содержащейся в справках звеньев, а также в заглавном ввене. 3.4.1. Однонаправленные списки. Наиболее естественным и простым типом списка является одно- направленный список, предназначаемый для того, чтобы просматри- вать его в одном направлении, от начала к концу. Однонаправленный список представляется в виде цепочки, в которой справка каждого звена состоит из двух значений. Первым значением является длина Nt звена, которая складывается из дли- ны записи и длины справки, равной 2. Вторым значением является ссылка на начало следующего звена. Далее следует тело звена (за- пись). Заглавное звено состоит из трех значений:
158 СТРУКТУРЫ ДАННЫХ [Гл. 3 /Vo=3 Ссылка на начало первого звена Ссылка на начало свободного места Первые два значения образуют справку заглавного звена, а ссылка на свободное место представляет собой тело этого звена. В справке последнего звена ссылка равна нулю. Схема представления однонаправленного списка изображена на рис. 3.5. Заметим, что рассмотренное ранее представление строки в виде Рис. 3.5. Схематическое изображение однонаправленного списка. списка, в котором все записи содержат по одному значению и за не надобностью в справках отсутствуют указания длин звеньев (по- скольку все звенья занимают стандартно по два элемента массива). Однонаправленный список можно отобразить в одномерный ал- гольный массив S следующим образом. Заглавная запись будет за- нимать элементы S[0], S(l] и 3(2 J. Далее размещаются остальные записи. Если индекс звена, содержащего запись х, равен i, то в элементе SU) находится длина этого звена, в элементе S[i+11 находится ин- декс звена со следующей записью списка (или нуль, если запись х последняя). Далее, начиная с элемента SU4-2], содержится сама за- пись х. 3.4.1.1. Включение записи в список. Пусть i — индекс звена, пос- ле которого нужно включить новую запись. Новое звено помеща- ется в массив 3 на свободное место, указываемое значением S12]. Таким образом, справка нового звена занимает в массиве 3 элементы с индексами S[2] и 3121+1. В первый из них элемент S[S[2]1 зано- сится значение длины нового звена. В следующий элемент 3[3[21+1] нужно занести ссылку на звено, которое прежде следовало за зве- ном с индексом I, т. е. нужно переписать туда S[i+11: S[S[21+ll:=S[i+H
».4) списки 15Э Далее расположится сама включаемая запись (тело нового звена). Само же звено с индексом t должно быть теперь сцеплено со вновь включенным звеном, и поэтому мы заносим в его справку индекс но- вого звена, равный 312]: S[< + 1]: = S[2] Схема включения новой записи изображена на рис. 3.6. Пусть новая запись хранится в массиве инф и занимает п эле- Рис. 3.6. Схема включения новой записи в однонаправленный список. кой займет (п+2) элемента. Поэтому в начало его справки (в эле- мент SISI2J1) мы заносим значение п+2. Теперь свободная часть массива 5 начинается за новым звеном, и нужно изменить значение указателя первого свободного элемента: S[2]: = S[2] + n + 2 Ниже приводится соответствующая процедура В СПИСОК на алголе. Цикл в теле процедуры служит для перенесения элементов массива инф в массив S. Для наглядности вводится вспомогатель- ная переменная нов, в которую помещается индекс S(2] начала но- вого звена. procedure В СПИСОК(З) запись: (инф) длина: (п) после: (i); - value n, i; integer array S, инф-, integer n, i\ begin integer нов, k; нов: =3(2]; Since]:=n+2; S[noe+l]:=S[i+ll; for k: = \ step I until n do S(noe+l+^]:=un0[^]; S[i+l]:=noe; S(2]:=S[2J+n+2 end В СПИСОК 3.4.1.2. Исключение записи из списка. Пусть задано значение t и нужно исключить из списка запись, которая содержится в звене, следующем за звеном с индексом i. Индекс исключаемого звена ра- вен значению Sli'+H. Исключение сводится к изменению этого зна- чения и аналогично изменению ссылки при исключении символа из строки. Иначе говоря, значение SU+1 ] заменяется на значение, ко- торое хранилось в ссылочной части справки исключаемого звена. Схематически это показано на рис. 3.7.
160 СТРУКТУРЫ ДАННЫХ [Гл. 3 Для наглядности введем переменную след, значением которой будет индекс звена, следовавшего за звеном с индексом I: след:—5И+1] Тогда исключение записи сводится к выполнению присваивания S[t + 1]: = 5[сл^+ 1] (Напомним, что эта пара операторов эквивалентна одному присваи- ванию S[t+l]:=S[SU+l]+l ]). Рис. 3.7* Схема исключения записи из однонаправленного списка. Соответствующая процедура записывается так: procedure ИЗ СПИСКА^) после:(1)\ value i\ integer array S; integer i\ begin integer след; c^d:=SU+l]; 5[/+1]:=Зкжд+1] end Как и в случае исключения символа из строки, исключаемое из цепочки звено остается в массиве 5. Заметим, что поскольку первые элементы заглавного звена офор- мляются так же, как справка любого звена (длина и ссылка), то процедуры включения и исключения выполняются правильно и при значении f=sO, т. е. при включении или исключении первой записи списка. Когда из списка исключаются записи, возникает вопрос об ис- пользовании места хранения этих уже не нужных записей. Если все записи имеют одинаковую длину, то вновь включаемые записи можно помещать на место исключенных; для этого из исключенных звеньев обычно образуется специальный список свободных звеньев. Если же длины записей разные, то проблема использования обра- зующихся пустот оказывается довольно сложной. Один из способов ее решения состоит в том, чтобы время от времени образовывать на новом месте копию списка, последовательно включая в нее только те записи, которые фактически присутствуют в списке. 3.4.2, Двунаправленные списки. * Рассмотренные выше однонаправленные списки предусматрива- ют жесткий порядок перебора записей, от первой к последней. Бы- вает так, что возникает потребность не только двигаться вперед по списку, но и возвращаться назад, чтобы посмотреть, а возможно, и изменить содержимое предыдущих записей. Эту возможность обес- печивает структура двунаправленного списка.
8.4J СПИСКИ 161 с Такой список представляется в виде цепочки, в которой каждое звено содержит ссылку на только на следующее, но и на предшест- вующее ему звено. Эта цепочка размещается в одномерном массиве и начинается со стандартного заглавного звена из четырех значений в элементах с индексами 0, 1, 2 и 3: JV.=4 Ссылка на первое звено (сначала «4») Нуль Указатель первого свободного элемента Каждое звено начинается со справки стандартного вида из трех вначений: Длина звена Ссылка на следующее звено Ссылка на предыдущее звено Схематически двунаправленный список показан на рис. 3.8. В заглавном звене пуста ссылка на предыдущее звено, а в пос- Рис. 3.8. Схематическое изображение двунаправленного списка» Такие списки позволяют удобным образом перебирать записи не только слева направо, но и справа налево. Поскольку можно пе- ребирать записи в любую сторону, мы получаем возможность, на- пример, в операции исключения записи указывать саму эту запись, а не предшествующую, как это приходилось делать в случае одно- * >• 3. Любвмекяй др.
162 СТРУКТУРЫ ДАННЫХ (Гл. 3 направленных списков. (Предшествующая запись нам понадобит* ся, но мы найдем ее по соответствующей ссылке в исключаемой за* писи.) 3.4.3. Кольцевые списки. Самой распространенной разновидностью двунаправленных списков являются кольцевые списки. В двунаправленном кольце- вом списке последнее звено ссылается как на следующее на заглав- ное звено, а то в свою очередь, ссылается на последнее звено как на Рис. 3.9. Схематическое изображение кольцевого списка. предыдущее. Такая организация списка упрощает процедуру по- иска или перебора записей с любого места с автоматическим перехо- дом от конца к началу или наоборот. Для краткости будем называть двунаправленный кольцевой список просто кольцевым списком. (Бывают и однонаправленные кольцевые списки, однако здесь мы не станем их рассматривать.) Схематически кольцевой список показан на рис. 3.9. 3.4.3.1. Включение записи в кольцевой список. Пусть требуется вставить звено с новой записью после звена с индексом i. Новое зве- но помещается на свободное место и включается в список путем кор- рекции ссылок в звене с индексом i и в том звене, которое раньше за ним следовало. Напишем процедуру В КОЛЬЦО включения записи в кольце- вой список. Запись длины п находится в массиве инф и вставляется в список после звена, начинающегося с элемента S[i]. Схема процедуры. Г. Заполнить первый элемент справки нового звена (его индекс находится в S[31) числом п+3. 2°. Заполнить второй элемент справки нового звена ссылкой на следующее за вставляемым звено. 3°. Заполнить третий элемент справки нового звена ссылкой на предшествующее вставляемому звено. 4°. Перенести в массив S запись из массива инф.
8.4] списки 163 5°. Изменить ссылку на предшествующее звено в справке звена, следующего за вставляемым. 6°. Изменить ссылку. на следующее звено в звене, указываемом значением i. 7°. Изменить указатель начала свободного места. Как и в случае однонаправленного списка, для наглядности ис- пользуем переменные нов и след. procedure В КОЛЬЦО (S) зтись: (инф) длина: (п) после: (i); value п, i; integer array 5, снф: integer n, i\ begin integer нов, след, k\ нов:=513); Med:=SU+l]; S [woel:=n+3; S[Hoe+ll:=ctfd; SlHoe-i-2]:==i; for k: — l step 1 until n do S [нов+2+к]:=инф [Л]; 3{след+2\:=нов\ SU+1 \:—нов\ S[3]:=S[3]+n+3 end 3.4.3.2. Исключение записи из кольцевого списка. Чтобы исклю- чить запись из кольцевого списка, нужно изменить две ссылки: у «соседа слева» изменяется ссылка на следующее звено, а у «соседа справа» — ссылка на предыдущее звено. Как и всякий двунаправленный, кольцевой список имеет то преимущество перед однонаправленным, что можно указывать ин- декс начала исключаемого звена, а не индекс начала звена, предше- ствующего исключаемому. (Этот последний индекс не обязательно указывать явно, так как его можно найти в справке исключаемого звена.) Схематически исключение записи показано на рис.3.10. При описании процедуры ИЗ КОЛЬЦА исключения записи вос- пользуемся переменными tiped=SU+2] и след=5[Н-11, значениями которых являются соответственно индексы звеньев, предшествую- 6*
164 j СТРУКТУРЫ ДАННЫХ [Гл. 3 ' щего и следующего за исключаемым звеном, которое указывается значением его индекса I. procedure ИЗ КОЛЬЦА (S) звено: (0; value 0 integer array S; integer i; begin integer след, пред', след:=5[Ж1; nped:=SU+2]; 3[пред+1]:=след; S [след+2]:=пред end 3.4.4. Иерархические списки. Когда мы объединяем информационные записи в список, тем самым образуется группа записей с определенным внутренним по* рядком. Можно рассматривать эту группу как единый информацион- ный объект и образовывать списки из таких составных объектов. Например, можно организовать список высших учебных заведений, где каждому вузу соответствует список факультетов. Такая иерар- хия может быть многоступенчатой, информацию о каждом факуль- тете можно представить как список курсов, информацию о курсе —> как список групп, а информацию о группе как список студентов. Если элементами списка верхнего уровня являются подчиненные списки, то удобно считать, что с точки зрения списка верхнего уровня входящая в него запись представляет собой ссылки на под- чиненный список, оформленные как заголовок этого подчиненного списка (заглавное звено цепочки, представляющей подчиненный список). При этом в теле заголовка подсписка может быть и некото- рая смысловая информация общего характера. Например, в заго- ловке списка студентов группы может указываться номер этой группы, фамилия старосты и т. д.
3.41 списки 165 Список верхнего уровня может содержать наряду с заголовками подчиненных списков и самостоятельные элементы (обычные записи). Таким образом, список верхнего уровня представляется в виде це- почки, в которой каждое звено состоит, во-первых, из справки и, во-вторых, из обычной записи или же заголовка подсписка (спис- ка с нижнего уровня иерархии). В иерархическом списке, схематически изображенном на рис. 3.11, двунаправленный список самого верхнего уровня состоит из четырех элементов, причем первый и третий элементы являются обычными записями. Второй элемент этого списка представляет со- бой однонаправленный список, в котором третий элемент — это двунаправленный кольцевой список, состоящий из обычной записи, однонаправленного кольцевого списка и еще одной обычной за- писи. Четвертый элемент списка самого верхнего уровня является двунаправленным кольцевым списком.из двух записей. Таким об- разом, иерархически связанные между собой списки могут быть разных типов. 3.4.5. Ассоциативные списки. Часто одни и те же объекты представляют интерес с различных точек -зрения, и возникает естественная потребность включать их в различные списки, составляемые по разным признакам. Напри- мер, одни и те же члены некоторого коллектива могут входить в раз- нообразные списки (по возрасту, месту рождения, месту учебы и другим признакам). Если в разных списках нужна одна и та же информация о каждом объекте, то желательно не дублировать за- писи. Избавиться от такого дублирования позволяют ассоциативные списки. Они организуются на одном общем наборе записей, причем каждый из ассоциативных списков объединяет в определенном по- рядке те записи из этого набора, которые обладают некоторым ха- , рактерным признаком. (Слово «ассоциативный» выбрано потому, что мы объединяем записи в список, ассоциируя их по некоторому - признаку.) Каждый ассоциативный список представляется в виде отдель- ной цепочки со своим заглавным звеном, которое помещается на фиксированном месте и позволяет производить операции над этим списком в отдельности. Эти цепочки переплетаются, поскольку од- ни и те же звенья входят в различные цепочки. Каждое такое зве- но, как и обычно, состоит из информационной части (записи) и стан- дартной справки. В справке находятся несколько ссылок или пар ссылок (по числу ассоциативных списков, в которые входят эти за- писи). Объединение различных списков на базе общего набора запи- сей называется также информационной сетью и представляет собой наиболее общую форму организации информации, соответствую- щую многосвязности объектов в окружающей среде. Как мы внаем, элементы данных, описывающие связанные элементы среды, также
166 СТРУКТУРЫ ДАННЫХ (Гл. 3 должны быть связаны между собой (либо через индексную арифме- тику, либо через сцепление.) Информационная сеть обеспечивает одновременную реализацию различных связей элементов данных. 3.4.6. Пример информационной сети с иерархическими и ассо- циативными связями. Составим список сотрудников некоторого подразделения с раз- биением на занимаемые ими служебные комнаты. Организуем так- же списки всех сотрудников данного подразделения с высшим обра- зованием, сотрудников мужского пола и сотрудниц. В качестве главного списка возьмем однонаправленный спи- сок комнат. Запись, представляющая комнату, является заголов- ком ассоциативного списка сотрудников, работающих в этой ком- нате. Этот и остальные ассоциативные списки будут основываться на одном общем наборе записей о сотрудниках. Информация о со- труднике может включать фамилию, имя, отчество, год рождения, место жительства и другие данные. Каждая запись о сотруднике по- падет в список одной из комнат, в список мужчин или женщин и мо- жет попасть в список имеющих высшее образование. Все эти ассо- циативные списки мы сделаем двунаправленными кольцевыми. За- главное звено списка каждой комнаты входит в список комнат и поэтому должно содержать ссылку^ вперед по списку комнат. Каж- дая запись о сотруднике представляет собой информационную часть соответствующего звена со4справкой стандартного вида, отражаю- щей связи этого звена в различных ассоциативных списках. Посколь- ку удобно, чтобы справка заглавного звена списка сотрудников в комнате не отличалась от справок остальных звеньев, относящихся к конкретным сотрудникам, то мы введем следующую единую струк- туру звена: SU’] — длина звена. SU+U — ссылка вперед по списку комнат (для звена, описы- вающего сотрудника, она не заполняется). SB+2] — ссылка вперед по списку сотрудников в комнате. SU+3] — ссылка назад по списку сотрудников в комнате. SU+4] — ссылка вперед по списку имеющих высшее образова- ние (для сотрудников без высшего образования она не заполняется). SU+5] — ссылка назад по списку имеющих высшее образова- ние (для сотрудников без высшего образования она не заполняется). S[i+6] — ссылка вперед по списку принадлежащих к мужскому или женскому полу. SU+7] — ссылка назад по списку принадлежащих к мужскому или женскому полу. SU+8] — начало информационной части звена. Элементы SU+6] и SU+7] будут использоваться по-разному в •ависимости от пола сотрудников. Такие ассоциативные списки называются альтернативными (в одних и тех же элементах справки
списки 167 в зависимости от пола сотрудников будут находиться ссылки, отно- сящиеся либо к списку мужчин, либо к списку женщин). Разумеет- ся, такое альтернативное использование места под ссылки возможно при строго непересекающемся членстве. Аналогично элементы SU+4] и SU+5] можно было бы использовать для организации ка- ких-либо списков сотрудников, заведомо не имеющих высшего об- разования,— например, для списка студентов-заочников. В звене, входящем в список комнат, заполняется элемент SU+11. В этом звене элементы SU+2] и SU+3] тоже заполняются, потому что оно входит в список сотрудников данной комнаты, яв- ляясь его заголовком. В его теле в элементе SU+8] договоримся хранить номер комнаты. Элементы справки этого звена S[i+4], SU’+5], SU+6] и SU+7] не заполняются, потому что они имеют смысл только для звеньев, относящихся к конкретным сотруд- никам. Заведем заголовки для главного и ассоциативных списков. Заго- ловки должны содержать ссылки в тех же позициях, что и обычные звенья соответствующих списков, поэтому в них образуются не за- полненные позиции. Положение всех заголовков фиксируем в по- следовательных элементах в начале массива S. 1) Заголовок списка комнат: S101 3. S[ll — ссылка вперед по списку комнат. S[2] — указатель свободного места. 2) Заголовок списка имеющих высшее образование: S[3] 6. S[4] не заполнено. S[5] не заполнено. S[6] не заполнено. S[7] — ссылка на первое звено в списке обладающих высшим образованием. S[8] — ссылка на последнее звено в списке обладающих высшим образованием (так как список кольцевой). 3) Заголовок списка мужчин: SI9] 8. S110] не заполнено. SI11] не заполнено. SI 12] не заполнено. SI 13] не заполнено. SU4] не заполнено. SI 15] — ссылка на первое звено в списке мужчин. SI16] — ссылка на последнее звено в списке мужчин. 4) Заголовок списка женщин выглядит так же: SU7] 8. S[181 не заполнено. S119] не заполнено.
168 СТРУКТУРЫ ДАННЫХ [Гл. 8 S[201 не заполнено. S1211 не заполнено. S122] не .заполнено. <S[23] — ссылка на первое звено в списке женщин. S124] — ссылка на последнее звено в списке женщин. Для иллюстрации сказанного рассмотрим пример списка подраз* деления, которое занимает две комнаты. В первой комнате работают две сотрудницы, во второй — одна. Первая сотрудница из первой комнаты и единственная сотрудница из второй комнаты обладают высшим образованием. Сотрудников мужского пола в подразделе- нии нет. На рис. 3.12 изображена схема объединения всех записей Рис. 3.12. Схема объединения записей о сотрудниках в списки. в списки, а в табл. 3.3 приведены значения всех элементов масси- ва S, соответствующие этой схеме объединения. С такой информационной сетью можно работать, по мере надоб- ности включая в нее или исключая записи о сотрудниках или за- писи о комнатах. Она позволяет перебирать информацию обо всех сотрудниках подразделения или отдельной комнаты, обо всех со- трудниках, имеющих высшее образование и т. д. Составим процедуру ВКЛСОТР включения записи о сотруднике во все ассоциативные списки, к которым относится этот сотрудник. Предварительно напишем вспомогательную процедуру ВКЛ включения записи о сотруднике в один (любой) из ассоциативных списков, организованных на данной совокупности записей. Одним из параметров этой процедуры будет указатель места, которое в стандартной справке каждого звена отводится для ссылок, относя- щихся к данному списку. Если указатель места ссылок равен k, то это означает, что в каждом звене, которое начинается с элемента
3.4} СПИСКИ 189 Таблица 3.3 1 s Ш Пояснения 0 1 2 3 25 81 Длина заголовка Ссылка на заголовок списка комнаты 1 Указатель свободного места Заголовок глав- ного списка 3 4 5 6 7 8 6 34 68 Длина заголовка Ссылка вперед по списку имеющих высшее образова- ние Ссылка назад по списку имеющих высшее образова- ние Заголовок спи- ска имеющих высшее образо- вание 9 10 11 12 13 14 15 16 го со 1 1 1 1 1 со Длина заголовка Ссылка вперед по списку мужчин Ссылка назад по списку мужчин Заголовок спи- ска мужчин 17 18 19 20 21 22 23 24 ® 1 1 1 1 IS $ Длина заголовка Ссылка вперед по списку женщин Ссылка назад по списку женщин Заголовок спи- ска женщин 25 26 27 28 29 30 31 32 9 46 34 55 Длина звена Ссылка на заголовок списка комнаты 2 Ссылка на 1-го сотрудника из комнаты Ссылка на последнего сот- рудника из комнаты Заголовок спи- ска комнаты 1 (справка звена главного спи- ска) Звено главного Списка
170 СТРУКТУРЫ ДАННЫХ (Гл. 3 Продолжение табл. 3.3 i SH1 Пояснения 33 1 Номер комнаты Тело звена 34 35 36 37 38 39 40 41 12 55 25 68 3 68 55 Длина звена |Ссылки по списку комнаты ^Ссылки по списку имею- /щих высшее образование jСсылки по списку женщин Справка звена Звено ассоциа- тивных списков 42 43 44 45 Информация о 1-й сотруд- нице из комнаты 1 Тело звена 46 47 48 49 50 51 52 53 9 0 68 68 Длина звена Следующей комнаты нет ^Ссылки по списку сотруд- / ников из комнаты 2 Заголовок спи- ска комнаты 2 (справка звена главного спи- ска) Звено главного списка 54 2 Номер комнаты Тело звена 55 56 57 58 59 60 61 62 13 25 34 0 0 34 17 Длина звена ^Ссылки по списку комнаты | Ссылки по списку имею- /щих высшее образование ^Ссылки по списку женщин Справка звена Звено ассоци- ативных спи- сков 63 64 65 66 67 Информация о 2-й сотруд- нице из комнаты 1 Тело звена
3.4] списки 171 Продолжение табл. 3,3 1 Пояснения 68 69 70 71 72 73 74 75 13 46 46 3 34 17 34 Длина звена |Ссылки по списку комнаты ^Ссылки по списку имею- j щих высшее образование |Ссылки по списку женщин Справка звена Звено ассоциа- тивных списков 76 77 78 79 80 Информация о 1-й сотруд- нице из комнаты 2 Тело звена 81 82 Свободное место в массиве S[f], элемент Sli+fel содержит ссылку вперед, a SU+&+1] содер- жит ссылку назад по данному ассоциативному списку. Поэтому для данного ассоциативного списка введенные нами ранее величины пред и след вычисляются так: nped:~Sli+k+\ 1; CAed:=S\i+k\ В нашем примере указатель места ссылок для списка сотрудников в комнате равен 2, для списка имеющих высшее образование равен 4, для списков мужчин или женщин равен 6. Будем писать вспомогательную процедуру ВКЛ, предполагая, что новая информация о сотруднике уже перенесена в массив S и начинается с элемента Мы включаем новое звено в конец каждого списка, а поскольку список кольцевой, то это означает, что новое звено включается перед заглавным звеном соответствую- щего списка. Поэтому удобно при обращении к процедуре BKJI указывать индекс след звена, перед которым нужно включить в список новое звено. (Прежде в аналогичных процедурах мы вклю- чали новое звено не перед, а после указанного звена.) Необходимо сформировать соответствующие данному списку ссылки в новом звене и откорректировать ссылки в тех звеньях, между которыми
172 СТРУКТУРЫ ДАННЫХ [Гл. 3 оно вставляется. Индекс пред звена, предшествующего включаемо- му, определяется присваиванием пред:=8(след+к+\]. Процедура ВКЛ может иметь вид: procedure ВКЛ (ное) перед: (след) место ссылок: (k); value ное, след, k; integer ное, след, k; begin integer пред’, пред :=5[след-|-Н-11; 8[нов+к]:=след; S (нов+к+1]:=пред; S\nped-\-k): =нов; 8[след+к+1 ]: =ное end При описании процедуры ВКЛСОТР будем пользоваться сле- дующими обозначениями: g — номер комнаты, в которую помещается новый сотрудник; высш — логическая переменная; истина, если сотрудник имеет высшее образование; муж — логическая переменная; истина, если сотрудник муж- ского пола; инф — массив, содержащий информацию о сотруднике; п — длина массива инф. Схема процедуры ВКЛСОТР. 1°. Занести длину п нового звена в первый элемент его справки. Это звено будет расположено на свободном месте массива S, начи- ная с элемента с индексом нов=5[2]. 2°. Найти индекс i заглавного звена для комнаты g путем после- довательного перебора списка комнат. 3°. Включить новое звено в список сотрудников этой комнаты, т. е. заполнить соответствующие ссылки в справке этого звена. (Включаемое звено попадет в конец списка, т. е. будет предшество- вать заглавному звену, индекс которого равен SU1). 4°. Включить то же звено в список имеющих высшее образова- ние, если это необходимо. (Звено включается перед заглавным зве- ном этого кольцевого списка, индекс которого есть 3.) 5°. Включить это звено в список мужчин или женщин (перед заглавным звеном с индексом 9 или 17). 6°. Переписать из инф в S информацию о сотруднике. 7°. Пересчитать указатель свободного места в элементе 5[2]. Процедура может выглядеть так: procedure ВКЛСОТР (инф, п) е список: (S) комната: (g) образование: (высш) пол: (муж); value п, g, высш, муж; integer array инф, S; integer п, g; boolean высш, муж; begin integer нов, i; rtoe:=S[2]; S [мов]:=п-|-8; i:=0; Af:t:=S(i-|-l]; if S[i+8]#=g then go to M; ВКЛ(нов) перед: (i) место ссылок: (2); if высш then ВКЛ (нов, 3, 4); if муж then ВКЛ (нов, 9, 6) else ВКЛ (нов, 17, 6);
ТАБЛИЦЫ 17» В.Б1 for f:=l step 1 until n do S[ ное 4-7 S[2]:=S[2]4-n+8 end ВКЛСОТР Замечание. Напомним, что оператор процедуры ВКЛ (ное) перед' (0 место ссылок'. (2) означает то же самое, что и оператор ВКЛ (ное, i, 2). Опишем теперь процедуру ИСКЛСОТР исключения сотрудника из списков. В ней описывается и используется вспомогательная процедура ИСКЛ исключения записи о сотруднике из одного ас- социативного списка. Как и в процедуре ВКЛ, параметром этой вспомогательной процедуры является указатель k места ссылок, относящихся к данному ассоциативному списку. В процедуре ИСКЛСОТР одним из параметров является ин- декс i звена, соответствующего исключаемому сотруднику. Последо- вательным применением вспомогательной процедуры ИСКЛ со- трудник исключается сначала из списка сотрудников в комнате, по- том из списка обладающих высшим образованием (если он там был), а затем из списка мужчин или женщин данного подразделения, procedure ИСКЛСОТР (0 образование: (высш) из списка: (S); value i, высш; integer t; boolean высш; integer array <S; begin procedure ИСКЛ(1г); value k; integer k; begin integer пред, след; пред:CAed:=S(i+k}; S[nped+k}:—cAed; S[cAed+k+l]:=nped end ИСКЛ; ИСКЛ(2); if высш then ИСКЛ (4); ИСКЛ(6) end ИСКЛСОТР Заметим, что нам не понадобилось вводить для вспомогательной процедуры ИСКЛ параметр, указывающий исключаемое звено, так как это звено указывается параметром i объемлющей процедуры ИСКЛСОТР. Когда мы обращались к вспомогательной процедуре ВКЛ, то указывали параметром след следующее звено списка, по- тому что для каждого списка оно было своим и поэтому изменялось _ от обращения к обращению. 3.5. Таблицы Широко распространенным видом деятельности, особенно при- годным для передачи его вычислительным машинам, является справочно-информационное обслуживание, которое включает в се- бя накапливание сведений, прием новых сведений и выдачу сведе- ний по запросам. Типичная задача справочно-информационного обслуживания со- стоит в том, чтобы организовать совместное хранение различных записей и выдавать по требованию любую запись вне зависимости
174 СТРУКТУРЫ ДАННЫХ [Гл. 3 от того, какие записи и в каком порядке выдавались ранее. Стан- дартной операцией является требование записи, и было бы обреме- нительно знать и при каждой такой операции указывать место хра- нения нужной записи. Избежать таких неудобств позволяет струк- тура данных, которая называется таблицей и в которой каждой за- писи соответствует определенное имя. Требуя запись, мы указы- ваем ее имя, а сама структура данных должна обеспечить достаточ- но быстрый поиск записи с искомым именем. Итак, таблица представляет собой набор именованных объектов (записей) произвольной природы. С каждым объектом однозначно связано его имя, которое и используется при работе с таблицей. «Именами» могут быть любые коды, но, чтобы в таблице можно было организовать эффективный поиск, нужна возможность сравнивать любые два имени и устанавливать, какое из них «меньше», а какое «больше». Существенно также, чтобы имена всех записей были раз- ными. Мы будем использовать в качестве имен числа. Имя записи часто называют также ключом этой записи. Каждая запись содер- жит свой ключ и некоторую информацию, связанную с этим клю- чом (текст записи). Над таблицами выполняются следующие операци