Текст
                    Ж Трамбле, П Соренсон
Введение
УРЫ
ных

J. P. TREMBLAY P. G. SORENSON Department of Computational Science University of Saskatchewan, Saskatoon An Introduction to Data Structures with Applications McGRAW-HILL BOOK COMPANY New York St. Louis San Francisco Auckland Diisseldorf J ohannesburg Kuala Lumpjur London Mexico Montreal New Delhi Panama Paris Sao Paulo Singapore Sydney Tokyo i Toronto
Ж. Трамбле, П. Соренсон ВВЕДЕНИЕ В СТРУКТУРЫ ДАННЫХ Перевод с английского В. И. БРИНКЕРА, В. В. ИВАНОВА, Ю. А. КИРЕЕВА, А. Е. КОСТИНА Под редакцией А. Е. Костина, В. Ф. Шаньгина МОСКВА « МАШИНОСТРОЕНИЕ » 1982
ББК 32.973 Т65 УДК 681.142.2 Трамбле Ж.> Соренсон П. Т65 Введение в структуры данных: Пер. ~ «нгл./Пер. В. И. Бриккер и др.; Пэд ред. А. Е. Костина, В. Ф. Шань- гина. —М.: Машиностроение, 1982. — 784 с., ил. В пер.: 3 р. 90 к. Книга посвящена структурам данных, являющимся основой для таких раз- делов вычислительной техники, как хранение и выборка информации, опера- ционные системы, разработка компиляторов, машинная графика, искусственный интеллект и т. д. Рассмотрены примитивные структуры данных и операции над ними, обработка символьных строк, вопросы формальных грамматик, линейные структуры данных (массивы, стеки, очереди, списки), нелинейные структуры (древовидные структуры), методы сортировки н поиска, структуры файлов и опе- рации над ними. Большинство понятий проиллюстрировано алгоритмами и про- граммами, записанными на языке ПЛ/1. Книга предназначена для специалистов по обработке данных иа ЭВМ, созда- нию программного обеспечения ЭВМ и сложных информационных систем. Она может быть полезна студентам и аспирантам соответствующих специальностей. _ 2405000000 -067 м Т -038(01)-82 67'82- ББК 32.973 6Ф7.3 Copyright © 1976 by^Mc Graw-Hill, Inc. © Перевод на русский язык, «Машиностроение», 1982 г.
ОГЛАВЛЕНИЕ Предисловие к русскому изданию .................................... 9 Предисловие.................................................... 11 0. Введение....................................................... 16 0-1. Структуризация и решение задач............................. 16 0-2. Система записи алгоритмов.................................. 21 Глава 1. Информация и ее представление в памяти................... 26 1-1. Природа информации......................................... 26 .Упражнения к п. 1-1.......................................’ . 29 1-2. Передача информации ....................................... 29 1-3. Хранение информации ....................................... 33 • 1-4. Примитивные структуры данных............................... 37 1-4.1. Операции над структурами данных ....................... 37 1-4.2. Системы счисления...................................... 39 Упражнения к п. 1-4.2......................................... 41 1-4.3. Преобразования систем счисления........................ 41 Упражнения к п. 1-4.3......................................... 47 1-4.4. Целые числа и их представление ........................ 48 Упражнения к п. 1-4.4......................................... 53 1-4.5. Действительные числа и их представление................ 53 1-4.6. Структуры хранения числовых данных .................... 55 Упражнения к п. 1-4.6......................................... 65 1-4.7. Символьная информация.................................. 65 1-4.8. Структуры хранения символьных данных................... 68 Упражнения к п. 1-4.8......................................... 73 1-4.9. Логическая информация ................................. 74 1-4.10. Структуры хранения логических данных ................. 75 1-4.11. Указатели ............................................ 76 1-4.12. Структуры хранения указателей......................... 80 Список литературы............................................... 33 Глава 2. Представление и манипулирование строками ................ 84 2-1. Определения и понятия ...................................... 84 2-2. Формальные системы обработки строк......................... 86 2-2.1. Алгоритмы Маркова..................................... 86 2-2.2. Грамматики............................................. 96 Упражнения к п. 2-2............................................ 109 2-3. Манипулирование строками и поиск по образцу............ 111 2-3.1. Примитивные функции для манипулирования строками ... 112 2-3.2. Базовые функции ........................................ Н5 2-3.3. Действия над строками в ПЛ/1.......................... 122 2-3.4. Манипулирование строками в языке СНОБОЛ............... 127 2-3.5. Рекурсивные образец-структуры......................... 137 5
Упражнения к п. 2-3....................................... 2-4. Машинное представление строк........................... Упражнения к п. 2-4 .......... ......................... 2-5. Применения операций над строками ...................... 2-5.1. Редактирование текста ............................. Упражнения к п. 2-5.1..................................... 2-5.2. Лексический анализ ................................ Упражнения к п. 2-5.2..................................... 2-5.3. Индексирование типа KWIC .......................... Упражнения к п. 2-5.3..................................... 2-5.4. Использование битовых строк при информационном поиске Упражнения к п. 2-5.4..................................... Список литературы............................................. 141 142 149 149 150 172 172 177 178 183 184 192 193 Глава 3. Линейные структуры данных и их последовательное пред- ставление в памяти................................... 194 3-1. Понятия о непримитивных структурах данных............... 194 3-2. Операции над непримитивныМи структурами данных ......... 195 3-3. Последовательные структуры хранения .................... 196 3-4. Простые структуры данных, определяемые программистом .... 197 3-5. Структуры хранения массивов ............................ 201 3-6. Стеки................................................... 207 3-6.1. Определения и понятия............................... 207 3-6.2. Операции над стеками ............................... 208 3-7. Применение стеков ...................................... 214 3-7.1. Рекурсия ........................................... 214- Упражнения к п. 3-7.1...................................... 231 3-7.2. Выражения в польской записи и их компиляция......... 232 Упражнения к п. 3-7.2 .'................................... 251 3-7.3. Стековые машины .................................... 251 ' 3-8. Очереди.........................’....................... 254 Упражнения к п. 3-8................’ ........................259 3-9. Моделирование системы с разделением времени............. 260 Упражнения к п. 3-9 .'....................................... 274 Список литературы.............................................. 277 Глава 4.л Линейные структуры данных и их связанное представление в памяти........................................................ 278 4-1. Указатели и связанное распределение памяти............... 278 4-2. Линейные связанные списки ............................... 289 -4-2.1. Операции над односвязными линейными списками......... 289 Упражнения к п. 4-2.1....................................... 309 4-2.2. Циклически связанные линейные списки................. 311 Упражнения к п. 4-2.2....................................... 313 4-2.3. Двусвязные линейные списки .................... 313 Упражнения к п. 4-2.3....................................... 318 4-3. Применение связанных линейных списков.................... 319 4-3.1. Манипулирование многочленами....................• . . 319 Упражнения к п. 4-3.1....................................... 323 4-3.2. Связанные словари.................................... 323 Упражнения к п. 4-3.2....................................... 330 4-3.3. Арифметика многократной точности..................... 331 Упражнения к п. 4-3.3....................................... 345 4-4. Ассоциативные списки ......................,............. 345 Упражнения к п. 4-4....................................... 351 Список литературы ........................................... 352 Глава 5. Нелинейные структуры данных . . . • •.................. 353 •5-1. Деревья................................................... 353 5-1.1. Определения и основные понятия....................... 353 6
Упражнения к п. 5-1.1........................................ 366 5-1.2. Представление деревьев в памяти и манипулирование деревьями 367 Упражнения к п. 5-1.2........................................ 382 5-2. Приложения деревьев ....................................... 382 5-2.1. Манипулирование арифметическими выражениями......... 383 Упражнения к п. 5-2.1........................................ 388 ? 5-2.2. Формирование таблиц символов........................... 388 Упражнения к п. 5-2.2...................................... 391 5-2.3. Синтаксический анализ............................... 391 Упражнения к п. 5-2.3........................................ 399 5-2.4. Трансляция таблиц решений............................. 400 5-3. Многосвязные структуры..................................... 419 5-3.1. Разреженные матрицы .................................. 419 Упражнения к п. 5-3.1........................................ 427 5-3.2. Генерация предметного указателя....................... 427 Упражнения к п. 5-3.2........................................ 435 5-4. Графы и их представление................................... 435 5-4.1. Матричное представление графов........................ 436 Упражнения к п. 5-4.1........................................ 443 5-4.2. Списковые структуры................................... 444 Упражнения к п. 5-4.2........................................ 454 5-4.3. Другие способы представления графов................... 454 Упражнения к п. 5-4.3........................................ 457 5-5. Применения графов ......................................... 458 5-5.1. PERT и связанные с ним методы......................... 458 Упражнения к п. 5-5.1........................................ 465 5-5.2. Применение в машинной графике......................... 465 Упражнения к п. 5-5.2........................................ 484 5-5.3. Символическое Дифференцирование ...................... 484 Упражнения к п. 5-5.3........................................ 489 5-5.4. Топологическая сортировка............................. 489 Упражнения к п. 5-5.4....................................... 499 5-6. Динамическое управление памятью.......................... 499 Список литературы............................................... 526 f л а в а 6. Сортировка и поиск................................. 528 6-1. Сортировка .............................................. 528 6-1.1. Система записи и основные понятия..................... 528 45-1.2. Сортировка способом выбора .......................... 530 6-1.3. Сортировка методом пузырька .......................... 532 ?_ 6-1.4. Обменная сортировка с разделением................... 534 6-1.5. Методы сортировки на деревьях......................... 536 f 6-1.6. Сортировка слиянием ................................. 540 » 6-1.7. Поразрядная сортировка ............................. 545 6-1.8. Сортировка с вычислением адреса...................... 548 Упражнения к п. 6-1......................................... 549 6-2. Поиск.................................................... 550 6-2.1. Последовательный поиск .............................. 550 6-2.2. Бинарный поиск....................................... 551 6-2.3. Деревья поиска ................................. 553 6-2.4. Методы хеширования таблиц ........................... 564 Упражнения к п. 6-2......................................... 582 Список литературы............................................... 582 Г л а в а 7. Файловые структуры данных ......................... 584 7-1. Устройства внешней памяти ............................... 584 7-1.1. Магнитные ленты...................................... 585 7-1.2. Магнитные барабаны .................................. 587 7-1.3. Магнитные диски...................................... 591 7-1.4. Массовая память...................................... 594 7
7-1.5. Промежуточная память..................................... 595 Упражнения к п. 7-1............................................. 596 7-2. Определения и основные понятия............................... 597 7-3. Организация записей.......................................... 600 Упражнения к п. 7-3............................................. 609 7-4. Последовательные файлы....................................... 610 7-4.1. Структура последовательных файлов ....................... 610 7-4.2. Обработка последовательных файлов ....................... 614 7-4.3. Последовательные файлы в ПЛ/1 ........................... 619 Упражнения к п. 7-4............................................. 628 7-5. Небольшая система оплаты счетов ............................. 629 7-5.1. Анализ системы .......................................... 630 7-5.2. Проектирование системы .................................. 631 7-5.3. Реализация .............................................. 635 7-6. Индексно-последовательные файлы ............................. 643 7-6.1. Структура индексно-последовательных файлов .............. 644 7-6.2. Обработка индексно-последовательных файлов .............. 652 7-6.3. Индексно-последовательная организация файлов в ПЛ/1 . . . 659 Упражнения к п. 7-6..................................... 666 7-7. Система контроля успеваемости студентов...................... 667 7-7.1. Системный анализ......................................... 667 7-7.2. Проектирование системы .................................. 668 7-7.3. Реализация .............................................. 674 7-8. Файлы прямого доступа........................................ 684 7-8.1. Структура файлов прямого доступа......................... 684 7-8.2. Обработка файлов прямого доступа......................... 690 7-8.3. Файлы прямого доступа в ПЛ /1 ........................... 694 Упражнения к п. 7-8............................................. 707 7-9. Онлайновая банковская система ............................... 708 7-9.1. Анализ системы .......................................... 708 7-9.2. Проектирование системы .................................. 710 7-9.3. Реализация .............................................. 714 7-10. Другие методы организации файлов ........................... 723 7-10.1. Виртуальная память...................................... 723 । 7-10.2. Файлы в методе доступа VSAM ........................... 732 Упражнения к п. 7-10 . . :...................................... 739 7-11. Доступ по многим ключам .................................... 739 7-11.1. Мультисписковая организация............................. 741 7-11.2. Инвертированные списки ................................ 743- 7-11.3. Мультисписковая структура с управляемой длиной списка 746 7-11.4. Структуры с разделением на секции....................... 748 7-11.5. Ведение мультисписковой структуры ...................... 759 7-11.6. Ведение инвертированных списков......................... 753 7-11.7. Ведение ограниченного мультисписка и секционированных структур ............................................... 754 7-11.8. Обзор методов доступа со вторичными ключами............. ^54 Упражнения к п. 7-11 ........................................... ?67 7-12. Введение в системы баз данных .............................. 758 7-12.1. Общие концепции в системах баз данных .................. '°8 7-12.2. Иерархический подход.................................... 761 7-12.3. Сетевой подход ......................................... 764 7-12.4. Реляционный подход ..................................... 768 7-12.5. Заключение......................................... . 773 Список литературы ... 774 Именной указатель................................................... 776 Предметный указатель .............................................. 778.
ПРЕДИСЛОВИЕ К РУССКОМУ ИЗДАНИЮ Предлагаемая книга посвящена структурам данных, представляющим собой один из важнейших компонентов в лю- фой компьютерной системе. Теория структур данных имеет дело к: формальным описанием данных, операций над ними и способами представления и организации данных в машинной памяти. Зна- ние этой теории является необходимым условием для изучения таких разделов вычислительной науки, как операционные системы, компиляторы, банки данных и системы управления базами дан- ных, информационно-поисковые системы, искусственный^ интел- лект и т. д. Важность структур данных подчеркивается в ряде известных программ обучения по вычислительной науке. Не углубляясь в формальные аспекты теории, авторы знакомят чи- тателя с широким спектром структур данных, начиная от наи- более примитивных (числа с фиксированной и плавающей точкой, логические данные, литеры и указатели) и кончая весьма разви- тыми файловыми структурами. Хотя в книге и имеется раздел, посвященный важнейшим концепциям и принципам организации баз данных, но основная часть материала отражает особенности того уровня информационных структур, который приближен к их физической организации и на котором могут быть созданы ориен- тированные на пользователя структуры верхнего (логического) уровня, характерные для систем управления базами данных. Это значит, что книга нацелена главным образом на «микромир» структур данных. В книге подробно рассмотрены и проиллю- стрированы на многочисленных примерах методы представления и операции над символьными строками, линейными структурами (массивами, стеками, очередями), связанными списками, древо- видными структурами и файловыми структурами с различной организацией. Каждая из структур данных проанализирована с точек зрения ее «внешнего» описания, физического представле- ния в машинной памяти и выполняемых над ней операций, вы- раженных да языке принятой в книге единой системы записи ал- горитмов, подобной той, которая использована в трех томах известного труда Д. Кнута «Искусство программирования для ЭВМ» (М.: Мир, 1976—1978). Основной материал книги дополня- ется удачно подобранными задачами, многие из которых представ- 9
ляют существенный практический интерес, а также детально проработанными учебными примерами, являющимися хорошей основой для разработки более реалистических систем обработки данных на базе идей, изложенных в книге. Несомненным достоинством книги является то, что ряд алго- ритмов, предназначенных для организации и манипулирования структурами данных, доведен до текстов программ, причем эти программы записаны на языке ПЛ/1, который широко использу- ется в нашей стране. Не перегруженная формально-теоретическими выкладками, книга последовательно и методически убедительно освещает всю проблематику структур данных. Она представляет большой интерес для широкого круга специалистов по обработке данных на ЭВМ, созданию программного обеспечения и проекти- рованию разнообразных информационных систем, а также может быть полезна студентам и аспирантам, специализирующимся в области вычислительной науки. В качестве терминологической основы при переводе послужил упомянутый выше труд Д. Кнута. Во многих местах книги пере- водчики отступили от установившейся традиции и применительно к языкам программирования наряду с термином «оператор» исполь^рвали термин «предложение». Кроме того, по аналогии с «последовательным файлом» и «индексно-последовательным фай- лом» было решено использовать «прямой файл» как синоним бо- лее распространенного термина «файл прямого доступа». Перевод книги выполнили В. И. Бриккер (гл. 1, 5, 6), В. В. Иванов (гл. 7), Ю. А. Киреев (гл. 3, 4) и А. Е. Костин (предисловие, гл. О и 2). А. Е. Костин В. Ф- Шаньгин
Посвящается Моим родителям Филиппу и Анне Трамбле Моей жене Линде и дочери Кимберли Соренсон ПРЕДИСЛОВИЕ Вычислительная наука имеет дело в первую очередь р изучением структур данных (информации) и их преобразованием Механическими средствами. Важность структур данных признана $ учебной программе «Curriculum 68» Ассоциации вычислитель- ных машин (АСМ)1 и ряде других документов этой же Ассоциа- ции, относящихся к дисциплинам «Структуры данных» и «Инфор- мационные Структуры» 2’3’4. Первое последовательное и основательное рассмотрение струк- тур данных предпринял Д. Кнут5. Его большой вклад в эту Область оказал влияние на построение и систему обозначений & гл. 3, 4, 5 и 6 данной книги. В большинстве учебных планов по вычислительной науке имеется по крайней мере один курс по структурам данных. Од- нако во многих случаях такой курс преподается на предпоследнем Йли последнем году обучения. Это обстоятельство затрудняет студенту применять концепции структур данных при изучении йтериала специфичных курсов, связанных с обработкой инфор- мации (например, анализ и проектирование информационных Ввстем, хранение информации, операционные системы, разработка компиляторов, машинная графика, искусственный интеллект и [ристическое программирование). Данная книга написана на нове материалов, используемых в двухсеместровом курсе, таемом во вторых семестрах первого и второго годов обучения университете пров. Саскачеван. Причина появления курса на оль раннем этапе учебной программы состоит в том, что он едназначен для ознакомления студента с понятиями и терми- 'логией, используемыми на последующих курсах. Любой курс йо структурам данных должен быть направлен на достижение Следующих основных целей. 1 САСМ, vol. 11, №3, 1968, рр. 151—197. 8 САСМ, vol. 14, №9, 1971, рр. 573—588. 3 САСМ, vol. 15, № 5, 1972, рр. 363/-398. * САСМ, vol. 16, № 12, 1973, рр. 727—749. 8 Кнут Д. Искусство программирования для ЭВМ. Т. 1. Основные алго- ритмы: Пер. с англ. М.: Мир, 1976. 735 с. И
1. Ввести студента в круг тех аспектов структур данных, ко- торые потребуются на последующих курсах по вычислительной науке. 2. Заинтересовать’ студента иллюстрацией ключевых концеп- ций на примерах из разных областей применения вычислительной техники. 3. Развить у обучаемого интуитивное понимание основных концепций структур данных. Новые понятия следует вводить модульным способом, т. е. в терминах ранее усвоенных понятий, и таким путем, который дает студенту возможность воспринимать вычислительную науку как единый предмет. Это может быть достигнуто охватом как можно большего числа дисциплин, которые требуют знания рассматрива- емых основных структур. Хорошо подобранные примеры могут облегчить выполнение этой задачи и в значительной мере стимули- ровать интерес студента. Такой курс должен познакомить сту- дента с терминологией, используемой на более поздних курсах. Этот подход будет порождать ощущение знакомства с предметом в начале изучения соответствующих дисциплин. Нам хотелось бы подчеркнуть, что понятия и терминологию следует вводить задолго до того, как они будут использоваться. В противном слу- чае студенту придется усваивать одновременно и основной ин- струмент и предмет, к которому этот инструмент приме- няется. Хотя обсуждаемые примеры предназначены для ознакомления с разными областями применения вычислительной техники, столь же важная их цель состоит и в том, чтобы проиллюстрировать процесс решения задач. Большинство студентов приходят рабо- тать в фирмы, которые нуждаются в специалистах по решению задач. Многие студенты, исследуя конкретную ситуацию в орга- низации, испытывают трудности при формулировке задачи и часто, завершив свои исследования, оказываются неспособны структурировать задачу, полагая, что это уже сделано. Гл. 1 содержит обсуждение природы информации. Вводятся примитивные структуры данных (действительные и целые числа, символьные данные, данные типа указателя и т. д.), а также методы представления всех этих данных в памяти, используемые в раз- ных вычислительных машинах. Кратко обсуждены понятия пре- образования систем счисления и типы кодов. Предмет гл. 2-—манипулирование строками. В ней обсужда- ются две формальные системы для манипулирования строками, а именно алгоритмы Маркова и формальная грамматика. С по- мощью этих систем может быть определен ряд желаемых прими- тивных операций для манипулирования строками и их сопоставле- ния. Необходимость этих операций мотивировалась, в частности, некоторыми приложениями обработки строк, такими как система редактирования текстов и система индексирования KWIC (Key Word In Context — ключевое слово, взятое в контексте). 12
Гл. 3 знакомит с линейными структурами данных, такими как массивы, стеки, очереди, деки и т. д., и с соответствующими операциями, которые могут выполняться над этими структурами. Обсужден ряд возможных структур хранения, основанных на 'последовательном распределении. Довольно подробно рассмотрено понятие рекурсии (и ее применение), поскольку многие языки программирования дают возможность использовать ее. Понятие рекурсии важно и само по себе, так как студенты в своей деятель- ности будут сталкиваться с задачами, в которых применение рекурсии неизбежно вследствие рекурсивной природы процесса или рекурсивной структуры данных. Упомянуты предложения ALLOCATE и FREE языка ПЛ/1. Приведен ряд примеров при- менения линейных структур данных (компиляция выражений в польской записи и моделирование простой системы с разделением времени). Гл. 4 посвящена представлению в памяти линейных структур данных, основанному на связанном распределении. Описаны атрибуты POINTER и BASED языка ПЛ/1. Обсуждены двусвяз- ные и циклические структуры. Представлены примеры их при- менения (формирование таблицы символов и выполнение арифме- тических действий с многократной точностью). В гл. 5 дается исчерпывающее описание нелинейных структур и их последовательного и связанного представлений в памяти. Наиболее важными нелинейными структурами являются дере- вья, и поэтому их представления и манипулирование ими обсу- ждены достаточно детально. Кроме того, в главу включен ряд при- меров, таких как построение словаря и таблиц решений. Опи- саны также многосвязные структуры. Представлены структуры типа графов и некоторые относящиеся к ним применения, такие как метод PERT, метод критического пути (СРМ) и машинная графика. И, наконец, дано введение в область динамического управления памятью. Гл. 6 описывает внутренние методы поиска и сортировки. Рассмотрены методы поиска, основанные на бинарных деревьях, и методы хеширования. Достаточно подробно описаны такие ме- тоды сортировки, как обменная сортировка с разделением, пира- мидальная сортировка, сортировка слиянием. Проведено срав- нение этих трех методов и показано, что характеристики некото- рых методов могут быть значительно улучшены выбором подхо- дящей структуры данных (и соответствующей структуры хране- ния). Наконец, гл. 7 содержит довольно исчерпывающее описание внешних файлов. Рассмотрены устройства внешней памяти, по- скольку их характеристики важны при проектировании файлов и их обработке. Дано введение в ряд способов организации фай- лов, таких как последовательный, индексно-последовательный и прямой. Обсужден ряд методов организации файлов со многими ключами (мультисписки и инвертированные списки). Для боль- 13
шинства файлов представлены примеры их применения: неболь- шая система оплаты счетов, банковская система реального вре- мени и система учета успеваемости студентов. Кратко рассмотрены системы управления базами данных. Упор в книге сделан главным образом на решение задач, ана- лиз алгоритмов и в меньшей степени на программирование. Мы стремились к тому, чтобы новые понятия были хорошо иллюстри- рованы примерами и отработанными задачами. Подход, приня- тый при изучении материала, основан на модульном принципе — сложные структуры рассматриваются в терминах более простых структур. Порядок изучения при этом таков: примитивные струк- туры данных, линейные структуры данных, деревья, многосвяз- ные структуры, графы (списки) и файлы. Выбор языка (языков) программирования для решения задач в упражнениях является неотъемлемой частью процесса решения задачи. В идеальном случае студент должен выбрать такой язык, который обеспечивает «безболезненную» формулировку решения задачи. Хотя для конкретных областей применения существует целый ряд специализированных языков, едва ли можно ожидать, что студент свободно владеет всеми такими языками. В книге об- суждаются особые черты, присущие широкому кругу языков; однако все примеры выполнены на языке, с которым студенты знакомы и который, содержит следующие характеристики. 1. Символьные строки с динамически изменяющейся длиной. 2. Структурные переменные, т. е. переменные, которые могут содержать элементы с различными типами данных. 3. Средства, позволяющие программисту динамически созда- вать и уничтожать переменные (включая структурные переменные). 4. Рекурсия. 5. Управляющие структуры, которые обеспечивают должное структурирование программ. Подходящими языками для этой цели являются АЛГОЛ W, ПЛ/1 и СНОБОЛ (хотя управляющие структуры в языке СНОБОЛ недостаточны с точки зрения характеристики 5). ФОРТРАН совершенно неприемлем для использования (в нем отсутствуют все названные пять черт). Мы выбрали ПЛ/1 в качестве основного языка, поскольку он обеспечивает требуемые средства доступа к файлам. Хотя по структурам данных имеется ряд книг, лишь в немно- гих из них приводится исчерпывающее описание файлов в терминах более простых структур, которые рассматривались бы там же. Кроме того, ни одна из тех книг, с которыми мы познакомились, не была написана в духе той философии и организации, какие предлагаются нами. Эта книга соответствует курсу 11 «Структуры данных» в сооб- щении «Curriculum 68» х, а также курсам UC1 и UC3 «Рекомен- * См. сноску 1 на с. 11. 14
даций по учебным планам для студентов в области информацион- ных систем» х. Мы не следуем точно этим курсам, но книга доста- точно близка им по духу. Предполагается знакомство читателя с основами языка ПЛ/1. Мы многим обязаны Джону А. Копеку, который высказал ряд ценных критических замечаний и предложений на всем этапе подготовки и корректуры данной книги. В частности, Джон А. Копек оказал помощь в подготовке пп. 5-2.2, 5-5.1 и 5-6. Мы также в долгу у Ричарда Ф. Дыочера, который предложил и отладил многие программы и помог при подготовке пп. 4-3.3, 5-3.1, 5-5.4, 7-1 и 7-3. Мы высоко ценим труд Линды Найлэндер, которая выполнила многие из иллюстраций и оказала помощь, в подготовке пп. 3-7.1, 3-9, 5-2.4, 5-4.2 и 5-5.2. Мы также признательны Уолтеру Риджвею за помощь в под- готовке пп. 2-5.4, 7-5, 7-7 и 7-9. Ричард Купер помог нам в при- мере применения генерации указателей, а Лорна Стюарт под- готовила все иллюстрации к гл. 7. Аллан Листон осуществил корректуру большей части рукописи и отладил ряд программ. Наконец, Роберт Кавэна помог нам в п. 5-5.2, а Ричард Бант — в корректуре пп. 3-9, 7-1 и 7-2. И особенно мы благодарны Дай- эне Гуд и Дорине Бейкер, которые превосходно справились с пе- чатанием рукописи, а также Гейлу Уолкеру за содействие в этой работе. Весь труд был бы невозможен без поддержки, оказанной университетом пров. Саскачеван. Ж. Трамбле П. Соренсон 1 См. сноску 2 на с. 11.
0. ВВЕДЕНИЕ В этой главе мы обсудим важность структуризации не только данных, относящихся к решению той илн иной задачи, но также и программ, оперирующих с этими данными. Поиск решения задачи становится проще, если ее можно пред- ставить в терминах подзадач. Процесс структуризации при решении задач обычно отражается и на соответствующей программе, которая в этом случае приобретает свойство модульности. Такой подход к постановке и решению задач оказал глу- бокое воздействие на разработку многих языков программирования, в частности языков без оператора GO ТО. Процессы или модули, соответствующие опера- циям, выполняемым над структурами данных, часто представляются подпро- граммами или функциями. Если программа предназначена для реализации сколь- ко-нибудь сложной задачи, то ее крайне необходимо организовать в виде сово- купности подходящих модулей, илн подпрограмм. В этом и состоит простей- ший подход к разработке программ. Все алгоритмы и программы, приведенные в этой книге, написаны в духе именно такой философии. Во втором, заключительном, параграфе главы изложена и проиллюстри- рована на примерах система записи алгоритмов, используемая в книге. 0-1. СТРУКТУРИЗАЦИЯ И РЕШЕНИЕ ЗАДАЧ Задачи, решаемые на вычислительных машинах, по- стоянно усложняются. Соответствующие им программы увели- чиваются в размере, а их понимание все более затрудняется. Про- граммист или программисты, ответственные за реализацию этих больших задач, нагружаются массой информации, содержащей их описания и структурные схемы. Разработка программы для вычислительной машины стано- вится проще, если исходная задача может быть представлена в терминах ее подзадач. Этот процесс структуризации обычно приводит к тому, что в результате получается модульная про- грамма, содержащая ряд небольших частей. Концепция модульности программ не нова. К настоящему времени имеется ряд операционных систем, созданных по модуль- ному принципу. При этом разработчик вычислительной машины поставляет пользователю операционную систему, состоящую из многих программных модулей. Пользователь может определить и создать конкретную операционную систему для своих нужд путем выбора подходящей совокупности модулей. Выбранные мо- дули могут быть настроены на определенные условия работы « помощью системных параметров, представляющих эти условия. 16
УроВень 2 Уровень 4 Уровень 3 Уровень 1 0-1.1. Иерархическая структура си- Рис. ( стемы Кроме того, поскольку про- грамма операционной системы находится обычно в состоянии постоянной модификации, соот- ветствующие изменения вы- полнить в ней гораздо проще, если она разделена на ряд про- граммных модулей, взаимосвязи которых просты и четко опре- делены. Сложную программу обычно нельзя представлять в виде на- бора программных модулей до тех пор, пока она не будет должным образом структурирована, или организована. При разра- ботке больших программных систем участвует много людей, и при этом решения, принимаемые одним программистом (например, выбор имен меток и переменных), не должны влиять на работу других программистов. Достичь этого можно только тогда, когда описание и спецификация каждого программного модуля и его интерфейсов представлены ясно и просто. При этом часто полу- чается, что должная организация и описание некоторых задач требуют гораздо больше времени и средств, чем их программиро- вание. Модульность большинства систем можно представить в виде иерархической структуры (графа), как показано на рис. 0-1.1. Эта структура содержит единственный основной модуль, с ко- торым ассоциируется уровень /, определяющий общее описание системы. Основной модуль связан с рядом подчиненных модулей, с которыми ассоциируется уровень 2, дающий более детальное описание системы, чем основной модуль. Модули уровня 2 свя- заны со следующим подразделением модулей на уровне 3 и т. д. Вполне возможно, что модули высоких уровней ссылаются на модули низких уровней (что обозначалось бы на схеме стрелками, идущими снизу вверх), хотя это и не показано на рис. 0-1.1 и не характерно для общего случая. Такая концепция иерархиче- ского структурирования является фундаментальной при реше- нии задач. Именно подобная форма организации, или структури- рования, позволяет уяснить систему на разных уровнях абстрак- ции и осуществить изменения на одном уровне без необходимости полного понимания более детальных описаний на высоких уров- нях. Кроме того, в этом иерархическом процессе структурирования весьма важна возможность понять тот или другой модуль системы независимо от прочих модулей того же уровня. В идеальном слу- чае программа структурируется подобно тому, как это показано на описанном рисунке. Эта идеальная модульная структура не обязательно предполагает, что существует прямое соответствие между схемой управления в программе и взаимосвязями между 17
модулями подзадач. Действительно, в большинстве случаев мо- дуль соответствует подпрограмме или процедуре, но вполне воз- можно, что с некоторым модулем задачи не связаны никакие ис- полнимые операторы. Наилучшим примером такого рода является модуль, описыва- ющий информационную схему, необходимую для коммуникации между другими модулями. В таком модуле для нас важнее всего структурирование информации, позволяющее применять легко понятные и эффективные методы доступа к этой информации. Ко- нечно, такие методы требуют использования подходящих струк- тур данных, характеризующихся эффективными способами пред- ставления в памяти машины. Следовательно, иерархическое струк- турирование программных модулей и подмодулей должно не толь- ко уменьшать сложность схемы распределения управления между операторами программы, но также и способствовать требуемому структурированию информации. До сих пор мы обсуждали желательность решения той или иной задачи путем разделения ее на ряд модулей, которые могли бы быть рассмотрены независимо друг от друга. В такой структурной схеме эти модули соответствуют понятиям, возникающим в голове программиста, когда он пытается охватить задачу в целом. Коне- чно, большая независимость между модулями в общем приводит и к большей ясности и определенности этих понятий. При выборе решения некоторой задачи на вычислительной ма- шине мы сталкиваемся, по крайней мере, с четырьмя взаимосвя- занными подзадачами. 1. Глубоко понять взаимоотношения между элементами дан- ных, существенными для решения задачи. 2. Выбрать необходимые операции над логически связанными элементами данных. 3. Разработать методы представления элементов данных в па- мяти машины, позволяющие: а) наиболее полно сохранить логи- ческие отношения, существующие между элементами данных; б) легко и эффективно выполнять операции над элементами дан- ных. 4. Выбрать, какой именно язык решения (программирования) окажется наиболее подходящим для данной задачи и позволит поль- зователю выражать в «естественном» виде те операции, которые он пожелает выполнить над данными. Рассмотрим каждую из этих подзадач более подробно. Чтобы выявить логические взаимосвязи между элементами данных в задаче, необходимо уяснить смысл данных в целом. Ра- зумеется, для хорошего понимания данных следует прежде всего проявить особое внимание к их подготовке и сбору! Данные, относящиеся к определенной задаче, состоят из набора элемен- тарных единиц, или атомов. Атом обычно содержит простые эле- менты (целые числа, биты, символы) или набор этих элементов. Человек, решающий определенную задачу, стремится установить 18
©вязи между атомами данных. Выбор атомов данных является необходимым и ключевым шагом в точном определении задачи й последующем ее решении. Возможные способы, посредством которых элементы данных, или атомы, оказываются логически связанными друг с другом, характеризуют различные структуры данных. В результате выбора конкретной структуры данных некоторые элементы располагаются рядом друг с другом, в то время как другие множества элементов оказываются связанными более слабо. Отражением непосредственного соседства двух элементов является свойство смежности в отношении порядка, которое может характеризовать структуру. Следует отметить, что класс понятий, связанных со структура- ми данных, за последние годы приобрел большое значение. Вна- чале цифровые вычислительные машины использовались главным образом для решения числовых научных задач, но это положение .резко изменилось с возникновением целого ряда нечисловых задач. •С решением числовых задач ассоциировались довольно примитив- ные структуры данных, такие как переменные, векторы и массивы. Эти структуры в большинстве случаев были адекватны решаемым числовым задачам. Однако при решении нечисловых задач эти примитивные структуры данных оказались явно недостаточно мощными, чтобы на их основе можно было задавать более сложные структурные отношения между данными. Обратимся ко второй подзадаче, т. е. к выбору операций над используемыми структурами данных. К таким операциям можно отнести создание и уничтожение структуры данных, включение или исключение элементов из структуры данных, а также опера- ции доступа к элементам в структуре данных. Разумеется, эти операции функционально различны для разных структур данных (например, в гл. 3 мы увидим, что операция включения в очередь •отличается от соответствующей операции для стека). Операции, связанные c конкретной структурой данных, могут быть реали- зованы в виде совокупности подпрограмм на определенном языке, или же эти операции могут быть осуществлены с помощью основ- ных операторов используемого языка программирования. Какая именно ситуация будет иметь место, зависит от применяемых струк- тур данных и доступных языков программирования. В любом -случае способ манипулирования данными зависит от представ- ления структуры данных в памяти машины, а это является содер- жанием третьей подзадачи. Представление определенной структуры данных в памяти вы- числительной машины называется структурой хранения. Разли- чие между структурой данных и соответствующей структурой хранения часто не учитывается, что нередко приводит к снижению эффективности представления данных и затрудняет разработчику оптимально использовать свои средства и ресурсы. Существует много возможных конфигураций памяти, или структур хранения, •соответствующих некоторой структуре данных. Например, име- 19
ется ряд возможных структур хранения для такой структуры дан- ных, как массив. Возможно также, что две разные структуры дан- ных будут представлены одной и той же структурой хранения. Во многих случаях почти все внимание программиста направлено на структуры хранения определенных данных и мало внимания уделяется структуре данных самой по себе. При этом нередко возникает значительная путаница между свойствами, относящи- мися к интерпретации и смыслу данных, с одной стороны, и струк- турам хранения, которые могут быть выбраны для представления их в системах программирования, — с другой. При обсуждении структур хранения мы будем иметь дело с представлением структур данных и в основной и во внешней па- мяти машины. Представление структуры хранения во внешней памяти часто называют файловой структурой. Четвертая подзадача, поставленная нами ранее, связана с выбором языка программирования, который должен быть ис- пользован при решении задачи. Если в применяемом языке про- граммирования отсутствуют средства для представления не- которой структуры данных, то программа для соответствующего алгоритма может получиться довольно сложной. Например, при обработке данных платежных ведомостей может потребоваться древовидное представление информации о каждом работающем. Такая структура отсутствует в некоторых языках программиро- вания, например в ФОРТРАНе. Это, однако, не означает, что мы не можем запрограммировать обработку платежных ведомостей на ФОРТРАНе. Потребуется написать программы формирования и обработки деревьев, но такой путь сложен. В этой ситуации целесообразно применить более подходящий язык обработки дан- ных, такой как КОБОЛ. В идеальном случае язык программиро- вания, выбранный для реализации алгоритма, должен содержать все типы представлений, требуемые для структур данных реша- емой задачи. На практике выбор языка может диктоваться также и тем, какими языками располагает данный вычислительный центр, какой язык наиболее предпочтителен для основного круга про- граммистов и т. д. Из предыдущего обсуждения ясно, что структуры данных, соот- ветствующие им структуры хранения и операции над структурами данных тесно связаны с решением задач на ЭВМ. Эти три аспекта будут подробно изучены в данной книге. Четвертый аспект — выбор подходящего языка, на котором надо выразить програм- мное решение задачи, основательно прорабатываться не будет. Однако при всякой возможности мы будем обсуждать выбор про- граммно-языковых средств из множества языков, особенно если эти средства касаются выразимости структур данных и соответству- ющих операций над ними. В книге довольно подробно рассмотрен ряд применений струк- тур данных. Особенности решения задач во многих из этих при- менений будут обсуждаться на следующих трех уровнях. 20
1. Выбор подходящей математической модели (включая струк- туры данных). 2. Описание алгоритмов на основе выбора, сделанного на уро- вне 1. 3. Разработка структур хранения для структур данных, выб- ранных на уровне 1. В следующем параграфе приводится описание системы записи алгоритмов, которая будет применяться для представления ал- горитмов, рассматриваемых в книге. 0-2. СИСТЕМА ЗАПИСИ АЛГОРИТМОВ Представление алгоритмов, используемое в данной книге, лучше всего описать на примерах. Рассмотрим следующий алгоритм определения элемента вектора с наибольшим алгебраи- ческим значением. Алгоритм GREATEST. Этот алгоритм определяет наибольший по значению элемент вектора А, содержащего п элементов, и присваивает это значение величине МАХ. При этом символ i используется в качестве индекса элемента вектора А. 1. [Вектор не содержит элементов?] Если п <_ 1, то печатать сообщение и закончить выполнение алгоритма. 2. [Начало.] Установить МАХ <— А [ 1 ], i «— 2. (Вначале по- лагаем, что А [1 ] является элементом с наибольшим значением). 3. [Проверены все элементы вектора? ] Повторять шаги 4 и 5 до тех пор, пока i sg и. 4. [Замена значения МАХ, если оно меньше значения следую- щего элемента.] Если МАХ < A [i], то установить МАХ <— *- А [i]. 5. [Формирование очередного значения индекса. ] Устано- вить i «— i + 1. 6. [Конец. ] Выход. Алгоритму присвоено идентифицирующее имя (в данном слу- чае GREATEST). За этим именем следует краткое описание за- дачи, решаемой алгоритмом, и по ходу этого описания называются переменные, используемые в алгоритме. После этого приводится сам алгоритм в виде последовательности пронумерованных шагов. Каждый шаг начинается фразой, заключенной в квадратные скобки, которая кратко описывает данный шаг. Вслед за этой фразой записывается упорядоченная последовательность опера- торов, определяющая те действия, которые должны быть выпол- нены. Отметим, что в общем случае операторы каждого шага дол- жны выполняться по порядку слева направо. Шаг может закон- читься комментарием, заключенным в круглые скобки и предна- значенным помочь читателю лучше понять этот шаг. Комментарии не вызывают никаких действий и включаются в алгоритм только Для ясности. 21
Шаг 2 в приведенном примере алгоритма содержит символ стрелки «<—», который используется для обозначения оператора присваивания. В частности, оператор МАХ А [1] означает, что элемент вектора А [ 1J должен заменить собой значение пере- менной МАХ. В нашей алгоритмической нотации символ «=» при- меняется всегда в качестве знака отношения, а не знака присваи- вания. Оператору присваивания или группе операторов присваива- ния, разделенных запятыми, предшествует слово «установить». Операция приращения индекса i на единицу в шаге 5 обозна- чена в виде i i.+ 1. Одно и то же значение может быть при- своено многим переменным с помощью оператора множествен- ного присваивания. Так, операторы i О, j ч- 0 и к -«-О можно было бы заменить одним оператором i •*- j к 0. Взаимный обмен значений двух переменных (который, на- пример, осуществляется последовательностью операторов TEMP •*- AL, AL В, В TEMP) будет для удобства иногда записываться в виде AL <—> В. Следует обратить внимание на то, что индексы элементов массивов заключаются в квадратные •скобки. Таким образом, А [4] представляет собой четвертый эле- мент массива А. Выполнение любого алгоритма начинается с шага 1 и продол- жается последовательно до тех пор, пока эту последовательность не нарушает оператор условного илн безусловного перехода. В рас- сматриваемом примере сначала выполняется шаг 1. Если вектор не содержит элементов, то выполнение алгоритма на этом завер- шается. В противном случае выполняется шаг 2, в котором пере- менной МАХ присваивается начальное значение А [1 ], а ин- дексу i — значение 2. Шаг 3 приводит к завершению алгоритма, если проверен последний элемент вектора А. В противном случае выполняется шаг 4. В этом шаге значение переменной МАХ сра- внивается со' значением следующего элемента вектора. Если переменная МАХ меньше следующего элемента, то ей присваи- вается значение этого элемента. При невыполнении данного усло- вия значение переменной МАХ не меняется. За шагом 4 следует шаг 5, в котором устанавливается очередное значение индекса после чего управление передается шагу 3, проверяющему условие завершения. В данной книге элемент вектора обозначается либо через Аь либо через A [i ]. В текстах алгоритмов обращение к элементу массива будем представлять в виде A [i ], а в программах на языке ПЛ/1 — в виде А [I]. Переменные, используемые в алго- ритмах, обычно изображают прописными буквами (например, МАХ и А), а однобуквенные переменные, применяемые в качестве счетчиков в циклах, индексов, указателей позиций символов в строке и т. д. — строчными буквами. Двумя более сложными операторами, используемыми при описании алгоритмов, являются операторы «если» и «повторять». Опишем их более подробно. 22
Оператор «если» имеет одну из следующих форм: 1. Если -----, то -----,-----, ...,----- 2. Если , то, , ; в противном слу- чае ---------------------------------------, -, - После союза «то» записывается упорядоченная последователь- ность операторов, которые должны быть выполнены, если проверя- емое условие истинно. В случае, когда после оператора «если» отсутствует оператор «идти к», то по завершении оператора «если» управление передается следующему за ним оператору. Область действия фразы «то» ограничивается точкой для формы 1 и точкой с запятой для формы 2. Если проверяемое условие ложно, то для формы 1 происходит переход к выполнению следующего опе- ратора, а для формы 2 выполняется упорядоченная последователь- ность операторов, записанных после слов «в противном случае». В последней ситуации после завершения фразы «в противном слу- чае» управление передается следующему оператору (если только- после слов «в противном случае» нет оператора «перейти к»). Операторы «если» могут быть включены в состав других операто- ров «если», но для облегчения понимания алгоритмов мы будем стараться избегать чрезмерной глубины включения операторов «если» друг в друга. Для управления процедурами итерации используется оператор «повторять». Он имеет одну из следующих форм: 1. Повторять при индекс = последовательность: -----, -----, 2. Повторять шаги и и и 4- 1 при индекс = последовательность. 3. Повторять шаги от и до р при индекс = последовательность. 4. Повторять шаги от п до р до тех пор, пока истинно логи- ческое выражение. Форма 1 используется для повторения группы операторов, не- посредственно следующих за двоеточением в данном шаге. Эта группа заканчивается точкой. Форма 2 является частным случаем формы 3. форма 3 применяется для повторения по порядку всех шагов в указанном диапазоне их номеров. Термин «индекс» озна- чает некоторую переменную алгоритма, используемую в качестве счетчика циклов, а «последовательность» — определенное пред- ставление ряда значений, которые последовательно принимает этот индекс. Начальное и конечное значения, а также приращение индекса должны быть заданы тем или другим способом. Например, «повторять при i = 1, 2, ..., 25:», «повторять при ТОР = = и + к, и + к — 1, .... 0:».« повторять шаги 5 и 6- при к = 9, 11, ..., 2* МАХ Н~ 1.» представляют различные ва- рианты правильных операторов «повторять». Форма 4 используется для повторения по порядку шагов с но- мерами от п до р до тех пор, пока логическое выражение не ста- нет ложным. Вычисление и проверка логического выражения выполняются в начале каждого цикла, и семантически этот опе- ратор аналогичен фразе WHILE в языке ПЛ/1. После выполнения 2$
всех операторов в области действия оператора «повторять» ин- декс принимает следующее значение в последовательности зна- чений, и все операторы в области действия оператора «повторять» выполняются снова. Предполагается, что проверка значения ин- декса на завершение происходит до выполнения какого-либо оператора, так что оператор «повторять» может порождать цикл, не выполняющийся ни разу. Например, операторы «повторять при i = —1, —2, ..., 10» и «повторять при к = 5, 6, ..., —17» не приведут ни к каким действиям, а будут трактоваться как опе- раторы, выполнение которых уже завершено. Как только выпол- нение оператора «повторять» закончится, управление переда- ется первому оператору, не входящему в область действия опера- тора «повторять». Если, однако, в области действия оператора «повторять» встретится оператор «перейти к» и управление будет передано из этой области, то считается, что выполнение оператора «повторять» завершилось. При этом управление передается шагу, указанному в операторе «перейти к». Рассмотрим следующий фрагмент алгоритма: 6. [Обнуление счетчика. ] Установить COUNT ч- 0. 7.....[Выполнение цикла.] Повторять шаги 8 и 9 при i = 1, 2..... и. 8. [Ввод числа.] Читать А [1 ]. 9. [Приращение счетчика, если число отрицательное. ] Если А [1 ] С 0, то установить COUNT ч- COUNT + 1. 10. [Вывод результата. ] Напечатать значение COUNT. Если п имеет значение 5 и первые числа в потоке вводимых данных равны 7, 4, —3, —2, 6, —17, 8, ..., то в шаге 10 на пе- чать будет выведено число 2, так как шаги 8 и 9 выполняются пять раз. В шаге 9 оператор «если» не передает управление шагу 10 после своего выполнения. Вместо этого управление возвращается на оператор «повторять», в котором происходит прира- щение индекса, после чего шаги 8 и 9 выполняются снова. (Можно представить, что шаг 9 завершается оператором ти- па CONTINUE). Поскольку многие примеры, которые нам придется рассматри- вать, связаны с обработкой нечисловых данных (а именно с ма- нипулированием символами, а не с «перемалыванием» чисел), в систему записи алгоритмов оказалось желательным включить определенные средства, которые упрощали бы обработку нечисло- вой информации. Эти средства дополняют стандартные математи- ческие функции и операции, считающиеся полезными в болыпин- :24
стве применений. Все программы в книге написаны на языке ПЛ/1, и поэтому средства нечисловой обработки заимствованы из совокупности определенных операций и функций языка ПЛ/1. Например, в гл. 2 мы вводим в рассмотрение функции манипули- рования строками LENGTH, INDEX и SUB, которые аналогичны соответствующим функциям LENGTH, INDEX и SUB STR в языке ПЛ/1. В последующих главах книги будут введены дополнительные элементы в системе записи алгоритмов для представления новых операций над структурами данных. Эти элементы будут объясняться и иллюстрироваться по мере необходи- мости.
Глава 1. ИНФОРМАЦИЯ И ЕЕ ПРЕДСТАВЛЕНИЕ В ПАМЯТИ Начиная изучение структур данных, нлн информационных струк- тур, необходимо ясно установить, что мы понимаем под информацией, как инфор- мация передается и как она физически размещается в памяти вычислительной машины. Разумеется, мы не собираемся заходить слишком далеко в вопросы, связанные с природой и передачей информации, поскольку это привело бы нас к необходимости глубоко изучать такие сложные научные дисциплины, как тео- рия информации н передача данных. Тем не менее мы хотим обсудить понятие информации, ее передачу н хранение с несколько упрощенной точки зрения для того, чтобы изучающий информационные структуры мог понять, что такое ин- формация н какова ее физическая природа. В конце главы мы свяжем наше обсуждение концепций, лежащих в основе понятия ннформацнн, с описанием примитивных структур данных, обычно ис- пользуемых прн решении разного рода задач на вычислительных машинах. Мы введем следующие примитивные структуры данных: целые н вещественные числа, логические данные, символьные данные и данные типа указателя. При обсуж- дении примитивных структур данных особое внимание будет обращено на нх представление в памяти. 1-1. ПРИРОДА ИНФОРМАЦИИ Не будет большой ошибкой сказать, что решение каж- дой задачи с помощью вычислительной машины включает запись в память, извлечение и манипулирование информацией. Напри- мер, информация записывается в память, когда модифицируется файл записей студентов по курсам. Информация извлекается, когда компилятор запрашивает для трансляции очередную строку исходного текста из файла с исходной программой. Когда же скла- дываются два числа в арифметическом устройстве, то здесь имеет место манипулирование информацией. Следовательно, записи студентов, инструкции программы, числа являются информацией. Здесь мы определили информацию как записываемый или переда- ваемый материал, имеющий некоторый смысл, связанный с ее символическим представлением. Можно ли измерить информацию? Например, как измерить информацию, содержащуюся в признаке, определяющем принад- лежность студента к курсу? Содержит ли программа А больше информации, чем программа В? В силу того, что решение таких вопросов лежит в основе вычислительной науки, равно как и ряда других наук (например, психологии, управления производством, 26
передачи данных), возникла специальная область — теория ин- формации.. Ниже вводятся основные понятия теории информации, с тем чтобы обеспечить понимание природы информации. В попытке найти критерий для измерения информации Хартли [3], Колмогоров [5], Винер [13] и Шеннон [91 предложили различные формальные определения для количества информации. В теоретико-информационном смысле информация рассматривается как мера разрешения неопределенности. Предположим, что име- ется п возможных конфигураций какой-нибудь системы, в которой каждая конфигурация имеет вероятность появления р(, причем все вероятности независимы. Тогда неопределенность этой системы определяется в виде #= - lAlogM). (1-1-1) 1=1 Чтобы проиллюстрировать определение (1-1.1), предположим, что мы имеем восемь игральных карт, пронумерованных в возра- стающем порядке от 1 до 8. Предположим далее, что карты поло- жены на стол номерами вниз. Выберем теперь наугад одну карту из восьми. Тогда вопросу: «Какой номер имеет выбранная карта?», соответствует мера неопределенности, связанная с угадыванием номера карты. Каждая карта с вероятностью 1/8 может иметь любой номер от 1 до 8. Следовательно, применяя соотношение (1-1.1), получим, что средняя неопределенность, связанная с иден- тификацией выбранной карты, определяется следующим образом: 8 # = — 2 log2 (-4") = — log2 (“4") = log2 (8) = 3 бит. г=1 Заметим, что для измерения неопределенности системы нами выбрана специальная единица, называемая битом. Бит является мерой неопределенности (или информации), связанной с нали- чием всего двух возможных состояний, таких, как, например, истинно—ложно или да—нет. Бит используется для измерения как неопределенности, так и информации. Это вполне объяснимо,, поскольку количество полученной информации равно количеству неопределенности, устраненному в результате получения инфор- мации, такой, например, как номер карты. Если задается воп- рос: «Билл имеет коричневую собаку?», то неопределенность, связанная с цветом собаки Билла, может быть устранена пра- вильным ответом «да» или «нет». Так как речь идет только о двух возможных ответах, то устраняемая ответом «да» или «нет» не- определенность равна одному биту. Мы вычислили, что для устра- нения неопределенности в номере карты (в случае выбора прону- мерованной карты) необходимо три бита. Из определения бита следует, что карту можно было бы определить, задав три вопроса типа «да—нет». Такое дерево вопросов изображено на рис. 1-1.1. 27
Номер Номер Номер Номер Номер Номер Номер ' ^дмер б 7 ; в Рис. 1-1.1. Дерево вопросов для определения пронумерованной карты Из него видно, как можно выяснить номер карты, предполага что на вопросы даются правильные ответы «да—нет». Три бита информации, связанные с определением номера карт] могут быть представлены сообщением длиной в три символ каждый из которых принимает значения 0 или 1, где 1 обознача< ответ «да», а 0 — ответ «нет». В табл. 1-1.1 схема сообщений представляет все возможные пути в дереве вопросов, где i-й 61 сообщения (i — 1, 2, или 3) представляет собой ответ на I- вопрос. Схема сообщений В является иллюстрацией того факт, что схема А никоим образом не должна рассматриваться как еди1 •ственно возможная, поскольку несложно построить иные тре: битовые схемы для однозначной идентификации каждой из восьм карт. Теперь возьмем набор из девяти карт, пронумерованных с 1 до 9. В этом случае потребуется двоичное сообщение длине уже в четыре бита. Как было установлено [8], при любом спосоС кодирования множества из п элементов, имеющих равную вер< ятность выбора, хотя бы один код должен иметь длину, котора больше или равна мере информации системы, т. е. Я= — YiPi Xo&Pi- <=i При изучении информационных структур очень важными явля тотся сообщения, состоящие из нулей и единиц. При обсуждени в следующем параграфе вопросов передачи информации мы уви дим, что сообщения в вычислительной машине представляютс в точно такой же форме (т. е. в виде строк из нулей и единиц] Мера среднего содержания информации Н, налагающая ограни чение на длину сообщения, может быть полезна в определени структуры хранения данных в памяти. Например, в гл. 7 числен ная оценка количества информации будет использоваться дл 28
Таблица 1-1.1 Схемы сообщений, представляющие восемь пронумерованных карт Номер карты Схема сообщений А Схема сообщений В Номер карты Схема сообщений А Схема сообщений В — । — 1 111 001 5 он 101 2 по 010 6 010 но 3 101 он 7 001 111 4 '' 100 100 8 000 000 определения эффективности различных структур данных, приме- няемых при организации записей в файлах. Было бы заблуждением считать, что можно измерить любую информацию. Например, можно спросить, будет ли информация, Содержащаяся на этой странице, одинаковой для студента и шести- 4етнего ребенка. Очевидно, что нет. Основным условием переда- ваемой информации является правильное ее понимание. Если получаемые сообщения понимаются не полностью, то в теории Связи принято говорить, что такие сообщения содержат шум. Совсем не просто определить, что в сообщении является шумом, а что — значащей информацией. Именно поэтому трудно при- менять теорию информации, в повседневной жизни — будь то оценка количества информации, содержащейся в лекции, или измерение информационных потоков в организации. Упражнения к п. 1-1 1. Рассмотрим вероятности выпадения суммы чисел при бросании ФУ* игральных костей. Вероятности выпадения всех возможных сумм приве- дены в табл. 1-1.2. Вычислите среднюю неопределенность, связанную с собы- тием, заключающимся в бросании двух костей. 2. Придумайте двоичный код фиксированной длины для представления каж- дого из возможных значений выпавшей суммы в упр. 1. Под фиксированной Длиной понимается одинаковая длина всех кодов. Сравните длину этого кода СО значением Н, вычисленным в упр. 1. Должна ли длина кода быть меньше или больше Н? 3. Составьте код переменной длины для представления выпавших сумм чисел в упр. 1. Постарайтесь придумать код, дающий в среднем лучшие резуль- таты, чем полученные в упр. 2, т. е. добейтесь сокращения средней длины кода ДЛЯ представления выпавших сумм при большом количестве бросаний двух костей. 1-2. ПЕРЕДАЧА ИНФОРМАЦИИ Шеннон [9] описывает процесс передачи информации как пятиэлементную систему, содержащую следующие компонен- ты: источник, или генератор, информации; передатчик, или коди- ровщик, сообщения, которое должно быть передано; канал, т. е. среда, через которую передается сообщение; приемник, или декодировщик, полученного сообщения; место назначения, или получатель информации. Соответствующие понятия иллюстри- 29
T a .6 л и ц a 1-1.2 Вероятность выпадения различных сумм при бросании двух игральных костей Сумма выпавших чисел 2 3 4 5 6 7 8 9 10 11 12 Вероят- ность 1/36 1/18 1/12 ,/9| 5/36 1/6 5/36 1/9 1/12 1/18 1/36 руются на рис. 1-2.1. Примером коммуникационной системы является человек (или человеческий мозг), рассматриваемый как источник; его рот, язык и другие органы речи — передатчик; воздух — канал; слуховая система другого человека — прием- ник; мозг человека, получающего сообщение, — место назна- чения. В простой вычислительной системе с пакетной обработкой ко- лоду перфокарт можно рассматривать как источник, считыватель с перфокарт — как передатчик, линию передачи от считывателя к самому компьютеру — как канал, устройство управления ка- налом — как приемник, и основную память компьютера — как место назначения. Проведенное обсуждение иллюстрирует физические компо- ненты процесса передачи информации. Существует три других аспекта, которые следует принимать во внимание при рассмо- трении передачи информации от источника к месту назначения: синтаксический, семантический и прагматический аспекты [7]. Синтаксис относится к физической форме передачи информации. Например, синтаксис информации, получаемой при прочтении данного предложения, может характеризоваться формой пятен типографской краски, из которого оно состоит. Пятна краски распознаются как буквы русского алфавита, которые соединяются в слова, в свою очередь объединяющиеся в предложения. С по- мощью предложения выражаются понятие или понятия, и их смысл связан с синтактической формой информации. Семантика относится к смыслу, который передается с помощью синтаксиче- ского представления. Прагматическая сторона передачи информа- Сообщение, преобразованное во времени, пространстве ипи интенсивности Рис. 1-2.1. Общая схема системы связи 30
иуи заключается в действиях, которые выполняются как резуль- тат интерпретации (т. е. приписывания смысла) информации, предпринимаемые в результате интерпретации действия часто [ большой степени зависят от поставленных ранее условий. Нйпрнмер, если мы не согласны с тем, что написано в конкретной ^иге (основываясь на предыдущем опыте), мы можем предпо- ^гть закрыть книгу — возможно, навсегда. Для успешной передачи данных следует рассматривать все аспекта. Если бы мы изменили синтаксическую структуру русского языка, например, включив пробелы в слова, это затруд- шдо бы связь. Аналогично связь усложнится или даже станет р^пзможной, если со словами нельзя ассоциировать никакой Лй^антики. Например, что означает предложение «Данные скромно ЙЙ&ользают»? Синтаксически это правильное русское предложение, К^бно не имеет смысла. Естественно, прагматическая сторона дое. предпринимаемые действия) зависит от семантического ас- ЭДгга, так же как семантический аспект зависит от синтаксиче- риого. В случае предложения «Данные скромно ускользают», ©Йиантика не понятна. Следовательно, нельзя ожидать, что по- дуют какие-то действия, за исключением, разве, повторного ния слов. Следующий пример взаимоотношений между указанными аспек- передачи информации можно привести из области програм- мирования. Для успешного выполнения написанной на языке ЙД/1 программы исходные предложения должны быть синтак- СЙчески правильными. Если пропущена запятая или точка с за- "й|ггой, или если они появляются не там, где требуется, то пред- ложение синтаксически ошибочно, и ему может быть дана невер- йая интерпретация. (И здесь семантика зависит от синтаксической формы информации). Даже если исходное предложение синтак- сически правильно, семантические интерпретации, которые с ним Связывают программист и транслятор, могут различаться. На- пример, мы можем написать предложение X = А + В * С; Для того, чтобы переменной X присвоить значение выражения (А + В)*С. Но компилятор интерпретирует присваиваемое вы- ражение как А + (В*С). Прагматическим результатом, или [Действием компилятора, является, конечно, генерация машинных |Кодов для предложения X = А + (В*С). ; Z Таким образом, чтобы передача информации была успешной, .Необходимо иметь согласованные между отправителем и прием- ником правила, относящиеся к синтаксическому и семантическому аспектам информации, плюс набор соответствующих действий, ^предпринимаемых приемником в ответ на извлеченный смысл. ! . При обсуждении трех аспектов информации, необходимых для €е передачи, мы считали, что процесс передачи должен соответ- i ствовать общему классу коммуникационных систем, как было 31
Рис. 1-2.2. Передача непрерывной ин- формации Время. Рис. 1-2.3. Передача дискретной ннфо мац и и с двумя значениями предложено Шенноном [9] (рис. 1-2.1). Интересно, однако, oi метить, что существуют два физических метода передачи инфор мации: непрерывный и дискретный. Информация, передающаяся в непрерывной форме, предстаЕ ляется в виде сигналов, выбранных из спектра амплитудны значений, как изображено на рис. 1-2.2. Классическим примеро непрерывно передающейся информации является связь с помощи слов, основанная на использовании частоты и амплитуды звуке вых волн. Информация, передаваемая в дискретной форме, представляете в виде сигналов, которые могут принимать лишь конечное числ' состояний в континууме значений амплитуды. Например, при сутствие или отсутствие импульса может представлять в ди скретной форме такую информацию, как условия «истина—ложь или «включено—выключено» (рис. 1-2.3). Обратите внимание н; существование точки отсечки, по которой определяется наличш или отсутствие импульса. Письменный текст дает нам классиче ский пример коммуникации, основанной на передаче дискретно! информации. Напишем ли мы «ДАННЫЕ» или «данные», мы полу чим ту же самую информацию. Хотя у каждого человека свое почерк, при интерпретации написанного отрывка, например русского текста, мы всегда пытаемся определить принадлежности каждого символа к одной из 33 букв русского алфавита. 33 буквь представляют, разумеется, конечное число состояний, характери зующих передачу дискретной информации. Аналоговые вычислительные машины сконструированы для хранения, поиска и обработки информации, являющейся непре- рывной по своей природе. Эти типы машин особенно удобны для решения линейных интегральных и дифференциальных урав нений, что может потребоваться, например, при управлении про- цессом фракционирования, при котором регулируется состав и количество газа в фракционной колонне. На основе информа- ции о составе необработанных материалов, поступающих в ко- лонну, и текущих измерениях температуры и давления, на осно- вании заданного закона управления вычисляется требуемый за кон регулирования температуры. Управляющий закон «запро- граммирован» (т. е. «зашит») в схемы интегрирования и дифферен- 32
Iвания аналоговой машины. Все измерения, регулировки и сления имеют форму непрерывных сигналов. аналоговых машин не хватает скорости, вычислительных воз- юстей и, самое главное, точности для выполнения необходимой 5отки во многих широко распространенных приложениях , как, например, обработка платежных ведомостей. Факти- 1 даже в приложениях к управлению процессами, подобными ессу в фракционной колонне, аналоговые машины не вполне петворительны и заменяются цифровыми вычислительными (нами, более дорогими, но обладающими большими вычисли- вши возможностями, или комбинацией цифровой и аналого- лашин, которая известна как гибридная вычислительная ма- i [10]. цифровой вычислительной машине вся информация переда- в дискретной форме в виде сигналов, представляющих одно вух состояний. Двоичным сигналам может соответствовать чие или отсутствие электрического тока, а также ток поло- льного или отрицательного направления. Проводилась ис- кательская работа с целью построения недвоичных компь- ов (особенно троичных); однако такие машины все еще на- гся на стадии испытаний, так как на сегодняшний день они що менее надежны, чем двоичные. алее в этой книге мы будем заниматься вопросами хранения, роиска и обработки дискретной информации (более точно, инфор- мации, представленной в двоично-кодированной форме) с помощью [цифровых вычислительных машин. В цифровых ЭВМ присутствует два вида дискретной информации, а именно: машинные команды (или операции) и данные, которыми манипулируют эти команды. При изучении структур данных мы будем рассматривать главным рбразом организацию данных для решения задач, имеющих кон- кретные приложения. Однако со структурами данных связано арпределенное множество операций, необходимых для требуемого доступа к информации. Поэтому, обсуждая вопрос о том, как Едолжна быть структурирована информация, мы также будем изу- чать и операции доступа к ней. Рассматриваемые операции будем представлять на языке высокого уровня, таком как ПЛ/1; однако [эти обобщенные операции могут быть переведены в эквивалентный {Набор операций машинного уровня. 1-3. ХРАНЕНИЕ ИНФОРМАЦИИ В цифровых вычислительных машинах существует два вида запоминающих устройств; операционные устройства .и собственно память. Обычно операционные устройства называют -регистрами. Регистры используются для временного хранения -и преобразования информации. Некоторые из наиболее важных регистров содержатся в цен- тральном процессоре компьютера. Центральный процессор содер- 2 Трамбле Ж.. Соренсон П. 33
жит регистры (иногда называемые аккумуляторами), в которые помещаются аргументы (т. е. операнды) арифметических операций. Сложение, вычитание, умножение и деление занесенной в акку- муляторы информации выполняется с помощью очень сложных логических схем. Кроме того, с целью проверки необходимости изменения нормальной последовательности передач управления в аккумуляторах могут анализироваться отдельные биты (напри- мер, для того чтобы выяснить, не требуется ли переход на ветвь программы, выполняющуюся при отрицательном значении числа, проверяется конкретный бит аккумулятора, называемый знаковым битом). Кроме запоминания операндов и результатов арифмети- ческих операций, регистры используются также для временного хранения команд программы и управляющей информации о номере следующей выполняемой команды. Регистры, будучи предназначенными для выполнения весьма специфических функций, создаются на основе сложных комбина- ционных логических схем. Это приводит к их высокой стоимости по сравнению с ячейками основной памяти. Вот почему регистры используют только для временного хранения информации. Собственно память предназначена для запоминания более по- стоянной по своей природе информации. Например, определенная ячейка памяти или множество ячеек могут быть связаны с кон- кретной переменной в программе. Однако для выполнения ариф- метических вычислений, в которых участвует переменная, необ- ходимо, чтобы до начала вычислений значение переменной было перенесено из ячейки памяти в регистр. Этот перенос необходим в связи с тем, что у ячеек памяти нет логических схем, необ- ходимых для выполнения арифметических операций. Если ре- зультат вычисления должен быть присвоен переменной, то ре- зультирующая величина снова должна быть перенесена из соот- ветствующего регистра в связанную с переменной ячейку памяти. Во время выполнения программы ее команды и данные в основ- ном размещаются в ячейках памяти. Полное множество элементов памяти, содержащихся в главной стойке или главной части ком- пьютера, часто называют основной памятью. Ниже, в гл. 7, мы узнаем, что в некоторых случаях программы могут также раз- мещаться на устройствах памяти, не принадлежащих к основной памяти. Примерами таких запоминающих устройств (которые часто называют устройствами внешней памяти) являются маг- нитные диски и барабаны. Обратимся теперь к описанию методов представления инфор- мации в основной памяти, так как именно в этой памяти хранятся программы и данные выполняемой программы. В настоящее время существует два главных вида основной памяти: память на магнит- ных сердечниках и полупроводниковая память. Именно на них мы сосредоточим свое внимание, хотя в настоящее время на основе разработанных технологий существует возможность создания и других типов памяти. Последующее обсуждение не является ис- 34
сердечника после 'ЭДк. 1-3.1. Чтение бита (xlf у,) из магнитного сердечника: м — состояние сердечника до чтения с разрушением: б — состояние Ктення с разрушением й^рпывающим описанием всех видов полупроводниковой памяти ш памяти на сердечниках, тем не менее оно передает их харак- терные черты. ' Запоминающее устройство на магнитных сердечниках исполь- зует магнитные сердечники для хранения двоичной информа- ции. Магнитный сердечник представляет собой тороид из магнит- ного материала, имеющий форму кольца. В каждом сердечнике Запоминается один бит информации, а состояние сердечника (т. е. $от факт, представляет ли он в настоящее время нуль или единицу) .определяется направлением магнитного потока через сердечник. £ Сердечники располагаются на платах внутри компьютера, при- мем каждый из них имеет три обмотки (рис. 1-3.1). Конкретный сердечник адресуется проходящими через него шинами х и у. 'Для чтения информации с сердечника по шинам х и у подаются 'Импульсы в виде отрицательных токов. Совместное воздействие Этих импульсов может привести к тому, что магнитный поток в ^Сердечнике изменит направление. Если перед считыванием магнит- ный поток в сердечнике имел положительное направление (еди- ничное направление), то поданные по шинам х и у отрицательные Токи вызовут изменение направления потока на отрицательное, -Или нулевое. Это изменение потока индуцирует разность потен- циалов (или напряжение) в третьей шине — шине считывания. Если до того, как по шинам х и у подаются импульсы, магнитный Поток в сердечнике имел отрицательное, или нулевое, направление, Изменения потока не произойдет, и в шине считывания ток не воз- никнет. Таким образом, отсутствие тока в шине считывания ука- зывает на то, что хранимая в сердечнике величина равна нулю, а присутствие тока — что эта величина равна единице. 2* 35
Рис. 1-3.2. Элемент полупроводниковой памяти Описанный процесс по- зволяет считывать инфор- мацию с сердечника. От- метим, что процесс чтения разрушает существующее состояние ячейки, так как все сердечники, с которых осуществляется считыва- ние, устанавливаются в нуль. Таким образом, для восстановления первона- чального состояния ячеек, подвергнутых операции чтения, требуется фаза перезаписи. Перезапись осуществляется сразу же после фазы чтения. Комбинация двух отрицательных или двух положительных импульсов, поданных по шинам х и у любого сердечника, может изменить направление намагничивания сердечника. Следовательно, процесс записи (а также фаза перезаписи) может быть выполнен с помощью однократной подачи им- пульсов. Для элементов памяти требуются только эти два процесса, а именно, чтение и запись. Выше было указано, что прочитанная из сердечника информация передается для обработки в регистр, а находящаяся в регистрах информация в том случае, если тре- буется более длительное ее хранение, предварительно записы- вается в память. Вторым широко используемым типом запоминающего устрой- ства является полупроводниковая память. Полупроводниковый элемент памяти (рис. 1-3.2) состоит из специальных последова- тельностной и комбинационной схем и располагается в неболь- шом по размеру^блоке (обычно называемом чипом) вместе со многими другими аналогичными элементами. Как и в случае па- мяти на сердечниках, полупроводниковый элемент содержит один бит информации. Информация вводится в элемент подачей отрицательного или положительного напряжения. Если нулевое значение представляется отрицательным напряжением, то еди- ничное значение — положительным, или наоборот. Значение ин- формации в элементе запоминается в последовательностной части его схемы, которая называется триггером. Триггер является простым устройством с двумя состояниями и, следовательно, удобен для хранения двоичной информации. Единичное значение запоминается в триггере при одновременной подаче положитель- ных импульсов по шинам входа и записи. Это приводит в активное состояние вход, устанавливающий триггер в единичное состоя- ние. С другой стороны, если отрицательный входной импульс совмещается с импульсом записи, наличие инвертора приводит к появлению импульса на входе сброса триггера, т. е. переводит его в нулевое состояние. 36
Шина чтения комбинируется с выходной шиной триггера. При подаче импульса по шине чтения может быть определено состоя- ние триггера и, следовательно, считано содержащееся в элементе двоичное значение. Память на магнитных сердечниках используется в качестве вапоминающей среды с 50-х годов. Позднее становится более рас- пространенной полупроводниковая память, главным образом благодаря резкому снижению стоимости интегральных схем. Другими преимуществами полупроводниковой памяти являются ►ее компактность, более низкие энергетические затраты (для чте- ния и записи информации требуется меньше электрической энергии й уменьшается нагрев элементов памяти) и более корот- кое время ответа при чтении или записи данных. Без сомнения, будущем для хранения информации будут использоваться и дру- гие более компактные и функционально более совершенные эле- менты памяти. 1-4. ПРИМИТИВНЫЕ СТРУКТУРЫ ДАННЫХ В этом параграфе мы будем рассматривать структури- рование данных в ЭВМ на наиболее элементарном уровне, т. е. Егруктуры данных, с которыми могут непосредственно работать ашинные команды. Мы обсудим представление этих структур 'данных для различных вычислительных машин. Изучаемые в этом Параграфе примитивные структуры данных служат основой для ^обсуждения и построения более сложных структур, которые будут ^определены в других главах книги. Г" Прежде чем приступать к обсуждению примитивных или дру- гих структур данных, мы должны познакомиться с некоторыми ^общими операциями, используемыми для манипулирования струк- турами данных и их элементами. I 1-4.1. Операции над структурами данных Одной из часто используемых операций над структурами Данных является их создание. Мы будем называть ее операцией /CREATOR (СОЗДАТЬ). Например в языках ПЛ/1, ФОРТРАН, АЛГОЛ и многих других переменные могут быть созданы с по- мощью оператора описания (объявления). Использование в ПЛ/1 оператора / DECLARE N FIXED DECIMAL; приводит к выделению адресного пространства для переменной N Во время исполнения программы при входе в блок (т. е. в блоки JSEGIN или PROCEDURE), в котором находится описание пере- менной N. В ФОРТРАНе в результате описания типа INTEGER Z 37
будет выделена память для переменной Z во время компиляции оператора описания. Память для переменной может также вы- деляться во время исполнения программы при первом появлении ее имени в исходной программе. Такие языки программирования, как СНОБОЛ и АПЛ, для создания структур используют именно такую форму. В дальнейшем мы встретимся и с другими спосо- бами создания структур данных. Главное заключается в том, что независимо от используемого языка программирования име- ющиеся в программе структуры данных не появляются «из ни- чего», а явно или неявно объявляются операторами создания структур. Операция уничтожения структур данных является допол- нительной к операции CREATOR. Она называется DESTROYER (УНИЧТОЖИТЬ). Некоторые языки, такие как ФОРТРАН, не дают возможности программисту уничтожать созданные структуры данных, так как они создаются во время компиляции программы, а не во время исполнения. В языках АЛГОЛ и ПЛ/1 структуры данных, имеющиеся внутри блока, уничтожаются в процессе выполнения программы при выходе из этого блока. Хотя операция DESTROYER и не является не- обходимой, она помогает эффективно использовать память. Возможно, что наиболее часто со структурами данных исполь- зуется операция, применяемая программистами для доступа к данным внутри самой структуры. Этот типа операций известен как SELECTOR (ВЫБРАТЬ). Форма операции SELECTOR в большой степени зависит от типа структуры данных, к которой осуществляется обращение. По мере того как будут вводиться более сложные структуры, мы увидим, что метод доступа явля- ется одним из наиболее важных свойств структур, особенно в связи с тем, что это свойство имеет непосредственное отношение к выбору конкретной структуры данных. Еще одной операцией, связанной со структурами данных, явля- ется операция изменения данных в структуре. Эта операция назы- вается UPDATER (ОБНОВИТЬ). Операция присваивания яв- ляется хорошим примером операции обновления. Для нее суще- ствуют и другие, более сложные формы, например передача пара- метров. Мы увидим, что обновление — важное свойство, которое должно быть принято во внимание при выборе структуры данных. Рассмотренные четыре типа операций обеспечивают нас всеми необходимыми средствами, обычно требующимися для работы с примитивными структурами данных, обсуждаемыми в этой главе. В процедурно-ориентированных языках типа ПЛ/1 и ФОРТРАН примитивные структуры данных совпадают с такими типами простых переменных, как действительные, целые, логи- ческие, и т. д. Простой переменной присваивается единственное значение, например, типа действительной величины. Сослаться на значение, связанное с простой переменной, можно с помощью ее идентификатора, например X. 38
1-4.2. Системы счисления Мы начнем обсуждение примитивных структур данных 'с анализа целых и действительных чисел. Чтобы обеспечить соот- ветствующую основу для изучения структур данных, которые Являются числовыми по своей природе, следует кратко обсудить Существующие типы систем счисления. Числа используются для символического представления коли- ества объектов. Очень простым методом представления коли- чества является использование одинаковых значков. В такой си- E|sмe между значками и пересчитываемыми объектами устанавли- ,ется взаимно однозначное соответствие. Например, шесть >ъектов могут быть представлены как ****** или НИИ. сно, что такая система становится очень неудобной, ли попытаться с ее помощью представить большие коли- ства. Системы счисления, подобные римской, обеспечивают частич- ке решение проблемы представления большого количества объ- :тов. В римской системе дополнительные символы служат для ►едставления групп значков. Например, если принять что = *, то V=IIHI, X=VV, L=XXXXX и т. д. Заданная вели- [на представляется с помощью комбинирования символов в со- ветствии с рядом правил, которые в некоторой степени зависят положения символа в числе. Недостатком системы, которая самого начала основывается на группировании некоторого мно- тва символов с целью формирования нового символа, явля- I то обстоятельство, что для представления очень больших ичеств (например, числа песчинок на пляже) требуется очень го (т. е. потенциально бесконечное число) уникальных сим- ов. Чтобы исключить задачу создания и запоминания большого ла символов, были созданы позиционные системы счисления, озиционной системе счисления используется конечное число R кальных символов. Величину R часто называют основанием пемы счисления. В позиционной системе количество пред- зляется как самими символами, так и их положением в записи Щисла. Для примера мы можем проанализировать наиболее зна- жомую нам систему счисления с основанием десять, или десятич- ную систему. Для иллюстрации того, что десятичная система является позиционной, рассмотрим число 1303. Мы интерпрети- руем 1303 в виде 1 X 103 + 3 X 102 + 0 X 101 + 3 X 10°. Отметим, что хотя в числе имеется две тройки, левая представляет Количество в три сотни, а другая тройка — количество три. Очевидно, что представляемое символом количество зависит от Положения символа в записи числа. 39
В позиционной системе могут быть представлены и дробные числа. Например, одна четвертая записывается в виде 0.25, что интерпретируется как 2 х КГ1 + 5 х 10'2. Значение десятичного числа определяется положением сим- волов в числе. Те же самые правила можно применять и к другим позиционным системам счисления, таким как двоичная система счисления. Двоичное число 11001.101 представляет то же самое количество, что и десятичное число 26.625. Мы можем убедиться в этом, разложив двоичное число в соответствии с его позицион- ным представлением: 1 X 24 + 1 X 23 + 0 X 2а -Т 0 X 21 + 1 X 2° + + 1 X 2"1 + 0 X 2~2 + 1 X 2“3 =- 16 + 8 + 1 + + 0.5 + 0.125 = 26.625. Наиболее часто мы будем встречаться с системами‘счисления, имеющими основание 2, 8, 10 и 16, которые обычно называют двоичной, восьмеричной, десятичной и шестнадцатеричной си- стемами соответственно. Интерес к этим системам счисления обу- словлен тем, что многие компьютеры выполняют свои действия над числами с основаниями 2, 8 и 16. Мы обратим особое внима- ние на то, как выполнить преобразования из десятичной си- стемы счисления в любую из трех других систем, обычно исполь- зуемых в компьютерах. Табл. 1-4.1 знакомит нас с двоичным, восьмеричным и шестнадцатеричным представлением целых чи- сел в интервале от 0 до 17. В последующем обсуждении мы будем различать числа, пред- ставленные в различных системах счисления, добавляя к ним соот- ветствующий индекс (кроме тех случаев, когда основание системы в представлении числа очевидно из контекста, как в табл. 1-4.1). Таблица 1-4.1 Двоичные, восьмеричные и шестнадцатеричные числа от 0 до 17 Число Двоич - ное Восьме- ричное Шестна- дцате- ричное Число Двоич- ное Восьме- ричное Шестна- дцате- ричное 0 0 0 0 1 9 1001 11 9 1 1 1 1 10 1010 12 А 2 10 2 2 11 юн 13 В 3 и 3 3 12 1100 14 С 4 100 4 4 13 1101 15 D 5 101 5 5 14 1110 16 Е 6 по 6 6 15 1111 17 F 7 111 7 7 16 10000 20 10 8 1000 10 8 17 10001 21 11 40
Если индекс опущен, предполагается, что число является де- рятичным. Например, числу 27 эквивалентны числа (11011)а, [33)8 и (1В)1в. Отметим одну особенность дальнейших обозначений: Гак как для шестнадцатеричной системы список различных де- сятичных Цифр недостаточен, для обозначения чисел 10, И, |2, 13, 14 и 15 будем использовать буквы А, В, С, D, Е и F. Упражнения к п. 1-4.2 ? 1. Напишите первые 17 целых чисел в троичной системе счисления. 2. Напишите первые 17 целых чисел в системе счисления с основанием 9. Существует ли какая-нибудь связь между числами, представленными в системах мисления с основаниями 3 и 9? if F 1-4.3. Преобразования систем счисления & Еще в школе нас научили представлять числа в деся- тичной форме. Мы так привыкли к десятичной системе счисления, иго нам очень трудно «почувствовать» количество, выраженное В другой системе счисления. Например, если бы мы увидели в витрине бюро путешествий объявление «САМОЛЕТОМ—В РВРОПУ ЗА 86 ДОЛЛ.», то нам захотелось бы тут же купить &акой дешевый билет. Но если затем, наведя справки, мы обнару- жили бы, что число 86 выражено в системе с основанием 21, #аше возбуждение быстро бы исчезло. Размышляя о том, под- ходит ли нам это сбивающее с толку предложение, мы, видимо, Захотим выяснить, насколько это дешево — 862Х долл. Наиболее Простой путь в такой ситуации — выразить (86)21 в десятичной системе счисления и сравнить результат с известными тарифами жругих бюро путешествий. Приведенный пример, хотя и довольно искусственный, пока- зывает, насколько важна для нас десятичная система для пони- ^гания и выражения величин. Однако, как было отмечено в пре- 1ДЫДущем пункте, современные компьютеры ориентированы на ^Двоичные, а не на десятичные числа. Таким образом, будучи Специалистами в вычислительной технике, мы встречаемся с про- блемой понимания двоичной системы и возможности преобразо- вания из двоичной системы в десятичную и наоборот. Представляют интерес также восьмеричная и шестнадиате- фйчная системы. Программисты, работающие на машинном уров- не, довольно быстро обнаружили, что большие двоичные числа Проще выражать в более компактных восьмеричной и шестнад- цатеричной системах. Кроме того, многие машинные операции ориентированы на восьмёричную и шестнадцатеричную системы, и очень часто схемы кодирования символьных данных (которые мы будем обсуждать в п. 1-4.7) выражены в восьмеричном и шест- надцатеричном форматах. Поэтому большинство примеров дан- ного пункта включают преобразования между двоичной, восьме- ричной, десятичной и шестнадцатеричной системами счисления. 41
X (25)1 4 (1 X 22 + О X 21 + 1 X 2й) X (23)й + (О X 24 1X24 + 1 X 2°) X (23П = 1 X 81 + 5 X 8° + 3 X 8-1 = (15.3)„. Известно, что 23 = 8. Следовательно, любая группа из трех двоичных цифр может быть представлена единственной восьмеричной цифрой. Объединяя двоич- ные цифры в группы по три, начиная от разделительной точки и двигаясь от нее в обе стороны, можно достаточно просто преобразовать двоичное число в его эквивалентное восьмеричное представление. Так как 2* = 16, то для получения эквивалентного шестнадцатеричного представления двоичного числа следует группировать двоичные цифры по четыре цифры, начиная от разделительной точки. Таким образом, (1101.011)2 = (1101.0110)2 = (D-6)ie. 3. Преобразование числа из системы счисления с основанием S в систему с основанием R, где Rk = S для некоторого целого k >• 1. Применяя только что рассмотренную процедуру группирования цифр, можно преобразовать числа из системы счисления с осно- ванием S в систему с основанием R. Проиллюстрируем в общем виде, как это осуществляется с помощью уравнения 1-4.1. Деся- тичный эквивалент числа в системе с основанием S выражается в виде N D= 4S‘ = 4vSJV4 •" 44-S244S144S°4 i= —М 4 d^S1 + • + d_MS~M = dN (Rk)N + • •. + d2 (R*y + d, (Rky + 4 d„ (Rk)° + d_t (R*)’i + • • ’ 4 d_M(Rk)~M. (1-4.3) Отметим, что d, являются цифрами, выраженными в системе с основанием S’. Для dt можно найти эквивалентное представле- ние в системе с основанием R, развертывая цифру 4 в k цифр системы с основанием R: (d()s = di\t-i х Rk 1 4 di'k-% х Rk 2 4 • • • 4 df \ x R 4 4.о • , Подставляя эту формулу для каждого 4 в уравнение (1-4.3) и умножая каждый его член на коэффициент (Rft)‘, получим сле- дующее выражение: D = х R<Af+1>*-1 4'4л-2 х R'N+r>k-2 4 • • • 4 4,*-1 х X R2 14 4,*-2 х R2k 2 4 4 4,о х Rk 4 4,*-i x R 1 4 4 4,k-2 x R 4 4 4,o x R° 4 4ц *-i x R 1 4 4 d-i k-2 x R 2 4 4 d-ifo x R k 4 4 ^-m,i x X R x R Коэффициенты d'u являются цифрами числа в системе с ос- нованием R для исходного числа в системе с основанием S. В сле- дующем примере иллюстрируется, как можно применить эту процедуру для развертывания цифр числа в системе с основанием S 44
’ целью получения его представления в системе счисления с ос- нованием R. j Пример 1-3. Чему равно число (76.21)8 в двоичной системе счисления? D = 7 X 8х + 6 X 8° + 2 X 8-1 1 X 8"» = 7 X (28) + 6 + 2 X (23)"1 + L + 1 Х(2®)-2=(1 Х2Ч 1 X 21 + 1 X 2°) X (23) Н- (1 х 22 + 1 х21-ф ' + О X 20) X (23)0 + (о X 22 + 1 X 21 + 0 х 20) х (28)-1 + (О X 22 + О X I* х 21 + 1 X 20) X (23)-2 = 1 X 2« + 1 X 2« + 1 X 2® + 1 X 23 + 1 X 21 + ; + О X 20 + о X 2~! + 1 х 2-2 + О X 2-3 + О X 2-4 + О X 2’6 + 1 X 2-«. „ Выписывая двоичные коэффициенты, мы получим, что (76.21)g — 8» (111110.010001)2. Конечно, если мы уяснили процесс развертывания, нам Йке не нужно всякий раз выполнять только что приведенную громоздкую про- ведуру. Мы смогли бы развернуть цифры исходного числа «в строчку», напри- k (7.А2)16 = ((0111)2-(1010)2(0010)2) = (0111.10100010)2 = (111.1010001)2. Ь- 4. Преобразование чисел между системами счисления с осно- ваниями S и Т, где S Т, S = RN и Т = RM. Для этого типа Перевода существует простая двухступенчатая процедура. г 1. Используя развертывание в соответствии с процедурой 3, преобразовать число из системы счисления с основанием S в си- стему с основанием R. с 2. Группируя цифры в соответствии с процедурой 2, преобра- зовать новое представление числа из системы с основанием R $ систему с основанием Т. ' Пример 1-4. Как представляется число (132.6)е в шестнадцатеричной системе Исчисления? я- Шаг 1. Преобразователь (132.6)8 в двоичную систему: (132.6)8 = ((001)2.(011)2 (010)2-(110)2) = (001011010.110)2. Шаг 2. Преобразователь (001011010.110)2 в шестнадцатеричную'систему: £ (001011010.110)2 = ((0101)2 (1010)2- (1100)2) = (5А.С)1в. £ £ 5. Преобразование целых десятичных чисел в систему счисле- ния с основанием R, если R не является степенью 10. Преобразование Целого десятичного числа в систему счисления с другим осно- ванием лучше всего выполнять с использованием алгоритма по- фледовательного деления. Алгоритм REPEATED_DIVISION. Дано целое десятичное яисло I; требуется преобразовать его в число, обозначенное RESULT, в системе счисления с основанием R. DIV является рабочей переменной, используемой для делимого, REM — рабо- чей переменной для остатка, и MOD (X, Y) является функцией, возвращающей остаток от деления X на Y. 1. [Инициализация.] Установить DIV-e-I и RESULT — пусто. 2. [Выполнение итераций до тех пор, пока не будет получено Делимое, равное 0.] Повторять шаги с 3 до 5 до тех пор, пока 1DIV 0. 45
3. [Нахождение остатка. ] Установить REM <-MOD (DIV, R). 4. [Нахождение нового делимого. ] Установить DIV -e-DIV/R. (Символ / означает деление нацело). 5. [Формирование числа в системе с основанием R.] Уста- новить RESULT REM О RESULT. 6. [Конец] Выход. Символ ’О’, используемый в шаге 5 алгоритма, обозначает оператор конкатенации. Например, если значение REM равно (0)16» а RESULT — (В)1в, то конкатенация переменных REM и RESULT, т. е. REM О RESULT, равна (0В)1в. Более формаль- ное рассмотрение операции конкатенации приводится в гл. 2. Пример 1-5. Чему равно целое число 267 в шестнадцатеричной системе счис- ления? В табл. 1-4.3 приведена последовательность значений, которые при- нимают переменные при выполнении алгоритма последовательного деления для целого числа 267. Отметим, что в этом примере R = 16. Тщательно изучив приведенный пример, мы можем исполь- зовать алгоритм последовательного деления и показать, что 47 = (101111)2. г 6. Преобразование десятичных дробей в систему счисления с основанием R, когда R не является степенью 10. Для преобра- зования десятичных дробей в другую систему счисления восполь- зуемся процедурой, двойственной процедуре последовательного деления — а именно, процедурой последовательного умножения. Алгоритм REPEATED MULTIPLICATION. Дана десятич- ная дробь F; требуется преобразовать ее в дробь системы счисле- ния с основанием R. PROD является рабочей переменной, исполь- зуемой для получения очередного произведения, a L — число цифр в системе с основанием R, необходимых для получения тре- буемой точности. FRACT (Р) — функция, возвращающая дроб- ную часть числа Р,а INT (Р) — функция, возвращающая целую Т а б л и ц а 1-4.3 Последовательность значений переменных при выполнении алгоритма REPEATED—DIVISION Шаг DIV REM RESULT 1 267 Не определено Пусто 2 & 3 267 И =(B)J6 Пусто 4 16 (В)1в Пусто 5 16 (B)ie (В)]в 2 & 3 16 (0)1в (В)16 4 1 (0)18 (В)1в 5 1 (0)ie (0В)и 2 & 3 1 (1)16 (0В)1в 4 0 (1)16 (0В)м 5 0 (1)16 (10В)1в 2 & 6 0 (1)16 (10В)1в 46
Таблица 1-4.4 Последовательные значения, принимаемые переменными при выполнении алгоритма REPEATED_MULTIPLICATION р— | Шаг PROD F RESULT К 1 Не определено 0.961 Пусто £2 & 3 7.688 0.961 Пусто В 4 7.688 0.688 Пусто К 5 7.688 '0.688 (7)„ Е2 & 3 5.504 0.688 (7)8 К 4 5.504 0.504 (7)8 $ 5 5.504 0.504 (75)я 2 & 3 4.032 0.504 (75)8 Е 4 4.032 0.032 (75)8 1 5 4.032 0.032 (754)8 а & 6 4.032 0.032 (0-754)в »сть числа Р (т. е. FRACT (3.92) = 0.92, INT (3.92) = 3). Пере- менная RESULT используется для размещения дробного числа, ^образованного в систему счисления с основанием R. ; 1. [Инициализация.] Установить RESULT ч-пусто. [ 2. [Выполнение итераций до тех пор, пока не будет достиг- нута требуемая точность.] Повторять шаги с 3 по 5 при i = 1, , 3, ..., L. . 3. [Вычисление нового произведения. ] Установить PROD ч- •—F * R. 4. [Выделение новой дробной части. ] Установить F ч- 4- FRACT (PROD). В-. 5. [Формирование частичного результата. ] Установить IfeESULT ч-RESULT о INT (PROD). 6. [Окончание. ] Установить RESULT ч- ’.’О RESULT и Кавер шить выполнение алгоритма. р Пример 1-6. Представить в восьмеричной системе число 0.961 с точностью Ко третьего дробного знака. В табл. 1-4.4 приведены последовательные значения переменных, иллюстрирующие работу алгоритма прн L = 3 и R = 8. Для про- Керки точности результата можно выполнить обратное прЛбразование числа ^ф.754)н в десятичную систему, для чего следует использовать процедуру 1. Вы- ьЯолнив ее, мы получим число 0.9609 с четырьмя дробными знаками. Г Описанные в этом пункте процедуры преобразования дают нам ^Инструмент для понимания того, как числовая информация за- Гооминается в компьютере. Однако, прежде чем перейти к изуче- нию представления числовой информации, обсудим сначала та- нине примитивные структуры данных, как целые и действительные 'числа. f Упражнения к п. 1-4.3 1. Преобразовать следующие числа в десятичную систему: а) (38)1в, I® (22.2)8, в) (1011)2, г) (1011)8, д) (1011)16, е) (AD.4)„, ж) (0.353)в, з) (92)12, Н (-77)в, к) (-77)м. 47
2. Выполнить следующие преобразования в другую систему счисления: а) (1100101)2 — в систему с основанием 16; б) (10101)2 — в систему с основанием 8; в) (1.11011)2 — в систему с основанием 16; г) (1.11)2 — в систему с основанием 8; д) (2 31)4 — в систему с основанием 16. 3. Выполнить следующие преобразования в другую систему счисления: а) (АС)1в — в систему с основанием 2; б) (712)е — в систему с основанием 2; в) (61.61)8 — в систему с основанием 2; г) (O.D1)16 - в систему с основанием 2; д) (Ol)ie —в систему с основанием 4. 4. Выполнить следующие преобразования в другую систему счисления: а) (7621)8 — в систему с основанием 16; б) (1 l)ie — в систему с основанием 8; в) (11)8— в систему с основанием 16; г) (Е.Е)16— в систему с основанием 8; д) (22)д — в систему с основанием 8. 5. Преобразовать следующие десятичные числа в заданную систему счис- ления: а) 72 — в систему с основанием 16; б) 36 — в систему с основанием 8; в) — 24 — в систему с основанием 2; г) 16 — в систему с основанием 16; д) 961 — в систему с основанием 2. 6. Преобразовать следующие десятичные числа в заданную систему счис- ления: а) 7.2 — в систему с основанием 16; б) 0.275 —в систему с основанием 8; в) 0.5625 — в систему с основанием 2; г) 0,5625 — в систему с основанием 16; д) 23.1—в систему с основанием 8. 1-4.4. Целые числа и их представление ' Все мы знакомы с понятием целого числа и многими операциями над ними (т. е. сложением, умножением и т. д.). Множество целых чисел может быть строго определено с помощью аксиом Пеано; однако для настоящего обсуждения не столь важно, сталкивался ли читатель когда-либо с формальным определением множества целых чисел. Достаточно сказать, что множество целых чисел имеет вид {...— (п -4- 1), —и, —2, —1, 0, 1, 2.......п, п + 1, Важность данных целого типа для вычислений очевидна. С помощью целых чисел может быть представлено количество объектов, являющихся дискретными по своей природе (т. е. счетное число объектов). Число долларов на банковском счете, число пассажиров некоторого рейса, число фигур на шахматной доске — вся эта информация выражается целыми числами. Применение целых чисел разнообразно и очевидно, поэтому нет необходимости в дальнейшем объяснении их важности в качестве типа данных. Однако метод представления целых чисел со знаком менее очевиден, особенно, если мы стремимся к эффективным и недорогим вычислениям. В этом пункте мы сосредоточим внима- ние на некоторых видах представления целых чисел со знаком и обнаружим, что наиболее знакомое нам представление необязательно является лучшим для"1 выполнения вычислений на ЭВМ. IF* При обычном способе записи отрицательного целого знак по- мещается перед числом. Этот метод, часто называемый методом знака и значения, был использован для представления чисел со знаком в ряде компьютеров. Обычно для знака отводятся первый 48
Знаковый бит Величина О 00 00 0111 оно (или самый левый) бит двоичного представления числа, а затем следует запись самого числа. Например, 4-7 и —6 в двоичном виде можно представить так: _________ Отметим, что в этом при- мере для представления знаков Числ< ПЛЮС и минус используются ----------- соответственно двоичные значе- ния 0 и 1. Такое предста- —6 вление знаков является обыч- ным. Хотя подобная нотация при записи числа имеет то преиму- щество, что легко воспринимается и знакома программисту, она не является самой экономной. Если мы хотим сложить или вы- честь два числа, то для решения вопроса о том, какие действия следует при этом предпринять, надо сначала определить знак каждого числа. Например, сложение чисел +6 и —7 на самом деле подразумевает операцию вычитания, а вычитание —6 из 4-7 — операцию сложения. Для анализа знакового бита требуется особая схема и, кроме того, при представлении числа в виде знака и величины необходимы отдельные устройства для сложения и вычитания. Наиболее экономичным и широко используемым методом яв- ляется представление целых чисел в виде дополнения до основания системы счисления., в частности в двоичных компьютерах приме- няется представление в форме дополнения до 2, или дополнитель- ного кода. В этом случае все арифметические операции выпол- няются по модулю М, где М = 7?/V, R —основание системы счисления, в которой выражено число, a N — максимальное ко- личество цифр, требуемое для представления целого по модулю М. Покажем в общих чертах, что такое модулярная арифметика. Предположим, что над числами х и у выполняется некоторая операция *, при этом получается результат z (т. е. г = х * у). Если такая операция выполняется по модулю М (будем ее обо- значать как *м), то результат х *м у есть величина Zm = = z mod М, т. е. zM является остатком, или вычетом, от деле- ния z на М. В арифметике для представления по модулю М каждого Целого х справедливо следующее соотношение: х = х 4- М. (1-4.4) С помощью простых алгебраических преобразований можно найти выражение для «отрицания» величины х, а именно = х. (1-4.5) Это равенство справедливо при условии, что обе его частя вычисляются по модулю М, т. е. обе части имеют один и тот же вычет. 49
Уравнение (1-4.5) показывает, как можно выразить положи- тельные н отрицательные числа в дополнительном коде. В каче- стве примера предположим, что мы оперируем числами, пред- ставленными в форме дополнения до 2, н используем арифметику по модулю М = 16 (так как 24 = 16, то для записи любого целого числа отводится всего N = 4 двоичных цифр). В табл. 1-4.5 для целых от —8 до 4-7 приведены дополнительные коды в системе с модулем 16. Таблица 1-4.5 Представление целых чисел в дополнительном коде в системе с модулем 16 Целое число Представление в дополнительном коде Целое число Представление в дополнительном коде 0 0000 —1 (16— 1 = 15) 1111 1 0001 —2(16—2 = 14) 1110 2 0010 —3(16 — 3= 13) 1101 3 ООП —4 (16 — 4 = 12) 1100 4 0100 —5 (16 — 5 = П) 1011 5 0101 -6 (16 — 6 = 10) 1010 6 оно —7(16 — 7= 9) 1001 7 0111 —8(16 — 8= 8) 1000 Отметим, что представление отрицательных чисел в дополни- . тельном коде получается из уравнения (1-4.5). Альтернативным методом получения дополнительного кода отрицательных чисел является использование двоичной арифметики. Например, —2 выражается в форме дополнения как 16 — 2 = (10000)2 — (10)в = (1110)2. Пример 1-7. Найти дополнительный код числа —38 в системе с’модулем 32. Сначала мы должны записать число —38 по модулю 32. Мы уже знаем, что —38 mod (32) = —6. Для того чтобы найти дополнительный код числа —6, применим равенство Л-4.5) Тогда 32 — 6 = (11010)2, или (100000)8 — (110)2 = (11010)а. Одно из основных преимуществ записи чисел в виде дополни- тельного кода состоит в возможности использования операций сложения и дополнения вместо сложения и вычитания. Отметим, что для двоичных чисел операция дополнения очень проста и заключается в выполнении следующих действий. 1. Запись нулей перед числом так, чтобы общее количество двоичных цифр соответствовало используемому модулю. 2. Инвертирование всех двоичных цифр. 3. Прибавление единицы к инвертированному коду. Следовательно, дополнительный код числа 6, выраженный пятизначным кодом, есть СОМР (6) = СОМР ((00110)г) = (11010)2. 50
Таким образом, операцию получения Дополнительного кода СОМР можно рассматривать как функцию целого числа, выра- женного в некоторой системе счисления R, преобразующую его в дополнительный код. Код (11010)2 совпадает с результатом, полученным в примере 1-7. В следующем примере иллюстрируется тот факт, что для сложения и вычитания целых чисел необходимы лишь операции двоичного сложения и дополнения. Пример 1-8. Вычислить 3 4- 4, 3 — 4, 7 + 7, —7 — 7, используя дополни- тельные коды чисел и арифметику по модулю 16. а) 3 + 4 — (0011)2 + (0100)2 = (0111)2 = 7; б) 3 — 4= (0011)2 + СОМР ((0100)2) - (0011)2+ (1100)2=(1111)8 = -1. Напомним, что (1111)2 является дополнительным кодом числа —1. в) —3 + 4 = СОМР ((0011)а) 4- (0100)2 = (1101)2 + (0100)2 = = (0001)2 = 1; г) 74- 7= (0111)84- (0111)2= (111О)2=-2; д) —7 — 7 = СОМР ((0111)2) 4- СОМР ((0111)2) = = (1001)2 4- (1001)8 = (0010)8 = 2. В примере 1-8 пункты г) и д)дают неправильные результаты. Вспомним, что по модулю 16 в дополнительном коде могут быть представлены числа от —8 до 4-7. Поэтому вычисление выраже- ний 7 4- 7 и -—1—1 приводит к арифметическому переполнению. Мы вернемся к проблеме переполнения после того, как познако- мимся с другими методами представления целых чисел в компь- ютере. Уменьшенное дополнение числа d в системе счисления с осно- ванием R определяется как R—1—d. Таким образом, двоичные цифры 0 и 1 в этой форме заменяются соответственно на 1 и 0. Уменьшенное дополнение целого числа можно найти по формуле f ORCOMP Отметим, что 7?" в системе с основанием R выражается цифрой 1 с п нулями. Существенная разница между формами дополнительного кода и уменьшенного дополнения состоит в том, что в последнем слу- чае величина модуля, для которого получают дополнение (т. е. Rn)t уменьшена на единицу. Для двоичных чисел уменьшенное дополнение обычно называют обратным кодом. Обратный код для целого числа 6 в системе с модулем 16 есть 16— 1 — 6 = 9, или (1СЮОО)2 - 1 — (0110)2 = (1111)2 — (0110)2 = (1001)2. Важно отметить, что обратный код СОМР1 (х), где х есть двоичное число, очень просто получить с помощью замены всех нулей числа х на единицы и всех единиц иа нули. Это хорошо видно из табл. 1-4.6. Одним из недостатков обратного кода является присутствие Двух нулей: 4-0 и —0. Тот факт, что оба они как числа равны, но синтаксически различны, может создать много вычислительных проблем. Записывая числа в обратном коде, можно, как и при использовании дополнительного кода, осуществлять сложение и . 51
Таблица 1-4.6 Обратные коды целых чисел в системе с модулем 16 Целое число Обратный код Целое число Обратный код 0 0000 —1 (COMF1 (0001)2) 1110 1. 0001 —2 (COMF1 (0010)2) 1101 2 0010 —3 (COMF1 (0011)2) 1100 3 ООН —4 (COMF1 (0100)2) 1011 4 0100 —5 (COMF1 (0101)2) 1010 5 0101 —6 (COMF1 (0110)2) 1001 6 ОНО —7 (COMF1 (0Ш)2) 1000 7 0111 —0 (COMF1 (0000) 2) 1111 вычитание с помощью операций сложения я дополнения; кроме того, появляется возможность прибавлять бит переноса. В при- мере 1-9 показано, как осуществляется сложение и вычитание чисел, записанных в обратном коде. Пример 1-9. Используя обратные коды, вычислить в системе с модулем 16 следующие выражения: 3 -J- 4, 3 — 4, —3 -]- 4, 7 + 7, —7 — 7. а) 3J 4 — (0011)а F (0100)2 — (0П1)2 = 7; 6) 3 — 4 = (0011)2 + СОМР1 ((0100)2) = = (0011)2 + (1011)2= (1110)2 = — 1. Напомним, что *(1110)2 является обратным кодом числа —1. в) —3 + 4 = СОМР1 ((0011)2) + (0Ю0)2 = = (1100)2 -4- (0100)2 = (0000)£= 0. Отметим, что здесь происходит перенос единицы. г) 7+ 7= (0111)2+ (0111)2= (11Ю)2= -1; д) — 7 — 7 - COMF1 ((0111)2) + COMF1 ((0111)2) = = (1000)2+ (]000)2 = (0000)2 = 0. Из примера 1-9 видно, что в пункте в) получен неверный ре- зультат (—3 +4 должно быть равно 1, а не 0). Отметим, что если к результатам пунктов а), б) и в) прибавить единицу пере- носа, то получатся правильные результаты: 7, —1 и 1 соответ- ственно. Такое циклическое прибавление бита переноса должно выполняться в операциях сложения и вычитания в обратном коде. В пунктах г) и д) снова, как и в пунктах г), д) примера 1-8, получается переполнение. Во время вычислений важно обнару- живать такие переполнения. Компьютеры обычно в этих случаях выдают сообщение об ошибке. Чтобы определить условия для обнаружения переполнений, следует иметь в виду следующее. 1. Сложение положительного и отрицательного чисел никогда не приводит к переполнению. 2. Сложение двух положительных или двух отрицательных чисел не приведет к переполнению, если результирующая сумма имеет тот же знак, что и операнды. 52
Эти правила справедливы для сложения дополнительных и обратных кодов, за исключением случая сложения двух дополни- тельных кодов «-разрядных двоичных чисел а и b таких, что = 2я-1. В этом случае для обнаружения переполне- ния следует проверить, не равны ли все значащие биты нулю, а бит переноса — единице. Более детальное рассмотрение вопро- сов, связанных с циклическим прибавлением бита переноса и обнаружением переполнений, можно найти в работах [1, И, 12]. Очевидно, что при вычислениях в системе с модулем 16 выра- жений 7 + 7 и —7 — 7 эти правила нарушаются. Переполнения могут возникать также и при выполнении других арифметических операций, таких как умножение и деление. Деление на нуль является классическим примером такой ситуа- ции. В'этом пункте мы намеренно сконцентрировали внимание на двух системах представления целых чисел, являющихся альтерна- тивами хорошо нам знакомого метода представления в виде знака и величины. Способы размещения целочисленных данных в па- мяти будут обсуждаться в п. 1-4-6. Из-за эффективности и про- стоты методов представления в виде дополнительных иля обратных кодов разработчики компьютеров обычно используют один из них. А сейчас обратимся к анализу другого типа простейших числовых данных — действительным числам. Упражнения к п. 1-4.4 1. Предполагая шестибитовос представление, получить дополнительные коды следующих целых чисел: а) —33, б) —52, в) (—33) 8) г) (—Е)1е, д) —241. 2. Применяя дополнительные коды, вычислять в системе с модулем 16 сле- дующие выражения: а) 6 — 1, б) 7 — (—2), в) —3 — 3. 3. Применяя обратные коды, вычислить в системе с модулем 32 следующие выражения: а) 8 4- 8, б) 6 1, в) 7 — 11. 1-4.5. Действительные числа и нх представление В п. 1-4.2 мы ввели, не подчеркивая явно этот факт, для вещественного числа А^ в системе с основанием R представ- ление с фиксированной точкой вида А^ = ± • 'Оха0а_у - • •«_ (м-нМ-м)/?» которое может быть получено нэ выражения М-1 А=+ V aft*. (1-4.6) В вычислениях на ЭВМ представление с фиксированной точкой является достаточным для большинства обычно встречающихся Действительных чисел. Однако в некоторых областях вычисления 53
требуются очень большие или весьма малые действительные числа. Одной из таких областей является астрономия. Напри- мер, в вычислениях гравитационных сил между небесными те- лами используют измерения огромных расстояний и масс-. Рас- стояния вроде 16 800 000 000 000 км являются вполне нормаль- ными. С другой стороны, в атомной физике при вычислении энер- гии в пузырьковой камере требуются измерения чрезвычайно малых расстояний Типичной величиной может быть 0. 0 000 000 000 832 м. Стоимость компьютера непосредственно зависит от точности чисел, которые могут участвовать в вычислениях. Эта стоимость в основном определяется стоимостью центрального процессора. Чем выше допустимая точность, тем больше и сложнее будет арифметическое устройство. Арифметические вычисления над приведенными выше действительными числами потребовали бы точности в 27 десятичных знаков. Для получения той же точности при двоичном представлении потребовался бы компьютер прибли- зительно с 90 двоичными разрядами (битами). Так как в настоя- щее время для представления чисел используется от 16 до 64 битов, компьютер с размером слова в 90 битов был бы неэкономи- чен как машина общего назначения. Альтернативным методом представления чисел для преодоле- ния этой трудности является запись с плавающей точкой, или научная форма записи. В этой сокращенной форме записи изме- рения расстояний, приведенные ранее, выражались бы как 0.168 : 1014 км и 0.832 :< Ю"10 м. В общем виде запись с плавающей точкой действительного числа в системе счисления с основанием R выражается в виде f-xt-r -Ам >- RE, где ... называется дробной частью, или мантиссой, а Е, всегда являющееся целым числом, — порядком. Очевидно, что при представлении числа в форме с плавающей точкой достигается значительный выигрыш. Так как для конструк- торов ЭВМ весьма важным параметром является объем простран- ства, т. с. число разрядов, требуемое для представления числа, запись с плавающей точкой была принята как средство представ- ления действительных чисел. В следующем пункте мы обсудим некоторые структуры для запоминания действительных чисел в форме с плавающей точкой. Следует иметь в виду, что обычно дробная часть числа с пла- вающей точкой содержит только старшие значащие цифры дей- ствительного числа. Например, мантисса числа 0.168 1014 км имеет длину три знака. Вполне вероятно, что первоначальные измерения были проведены с точностью до первых трех знаков, т. е. 0.168 10й является приближенным значением точного измерения, например, 16 817 210 391 704 км. Разницу между 54 приближенным и точным значениями измерения называют ошиб- кой округления. Из приведенных соображений видно, что запись с плавающей точкой является весьма эффективным средством представления очень больших и весьма малых действительных чисел при усло- вии, что они содержат ограниченное число значащих цифр. Ком- пьютеры могут запоминать лишь конечное число значащих цифр, щ следовательно, не все действительные числа могут быть пред- ставлены в памяти. Обычно число используемых при вычислениях значащих цифр таково, что для большинства задач ошибки округ- ления пренебрежимо малы. Но если используемый нами компью- тер не может представить все существенные значащие цифры действительного числа, остаются три альтернативы. Первый возможный выход состоит в том, чтобы проводить вы- числения на большей машине. В следующем параграфе мы узнаем, что существуют компьютеры, обеспечивающие точность почти в двадцать десятичных цифр. Однако, как ранее отмечалось, за точность надо платить, и затраты на вычисления могут стать не- померно высокими. Второй альтернативой является использование пакета про- грамм для работы с многократной точностью. В п. 4-3.3 мы де- тально обсудим пример вычислений с многократной точностью. >В этом пакете действительное число делится на несколько сег- ментов, каждый из которых обычно соответствует машинному ►слову. Операции, например, сложения, выполняются над отдель- ными сегментами, а программы пакета обеспечивают управление переносом и заемом, которые при этом происходят. Использова- ние вычислений с многократной точностью также может быть дорогим, так как каждая операция над сегментами должна интерпретирована программой пакета. Третий, наиболее часто применяемый метод, состоит в округ- лении действительного числа до такого количества значащих цифр, какое может быть представлено в машине. Округление является очень простой операцией, которую лучше всего описать на примере. Пример 1-10. Чему равно действительное число 21.833652, округленное до пяти десятичных знаков? L 21.833652+ 0.0005 = 21.834152. Итак, число после округления равно 21.834. Принятая нами процедура состоит в прибавлении числа 5 к шестому деся- тичному знаку, а затем выделении старших пяти значащих цифр. Вообще для округления десятичного числа до п цифр следует прибавить 5 кг.-? 1-й цифре И сохранить п старших цифр. | 1-4.6. Структуры хранения числовых данных В предыдущих параграфах мы определили два типа Простейших структур данных -- целые и действительные числа, и рассмотрели, как они должны быть представлены для вычисле- L 55 очень 1быть
ний на ЭВМ наиболее простым способом. В данном пункте целые и действительные числа будут исследованы более подробно с точки зрения методов Их хранения в ЭВМ. Это будет сделано с помощью примеров структур хранения в различных семействах компьюте- ров: IBM 360-370, CDC6000, CYBER 70-170, Burroughs В5000 и В6000, Univac 1108, PDP-10, Hewlett-Packard 2100 и PDP-11. Начнем наше рассмотрение с методов машинного представле- ния целых чисел. 1-4.6.1. Машинное представление целых чисел Общепринятым является представление целых в ЭВМ в виде двоичных чисел. Типичной является ситуация, когда для хранения целого числа отводится одно машинное слово. На языке ассемблера и некоторых других языках программирования, та- ких как ПЛ/1, у программиста есть возможность определить для хранения целого числа участок памяти размером меньше слова. Минимальным объемом памяти, которое может быть использовано для хранения целого числа, является наименьшая непосредственно адресуемая единица памяти, часто называемая байтом. Однако использование структур хранения с размерами, мень- шими машинного слова, имеет определенные недостатки. При выполнении вычисления над целыми числами, занимающими в па- мяти объем, меньший одного слова, число переносится в ариф- метическое устройство и расширяется до полного слова; дальней- Таблица 1-4.7 Структура хранения целочисленных данных в некоторых ЭВМ ЭВМ или семейство ЭВМ Двоичное представление Десятичное представление IBM 360-370 Дополнительный код Десятичное упакован- 16- и 32-разрядных чи- сел ное с 7 десятичными зна- ками в 32-разрядном слове CDC 6000 и CYBER 70-170 Обратный код 18- и 60-разрядных чисел Десятичное упакован- ное с 14 десятичными зна- ками в 60-разрядном слове Burroughs В5000 и 156000 40-разрядные числа со знаком н величиной Символьное представ- ление 6 бит/символ, 8 де- сятичных цифр в 48-раз- рядиом слове Univac 1108 Дополнительный код 36- и 72-разрядных чи- сел Отсутствует PDP-10 Дополнительный код 36-разрядных чисел Hewlett-Packard Дополнительный код 16-разрядных чисел » PDP-11 Дополнительный код 16-разрядных чисел » 56
Знак 0 0000000000000000000000000001101 Кис. 1-4.1. Машинное представление целого числа 13в 32-разрядиом слове в дополни- К«льном коде Кие вычисления проводятся с этим новым представлением числа. К(ля запоминания целого числа в части слова должна быть при- менена обратная процедура, т. е. в регистре выделяется некоторая [часть полного слова с размером по крайней мере не меньше байта, [которая затем переносится в нужную область памяти. [ Чтобы избежать неоднозначности для выражений +0 и —0, [отрицательные числа обычно представляются в дополнительном, [а не обратном, коде. В табл. 1-4.7 этот факт иллюстрируется на [примере некоторых выпускаемых в настоящее время машин. Первый бит двоичного представления является знаковым; осталь- ные биты выражают величину числа в дополнительной форме. [На рис. 1-4.1 приведено 32-бнтовое представление целого числа 13 |в виде дополнительного кода. Примеры хранения 16-разрядных кисел приведены в табл. 1-4.8. » Таблица 1-4.8 е Примеры представления целых 16-разрядных двоичных чисел Г. в дополнительном и обратном кодах Десятичное число Обратный код Дополнительный код 9 613 0010010110001101 0010010110001101 —9 613 1101101001110010 1101101001110011 317 0000000100111101 0000000100111101 —317 1111111011000010 1111111011000011 32 767 0111111111111111 0111111111111111 —32 767 1000000000000000 1000000000000001 —32 768 Не представимо 1000000000000000 Важно знать диапазон целых величин, которые могут хра- ниться в п разрядах памяти. Для п разрядов формула суммиро- вания имеет вид 2° 4- 21 4~ 22 4- ... + 2П-1, или (1-4-7) £.2< = 2"-1 При n-битовом хранении дополнительного кода первый бит выражает знак целого числа. Поэтому положительные числа пред- ставляются в диапазоне от 0 до 1 X 2° + 1 Y 21 + — 4* 41 X 2п~2 или [из равенства (1-4.7)] от 0 до 2е”1 — 1. Все Другие конфигурации битов выражают отрицательные числа ft 57
в диапазоне от —2/1-1 до —1. Таким образом, можно сказать, что целое число N может храниться в п разрядах памяти, если —2"-’ N 2я-1 — 1. Так как при использовании обратного кода существует как +0, так и —0, диапазон представления в этом случае на единицу меньше, чем в форме дополнительного кода. Следовательно, если применяются «-разрядное представление и обратный код, число N может находиться в диапазоне —2я 1 + — 1. Проиллюстрируем границы представления целых чисел при запоминании их в 16 и 32 двоичных разрядах. При использова- нии дополнительного кода в 16-разрядном машинном слове может быть представлено целое N, если — 215 215 — 1 или —32768с С N с 32767. В 32-разрядном машинном слове при применении дополнительного кода можно представлять числа из гораздо более широкого диапазона, а именно —2s4cM^281 — 1, или —21474836482147483647 В некоторых больших компьютерах кроме двоичного пред- ставления существует десятичная форма хранения целых чисел. При этом для действий над десятичными числами применяют некоторый набор основных команд, включающих по крайней мере сложение, вычитание и сравнение. Основной целью деся- тичного представления н связанных с ним команд является повы- шение эффективности в тех случаях, когда выходные данные полу- чаются из исходных в результате немногих вычислительных ша- гов. В этом случае преобразования между исходной и десятичной формами могут быть выполнены без использования промежуточ- ного представления, необходимого при переходе от исходной к двоичной форме. В некоторых системах, в частности в Системах IBM 360-370, промежуточной формой фактически является двоич- ное представление. Может создаться впечатление, что приведенное обсуждение представления и обработки целочисленных данных в десятичной форме противоречит прежним нашим предпосылкам о том, что информация в компьютере хранится в виде двоичных цифр, т. е. битов. Вовсе нет! Для представления целых в десятичном формате десятичные цифры кодируют с помощью четырехбитового двоич- ного кода, часто называемого кодом BCD. В этом коде каждая десятичная цифра представляется в виде ее четырехразрядного двоичного эквивалента. Например, целое число 9613 кодируется как 1001 0Ц0 0001 ООН 9 6 13 58
Цифра Uucppa Цифра Цифра Цифра Цифра Цифра Знак рис. 1-4.2. Представление упакованного десятичного числа Отметим, что в коде BCD четырехразрядные коды 1010, 1011, 1100, 1101, 1110, 1111 не имеют десятичного эквивалента. Два из этих излишних кодов часто используют для представления знака числа. Представление в виде кодов BCD (называемое упакованным десятичным) существует в Системах IBM 360-370. Общая форма представления упакованного десятичного числа приведена на рис. 1-4.2. Некоторые примеры упакованных десятичных чисел даны в табл. 1-4.9. Отметим, что коды 1100 и 1101 представляют 1 абл ица 1-4.9 Примеры представления упакованных десятичных чисел Целое число Упакованная десятичная форма 0149 —0149 935 —6 00000001010010011100 00000001010010011101 1001001101011100 01101101 соответственно знаки плюс и минус. Могут существовать и дру- гие формы представления десятичных цифр; в частности, в ЭВМ фирмы Burroughs принят метод шестибитового кодирования (см. табл. 1-4.7). 1-4.6.2. Машинное представление действительных чисел Ранее мы обсудили два метода представления действи- тельных чисел: с плавающей точкой, например 0.2364 X 102, н с фиксированной точкой, например 23.64. В этом подпункте мы сначала обсудим машинное представление действительных чисел с плавающей точкой, которое наиболее широко используется для вещественных данных. Представление в форме с фиксированной точкой рассмотрим ниже, одновременно будут освещены достоин- ства и скрытые недостатки данного представления. Наиболее широко используемый формат для представления чисел с плавающей точкой содержит одно или два поля фиксиро- ' 59
иная Характеристика ЛроЬная часть Рис, 1-4.3. Машинное представление действительного числа с плавающей точкой ванной длины, причем одно поле равно машинному слову. Хотя как основание системы счисления, так и количество позиций для значащих цифр различны в разных ЭВМ, существует, тем не менее, общий формат, приведенный на рис. 1-4.3. Обычно первый бит представления с плавающей точкой яв- ляется знаковым, и по принятому соглашению нуль обозначает положительное число, а единица — отрицательное. В форме записи со смешением вместо порядка используется характеристика^ являющаяся смененным порядком. Чтобы проиллюстрировать смысл записи со смещением, предположим, что поле характери- стики в представлении с плавающей точкой имеет размер 7 битов (как в Системах IBM 360-370, RCA Spectra 70, XDS Sigma 7). При использовании дополнительного кода в семибитовом поле можно представить целые в диапазоне от —64 до +63. Однако для представления порядка дополнительный код не очень удобен. При сложении или вычитании чисел с плавающей точкой для того, чтобы выровнять операнды, требуется сдвиг влево или вправо дробной части числа. Сдвиг можно осуществить с помощью един- ственного счетчика, в который сначала заносится положительное число, уменьшающееся затем до тех пор, пока не будет выпол- нено требуемое число сдвигов. В связи с такими особенностями аппаратурной организации желательно порядок записывать так, чтобы его значения выражались целыми неотрицательными чис- лами. В семиразрядном коде мы можем представить целые неотри- цательные числа в диапазоне от Одо 127. Чтобы обеспечить такой диапазон и иметь возможность выражать положительные и отри- цательные значения порядка, необходимо сместить истинный порядок на 64. Таким образом, для того, чтобы по значению по- рядка числа с плавающей точкой получить его характеристику, нужно прибавить к порядку коэффициент смещения, т. е. в дан- ном случае 64. Следовательно, характеристика числа с плавающей точкой, имеющего порядок — 48, равна —48 + 64 = 16. Такой способ обычно называют записью со смещением 64. Третьим и последним компонентом представляемого в машине числа с плавающей точкой является дробная часть, или мантисса. Для увеличения количества значащих цифр в представлении числа и исключения переполнения при делении дробную часть обычно подвергают нормализации. Нормализация означает, что дробная часть (назовем ее F), кроме случая, когда F = 0, должна находиться в интервале /+1 sc F <» 1. Если для дробной части выбрать основание системы счисле- ния R ~ 2, то, в связи с тем, что 2-1 < F < 1, ненулевая дроб- 60
ря часть любого хранимого числа с плавающей точкой должна зачинаться с двоичной единицы. В последнее время, однако, тановятся популярными восьмеричное и шестнадцатеричное зредставления чисел с плавающей точкой. Эти способы имеют ipa определенных преимущества перед двоичным представлением, 'ри шестнадцатеричном представлении, например, для того чтобы 1исло было выражено в нормализованной форме, дробная часть рлжна находиться в интервале 161 1, или (0.0001)2^ -: <; 1, т. е. для двоичных дробей нормализация после счислений требуется в 8 раз чаще, чем для шестнадцате- шчных. Второе преимущество шестнадцатеричного представления со- стоит в том, что при заданной разрядности характеристики ше- стнадцатеричная система позволяет представлять числа гораздо большей величины. Например, при семибитовой характеристике । шестнадцатеричной системе мы можем представить числа от 6“84 до 16+6i, тогда как в двоичной системе—только от Ю 2+е4. Одно из достоинств двоичной формы представления с плава- ющей точкой состоит в том, что процесс нормализации создает дробь, первый бит которой равен (1)2. Поэтому в структуре некото- рых машин (в частности, PDP-11) учитывается, что всегда при- сутствует эта начальная двоичная единица, однако она не запи- сывается в дробную часть. Этот дополнительный бит часто назы- вают скрытым битом. В некоторых компьютерах, например в машинах фирмы Burroughs, разделительная точка помещается справа после ман- тиссы. В таком случае мантиссу не следует рассматривать как дробь, и нормализация при этом не проводится. Преимущество ^такого представления состоит в том, что для действительных и целых чисел используется одна и та же структура хранения. Целые определяются просто из того факта, что их порядок равен нулю. В семействе машин CDC 6000 для выполнения нормализа- ции предусмотрена специальная команда (т. е. автоматически нормализация не производится). Работа с целыми числами выпол- няется без нормализации, а в операциях с действительными чис- лами для максимального увеличения количества значащих цифр в конечном результате должна быть в явном виде выполнена нормализация. Машины фирмы IBM обеспечивают два набора команд для операций над числами с плавающей точкой: один для нормализованных вычислений, а другой — для вычислений без нормализации. На рис. 1-4.4 приведены примеры машинного представления Ряда действительных чисел. Предполагается 32-разрядное пред- ставление с плавающей точкой, состоящее из знакового поля раз- мером 1 бит, семиразрядной характеристики (в записи со смеще- нием 64), 24-разрядиой дробной части (нормализованное шест- надцатеричное число). 61
Десятичное Шестнадцатеричное 32-разрядное число с _ число числа' плавающей точкой .о х 10° .(О),* х 16» 0 кммхюо сооакюоюоо’эооооооооооо -.17307 X 10‘ -.(439В)1. X 16’ 1 1000100 0)0000111001101100000000 .28692 х 10». ,(1СВ12бЕ9)1е X 16» 0 1000010 000111001011000100100110 -.75 х 10° ~.(С)И х 16? 1 1000000 110000000000000000000000 .8317 X 10-» .(3681904),, X 16-s 0 0Ш110 001101101000000110011100 Рис. 1-4.4. Иллюстрация машинного представления числа с плавающей точкой Таблица 1-4.10 Структуры хранения действительных чисел в некоторых ЭВМ ЭВМ или семейство ЭВМ Длина порядка, бит Осно- Длина мантиссы, бит (исключая знак) Поло- жение раздели- тельной точки относи- тельно мантиссы Наличие нормали- зации по- рядка обычная с двой- ной точ- ностью IBM 360-370 7 (со смеще- нием 64) 16 24 56 Слева Да/Нет CDC 6000 11 (со смеще- нием 1024) 2 48 - Справа По команде Burroughs В5500 & В6500 6 (и бит знака) 8 39 78 (только у В6500) Справа Нет PDP-10 8* 2 27 Слева Да Univac 1108 8 (смещение 128 для обыч- ной точности) и 11 (смеще- ние 1024 для двойной точности) 2 27 61 Слева Да PDP-11 (модель 45) 8 (смещение 128) 2 24 (со скры- тым битом) Слева Да’ Hewlett- Packard 2100 Аппаратные средства для представления чисел с плаваю- щей точкой отсутствуют, но можно использовать пакет про- грамм или дополнительный микропрограммный блок * Запись со смещением 128 для положительного порядка, обратный код для отрицательного порядка. 62
| Хдраюпёрмтика | Дробная | "уоссть г , . 8 9 32 33 Рис. 1-4.5. Машинное представление числа с двойной точностью В большинстве ЭВМ (табл. 1-4.10) число с плавающей точкой целиком содержится в одном машинном слове. Однако для ряда научных задач дробная часть при однословном представлении не- достаточна для получения требуемой точности. Поэтому многие машины имеют возможность представлять числа с плавающей точкой в форме с двойной точностью. В качестве примера на рис. 1-4.5 дана структура хренения числа с двойной точностью в машинах Систем IBM 360-370. Прежде чем закончить обсуждение представления действи- тельных чисел с плавающей точкой, необходимо отметить, что > в некоторых небольших компьютерах (таких миникомпьютерах, [как PDP-11 или машинах семейства NOVA) операции с плава- ющей точкой необязательно являются неотъемлемой частью си- стемы команд машины. В этих случаях можно приобрести и при- соединить к центральному процессору добавочные аппаратурные модули Кроме того, для некоторых ЭВМ имеются специальные пакеты программ, которые моделируют выполнение отсутству- ющих аппаратных команд с плавающей точкой. Так как команды моделируются, вычисления в форме с плавающей точкой происхо- дят крайне медленно, и по возможности их следует избегать. : Действительные числа могут запоминаться не только в форме чисел с плавающей точкой, но и в виде чисел с фиксированной точ- кой. Выполнение машинных команд над действительными числами с фиксированной точкой в общих чертах идентично работе с це- • лыми числами. Причина этого состоит в том, что структуры хра- нения целых чисел и действительных чисел с фиксированной точкой одинаковы. В языке программирования ПЛ/1 целое число должно быть объявлено фактически как число с фиксированной точкой, которая находится в правом конце фиксированного фор- мата. Действительные числа с фиксированной точкой очень удобны при ручных вычислениях (например, 3.782 ф §3.2 = 96.982), " но в ЭВМ ситуация совсем другая. Как уже говорилось, в комцью- ‘ тере не существует как таковых аппаратурно реализованных арифметических операций для чисел с фиксированной точкой; они моделируются с помощью операций над целыми. Рассмотрим гипотетический пример. В «десятичном компьютере» числа 3.782 и 93.2 могли бы быть представлены как 00 ... 03782 и 00 ... 0932. Конечно, в дополнение к такому представлению в памяти для каждого числа должна содержаться информация о положении Десятичной точки (в нашем примере — перед позициями 3 и 1). 63
Если над этими числами выполняется некоторая вычислительная операция, например сложение, то для правильного ее выполнения (суммирован-ия) хранимое в памяти число 00 ... 0932 должно быть сдвинуто влево на две позиции. После завершения целочис- ленного сложения полученный результат был бы равен 00 ... 96982 с разделительной точкой перед позицией 3. Хотя предыдущее обсуждение касалось целых операций над десятичными числами, легко представить, как должны были бы выполняться операции сравнения двоичных чисел с фиксированной точкой. В языке ПЛ/1 при объявлении переменной типа FIXED можно специфицировать как точность числа, так и положение раздели- тельной точки. При любых вычислениях с участием переменных типа FIXED следует принимать во внимание предполагаемое положение разделительной точки независимо от того, имеет ли переменная атрибут основания системы BINARY (двоичное) или DECIMAL (упакованное десятичное). Хотя такие операции над числами с фиксированной точкой, как сложение, вычитание и умножение, не представляют труда, деление переменных типа FIXED приводит к интуитивно неясным результатам. Это про- иллюстрировано в примере 1-11. Пример 1-11. Предложим, что X и Y являются переменными программы, написанной на языке ПЛ/1 с объявленной точностью 6 и 15 десятичных цифр и масштабными коэффициентами (т. е. положением десятичной точки), равными соответственно 3 и 0. Если переменной X присвоено значение 432.432, представ- ляемое как 432 Д 432, а величина Y равна 2 (что представляется как 00...02Д), то результатом операции Х/Y является число 216 Д 216 000 000 000. С другой стороны, если Y имеет точность 15 десятичных знаков, масштабный коэффициент 9 и значение 2.00001 (представляемое как 000002 А 000010000), то по существую- щим в ПЛ/1 правилам деления величин типа FIXED операция Х/Y даст резуль- тат 000000000216 Д 216. Таким образом, чем больше задается значащих цифр для операндов, тем меньше значащих цифр содержит результат. Замечание о типах данных в ПЛ/1. Основной причиной недо- вольства ПЛ/1 как языком высокого уровня является неудачный выбор числовых типов данных. В этом языке существует четыре основных типа данных (здесь мы не рассматриваем тип данных COMPLEX) : FIXED BINARY, FIXED DECIMAL, FLOAT BINARY и FLOAT DECIMAL. Программист не имеет возмож- ности объявить в ПЛ/1 числовые величины в терминах знакомых ему простейших типов данных INTEGER и REAL. Вместо этого он должен описывать числовую информацию в терминах струк- тур хранения, имеющихся в используемом семействе машин фирмы IBM. На самом деле типы данных ПЛ/1 не полностью сов- местимы с имеющимися структурами хранения, так как факти- чески переменные типа FLOAT DECIMAL запоминаются в стан- дартном шестнадцатеричном формате с плавающей точкой, харак- терном для семейства машин Систем IBM 360-370. Такой неудач- ный выбор типов данных делает ПЛ/1 трудным для изучения, так как прежде чем начать программировать на этом языке, про- 64
граммист должен приложить усилия для детального ознакомле- ния со способами машинного представления чисел. В этом подпункте мы рассмотрели в качестве простейших числовых структур данных целые и действительные числа. Основ- ное внимание при этом уделялось описанию наиболее широко используемых структур хранения; мы сохраним такой подход и в последующих пунктах данной главы при анализе нечисловых структур данных. Упражнения к п. 1-4.6 1. Выразить следующие целые числа в 16-разрядном представле- нии с помощью обратных и дополнительных кодов: а) 27, б) —256, в) —7, г) 7, Д) 44. 2. Приведенные в упр. 1 числа выразить в упакованном десятичном фор- мате. 3. Выразить 0.250 в виде числа с плавающей точкой, хранимого в маши- нах Систем IBM 360-370 и в семействе машин CDC 6000. 4. Выразить число 0.625 в виде числа с плавающей точкой для машин Uni- vac 1108. 1-4.7. Символьная информация Первые компьютеры по сути дела являлись сложными калькуляторами в том смысле, что они могли обрабатывать лишь числовые данные. Даже первые машинные программы были на- писаны в строго числовой форме, т. е. в машинных кодах. Вскоре стало очевидным, что такие программы громоздки, их трудно читать и исправлять. Для решения этой проблемы были созданы символьные коды, которые представляли информацию, являю- щуюся по своей природе символьной, а не числовой. Под симво- лом 1 мы понимаем литеральное представление некоторого эле- мента, выбранного из алфавита (например, ’А’ является символом буквы английского алфавита). Использование символьных дан- ных привело к созданию мнемонических обозначений для опера- ций и адресов, которые, в свою очередь, вызвали разработку язы- ков ассемблера и позднее процедурно-ориентированных языков. Большинство известных компьютеров работает с разнообраз- ными наборами, или алфавитами, символов. Двумя крупней- шими и широко распространенными наборами символов являются EBCDIC (Extended Binary Coded Decimal Interchange Code — расширенный двоично-кодированный десятичный код для обмена информацией) и ASCII (American Standard Code for Informa- tion Interchange — американский стандартный код для обмена информацией). EBCDIC является системой символьного кодиро- вания, первоначально использованной в Системах IBM 360-370. ASCII был разработан в качестве стандартного кода для приме- нения в вычислительной технике и в настоящее время используется во многих других машинах, не относящихся к машинам фирмы 1 Используется также термин «литера». — Прим. ред. 3 Трамбле Ж., Соренсон II. 65
IBM. Эти два метода кодирования обеспечивают описанные ниже наборы символов. Набор символов ASCII. В этот набор входят следующие сим- волы. 1. Латинский алфавит, включающий как малые, так и заглав- ные буквы {а, Ь, с, ..., z, А, В, С, ..., Z}. 2. Десятичные цифры {0, 1, 2, 3, 4, б, 6, 7, 8, 9}. 3. Знаки операций и специальные символы {+, —/, >, <Z, | , пробел (SP), ! , ” , фД $ , %, &, (,),,, I, XJ, t> —. b }•-!• 4. Управляющие символы, такие как DEL (вычеркнуть или стереть), STX (начало текста), ЕТХ (конец текста), АСК (под- тверждение), НТ (горизонтальная табуляция), VT (вертикальная табуляция), LF (перевод строки), CR (возврат каретки), NAK(отри- цание), SYN (синхронизация), ЕТВ (конец блока), FS (разде- литель файла), GS (разделитель группы), RS (разделитель за- писи). Набор символов EBCDIC. В этот набор входят символы, назван- ные в описании набора символов ASCII в п. 1—3, плюс управля- ющие символы, имеющие отличную от приведенных выше в п. 4 мнемонику и имена, но выполняющие те же управляющие функ- ции. Рассмотрим эти символьные наборы с точки зрения выполняе- мых ими функции. Латинские буквы, десятичные цифры и спе- циальные символы можно совместно использовать для формирова- ния английского текста. Тексты иа естественном языке встре- чаются в многочисленных приложениях ЭВМ. Числа, конечно, чаще всего используются в вычислениях. Символы, обозначающие действия, например +, —, ,/, = , обычно применяют в програм- мах для представления на языке программирования таких опе- раций, как сложение, вычитание, умножение, деление и присваи- вание значения. С множеством управляющих символов програм- мисты знакомы менее всего. Они являются сигнальными симво- лами, используемыми при передаче и запоминании информации в системах связи. На рис. 1-4.6 в качестве примера приведен типичный формат, предназначенный для передачи блока данных, содержащего несколько записей. При запоминании информации требуются такие управляющие символы, как FS, GS, RS, которые служат для разделения файлов, групп и записей информации. В гл. 7 будет более под- робно сказано о важности управляющих символов для органи- зации файлов. Функция управляющих символов состоит в струк- турировании информации, а не в обеспечении информацией вы- полняющих задач. Поэтому они играют существенную роль в ма- шинном представлении структур данных, а не в самих структу- рах данных. Для специализированных применений ЭВМ были созданы свои наборы символов. Многие графические системы, построенные 66
Рис. 1-4.6. Блок данных, передаваемый по каналу связи Заголовок сообщения J Текст сообщения Символ начала текста Символ начала заголовка Конец записи, но не конец блока Символы синхрояаации. Перед блоком должно быть но крайней мере два символа 16-битовый код для обнаружения ошибки на базе вычислительных машин, используют символы функцио- нального типа для манипулирования точками и линиями на элек- тронно-лучевой трубке. Использовались, например, специальные символы для обозначения записи, смещения, увеличения или контрастирования изображений на экране. Другим примером специального множества символов является набор знаков, исполь- зуемый в системе программирования APL (A Programming Lan- guage). Язык программирования APL, первоначально разрабо- танный Айверсоном, чревычайно эффективен для работы с масси- t вами. Множество символов в APL включает заглавные буквы I английского алфавита, десятичные цифры, специальные символы, [ содержащиеся в наборах знаков EBCDIC и ASCII, некоторые F греческие буквы /сс, Д, е, т, р, w) и математические символы [ 3* 67
(с, =>, п, и,г> 1_, ±,тд, х, о, со. Для специальных приложений существует много других «не- стандартных» алфавитов, но их рассмотрение выходит за рамки этой книги. В начале данного пункта было указано, что введение символь- ных данных явилось необходимой ступенью в разработке языков программирования высокого уровня. Интересно отметить, однако, что многие первые языки, такие как ФОРТРАН и АЛГОЛ 60, лишь в ограниченной степени -использовали символьные данные, разрешая программистам пояснять литеральным текстом выход- ные результаты. В разработанных позднее языках (СНОБОЛ, ПЛ/1 и АЛГОЛ 68) были предусмотрены символьные строки и операции для их обработки. Строка символов представляет собой некоторое число литеральных знаков, которые объединены с по- мощью операции конкатенации с целью создания структуры данных, более сложной, чем односимвольный элемент (например, четыре буквы D, А, Т, А в результате конкатенации образуют символьную строку DATA). Хотя в качестве простейших структур данных и следовало бы рассмотреть символы, во многих отношениях они слишком при- митивны, чтобы быть полезными для выражения разнообразной нечисловой информации, которая может обрабатываться компью- тером. Тот факт, что разработчики языков типа ПЛ/1 выбрали в ка- честве базового типа данных символьную строку, а не символ, не является чистой случайностью. Причины, лежащие в основе такого решения, будут рассмотрены в гл. 2. 1-4.8. Структуры хранения символьных данных Символ представляется в памяти в виде последователь- ности битов, причем каждому символу приписана отличная от дру- гих последовательность битов. Битовые последовательности фик- сированной длины можно обрабатывать гораздо более эффективно, чем последовательности с переменной длиной. Поэтому обычно разработчики вычислительных машин используют символьные наборы, закодированные с помощью битовых последовательно- стей постоянной длины. В п. 1-1 было показано, что битовая последовательность дли- ной п может быть использована для представления х уникальных объектов, где log2 х *= п. Если рассматриваемые объекты яв- ляются символами, то с помощью битовой последовательности длиной п можно выразить х — 2П различных символов. Таким образом, при п — 6 и п = 8 можно закодировать соответственно 64 и 256 разных знаков. В предыдущем пункте мы познакомились с некоторыми набо- рами символов, предназначенными для использования в ЭВМ. Большинство из них включает десятичные цифры, буквы из алфа- вита естественного языка, знаки пунктуации и арифметических 68
операций. К сожалению, несмотря на значительные усилия в на- правлении стандартизации кодов в вычислительной технике, двоичные коды для представления идентичных или почти иден- тичных наборов символов до сих пор не стандартизованы. Некото- рые из наиболее широко используемых кодов для набора символов ФОРТРАНа приведены в табл. 1-4.11. Мы выбрали именно этот набор, так как язык ФОРТРАН, а следовательно, и его набор символов фиксированы и используются на многих ЭВМ. Ниже перечислены коды, используемые в некоторых вычис- лительных машинах: EBCDIC — Системы IBM 360-370; ASCII—машины семейств Burrougs 5000-6000, PDP-10, PDP-11, Hewlett-Packard 2100; Код центрального процессора UNIVAC — машины Uni- vac 1108; Код машин семейства CDC 6600. Внешний код BCD является шестибитовым кодом, используе- мым для хранения информации на магнитной ленте. Код Холле- рита стал стандартным кодом для представления информации на перфокартах. С обоими этими кодами работают многие вычисли- тельные машины. Несмотря на огромное влияние фирмы IBM на развитие вы- числительной техники, наиболее широко принятым внутренним кодом, т. е. кодом, используемым для представления символьных строк в основной памяти ЭВМ, является ASCII. Этот код исполь- зуется во многих недавно появившихся ЭВМ. Признанные фирмы, такие как IBM, не решаются изменить свои символьные коды, так как в этом случае их новые изделия не будут совместимы с ранее выпущенными. . Выше в этом пункте мы отметили, что, используя восьмиби- товую последовательность, можно представить до 256 различных символов. Хотя код EBCDIC имеет длину 8 битов, стандартный набор символов, с которым работает оборудование 'фирмы IBM, содержит только 105 разных знаков (включая управляющие символы). Таким образом, в большинстве случаев возможности кода EBCDIC используются лишь на 105/256 < 100 % ~ 41 %. Одиако, если для специализированных приложений (например, для задач машинной графики) потребуются дополнительные сим- волы, им можно легко присвоить значения кодов, не занятых в существующей схеме кодирования. Каждый символ кода ASCII занимает 7 битов, при этом для кодирования используются все 128 возможных комбинаций. Так как каждой из них соответствует уникальный символ, ASCII является весьма эффективным методом кодирования. Однако, если для специальных задач потребуется новый знак, свободных кодов в этой схеме найти не удастся. По всей видимости, созда- тели ASCII полагали, что выбранное ими множество символов достаточно универсально для использования в любых задачах. 69
Таблица 1-4.11 Используемые коды множества символов ФОРТРАН а (в восьмеричной системе) Символ Код EBCDIC в Системах 360-370 Код ASCII Код цен- трального процессора UNIVAC Код Системы CDC 6600 Внешний код BCD Код Холлерит* (пробивки на карте) А 301 041 06 01 61 12-1 в 302 042 07 02 62 12-2 с 303 043 10 03 63 12—3 D 304 044 11 04 64 12—4 Е 305 045 12 05 65 12—5 F 306 046 13 06 66 12-6 G 307 047 14 07 67 12—7 Н 310 050 15 10 70 12—8 I 311 051 16 11 71 12—9 J 321 052 17 12 41 11—1 К 322 053 20 13 42 11—2 L 323 054 21 14 43 11—3 М 324 055 22 15 44 11—4 N 325 056 23 16 45 11—5 О 326 057 24 17 46 11—6 Р 327 060 25 20 47 11—7 Q 330 •061 26 21 50 11—8 R 331 062 27 22 51 11—9 S 342 063 30 23 22 0—2 Т 343 064 31 24 23 0—3 и 344 065 32 25 24 0-4 V 345 066 33 26 25 0—5 W 346 067 34 27 26 СР-6 X 347 070 35 30 27 0—7 Y 350 071 36 31 30 0-8 Z 351 072 37 32 31 0-9 0 360 120 60 33 12 0 1 361 121 61 34 01 1 2 362 122 62 35 02 2 3 363 123 63 36 03 3 4 364 124 64 37 04 4 5 365 125 65 40 05 5 6 366 126 66 41 06 6 7 367 127 67 42 07 7 8 370 130 70 43 10 8 9 371 131 71 44 11 9 + 116 ИЗ 42 45 50 12 140 115 41 46 40 11 / 141 117 74 50 21 **0—1 Пробел 100 100 05 55 20 Пропуск ( 115 НО ' 51 51 34 0—8—4 ) 135 111 40 52 74 12—8—4 $ 133 104 47 53 53 11—8—3 176 135 44 54 13 8—3 153 114 56 56 33 0—8—3 113 116 75 57 73 12—8—3 70
Таблица 1-4.12 Пятибнтовый телеграфный код Бодо КеД Знак буквенного регистра Знак цифрового регистра Код буквенного регистра Знак цифрового регистра 11000 А х 11100 Q I 10011 в ? 01010 R 4 OHIO с 10100 S 10010 D $ 00001 Т 5 10000 Е 3 11100 и 7 юно F 1 они V 2 01011 G & 11001 W 00101 Н 10I1I X / 6 01100 I 8 I010I У ною J Звонок 10001 Z >> 11110 К ( 00000 Пробел 01001 L ) II111 Буквенный регистр 00111 М НОИ Цифровой регистр 00110 — N 9 00100 Пропуск 00011 О 00010 Возврат каретки 01101 Р 0 01000 Перевод строки К сожалению, они не учли появление таких истем программи- рования, как APL. Одним из методов расширения набора символов, когда заняты все допустимые значения кода, является использование одного или двух из существующих знаков в качестве символов переклю- чения (или сдвига). Прекрасным примером расширения набора символов является пятиразрядный 32-знаковый телеграфный код, приведенный втабл. 1-4.12. В этом случае диапазон представления знаков расширяют два символа: буквенный регистр и цифровой регистр. Если появляется знак цифрового регистра, следующие за ним коды интерпретируются как верхние символы клавиатуры. Аналогично, коды вслед за знаком буквенного регистра воспри- нимаются как буквы, пока не встретится знак цифрового регистра. Например, текст OIL PRODUCTION (MAY 4) 94, 247 BPD. кодируется в виде б OIL PRODUCTION ц (б MAY ц4) 94, 247 б BPD ц„ [Де б обозначает «буквенный регистр», а ц — «цифровой регистр». Простейшей модификацией этого метода является использова- ние символа переключения для указания на изменение режима. Появление символа переключения свидетельствует о том, что все последующие знаки, вплоть до нового появления символа переключения, соответствуют расширенному набору символов. 71
В этом методе кодирование предыдущего текста выглядело бы так: OIL PRODUCTION с (с MAY с4) 94, 247 с BPD с., где «с» обозначает символ переключения. Методы кодирования с использованием символов переключе- ния широко используют для передачи и временного хранения ин- формации. Если требуется обработка буквенного текста, символы обычно преобразуют в «полный» код, т. е. код, не содержащий сим- волов переключения, например ASCII, более удобный для обра- батывающего устройства ЭВМ. Последними рассматриваемыми здесь задачами, связанными с представлением в памяти символьных данных, являются преоб- разование цифровых данных в символьные и наоборот. Необхо- димость в решении этих задач обусловлена, в частности, тем, что в приложениях достаточно часто встречается текст, содержащий числовую информацию (например дату, адрес, количество зака- занного товара и т. д.). Кроме того, даже в тех задачах, которые по своей природе обычно являются нечисловыми (например, об- работка деловой информации), иногда требуется проводить не- которые вычисления с помощью арифметических операций. Для их выполнения необходимо преобразовывать цифровые символы в их внутреннее представление (в форму с фиксированной или плавающей точкой). В некоторых машинах, например машинах Систем IBM 360-370, это преобразование обеспечивается аппара- турными средствами. В большинстве случаев, однако, перевод символов в числа и чисел в символы должен выполняться про- граммно. В результате анализа существующих методов кодирования символов очевидно, что большинство символьных кодов цифр выбрано так, чтобы преобразования можно было выполнить в уме. Смежным цифрам, например 6 и 7, соответствуют смежные коды [например, (126)8, (127)е в ASCII; (F6)16 и (F7R, в EBCDIC]. Это свойство смежности облегчает преобразование за счет того, что числовое значение данного символа получается при вычитании кода нуля из кода символа. Например, символы 0 и 9 представля- ются в ASCII в виде (120)g и (131)8. Результат вычитания значе- ния кода’ОЧт. е. (120)8 ] из значения кода 9 [т. е. (131)81 равен числу (11)8, которое является правильным представлением числа 9 в восьмеричной системе. Приведем алгоритм преобразования символа в число с фикси- рованной точкой для любого метода кодирования, обладающего свойством смежности. Алгоритм обобщен таким образом, чтобы выполнять преобразования строк цифровых символов, например 4937, в число с фиксированной точкой. Алгоритм СО NVE RT—CH A RACTE R_TO_ N UME R1C. Дана состоящая из последовательности цифровых символов стро- ка, обозначаемая NUMBCHAR. Алгоритм формирует ее эквива- 72
леитное целочисленное представление и присваивает это число переменной NUMBER. NUMBCHAR 11] обозначает i-й слева символ строки NUMBCHAR. Функция DECODE возвращает код символьного аргумента (например, если символ ’О’ закоди- рован в ASCII, то DECODE (’О’) возвратит (120 )8, т. е. эквивалент- ное числовое представление символа). 1. {Инициализация целой величины и константы ZEROREPL Установить NUMBER 0 и ZEROREP ч~ DECODE (’О’). 2. [Организация цикла для обработки числа длиной LENGTH (NUMBCHAR)]. Повторять шаг 3 при i = 1, 2, 3, ...» LENGTH (NUMBCHAR). 3. [Преобразование i-ro символа исходной строки.] Устано- вить NUMBER 10 ?< NUMBER 4- DECODE (NUMBCHAR [i ]) — ZEROREP. 4. [Конец. ] Выход. Задача преобразования положительного числа в последователь- ность цифровых символов является по существу обратной по от- ношению к задаче преобразования, решаемой предыдущим алгоритмом. Ее общее решение описывается следующим алго- ритмом. Алгоритм СО NVE RT_ N UME R1C_ТО-СНА RACTE R. При заданном целом числе NUMBER алгоритм формирует его эквивалентное символьное представление. Функция ENCODE возвращает символьный эквивалент кода, используемого в ка- честве аргумента, т. е. ENCODE (131 )8 в ASCII дает ’9’. MOD — это функция, которая возвращает остаток от деления первого ее аргумента на второй [например, MOD (23,10) равно 3]. 1. [Инициализация.] Установить NUMBCHAR пустая строка. 2. Повторять шаги 3 и 4 до тех пор, пока 'NUMBER =/= 0. 3. [Конкатенация очередного символа цифры с уже сформи- рованной частью символьной строки. ] Установить NUMBCHAR ч- ENCODE (DECODE (’О’) -|- MOD (NUMBER , 10)) О NUMBCHAR\ 4. [Удаление наименьшей значащей десятичной цифры. ] Установить NUMBER —- NUMBER/10. 5. [Конец. ] Выход. В данном пункте мы рассматривали главным образом формы машинного представления символьных данных. Обсуждение сим- вольной информации на этом не закапчивается. В гл. 2 символ будет рассмотрен как часть более сложной структуры данных, а именно символьной строки.- ' Упражнения к п. 1-4.8 1. Дан аргумент, состоящий нз одного символа; функция языка ПЛ/1 UNSPEC возвращает строку битов, являющуюся внутренним представ- лением в памяти этого символа. Например, UNSPEC (’!’) дает *11110001’ В, a UNSPEC (’*’) — ’0ЮП100’ В. Написать на ПЛ/1 программу для преобразо- 73
Вания символа десятичной цифры в ее представление типа FIXED. Иначе говоря, это программа должна преобразовывать символ ’4' в число 4. 2. Написать на ПЛ/1 процедуру преобразования целого числа в символ десятичной цифры (например, числа 4 в символ '4'). 1-4.9. Логическая информация Элемент логических данных является простейшей струк- турой данных, принимающей всего два значения: «истина» или «ложь». В программировании истинное и ложное значения выра- жаются различными способами в зависимости от используемого языка программирования: например, в ФОРТРАНе — .TRUE, и .FALSE., АЛГОЛе—true и false, ПЛ/1 — ’1’В и ’О’В, ЛИСПе—Т и NIL, и т. д. Логические величины имеют либо постоянное значение истинности (например, высказывание «че- тыре делится иа два» всегда истинно, а «пять делится на два» — всегда ложно), либо переменное, зависящее от времени и обстоя- тельств (например, «Джиму 20 лет»). Существуют только две ло- гические постоянные: true (истина) и false (ложь). Логические си- туации, допускающие изменения, могут быть представлены логи- ческими переменными. Однако, чтобы должным образом мотиви- ровать использование логических переменных, следует сначала обсудить логические операции, которые можно применять над логическими данными. Подобно операциям для арифметических величин (т. е. сложению, вычитанию, умножению и т. д.), сущест- вуют операции и для логических данных. Тремя наиболее распро- страненными логическими операциями являются И, ИЛИ и НЕ, обозначаемые обычно в виде Д, у и — соответственно. Если через X и Y обозначить логические переменные, то операции Д, V и — можно определить следующим образом. Результат X Д Y имеет истинное значение только в том слу- чае, если истинны значения обеих переменных X и Y; в противном случае результат ложен. Результат X \/ Y ложен только в том случае, если одновре- менно ложны значения обеих переменных X и Y; в противном случае результат имеет истинное значение. Результат операции —X (заметим, что — является унарным оператором, т. е. оператором, требующим только одного операнда) имеет истинное значение, если значение переменной X ложно, и ложен, если переменная X имеет истинное значение. Табл. 1-4.13 определяет результаты выполнения трех описан- ных логических операций. Выражения с логическими операндами обычно встречаются при использовании операторов отношений <, =, >. Мы знакомы с применением операторов отношений в арифметических операциях. Результатом операций отношений являются логиче- ские значения «истина» и «ложь», что иллюстрируется, например, выражениями типа 1 -4- 5 — 6 (которое истинно) и 5 < 4 (кото- рое ложно). Операторы отношений в ограниченном смысле можно 74
Т а б л и ца 1-4.13 Табличное определение логических операторов Д, V» — X Y X Д Y X у Y -X Т Т Т Т F Т F F Т F| F Т F Г Т F F F F Т Примечание. Т — истина, F — ложь. также использовать с символьными данными и указателями. По- зднее мы еще будем встречаться с примерами операторов отно- шений. Программисты постоянно сталкиваются с логическими зада- чами. При разработке алгоритма наши мысли организованы в форме логических шагов, которые ведут к решению поставленной задачи. Эти логические шаги отражаются в управляющей струк- туре программ, соответствующих нашим алгоритмам. Например, при программировании в учетно-платежной сфере фрагмент задачи может формулироваться так: «Если текущий баланс отрицателен, то вычислить и просуммировать процент издержек». Здесь «те- кущий баланс отрицателен» является логическим условием, и в программе оно может быть записано в виде CURRENT_BAL < 0 (текущий_бал < О». Значение такого условия может быть переменным, так как зависит от операндов, способных изменяться во время выполнения программы. Иногда условия типа CURRENT_BAL < 0, изме- няющиеся во время выполнения программы, удобно представлять с помощью логических переменных. Логические переменные наиболее часто используются длгьщредставлеиия значений слож- ных логических выражений [например, (А < В & С < D) | (В < < A 8r D < С)], которые в течение выполнения программы тре- буют частых повторных вычислений. Логические переменные при- меняют также для представления условий окончания операторов цикла, в которых эти условия зависят от логических выражений (например, группа DO WHILE в языке ПЛ/1). 1-4.10. Структуры хранения логических данных Представление логических величин в машинной памяти зависит от транслятора, т. е. компилятора или интерпретатора, обр абатывающего программу, и машины, для которой этот тран- слятор создан. Наиболее очевидной структурой для хранения логических данных является бит. При этом методе значение 75
истина, обычно представляется «установленным» битом (т. е. битом со значением 1), а ложь — «сброшенным» битом (т. е. би- том со значением 0). Компилятор с языка ПЛ/1 уровня F фирмы IBM использует для хранения логических величин такую одно- битовую структуру. У большинства машин отсутствуют команды для прямого доступа к единственному биту памяти, поэтому выделение значе- ния конкретного элемента логических данных может потребовать выполнения нескольких машинных команд. Следовательно, дол- жен быть выбран другой метод представления логических данных. Один из наиболее неэффективных (с точки зрения расхода па- мяти) способов представления логических данных состоит в за- креплении за логическим элементом целого машинного слова. Например, в компиляторе WATFIV значения .TRUE, и .FALSE, представлены соответственно восемью единичными и восемью нулевыми битами в крайнем левом байте 32-разрядного слова. Хотя такое представление в виде целого слова с точки зрения расхода памяти крайне расточительно, оно облегчает быстрый доступ к логической информации. При этом быстрота достигается за счет того, что машинные команды работают со словами данных, а не с отдельными битами информации. Другие компиляторы (например, компилятор ПЛ/С для машин Систем IBM 360-370) для представления логических ве- личин использует наименьшую адресуемую единицу памяти, т. е. восьмибитовый байт. Такое представление позволяет осуще- ствить быстрый доступ и предъявляет невысокие требования к па- мяти. Этот пункт завершает краткое описание логических данных и способов их машинного представления. Мы вернемся к нему в последующих главах, когда будем описывать более сложные структуры, в частности логические или битовые строки и логи- ческие массивы. 1-4.11. Указатели Указателем (или связкой} называют ссылку на струк- туру данных. Мы можем проиллюстрировать возможное исполь- зование указателя иа примере задачи, часто возникающей при компиляции программ. Предположим, что в программе четыре раза встречается константа 3.1459. Во время компиляции могли бы быть созданы четыре числа 3.1459. Однако более эффективным приемом является использование одной копии числа и трех ука- зателей на эту единственную копию, так как для представления указателя требуется меньше памяти, чем для действительного числа с плавающей точкой (рис. 1-4.7). О машинном представле- нии указателей мы поговорим более подробно в следующем пункте. Вероятно, наиболее важное свойство указателя состоит в том, что, являясь элементом данных фиксированного размера, он 76
Представление В памяти числа 3.1459 Представление В памяти числа 3.1459 Представление В памяти числа 3.1459 Представление В памяти числа 3.1459 Рис. 1-4.7. Многократные ссылки на действительное число позволяет использовать одинаковый способ доступа к любой структуре данных, независимо от ее типа и сложности. В преды- дущем примере три указателя могли ссылаться на константы целого типа, символьные или логические константы, и метод до- ступа к ним один и тот же, т. е. осуществляется с помощью адреса, указывающего на нужный элемент данных. Наконец, важной характеристикой указателей является то, что в некоторых случаях с их помощью можно быстрее включить элементы в некоторую структуру данных и исключить их оттуда.' В последующих главах, особенно в гл. 4, 5 и 7, мы увидим, что это свойство модификации структуры является крайне важным. Необходимо ясно сознавать, что с введением указателей мы располагаем двумя методами доступа к структурам данных. Наиболее знакомый иам метод обычно называется методом вы- числяемого адреса, в котором доступ к структуре данных осуще- ствляется непосредственно по ее адресу, абсолютному или отно- сительному. Этот адрес вычисляется транслятором. .языка (т. е. компилятором, интерпретатором или ассемблером), обрабатыва- ющим исходную программу. Во втором методе, называемом мето- дом указательной, или ссылочной, адресации, адрес нужного эле- мента не вычисляется, а присваивается указателю. Для доступа к элементу структуры данных мы сначала получаем величину указателя. Значение указателя (т. е. адрес) обеспечивает доступ к интересующей нас структуре данных. Доступ к конкретному элементу внутри структуры данных может потребовать некоторых дополнительных вычислений, которые зависят от сложности структуры. Мы подробно разберем этот вопрос, когда познако- мимся с более сложными структурами данных. Так как доступ к любому элементу при этом всегда осуществляется через указа- тель, то вообще говоря, при методе указательной адресации 77
расходуется больше времени, чем при методе вычисляемого адреса. Однако существуют ситуации, в которых гибкость, обеспечивае- мая использованием указателя для косвенной ссылки на элемент данных, вполне оправдывает любые возникающие при этом до- полнительные затраты времени. Разницу между двумя методами адресации можно ясно уви- деть при исследовании предмета, который программисты слишком часто неправильно понимают. Речь идет о передаче параметров в подпрограммах (т. е. процедурах) и функциях. Рассмотрим сле- дующий фрагмент программы: модуль EXAMPLE (X) формальный параметр конец EXAMPLE Y <-4 п вызов EXAMPLE (Y) фактический параметр В этом фрагменте EXAMPLE является именем модуля (т. е. подпрограммы или функции), X — формальным параметром, a Y — фактическим параметром в операторе вызова. Одним из методов передачи параметров, существующим в таких языках программирования, как СНОБОЛ, ЛИСП и АЛГОЛ W, является передача по значению. В этом методе во время выполнения про- граммы значение фактического параметра помещается в опре- деленную ячейку, выделенную для соответствующего формаль- ного параметра (рис. 1-4.8). Всякий раз, когда требуется формальный параметр, доступ к нему осуществляется непосредственно по определенному вы- числяемому адресу. Любое присваивание нового значения фор- мальному параметру воздействует только на его величину, а не на величину фактического параметра. Поэтому, когда в приведен- ном нами фрагменте программы управление возвращается из под- программы оператору, следующему за оператором вызова, ве- личина Y остается такой же, как и до обращения к подпрограмме. Ячейка вля величины У ----ч Ячейка для. величины К Рис. 1-4.8. Механизм вызова по значению 78
Ячейка для Величины У Ячейка для Величины X 3 Рис. 1-4.9. Использование указателя при передаче параметров В двух других методах передачи параметров (передача по имени в АЛГОЛе 60 и АЛГОЛе w и передача по ссылке в ФОРТРАНе и ПЛ/1) для доступа к значениям параметров используют указа- тели. Вместо величины фактического параметра формальному параметру передается указатель на фактический параметр, как это показано на рис. 1-4.9. Поэтому если в модуле EXAMPLE используется величинах,то доступ к соответствующему ей пара- метру Y осуществляется через указатель, связываемый с X. При использовании методов передачи параметров с помощью указателей могут возникнуть некоторые трудности. Если факти- ческий параметр является ие простой переменной, а выражением, например, Y + 4, то вместо присваивания значения соответству- ющему формальному параметру X результат будет присвоеи выражению Y + 4. Такие нелепые присваивания создают трудности при исполь- зовании в ФОРТРАНе передач по ссылкам. Например, если мы обращаемся к модулю EXAMPLE с константой 2 в качестве.фак- тического параметра, то адрес константы 2 присваивается фор- мальному параметру X (рис. 1-4.10). Если параметру X в модуле присваивается значение 4, то при этом число 4 помещается в ячейку памяти, занимаемую константой 2. Пусть теперь после возврата управления оператору, следующему за оператором обращения к модулю EXAMPLE, выполняется оператор Y <- 2 ф 2. Однако к этому времени константа 2 изменила свое значение на 4, и пере- менной Y будет присвоено ошибочное значение 4 + 4, или 8. Поразительно, но факт! В языке ПЛ/1 эта ситуация была устра- нена созданием фиктивной ячейки для вычисляемого выражения, модуль EXAMPLE (X) X <-4 конец EXAMPLE вызов EXAMPLE (2) Y 2+ 2 Рис. 1-4.10. Фрагмент программы, иллюстрирующий опасность, которая существует В ФОРТРАНе при передаче параметров в вьрове по ссылке 79 j
являющегося фактическим параметром. Следовательно, если фор- мальному параметру будет присвоено значение 4, то изменится содержимое фиктивной ячейки, а не константа 2. При передаче параметров в вызове по имени формальному параметру не разрешается присваивать выражение в качестве фактического параметра. Для вычисления значения фактического параметра используется специальная подпрограмма, называемая санк \ Ее адрес присваивается ячейке, содержащей формальный параметр. Таким образом, если потребуется значение формаль- ного параметра, то через указатель, находящийся на месте фор- мального параметра, будет найдена подпрограмма санк и затем с ее помощью вычислено требуемое значение Следовательно, хотя и передача по ссылке, и передача по имени используют адре- сацию с помощью указателей, они существенно различаются. В передаче по ссылке фактический параметр вычисляется только один раз — перед обращением к процедуре. В передаче по имени фактический параметр вычисляется каждый раз, когда встречается ссылка на формальный параметр. У некоторых первых языков программирования, таких как КОБОЛ, ФОРТРАН и АЛГОЛ 60, отсутствуют данные типа указателя. Однако в этих языках действие указателей можно смо- делировать, используя индекс массива. Мы обсудим эту идею в гл. 4. Такие более поздние языки, как ПЛ/1, СНОБОЛ, АЛГОЛ W и АЛГОЛ 68, имеют указатели, которые в явном виде контроли- руются программистом. Преимущества указателей станут более очевидными после того, как в последующих главах мы рассмотрим алгоритмы доступа к связанным структурам данных. 1-4.12. Структуры хранения указателей В предыдущем пункте было отмечено, что указатель является адресом, отсылающим к структуре данных. В большин- стве компьютеров для адреса в памяти отводится слово или полу- слово. Разумеется, чем больше адресов (т. е. чем больше адрес- ное пространство), тем больший объем памяти необходим для представления одного адреса. Адрес, содержащийся в указателе, может быть абсолютной или относительной ссылкой на структуру данных. В первом случае подразумевается, что указатель является абсолютным адресом структуры данных. Относительный указатель содержит значение смещения в области памяти относительно некоторого базового адреса этой области. (Этот базовый адрес часто помещается в спе- циальный регистр, называемый базовым регистром.) Следова- тельно, если значение указателя равно 5 и базовый адрес области данных программы есть 7627, то эффективный абсолютный адрес указателя равен 7627 -J- 5 — 7632. 1 Термин «санк» предложил Ингермаи в 1961 г. — Прим. ред. 80
В ранних моделях ЭВМ существовала только абсолютная адресация. Сейчас, однако, все компьютеры, за исключением очень простых мини-машин, имеют возможность относительной адре- сации. Большинство вычислительных систем допускают, чтобы в памяти находилось одновременно несколько программ. Поэтому если программа выполняется дважды, существует большая ве- роятность того, что ей не будет выделен один и тот же участок памяти. Если используется относительная адресация, программа может перемещаться в памяти просто с помощью изменения зна- чения базы, а не всех указателей, что было бы неизбежно при абсолютной адресации. По этой причине относительная адреса- ция более популярна, чем абсолютная. На уровне машинных команд доступ к данным через указа- тели осуществляется одним из двух способов. Первый способ заключается в следующем. Сначала в аккумулятор, или индекс- ный регистр, загружается значение указателя (т. е. адрес пер- вого элемента структуры данных). Затем для обеспечения доступа к желаемой структуре выполняется команда машинного уровня, операнд которой использует этот индексный регистр. Такое при- менение индексного регистра иллюстрируется следующими ко- мандами машинного уровня (LD означает «загрузить», LDI — «загрузить по индексу»): LD X, R1 —загрузить указатель X в регистр R1; LDI R1, R2— загрузить в регистр R2 содержимое ячейки .памяти, определяемое указателем (т. е. адресом), содержащимся в R1. Второй способ заключается в использовании косвенной адреса- ции. В этом случае для непосредственного получения адреса информации, к которой требуется доступ, используется операнд машинной команды. Рассмотрим, например, такую команду машинного уровня: LD^X,R2 Если содержимое ячейки X есть адрес, например Y, то при выполнении этой команды содержимое ячейки с адресом Y будет загружено в регистр R2. Таким образом, окончательным резуль- татом является непосредственная загрузка в регистр R2 содер- жимого ячейки памяти, на которую указывает Y. Одна команда с косвенной адресацией эквивалентна двум ранее приведенным командам с индексным регистром. Косвенная адресация обеспечивает более быстрый одноразо- вый доступ к структуре данных, чем использование индексных регистров. Однако, если требуются обращения к большому числу последовательных элементов данных, использование индексных регистров предпочтительнее косвенной адресации. Это обуслов- лено тем, что индексный регистр загружается лишь однажды, и для вычисления смещений различных элементов данных его содержимое может быть изменено более быстро, чем косвенный 81
Т а б л и ц а 1-4.14 Возможности адресации в некоторых ЭВМ ЭВМ или семейство ЭВМ Наличие косвенной адресации Число регистров Средства и возможности адресации памяти IBM 360-370 CDC 6600 Burroughs 5500 PDP-10 Univac 1108 PDP-11 Hewlett- Packard 2100 Отсутствует » » Есть » » » 16 индексных и базовых регистров 3 индексных и 8 базовых реги- стров 8 базовых реги- стров 15 индексных ре- гистров, ячейки па- мяти с 1 по 15 так- же могут использо- ваться в качестве индексных регистров 2 базовых и 15 индексных ре- гистров 8 индексных ре- гистров, одни ис- пользуется в каче- стве счетчика команд В качестве реги- стра базы исполь- зуется счетчик команд Используются ин- дексные и базовые ре- гистры. Максимальное смещение относитель- но базы — 4095 байтов Используются ин- дексные и базовые ре- гистры. Максимальное смещение относитель- но базы 262 143 байта Имеется непосред- ственный доступ к 32 768 словам памя- ти. Может использо- ваться базовый ре- гистр с максимальным смещением в 4095 слов Базовые регистры с максимальным сме- щением 8192 и 16 384 слов (в зависи- мости от реализации) Используются базо- вые регистры, индекс- ные регистры и кос- венная адресация. Максимальное смеще- ние относительно базы равно 65 535 словам Используются ин- дексные регистры и косвенная адресация. Диапазон допустимых смещений для индек- сированных операций от —32 768 до -1-32 767 байтов Адресация относи- тельно счетчика команд и косвенная адресация. Макси- мальное смещение 1024 слова адрес (косвенный адрес размещается не в регистре, а в основной памяти). В табл. 1-4.14 приводятся возможности адресации в не- которых существующих ЭВМ. В гл. 4, 5 и 7 указатели играют существенную роль при опи- сании рассматриваемых там связанных структур В ходе их изучения станет очевидна та гибкость, которая присуща указа- телям при доступе, исключении и включении элементов в струк- туры данных. Список литературы 1. Dietmeyer D. L. Logic Design of Digital Systems. Boston: Allyn and Bacon, 1971. 2. Foster С. C. Computer Architecture. New York: Van Nostrand Reinhold Co. 1970. 3. Hartley R. V. L. Transmission of Information. —Bell System Tech. J., Vol. 7, 1928, p. 535. 4. Iverson К. E. A Programming Language. New. York: Wiley, 1962. 5. Колмогоров A. H. Интерполирование и экстраполирование стационар- ных случайных последовательностей. — Известия АН СССР, серия математи- ческая, т. 5, 1941, с. 3—14. 6. Mano М, М. Computer Logic Design. Englewood Cliffs: Prentice-Hall, 1972. 7. Morris C. W. Signs, Language, and Behavior. New York: Prentice-Hall, 1946. 8. Reza F. M. An Introduction to Information Theory. Toronto: McGraw- Hill. 1961. 9. Shannon С. E. and W. Weaver The Mathematical Theory of Communi- cation. Urbana: University of Illinois Press, 1949. 10. Smith C. L. Digital Control of Industrial Processes. — Computing Sur- veys, Vol. 2, No 3, Sept., 1970, pp. 211—242. 11. Stone H. S. Introduction to Computer Organization and Data Structu- res. New York: McGraw-Hill, 1972. 12. Tremblay J. P. and R. P. Manohar. Discrete ‘Mathematical Structu- res with Applications to Computer Science. New York: McGraw-Hill, 1975. 13. Винер H. Кибернетика или управление и связь в животном и машине/ Пер. с англ. М.: Советское радио, 1968. 362 с. 82
Глава 2. ПРЕДСТАВЛЕНИЕ И МАНИПУЛИРОВАНИЕ СТРОКАМИ В предыдущей главе в качестве примитивной структуры данных был определен символ. Отмечалось, что в программах, реализующих нечисловые опера- ции, более полезной структурой данных является не символ как таковой, а строка символов. В этой главе мы увидим, как сформировать строку из символов. Будут рассмотрены две формальные системы обработки строк: алгоритмы Маркова и грамматика с фразовой структурой. В главе обсуждаются операции над стро- ками и сопоставление строк, особенно в связи с языками программирования П.П/1 и СНОБОЛ. Исследуется ряд представлений строк в памяти. И наконец, описывается использование строк для редактирования текстов, лексического анализа, индексирования типа KWIC и информационного поиска. 2-1. ОПРЕДЕЛЕНИЯ И ПОНЯТИЯ В этой главе рассматриваются операции (или мани- пуляции), которые можно проводить над строками. Как н у ариф- метических операций над натуральными числами, у операций над строками существует много любопытных свойств. Обратимся, например, к операции сложения натуральных чисел. Ее можно представить в виде функции двух переменных: f (х, у) = х+ у, где х и у — натуральные числа. Эта хорошо знакомая нам функ- ция обладает некоторыми интересными свойствами. Во-первых, сумма любых двух натуральных чисел является натуральным числом. Это свойство называется замыканием. Замыкание — необ- ходимый атрибут системы (т. е. совокупности некоторого мно- жества и операции над этим множеством), называемой алгеброй, или алгебраической системой. Во-вторых (х + у) + z = х + + (у + z) = х + у -|- z, когда х, у и z есть натуральные числа. В соответствии с этим операция сложения ассоциативна. В-третьих, существует число г, такое, что для любого натурального числа х справедливо х + i = х. Это число i является нулем и называется нейтральным элементом аддитивной системы. Имеется много дру- гих важных свойств, в частности дистрибутивность и коммутатив- ность, которые присущи арифметическим операциям (таким как сложение и умножение) иад множеством натуральных чисел. Обсуждение строк начнем с формального определения строки. С этой целью введем понятие алфавита и операцию конкатенации. 84
Вообще говоря, алфавитом V называют конечное непустое мно- жество символов. Знакомым примером алфавита является мно- жество V = {а, Ь, с, z}, а (сс, (3, у, е} —четырехсимвольный алфавит, который представляет собой подалфавит греческого алфавита. Говорят, что конкатенация двух символов алфавита, напри- мер, символов ’а’ и ’Ь’, образует последовательность символов, а именно ’ab’. (Отметим, что в дальнейшем при ссылке на символ из алфавита или на последовательность таких символов они будут заключаться в апострофы.) Операция конкатенации применима также и к последовательностям символов. Например, в резуль- тате конкатенации ’ab’ и ’ab’ получится ’abab’. Оператрр конка- тенации будет обозначать специальным знаком О- Это даст воз- можность записывать выражения, такие как ’ab’ О ’а’, которое эквивалентно последовательности ’aba’ Строка 1 над алфавитом V есть 1) символ из алфавита V или 2) последовательность символов, полученная в результате конкате- нации символов из алфавита V. Примерами строк над алфавитом V — {1, 2, 3} являются ’Г, ’ЗГ, ’3332Г и ’222’. Пусть V О V = V2 означает множество всех строк длины 2 над V, VqVOV=V2OV = V3 — множество строек длины 3 над V, и вообще V Q V Q ... Q V = Vn — множество строк длины и над V. Тогда транзитивное замыкание алфавита V, обозначаемое через V+, определится как v+= vu v2u v3u • • • Добавим к этому множеству для полноты специальную строку Л, называемую пустой (или нулевой) строкой, и получим рефлек- сивное замыкание V* алфавита V, т. е. V* = A U V[j V2 и V3J • • • :—AU V+. Строка Л имеет свойство единицы (т. е. х О Л — A Q к = х для любой строки х, входящей в множество V*) и называется единичным элементом в алгебре, образованной миожеством V* и операцией конкатеиации. Другим свойством этой алгебры яв- ляется ассоциативиость [т. е. (х О у) О z — х О (у О z) — = х О у О z для х, у, z С V*]. В качестве примера рассмотрим миожество строк V*, которое может быть порождено из алфавита V — {а, Ь|. Некоторые под- множества множества V* таковы: V2 = {’аа*, ’ab’, ’ba’, ’bb’}, V3 = faaa’, ’aab’, ’aba’, ’abb’, ’baa’, ’bab’, ’bba’, ’bbb’}, V4 = j’aaaa’, ’aaab’, ’aaba’, ’aabb’, ’abaa’, ’abab’, ’abba’, ’abbb’, ’baaa’, ’baab’, ’baba’, ’babb’, ’bbaa’, ’bbab’, ’bbba’, ’bbbb’}, 1 Употребляется также термин «цепочка». — Прим. ред. 85
Далее мы будем неоднократно ссылаться на -замыкание V* того или другого алфавита. Для иллюстрации еще одного примера строки обратимся к языку программирования ФОРТРАН. Алфавит ФОРТРАНа состоит из 26 букв, 10 цифр и набора специальных зиаков, таких как ’’ и т- Д- При написании про- граммы на ФОРТРАНе используются только эти символы. Следо- вательно, программу можно рассматривать как конкатенацию символов над алфавитом языка программирования для получения строки с произвольной длиной. В п. 2-5.2 мы увидим, что кон- троль допустимого набора символов в программе выполняет ком- пилятор ₽ фазе сканирования. 2.2. ФОРМАЛЬНЫЕ СИСТЕМЫ ОБРАБОТКИ СТРОК В этом параграфе мы опишем две системы для представ- ления и манипулирования символьными строками. При этом мы преследуем две цели: подкрепить понятия, введенные в п. 2-1, и, что более важно, дать понимание примитивных операций, которые могут выполняться над строками. Первая система, или модель, называемая алгоритмами Маркова, основана на опера- ции типа подстановки, которая может быть применена к любой входной строке из данного алфавита. Типы манипуляций над строками в рамках этой модели фактически иеограиичеиы. Во второй модели, называемой грамматикой, нас интересуют не столько манипуляции строками, сколько порождение или распо- знавание определениого подмножества из множества строк, полу- ченных при заданном алфавите. Обе модели представляют прак- тическую ценность, что подтверждается рядом примеров, приве- денных в последующих параграфах этой главы. 2-2.1, Алгоритмы Маркова Первую формальную систему манипулирования стро- ками, которую мы исследуем, представляют нормальные алго- ритмы Маркова (предложены советским математиком А. А. Мар- ковым 11]). Будучи формальной математической системой, алго- ритмы Маркова послужили основой для первого языка обра- ботки строк COMIT. Кроме того, существует удивительное сход- ство между марковской моделью и широко используемым языком программирования СНОБОЛ (SNOBOL), появившимся после языка COMIT. Ниже, в п. 2-3.3, мы рассмотрим некоторые из функций манипулирования строками в СНОБОЛе, откуда станет ясно, что структуры данных и управление, присущие марковским алгоритмам, очень точно моделируются этим языком. Общая стратегия работы марковского алгоритма состоит в том, чтобы, подвергнув некоторую строку х ряду операций (продукций), преобразовать ее в выходную строку у. Этот про- 86
цесс преобразования является обычным в таких областях приме- нения ЭВМ, как редактирование текста или компиляция про- граммы. В частности, компиляцию можно представить как про- цесс преобразования строк исходного языка (такого как ПЛ/1) в строки объектного кода (например, языка ассемблера или ма- шинного кода). Единственной структурой данных, с которой оперирует мар- ковский алгоритм, служит входная строка х, но она может уве- личиваться или уменьшаться по длине по мере выполнения пре- образований. И исходная строка х, и преобразованная выходная строка у состоят из символов некоторого алфавита V. Следова- тельно, строки хну принадлежат множеству V*. Простой (марковской) продукцией1 называется запись вида и w, где ни w — строки в V*, причем алфавит V не содержит символов и В выражении для продукции величина и называется антецедентом, a w — консекеентом. Продукция с антецедентом и и коисеквентом w применима к строке z б V*, если существует хотя бы одно вхождение и в z. В противном случае продукция неприменима к строке z. Если продукция применима, то первое (т. е. самое левое) вхождение и в z заменяется на w. Например, если продукция ’Ьа’ ’с’ применена к входной строке ’ababab, то в результате получится строка ’acbab’. В то же время продукция ’baa’ ’с’ к строке ’ababab’ непри- менима. Марковский алгоритм содержит упорядоченное множество2 продукций Рх, Р2, ..., Рп. Последовательность выполнения алго- ритма зависит от того, применима или неприменима к строке очередная продукция. Выполнение начинается с проверки пер- вой продукции. Если она применима к строке, то строка преобра- зуется в соответствии с продукцией. Если же продукция непри- менима (т. е. в строке не найдена подстрока, которую можно было бы заменить), то осуществляется переход к проверке следующей продукции. Марковский алгоритм завершается в одном из двух случаев. 1. К строке неприменима ии одна из имеющихся продукций. 2. К строке применима терминальная продукция Терминальная продукция—это запись вида х-*• у., где х И у — строки в V*, а символ следует сразу за консеквеитом 3. Пример 2-1. Рассмотрим алгоритм Маркова с продукциями Рх : ’ab’->- ’Ь’ Р2 : 'ас’-4-’с' 1 Вместо термина «продукция» А. А. Марков использует термин «формула Подстановки». — Прим. ред. 8 Упорядоченное множество продукций называется также схемой алго- ритма. — Прим. ред. 3 А. А. Марков записывает терминальную продукцию в виде х-> -у — Прим. ред.
над алфавитом V (а, Ь, с). Этот алгоритм вычеркивает все вхождения сим- вола 'а’ в строке за исключением случая, когда символ ’а’ находится в конце строки. Проследим работу алгоритма, если входная строка имеет вид ’bacaabaa’. Далее символ "=»” используется для указания на результат преобразования, а заменяемая подстрока подчеркивается. ’bacaabaa’ =>’bacabaa’ (Рг)* ’bacabaa’ =>’bacbaa’ ’bacbaa’=> ’bcbaa’ (P.) (P2) ’bcbaa’ => ’bcba’ Поскольку никакая из имеющихся продукций, в той числе и последняя продукция Р3, к строке 'bcba’ неприменима, то алгоритм на этом завершается. Теперь, используя нашу систему записи алгоритмов, мы мо- жем более формально описать выполнение марковского алгоритма. Алгоритм MARKOV. Задан марковский алгоритм с коиечиой последовательностью продукций Р2, Р2, Рп, которые требуется проверить для входной строки z0 £ V*. Переменные i и j служат индексами, a Zj означает результирующую строку после Его преобразования входной строки z0. 1. [Инициализация переменной 1, т. е. индекса текущей строки.] Установить i <— 0. 2. [Инициализация переменной j, т. е. индекса продукции. ] Установить j <— 1. 3. [Определить цикл. ] Повторять шаг 4 до тех пор, пока j п. 4. [Проверка применимости продукции или достижения тер- минальной продукции. ] Если продукция Pj применима, то (при- менить продукцию Pj и установить i <— i 4- 1 для новой строки zg если Pj — термииальиая продукция, то печатать строку и за- кончить выполнение алгоритма; -в противном случае установить j I); в противном случае установить j «—j -Ь 1. 5. [Завершение ] Напечатать строку z, и закончить выполне- ние алгоритма. В примере 2-1 продукции Рх: ’ab’ -* ’а’, Р2 : ’ас’ ’с’ и Р3 : ’аа’ -> ’а’ можно выразить одной обобщенной продукцией ’а’х -> х (х Е V) с помощью переменной х, используемой в работе алгоритма. В этой продукции переменная, х играет такую же роль, что и переменная в программе (т. е. х может принимать любое значение из определенного множества, в данном случае — множе- ства {а, Ь, с}). Формальное описание марковского алгоритма * Здесь и в последующих примерах преобразования строк в круглых скобках указаны примененные продукции. — Прим. ред. 88
для вычеркивания всех вхождений символа ’а’, кроме символа в конце строки, получает простой вид: МА : DELETE, А (х С V) Рх : ’а’х -*• х Заметьте, что в это описание включен заголовок МА (Markov Algorithm — марковский алгоритм) и описаиие переменной х с указанием множества ее значений V. Пример 2-2. Пусть V — некоторый алфавит и у — символ из этого алфавита. Запишем марковский алгоритм, преобразующий любую строку z £ V* 1) в строку yz и 2) в строку zy. Первая задача имеет следующее решение: МА : YZ (у £ V) Рх : Л-*- у. Значит, для любой входной строки z символ у присоединяется к началу строки, и алгоритм YZ на этом завершается. Вторая задача ие так проста. Поскольку первое вхождение символа Л в строке z встречается слева от .первого символа в z, то продукция Л—>- у. в дан- ном случае непригодна. Алгоритм с другой продукцией zy. также не решает поставленной задачи, так как z — произвольная строка множества V*, а пере- менные в алгоритме Маркова могут принимать лишь однобуквенные значения Чтобы алгоритм мог работать при любой возможной строке z, он должен содер- жать бесконечное число продукции х. По условию продукция х—> у всегда применяется к первому встретивше- муся вхождению х в строке z. Трудность возникает в том случае, когда требуется применить продукцию ко второму, третьему, четвертому или последнему вхож- дению х в z. Для ее преодоления введем набор вспомогательных символов, кото- рые будем использовать в качестве маркеров. Маркеры позволяют отметить определенную позицию в строке, давая тем самым возможность применить к этой позиции определенную продукцию. Возвращаясь ко второй задаче рассматриваемого примера, допустим, что V — алфавит, а а — маркер, причем а V. С использованием маркера преоб- разование любой строки z в строку zy. будет осуществляться следующим мар- ковским алгоритмом: МА: ZY (х, у £ V) Рх : ах —ха Р2: а-> у. Р3 : Л-*-а Поскольку входная строка z вначале не содержит вспомогательного сим- вола а, то продукция Р3, примененная к z, дает строку az. Последующее повтор- ное применение продукции Рх приводит к перемещению символа а в позицию справа за последним символом в z. Если строка z содержит m символов, то после m-кратиого применения продукции Рх получится строка za. К этой строке при- менима уже не продукция Рх, а терминальная продукция Р2, в результате чего получается строка zy. Рассмотрим еще один пример с использованием маркеров. Пример 2-3. Дана строка z £ V*, такая, что z = zxz2z3 ... zn_xzn Требуется сформировать выходную строку z', являющуюся обратной по отношению к строкеz 1 По одной продукции для каждой возможной строки. — Прим. ред. 89
(т. е. z* = znzn_i ... ZsZgZi). В качестве Маркеров будем использовать сим- волы а и р. MA:REVERSE (x,y£V) Pi: аа—> Р Ps: ₽а-> р Р8 : рх-> хр Р4: 0->Л.’ Р6 : аху—> уах Рв : Л->а] В начале работы алгоритма символ а помещается в начало строки z в резуль- тате применения продукции Ре. Затем последовательное применение продукции Р5 приводит к перемещению символа а в позицию за последним символом строки z или в позицию, предшествующую другому символу а. Процесс применения про- дукций Рв и Р6 продолжается до тех пор, пока в начале строки не окажутся два символа а. После этого маркер Р используется для вычеркивания всех символов а. Это осуществляется применением продукций Рх, Р2 и Р3. Вслед за удалением всех символов а применяется продукция Р4, в результате чего будут вычерк- нуты символы Р, и выполнение алгоритма закончится Пусть для этого примера строка z== ’abc*. Тогда последовательность пре- образований данной строки алгоритмом REVERSE будет следующей: ’abc’ =>cc’abc’ (Рв) => ’Ь'а’ас’ (Р5) =► ’Ьс’а’а’ (Рв) =j-a’bc’a’a’ (Р6) => ’с’а’Ь’а’а’ (Р6) а’с’сс’Ь’а’а’ (Р6) аа’с’а’Ь’а'а’ (Р6) => P’c’a’b’a’a’ (Р,) => ’с’Ра’Ь’а’а’ (Р3) =ф- ’с’р’Ь’а’а’ (Р2) => ’cb’pa’a’ (Р3) => ’cb’P’a’ (Р8) => ’cba’P (Р3) => ’cba’ (Р4) Из рассмотренных примеров видно, что порядок следования продукций полностью определяет последовательность выполнения марковского алгоритма. Если в примере 2-3 расположить продук- цию Рс (т. е. Л —> а) на месте продукции Ръ то алгоритм REVERSE генерировал бы бескоиечную Последовательность сим- волов а в начале входной строки и никогда бы не завершился. Будучи простой, структура управления в алгоритмах Маркова фактически не позволяет гибко изменять последовательность вы- полнения продукций. В качестве альтернативы предложенной марковской модели введем другую модель, в которой продукции могут иметь метки, что дает возможность более гибко управлять порядком выполне- но
иия продукций. Введение меток изменяет исходную марковскую модель в двух аспектах. 1. Если продукция применима и имеет метку перехода, то сле- дующей должна проверяться продукция, указанная этой меткой х. 2. Если продукция неприменима или не содержит метки пере- хода, то проверяется следующая за ней продукция (если таковая имеется) или алгоритм завершается (если продукция была послед- ней). Пример 2-4. Алгоритм REVERSE из примера 2-3 перепишем в форме поме- ченного марковского алгоритма, обозначаемого далее LMA*. LMA: REVERSE (х, у £ V) Px: axy—>yax (Pl) P2: ccxp—> Px (PJ P3: ax—>-Px (Pe) P4; ap->- Л. P5: aa—> Л. P«: AJ—. a (Pl) Последовательность выполнения этого алгоритма для входной строки 'abc* такова: Промеэюуточная Примененная Число продукций, строка продукция проверенных на применимость ’abc’ =><z’abc’ Ре 6 => ’b’a’ac’ 1 => ’bc’a’a’ Pi 1 => ’bc’p’a’ ps 3 a’bc’P’a’ Pe 1 ’c’a’b’p’a’ Pi 1 => c’p’ba’ p2 2 =>-a’c’P’ba’ p. 3 => P’cba’ p, 2 > ctP’cba’ p. 3 -> ’cba’ p« 4 Всего 27 (Отметим, что продукция Р5 применима лишь тогда, когдаЕстрока z вначале имеет вид Л..) Для входной строки z = 'abc' потребовалось выполнить 27 проверок при- менимости продукций, в то время как в примере 2-3 — 57 проверок. (Под числом проверок применимости продукций подразумевается число попыток применить продукцию к строке в ходе выполнения алгоритма). Введение меток не только обеспечивает гибкость в разработке алгоритма, но и способствует более эффек- 1 Метка перехода записывается в скобках справа от продукции. — Прим, пер. * LMA — Labelled Markov Algorithm (помеченный марковский алгоритм).— Прим. пер.
тивпому его выполнению. При этом подходе можно, в частности, получить более удачные алгоритмы для реверсирования строки. Один из них приводится ниже. LMA: REVERSE1 (х, у £ V) Рг; сих —> Л (Р4) Р2: аху-»-уах (Р2) Р8: Л-*-a (PJ Р4: *а-»- Л (Р4) По сравнению с предыдущим алгоритмом, этот алгоритм содержит на две про- дукции меньше, всего одни маркер и реверсирует входную строку 'abc', выпол- няя всего лишь 18 проверок применимости продукций. Пример 2-5. Рассмотрим еще один пример помеченного марковского алго- ритма. Пусть требуется вычеркнуть из строки первый встретившийся символ ’а* вместе с предшествующими ему другими символами. Если слева от символа ’а‘ есть другие символы, то после вычеркивания в начало оставшейся строки надо поместить символ ’с’, а в противном случае — символ 'сГ. Для написания такого алгоритма потребуется вспомогательный символ а, отмечающий первое вхождение символа ’а’ во входной строке. После включения маркера в строку остается удалить все предшествующие ему символы и поместить символ 'с' или ’d’ в начале результирующей подстроки. Поставленную задачу решает каждый из двух£алгоритмов, приведенных в табл. 2-2.1, Таблица 2-2.1 Алгоритмы к примеру 2-5 LMA: LEFTSUBSTR1 (х О') Pp х’а’ -> а Р2: ха а (Р2) Р8: а ’с’. Р4: ’а’ -> ’(Г LMA: LEFT SUBST R2 (х £V) х’а*-> а DELETE: ха->а (DELETE) а-> ’с’. ’а’ -*•’d’ Отметим, что во втором алгоритме в табл. 2-2.1 мы не стали помечать все продукции. Метку перехода имеет лишь та продукция, для которой это необ- ходимо. Кроме того, для метки выбрано более содержательное имя. Такая форма записи помеченных марковских алгоритмов будет применяться и далее. Может возникнуть вопрос о том, не изменяет ли введение меток вычислительную силу марковской модели. Гэллер и Перлис [21, у которых мы позаимствовали рассматриваемый аппарат меток, конструктивно доказали, что обе модели МА и LMA эквивалентны по своей вычислительной силе (т. е. любая функция, выполнимая с использованием модели МА, выполнима также моделью LMA, и наоборот). «Конструктивное доказательство» в данном случае означает, что авторы описали алгоритм Маркова, который, при- нимая в качестве входной строки помеченный марковский алго- ритм (LMA), преобразует его в эквивалентный алгоритм МА. Очевидно, каждый алгоритм МА можно преобразовать в ал- горитм LMA простым добавлением метки Рх к каждой нетерминаль- ной продукции. Следовательно, метки удобны для написания бо- лее эффективных алгоритмов, но ие увеличивают вычислительную силу алгоритмов. 92
Прежде чем закончить этот параграф, следует обсудить два важных вопроса: понятие подалгоритма и определение множест- венных входных параметров. Подалгоритм в марковской модели выполняет роль, аналогичную роли процедуры или подпрограммы в языке программирования высокого уровня, т. е. он предназна- чается для уменьшения числа продукций (или правил), необхо- димых для выражения алгоритмического процесса, который со- держит ряд идентичных участков. Это назначение подалгоритма, а также способ обращения к подалгоритмам в марковской модели иллюстрирует пример 2-6. Пример 2-6. Допустим, что первым символом входной строки z £ V* может быть 'а’, 'Ь' или 'с’. Если первый символ есть 'а', то результатом преобразова- ния должна быть двойная копия входной строки. Если на первом месте стоит символ 'Ь’, то выходная строка должна состоять из четырех копий входной строки. В случае, когда в начале входной строки находится символ 'с', резуль- тат должен содержать восемь копий входной строки. Входная строка z должна остаться без изменений, если z — Л или первый ее символ отличен от ’а’, ’Ь’ или ’с'. Очевидно, что для этого примера желательно было бы иметь преобразование, которое обеспечивало бы получение двойной копии заданной строки. Описание соответствующего такому преобразованию помеченного марковского алгоритма и последовательность его работы для входной строки ’abb' приводятся ниже: LMA: DUPL (х, у е V) Л a DOUBLE: ах->х₽ха (DOUBLE) POSITION: Рху—>-уРх (POSITION) REMOVEP: 0->A (REMOVEP) a Л Для входной строки 'abb’ будут выполнены следующие преобразования: 'abb' => a'abb' => 'a’P’a'a’bb' (DOUBLE) =>’a’P’ab'p'b'a'b’ (DOUBLE) ‘ ‘ (DOUBLE) (POSITION) (POSITION) (POSITION) -'abba’p’b’P'b’a (REMOVEp) -'abbab'P'b'a (REMOVEP) -’abbabb’a (REMOVEP) -'abbabb' (Применена первая продукция) ’-’a’P'ab'P’bb'P’b’a ’ab'P'a’P’bb’P’b’a -’ab’P’ab’P’b’P’b'a 'abb' P' a’P’ b' P' b’cc (Применена последняя продукция) Отметим, что в алгоритме DUPL потребовалось два вспомогательных сим- вола: a — для удваивания символа и Р — для восстановления в каждой копии такой же последовательности символов, как и в исходной строке. Для исходной задачи, сформулированной в этом примере, помеченный мар- ковский алгоритм может быть записан в следующем виде: LMA: EG2—6 A -> a ct’a’ ’a’ a’b’ -> ’b’ (2TIMES) (4TIMES) 93
а’с’ - а ►л. (8TIMES) 8T1MES : А- (DUPL) 4TIMES : л- >л (DUPL) 2TIMES: л- >л (DUPL) Обращение к подалгоритму DUPL осуществляется по его имени, записывае- мому в качестве метки перехода в соответствующих продукциях. После завер- шения работы подалгоритма управление передается обратно вызвавшему алго- ритму на ту продукцию, которая следует за продукцией, послужившей причиной Для обращения к подалгоритму. До сих пор рассматривались марковские алгоритмы, которые оперировали с единствеиной входной, или осиовиой, строкой. Допустим теперь, что потребовался алгоритм, который, например, вычеркивает из строкиТ/w ‘"подстроку и, если такая подстрока имеется. В этом случае существуют два входных параметра и и w. Такую задачу можно решить, образовав одну входную стро- ку z, составленную из w и и. Пусть при этом строка z содержит некоторый символ, скажем ’Д:’, который входит в алфавит V, но отсутствует в строках w или и. Этот символ может быть исполь- зован как разделитель в сцеплении строк w и и при образовании строки z. Следовательно, z — w’#’u. Отыскание подстроки и в строке w можио начать с выполнения функции DUPL (описанной в примере 2-6) для получения основной строки вида zz. Затем слева направо сканируется первая строка z, чтобы выделить подстроку и в w, причем прочие символы вычерки- ваются. Одновременно с этим результат сканирования первой w с помощью маркеров отображается во второй подстроке w без вычеркивания символов. После того как первая подстрока и выделена (если она имеется в w), вычеркивается соответствующая ей подстрока во второй w. Теперь остается удалить символы ча- стично разрушенной первой подстроки z, вторую подстроку и и получить правильный результат. Написание алгоритма типа LMA для выделения подстроки является длительным и утомительным делом. Чтобы облегчить эту работу и не уменьшить при этом вычислительную силу марковского алгоритма, можно ввести еще один программный атрибут, который называется ячейкой памяти, или адресуемой ячейкой хранения. Такие ячейки памяти предназначены для хранения строк, ис- пользуемых только в промежуточных операциях. Имеются одна входная строка и выходная строка, представляющая собой основ- ную строку. В алгоритме можио использовать конечное миожество ячеек памяти, обозначаемых [Мх], (М21, [Мп]. Гэллер и Пер- лис [1] детально рассмотрели концепцию ячеек памяти. Можно показать, что применение этой концепции упрощает составление марковского алгоритма, однако не дает никаких дополнительных преимуществ с вычислительной точки зрения. 94 Кроме функций обработки строк, марковские алгоритмы можно с некоторыми допущениями применять и для арифметических вычислений. Например, если любое целое число N мы представим в виде строки одинаковых значков (скажем, 111 ... 1), то сложение любых двух чисел может быть реализовано следующим алгоритмом LMA: LMA : ADD ’ 4- ’->Д При этом предполагается, что входная строка имеет вид ’11 ... 1 4- + 11 ... 1’. Но для выполнения умножения (если входная строка есть ’11 ... 1*11 ... 1’) алгоритм LMA получается более сложным: LMA : MULT ADD? : ’1*’ — ’ *’? (CREATEp) REMOVE1 : ’*1’ -> ’*’ (REMOVE1) REPLACE^ : p - 1 (REPLACEp) * —>A. CREATEp : y’l’ ’1’py (CREATEp) MOVEp : P’l’ ’1’P (MOVEp) у A (ADDy) В этом алгоритме символ у отмечает число сложений множимого, т. е. число символов единицы после знака * в исходной строке (иапример, ’11’ в строке ’111 * 11’). Символ р представляет цифру в произведеиии, т. е. в результирующей строке. Ниже приводится фрагмент последовательности умножения' для входной строки ’111*11’: ’111*11' =>’ll*’y’ll’=>’11*1’Рт’1’=> => ’11*1’р’1’рт^’11*11’ррт => ’11*11’рр^ ’1*’у’11’рр => ... ^> ’*1Гррурррр => ’*11’рррррр => М’рррррр => ’*’рррррр^ • • • => ’*11111’Р => ’*111111’ => ’ШШ’ Нетрудно понять, что вычисление арифметического выраже- ния, такого как 25 * 26/(31 + 10), с помощью марковских алго- ритмов окажется утомительным, но возможным. Марковская мо- дель хотя и кажется ограниченной, достаточио эффективна для обработки любой вычислимой функции (т. е. любой функции, зна- чение которой можно вычислить). Однако марковская модель не- удобна, особенно при арифметических вычислениях. Тем ие меиее марковские алгоритмы дают возможность показать манипулиро- вание строками иа элементарном и понятием уровне. Как увидим в п. 2-3.3, они служат основой весьма мощного языка обработки строк СНОБОЛ. 95
2-2.2. Грамматики В п. 2-1 было введено понятие замыкания V*, в соответ- ствии с которым строка z входит в V*, если каждый ее символ при- надлежит алфавиту V- Например, ’1001Г есть элемент множества V*, если V = {0, 1|. Очень часто нас интересует лишь определенное подмножество строк в V*. Например, для алфавита V = {0, 1} может потребо- ваться рассмотрение только таких строк, которые содержат одиу или более символьных последовательностей, каждая из которых начинается с символа ’О’, за которым следует один или несколько символов ’Г. Обычно строка, содержащая один или несколько символов ’1’, обозначается в виде ’1+’ Следовательно, в нашем примере интерес могут представлять строки вида ’01+01+ ... 01+’ или (’01+’)+. Обозначим это особое подмножество строк через L. Тогда *011010111’ £ L, а ’0100Г L, так как последняя строка содержит смежные символы ’О’. Обычно миожество строк L может быть использовано в тех ситуациях, для которых характерна передача двоично-кодиро- ваниой информации от некоторого источника к приемнику. Коли- чество символов ’Г в определенной последовательности (которая ограничивается нулями) может представлять значение передан- ного числа. Например, последовательность ’011010111’ можно интерпретировать как число 213. Из гл. 1 понятно, что такая схема кодирования не очень эффективна. Однако приведенный пример достаточен для иллюстрации того, что в некоторых случаях ин- терес представляют ие все строки, полученные из заданного ал- фавита, а лишь то или другое их подмножество. Такое подмно- жество строк часто называют языком. Более формально опреде- лим язык L как множество строк над некоторым конечным алфа- витом V, такое, что Ley*. И естественный язык, такой как английский, и язык програм- мирования, иапример ПЛ/1, удовлетворяют приведенному опре- делению языка. В обоих случаях, язык задается в виде определен- ного подмиожества множества строк V* из алфавита, такого как V = {А, В, С, .... Z, 1,2, ..., 9, 0, $, £ , ! и другие специаль- ные знаки}. Строки, образующие язык, называют предложе- ниями. Сразу возникает вопрос: «Как мы можем представить язык?» Мы могли бы сделать это в виде словесного описания строк, ко- торые нас интересуют, но такое представление не является ни точным, ни кратким. Механизм описания языка должен обеспе- чивать возможность представления бесконечного множества строк, поскольку некоторые языки содержат бесконечное количество предложений (например, множество строк вида (’01+’)+). Такой механизм должен быть пригоден для выявления структурных взаимоотношений (или синтаксиса), существующих между под- 96
строками, которые при должном соединении друг с другом обра- зуют предложения языка. Например, строка ’I DATA STRUCTURES’ синтаксически неправильна, так как в ней отсутствует сказуемое, в то время как предложение ’1 LOVE DATA STRUCTURES’ имеет очевидный смысл Ч Отметим, что и в естественных языках, и в языках программирования требуется учитывать два уровня структурных взаимоотношений, а именно уровень слов (т. е. под- строк, которые образуют слова, составляющие словарь данного языка) и уровень предложений (т. е. последовательностей под- строк, которые при соединении формируют предложения языка), к этому вопросу мы вернемся еще раз в данном же параграфе и в п. 2-5.2 при обсуждении лексического анализа. Метод определения, учитывающий эти синтаксические свой- ства языка, является грамматикой. Грамматика состоит из конеч- ного множества правил подстановки, или продукций. Граммати- ческая продукция и продукция в марковских алгоритмах (рас- смотренных в п. 2-2.1) полностью аналогичны по своей функции, т. е. функции описателя подстановки. Однако они различны по своей цели, поскольку грамматическая продукция применяется для порождения строк (т. е. предложений), содержащихся в язы- ках, в то время как марковская продукция используется при манипулировании заданной основной строкой. Прежде чем дать более формальное определение грамматики, обсудим метаязык для выражения грамматики. Метаязык — это язык, который используется для описания другого языка. Напри- мер, английский язык можно считать метаязыком, если с его по- мощью изучается французский язык. В вычислительной технике для представления синтаксиса языков программирования разра- ботано три таких метаязыка: БНФ, АЛГОЛ 68 и Венский метод формального описания языков 2. Метаязык БНФ (означает Бэ- куса-Наура форма или Бэкусова нормальная форма) стал популяр- ным после того, как был применен для описания синтаксиса языка АЛГОЛ 60 [3]. Он используется по-прежнему широко, и именно его мы будем применять в этой книге. 1 Соответствующие варианты строк иа русском языке: ’Я СТРУКТУРЫ ДАННЫХ’ и ’Я ЛЮБЛЮ СТРУКТУРЫ ДАННЫХ’. — Прим. пер. 2 См. Оллонгрен А. Определение языков программирования интерпрети- рующими автоматами: Пер. с англ./Под ред. В. Ш. Кауфмана. М.: Мир, 1977.— Прим. ред. 4 Трамбле Ж.. Соренсон П. 97
Рассмотрение метаязыка БНФ начнем с примера, иллюстри- рующего правило (или продукцию) БНФ для описания цифры. (цифра) : : = 0| 112| 3 ] 4 |Ъ | 61.7] 81 9 Для того чтобы ие смешивать символы метаязыка с символами са- мого языка, применяют четыре специальных металингвистиче- ских символа (,), которые не содержатся в алфавите языка. Грамматика языка записывается в виде множества продук- ций, каждая из которых имеет левую часть, за которой следует метасимвол : : — , а за ним — список, образующий правую часть. Левой частью является всегда нетерминальный символ, или нетерминал (например, (цифра)), который обозначается пере- менной, представляющей синтаксический класс в грамматике. Нетерминалы всегда заключаются в металингвистические скобки (и). Элементами правой части, разделенными метасимволом | , являются строки, которые содержат терминальные и (или) не- терминальные символы. Терминальный символ, или терминал, — это символ или строка символов из алфавита языка. Например, цифры 0, 1, 2, ..., 9 — терминальные символы в приведенной выше продукции для нетерминала (цифра). В этом примере про- дукция означает, что «цифра — это символ 0, или 1, или 2, ..., или 9». Определим грамматику формально четверкой G = (VN, VT, S, Р), где VN и VT — непересекающиеся множества нетерминаль- ных и терминальных символов соответственно; S —• выделенный символ в VN, который обычно называют исходным, или начальным, символом; Р—это конечное множество продукций. Множество V = VT U VN называют словарем грамматики. Пример 2-7. Пусть Gx = (VN, VT, S, P) —• грамматика, описывающая син- таксис узкого подмножества английского языка. Тогда VN = {(предложение) , (подлежащее) , (сказуемое), (артикль), (существительное),(глагол), (прямое дополнение’-); Vy —{a, the, Linus, Charlie, Snoopy, blanket, dog, song, holds, pets, sings}; S ^(предложение); P ={1. (предложение) : : — (подлежащее) (сказуемое) 2. (подлежащее) : : = (артикль/ (существи- тельное) | (существительное'. 3. (сказуемое) : : = (глагол) (прямое дополнение) 4. (артикль) : : = а ] the 5. (существительное) : : = Linus J Charlie | Snoopy) blanket | dog | song 6. <глагол) : : = holds | pets | sings 7. (прямое дополнение) : : = (артикль) (существи- тельное) . | (существительное) |. В этом примере продукции пронумерованы числами от 1 до 7 для последу- ющих ссылок. 98
Язык, который обозначим L1} порождаемый грамматикой G1} описанной в примере 2-7, состоит из ряда предложений (всего 972 предложения). Некоторые из этих предложений таковых: ’Charlie pets the dog’ ’Linus holds the blanket’ ’Snoopy sings a song’ ’The blanket holds a dog’ Следует отметить два важных момента, относящихся к порож- дению строк (т. е. предложений) этой грамматикой. Во-первых, в совокупность терминалов для простоты не были включены сим- волы пробела, которые обычно появляются между словами в пред- ложениях. В этой книге мы будем полагать, если это не очевидно из контекста, что символ пробела разделяет все терминалы. Во-вторых, следует подчеркнуть, что ие все строки, образован- ные из терминалов, представляют предложения (иапример, строка ’Linus the a holds’ синтаксически неверна). Следовательно, необ- ходимо разработать метод анализа различных элементов строки для определения того, является или не является строка предло- жением языка. Такой процесс существует и называется граммати- ческим разбором. Для предложения можно составить схему грам- матического разбора, в то время как для строки, ие являющейся предложеиием, такой схемы не существует. Схема грамматиче- ского разбора отображает синтаксис предложения в форме, по- добной дереву, и поэтому называется синтаксическим деревом. 1 «Чарли ласкает собаку» «Лайнус держит одеяло» «Скупи поет песню» «Одеяло держит собаку» {существительное > < глагол > {артикль > {существительное > I® I® I® Linus bolds the blanket- Рйс. 2-2.1. Разбор предложения ’Linus holds the blanket’ 4*
На рис. 2-2.1 показан грамматический разбор предложения ’Linus holds the blanket’. Как же выполнить грамматический разбор предложения или, что одно и то же, построить синтаксическое дерево? Существует два метода разбора—нисходящий и восходящий. Рассмотрим оба эти метода подробно. В нисходящем грамматическом разборе построение синтакси- ческого дерева начинается с корня дерева (т. е. с выделенного символа) и, продолжается вниз к листьям (т. е. к символам ана- лизируемой строки). Цель такого процесса состоит в том, чтобы систематически порождать предложения языка до тех пор, пока не будет получено предложение, совпадающее с рассматриваемой строкой. Если такое предложение не будет получено, то задан и ая строка не является предложением языка. Порождение предложе- ний можно отразить с помощью особого отношения =ф-, для ко- торого запись х •=> у интерпретируется как «строка х порождает строку у (или у сводится к х)» на некотором шаге разбора. Про- иллюстрируем шаги порождения предложения ’Linus holds the blanket’ с использованием правил грамматики Gp (предложение' => -.'подлежащее) (сказуемое) => (существительное) (сказуемое) => Linus (сказуемое) => Linus (глагол)(прямое дополнение) => Linus holds (прямое дополнение) => Linus holds (артикль) (существительное) Linus holds the (существительное) => Linus holds the blanket (Продукция I) (Продукция 2) (Продукция 5) (Продукция 3) (Продукция 6) (Продукция 7) (Продукция 4) (Продукция 5) На синтаксическом дереве, представленном на рис. 2-2.1, число в кружке означает номер продукции, использованной при построении соответствующей части синтаксического дерева. Легко провести параллель между построением дерева и описанным про- цессом порождения предложения. На практике при построении синтаксического дерева методом нисходящего разбора может быть испытан целый ряд неподходя- щих продукций, прежде чем получится правильное дерево. В нашем примере на втором шаге порождения‘было использовано правило (подлежащее) (существительное). Но можно было бы на этом шаге с тем же правом выбрать продукцию (подлежащее) " = (артикль) (существительное). Это привело бы к порождению предложения, которое не совпало бы с заданной входной строкой, причем этот факт мог бы быть обнаружен лишь на следующем шаге при попытке применить продукцию (артикль) ::= а или (артикль) ::=the, так как ни ’а’, ни ’the’ не совпадают с ’Linus’. Такне ошибки в выборе продукций могут быть исправлены довольно просто возвратом к предыдущим шагам в формировании синтак- сического дерева и примеиением других продукций. В нашем примере альтернативным шагом является использование продук- ции (подлежащее) :;= (существительное). Такие действия вы- 100 полняются в ходе грамматического разбора до получения пра- вильного предложения. Более подробно о нисходящем разборе и стратегиях возврата при ошибках будет говориться в п. 5-2,3. Во втором методе, называемом восходящим грамматическим разбором, построение синтаксического дерева начинается с листьев и продолжается продвижением вверх к корню. Применяя эту стратегию к грамматике Gx, описанной в примере 2-7, можио полу- чить при восходящем разборе предложения ’Linus holds the blanket следующий ряд выводов (вспомним, что х =* у означает также, что у сводится к х, и именно так интерпретируются выводы в вос- ходящем разборе): (существительное) holds the blanket => Linus holds the blanket (Продукция 5) (подлежащее) holds the blanket => (Продукция 2) (подлежащее) (глагол) the blanket => (Продукция 6) (подлежащее) ^глагол'? (артикль) blanket => (Продукция 4) (подлежащее) (глагол) (артикль) ^существительное) => (Продукция 5) (подлежащее’^ (глагол) (прямое дополнение) => (Продукция 7) (подлежащее) (сказуемое) => (Продукция 3) (предложение) => (Продукция 1) Заметьте, что построено то же самое дерево (рис. 2-2.1) и исполь- зованы те же продукции, что и при нисходящем разборе. Однако при этом продукции применяются в совершенно другом порядке. В частности, поскольку в обоих методах разбор предложений шел слева направо, то порядок применения продукций в одном методе не является обратным по отношению к порядку применения про- дукций в другом методе. Если бы иисходящий разбор предложения выполнялся справа налево, то порядок применения продукций был бы обратным по сравнению с порядком их применения при восходящем разборе предложения слева направо. И, наоборот, порядок применения продукций при восходящем разборе пред- ложения справа налево был бы обратен порядку применения про- дукций при нисходящем разборе предложения слева направо. Вследствие того, что восходящий разбор предложений слева на- право обычно используется в компиляторах для грамматического разбора предложений языков программирования, он часто назы- вается каноническим разбором х. Основная стратегия канонического разбора начинается с выде- ления особой подстроки в заданной входной строке или выходной строке, полученной в результате применения ряда продукций к входной строке. Такую преобразованную строку часто назы- вают сентенциальной формой, а ‘выделяемую в ней строку основой. В восходящем разборе предложения ’Linus holds the blanket’ начальная строка—это ’Linus holds the blanket’, ко- нечный вывод — ’(предложение)’, а все промежуточные выводы i Более точное определение см., например, в книге Д. Грис. Конструиро- вание компиляторов для цифровых вычислительных машин. М>: Мир, 1975. — Прим. ред. 101
являются примерами сентенциальных форм. Основа в предложе- нии должна быть самой левой фразой (фраза — это подстрока сентенциальной формы, которая совпадает с правой частью не- которой продукции), соответствующей тон продукции, которая может быть применена в данном контексте основы в сентенциаль- ной форме. Например, ’Linus’ — основа в сентенциальной форме ’Linus holds the blanket’, а ’(глагол) (прямое дополне- ние)’ — основа в сентенциальной форме ’(подлежащее) (глагол) (прямое дополнение)’. В примере, который будет приведен ниже в этом параграфе, показывается, как анализ контекста фразы влияет на решение о том, является фраза основой илн не является. Как только основа выделена, левая часть соответствующей продукции (в нашем примере — (существительное)) подставляется вместо правой части, и получается новая сентенциальная форма (т. е. в рассматриваемом примере — сентенциальная форма ’ (су- ществительное) holds the blanket’). Этот процесс продолжается до получения начального символа (т. е. символа ’(предложение)’), если это возможно. Если в преобразованной строке остается только начальный символ, то исходная входная строка представляет собой предложение в языке, описанном грамматикой. В против- ном случае строка не является предложением в этом языке. Важным понятием, которое возникает при рассмотрении грам- матик и целого ряда других разделов, служит понятие рекурсии. Рекурсию нестрого можно определить как процесс, в котором не- что выражается через само себя. Ниже приводится пример грам- матики, которая рекурсивно определяет простой язык: G2 = fVN, VT, S, P), где VN = {''цифра) , (чс), (число)} VT = JO, 1, 2, 3, 4, 5, 6, 7, 8, 9) S — 'число) Р — [L (число) : : = (чс> 2. (чс) : : = (цифра', j (чс) {цифра\ 3. (цифра) : 0 | 1 | 2 | 3 | 4 ] 5 | 6 | 7 i 8 | 9]. В продукции 2 синтаксическую фразу (чс’/ можно определить как (чс) (цифра). Это и есть рекурсивное определение. Для того чтобы уяснить, как оно применяется, допустим, что надо сформи- ровать строку ’694’ языка с грамматикой G2- Начнем с выделен- ного символа (число) и продолжим так, как записано далее: (число) => (чс) =>(чс) (цифра) =*-(чс) (цифра) (цифра) =>(цифра) (цифра) (цифра) =ф- 6 (цифра) (цифра) => 69 «'цифра) => 694’ Продукция (чс) (чс) (цифра) представляет собой весьма мощное средство, так как дает возможность сформировать двух- цифровой элемент из одноцифрового. Этот процесс развертывания 102 дозволяет выразить любое желаемое целое положительное число независимо от его значения. Для заданного целого числа выпол- нение рекурсивных шагов и, значит, развертывание должны рано или поздно завершиться. Это происходит после применения нерекурсивной продукции, такой как (чс) ::= -„цифра). Такой нерекурсивный шаг называется базисным шагом рекурсивного процесса. Рекурсивно-определяемые продукции используют в тех слу- чаях, когда некоторые подстроки могут многократно повторяться в предложениях языка. Например, в языке, который был описан в начале данного пункта (т. е. в языке с предложениями вида ’0F ... 0F’), многократно появляются две подстроки ’1’ и ’0F’. Эти подстроки можно определить рекурсивно, как иллюстрируют продукции с синтаксическими фразами (единицы) и (нуль- единнцы) в грамматике G8: Gs = (Vn, Vt, S, P), где VN = l\L), (нуль-едипилы),(единицы vT = {0,1; s -- <L) P = {1. (L) : : = (нуль-единицы) 2. (нуль-единицы) : : = (единицы) [ (нуль-единицы) (единицы) У 3. (единицы) :: = 01 | (единицы’- 1} К Значение рекурсивных определений для грамматики весьма велико, и следующие два утверждения, которые можно доказать как теоремы, иллюстрируют эту точку зрения. 1. Всякая грамматика, содержащая рекурсивно-определяемую продукцию, описывает бесконечный язык (т. е. язык с бесконеч- ным числом предложений). 2. Всякая грамматика, в которой отсутствуют рекурсивно- определяемые продукции, описывает конечный язык (т. е. язык с конечным числом предложений). Следовательно, языки, описанные в этом пункте граммати- ками G2 и G8j — бесконечные, а язык, описанный грамматикой Gj, - конечный. В качестве еще одного примера, иллюстрирующего использо- вание рекурсивных продукций, рассмотрим задачу определения грамматики для описания имен идентификаторов. Во многих язы- ках программирования имя идентификатора состоит из единствен- ного буквенного символа или из буквенного символа, за которым следует конечный ряд буквенных, цифровых и специальных сим- волов. Примером требуемой грамматики может быть следующая грамматика: Ц = (V,v, v7, S, Р), где К VN — {(идентификатор), (идент), (буква), (прочее)} VT= {А, В, ..., Z, 0, 1, .... 9, —, #, $} S = (идентификатор) ЮЗ
Р — {1. (идентификатор) : : = (идент) 2. (идент) : : = (буква) I (идент) (буква) | (идент) (прочее) 3. (буква) : : = А | В | С | ... | Z | # | $ 4. (прочее) :: = 0[1|2|3|4|5|6|7|8|9| — } Пример синтаксического дерева, сформированного при разборе строки ’LAB_ Г, представлен на рис. 2-2.2. В большинстве языков программирования существуют практи- ческие ограничения на длину имен идентификаторов (например, в языке ПЛ/1 максимальная длина идентификатора составляет 31 символ). Приведенная выше грамматика G4 допускает любую длину идентификатора. Незначительно модифицировав мета- язык БНФ, можно в грамматику включить продукцию, которая применяется итеративно не более 30 раз. Для нашего примера продукцию 2 можно переписать так: 2. (идент) ::= (буква) [ (буква) [(буква) | (прочее' ] я0. В общем случае продукция вида х : : — у [z]n интерпретируется следующим образом: х : : — yz|yzz|yzzz|... |yzz ... z п раз Пример 2-8. Для последнего примера в этом пункте составим грамматику, описывающую простые арифметические выражения в языке программирования, подобном ПЛ/1. Простым арифметическим выражением может быть идентифи- катор, числовая константа (для простоты допустим лишь целые константы без знака) или выражение, содержащее константы и идентификаторы в качестве опе- рандов, свизанных операциями сложения (+), вычитания (—), умножения (*) н деления (/). Вспомним, что в языке ПЛ/1 операции умножения и деления имеют приоритет иад операциями сложения и вычитания, т. е. иапример, {идентификатор ) Ф Рис. 2-2.2. Разбор строки *LAB—1* с использованием продукций грамматики G* 104 X «• 2 — 3/Y + 1 интерпретируется как (X * 2) — (3/Y) + 1. Покажем, как можно учесть этот приоритет в описании грамматики: GB = (VVT,S,P), где VN — {(врж), (терм), (форма), (первичный)) VT - ( + . *, /, i, л) S = (врж) Р — {1. (врж) :: = (терм) 2. (терм) : : = (форма) | (терм) -}- (форма) | (терм) — (форма) 3. \форма) : ; = (первичный) | (форма) * (первичный) | (форма) / (первичный) 4. (первичный) : : - i | и) Отметим, что i (означает (идентификатор)) и п (означает (число)) считаются в этой грамматике терминальными символами. Оба эти терминала были опре- делены ранее как начальные символы в грамматиках G2 и G4. Значит, обе эти грамматики можно представлять как подграмматики в G5, н,' следовательно, полную грамматику при желании можно определить до уровни отдельных знаков. Выделение в грамматике G6 подграмматик, соответствующих классам (число) и (идентификатор), имеет определенный логи- ческий смысл для большинства разработчиков компиляторов.Один из модулей компилятора, называемый сканером, осуществляет анализ строк на пословном (или лексическом) уровне. Это значит, что символьные строки, образующие предложения языка, анали- зируются и группируются в словоподобные конструкты, называе- мые лексемами, такие как (число) и (идентификатор). Другой модуль компилятора, синтаксический анализатор, принимает на вход представления, или коды, лексем, сформиро- ванные сканером. Цель синтаксического анализатора состоит лишь в том, чтобы идентифицировать синтаксически правильные предложения, воспринимаемые в виде совокупностей слов языка. По этой причине точное значение лексем (например, равно ли (число) 26 или 3782) безразлично для синтаксического анализа- тора, так как эти значения анализируются сканером. Понятно, что грамматики, необходимые при описании лексем для сканера, гораздо проще грамматик для синтаксического анализатора (как это иллюстрируется грамматикой G5). Разделение фазы синтак- сического анализа в компиляторе на два модуля, сканер и синтак- сический анализатор, дает возможность разработать весьма эф- фективный сканер. Это обусловливается тем, что сканер вызывает- ся компилятором намного чаще, чем синтаксический анализатор. Из предыдущего обсуждения следует, что грамматики G2 и G4 описывают синтаксические объекты, обрабатываемые скане- ром, в то время как грамматика G5 предназначена для синтакси- ческого анализатора. Выполним канонический разбор (т. е. восходящий разбор слева направо) выражения 2 + X *Y. В рамках грамматики G6 это выражение имеет вид строки ’(число; ф- (идентификатор) * (идентификатор)’ 1-05
Поэтому, используя сокращения (т. е п = (число), I = (иден- тификатор), (р) =. (первичный), - f) = (форма), (t) = ;терм) и (е) = (выражение)), можно записать следующую последова- тельность разбора: Ip) i * i => n 4- i * i (Продукция 4) i) 4~ i * i => (Продукция 3) t) 4- i * i =► (Продукция 2) t) 4- <P> * i => (Продукция 4) t) 4- (f) * I => (Продукция 3) t) * i => (Продукция 2) Отметим, что последняя сентенциальная форма начинается со строки ’(t) * Из рассмотрения продукций в грамматике G,-, видно, что за символом (терм) должен следовать один из терми- нальных символов 4- или — илн не следовать никакой символ. Поэтому эта строка не может быть сведена к начальному символу из данного состояния разбора. Вернемся теперь к той стадии разбора, на которой была полу- чена сентенциальная форма ’(t) + (f)*i’. Просмотрев эту строку слева направо, можно обнаружить, что- символ следует за символом ’(f)’. Если сначала выполнить все редукции подстроки ’(f) * Г, а результат объединить с подстрокой ’<t> 4- ’, то можно успешно завершить разбор всего выражения. Итак, обратимся к сентенциальной форме ’ ,t) + \f) * Г и продолжим разбор в только что предложенном порядке. 'Л) + <С * (Р> => (t) 4- (f) * i (Продукции 4) (t) + (f) => (Продукция 3) (t) => (Продукция 2) (e) => (Продукция 1) Таков наш разбор! Необходимость просмотра строки «вперед» при разборе возни- кает вследствие определенных зависимостей, существующих в грамматической структуре. Например, в грамматике G5 синтак- сическая форма (терм) зависит от нетерминала (форма) (т. е. (терм): : = (форма) ] (терм^ 4- (форма) j (терм) — (форма)). Следовательно, все возможные редукции символа (форма) должны быть выполнены раньше редукций символа (терм). Именно это правило и было использовано при разборе строки ’2 + X * Y’. Этот пример четко свидетельствует о важности просмотра строки «вперед» в принятии решения о том,, является или не является основой данная сентенциальная форма. Синтаксическое дерево на рис. 2-2.3 показывает, что оператор умножения и его операнды объединяются в группу раньше группирования оператора сложе- ния и его операндов. Поэтому заданное выражение вычисляется как ’2 4- (X « Y)’, т. е. в данной грамматике обеспечивается не- обходимый приоритет выполнения операций * и / над операциями Ни—. Как правило, чем сложней грамматика, тем большее число символов необходимо просмотреть «вперед» при выполнении раз- 106
{вр*У {терм} {терм} I® (форма У |@ (первичный ) I® (ЧИСЛО У I no 6-2 I 2 Рис. 2-2.3. Разбор выражения 2 -|- X* V с использованием продукций грамматики Ge бора (поэтому некоторые авторы использовали это число в каче- стве меры сложности грамматики). В частности, грамматика G5 требует просмотра вперед на один символ. Существуют грамматики, требующие просмотра вперед на к символов при конечном к, а также грамматики с потенциально неограниченным значением этой величины. Прежде чем завершить этот параграф, кратко обсудим два дополнительных вопроса — понятия синтаксиса н семантики и их связь с грамматиками, а также взаимоотношение между марков- скими алгоритмами и грамматиками. Грамматика дает возмож- ность описать лишь структуру (т. е. синтаксис) того или другого предложения. Для некоторых языков не всегда удобно или воз- можно выразить точно то множество строк, которые образуют пред- ложения языка. Хорошим примером такой ситуации служит под- множество английского языка, определяемое грамматикой Gj. Предложениями, порождаемыми этой грамматикой, являются и такие х: г ’ Snoopy sings the blanket’ ’ The song pets Charlie’ Оба эти предложения синтаксически правильны, по лишены или почти лишены смысла, и поэтому с семантической точки зрения 1 ’Скупи поет одеяло’ ’Песня ласкает Чарли’ L07
не являются предложениями. Очень часто бывает, что Строки, являющиеся предложениями в смысле формальной грамматики, не могут считаться предложениями языка, и по этой причине грам- матический разбор предложения должен сопровождаться допол- нительной проверкой на семантическую корректность. Грамматики, описанные в этом параграфе, называются кон- текстно-свободными грамматиками. Контекстно-свободная грам- матика — это такая грамматика, в которой левая часть каждой продукции может содержать только одни нетерминал. Ниже при- веден пример грамматики G6, не являющейся контекстно-свобод- ной: G6 = ({(S), <В>, (С)}, (а, Ь, с}, <S>, Р), где Р = {<S> : : = a <S> <В> <С> (S) : : = а <В> <С> (Q (В) : : = (В? (С) а (В) : : = ab b <В> : : = bb b 'С; : : = Ьс с <С> : : = сс| Грамматика Gc описывает язык, содержащий строки вида а"ЬГ1сп при п 1 (например, при п = 3 a3b3c3 = aaabbbccc). Порождение строки а2Ь2с2 иллюстрируется последовательностью (S) => a <S> {В} <С‘ => аа (В> (С) <В> (С) => аа (В) (В) <С> (С) => aab (В) (С) (С? aabb <С) => aabbc (С) => aabbcc. Можно показать, что всякая грамматика с фразовой структу- рой представима в форме марковского алгоритма. Грамматики с фразовой структурой образуют наиболее общий класс грамма- тик в том смысле, что они не накладывают ограничений на про- дукции (в частности, они не требуют того, чтобы левая часть каж- дой продукции содержала только один символ). Используя ап- парат марковских алгоритмов, можно без труда написать алго- ритм для генерации строк вида апЬпсп: LMA: GENERATE START: Л -> ’abc’ MOVE: ’ba* -> ’ab’ (MOVE) ’ca’ —♦ ’ac’ (MOVE) ’cb’ — ’be’ (MOVE) вывод строки (START) Команда вывод строки записана для того, чтобы обеспечить выдачу результата работы алгоритма в виде строки. Отметим, что 168
алгоритм GENERATE порождает множество строк {anbncn| : для каждого п та 1. 1 LMA RECOGNIZE ’а’ —> а’а’ ! •ь’ — р’ь; ’с’ —» у'с j START: а’а’ — a (DELETEB) -• afty ’s’. ’ Л->Л. DELETEB: p’b’ -> р (DELETEC) X -> Л. 5 DELETEC: ?’с’ — v (START) л — а. : Если после завершения работы алгоритма RECOGNIZE по- > лучается строка ’s’, то это означает, что исходная строка есть пред- ложение. Любая другая результирующая строка свидетельствует ; о том, что исходная строка предложением не является. j Марковские алгоритмы и грамматики с фразовой структурой j эквивалентны с точки зрения вычислительной силы. Однако эти : две формальные модели предназначены для разных целей. Марков- ] ская модель обладает свойствами, являющимися основными для ; обработки строк и элементарных операций сопоставления строк. Этн свойства будут рассмотрены подробно в следующем параграфе. Грамматическая же модель больше подходит для распознавания и порождения строк. Обе эти функции будут исследованы более полно в п. 2-5.2. Упражнения к п. 2-2 ; 1. Составить марковский алгоритм, который для произвольной строки х пад заданным алфавитом (например, V = (а, Ь, с}) должен установить, ; совпадает ли она с некоторой определенной строкой w (например, w= ’abc’). Если строки х и w одинаковы, то строка х должна быть заменена другой опре- J деленной строкой у (пусть у = 'Ь'). В противном случае строку х следует заме- j нить строкой z (скажем, z — ’с'). Применить в алгоритме маркирующий символ. 2. Записать марковский алгоритм, который преобразует строку хг х2. .. хп, ; где каждый х; £ {а, Ь), в строку ххх2 ... хпхпхп_г ... Хц. „ - 3. Часто требуется определить число символов в определенной строке. . Сделать это можно, используя систему значков путем замены каждого символа строки значком «1». Например, алгоритм, содержащий продукции вида х -* 1 ’ (х € V), преобразует произвольную строку в последовательность значков. Так как 1 V, то алгоритм завершается после того, как каждый символ строки будет преобразован в значок. Пусть теперь заданы две строки ххха ... хп и j У1У2 — Уш> где каждое Xj и yt есть значок. Требуется записать марковский ал- горитм, преобразующий строку ххха ... хп —уху8 ... ут в строку z^ ... zn_m, } которая представляет разность значков. Например, строка 'ill—II’ преобра- j зуется в *Г. Принять, что п >• m. I 4. Составить марковский алгоритм (помеченный или непомеченный), кото- J рый распознает палиндром. Палиндром — это строка вида ххха -..xn_x xnxnxn-l ! х,, в которой каждый xj С V для некоторого определенного алфавита V. i Если входная строка есть палиндром, то вых одной должна быть строка ’а', где •. а С V. В противном случае выходной Должна быть строка ’Ь’, где b £ V. I- Ю9 ;
5. Ранее в пункте о грамматиках обсуждался простой язык L = ((’0I+’)+IV = (0,1)|, . где строки ’01011’ и ’011011Г являются элементами в L, а строЖ’0100’— не является. Составить марковский алгоритм (помеченный или непомеченный), который распознает только строки из этого языка. Алгоритм должен завер- шаться с результатом ’1’, если исходная строка является элементом языка L, или результатом ’0’ — в противном случае. 6. Написать марковский алгоритм, для которого входная строка z имеет вид х,х2 ... Хп’^’у,^ ... ут, где хь ’#•, у; ( V при 1 < i < п, 1 < j < m. Алгоритм должен выдать результат ’44’» если х есть подстрока в у или у — под- строка в х. В противном случае результатом должна быть строка ’44#’- 7. Составить алгоритм типа LMA, реализующий целочисленное деление двух чисел х и у. Входная строка задана в виде значков, представляющих два операнда, которые разделены символом ’/’. Поэтому 5 : 2 представляется стро- кой ’ 11111/1Г. Ответ должен получиться тоже в виде значков (т. е. ’11’ для при- веденного примера). 8. Рассмотренный аппарат марковских алгоритмов не представляет собой язык программирования как таковой. Какие средства следует включить в этот аппарат, чтобы превратить его в язык программирования? 9. Рассмотрим следующую грамматику с множеством символов [а, Ь): S->a, S->b, S->Sa, S->bS. Описать множество строк, порождаемое этой грамматикой. 10. Составить грамматики для порождения: а) множества неотрицательных нечетных целых чисел; б) множества неотрицательных четных целых чисел без незначащих (перед- них) нулей. 11. Составить грамматики языков, порождающих следующие множества: а) {а^а1 | i 0); б) {wbwR | w б {0,1}*}, где —обратная строка по отношению к w, т. е. если w = 001, w-0 — 100; в) {(имя/ | (имя4 есть идентификатор в языке ФОРТРАН). Заметим, что идентификаторы в ФОРТРАНе должны содержать не более шести символов. 12. Определить грамматику (используя метаязык БНФ), которая описы- вает операторы комментария в языке ПЛ/1. Предполагается, что множество терминалов этой грамматики есть {А, В, С, ..., Z, 0, 1, ..., 9, L *» —, +, /> пробел). Применяя эту грамматику, выполнить разбор комментария * INPUT/OUTPUT*/ 13. Описать на метаязыке БНФ предложение (заголовок процедуры) в языке ПЛ/1. Примером заголовка процедуры является EXAMPLE: PROCEDURE (X, Y, Z) RECURSIVE RETURNS (CHARACTER (*) VARYING); Принять, что в качестве аргументов атрибута RETURNS могут быть только атрибуты FIXED, FLOAT, BIT (*) VARYING или CHARACTER (*) VAR- YING. Можно также допустить, что символ (идентификатор) является терми- налом в этой грамматике. 14. Следующая грамматика генерирует простые арифметические выражения, содержащие операции сложения (-{-), вычитания (—), умножения (*) и деле- ния (/). Символ 1 представляет ими переменной. (множитель): : == ((выражение)) | i (выражение) : : — (терм) | (выражение) (терм) | (вы- 110
ражение) — (терм) (терм) : : =. (множитель) | (терм) * (множитель) | (терм' / (множитель) Написать выводы следующих выражений: i + i, i — i/i, i * (i т i), i * i + i. 15. Допустим, что требуется реализовать DDC— компилятор для языка DDC1, предназначенного для выполнения арифметических операций над цело- численными аргументами. Приведенное ниже описание грамматики на мета- изыке БНФ определяет синтаксис языка DDC. К сожалению, эта грамматика неоднозначна. (Грамматика языка считается неоднозначной, если то или иное предложение этого языка может иметь более одного разбора.) (DDC врж) : : — 4DDC терм) ' (DDC врж) (onl) (DDC врж\ (DDC терм) : : = (десятичный арг) | (DDC терм) (оп2) -^десятичный арг) (десятичный арг) : : = (цифра) | (десятичный арг) (цифра) (цифра) :: = 0|1|2|3]4|5|6 7 | 8 | 9 <ОП1) : : _ + | — чоп2) : : = $ | / а) Показать, что эта грамматика в действительности неоднозначна. б) Модифицировать грамматику так, чтобы она стала однозначной. в) Определить значение выражения 7 6 * 3/2 в соответствии с этой повой грамматикой. г) если изменить БНФ — описание символов (onl) и (оп2) следующим образом: (ол!) : : = * ) — (оп2 | / то чему станет равно значение выражении 7 -|- 6 * 3/2 в соответствии с моди- фицированной грамматикой? 16. Составить грамматики дли порождения следующих множеств строк: a){anbam} при п, т^>1; (anbnan) при п > 1. (Подсказка: возможно, что в левой части той или другой продукции при- дется записать более одного символа.) 2-3. МАНИПУЛИРОВАНИЕ СТРОКАМИ И ПОИСК ПО ОБРАЗЦУ Аппарат марковских алгоритмов, рассмотренный в п. 2-2.1, дает нам основательное представление о концепциях, относящихся к действиям над строками и поиску по образцу. Тем не менее марковская система непрактична с точки зрения программирования и обработки данных. В этом параграфе мы предлагаем набор примитивов и совокупность базисных функций обработки строк, которые отчасти основываются на свойствах, проявляемых двумя формальными системами, описанными в п. 2-2. Будут описаны средства обработки строк, имеющиеся в универ- сальном языке ПЛ/1 и языке манипулирования строками СНО- 1 DDC — Decimal Digit Calculator (десятичный калькулятор). — Прим. „ пер. Ш
БОЛ, причем эти средства будут сопоставлены с ранее рассмотрен- ными функциями обработки строк. Параграф заканчивается об- суждением рекурсивного поиска по образцу. 2-3.1. Примитивные функции для манипулирования строками На основе тщательного анализа тех базисных средств обработки строк, которые требуются для любой системы формиро- вания и редактирования текста, можно получить следующий спи- сок необходимых примитивных функций. 1. Создать текстовую строку. 2. Выполнить конкатенацию двух строк для получения новой строки. 3. Отыскать и заменить (если надо) заданную подстроку в строке. 4. Проверить тождественность строк. 5. Определить длину строки. В этом пункте обсуждается важность каждой из перечисленных функций для системы обработки строк. Кроме того, наша система записи алгоритмов будет дополнена операциями, реализующими эти функции. Создание строки предполагает не только умение сформиро- вать представление строки, но также и способность сохранить зна- чение строки в некоторой переменной (или ячейках памяти). Этот атрибут присущ марковской алгоритмической модели, где соз- дается одна строка (основная), и грамматике, в которой возможна генерация строки (в частности, предложения). Способностью соз- давать строку должна обладать любая система обработки строк. При записи алгоритмов строка будет выражаться последова- тельностью символов, заключенных в апострофы. Для получения стройной системы представления строк апостроф внутри строки будет изображаться двумя апострофами. Поэтому строка ”It is John’s program” представляется в виде ’IT IS JOHN”S PROG- RAM’. Для сохранения значений строк можно использовать пере- менные. Так, чтобы присвоить строку ’CAT’ в качестве значения переменной РЕТ, будем писать. Установить РЕТ <- ’CAT’. Пустая (или нулевая) строка обозначается либо двумя апостро- фами (”), либо символом Л. Наиболее важная операция над строками — это конкатена- ция. В начале этой главы была определена конкатенация отдель- ных символов. В марковских алгоритмах и в грамматиках про- дукции имеют правую и левую части, состоящие из сцепленных строковых и (или) односимвольных элементов. Конкатенация на- столько важна и естественна при манипулировании строками, что во многих системах (например, в языке программирования СНО- 112
БОЛ) для нее нет специального оператора, а конкатенация реали- зуется размещением строковых аргументов рядом друг с другом. Однако в целях получения законченной системы представления опе- раторов будем использовать символ О для обозначения конкате- нации при записи алгоритмов. Поэтому, чтобы сцепить строки ’STRUCTURES’ и ’DATA’ и присвоить результат переменной SUBJECT, запишем Установить SUBJECT «- ’DATA’О’STRUCTURES’. Операндами операции конкатенации могут быть строковые пере- менные и строковые константы (например, установить РЕТ ч— Ч- РЕТ о ’DOG’). При поиске подстроки в заданной строке необходимо опреде- лить позицию подстроки в строке, если эта подстрока найдена. Эту позицию часто называют курсорной позицией, и она выра- жается целочисленной величиной, обозначающей номер позиции самого левого символа искомой подстроки. Функция, выполняю- щая эту операцию, в системе записи алгоритмов называется INDEX. Значением функции INDEX (SUBJECT, PATTERN) является номер позиции самого левого вхождения строки PAT- TERN в строке SUBJECT. Если строка PATTERN ие содержится в строке SUBJECT, то значение этой функции равно нулю. На- пример, если SUBJECT = ‘ВАСАВ АВА', то INDEX (SUBJECT, 'АВА')-4, INDEX (SUBJECT, 'А') = 2и INDEX (SUBJECT, 'ABC') = 0. Переменная PATTERN на месте второго аргумента функции INDEX используется для вполне определенной цели. Строка, соответствующая аргументу PATTERN, налагается на основную строку посимвольно, начиная с первого символа основной строки Ч Это процесс наложения строки-образца на основную строку обычно называют поиском по образцу1 2. Функция INDEX представляет собой первый пример операции поиска по образцу. Существует много других операций поиска по образцу. Ряд таких операций будут описаны в этой главе. Однако функция INDEX выполняет наиболее примитивную форму поиска по образцу и является ос- новой для более развитых операций подобного рода. Следует за- метить, что применение марковской или грамматической продук- ции также основывается на некоторой форме поиска по образцу (например, для заданной основной строки 'ABCDE' применение марковской продукции 'CD' —> 'Е' требует, чтобы в основной строке образец 'CD' был найден до замещения его строкой'Е'). Другой важной функцией является выделение подстроки в стро- ке. В марковской модели указание подстроки обеспечивалось с по- 1 Основная строка (используется также’термин «субъектная строка», или «субъект») соответствует первому аргументу функции INDEX. — Прим. пер. 2 Применяются также термины сопоставление с образцом и поиск, по шаб- лону. — Прим. ре$. 113
мощью маркерных символов и символьных переменных. Например, продукции |«ху урх ₽-*Л. Л —> а} меняют местами первые два символа основной строки. В системе записи алгоритмов вместо маркерных символов для выделения под- строки будем использовать номер курсорной позиции совместно с длиной подстроки. Дадим этой функции имя SUB, Значением функции SUB (SUBJECT, i, j) или SUB (SUB- JECT, i) служит подстрока в SUBJECT, которая определяется параметрами i и j, или i и подразумеваемым значением j. Параметр i задает начальную курсорную позицию подстроки, а параметр j — длину требуемой подстроки. Если параметр j отсутствует, то его значение подразумевается равным к—i -}- 1, где к равно длине ар- гумента SUBJECT. Для того чтобы завершить определение функ- ции SUB, необходимо учесть некоторые дополнительные ситуа- ции. 1. Если ]' sg 0, то (независимо от i) значением функции SUB является пустая строка. 2. Если i 0, то (независимо от j) значением функции SUB является пустая строка. 3. Если i > к, то (независимо от j) значением функции SUB является пустая строка. 4. Если i 4- j > k + 1, то j предполагается равным k — i 4- 1 - Рассмотрим следующие примеры для пояснения функции SUB. Функции SUB (’ABCDE’, 2) и SUB (’ABCDE’, 2, 7) имеют одно и то же значение ’BCDE’, функция SUB (’ABCDE’, 3, 2) имеет зна- чение 'CD' и функции SUB (’ABCDE’, 0, 3) и SUB (’ABCDE’, 6) обе принимают значение пустой строки. Функцию SUB можно также применять в левой части оператора присваивания (т. е, при выполнении операций типа подстановки). Например, если SUBJECT = ’ABCDE', то оператор Установить SUB (SUBJECT, 2, 2) ’А’ изменит значение строки SUBJECT на ’AADE’. Если функция SUB (SUBJECT, i, j) находится в левой части оператора при- сваивания и i с О или j 0, то присваивание не выполняется. Если i > к или i + j > к -J- 1, то присваиваемые символы рас- полагаются в соответствующих позициях справа от конца основной строки. Промежуточные позиции заполняются символами пробела. Следующие две примитивные функции не являются основными для марковских алгоритмов или формальных грамматик. Тем не менее они важны для практических применений, относящихся к редактированию и форматизации строк. Их можиб реализовать в терминах марковских алгоритмов или грамматик, 114
Проверка тождественности строк предполагает существование некоторого предиката, который получает значение true (истина) или false (ложь) в результате сравнения основной строки с другой строкой. При записи алгоритмов будут использоваться два опе- ратора отношения = и =^. Для того чтобы проиллюстрировать их смысл, рассмотрим следующие примеры: ’XMAS’ = ’XMAS’ — true, ’XMAS’ ’CHRISTMAS’ — true, HO ’XMAS’ = ’MAS’ — false, ’XMAS’ ’XMAS’ — false. Это средство сравнения легко расширить, включив в него все широко используемые операторы отношений (т. е.<, ^,>, < и >). С этой целью определим сопоставляющую последователь- ность, относительно которой можно выполнить лексическое (т. е. пословное) упорядочение. Сопоставляющая последовательность, применимая к большинству символьных наборов для устройств чтения с перфокарт, такова: пробел '0 <(+ I &! $ * ); —/ > %___= ”А ... Z0 ... 9. Эта последовательность осно- вана на внутреннем представлении символов, как было рассмо- трено в п. 1-4.7. Лексическое упорядочение строк подобно упорядо- чению в словаре или в телефонном справочнике. Следовательно, утверждения ’XMAS’>’CHRISTMAS’, ’XMAS’ ’XMAS1974’, ’XMAS’ ’XMA’ все истинны, а утверждения ’XMAS’> ’XMAS’, ’Y’ > ’XMAS’ и ’X’ ’XMAS’ ложны. Из этих примеров ясно, что сравнения выполняются посимвольно, начиная с самого ле- вого символа каждой строки, участвующей в сравнении. Наличие в позиции любого символа (даже пробела) всегда считается боль- шим значением по сравнению с отсутствием символа (т. е. ’X ’ Д> > ’X’ истинно). Длина строки служит важным параметром при форматизации символьных строк для их вывода на то или другое устройство. Именно по этой причине данная операция обычно включается в состав примитивных в формальных системах. В системе записи алгоритмов вычисление длины строки обеспечивается функцией LENGTH. Если SUBJECT есть символьная переменная, то функ- ция LENGTH (SUBJECT) возвращает значение, равное числу сим- волов в строке, представленной переменной SUBJECT. Значение функции равно нулю, если строка SUBJECT пустая. Например LENGTH (’ТОР’) = 3, LENGTH (SUBJECT) = 4, если SUBJECT = ’ABAB’, и LENGTH (SUB (SUBJECT, 1, 0)) = = 0. 2-3.2. Базовые функции До сих пор в этом параграфе подробно определялись и обсуждались примитивные функции обработки строк, примитив- ные в том смысле, что большинство других функций обработки 115
строк может быть сформировано по модульному принципу из этих примитивных функций. Обратимся теперь к обсуждению четырех функций LEN, MATCH, SPAN и FIND, которые, не будучи при- митивными, являются в то же время базовыми для задач обра- ботки строк. Эти функции будут представлены в системе записи алгоритмов и описаны в терминах только что рассмотренных при- митивных функций. Указанные четыре функции будут широко ис- пользоваться в конце данной главы в п. 2-5. Поскольку все упомянутые функции относятся к типу функций поиска по образцу, их можно постоянно характеризовать одним и тем же набором аргументов, а именно, каждый алгоритм поиска по -образцу оперирует следующими аргументами. 1. SUBJECT — строка, в которой осуществляется поиск по образцу \ 2. PATTERN — строка, для которой в основной строке отыс- кивается соответствующая ей подстрока. (В алгоритме LEN вместо аргумента PATTERN используется аргумент NUM, пред- ставляющий собой число сопоставляемых символов.) 3. CURSOR — та позиция символа в строке SUBJECT, с ко- торой надо начать сопоставление с образцом. Поэтому в операции поиска по образцу рассматривается лишь часть основной строки, начинающаяся с позиции CURSOR и заканчивающаяся последним символом строки. 4. MATCH—STR — переменная, принимающая значение най- денной (т. е. выделенной) подстроки. Если поиск по образцу ока- зался безуспешным, то MATCH—STR не меняется. 5. RERLACE—STR — строка, замещающая собой выделен- ную подстроку при условии, что переменная REPLACE—FLAG имеет значение true (истина). 6. REPLACE—FLAG — флажок, представляющий признак замещения выделенной части основной строки. Если REPLACE- FLAG имеет значение true, то замещение выполняется, а если false (ложь) не выполняется. Все четыре названные выше функции поиска по образцу опи- сываются ниже как логические функции (т. е. предикаты). Во всех случаях каждая из этих функций возвращает значение true, если операция поиска по образцу оказалась успешной 1 2. В про- тивном случае функция возвращает значение false. Алгоритм LEN. При заданных шести аргументах, описанных выше, результатом работы алгоритма LEN является значение true, если в строке SUBJECT, начиная с позиции CURSOR, содержится NUM символов. В этом случае переменной MATCH— 1 Как и ранее, эта строка будет называться основной строкой. — Прим. пер. 2 В широком смысле поиск по образцу в строке считается успешным, если в ией найдена подстрока, которая соответствует образцу с точки зрения заданного условия или совокупности условий (таким условием может быть, в частности, идентичность подстроки и образца). — Прим. ред. 116
STR присваивается Значение выделенной строки, имеющей длину NUM. Если REPLACE—FLAG имеет значение true, то NUM выделенных символов замещаются строкой REPLACE—STR. 1. [Проверка наличия заданного количества символов. 1 Если CURSOR + NUM > LENGTH (SUBJECT) 4- I, то установить LEN false и закончить выполнение алгоритма. 2. [Выполнить сопоставление и замещение, если это задано.] Установить LEN true, MATCH-STR ч- SUB (SUBJECT, CURSOR, NUM). Если REPLACE—FLAG, то установить SUB (SUBJECT, CURSOR, NUM) ч- REPLACE .STR, CURSOR ч- <- CURSOR + LENGTH (REPLACE_STR) и закончить вы- полнение алгоритма. 3. [Замещение не задано. ] Установить CURSOR <- CURSOR 4 -[- NUM и закончить выполнение алгоритма. Для иллюстрации работы алгоритма LEN проследим последо- вательность действий для аргументов SUBJECT = ’ТО BE OR NOT ТО BE’, NUM = 3, CURSOR = 10, MATCH—STR неиз- вестно, REPLACE-STR = ’NEVER’ и REPLACE—FLAG = = true. Поскольку значение CURSOR 4- NUM (оно равно 13) не превышает длины основной строки, увеличенной на единицу (что дает 19), то проверка возможности сопоставления с образцом в шаге 1 получается успешной. В шаге 2 переменная LEN прини- мает значение true, а строка MATCH—STR — значение подстроки ’NOT’. Так как установлен признак REPLACE—FLAG, то вы- деленная часть основной строки (т. е. ’NOT’) замещается значе- нием аргумента REPLACE—STR (т. е. ’NEVER’), в результате чего получается новая строка ’ТО BE OR NEVER ТО BE’. И наконец, значение аргумента CURSOR увеличивается до 15, что соответствует курсорной позиции символа, следующего за послед- ним символом замещающей строки. Ниже приведен ряд дополнительных примеров, иллюстрирую- щих действие функции LEN. Во всех этих примерах начальное значение аргумента SUBJECT равно ’ТО BE OR NOT ТО BE’. LEN (SUBJECT, 1, 19, MATCH-STR, REPLACE—.STR, false) возвращает значение false, Так как длина строки SUBJECT равна всего 18 символам, и поэтому строка не может содержать подстроку, начинающуюся с позиции 19. LEN (SUBJECT, 2, 7, MATCH-STR, REPLACE—STR, false) возвращает значение true, причем MATCH—STR получает значение ’OR’, CURSOR — значение 9, а строка SUBJECT остается без изменения. LEN (SUBJECT, 6, 1, MATCHS___TR, ”, true) возвращает значение true, причем MATCH—STR получает значение ’TO BE’, CURSOR—значение 1, а строка SUBJECT становится ’OR NOT TO BE’. А теперь рассмотрим следующую из базовых функций поиска по образцу. Функция MATCH проверяет совпадение заданного 117
образца с некоторой подстрокой в строке SUBJECT, начиная с по- зиции, указываемой аргументом CURSOR. Алгоритм MATCH. При заданных шести аргументах, описан- ных выше, функция MATCH возвращает значение true, если в стро- ке SUBJECT, начиная с позиции, указываемой аргументом CURSOR, найдена подстрока, совпадающая с образцом PAT- TERN. Если поиск оказался успешным, то аргументу MATCH- STR присваивается значение аргумента PATTERN, а если при этом .установлен признак REPLACE—FLAG, то выделенная часть основной строки замещается значением аргумента REPLACE- STR. I. [Проверка}’того, что поиск задан в границах основной строки.] Если CURSOR + LENGTH (PATTERN) > LENGTH (SUBJECT) + 1, то установить MATCH -«—false и закончить вы- полнение алгоритма. 2. [Проверка совпадения с образцом.] Если SUB (SUBJECT, CURSOR, LENGTH (PATTERN)) + PATTERN, то установить MATCH ч- false и закончить выполнение алгоритма. 3. [Присваивание значения аргументу MATCH—STR и заме- щение. 1 Установить MATCH —STR ч- PATTERN, MATCH ч- ч- true. Если REPLACE-FLAG, то установить SUB (SUBJECT, CURSOR, LENGTH (PATTERN)) ч-REPLACE—STR, CUR- SOR 4-CURSOR + LENGTH (REPLACE________STR) и закончить выполнение алгоритма. 4. [Замещение не требуется.] Установить CURSOR ч-CUR- SOR + LENGTH (PATTERN) и закончить выполнение алго- ритма. В качестве примера, поясняющего выполнение функции MATCH, примем значения аргументов ’ SUBJECT = ’SHAKESPEAREAN SONNETS ARE IN IAMBIC PENTAMETER’, PATTERN = ’SHAKESPEARE’, CURSOR = I, REPLACE_STR = ’ELIZABETH’, REPLACE_FLAG = true. В шаге 1 проверяется условие, что значение CURSOR -j- -г LENGTH (PATTERN) (оно равно 12) меньше значения LENGTH (SUBJECT) + 1 (последнее равно 47); при выполнении условия переходим к шагу 2, в котором проверяется возможность операции поиска по образцу. Подстрока, начинающаяся с курсорной пози- ции 1 и равная по длине строке-образцу, совпадает со строкой, представленной аргументом PATTERN. В шаге 3 аргументу MATCH—STR присваивается значение аргумента PATTERN, а функции MATCH — значение true. Так как установлен признак REPLACE—FLAG, то аргументу SUBJECT присваивается зна- чение ”о’ ELIZABETH’ О’ AN SONNETS ARE IN IAMBIC 118
PENTAMETER’, или ’ELIZABETHAN SONNETS ARE IN IAMBIC PENTAMETER’, а переменной CURSOR — 10. Ниже приведены дополнительные примеры, иллюстрирующие выполнение функции MATCH. Во всех случаях строка SUBJECT имеет значение ’SHAKESPEAREAN SONNETS ARE IN IAMBIC PENTAMETER’ MATCH (SUBJECT, ’ELIZABETH’, 2, MATCH_STR, REPLACE—STR, false) не обнаруживает совпадения с об- разцом, и поэтому значения SUBJECT, CURSOR и MATCH—STR ие изменяются. MATCH (SUBJECT, ”, 47, MATCH-STR, ’AND CONSIST OF THREE QUATRAINS’, true) обнаруживает совпадение пустой строки в конце основной строки собразцом. Следо- вательно, SUBJECT принимает значение ’SHAKESPEA- REAN SONNETS ARE IN IAMBIC PENTAMETER AND CONSIST OF THREE QUATRAINS’, CURSOR —77 (т. е.иа единицу больше длины новой строки), a MATCH—STR — значение пустой строки. Следующая базовая функция поиска по образцу, которую мы рассмотрим, позволяет нам выделять любые символы в основной строке, начиная с символа, находящегося в курсорной позиции, и кончая первым же символом, не содержащимся в образце. Эта функция особенно полезна в задачах редактирования текста, как будет показано в п. 2-5.1. Алгоритм SPAN. При заданных шести аргументах, описанных выше, функция SPAN возвращает значение true, если символ, указываемый аргументом CURSOR, совпадает с любым символом в образце PATTERN. Если такое совпадение имеет место, то ар- гументу MATCH—STR присваивается значение строки символов, начинающихся с позиции CURSOR (включая и эту позицию) и совпадающих с каким-нибудь символом в образце PATTERN. Сопоставление с образцом заканчивается, когда в основной строке встретится символ, отсутствующий в образце PATTERN, или когда достигнут последний символ основной строки. Выделенная пос- ледовательность замещается строкой REPLACE—STR, если при- знак REPLACE—FLAG имеет значение true. I. (Проверка попадания образца в границы основной строки. ] Если CURSOR > LENGTH (SUBJECT), то установить SPAN ч- false и закончить выполнение алгоритма. 2. [Присвоить индексу основной строки i значение курсора. 1 Установить i ч-CURSOR. 3. [Проверка того, что в строке PATTERN есть символ, сов- падающий с i-м символом основной строки. I Повторять до тех пор, пока i LENGTH (SUBJECT) и INDEX (PATTERN, SUB (SUBJECT, i, I)) =f= 0: установить 1 ч-i + 1. 4. [Сивмол не найден в строке PATTERN. ] Если i = ~ CURSOR, то установить SPAN-ч- false и закончить выполне- ние алгоритма. 119
5. [Присвоить значения и заменить. 1 Установить SPAN<- ч-true и MATCH-STR ч-SUB (SUBJECT, CURSOR, i — CUR- SOR). Если REPLACE—FLAG, то установить SUB (SUBJECT, CURSOR, i — CURSOR) REPLACE—STR, CURSOR <- -«-CURSOR + LENGTH (REPLACE—STR) и закончить выпол- нение алгоритма. 6. [Замещение ие требуется. 1 Установить CURSOR ч i и закончить выполнение алгоритма. Рассмотрим основную строку ’НЕ WALKED.... AND WAL- KED .... AND WALKED’. Допустим, что требуется отредактиро- вать эту строку, заменив первую подстроку символов запятой. Зададим PATTERN = CURSOR = 10, REPLACE.STR = = REP LACE „FLAG = true и выполним алгоритм SPAN. Ясно, что значение CURSOR меньше длины основной строки. В шаге 2 индексу i присваивается значение курсорной позиции, равное 10. Условие " до тех пор, пока * в шаге 3 справедливо для i = 10, 11, 12 и 13. Эти номера позиций соответствуют подстроке ’....’, т. е. первой подстроке заданного вида в строке SUBJECT. Упомянутое условие не выполняется при i = 14, так как SUB (SUBJECT, 14,1) равно пустой строке”, которой соответствует нулевое значение INDEX в образце PATTERN. Поскольку в шаге 4 i -/= CURSOR, то функция SPAN возвращает значение true, a MATCH—STR в шаге 5 — значение Поскольку признак REPLACE_FLAG установлен, то основной строке присваивается значение ’НЕ WALKED, AND WALKED .... AND WALKED’. И, наконец, аргументу CURSOR присваивается значение 11. Ниже приведены другие примеры, демонстрирующие возмож- ности и ограничения функции SPAN. Во всех этих примерах SUBJECT имеет значение ’НЕ WALKED. ...AND WALKED.... ....AND WALKED’. SPAN (SUBJECT, ”, 1, MATCH-STR, REPLACE—STR, false) не завершается успешным поиском по образцу, так как в курсорной позиции 1 основной строки нет символа пробела. SPAN (SUBJECT, ’ADEKLW. 4, MATCH-STR, REPLACE_STR, false) обеспечивает выделение подстроки ’WALKED ....’. Эта подстрока присваивается аргументу MATCH—STR. Так как признак REPLACE________FLAG .не установлен, то выделенная подстрока не замещается, и CURSOR получает значение 14. SPAN (SUBJECT, ’ ADEHKLNW. ’, 1, MATCH—STR,' ”, true) обеспечивает выделение всей основной строки, которая присваивается аргументу MATCH—STR. Так как признак REPLACE_FLAG установлен, то вся основная строка замещается пустой строкой REPLACE—STR, и CURSOR принимает значение 1. Последняя функция поиска по образцу, которую мы обсудим, называется FEND. Функция FIND выявляет первое вхождение 120
подстроки, совпадающей с заданным образцом и находящейся в любом месте, начиная с заданной курсорной позиции и кончая последним символом основной строки. Алгоритм FIND. При заданных шести аргументах, описанных ранее, функция FIND возвращает значение true, если строка PATTERN найдена в любом месте основной строки, начиная с кур- сорной позиции и до конца основной строки. Если строка-образец найдена, то аргумент MATCH—STR получает значение строки, которая начинается с позиции, заданной аргументом CURSOR, и заканчивается символом, предшествующим первому символу выделенной строки. Если выделенная строка начинается с по- зиции CURSOR, то MATCH—STR получает значение пустой строки. Если установлен признак REPLACE_FLAG, то все сим- волы, начиная с исходной позиции CURSOR и кончая самым пра- вым символом образца, заменяются строкой REPLACE—STR. 1. [Проверка попадания строки-образца в Гранины основной строки. 1 Если CURSOR LENGTH (SUBJECT), то установить FIND-*-false н закончить выполнение алгоритма. 2. [Поиск по образцу.] Установить i •<— INDEX (SUB (SUB- JECT, CURSOR), PATTERN)); если i =0, то установить FIND 4- false и закончить выполнение алгоритма. 3. [Установка значений при успешном поиске и замещение. 1 Установить FIND <— true, MATCH—STR <— SUB (SUBJECT, CURSOR, i — 1). Если REPLACE—FLAG, то установить SUB (SUBJECT, CURSOR, LENGTH (PATTERN) + i — 1) REPLACE-STR, CURSOR CURSOR -j LENGTH (REP- LACE__STR) и закончить выполнение алгоритма. 4. [Замещение не задано.] Установить CURSOR CURSOR J- + i 4- LENGTH (PATTERN) — 1 и закончить выполнение ал- горитма . В качестве примера, иллюстрирующего выполнение функции FIND, рассмотрим ситуацию, когда SUBJECT = ’ 1 MET HER THE SUMMER BEFORE I WAS TO ENTER COLLEGE’, PAT- TERN = ’I’, CURSOR = 2, REPLACE_STR = ”, REPLACE- FLAG = true. Если обращение к функции FIND происходит с этими параметрами, то ее выполнение осуществляется следую- щим образом. Значение параметра CURSOR меньше длины основ- ной строки, и поэтому в шаге 2 осуществляется поиск по образцу с помощью функции INDEX. Он оказывается успешным, причем самый левый символ выделенной подстроки находится в позиции 29. Так как обнаружено совпадение с образцом (при этом i = 28), то выполняется шаг 3, в результате чего функция FIND, получает значение true, а параметр MATCH—STR—значение ‘ MET HER THE SUA1MER BEFORE *. Поскольку установлен признак REP- LACE—FLAG, то подстрока, соответствующая MATCH—STR О О PATTERN (т. е. ’ MET HER THE SUMMER BEFORE Г), замещается строкой REPLACE—STR (т. е.“),что дает новую строку ’ I WAS ТО ENTER COLLEGE’. Параметр CURSOR принимает 121
значение, равное исходному значению, увеличенному на длину замещающей строки (т. е. CURSOR = 3). Ниже приведены дополнительные примеры использования функ- ции FIND для основной строки ’ I MET HER THE SUMMER BEFORE I WAS TO ENTER COLLEGE*. FIND (SUBJECT, ’ENTERED’, 1, MATCH-STR, REP- LACE___STR, false) не приводит к успешному поиску по образцу, так как образец ’ENTERED’ отсутствует в основ- ной строке. Следовательно, SUBJECT, MATCH—STR и CURSOR остаются без изменения. FIND (SUBJECT, ’Г, 1, MATCH_STR, ’НЕ’, true) за- вершается выделением подстроки и ее замещением. После этого SUBJECT становится ’НЕ MET HER THE SUMMER BEFORE I WAS TO ENTER COLLEGE’, параметру MATCH—STR присваивается значение пустой строки, а CURSOR устанавливается равным 3. На этом завершается рассмотрение базовых функций для мани- пулирования строками. В следующих двух пунктах мы изучим средства манипулирования строками, имеющиеся в двух языках программирования ПЛ/1 и СНОБОЛ, и сравним эти средства с при- митивными и базовыми функциями, описанными в данном пункте. 2-3.3. Действия иад строками в ПЛ/1 Для эффективного манипулирования строками язык программирования должен содержать по меньшей мере простей- шие средства и функции, описанные в п. 2-3.1 (т. е. переменные типа строки, присваивание строковых данных, конкатенацию и функции SUB, INDEX, LENGHT). Хотя все эти средства с незна- чительными синтаксическими модификациями имеются в языке ПЛ/1, для них характерны два главных различия на семантическом уровне 14]. Отметим прежде всего синтаксические различия. Оператор при- сваивания строки в языке ПЛ/1 обозначается в виде ’ = а не Конкатенация обозначается как ’ || ’, а не знаком О, и вместо SUB используется функция SUBSTR. Следовательно, выражение "Установить X ч-SUB (X, 1, 2,) О ’А’ ”в ПЛ/1 запишем X = - SUBSTR (X, 1, 2) || ’А’. Функции INDEX и LENGTH языка ПЛ/1 выражаются так, как описано в п. 2-3.1. Первое главное семантическое отличие касается типа сим- вольных строк, допустимых в ПЛ/1. До сих пор предполагалось, что все символьные строки были строками с переменной длиной. Это значит, что строка, скажем, X могла по желанию увеличиваться в длину (например, с помощью оператора Установить X X О ’STRING’) или уменьшаться (например, с помощью оператора Установить X ч—SUB (X, 1, 1)). В ПЛ/1 это не имеет места. В ПЛ/1 есть два типа символьных строк — строки с фиксиро- ванной длиной и строки с переменной длиной. Строка фиксирован- 122
ной длины состоит из фиксированного числа символов. Длина та- кой строки определяется предложением описания строки (за- метим, что каждая строка в программе на языке ПЛ/1 должна быть описана). Например, предложение DECLARE S CHARACTER (10); описывает S как переменную типа строки длиной 10 символов. Строки фиксированной длины не могут удлиняться или укорачи- ваться, их длина остается постоянной. Поэтому, если строке фиксированной длины, скажем, строке S, присвоено значение дру- гой более длинной строки, то лишние символы присваиваемой строки отбрасываются с конца перед присваиванием значения строке S. Следовательно, если S имеет значение ’0123456789’, то S = S || ’АВ’ интерпретируется как попытка присвоить значение ’О123456789АВ:,: переменной S. Но так как S представляет строку фиксированной длины 10, то значение ’0123456789АВ’ урезается до ’0123456789’ и затем присваивается переменной S. Если строке фиксированной длины, скажем, строке S, присваи- вается значение более короткой строки, то к присваиваемой строке добавляются справа символы пробела для образования строки, длина которой была бы равна длине строки S. Значение этой новой строки присваивается строке S. Например, если S = = ’0123456789’, то S = SUBSTR (S, 1, 6) рассматривается как присваивание значения ’012345’ переменной S. Однако S представляет строку фиксированной длины 10 и поэтому получает значение ’012345bbbb’, где b интерпретируется как символ пробела. Н*, Второй тип строки в ПЛ/1 допускает изменение длины до мак- симальной объявленной длины (включая и ее). Например, описа- ние символьной строки V с максимальной длиной 10 имеет вид DECLARE V CHARACTER (10) VARYING; При попытке увеличить строку переменной длины больше’макси- мальной происходит отбрасывание символов справа аналогично соответствующей процедуре для строк фиксированной длины. Для иллюстрации основного различия между строкой фиксированной длины (такой, как ранее описанная строка S) и строкой перемен- ной длины (такой, как ранее описанная строка V) рассмотрим сле- дующие четыре оператора языка ПЛ/1: S = ’0123’; V = ’0123’; S - S J) S; V = V Н V; После выполнения этих операторов строка S имеет значение *0123bbbbbb‘, а строка V — значение *01230123’. 123
Второе важное различие, существующее между средствами обработки строк в языке ПЛ/1 и функциями, описанными в преды- дущем пункте, касается выделения подстроки. В языке ПЛ/1 выделение подстроки задается как замещение некоторой подстроки фиксированной длины в строке фиксированной или переменной длины. Для иллюстрации этого допустим, что переменная V опи- сана как сивмольная строка переменной длины с максимальной длиной 10, и начальное ее значение есть ’0123456789’. Тогда выпол- нение функции SUBSTR (V, 2, 3) — ’ABCD’ дает новое значение ’0АВС456789’ строки V. Этот результат является следствием того, что в качестве замещающего образца определена подстрока фик- сированной длины 3, и, значит, дополнительный символ D дол- жен быть отброшен из замещающей строки до выполнения опера- ции замещения. Вследствие применения этого правила замещения подстрокой фиксированной длины функция SUBSTR (V, 2, 3)=’ изменяет начальное значение ’0123456789’ строки V на ’0ЬЬЬ456789’. Отметим, что функции SUB (V, 2, 3) <- ABCD’ и SUB (V, 2, 3) <- ” дают значения ’0ABCD456789’ и ’0456789’ в соответствии с определением функции SUB в п. 2-3.1. В табл. 2-3.1 приведены эквивалентные предложения языка ПЛ/1, необходимые для выполнения ряда замещений подстрок с использованием функции SUB. Эти примеры иллюстрируют многословность и «неестественность» функции SUBSTR языка ПЛ-Ч при использовании ее в режиме замещения. Таблица 2-3.1 Некоторые операции над строками Наша модель Установить SUB (V, 2, 3) <- - ’ABCD’ Установить SUB (V, 2, 3) ’А’ Установить SUB (V, 2, 3) | ” Эквивалент в ПЛ/1 V = SUBSTR (V, 1, 1) || ’ABCD’ || SUBSTR (V, 5) V= SUBSTR (V, 1, 1) || ’A’ || SUB STR (V, 5) V = SUB STR (V, 1, 1) 1| SUBSTR (V, 5) Кроме примитивных функций для манипулирования строками в языке ПЛ/1 имеется ряд других функций обработки строк, ко- торые небесполезны для программиста. Так, VERIFY — это функция двух аргументов, оба из которых могут быть строками. Результат выполнения данной функции — это целое число (типа FIXED BINARY), означающее номер позиции первого символа в первом аргументе, отсутствующего во втором аргументе. Если все символы первого аргумента имеются также и во втором аргу- менте, то результат равен нулю. Например, VERIFY (’ЬЬАВС’, ’Ь’) 124
LEM-. PROCEDURE ISUBJECI ,NUM>CURSOB »HATCH-STR?REPL_STR,REPL_FI.AGl RETURNS <BIT(1)J; DECLARE SUBJECT CHARACTER 1*1 VARYING» NUM FIXED BINARY, CURSOR FIXED BINARY, HATCH_$TR CHARACTER(«) VARYING» REPL-STR CHARACTER!л J VARYING, REPL^FLAG В ITI *) : IF CURSOR ♦ NUM > LENGTH!SUBJECT ) ♦ 1 THEN RtTURNl ‘O’Bl X MATCH—SIR = SUB STR!SUBJECT,CURSOR,NUMJ» IF REPL-FLAG THEN DO, IF CURSOR * NUM x LENGTH! SUBJECT) -*• 1 THEN SUBJECT « SUBSTR ISUBJECT ,l,Ct)RSOR-D II REPL-STR ELSE SUBJECT - SUB STR(SUBJECT,1,CURSOR-1) II REPL-STR 11 SUBSTR ( SUBJECT ,CUR SOR «-NUM ); CURSOR = CURSOR ♦ LENGT H(REPL_STR ) ; ENO: ELSE CURSOR » CURSOR * NUM; RETURN!' 1'6) I ENO LEN: Рис. 2-3.1. Процедура языка ПЛ/1, реализующая функцию I-EN дает в результате 3, в то время как VERIFY(’ЬЬАВС’, ’ABCDb’) дает в результате 0, где ’Ь’ интерпретируется как символ про- бела. Функция REPEAT имеет два аргумента; первый из них — это строка, а второй — целая константа. Результатом выполне- ния этой функции является значение первого аргумента, с которым сцеплено столько его копий, каково значение второго аргумента. Поэтому REPEAT (’АВС’, 3) дает в результате строку ’АВСАВСАВСАВС’. Существуют и другие функции обработки строк, полезные в особых ситуациях. Читателю предлагается обратиться к подходя- щему руководству по языку ПЛ/1 для ознакомления с описаниями функций BIT, BOOL, CHAR, HIGH, LOW, STRING и TRANS- LATE1. Функции LEN, MATCH, SPAN и FIND могут быть выражены в виде процедур языка ПЛ/1, включающих в себя описание строк, операторы присваивания строковых данных, конкатенацию и функции LENGTH, INDEX и SUBSTR. Тексты таких процедур приведены на рис. 2-3.1—2.3.4. Заметим, что они отличаются от алгоритмов, описанных в п. 2-3.2, особенно в отношении исполь- зования функции SUBSTR вместо SUB. Эти процедуры весьма важны, особенно если потребуется за- писать на языке ПЛ/1 некоторые из алгоритмов, рассматриваемые в п. 2-5 в конце этой главы. В этом пункте не были обсуждены концепции, относящиеся к строкам битов в языке ПЛ/1. В п. 2-5.4 средства обработки строк 1 См., например, Скотт Р., Сондак Н. ПЛ I для программистов: Пер. с англ./ Пер. Э. А. Трахтенгерца. М.: Статистика, 1977. 223 с. — При.ч. ред- 125
WATCH: PROCEDURE(SUBJECT,PATTERN-CURSOR,MATCH_STR-REPL_STR,R£PL_FLAG) RETURNS (BIT(D); DECLARE SUBJECT CHARACTERS) VARYING? PATTERN CHARACTERS) VARYING, CURSOR FIXED BINARK, NATCE-STR CHARACTERS) VARYING, RE₽L_STR CHARACTERS) VARYING, REPL-FLAG BITS); IF CURSOR + LENGTH I PATTERN) > LENGTH(SUBJECT) + 1 THEN RETURN!’O'B); IF SU6STR (SUBJECT,CURSOR,LENGTH!PATTERN} ) -.= PATTERN THEN RETURN!’0’6); MATCH-STR = PATTERN; IF REPL-FLAG THEN do; IF CURSOR + LENGTHIPATTERNJ = LENGTH!SUBJECT) ♦ 1 THEN SUBJECT = SUBSTR(SUBJECT,1,CURSOR-1) II REPL-STR; ELSE SUBJECT = SUBSTR!SUBJECT,!,CURSOR-1) II REPL_STR I I SUBSTR!SUBJECT »CURSOR*LENGTH(PATTERNlI; CURSOR = CURSOR * LENGTH!REPL_STR}; END; , ELSE CURSOR = CURSOR + LENGTH(PATTERN); RETURN!’1’B); END MATCH; Рис. 2-3.2. Процедура языка ПЛ/J, реализующая функцию MATCH SPAN t PROCEDURE(SUBJECT,"PATTERN,CURSOR,MATCH_STR,RE PL-STR,REPL_FLAG) RETURNS (BIT |D); DECLARE SUBJECT CHARACTER!’) VARYING, PATTERN CHARACTER!ftl VARYING, CURSOR FIXED BINARY, NATCH-STR CHARACTER!’) VARYING, REPL_STR CHARACTER!’) VARYING, REPL.FLAG BIT!’); DECLARE I FIXED BINARY; IF CURSOR > LENGTH!SUBJECT) THEN RETURN!'0‘B); I » VERIFY(SUBSTR!SUBJECT.CURSOR),PATTERN); IF I = 1 THEN RETURN!«О’B); IF I = 0 THEN DO; HATCH-STR = SUBSTR(SUBJECT,CURSOR); IF REPL-FLAG THEN DO; SUBJECT = SUBSTR!SUBJECT,I,CURSOR-11 I I REPL.STR; CURSOR = CURSOR ♦ LENGTH!REPL—STR)• END; ELSE CURSOR = LENGTH!SUBJECT, ♦ 1; END; ELSE DO; MATCH—STR = SUBSTRISUBJECT,CURSOR,1-1) ; IF REPL-FLAG THEN DO; SUBJECT « SU6STR4SUBJECT,1,CURSOR-1) Il REPL.STR || SUBSTR(SUBJECT,I♦CURSOR-1); CURSOR = CURSOR ♦ LENGTH!REPL-STR); end; ELSE CURSOR = I ♦ CURSOR - 1; ENO; RETURN!’1’B); END SPAN; Рис. 2-3.3. Процедура языка ПЛ/1, реализующая функцию SPAN 126
FIND: PROCEDURE(SUBJECT-PATTERN,CURSOR,M4TCH_STR,REPL_STR,REPL_FLAG) RETURNS (BIT(I)J; DECLARE SUBJECT CFARACTER<») VARYING, PATTERN CHARACTER (<) VARYING, CURSOR FIXED BINARY, MATCH_STR CHARACTER I») VARYING, REPL-STR CHARACTER(») VARYING, REPL_FLAG BITI*-); DECLARE I FIXED BINARY: IF CURSOR > LENGTHtSUBJECT) THEN RETURN('0'B); I = INDEX(SUBSTRtSUBJECT,CURSOR).PATTERN); IF I = О THEN RETURN(’О'Б>; HATCH-STR = SUBSTRtSUBJECT»CURSQR-T-l); 1=1+ LENGTH(PATTERN) - I; IF REPL-FLAG THEN DO; IF CURSOR + I = LENGTH(SUBJECT) + 1 THEN SUBJECT = SUBSTR(SUBJECT,1,CURSOR-11 II RE₽L_STR; ELSE SUBJECT = SU8STR(SUBJECT,!,CURSOR-I) H RE₽L_STR II SUESTR(SUBJECT,CURSOR + I)> CURSOR = CURSOR ♦ LENGTHtREPL STR); END; ELSE CURSOR = CURSOR' + I; RETURN!'1’B); .END Find; Phc. 2-3.4. Процедура языка ПЛ/1, реализующая функцию FIND битов языка ПЛ/] будут обсуждаться в рамках приложений, ка- сающихся информационного поиска. А теперь мы обратимся к язы- ку, специально разработанному для манипулирования строками. 2-3.4. Манипулирование строками в языке СНОБОЛ Рассмотрение марковских алгоритмов в и. 2-2.1 создает прочную основу для ознакомления с языком СНОБОЛ. Этот язык был впервые разработан фирмой Bell Telephone Laboratories в 1962 г. специально для решения задач, включающих действия над строками. Мы исследуем четвертую расширенную версию СНОБОЛа, а именно СНОБОЛ 4. В результате этого исследова- ния станет ясно, что СНОБОЛ содержит средства обработки строк, которые гораздо мощней простых функций манипулирования строками в ПЛ/1. Оператор в СНОБОЛе по форме и определению подобен марков- ской продукции. Он имеет следующий общий вид 1: метка основа образец-заместитель метка-перехода Метка по своему назначению идентична метке в помеченной марковской продукции. В то же время она отличается по форме, так как должна начинаться с первой символьной позиции опера- тора СНОБОЛа, и, кроме того, после нее не ставится двоеточие. 1 Вместо терминов «основа» и «заместитель» используются также термины «субъект» и «объект» соответственно. — Прим. ред. 127
Оператор СНОБОЛа, содержащий пробел в первой символьной позиции, является непомеченным. Основа — это переменная или константа, представляющая основную строку в операциях поиска по образцу, выполняемых данным оператором. Это средство позволяет включать в программу столько же основных строк, сколько в ней имеется операторов. Следовательно, языку присуща гибкость, которая свойственна марковской модели, использующей концепцию ячеек памяти. Образец выполняет роль антецедента марковской продукции Это значит, что образец представляет собой строку, отыскиваемую в основной строке. Образец сопоставлен; если в любом месте основ- ной строки содержится строка-образец. Существует исключение в виде особого способа выполнения операции, называемого спосо- бом закрепления, в котором сопоставление должно начаться с пер- вого символа основной строки. Заместитель — это строка, которая замещает собой сопостав- ленную часть основной строки, если сопоставление с образцом оказалось успешным. Поэтому его роль идентична роли консек- вента марковской продукции. Метка-перехода в СНОБ О Ле отличается от метки перехода марковской продукции в том смысле, что в языке переход можно определить как при неудачном сопоставлении, так и при удачном. Переход задается двоеточием, за которым следует F (метка), S (метка), (метка) или же вместе F (метка) и S (метка). F (метка) означает переход на указанную метку, если сопоставление с образ- цом неудачно, S (метка) — в случае успешного сопоставления с образцом, а (метка) интерпретируется как безусловный переход (т. е. переход на метку независимо от результата сопоставления с образцом) г. Последовательность управления при выполнении операторов такая же, как и в помеченной марковской продукции, в том смысле, что если ие осуществляется переход, то выполняется очередной оператор программы. Следующий набор операторов СНОБОЛа осуществляет вывод строки, представленной именем SUBJECT, после удаления из нее всех символов ’А1, SUBJECT = INPUT AGAIN SUBJECT ’A’ = ” : S (AGAIN) ‘ OUTPUT = SUBJECT END Ввод данных для программы обеспечивается с помощью псевдо- переменной INPUT, а вывод — присваиванием значения строки, предназначенной для вывода, псевдопеременной OUTPUT. Во втором операторе программы с содержимым строки SUBJECT * F и S — начальные буквы слов failure (неудача) и success (успех). — Прим. пер. 128
сопоставляется односимвольная строка ’А’. Самый левый символ ’А’ основной,строки сопоставляется первым и замещается пустой строкой. Успешное сопоставление приводит к немедленному возврату на этот же самый оператор, после чего снова предпри- нимается попытка сопоставления с образцом. Этот процесс про- должается до тех пор, пока в строке, представленной именем SUBJECT, не останется символов ’А’. После этого сопоставление с образцом оказывается безуспешным, и управление передается на третий оператор, в котором выводится текущее значение строки SUBJECT. Программа завершается после выполнения опера- тора END. Эта программа иллюстрирует важный момент, а именно, что оператор СНОБОЛа не всегда должен содержать строку-образец (как показано в первом и третьем операторах). Отметим также, что сопоставление с образцом всегда начинается с первого символа строки SUBJECT. Это значит, что если SUBJECT имеет вначале значение 'САВВАС’, то после первого успешного сопоставления с образцом и замещения получается значение ’СВВАС’. При вто- ром выполнении оператора с меткой AGAIN курсор возвращается к позиции первого символа ’С’, и сопоставление с образцом начи- нается с этого символа, а не с того места, в котором успешно завершилось последнее сопоставление с образцом (т. е. не с пер- вого ’В’). Следовательно, до второго успешного сопоставления необходимо повторное сканирование строки. Хотя это может показаться неэффективным, повторное сканирование основной, строки необходимо в некоторых случаях. Один из них мы рас- смотрим в п. 2-5.1. СНОБОЛ существенно отличается от ПЛ/1 во многих аспектах. Одно фундаментальное отличие заключается в том, что в про- грамме на СНОБОЛе не требуется описания переменных. Хотя в СНОБОЛе имеются целые, вещественные и символьно-строковые типы данных, истинный тип переменной в данной точке программы диктуется контекстом, в котором появляется эта переменная.. Например, переменная X соответствует трем различным типам (а именно, выступает как целая переменная, вещественная пере- -менная и строка символов) в контекстах целочисленного сложения, вещественного умножения и конкатенации строк, что иллюстри- руется следующими тремя операторами: X = X + 2 X = X * 2.0 X = X “STRING* Заметим, что поскольку конкатенация выполняется весьма' часто, то для ее выражения в языке нет специального оператора. Строки, подлежащие конкатенации, просто записываются рядом одна за другой, с одним или более пробелами между ними. 5 Трамбле ДС, Соренсон П, 129
Двумя очень важными понятиями в СНОБОЛе являются образец-переменная и образец-структура. Образец-перемеиной присваивается значение образец-структуры. Простейшая форма образец-структуры — это строка. Например, в операторах РАТ = ’ERE’ SUBJECT = ’THERE HOME IS OUR HOME* SUBJECT PAT = ’EIR’ OUTPUT = SUBJECT PAT — это образец-переменная, значением которой является образец-структура ’ERE’. Можно сформировать более сложные образец-структуры с по- мощью двух операций альтернации и конкатенации. Операция альтернации 1 (обозначается в виде | ) полезна при описании образца, который учитывает возможность ряда значений одной и той же основной строки в данной ситуации сопоставления. На- пример, если мы хотим заменить все цифры в строке символом ’^+’, то для этого можно использовать следующую программу (переменная ГЕХТ имеет значение заданной строки): DIGIT = ’О’| ’1’ |’2’ |’3’ |’4’ |’5’ |’6’ j’7* |’8’ j’9’ TEXT = INPUT LOOP TEXT DIGIT = ’# : S (LOOP) OUTPUT = TEXT END Образец-структура для переменной DIGIT означает, что достигается успешное сопоставление, если любой из символов ’0’ или ’Г, или ’2’, ..., или ’9’ найден в основной строке, в кото- рой выполняется поиск по образцу. Сопоставление осуществляется для самой левой цифры в основной строке, и эта цифра замещается символом ’4ф’. Следовательно, если мы проследим значение строки TEXT в ходе выполнения программы, полагая начальное значение строки TEXT равным ’Мау 14,1942’, то получим последователь- ность ’Мау 14,1942’, ’Мау 4,1942’, 'Мау +£#, 1942’, ’Мау ####' Чтобы проиллюстрировать использование конкатенации, пред- положим, что мы хотим написать программу, которая подсчиты- вает число слов, оканчивающихся на гласный звук, в заданной строке, представляемой переменной TEXT: ENDTNG = ’А ТЕ ’|’I ’|’ O’l’U ’ TEXT = INPUT LOOP TEXT ENDING = F (OTPT) COUNT = COUNT + 1 : (LOOP) OTPT OUTPUT = ’ЧИСЛО СЛОВ С ГЛАСНЫМ В КОНЦЕ РАВНО’ COUNT _________ END 1 Ее называют также операцией альтернативного обозначения.—Пр им. ред. 130
В операторе с меткой LOOP строка TEXT сканируется с целью обнаружения двухсимвольной последовательности, начинающейся с гласного, за которым следует пробел. Если найдена такая после- довательность, то она заменяется строкой '4ф’, и счетчик COUNT (которому вначале автоматически присваивается значение пустой строки, а оно, в свою очередь, преобразуется в целый нуль в соот- ветствии с типом четвертого оператора) наращивается на единицу. После этого происходит возврат на оператор с меткой LOOP с целью выполнить еще одно сопоставление с образцом. Этот циклический процесс продолжается до тех пор, пока очередное сопоставление с образцом не окажется безуспешным, и в этом случае осуществляется переход на оператор, обеспечивающий вывод результата 1. Образец ENDING можно упростить, используя конкатенацию. Мы можем записать ENDING в виде ENDING = (’A’ fE’fl’fO’ |’U’) ”, где каждая из альтернатив сцеплена с символом пробела. Отметим, что мы не можем записать ENDING == ’А’ |’Е’ |Т |’О’ |’U’ ”, так как операция конкатенации приоритетней операции альтерна- ции, и поэтому этот оператор будет интерпретироваться как ENDING = ’А’ |’Е’ [’Г J’O’ |’U Двумя операциями, которые чрезвычайно полезны, а иногда и необходимы при использовании образец-структур в связи с вы- полнением операции альтернации, являются операция условного присваивания значения и операция немедленного присваивания значения. В предыдущем примере у нас не было способа опреде- лить, для какого именно из альтернативных образцов сопоставле- ние оказалось успешным. Если нам нужна эта информация, то ее можно получить с помощью операции условного присваивания значения (она обозначается точкой -). Если ENDING вы- ражено в виде ENDING = ((’А’ | ’Е’ fl’ |’О’ f U’) ”)• RESULT, то успешно сопоставленная образец-строка, соответствующая одной из альтернатив, присваивается переменной RESULT. С помощью оператора LIST = LIST RESULT, помещенного хмежду оператором с меткой LOOP и оператором модификации счетчика COUNT, может быть составлен список всех сопоставлений с образцом. 1 Предполагается, что в алфавит языка включены буквы кириллицы.— Прим. пере в. 5* 131
Если в ходе процесса сопоставления с образцом требуется получить промежуточные результаты, то это можно сделать с по.- мощью операции немедленного присваивания (она обозначается $). Например, если в программе желательно иметь распечатку всех гласных для обнаружения слов, которые оканчиваются на гласный, то образец ENDING следует записать как ENDING = (’Д’ |’Е’ |’Г |’О’ |’U’) $ OUTPUT ”. В этом случае результат любого частичного совпадения с образ- цом, связанного с гласным, присваивается псевдопеременной OUTPUT и, следовательно, выводится на печать. Поэтому, если TEXT = ’THE DOG RAN HOME’ и образец ENDING определен так, как было только что указано, то в результате выполнения последнего запрограммированного примера на печать будет вы- дано Е О ' А О Е ЧИСЛО СЛОВ С ГЛАСНЫМ В КОНЦЕ РАВНО 2 Сравнение функции MATCH, алгоритм выполнения которой был описан в п. 2-3.2, и только что рассмотренных средств поиска по образцу, содержащихся в СНОБОЛе, показывает, что в обоих случаях имеются возможности сопоставления строки-образца с той или иной подстрокой основной строки, замены выделен- ной подстроки замещающей строкой (с помощью параметра REPLACE_STR функции MATCH и оператора присваивания «=» в СНОБОЛе) и присваивания значения выделенной строки стро- ковой переменной (посредством параметра MATCH_STR функции MATCH и операции условного присваивания «•» в СНОБОЛе). Главное различие между этими двумя средствами состоит в том, что функция MATCH осуществляет поиск по образцу в основной строке, начиная с позиции, номер которой указан параме- тром CURSOR, в то время как в общем операторе поиска по об- разцу языка СНОБОЛ сопоставление проверяется в процессе сканирования основной строки слева направо для всех возможных подстрок. Теперь мы можем описать и привести примеры других более развитых функций поиска по образцу, имеющихся в СНОБОЛе. Функция ЕЕГЧ(целое) возвращает образец, с которым сопоста- вляется любая строка, с длиной, указанной целочисленным аргу- ментом. Поэтому SUBJECT LEN (5)-Х = выделяет первые пять символов в строке SUBJECT и с помощью операции условного присваивания значения присваивает их 132
переменной X (если длина строки SUBJECT больше 4). Если строка с заданной длиной выделена, то соответствующая часть основной строки замещается пустой строкой. Присваивание зна- чения пустой строки можно выразить пустой правой частью оператора либо с помощью операции присваивания значения пустой строки ”. Оператор „ LOOP TEXT (” LEN (3) ” )-WORD = ” : S (LOOP) обеспечивает поиск и удаление всех трехбуквенных слов в строке TEXT, после которых отсутствует знак препинания. Функция LEN, алгоритм выполнения которой описан в п. 2-3.2, и функция LEN в СНОБОЛе почти идентичны и отличаются лишь способом управления курсором. В первохМ случае CURSOR задается как явный параметр, в то время как в СНОБОЛе курсор всегда указывает на следующий символ в процессе поиска по образцу- Отметим также, что в первом случае функция LEN возвращает значение true или false, а функция LE N в СНОБОЛе — образец. Функция SPAN(CTpoKa) возвращает образец, сопоставляемый с самой длинной подстрокой основной строки, начинающейся с текущей курсорной позиции и содержащей только символы строки-аргумента. Поэтому операторы NUMB_STR-SPAN ('0123456789') TEXT NUMB_STR-NUMBER формируют образец, совпадающий с первой подстрокой цифр в строке, которую представляет переменная TEXT. Следова- тельно, если TEXT = ’А0932—716’, то будет выделена подстрока ’0932’ и присвоена переменной NUMBER. Однако если TEXT — = ’АВС — DC’, то поиск по образцу окажется безуспешен. За- метим, что мы можем всегда записать эту и другие функции по- иска по образцу в одном операторе СНОБОЛа (например, TEXT SPAN (’0123456789’)-NUMBER). Однако понимание программы часто облегчается, если отделить формирование образец-структуры от оператора поиска по образцу. Функция SPAN в СНОБОЛе и функция SPAN, описанная алгоритмически в п. 2-3.2, идентичны по своей цели, но разли- чаются по выполнению. SPAN в СНОБОЛе выделяет подстроку, начиная с первого же символа, соответствующего любому символу строки-аргумента. Алгоритмически описанная функция SPAN начинает поиск по образцу с заданной курсорной позиции. Кроме того, эта функция возвращает значение true или false, а функция SPAN в СНОБОЛе — только образец. Функция В КЕАК(строка) возвращает образец, сопоставля- емый с самой длинной подстрокой основной строки, содержащей символы, которых нет в строке-аргументе. Следовательно, эта 133
функция обратна по отношению к функции SPAN. Например., следующая программа: AGAIN TEXT BREAK (’,.!? : ;*)-WORD = : F (END) OUTPUT = WORD TEXT SPAN (’ ,.!?:;’) = : (AGAIN) END может быть использована для вывода всех слов строки TEXT, за исключением разделяющих их символов пробела. Отметим, что процесс поиска по образцу продолжается до тех пор, пока строка TEXT не станет пустой при условии, что в исходном со- стоянии TEXT заканчивается знаком препинания. Как только это произойдет, очередной цикл поиска по образцу оказывается неуспешным, и программа завершается. Заметим, что функция BREAK совершенно подобна функции FIND, описанной в п. 2-3.2, с той лишь разницей, что для функ- ции FIND процесс поиска по образцу является успешным в слу- чае, когда выделена подстрока, совпадающая с аргументом PATTERN полностью, а не частично. При этом все символы основ- ной строки, стоящие до выделенной подстроки и включая ее, могут быть замещены строкой REPLACE_STR. Функция ТАВ(целое) возвращает образец, сопоставляемый с подстрокой, начинающейся с текущей курсорной позиции основ- ной строки и заканчивающейся отмеченной позицией, указываемой целым аргументом Функцию TAB можно использовать для того, чтобы перемещать курсор в определенные позиции при обра- ботке сложноформатных основных строк. Например, оператор TEXT TAB (10) ’SEX? LEN (1).SEXTYPE реализует успешный поиск по образцу, если подстрока ’SEX:’ непосредственно следует за первыми десятью символами строки TEXT. Если обнаружена подстрока ’SEX:’, то следующий за ней символ выделяется с помощью функции LEN (1) и присваивается переменной SEXTYPE. В языке имеется также функция RTAB. Она выделяет все сим- волы, начиная с текущей курсорной позиции вплоть до отмечен- ной позиции (но не включая ее), заданной целым аргументом, который указывает число позиций с конца основной строки. Объединяя функции TAB и RTAB, можно записать оператор TEXT TAB (10) RTAB (lO)-MIDDLE для присваивания переменной MIDDLE значения подстроки в TEXT, не содержащей первых и последних десяти символов основной строки TEXT. Очевидно, если длина строки TEXT 1 При условии, что курсор находился до этого слева от заданной познцн н.— Прим., ред. 134
меньше двадцати символов, то выделение подстроки оказывается безуспешным. Функция РО5(целое) возвращает образец, сопоставляемый с пустой строкой, если курсор находится в той позиции основной строки, которая указана целым аргументом. Поэтому оператор TEXT ’SEX:* POS;<14) LEN (l)-SEXTYPE обеспечивает успешное сопоставление, если подстрока ’SEX:’ располагается в строке TEXT непосредственно перед четырнадца- тым символом, включая и его. Этот пример и первый пример опе- ратора с функцией TAB функционально эквивалентны. Отметим, однако, что POS используется для проверки текущего положения курсора, в то время как TAB вызывает перемещение курсора. В частности оператор TEXT BREAK (’ ’) POS (5) завершается успешным сопоставлением только в том случае, когда первые четыре символа основной строки — не пробелы, а пя- тый — пробел. Имеется также функция RPOS, которая возвращает образец, сопоставляемый с пустой строкой, если значение целого аргу- мента совпадает со значением курсорной позиции, считая с конца основной строки. Прежде чем закончить рассмотрение средств обработки строк в СНОБОЛе, следует перечислить ряд функций, которые по своей природе не относятся к функциям поиска по образцу, но тем не менее имеют важное значение для обработки строк. Функция SIZE(cTpOKa) возвращает длину строки- аргумента. Поэтому если строка TEXT имеет зна- чение ’DATA’, то SIZE (TEXT) возвращает зна- чение 4. Функции SIZE н LENGTH идентичны. Функция DUPL(cTpoKa, целое) возвращает строку, состоящую из повторений строк-аргумента (DUPL подобна, но не идентична функции REPEAT языка ПЛ/1). Значит, DUPL (’АВС’, 3) выдает строку ’АВСАВСАВС’. Условная функция (предикат) в СНОБОЛе возвращает значе- ние пустой строки, если проверяемое условие истинно. В против- ном случае оператор, содержащий проверку условия, не выпол- няется. Кроме операций отношения для арифметических выра- жений, таких как LT, LE, GT, GE, EQ и NE, в языке СНОБОЛ имеется операция отношения для строк LGT. Префикс L в назва- нии этой операции означает «лексически», и предикатная функция LGT возвращает пустую строку (т. е. значение true), если значение первого аргумента функции больше значения второго аргумента функции в соответствии с лексическим упорядочением, определя- емым сопоставляющей последовательностью набора символов 135
ЭВМ. Поэтому LGT (’В’, ’А’) имеет значение true, a LGT (’А’ ’В’) — значение false. Условные функции могут находиться лишь в правой части операторов присваивания, при этом присваивание является услов- ным и зависит от истинности условной функции. Например, программа BEGIN LINE = INPUT :F (OUT) J = LT (J, 5) J 4- 1 :S (BEGIN) OUTPUT = ’БОЛЕЕ 5 КАРТ’ :(END) OUT OUTPUT = ’МЕНЕЕ 6 КАРТ’ END' обеспечивает считывание перфокарт до тех пор, пока не будет введено шесть перфокарт (т. е. строчек) или не кончатся вводимые перфокарты. Последний случай отмечается как безуспешное за- вершение оператора ввода. В качестве последнего примера рассмотрим задачу преобразо- вания входной записи данных в виде CHARLES W. SMITH 9872 в выходную запись, имеющую форму SMITH, С. W. $ 98.72 Входная запись состоит из имени (содержащего собственно имя» инициал и фамилию), за которым следует сумма расходов в цен“ тах. На формат входных записей не наложено никаких ограниче" ний, за исключением того, что каждая запись размещается на одной перфокарте. Конец потока входных записей обозначается картой, содержащей символ в колонке 1. Каждая выходная запись состоит из имени (содержащего фа- милию, запятую и два инициала), за которым следует сумма рас- ходов, представленная в долларах. Требуется, чтобы текст вы- ходной записи начинался с колонки 5, а сумма расходов — с ко- лонки 50. Желаемую форматизацию данных выполняет следующая про- грамма на СНОБОЛе: ♦ КОММЕНТАРИИ В СНОБОЛЕ ОБОЗНАЧАЮТСЯ * В ПЕРВОЙ ПОЗИЦИИ. ♦ СОЗДАНИЕ ОБРАЗЦОВ, ИСПОЛЬЗУЕМЫХ В ПРОГРАММЕ BLANKS = SPAN (' ’) | ” FIRST— INITIAL_PATTERN = BLANKS LEN (I)-FIRSTINIT BREAK (’ ’) BLANKS SECOND_IN1TIAL_PATTERN = LEN (I)-SECONDINIT BREAK (’ ’) BLANKS SURNAMEJPATTERN = BREAK (’ ’)• SURNAME AMOUNT_PATTERN = BLANKS BREAK f ’)• AMOUNT ♦ ГЛАВНАЯ ЧАСТЬ ПРОГРАММЫ START CARD = INPUT CARD POS (I) : S (END) CARD J FIRST JNITIAL_PATTERN = ’ CARD SECOND_INIT1ALJPATTERN= 136
CARD SURNAME-PATTERN = CARD AMOUNT-PATTERN = * РАЗДЕЛИТЬ СУММУ НА ДОЛЛАРЫ И ЦЕНТЫ AMOUNT RTAB (2)-DOLLARS LEN (2)-CENTS * СФОРМИРОВАТЬ ВЫХОДНУЮ ФОРМУ NEWNAME = DUPL (”, 4) SURNAME ’» ’ FIRSTINIT * . * SECOND1N1T • . ’ EXPENDITURE = ’$ * DOLLARS ’ . ’ CENTS OUTPUT = NEWNAME DUPL (”,49 —LENGTH (NEWNAME)) EXPENDITURE: (START) END СНОБОЛ — весьма развитый и интересный язык программи- рования. Материал данного пункта представляет собой лишь крат- кое введение в этот язык с упором на некоторые важные его сред- ства обработки строк. Для полного усвоения языка читатель от- сылается к учебнику Р. Грисуолда и М. Грисуолда (51. Не удиви- тельно, что в языке СНОБОЛ имеются все примитивные и базовые функции обработки строк, рассмотренные в п. 2-3.2. Кроме того, СНОБОЛ содержит многЪ дополнительных возможностей. Одной из таких возможностей, которую мы исследуем, является рекур- сивное определение образец-структуры. 2-3.5. Рекурсивные образец-структуры Из рассмотрения предыдущего материала главы должна стать понятной важность операции поиска по образцу как одного из средств манипулирования строками. В данном пункте мы рас- ширим рамки обсуждаемой темы поиска по образцу, введя понятие рекурсивной образец-структуры. Несколько примеров рекурсив- но-определяемых объектов нам уже встречалось в п. 2-2.2, в ко- тором было введено понятие рекурсивно-определяемой граммати- ческой продукции. Напомним, что рекурсивное определение целого числа без знака имеет вид (целое без знака) :: = (цифра) | (целое без знака) (цифра) (цифра) ::=0|1|2|3|4|5[6|7|8|9 Следовательно, целое число без знака определяется либо как ’единственная цифра, либо как целое число, состоящее из цифры, за которой следует цифра, либо как целое число, состоящее из двух цифр, за которыми следует цифра, и т. д. Давая сжатое определе- ние, продукции, порождающие целое число без знака, обеспечи- вают в то же время распознавание любого элемента бесконечного множества целых чисел без знака. Отметим попутно, что процесс распознавания строк есть одна из форм поиска по образцу. В СНОБОЛе рекурсивные образец-структуры могут быть опре- делены с помощью оператора невычисляемого выражения *. Этот оператор задерживает вычисление своего операнда до тех пор, пока в процессе поиска по образцу не потребуется значение операнда. 137
Начнем описание оператора * с иллюстрации его использо- вания при определении нерекурсивной образец-структуры. В та- ком операторе, как РАТ = TAB (N) LEN(1)-X формируемый образец задается частично значением величины N в момент выполнения операции присваивания. В СНОБ О Ле удобно и принято определять все образец-структуры в начале программы, чтобы таким образом избежать частых повторных вычислений в тех случаях, когда образцы используются регу- лярно. Для того чтобы сохранить это удобство и вместе с тем обеспечить возможность изменения образца в зависимости от не- которого параметра, например N, мы будем применять оператор *. Например, если переменную РАТ определить как РАТ = TAB (*N) LEN (1)-Х, то оператор * предотвращает выполнение оператора присваива- ния до тех пор, пока РАТ не будет использована как образец в другом операторе СНОБОЛа, например таком: LAB SUBJECT PAT == Теперь при каждом выполнении оператора с меткой LAB сначала вычисляется образец, что, таким образом, допускает различные значения N и, следовательно, формирование нового образца при каждом использовании переменной РАТ. Эта возможность задерживать вычисление образца необходима при формировании рекурсивной образец-структуры. Например, операторы СНОБОЛа DIGIT = ’О’ | ’1’ | ’2’ | ’3’ | ’4’ ] ’5’ | ’6’ | ’7’ | ’8’ | ’9’ UINTEGER = DIGIT | *UINTEGER DIGIT определяют образец, соответствующий целому числу без знака- Поэтому в операторах SUBJECT = ’Al 3982’ SUBJECT UINTEGER = образец UINTEGER сначала вычисляется как одна из альтерна- тив образца DIGIT, что приводит к выделению символа ’1’ в основной строке. Следующий числовой символ ’3’ выделяется вместе с ’1’ посредством второй альтернативы для UINTEGER, а именно UINTEGER DIGIT. Это оказывается возможным по- тому, что UINTEGER можно заменить на DIGIT и, следовательно, вторая альтернатива представляет собой образец DIGIT DIGIT. Аналогично строка ’139’ выделяется с помощью альтернативы *UINTEGER DIGIT, где ^UINTEGER теперь заменяется на DIGIT DIGIT. Нетрудно видеть, как рекурсивно выполняется далее этот процесс и выделяет всю цифровую строку ’13982’, т. е. любую цифровую строку, образующую целое число без знака. 138
В качестве еще одного примера рассмотрим рекурсивную обра- зец-структуру, которая могла бы быть применена для определения идентификатора: LETTER - ’A’ J ’В’ | ’С’ | ’D’ | ’Е’ | ’F’ | ’G’ | ’Н’ | ’Г I ’J’ | ’К’ I ’L’ I ’М’ I ’N’ I ’О’ I ’Р’| ’Q’ | ’R’ | ’S’ ] Т’ | ’U’ | ’W’ V [ ’X’ | ’Y’ I ’Z’ DIGIT = ’0’ | ’1’ | ’2’ | ’3’ | ’4’ | ’5’ I ’6’ | ’7’ | ’8’ [ ’9’ IDENTIFIER = LETTER | «IDENTIFIER LETTER | «IDENTIFIER DIGIT Эти операторы описывают образец-структуру, состоящую из множества строк, причем каждая строка данного множества пред- ставляется одной буквой или буквой, за которой следует произ- вольное число алфавитно-цифровых символов. В списке примитивных и базовых функций манипулирования символами, рассмотренных в пп. 2-3.1 и 2-3.2, нет оператора или функции, которые были бы подобны оператору невычисля- емого выражения (*) в СНОБОЛе. Тем не менее с помощью рекурсивного обращения к функциям поиска по образцу можно смоделировать рекурсивную образец-структуру. Например, целое число без знака можно рекурсивно сканировать с помощью двух алгоритмов. Первый алгоритм UINTEGER выделяет первую цифру в основной строке, а затем вызывает второй алгоритм DIG 1Т_SCAN, который выделяет строку цифр, образующих первое слева вхождение целого без знака в основной строке. Это выделение достигается рекурсивными обращениями к алго- ритму DIGIT_SCAN. Алгоритм UINTEGER. Заданная строка SUBJECT скани- руется до тех пор, пока не будет найдена первая (т. е. самая ле- вая) цифра. После этого для выделения самого левого целого числа’без знака вызывается алгоритм DIGIT-SCAN. Шесть пара- метров процесса поиска по образцу имеют смысл, определенный в п. 2-3.2. 1. [Начальная установка.] Установить CURSOR ч-1. 2. [Сканирование до первого числового символа. ] Повторять до тех пор, пока LEN (SUBJECT, 1, CURSOR,MATCH_STR, REPLACE_STR, false) и MATCH_STR < ’O’. (В операции сравне- ния строк знак < означает, что числовые символы от 0 до 9 нахо- дятся справа от всех других символов в упорядоченной последо- вательности. При выполнении функции LEN параметр CURSOR автоматически наращивается, если поиск числового символа оказался успешным.) 3. [Обнаружен числовой символ? 1 Если MATCH-STR < ’О’, то установить UINTEGER ч- (В строке SUBJECT отсутствуют цифры), в противном случае установить UINTEGER ч- 139
*— DIGIT-SCAN (SUBJECT, CURSOR). (T. e. в этом случае воз- вратить цифровую строку как целое число без знака.) 4. [Конец. I Закончить выполнение алгоритма. Алгоритм DIGIT_SCAN осуществляет рекурсивную конкате- нацию цифровых символов строки SUBJECT с целью формиро- вания строки цифр, образующих целое число без знака. Алгоритм DIGIT_SCAN. Заданы строка SUBJECT и значение курсора CURSOR. Требуется выделить, начиная с символа строки SUBJECT, указываемого курсором, строку цифровых символов, имеющую максимально возможную длину. 1. [Проверка конца цифровой строки.] Если LEN (SUBJECT, 1, CURSOR, MATCH STR, REPLACE_STR, false) и MATCH_STR> > ’O’,то установить DIGITSCAN «-MATCH.STR О DIGIT.SCAN (SUBJECT, CURSOR); в противном случае установить D1GIT_SCAN (Возвратить значение пустой строки.) 2. [Конец.] Закончить выполнение алгоритма. Алгоритм DIGIT.SCAN выделяет цифры и затем сцепляет их с результатом предыдущих обращений к этому алгоритму. Напри- мер, если SUBJECT = ’MAY 1980', и CURSOR имеет вначале значение 5, то последовательность выполнения алгоритма DIGIT.SCAN отображается в табл. 2-3.2. Отметим, что в процессе сканирования строки SUBJECT осу- ществляются четыре рекурсивных обращения к алгоритму DIGITSCAN. При каждом таком обращении возврат совершается в точку вызова в шаге 1 [т. е. на оператор DIGIT-SCAN ч-MATCH.STR О DIGIT,SCAN (SUBJECT, CURSOR)], и вы- деленный символ сцепляется с тем значением строки MATCH_STR. которое она имела в момент обращения к алгоритму DIGIT.SCAN. Здесь важно то, что при рекурсивных обращениях к алгоритму Таблица 2-3.2 Последовательность выполнения алгоритма DIGIT—SCAN ’ Шаг CURSOR MATCH-STR DIGIT-SCAN I 5 ’Г ? 1 6 9’ ? 1 7 ’8’ ? 1 8 ’0’ ? 1 8 Возврат к шагу 1 н выход в шаге 2 8 ’0- • •0’ То же 8 ’8’ 80’ » 7 ’9’ 980’ » Возврат в алгоритм U1NTEGER 6 ’1’ ’1980’ 140
промежуточные [значения сохраняются в строке MATCH-STR. Рекурсивные функции более глубоко обсуждаются в п. 3-7.1, где будет, в частности, объясняться, как эти промежуточные значения запоминаются между соседними обращениями к рекур- сивной функции. Заметим, что алгоритм DIG1T_SCAN может быть реализован и без использования рекурсии. Для этого пригоден итеративный подход, такой как удаление головных нецифровых символов строки алгоритмом U INTEGER. Однако приведенная версия алго- ритма DIGIT„SCAN иллюстрирует метод рекурсивного поиска по образцу с использованием базовых функций обработки строк. При решении многих задач составить рекурсивный алгоритм легче, чем итеративный алгоритм. С примерами таких задач мы встретимся в гл. 5 при обсуждении прохождения деревьев. Упражнения к п. 2-3 1. При обсуждении примитивных функций манипулирования стро- ками мы ввели в рассмотрение операцию конкатенации и функции INDEX, SUB и LENGTH. Может ли любая из этих функций или операций быть выра- жена в терминах остальных функций? Если может, то покажите это. 2. Чем отличается функция VERIFY языка ПЛ/l а) от базовой функции манипулирования строками SPAN и б) от функции SPAN СНОБОЛа? 3. Составьте алгоритм для дублирования заданной символьной строки. Например, если задан аргумент в виде строки ’WAKA’. то алгоритм DUPL должен генерировать строку ’WAKAWAKA’. 4. Составьте алгоритм, который отбрасывает все хвостовые пробелы сим- вольной строки. Например, если задан аргумент’НЕ ENDED IT ALLIbbbbb’, то этот алгоритм должен выделить строку ’НЕ ENDED IT ALL!’ 5. Разработайте алгоритм, который имитирует функцию поиска по об- разцу BREAK. Для заданных параметров — основной строки, образец-строкн, начальной курсорной позиции, первоначально пустой выделенной строки, за- мещающей строки и условия замещения (true или false), этот алгоритм должен посимвольно сканировать основную строку, начиная с заданной курсорной по- зиции вплоть до первого символа, содержащегося также и в образце. Если такой символ найден, то: а) алгоритм должен завершиться значением true; б) параметру выделенной строки должно быть присвоено значение подстроки, начинающейся в основной строке с исходной курсорной позиции и заканчивающейся найденным символом (но не включая его); в) замещающая строка заменяет собой выделенную под- .строку в основной строке, если условие замещения имеет значение true; г) после замещения курсор устанавливается в позицию найденного символа. Если же в основной строке не обнаружен хотя бы одни символ, имеющийся в образце, то алгоритм должен завершиться значением false, и при этом никакие другие изме- нения не должны происходить. Поэтому» если даны основная строка SUBJECT со значением ’’’THIS”, НЕ SAID, ’’CANNOT BE SO”.*, образец PATTERN co значением курсорная позиция CURSOR, имеющая значение I, н за- мещающая строка REPLACE_STR в виде пустой строки ”, то функция BREAK (SUBJECT, PATTERN, CURSOR, MATCH-STR, REPLACE.STR, true) преобразует основную строку к виду НЕ SAID, ” CANNOT BE SO | Параметр MATCH.STR принимает значение ’ ’’THIS” a CURSOR — зна- чение 1. 141
6. Что будет выдано на печать в результате выполнения следующих one раторов языка ПЛ/1: а) I = INDEX (’TESTSTRING’, ’Т’); PUT LIST (’! = ’, I); 6) STRING = ’LIST OF LETTERS’; SUBSTR (STRING, 9, 8) == ’NUMBERS’; PUT LIST (’STRING = ’, STRING); в) NUMBERS == ’01234567890’; PUT LIST (’VERIFICATION OF 35’, VERIFY (NUMBERS, ’35’)); 7. Напишите программу на ПЛ/1 с именем DELETE для удаления из одной строки всех вхождений каждого из символов, имеющихся в другой строке. За- даны две следующие строки: a) STR — строка, из которой требуется удалить символы; б) LIST — строка, содержащая символы, которые должны быть удалены из STR. Например, если STR - ’THEbEZNZZXDX’ и LIST = ’XZ’, то требуемый ответ будет STR - ’THEbEND’. 8. Напишите: а) программу на ПЛ/1 или б) программу иа СНОБОЛе, кото- рая могла бы определять, соответствует или ие соответствует предложение опре- деленному образцу. Например, пусть образец имеет следующий вид: THE ------- BELONGS ТО THE---------- Этому образцу соответствуют, в частности, такие предложения; THE BOOK BELONGS ТО |TH F LIBRARY. THE CAT BELONGS TO THE FORD FAMILY. 9. Одной из интересных задач лингвистики является анализ текстов. Допу- стим, что мы хотим разработать алгоритм для анализа рукописи с целью полу- чения следующих данных: а) число предложений в рукописи; б) среднее число слов в предложении; в) среднее число символов в слове. Допустим, что: а) каждое предложение в тексте отделяется от следующего точкой и двумя. пробелами, причем точки ие могут появлятьси в предложении; б) слова в предложении отделены друг от друга одним пробелом; в) запятые, точки с запятой, точки и дефисы ие подсчитываются как сим- волы; г) последнее предложение в рукописи отмечается наклонной чертой в по- зиции, следующей за точкой и двумя пробелами; д) слова не переносятся с одной строчки иа другую, и за последним словом каждой строчки следует, по крайней мере, один пробел; е) первому слову в каждой строчке предшествуют пробелы только в том случае, если оно является первым словом параграфа. Напишите программу на ПЛ/1 или СНОБОЛе для решения этой задачи. 10. Заданы следующие выражения: (выражение) : : = (выражение) - j- (терм) | (терм) (терм) : : = (форма) * (терм) | (форма) (форма) : : == I Запишите на языке СНОБОЛ рекурсивно определяемый образец для распозна- вания строк ’I 4-1 ’, ’I * I I’ и ’Г. Такая строка, как ’I 4- I I’, распозна- ваться не должна. 2-4. МАШИННОЕ ПРЕДСТАВЛЕНИЕ СТРОК Строка — это последовательность символов и, как тако- вая, наиболее естественно представляется в последовательных ячейках памяти ЭВМ. В п. 1-4.8 было указано, что символы вы- 142
С/Ю8О L Слабо 1*1 * A f Пробел Пробел Пообел | 01011Ю0 | паши | 111Ю001 | слюда» j Х'5С X'Cf Х'Н' X'W XW Х'1Ю' Рис. 2-4.1. Представление символьной строки фиксированной длины ’❖А! ' в коде EBCDIC ражаются двоичными кодами, которые обычно имеют длину 6, 7 или 8 битов. Поэтому почти во всех вычислительных машинах в одном слове памяти помещается целое число (например, п) символов, где и = l_w/f_|, w — длина слова в битах и f — длина поля в битах, занимаемого двоично-закодированным символом. В вычислительных машинах Систем IBM 360-370 w = 32 бита, f — 8 битов (для кода EBCDIC) и, следовательно, п = 4 символа на каждое слово. В этой главе мы рассматривали три типа символьных строк, а именно: строки фиксированной длины (имеющиеся в ПЛ/1), строки изменяющейся длины (также имеются в ПЛ/1) и строки неограниченной длины (характерные для СНОБОЛа). Строки любого из этих трех типов могут быть представлены в виде после- довательности слов или ячеек памяти. Объем памяти, требуемый для размещения строки фиксирован- ной длины, известен при создании строки. Например, предложе- ние языка ПЛ/1 DECLARE S CHARACTER (6) INITIAL (’*AF); описывает поле из шести двоично-кодированных символов (в Си- стемах IBM 360-370 это соответствует полю из шести байтов). Установка начального значения **А1’ для строки S говорит о том, что ей присваивается значение ’*Albbb’. Результат этого присваивания показан на рис. 2-4.1, где Х’бС’, Х’СГ, X’FP и Х’40’ читаются: шестнадцатеричные 5С, Cl, F1 и 40, т. е. (5С)16, (Cl)ie, (FI)ie и (40)16 являются представлениями в коде EBCDIC символов ’А’, ’Г и пробела соответственно. Заметим, что в слове i -]- 1 два младших байта строкою S не заняты. Такие лишние байты могут быть использованы для хранения другой символьной строки, если компилятор или интерпретатор осуще- ствляет эффективное управление памятью при размещении строк. Максимальный размер строки изменяющейся длины тоже известен в момент ее создания. При этом выделяется память, достаточная для размещения соответствующего максимального числа символов и информационного поля, содержащего текущую длину строки. В результате выполнения предложения описания в ПЛ/1 DECLARE С CHARACTER (6) VARYING INITIAL f*Al’); будет выделена область памяти, показанная на рис. 2-4.2. 143
Слобо i Слобо i*1 Гопе длины * д f Используется для долее длинной строки | (ШаШОООООН | 010П100 | 1Ю00001 j | 11110001 | Неизвестно | /tofecrnw | Неизвестно | Длина = 3 к'5С К Cl' Х'Н' / Рис. 2-4.2. Представление символьной строки изменяющейся длины ’ФАГ в коде EBCDIC Формат, показанный на рис. 2-4.2, позволяет представить в коде EBCDIC строки с текущей длиной до 65535 (т. е. 216 — 1) символов с тем лишь ограничением, что текущая длина строки не должна превышать максимально допустимой длины. В некоторых случаях оказывается необходимым включить в этот формат второе иоле длины, содержащее максимальную длину строки. Это второе поле используется для проверки условия переполнения строки (например, когда присваивается значение строки с длиной, пре- вышающей заранее определенную максимальную длину). Наиболее динамичный и развитый тип строки — это строка неограниченной длины. В этом случае ни фактическая, ни макси- мальная длины неизвестны в момент создания строки. Для этого типа строки структура хранения может быть организована мно- гими способами. В двух широко известных методах представления применяются граничные маркеры и описатели (дескрипторы) строки [6]. Граничный маркер—это символ, который необяза- тельно подлежит печати и который отсутствует в тексте строки. Например, для представления строки **АГ в коде ASCII можно использовать печатаемый символ ’ f ’, как показано на рис. 2-4.3. Предполагается, что для представления символов применен семи- битовый код ASCII на ЭВМ с 16-разрядным словом. Отметим, что при этом старший бит каждого полуслова (т. е. байта) равен нулю или единице в зависимости от типа контроля четности, принятого в машине. Если двоичный код каждого символа должен быть чет- ным, как на рис. 2-4.3, то количество единиц в коде символа должно быть четным. Если же принят нечетный код, то количество единиц должно быть нечетным числом. В том случае, когда чет- ность закодированного символа не совпадает с принятой в ЭВМ, фиксируется ошибка при передаче или запоминании этого символа. Дескриптор строки — это элемент, состоящий из поля длины и поля указателя. Поле длины содержит текущую длину строки. Слобо i Слово i+1 Слово 1+2 I * А 1 f | 11011110 | 10101010 | [ 01000001 | 10110001 | [ 11011110 | ИзуетснЬ' | Рис. 2-4.3. Представление символьной строки с неограниченной длиной ’4=АГ в семи- битовом коде ASCII с использованием граничных маркеров 144
Роле Поле длины указатели 00000001 00010110 Дескриптор Длина-3 Адрес =22 I Г Часть строкового пространства I I Адрес 22 L Сповои Слово /2 Рис. 2-4.4. Представление символьной строки с неограниченной длиной ’*А1’ в семи- витовом коде ASCII с использованием дескрипторов В поле указателя хранится адрес первого символа строки в обшир- ной области данных, называемой строковым пространством. Стро- ковое пространство содержит закодированную информацию обо всех строках, применяемых в программе. Например, на рис. 2-4.4 строка **АГ представлена с помощью дескриптора. Отметим, что для ссылки на строку мы должны сначала обратиться к дескрип- тору, который в свою очередь, даст адрес строки, хранящийся в поле его указателя. Для понимания описываемых здесь структур хранения строк полезно познакомиться с тем, как выполняются некоторые при- митивные операции и функции манипулирования строками (на- пример, операции присваивания и конкатенации и функции LENGTH, SUB, INDEX) при различных способах представлений. Присваивание значения строки сводится к тому, что копируется строка или ссылка на строку, соответствующую правой части (или цели) оператора присваивания, в область памяти, связанную с левой частью (или объектом) этого оператора. В присваивании значений строковым переменным с фиксированной или изменя- ющейся длиной трудностей не возникает, поскольку при создании таких переменных сразу отводится достаточная память. Если раз- мер присваиваемой строки больше длины, назначенной для стро- ковой переменной с фиксированной или изменяющейся длиной, то лишние символы отбрасываются с правого конца строки, и при- сваивается строка, соответствующая максимальной длине пере- менной. Если присваивание выполняется для строковой перемен- ной с фиксированной длиной, то присваиваемая строка предвари- тельно дополняется справа пробелами (если ее длина меньше заранее определенной фиксированной длины, назначенной для переменной). Такое дополнение осуществляется до тех пор, пока длина присваиваемой строки не станет равна фиксированной длине соответствующей переменной. 145
СтрокоВое пространство Рис. 2-4.5. Присваивание значений ’CAT’ и ’ANIMAL’ строкам с не- ограниченной длиной SI и S2 соот- ветственно А Т 4 A N 1 М A L I MD U S Е I Строковое пространство Рис. 2-4.6. Результат присваивания S1 = = ’MOUSE’ для строки с неограниченной длиной Строковое пространство CATANI MALM0US Е Присваивание значений строковым переменным, имеющим не- ограниченную длину, вызывает некоторые трудности при распре- делении памяти. Текущая область памяти, выделенная строковой переменной, может быть недостаточна для размещения присва- иваемого строкового значения. Чтобы проиллюстрировать этот факт, допустим, что выполняются присваивания SI = ’CAT’ и S2 — ’ANIMAL’. Результат выполнения этих операций при использовании метода граничных маркеров показан на рис. 2-4.5, а. На рис. 2-4.5, б представлен результат с использо- ванием дескрипторного метода. Для краткости и наглядности на рис. 2-4.5 и последующих рисунках этого параграфа мы исполь- зуем символы вместо их двоично-кодированного представления. Присваивание SI — ’MOUSE’ требует выделения нового уча- стка памяти в строковом пространстве для переменной S1, как показано на рис. 2-4.6, а н б для двух методов представления строк. На рис. 2-4.7,о н б показано, что если одной переменной при- сваивается значение другой переменной, например S1 — S2, то для этого не требуется выделять дополнительную область в стро- ковом пространстве. Вместо этого значение ссылки (в методе граничных маркеров) или описателя (в дескрипторном методе) целевой строки S2 присваивается объектной строке S1. Если такой метод применить для присваивания одной пере- менной значения другой переменной, то должна быть выделена новая область в тех случаях, когда в операции присваивания участвует строковое выражение или строковая константа. В про- тивном случае, если, иапример, переменной S2, представленной 146
Строковое гротракстео дг[с IЧ Рис. 2-4.7. Результат присваивания SI = S2 для строки с неограниченной длиной Рис. 2-4.8. Побочные эффекты, воз- никающие при изменении содержимого памяти, выделенной S2 на рис. 2-4.7, присваивается значение ’DOG’, как показано на рис. 2-4.8, то в результате появляется побочный эффект. При- сваивание значения ’DOG’ переменной S2 изменяет значение переменной S1 на ’DOG’ в методе граничных маркеров и на ’DOGMAL’ в случае использования дескрипторов, причем оба изменения ошибочны. Поэтому для правильного присваивания следует для нового значения S2 выделить дополнительную область в строковом пространстве, как показано на рис. 2-4.9. Операция конкатенации приводит к формированию новой строки из двух строк-аргументов. Поскольку строки-аргументы необязательно смежны в памяти и могут располагаться не в том порядке, который определяется конкатенацией, то при выполне- нии конкатенации требуется дополнительная память для копиро- СтрокоВое пространство Рис. Результат выделения допол- нительной области памяти при присваи- вании S2 = ’DOG' для строки с неогра- ниченной длиной Строковое пространство вания строк-аргументов в смежные участки памяти. В структурах хранения строк с фиксированной или изменяющейся длиной эта память может уже быть в наличии, если сцепленная строка при- сваивается переменной. Но при конкатенации строк с неогра- ниченной длиной необходимо всегда выделять дополнитель- ную память, что соответствует методу присваивания строковых выражений строкам с неогра- ниченной длиной. Рис. 2-4.10 иллюстрирует результат вы- полнения оператора S1 = — S1 О ’HORSE’ для перемен- ной S1, представленной на рис. 2-4.9. Функция вычисляется сированной длина таких быть задана при выделении LE NGTH легко для строк с фик- длииой, так как строк должна 147
Строковое пространство S1 SZ а) Строковое пространство si |-<Н Рис. 2-4.10. Результат присваивания Si = SI O’HORSE’ для строки с неограничен- ной длиной им памяти. Для строки с изменяющейся или неограничен- ной длиной в дескрипторном методе параметр длины задан в явном виде в поле структуры хранения, соответствующей каждому типу строки. В методе граничных маркеров, окаймля- ющих строку с неограниченной длиной, для вычисления длины необходимо просканировать всю строку. Функция SUB, используемая для выделения подстроки в основной строке, реализуется одинаково во всех структурах хранения строк (т. е. после нахождения начала строки в памяти остается лишь выделить подходящую последовательность, ука- занную аргументами функции SUB). При этом должна быть вы- полнена проверка того, что индекс и длина, т. е. второй и третий аргументы функции SUB, имеют правдоподобные значения для заданной основной строки. Такая же проверка аргументов функ- ции SUB должна быть проделана и в том случае, когда эта функ- ция используется для замещения, например, SUB (X, 1,1) — Если аргументы индекс и длина задают подстроку, выходящую за ограниченную длину строковой переменной с фиксированной или изменяющейся длиной, то фиксируется ошибка. Для строк с неограниченной длиной такая проверка основной строки не нужна, так как функция SUB будет выполняться без ошибки. Как и функция SUB, функция INDEX реализуется одина- ково для всех структур хранения строк, если известна область памяти, в которой находится строка. Процесс посимвольного просмотра основной строки слева направо с целью выделения в ней образца-аргумента функции INDEX прост, но занимает много времени. Прежде чем закончить этот параграф, следует подчеркнуть, что строки с неограниченной длиной, будучи чрезвычайно гиб- кими, представляют некоторые трудности с точки зрения управле- ния памятью. Например, присваивание значения ’MOUSE’ пере- 148
менной S1 на рис. 2-4.6 делает недоступной строку ’CAT’, в ре- зультате чего в строковом пространстве появляется «мусор». Количество «мусора» увеличивается после выполнения ряда стро- ковых присваиваний. Для эффективного использования области памяти, отведенной строкам, необходимо применить какой-нибудь метод сбора этого «мусора». В п. 5-6 мы будем исследовать не- которые методы сбора «мусора» в памяти, занимаемой строками с неограниченной длиной. В следующем параграфе мы рассмотрим решение ряда задач с применением методов манипулирования строками. Упражнения к п. 2-4 1. Изобразите структуру хранения строки S, которая имеет зна- чение ’STRING’, полагая, что S а) строка с фиксированной длиной; б) строка с изменяющейся длиной, максимальное значение которой равно 8; в) строка с неограниченной длиной. Для а) и б) использовать внутреннее представление символов в коде EBCDIC,, а для в) — в коде ASCII. Напомним, что символьные коды EBCDIC и ASCII приведены в гл. 1. 2. Из последнего параграфа вытекает следующее правило для символьных строк с неограниченной длиной: корректный метод присваивания состоит в том, чтобы выделять дополнительную область строкового пространства для каждого нового значения, присваиваемого переменной типа символьной строки, если только присваивание не относится к типу «переменная—переменная» (как в слу- чае S1 <- S2). В последнем случае переменной S1 просто присваивается ссылка- или дескриптор переменной S2. а) Проиллюстрируйте результаты следующих присваиваний типа «пере- менная—переменная»: Установить SI <- ’DATA’, S2 S1. используя дескрипторный метод хранения строк с неограниченной длиной, б) Проанализируйте оператор Установить Sl'<- SUB (SI, I, 2). Можно или нельзя выполнить присваивания для подстроки, как записано в опе- раторах, без запроса новой области памяти, если применить: 1) метод граничных маркеров; ii) дескрипторный метод. 3. При использовании строк с переменной длиной накапливается большое количество «мусора» в результате присваивания новых значений строковым переменным. Один из методов сбора этого «мусора» и повторного его использова- ния применительно к дескрипторному методу состоит в том, чтобы завести век- тор «старых» дескрипторов, указывающих на участки доступной свободной области памяти. При появлении запроса на новую область памяти для строки сначала просматривается список этих дескрипторов, и, если возможно, опреде- ленный участок памяти из «мусора» выделяется для использования. Исследуйте возможность построения такой системы и особенно вопрос о том, что считать «мусором» и как сформировать и управлять таким «агенством по сбору мусора»- 2-5. ПРИМЕНЕНИЯ^ ОПЕРАЦИЙ НАД СТРОКАМИ В этом параграфе детально рассмотрены четыре при- мера, которые иллюстрируют возможности применения изложен- ных в данной главе концепций, особенно функций обработки строк. В первом примере описаны текстовый редактор 149
ЕТЕХТЕ и те операции по обработке текста, которые он выпол- няет. Этот пример превосходно иллюстрирует целый ряд возмож- ностей по использованию операций над текстовыми строками. Второй и третий примеры посвящены лексическому анализу и ге- нерации указателей типа KWIC. Оба эти примера интересны и важны с практической точки зрения. Четвертый пример отно- сится к задаче информационного поиска, решаемой с использова- нием битовых строк. Поскольку битовые строки в главе подробно не обсуждались, в четвертом примере даются сведения о них и связанных с ними операциях, а также показываются приемы •использования таких строк. 2-5.1. Редактирование текста Редактирование текста представляет одну из наиболее широких областей, в которых используются методы манипулиро- вания строками. Редактирование и набор газеты, редактирование книги или отчета, редактирование данных и программ для ЭВМ — все это включает средства онлайнового редактирования текста. Вместо того, чтобы записывать текст на бумагу и затем коррек- тировать его вручную, можно в каждом из перечисленных случаев запомнить текст в символьной форме на некотором устройстве внешней памяти (например, диске) ЭВМ, После этого для добавле- ния, удаления или изменения текста и вывода его в желаемом формате можно применить программы редактирования. Конкретный вариант текстового редактора, обсуждаемый в этом параграфе, называется ЕТЕХТЕ (Elementary TEXT Editor—• элементарный редактор текста). ЕТЕХТЕ обеспечивает выполне- ние основных операций текстового редактирования с помощью списка команд. Каждая команда редактора начинается с первой позиции входной строки данных, причем первыми двумя симво- лами- команды являются $$, чтобы отличить команду от тексто- вого материала (т. е. мы полагаем, что строчка текста никогда .не начинается с $$). Каждая команда должна записываться вместе со своими параметрами на отдельной входной строчке. .Рассмотрим подробно команды редактора. 1. Команда $$ADD. Данная команда указывает, что следу- ющий за ней текст должен быть добавлен построчно к концу текста, сформированного к данному моменту. Если редактор ис- пользуется в режиме ввода данных с перфокарты, то строчкою текста считается содержимое одной перфокарты. Для иллюстра- ции предположим, что мы хотим добавить текст, который объяс- няет, как происходит запоминание текста в системе ЕТЕХТЕ. $$ADD ДОПУСТИМ, ЧТО ВХОДНОЙ ТЕКСТ ОТПЕРФОРИРОВАН НА КАРТАХ. ЭТОТ ТЕКСТ ЗАПОМИНАЕТСЯ ПОСТРОЧНО в СИМВОЛЬНЫХ ПОЗИЦИЯХ 11—91 ЭЛЕМЕНТОВ Ш
ОДНОМЕРНОГО МАССИВА СИМВОЛЬНЫХ СТРОК. КАЖДЫЙ ЭЛЕМЕНТ ЭТОГО МАССИВА СООТВЕТСТВУЕТ ОДНОЙ ВХОДНОЙ СТРОЧКЕ. СИМВОЛЬНЫЕ ПОЗИЦИИ ЭЛЕМЕНТА МАССИВА С ПЕРВОЙ ПО ПЯТУЮ СОДЕРЖАТ ПОРЯДКОВЫЙ НОМЕР. ДЛЯ ЛЮБОГО ТЕКСТОВОГО МАССИВА ПОРЯДКОВЫЕ НОМЕРА НАЧИНАЮТСЯ С 00010 И НАРАЩИВАЮТСЯ НА ВЕЛИЧИНУ 00010. Распознав команду $$ADD, редактор читает следующую строчку текста и обращается к программе записи всего последу- ющего текста вплоть до очередной команды или завершения сеанса работы редактора. Конец сеанса обозначается командой завершения при работе с системой ЕТЕХТЕ в онлайновом ре- жиме с терминала или признаком конца файла в случае офлай- нового ввода, например ввода с перфокарт. Опишем теперь алго- ритм выполнения команды $$ADD. Алгоритм $$ADD. Задана первая строчка INPUT входного текста, который должен быть помещен в память машины вслед за находящимся тала основным текстом. Входной текст вплоть до следующей команды редактора илн признака конца сеанса до- бавляется к основному тексту. Индекс LCNT (означающий счет- чик строчек) задает номер элемента в одномерном символьном массиве LINE, в котором запоминается текущая входная строчка текста. Функция F____COMMANDS анализирует форматные команды, прежде чем запомнить строчку INPUT. Названная функ- ция обсуждается в этом пункте после описания форматных кодов. В данном же алгоритме F__COMMANDS можно рассматривать как «фиктивную» функцию (т. е. она не меняет значения параметра INPUT). L и С — рабочие переменные, a CONVERTCHAR — это функция, преобразующая числовой аргумент в символьную строку, как описано в гл. 1. 1. (Цикл до прекращения действия команды $$ADD.) Повто- рять шаги от 2 до 5 до тех пор, пока не закончится сеанс. 2. [Установка счетчика строчек текста. ] Установить SUB (LINE 1LCNT], 1,5) *- ’00000’, С <- CONVERTCHAR (LCNT * * 10), L^-LENGTH (C), SUB (LINEfJLCNT ], 6—L, L) ч-C. 3. [Запоминание текста карты.] Установить SUB (LINE [LCNT], 11)^- F—COMMANDS (INPUT). 4. (Приращение счетчика строчек. ] Установить LCNT LCNT Ж 1. 5. [Чтение новой карты и проверка признака наличия новой команды.) Читать INPUT. Если SUB (INPUT, 1, 2) = то закончить выполнение алгоритма. 6. [Конец сеанса.] Выход. 2. Команда $$LIST. При наличии в редакторе режима ввода текста желательно также иметь и возможность отобразить то, что было ранее помещено в память. Это позволяет нам обнаруживать 151
ошибки и облегчает процесс их исправления. Данная команда редактора обеспечивает такое средство отображения текста. $$LIST/ [номер начальной строчки ]/ [номер конечной строчки ]/ Команда $$L1ST, как показывает ее общий формат, имеет список параметров. Чтобы проиллюстрировать три возможных формы команды $$LIST, рассмотрим следующие примеры: $§LIST/00010/*/ вызывает печать всего накопленного в памяти текста. В команде $$LIST и всех последующих командах знак * представляет номер последней строчки запомненного текста. На- пример, если текст помещен в память с помощью описанной выше команды $$ADD, то указанная команда $$LIST обеспечит -следующий вывод: '00010 ДОПУСТИМ, ЧТО ВХОДНОЙ ТЕКСТ ОТПЕРФОРИРОВАН НА КАРТАХ. ЭТОТ ТЕКСТ 00020 ЗАПОМИНАЕТСЯ ПОСТРОЧНО В СИМВОЛЬНЫХ ПОЗИЦИЯХ 11—91 ЭЛЕМЕНТОВ 00030 ОДНОМЕРНОГО МАССИВА СИМВОЛЬНЫХ СТРОК. 00040 КАЖДЫЙ ЭЛЕМЕНТ ЭТОГО МАССИВА СООТВЕТСТВУЕТ ОДНОЙ ВХОДНОЙ СТРОЧКЕ. 00050- СИМВОЛЬНЫЕ ПОЗИЦИИ ЭЛЕМЕНТА С ПЕРВОЙ ПО 00060 ПЯТУЮ СОДЕРЖАТ ПОРЯДКОВЫЙ НОМЕР. ДЛЯ ЛЮБОГО ТЕКСТОВОГО МАССИВА 00070 ПОРЯДКОВЫЕ НОМЕРА НАЧИНАЮТСЯ С 00010 И Ю0080 НАРАЩИВАЮТСЯ НА ВЕЛИЧИНУ 00010. По команде _$$LIST / 00020 / 00040 выводится следующее: 00020 ЗАПОМИНАЕТСЯ ПОСТРОЧНО В СИМВОЛЬНЫХ ПОЗИЦИЯХ 11—91 ЭЛЕМЕНТОВ 00030 ОДНОМЕРНОГО МАССИВА СИМВОЛЬНЫХ СТРОК. 00040 КАЖДЫЙ ЭЛЕМЕНТ ЭТОГО МАССИВА СООТВЕТСТВУЕТ ОДНОЙ ВХОДНОЙ СТРОЧКЕ. Следовательно, в данном случае выводятся строчки, имеющие номера от 00020 до 00040 включительно. $$LIST / 00070 / * / обеспечивает вывод 00070 ПОРЯДКОВЫЕ НОМЕРА НАЧИНАЮТСЯ С 00010 И 00080 НАРАЩИВАЮТСЯ НА ВЕЛИЧИНУ 00010. Ко да распознается команда $$LIST, то происходит обраще- ние к программе печати текста с параметрами В EG INLINE (озна- чает номер начальной строчки текста) и ENDLINE (номер конеч- ной строчки.) Если вместо BEGINLINE или ENDLINE указан знак *, то подразумеваегся значение параметра, равное LCNT—1 (т. е. равное номеру последней строчки текста). Это правило подстановки значений для параметров BEGINLINE и ENDLINE справедливо и для остальных команд редактора, начина- 152
ющихся символами $$. Ниже приводится алгоритмическое опи- сание программы, обрабатывающей команду $$LIST. Алгоритм $$LIST. Заданы одномерный массив символьных строк с именем LINE, а также параметры BEGINLINE (номер начальной строчки) и ENDLINE (номер конечной строчки). Осу- ществляется вывод соответствующих элементов массива LINE. Индекс j используется при выводе эдементов этого массива. 1. [Вывод заданного текста. ] Повторять при / = BEGINLINE» ..., ENDLINE: если SUB (LINE [j], 11) (если не пустая строка), то печатать SUB (LINE [j], 1,5) Q ’bbbbb’ О SUB (LINE [j], 11). 2. (Конец. I Выход. В шаге 1 для каждой строчки, номер которой находится в диа- пазоне от BEGINLINE до ENDLINE включительно, печатаются порядковый номер, пять разделяющих пробелов и текст строчки. Строчка LINE [j 1 не печатается целиком, так как в ее позициях 6—10 может находиться отличная от пробелов символьная ин- формация, сущность которой будет описана в этом пункте ниже. 3. Команда $$CHANGE. Во многих случаях бывает необхо- димо исправить входную строчку из-за типографских и орфогра- фических ошибок или желания изменить содержание строчки. Эту возможность обеспечивает команда $$CHANGE, [которую мы сейчас опишем. $$CHANGE/[номер начальной строчки]/[номер конечной строчки ]/(заменяемый текст)/(заменяющий текст)/ Допустим, что в строчке 30 ранее приведенного текста мы хотим заменить фразу ’ОДНОМЕРНОГО МАССИВА СИМВОЛЬ- НЫХ СТРОК’ более короткой фразой ’ВЕКТОРА СИМВОЛЬ- НЫХ СТРОК’. Такая замена осуществляется с помощью команд $$CHAN GE/ЗО/ЗО/ОД НОМЕРНОГО// $$CHANGE/30/30/MACCHBA/BEKTOPA/ В последующем описании алгоритма обработки команды SSCHANGE переменные BEGINLINE, ENDLINE, PATTERN, REPLACEMENT соответствуют четырем параметрам команды. Алгоритм $$CHANGE. При заданных параметрах BEGINLINE, ENDLINE, PATTERN и REPLACEMENT в мас- сиве символьных строк LINE изменяются строчки с номерами то BEGINLINE по ENDLINE включительно путем замены текста, представляемого параметром PATTERN, текстом, который пред- ставляется параметром REPLACEMENT. Переменная j — это индекс массива LINE, IND — рабочая переменная, a CHANGEFLAG представляет признак того, что при выполне- нии алгоритма произошло по крайней мере одно изменение. 1. [Организация цикла.] Установить CHANGEFLAG -«—false. Повторять шаги 2 и 3 при j = BEGINLINE, ENDLINE. 153»
2. [Локализация текста, подлежащего изменению. ] Устано- вить IND — INDEX (LINE [j], PATTERN). 3. [Замена текста, если возможно. 1 Если IND =# 0, то уста- новить SUB (LINE [j], IND, LENGTH (PATTERN)) <- REPLACEMENT,CHANG EFLAG true. 4. [Конец. 1 Если ~] CHANGEFLAG, то напечатать сообщение ‘ТЕКСТ, ПОДЛЕЖАЩИЙ ЗАМЕНЕ, НЕ ОБНАРУЖЕН'. Выход. Из описания алгоритма очевидно, что команда $$CHANGE реализует очень простой процесс поиска по образцу. Она служит хорошей иллюстрацией применения поиска по образцу в важной практической области. Отметим, что $$CHANGE не выполняет сравнения заданных строк с элементами массива LINE, хотя такое весьма существенное средство должно быть в хорошем' текстовом редакторе. Мы предлагаем в качестве упражнения в конце этого пункта разработку алгоритма $$CHANGE, кото- рый обеспечивал бы эту возможность. 4. Команда $$DELETE. Описываемый редактор содержит также команду для удаления строчек текста. Общая форма этой команды следующая: $$DELETE/[номер начальной строчки ]/[номер конечной строчки ]/ Такая команда позволяет удалить одну строчку или все строчки, начинающиеся с заданного номера. Например, для уда- ления последней строчки приведенного ранее текста мы можем использовать команду $$DELETE/00080/00080/ Если вслед за этой командой применить команду $$LIST для печати запомненного текста, то строка с номером 00080 не будет напечатана. Команды $$CHANGE/00060/000604IUIfl ЛЮБОГО ТЕКСТОВОГО МАССИВА// $$DELETE/00070/ * / удаляют из приведенного ранее текста последнее предложение. Вторая команда указывает при этом способ удаления последовательности строк. Команда $$DELETE/00010/00080/ или $$DELETE/00010/ * / удаляет текст целиком Ниже приведено алгоритмическое описание команды удаления строк. В этом описании BEGINLINE представляет номер началь- ной строчки в удаляемой последовательности строк, д ENDLINE — номер конечной строчки в этой последователь- ности. 154
Алгоритм $$ DELETE. При заданных параметрах BEGINLINE и ENDLINE выполняется удаление множества строчек, имеющих номера от BEGINLINE до ENDLINE включи- тельно. 1. [Удаление последовательности строчек.] Повторять при i = BEGINLINE, ..., ENDLINE : Установить LINE [i 1 ч- ”. 2. [Конец.] Выход. Как и описанные выше алгоритмы, алгоритм $$DELETE очень прост и почти не нуждается в пояснении. Отметим, что уда- ление строчки осуществляется ее заменой пустой строкой. Пустые строки не выводятся на печать при выполнении команды $$LIST. 5. Команда $$PRINT. Последняя команда редактора, кото- рую мы хотим описать, позволяет выводить заполненный в машине текст в форматизованном виде. Она обеспечивает формирование таблиц, заголовков, правую выключку строк, а также пропуск строк и страниц. Все эти средства в системе ЕТЕХТЕ реали- зуются командой $$PRINT: $$PRINT/ [номер начальной строчки ]/1номер конечной строчки ]/ Команда $$PRINT широко использует базовые функции поиска по образцу, описанные в п. 2-3.2. При ее выполнении осуществляется поиск форматного кода в каждой строчке заданной части текста. Форматные коды включаются в текст для указания,, когда следует делать абзацы, формировать заготовки страниц, и выполнять многие другие операции форматизации. Имеется четыре группы форматных кодов: табулирующие коды,, коды формирования заголовков, коды пропуска и коды правой, выключки строчек. Каждый форматный код должен начинаться в строчке с одиннадцатой символьной позиции, причем ему пред- шествуют два специальных символа, а за кодом может следовать список параметров. Форматные коды включаются в текст с по- мощью форматных команд. Рассмотрим подробно каждую из этих команд. 6. Команда табулирования @@ТАВ (расстановка коло- нок) 1@/~]]. Эта команда, называемая также командой абзац- ного отступа, начинается с символов @@, за которыми следует- слово TAB и список позиций колонок. Символ @ после списка позиций колонок ставится лишь в случае, когда список позиций колонок относится не только к данной строчке, но и ко всем последующим строчкам вплоть до появления очередного табули- рующего кода. Символ “] используется в том случае, когда список позиций относится только к оставшейся части строчки, непосред- ственно следующей за командой табулирования. Команда табу- лирования и все остальные форматные команда, которые будут- обсуждаться ниже, запоминаются в форме, отличной от их записи. Для экономии памяти ключевое слово (в данном случае TAB) 155.
отбрасывается, и запоминается остаток команды. Поэтому @(gxTAB/6/6I/@ запоминается в виде @@6/61/@. Элементы текста, которые должны печататься в заданных ко- лонках таблицы, разделяются в команде наклонной чертой 7’. Допустим, что в некотором сеансе редактирования задан входной текст, представленный ниже, и что до этого редактор не поместил в память машины никакого другого текста: $$ADD @@ТАВ /15/30/45/ МИЛИ/МИЛЬ//ГАЛЛОН/ЦЕНА/ 200/19.5/ $ 5.25/250/21.0/$ 6.85/ 195/16.4/$5.20/ .$$LIST /00010/ * / $$ PRINT /00010/ * / По команде $$ LIST будет выдано следующее: Ю0010 «0020 -00030 Команда $$ PRINT обеспечит следующий формата зов энный вы- вод: @(gi 15/30/45/@МИЛИ/МИЛЬ//ГА ЛЛОН/ЦЕН А/ 200/19.5/$5.25/250/21.0/$6.85/ 195/16.4/$5.20/ МИЛЬ/ГАЛЛОН ЦЕНА 19.5 21.0 16.4 вместо PRINT то будет выдано: 19.5 $5.25 21.0 $6.85 16.4 $5.20 МИЛИ 200 250 195 Заметим, что если $$ PRINT/00020/00030/, 200 250 195 •Следовательно, по-прежнему происходит формирование таблицы, хотя команда TAB отсутствует в указанных выводимых строчках. Чтобы сформировать таблицу, в которой колонки центрированы относительно заголовков и имеют более удачное взаимное рас- положение, можно применить следующие команды: $$ADD] @@ТАВ/15/26/43/ 1 МИЛИ/МИЛЬ//ГАЛЛОН/ЦЕНА/ @@ТАВ/16/30/43/@200/19.5/$5.25/250/ 21.0/$ 6.85/195/16.4/$5.20/ $$PRINT/ 00010/ * / По команде $$ PRINT будет выдано: МИЛЬ/ГАЛЛОН 19.5 21.0 16.4 $5.25 $6.85 $5.20 применить команду МИЛИ 200 250 195 Если вместо $$ PRINT мы применим 00020/, то будет напечатано следующее: 200 19.5 $5.25 250 ЦЕНА $5.25 $6.8Г $5.20 команду $$ PRINT/00020/ 156
Следовательно, расстановка колонок таблицы выполняется для незаконченных списков табулируемых данных, как это видно на примере элемента 250. С помощью этой же команды TAB легко выполняется отступ (абзац). Например, по командам $$DELETE /00010/ * / $$ADD @@,ТАВ/5/ 1 ЭТО НАЧАЛО ПАРАГРАФА, КОТОРЫЙ БУДЕТ ИСПОЛЬЗОВАН ТОЛЬКО В ИЛЛЮСТРАТИВНЫХ ЦЕЛЯХ. S&PRINT /00010/ * / будет выдано на печать ЭТО НАЧАЛО ПАРАГРАФА, КОТОРЫЙ БУДЕТ ИСПОЛЬЗОВАН ТОЛЬКО В ИЛЛЮСТРАТИВНЫХ ЦЕЛЯХ. 7. Команда формирования заголовков % % TITLE/[С или $L1 [U или N]/. Рассматриваемый редактор может располагать за- головки по центру, что задается кодом С, либо сдвигать их к ле- вому полю страницы (на это указывает код L). Заголовки могут подчеркиваться (код U) или не подчеркиваться (код N). Пред- положим, что каждая строчка содержит 72 позиции и что никакой текст до этого еще не вводился в систему. Тогда команды $$ADD %%'HTLE/CU/INTRODUCTION ГО DATA STRUCTURES WITH APPLICATIONS %%T1TLE/CN/BY %% TITLE/CN/J .P.TREMBLAY % % T1TLE/CN/P.G.SORENSON %%TITLE/LU/Published by: % % TITLE/LN/McGraw-Hill $$PRINT/00010/ * I обеспечат печать следующего текста: INTRODUCTION ТО DATA STRUCTURES WITH APPLICATIONS BY J.P. TREMBLAY P.G. SORENSON Published by: McGraw-Hill Заметим, что кода формирования заголовков справедливы только для одной строчки и не влияют на последующие строчки до появления очередного кода формирования заголовков. 8. Команда пропуска zjz^SKlP/ (число строчек) [ Р/. Коды пропуска обеспечивают пропуск как строчек, так и страниц. Об- щая форма кода пропуска имеет вид (число строчек) | Р/, где параметр, следующий за символами есть число (означа- ющее количество пропускаемых строчек) или буква Р (означа- ющая страницу). Например, команды $$DELETE/0010/ * / $$ADD 157
##SK1P/P/ % % TITLE/CN/CHAPTER 2 ##skip/i/ %%T1TLE/CN/THE REPRESENTATION AND MANIPULATION OF STRINGS ##skip/i/ @(g}TAB/5/ ~| IN THE PREVIOUS CHAPTER AVE INTRODUCED THE LITERAL CHARACTER $$ PRINT/00010/ * I сформируют текст, представленный на рис. 2-5.1. 9. Команда выключки строчек && JUSTIFY/[”]] {правое поле) /. Правая выключка строчек означает, что в печатной форме текста правое поле выровнено для всех напечатанных строчек. Например, текст на этой странице выровнен с правой стороны, т. е. выключен справа. В набираемом тексте, например тексте данной книги, для осуществления правой выключки сначала пытаются выполнить перенос слов с одной строчки на другую, затем между словами делают более широкие интервалы и, на- конец, увеличивают, если надо, расстояние между буквами. Набор книг, журналов и газет за последние пять-десять лет достиг высокого уровня автоматизации. С помощью средств, подобных тем, какие обеспечивает система ЕТЕХТЕ, вычисли- тельные машины применяются для редактирования и форматиза- ции текста. В процессе набора главную трудность представляет правая выключка строчек. Перенос слов с одной строчки на дру- гую в большинстве случаев можно выполнить, используя храня- щиеся в памяти слоги многих распространенных слов и ряд про- стых правил разделения слов по слогам. Разбиение «необычного» слова на слоги может потребовать вмешательства оператора. После этого система может запомнить такое слово и место его- разделения. Это позволит системе самостоятельно перенести данное слово в другой раз, когда оно выйдет за пределы печата- емой строчки. В тексте, который печатается на устройстве построчной печати или телетайпе, интервалы между словами приходится делать- более примитивным способом. В настоящее время для таких устройств изменять расстояние между буквами нельзя, так как печатающие устройства и телетайпы являются устройствами с фиксированной печатью (т. е. интервал между смежными симво- лами фиксирован). Интервалы между словами можно расширять.. CHAPTER 2 STRING MANIPULATION IN THE PREVIOUS CHAPTER WE INTRODUCED THE LITERAL CHARACTER: Рис. 2-5.1. Иллюстрация действия кодов пропуска 158
помещая более одного символа пробела между словами. Ниже приведен алгоритм выполнения правой выключки строчек, осно- ванный на процедуре, которая управляет интервалами между словами. Алгоритм JUSTIFICATION. Дана символьная строка PRINTLINE, которая содержит текст, начинающийся и заканчи- вающийся символом, отличным от пробела. Длина этой строки выходит за границу RMARGIN правого поля полосы. Алгоритм осуществляет правую выключку строки PRINTLINE и возвра- щает лишний текст. BLANKS — это переменная, значение кото- рой равно общему числу пробелов, подлежащих включению в строку, a BFIELD — символьная строка пробелов, равная по длине полю пробелов, разделяющему слова. Первоначально раз- мер этого поля равен единице. 1. [Проверка того, что текст уже удовлетворяет условию пра- вой выключки.] Если SUB (PRINTLINE, RMARGIN, I) ’b’ и SUB (PRINTLINE, RMARGIN + 1,1) = ’b’, то печатать SUB (PRINTLINE, 1, RMARGIN), установить JUSTIFICATION<— <— SUB (PRINTLINE, RMARGIN 4- 1) и закончить выполне- ние алгоритма. 2. [Проверка наличия символа, отличного от пробела, в по- зиции RMARGIN. ] Установить j RMARGIN— 1, если SUB (PRINTLINE, RMARGIN, 1) -4= ’b’, то повторять до тех пор, пока SUB (PRINTLINE, j, 1) =?= ’b’ : установить j <— j — 1. 3. [Поиск следующего символа, отличного от пробела. ] Уста- новить j j — 1. Повторять до тех пор, пока SUB (PRINTLINE, j, 1) = ’b’ : установить j <— j — 1. 4. [Цикл для добавления пробелов. ] Установить BLANKS •<— -«-RMARGIN — j, BFIELD’b’. Повторять шаг 5 при k = = 1, 2, ..., BLANKS: 5. [Последовательное добавление пробелов в поле, разделя- ющее слова. ] Повторять до тех пор, пока ~]МАТСН (PRINTLINE, BFIELD, j, ”, BFIELD Q ’b’, true): установить j j — 1, если j — 0, то установить j RMARGIN — BLANKS Jr k — 1, BFIELD BFIELD О ’b*. Установить j ч— j — LENGTH (BFIELD) — 2 (восстановление счетчика j). 6. [Выдача выключенного справа текста.] Печатать SUB (PRINTLINE, 1, RMARGIN). Установить JUSTIFICATION^ SUB (PRINTLINE, RMARGIN 4- 1) и закончить выполнение алгоритма. В шаге 1 алгоритма JUSTIFICATION обрабатывается текст, который не требует включения пробелов в текст для обеспечения правой выключки. В качестве простого примера допустим, что PRINTLINE имеет значение ’THE BOOK IS AUTHORED BY W. M. FINDLING. HE DISCUSSES DATA MANAGEMENT...’, a RMARGIN равно 20. 159
Тогда SUB (PRINTLINE, 20, 1) равно ’D’, a SUB (PRINTLINE, 21, 1) равно ’b’. Поэтому в шаге 1 будет напечатана строчка ’THE BOOK IS AUTHORED’ и возвращен остаток исходного текста. Если из остатка удалить стоящий в его начале пробел, а текст присвоить строке PRINTLINE и затем попытаться выполнить для нее правую выключку, то SUB (PRINTLINE, 20, 1) будет равно ’Ь’ и в соответствии с шагом 1 непосредственная выключка строки окажется невозможна. В шаге 2 для j будет установлено значение 19. Но в девятнадцатой позиции строки PRINTLINE также содержится пробел. Поэтому в шаге 2 выполняется цикл поиска самого правого символа строки, отличного от пробела. Такой символ обнаруживается в позиции 18, определяемой j. В шаге 4 переменная BLANKS принимает значение 20—18, т. е. 2, а строка BFIELD — начальное значение ’Ь’. Шаг5 повторяется прн значениях к, равных 1 и 2. В этом шаге осуществляется обра- щение к базовой функции поиска по образцу MATCH (как опи- сано в п. 2-3.2). Строка PRINTLINE сканируется справа налево с целью выделения в ней текущего значения BFIELD. В нашем примере поиск оказывается успешным при j = 9. После этого в строку включается пробел, что обозначается символом ’Ь’. Следовательно, PRINTLINE будет иметь теперь значение ’BY W/M. bFINDLlNG. НЕ DISCUSSES DATA MANAGEMENT...’ Далее значение j проверяется на нуль (в данном случае оно не равно нулю). После этого для j устанавливается значение 11 — 1 — 2 = 8, и шаг 5 повторяется последний раз при к = 2. Снова, начиная с текущей позиции j, осуществляется поиск сле- дующего самого правого пробела. Пробел обнаруживается в сим- вольной позиции 6, и после включения в текст второго пробела PRINTLINE принимает вид ’BY W. ЬМ. bFINDLlNG. НЕ DISCUSSES DATA MANAGEMENT ...’ В шаге 6 будет напечатана строка из 20 символов ’BY W. ЬМ. bFINDLlNG.* и возвращен остаток текста ’ НЕ DISCUSSES DATA MANAGEMENT ...’ Как и ранее, мы можем отбросить пробелы, имеющиеся в на- чале возвращенного остатка и присвоить этот новый текст строке PRINTLINE для продолжения правой выключки. В данном слу- чае PRINTLINE имеет значение ’НЕ DISCUSSES DATA MANAGEMENT...’ 160
Проверив в шагах I и 2 двадцатый символ, мы обнаружим, что таковым является первая буква ’А’ в слове MANAGEMENT. Чтобы выполнить для этого текста правую выключку, мы должны отыскать первый отличный от пробела символ слева от поля про- белов, непосредственно предшествующего слову MANAGEMENT. Таким символом является вторая буква ’А’ в слове’DATA*. Этот символ находится в позиции 17 строки PRINTLINE. Шаги 2 и 3 завершают поиск символа. Следовательно, удлинению до 20 по- зиций подлежит теперь строка ’НЕ DISCUSSES DATA’ В шаге 4 j имеет значение 17, а из этого следует, что число пробелов, подлежащих включению в строку, равно 3 (т. е. BLANKS = 3). Шаг 5 повторяется 3 раза. В первых двух повто- рениях пробелы добавляются между словами ’DISCUSSES’ и ’DATA’ и между словами ’НЕ’ и ’DISCUSSES’. При третьем повторении j уменьшается до нуля, после чего он восстанавли- вается до 19, a BFIELD принимает значение ’bb’. После этого строка повторно сканируется справа налево с целью выделения первого вхождения поля, состоящего из двух пробелов. Такое поле обнаруживается между словами ’DISCUSSES’ и ’DATA’. Эти два пробела заменяются полем из трех пробелов, и алго- ритм JUSTIFICATION завершается, выдав на печать ’НЕ bDISCUSSES bbDATA’ и возвратив значение остатка ’ MANAGEMENT До сих пор коды выключки еще не были описаны. Общая их форма имеет вид & & (число) I | (число), где (число) яв- ляется значением переменной 1 RMARGIN, а —| указывает, что для последующего текста правую выключку выполнять не надо. Следовательно, команда && JUSTIFY/80/ указывает, что для всего последующего текста вплоть до нового кода типа & &, за исключением текста, содержащего коды форми- рования заголовков, необходимо выполнять правую выключку по 80 символов в каждой печатаемой строчке Текст, следующий за кодами формирования заголовков, по понятной причине не должен подвергаться правой выключке. Команда & & JUST1FY/~]8O/ прекращает процесс выполнения правой выключки, но фиксирует значение 80 для RMARGIN. Это значит, что если в восьмидесятой позиции строки PRINTLINE находится пробел, то печатаются первые 80 символов строки. Если же в позиции 80 имеется символ, 1 Эта переменная указывает номер позиции правой колонки в полосе на- бора. — Прим. пер. 6 Трамбле Ж-, Соренсон П. 161
отличный от пробела, то осуществляется поиск первого пробела слева от этой позиции, а затем выводятся на печать все символы, находящиеся слева от найденного пробела. Предлагаем в качестве упражнения в конце данного пункта разработку алгоритма NO—J USTIFICATIO N, который’ при за- данных параметрах PRINTLINE и RMARGIN выдает строку, не подвергнутую правой выключке. При выполнении правой выключки текста возникает особая ситуация, если за строчкой, которая подвергается правой вы- ключке, следует строчка, состоящая из пробелов или содержащая код пропуска, код абзацного отступа или код, отменяющий пра- вую выключку. В этих случаях нецелесообразно выполнять правую выключку, поскольку такие ситуации возникают при завершении параграфа. Например, если RMARGIN имеет значе- ние 60, a PRINTLINE представляет текст ’END OF PARAGRAPH,’ то после выполнения правой выключки этого текста будет выдано следующее: ’END bbbbbbbbbbbbbbbbbbbbOF bbbbbbbbbbbbbbbbbbbbbb PARAGRAPH.’ Ясно, что такой результат непривлекателен с эстетической точки зрения. Кроме того, если делается попытка выполнить правую выключку строчки, содержащей слева от конца параграфа всего лишь один символ, то невозможно будет включить в строчку про- белы, и алгоритм JUSTIFICATION никогда не завершится. Очевидно, такую ситуацию следует выявить при обработке команды $$PRINT и не обращаться к программе выполнения правой выключки. Теперь мы готовы к тому, чтобы описать алгоритм обработки команды $$PRINT. Обращение к алгоритму $$PRINT проис- ходит тогда, когда во входном тексте редактор распознает команду SPRINT. Алгоритм $$PRINT« Заданы текст, находящийся в памяти ЭВМ в символьном массиве LINE, и параметры BEGINLINE и ENDLINE, определяющие номера строчек. Осуществляется вывод на печать строчек этого текста от LINE [BEGINLINE] и до LINE [ENDLINE] включительно в соответствии с формат- ными кодами, имеющимися в тексте. TABF___________FOUND и JUST—FOUND — логические переменные, используемые для ука- зания на то, что при обратном просмотре текста от BEGINLINE до первой входной строки обнаружены коды табулирования и выключки. TABFLAG показывает, что текущий код табулирования имеет глобальное или локальное действие (т. е. влияет только на следующую строчку) или табулирование не действует вовсе. Соответственно TABFLAG может иметь одно из значений 'G', 'L' или 'N'. NO—OF—TABS равно текущему числу колонок таблицы, причем значения позиций колонок запоминаются в мас- сиве TAB. RJUSTIFY — логическая переменная истинное зна- 162
чение которой указывает на необходимость выполнения правой выключки последующего текста, а ложное значение запрещает выполнение правой выключки. Переменная RMARGIN содержит текущее значение правой границы текста. Другие функции и переменные будут объяснены после описания алгоритма. 1 . [Начальная установка для поиска предыдущих кодов та- булирования и выключки J Установить TAB—FOUND JUST-FOUND false, CURSOR 13. Повторять шаги от 2 до 4 при i = BEGINLINE, ..., 2, 1: 2. [Проверка наличия кодов табулирования и выключки в предыдущем тексте. ] Если TAB —FOUND и JUST—FOUND, то перейти к шагу 5. 3. [Проверка наличия кода табулирования в тексте LINE [il. ] Если ПТАВ—FOUND и SUB (LINE [iJ, 11, 2) = — то установить TAB________FOUND true, DUMMY <- SPAN (LINE [il, ’0123456789/’, CURSOR, TABLIST,”, false), если SUB (LINE [ij, CURSOR, 1) — ’iaf, (Глобальный код табулирования?) то установить TABFLAG ч- ’G’ и вызвать процедуру SET-TABS (TABLIST); в противном случае установить TABFLAG ч- ’N’. 4. [Проверка наличия кода выключки в тексте LINE [i J. I Если Й JUST-FOUND и SUB (LINE [il, 11, 2) = ’&&’, то если SUB (LINE [il, 13, 1) = ’П’> то установить RJUSTIFY ч- false, c 14; в противном случае установить RJUSTIFY ч- true, с 13, RMARGINSUB (LINE [i], c, INDEX (SUB (LINE [i J, c), 7’) - 1). Установить i BEGINLINE — 1, PRINTLINE ”. 5. [Инициировать фазу печати.) 6. [Запустить фазу печати.) Установить ii + 1. Если i > ENDLINE, то печатать PRINTLINE и закончить выпол- нение алгоритма. 7. [Проверка наличия кодов пропуска. J Если SUB (LINE [i), 11,2) = ’4ф4ф’, то печатать PRINTLINE, если SUB (LINE [il, 13, 1) = ’P’, то перейти на новую страницу; в противном случае пропустить SUB (LINE [i], 13, INDEX (SUB (LINE [i ], 13), ’Г)— 1) строчек. Установить PRINTLINE-*- SUB (LINE [i), 14 + INDEX (SUB (LINE [il, 14), */’)). 8. [Проверка наличия кодов формирования заголовков. J Если SUB (LINF [il, 11, 2) = то печать PRINTLINE (печать не производится, если строка PRINTLINE пуста), если SUB (LINE [ij, 13, 1) = ’С’, то вызвать процедуру CENTER (LINE [i J, RMARGIN); в противном случае 6* 163
печатать SUB (LINE [i], 16) и установить PRINTLINE^-”, если SUB (LINE [i j, 14, 1) = ’U\ то печатать символы подчеркивания. 9. [Проверка наличия кода абзацного отступа. ] Если SUB (LINE [i], II, 2) = то печатать PRINTLINE, установить CURSOR 13, Установить DUMMY SPAN (LINE [ij, ’0123456789/’, CURSOR, TABLIST,”, false), вызвать процедуру SET—TABS (TABLIST), если SUB (LINE [i], CURSOR,, 1) = то установить TABFLAG <— ’G’; в противном случае установить TABFLAG <— ’L’. Установить PRINTLINE^- SUB (LINE [i], CURSOR + 2), если PRINTLINE = ”, то вернуться к шагу 6. 10. [Проверка наличия кода выключки. ] Если SUB (LINE [i 1, 11, 2] = то если SUB(LINE[i), 13, 1) = то установить RJUSTIFY false, RMARGINSUB (LINE [i], 14, INDEX (SUB (LINE [i I, 14, 7’)— 1); в противном случае установить RJUSTIEY-ч- true, RMARGIN <— ч- SUB (LINE li), 13, INDEX (SUB (LINE [i ], 13), V’) — 1). 11. [Обработка абзацев, если задано.) Если TABFLAG = ’L’ или - TABFLAG = ’G’, то если NO—OF_TABS > 1, то вызвать процедуру TABPRINT (PRINTLINE) и перейти к шагу 6; в противном случае установить PRINTLINE ч- DUPL (’b’, TAB [1]) О PRINTLINE. 12. [Задание цикла печати. 1 Повторять шаги 13 и 14 до тех пор пока LENGTH (PRINTLINE) > RMARGIN: 13. [Выполнение правой выключки. 1 Если RJUSTIFY, то установить PRINTLINE JUSTIFICATION (PRINTLINE, RMARGIN); иначе установить PRINTLINE NO—JUSTIFICATION (PRINTLINE, RMARGIN). 14. [Левая выключка строки PRINTLINE и установка аб- заца.] Установить DUMMY SPAN (PRINTLINE, ’b’, 1,”,”, true). (Фиктивное присваивание выполняется с целью стягивания начальных пробелов строки.) Если TABFLAG = ’G’, то установить PRINTLINE DUPL (’b’, TAB [1]) (□PRINTLINE. 15. [Модификация TABFLAG. 1 Если TABFLAG =’L’, to установить TABFLAG -e?- *N’. Вернуться к шагу 6. Поскольку алгоритм $$PRINT довольно сложен, целесооб- разно исследовать его подробней. Шаги 1—4 проверяют наличие 164
предыдущих глобальных кодов табулирования и правой выключки, действие которых распространяется на строчки текста с номерами от BEGINLINE по ENDLINE. В шаге 1 выполняется начальная установка переменных, используемых в процессе поиска, и орга- низуется необходимый для поиска цикл с убывающим номером строчки. Если обнаружены и код тубулирования и код правой выключки, то в шаге 2 осуществляется выход из этой части алго- ритма. В шаге 3 проверяется наличие символов табулирования если соответствующий код не был обнаружен ранее. В слу- чае успеха выделяется список параметров, относящихся к коду табулирования, и присваивается строке TAB LIST с помощью базовой функции поиска по образцу SPAN. Отметим, что мы применяем функцию SPAN в операторе фиктивного присваивания, чтобы получить желаемый результат без использования значения логического выражения, возвращаемого функцией SPAN. Обра- щение к алгоритму SET___TABS осуществляется для того, чтобы сохранить параметры, заданные табулирующим кодом, если этот код является глобальным. (Разработка алгоритма SET___TABS дана в качестве упражнения в конце пункта.) Шаг 4 проверяет наличие кода выключки и присваивает соответствующее логиче- ское значение переменной RJUSTIFY. В шаге 6 начинается цикл печати. Следует учесть, что когда в шаге 7 происходит печать, то для каждой строки PRINTLINE выполняется левая выключка, причем длина строки прн этом меньше значения RMARGIN. Шаг 7 проверяет наличие кодов пропуска в тексте LINE [i I и при его обнаружении выводит иа печать строку PRINTLINE и осуществляет указанное число про- пусков строчек или пропуск страницы. Шаг 8 распознает коды формирования заголовков. Описание алгоритма CENTER, к ко- торому происходит обращение, если задан параметр ’С’, пред- лагается в качестве упражнения в конце этого пункта. Коды абзацного отступа обрабатываются в шаге 9. При этом снова происходит обращение к функции SPAN и алгоритму SET—TABS для сохранения параметров кода. В зависимости от того, являются ли эти параметры глобальными или локаль- ными, переменной TABFLAG присваивается значение ’G’ или *L’. Шаг 10 устанавливает значение индикатора RJUSTIFY и присваивает переменной RMARGIN новое значение, вычисляе- мое в этом шаге. В шаге 11, если TABFLAG имеет значение *L’, а значение переменной NO___OF__.TABS меньше 2, перед началом строки PRINTLINE добавляются пробелы. В противном случае проис- ходит обращение к алгоритму TABPRINT. Его описание приво- дится ниже. Шаги 12—14 реализуют цикл, обеспечивающий непрерывную выдачу текста, пока строка PRINTLINE не станет короче значе- ния RMARGIN. Функция DUPL заимствована из языка СНОБОЛ н используется в шагах 11 и 14 для формирования абзаца. 165
В шаге 15 TABFLAG получает значение, имеющее смысл «неприменимо», если предыдущий код табулирования имеет только локальное действие. В описываемом ниже алгоритме TABPRINT позиции колонок таблицы, представленные вектором TAB, используются при обработке текста, который содержится в строке PRINTLINE. Элементы текста, начинающиеся с новых позиций колонок, закан- чиваются символом 7’. Отметим, что при обращении к TABPRINT правая выключка игнорируется, так как предполагается, что текст, представляющий таблицу, не должен подвергаться правой вы- ключке. Алгоритм TABPRINT. Заданы строка PRINTLINE и позиции колонок таблицы, представленные вектором TAB. Осуществляется печать текста содержащегося в PRINTLINE, в табулированной форме вплоть до разделяющего символа 7’. LINEOUT — это символьная строка, которая должна быть напечатана в табули- рованной форме на одной строчке полосы, NO—OF—TABS — текущее число позиций колонок и i — счетчик цикла. 1. [Начальная установка. J Установить i 1, LINEOUT <- ”, CURSOR ч- 1. 2. [Цикл поиска разделяющего символа 7’.] Повторять шаги от 3 до 5 до тех пор, пока FIND (PRINTLINE, 7’, CURSOR, MATCH—STR,”, false): 3. [Проверка наличия в тексте символа /, представленного в виде / /. 1 Если SUB (PRINTLINE, CURSOR, 1) = 7’, то уста- новить TEMP—STR ч- TEMP—STR О MATCH—STR Q 7’, CURSOR CURSOR + 2 и вернуться к шагу 2; в противном случае установить TEMP—STR ч- TEMP—STR Q[MATCH _STR. 4. [Занесение строки TEMP—STR в заданное место в LINEOUT.] Установить SUB (LINEOUT, >ТАВ*[П, LENGTH (TEMP—STR)) ч~, TEMP—STR, TEMP—STR'ч- 5. [Модификация счетчика и сравнение его значения с числом колонок таблицы.] Установить i ч- 1 + 1. Если i > NO—OF_TABS, то установить PRINTLINE ч- SUB (PRINTLINE, CURSOR), пе- чатать LINEOUT, установить i ч—1, LINEOUT-*-”. 6. [Вывод табулированной строки.] Печатать LINEOUT и закончить выполнение алгоритма. Последнюю часть системы ЕТЕХТЕ, которую мы обсудим, представляет интерпретатор команд. Интерпретатор получает входные данные либо с перфокарт, либо с телетайпа и проверяет в них наличие команды. В результате интерпретации команд в текст включаются форматные коды и осуществляется обращение к необходимым модулям для выполнения тех действий, которые определяются командами. Функционирование интерпретатора команд системы ЕТЕХТЕ иллюстрируется алгоритмом С—INTERPRET. 166
Алгоритм С_INTERPRET. Заданная входная строка INPUT исследуется на наличие в ней команд редактирования. Каждая команда редактирования интерпретируется посредством обраще- ния к модулям, рассмотренным в данном пункте. LCNT — это индекс массива LINE, указывающий очередную строчку текста, a LMTFIND — алгоритм, вычисляющий значения переменных BEGINLINE и ENDLINE для команд типа $$. в. [Обработка входных данных до конца сеанса редактиро- вания. ] Повторять шаги от 2 до 9 до тех пор, пока не закончится сеанс. 2. [Ввод очередной входной строки текста и ее печать. ] Чи- тать INPUT и печатать INPUT. 3. [$£ —команды?! Если SUB (INPUT, 1, 2) ’$$*, то печатать ’НЕДОПУСТИМЫЕ ДАННЫЕ’ и вернуться к шагу 1. 4. [Обработка $5 — команд, начиная с команды $$ADD. ] Если SUB (INPUT, 1, 5) = ’$$ADD’, то вызвать процедуру $$ ADD и вернуться к шагу 1. 5. [ $.$LIST? ] Установить CURSOR 8. Если SUB (INPUT, 1, 6) = ’$$LIST’, то вызвать процедуру LMTFIND (INPUT, CURSOR) (LMTFIND формирует значения переменных BEGINLINE и ENDLINE), вызвать процедуру LIST (BEGINLINE, ENDLINE) и вернуться к шагу 1. 6. [$$CHANGE?J Установить CURSOR 10. Если SUB (INPUT, 1, 8) = ’$$ CHANGE', то вызвать процедуру LMTFIND (INPUT, CURSOR), вызвать процедуру $$CHANGE (BEGINLINE, ENDLINE) и вернуться к шагу 1. 7. [$$DELETE?J Установить CURSOR 10. Если SUB (INPUT, 1, 8) = ’$$DELETE’, то вызвать процедуру LMTFIND (INPUT, CURSOR), вызвать процедуру $$DELETE (BEGINLINE, ENDLINE) и вернуться к шагу 1. 8. [$$PRINT?J Установить CURSOR 9. Если SUB (INPUT, 1,7) = ’$$PRINT’, то вызвать процедуру LMTFIND (INPUT, CURSOR), вызвать процедуру $$PRI N'T (BEGINLINE, ENDLINE) и вернуться к шагу 1. 9. [Ошибка во входных данных. ] Печатать ’ НЕДОПУСТИМАЯ КОМАНДА’ и вернуться к шагу 1. Алгоритм С— INTERPRET состоит из одного большого цикла, в котором интерпретируются команды типа $$. Идентификация $-$-команды осуществляется простым сравнением строк на ра- венство. Обращение к процедуре LMTFIND производится для присваивания переменным BEGINLINE и ENDLINE значений граничных номеров строчек. В процессе выполнения алгоритма в соответствии с заданными командами происходит обращение к другим алгоритмам, рассмотренным в этом пункте. Ниже приводится алгоритм LMTFIND, вычисляющий значе- ния переменных BEGINLINE и ENDLINE. Алгоритм LMTFIND. Заданы текстовая строка INPUT и зна- чение курсора, указывающего для соответствующей команды 167
C_INIER₽RET Рис. 2-5.2. Схема логических связей между модулями системы ЕТЕХТЕ типа $5 начало списка ее аргументов, представляющих иомера строк. Алгоритм вычисляет значения переменных BEGINLINE и ENDLINE. 1. выделение полей параметров для BEGINLINE и ENDLINE. J Если FIND (INPUT, 7’, CURSOR, BEGINLINE, ”, false), то если “[FIND (INPUT, ’Г, CURSOR, ENDLINE, ”, false), to печатать ’ОШИБКА — ОТСУТСТВУЕТ НОМЕР КОНЕЧНОЙ СТРОКИ’ и закончить выполнение алгоритма. В противном слу- чае печатать ’ОШИБКА — ОТСУТСТВУЮТ ПАРАМЕТРЫ’ и закончить алгоритм. 2. [Проверка наличия знака * и формирование номера строчки. 1 Если BEGINLINE = то установить BEGlNLINE-<- LCNT — 1 и закончить выполнение алгоритма. Если ENDLINE = **’, то установить ENDLINE LCNT — 1 и закончить выполнение алгоритма. Установить BEGINLINE BEGINLINE/10, ENDLINE ч- ENDLINE/10 и закончить выполнение алгоритма. Рис. 2-5.2 поясняет логические связи между алгоритмами, кото- рые были рассмотрены выше или предложены в качестве упраж- нений в конце пункта. Среди представленных на рисунке алгоритмов имеется и алго- ритм F—COMMANDS, обращение к которому происходит в алго- ритме $$ADD. Но при обсуждении команды ^ADD мы не стали ни описывать алгоритм F___COMMANDS, ни предлагать его в качестве упражнения, поскольку в то время еще не были определены форматные коды. И только теперь, хорошо их усвоив, мы можем обратиться к описанию алгоритма F____COMMANDS. Алгоритм F—COMMANDS. Заданная строка INPUT скани- руется с целью обнаружения в ней форматных команд. Если форматная команда будет найдена, то происходит ее интерпрета- ция и формирование соответствующего форматного кода. 1. [Проверка наличия форматных команд и удаление ключе- вых слов.) Если MATCH (INPUT, TAB/’, 1, ”, *@@Д true), то перейти к шагу 2. Если MATCH (INPUT,’ 9© % TITLE/’, 1, true), то перейти к шагу 2. Если MATCH (INPUT, ’##SKIP/’, 1, ”, ’##*, true), то перейти к шагу 2. 168
Установить DUMMY MATCH (INPUT, ’&& JUSTIFY/’, 1, ”, true). 2. [Установка значения результатов и выход. ] Установить F__COMMANDS-е-INPUT и закончить выполнение алгоритма. В этом алгоритме форматные команды идентифицируются с помощью базовой функции поиска по образцу MATCH, как описано в п. 2-3.2. Ключевые слова TAB, TITLE, SKIP н JUSTIFY выбрасываются из кодов, которые запомнены в массиве LINE. Рассмотрим теперь пример, демонстрирующий'большинство рассмотренных возможностей системы ЕТЕХТЕ. Некая компа- ния по производству коммерческих машин, поставляющая обо- рудование в Канаду и США, управляет продажей из своей штаб- квартиры, имея торговых представителей в каждой провинции и каждом штате. Ежегодно компания рассылает письма потен- циальным покупателям, информируя их о том, что в ближайшее время к ним могут обратиться местные торговые представители компании. Письма подготовляют в большом количестве экземпля- ров, а для их персонализации используется вычислительная ма- шина. Каждое письмо поступает в систему ЕТЕХТЕ в следующей общей форме: $$ADD &&JUSTIFY/-|7O/ @(tflTAB'40/(frl87 MAIN STREET WINNIPEG 1, MANITOBA ♦DATE* ##SKIP/1/ @(gTAB 0 '(gf *x* ♦ADDRESS* •CITY* «PROVINCE* ##SKIP/1/ DEAR *Z* ##SKIP/1/ &&JUSTIFY/7O/ @@TAB/5/"l THE BUSINESS WORLD IS RAPIDLY CHANGING AND OUR CORPORATION HAS BEEN KEEPING PACE WITH THE NEW REQUIREMENTS FORCED UPON OFFICE MACHINERY. WE ARE GIVING YOU. *Z* AS A KEY FIGURE IN THE *CITY* BUSINESS COMMUNITY. AN OPPORTUNITY TO BECOME FAMILIAR WITH THE LATEST ADVANCEMENTS IN OUR EQUIPMENT. A REPRESENTATIVE OF OUR CORPORATION IN «PROVINCE* WILL BE SEEING YOU WITHIN *N* WEEKS. HE WILL TAKE SEVERAL MACHINES TO *CITY* WHICH ARE INDICATIVE OF A WHOLE NEW LINE OF OFFICE MACHINES WE HAVE RECENTLY DEVELOPED. @@TAB. 5<1 OUR SALES REPRESENTATIVE IS LOOKING FORWARD TO HIS VISIT IN •CITY* HE KNOWS THAT THE MACHINES HE SELLS COULD BECOME AN INTEGRAL PART OF YOUR OFFICE ONLY A FEW DAYS AFTER INSTALLATION? 169
##SKIP/1/ &&JUSTIFY/ 70/ @фТАВ/40/©. SINCERELY, ##SKIP/1.' ROGER SMITH, MANAGER OFFICE DEVICES INCORPORATED ##SKIP/P; 1 В сфере бизнеса происходят быстрые изменения, и паша компания идет в ногу с новыми требованиями, предъявляемыми к коммерческой тех- нике. Вам, * *Z *, как ведущей фигуре в деловом мире города ГОРОД*, мы даем возможность ознакомиться с нашими последними достижениями в разработке оборудования. Представитель нашей компании в провинции * ПРОВИНЦИЯ* нанесет вам визит в течение ближайших *N* недель. Он доставит в город * ГОРОД * несколько машин, являющихся образцами нового семейства коммерческих машин, недавно разработанных нами. @@ТАВ/5/ ~| Наш торговый представитель готовится к визиту в город * ГОРОД*. Он хорошо знает, что продаваемые им машины могли бы стать неотъемлемой частью вашего учреждения почти сразу после их установки. Письмо в такой форме вводится в основную память, а его копия помещается в файл во внешней памяти для повторной обработки в любое другое время. Затем поля текста, выделенные знаками *, подвергаются изменению в соответствии со следу- ющими конкретными данными. 1. Дата (например August 17, 1975), 2. Мистер (или миссис, и т. п.): инициалы, фамилия (например, Mr.A.L. Strider). 3. Адрес улицы (например, 2014 Centennial Drive). 4. Город, провинция или штат (например, Thompson, Ma- nitoba). 5. N — число недель до визита торгового представителя (например, three — три). Сеанс работы системы ЕТЕХТЕ для подготовки персонального письма осуществляется в соответствии со следующими коман- дами: $$CHANGE/000!0/ */ * DATE * /AUGUST 17, 1975/ $$CHANGE/00010/*/* ADDRESS*/2014 CENTENNIAL DRIVE/ $$CHANGE/'00010/' */ *C1TY */THOMPSON/ $$CHANGE/00010/ * / * PROVINCE * /MANITOBA/ $$CHANGE/00010/ * / * N * /THREE/ $$CHANGE/000I0/ */ * X */MR.A.L.STRIDER/ $$CHANGE/00010/ */ * Z */.MR.STRIDER/ $$PRINT/00010/*/ 170
В результаее будет напечатано письмо в заданном формате: 187 MAIN STREET WINNIPEG 1, MANITOBA AUGUST 17, 1975 MR. A.L. STRIDER Л014 CENTENNIAL DRIVE THOMPSON, MANITOBA DEAR MR. STRIDER, THE BUSINESS WORLD IS RAPIDLY CHANGING AND OUR CORPORATION HAS BEEN KEEPING PACE WITH THE NEW REQUIREMENTS FORCED UPON OFFICE MACHINERY. WE ARE GIVING YOU, MR. STRIDER, AS A KEY FIGURE IN THE THOMPSON BUSINESS COMMUNITY, AN OPPORTUNITY TO BECOME FAMILIAR WITH THE LATEST ADVANCEMENTS IN OUR EQUIPMENT. A REPRESENTATIVE OF OUR CORPORATION IN MANITOBA WILL BE SEEING YOU WITHIN THREE WEEKS. HE WILL TAKE SEVERAL MACHINES TO THOMPSON WHICH ARE INDICATIVE OF A WHOLE NEW LINE OF OFFICE MACHINES WE HAVE RECENTLY DEVELOPED. OUR SALES REPRESENTATIVE IS LOOKING FORWARD TO HIS VISIT IN THOMPSON. HE KNOWS THAT THE MACHINES HE SELLS COULD BECOME AN INTEGRAL PART OF YOUR OFFICE ONLY A FEW DAYS AFTER INSTALLATION. SINCERELY, ROGSR SMITH, MANAGER CFFICE DEVICES CORPORATION После того как будет напечатано определенное письмо, может быть введено или считано новое множество команд типа $$CHANGE, соответствующее информации о новом покупателе. При этом, конечно, для нового набора данных в основную память машины должен быть считан общий текст письма с внешнего устройства, содержащего главную копию. Этот процесс продол- жается до тех пор, пока не будет подготовлено требуемое коли- чество писем к покупателям. Мы подробно исследовали в этом пункте редактирование текста главным образом по той причине, что оно дает отличную возмож- ность проиллюстрировать концепции, относящиеся к средствам манипулирования символами, рассмотренным в этой главе. Мы не коснулись многих важных команд, которые должны быть в ре- дакторе текста. В частности, не рассматривались команды для включения текста между строками или для слияния больших частей текста. Выполнение таких операций требует особого типа представления данных в памяти — в виде связанного списка, 171
обсуждаемого нами в гл. 4. Упражнения в конце гл. 4 содержат задачи, имеющие отношение к разработке команд включения и слияния для системы ЕТЕХТЕ. В следующих упражнениях от читателя требуется разработать те алгоритмы, которые использо- вались в этом пункте, но ие были формально описаны. Упражнения к п. 2-5.1 1. Алгоритм $$CHANGE не обеспечивает поиск по образцу для строк по всем элементам массива LINE, используемого для построчного запоминания текста. Составьте новый алгоритм $$CHANGE, который обладал бы этим свойством. 2. Пусть в качестве параметров заданы символьная строка PRINTLINE и значение максимальной длины строки RMARG1N. Сформулируйте алгоритм NO—JUSTIFICATION, обращение к которому производилось бы тогда, когда печатный текст не должен подвергаться правой выключке. 3. Дана символьная строка TABLIST, содержащая значения позиций таб- личных колонок, указанные в команде iaj(a[,TAВ. Напишите алгоритм для вы- деления отдельных значений позиций и их присваивания вектору ТАЕ. Напри- мер, если TABLIST есть строка ’20/40/ 60/', то алгоритм должен установить TAB [11 <- 20, ТАЕ [21 <- 40 и ТАЕ [3] 60. 4. Даны определенная строка текста LINE [I] и текущее значение пере- менной RMARGIN. Составьте алгоритм для расположения текста LINE (i j по центру выходной строчки, предположив, что максимальная длина этой строчки равна RMARGIN. 2-5.2. Лексический анализ При рассмотрении грамматик в п. 2-2.2 мы указывали, что часто язык должен описываться на двух уровнях — уровне «слов» и уровне «предложений». Например, в английском языке для образования слов можно соединять друг с другом лишь опре- деленные сочетания символов из алфавита языка. Если строка сим- волов содержит форму, не являющуюся английским словом, то эта строка не может быть предложением английского языка. С другой стороны, даже если строка символов алфавита состав- лена из английских слов, она не обязательно образует предложе- ние английского языка (что иллюстрируется строкой 1 'The sang the girl'). Поэтому решение о том, является ли строка предложе- нием языка, похожего на естественный, требует сначала ее ана- лиза на пословном (или лексическом) уровне, чтобы определить, состоит ли строка из множества правильных символов и слов, и затем — на уровне предложений (или синтаксическом уровне) для выяснения того, что слова в строке образуют предложение языка. В этом пункте мы займемся лексическим анализом исходных строк (или операторов) подмножества некоторого воображаемого языка программирования высокого уровня. Многие из представ- ленных здесь методов применимы к лексическому анализу, вы- полняемому и при машинном переводе естественного языка. Непереводимый набор слов. — Прим. пер. 172
В п. 5-2.3 будет обсуждаться вторая часть анализа, а именно синтаксический анализ. Там основное внимание мы уделим син- таксическому анализу языка программирования, выполняемому компилятором или интерпретатором. В компиляторе или интерпретаторе модуль, который во вход- ном тексте выделяет основные «словоподобные» конструкты, та- кие как имена идентификаторов, числовые константы, ключевые или зарезервированные слова, операторы и т. д., обычно называют сканером [7}. В последующем обсуждении мы рассмотрим состав сканера для простого языка, содержащего только арифметические выражения. Пословными, или лексическими, компонентам и в этом языке являются идентификаторы, числовые константы, операторы сложения, вычитания, умножения, деления и возведения в сте- пень, а также скобки н оператор присваивания. Синтаксис каж- дого из этих примитивных лексических классов задается следу- ющими грамматическими правилами: (идентификатор) : : = (имя) (имя) : : = (буква) j(имя) (буква) | (имя) (цифра) (буква) :: = A[B|C|D|E|F|G|H|I|J|K|L|M[N[O| P|Q|R|S|T|U|V[W|X|Y|Z (число) : : — (цифровая строка) j .(цифровая строка) | (цифровая строка).(цифровая строка) (цифровая строка) : : = • цифра) | 'цифровая строка) (цифра) 0|1|2|3[4|5|6[7|8|9 (цифра) : : (слож/выч) (умн/дел; (степ^ О') й: # Терминалы этой грамматики [т. е. А, В, ..., Z, 0, 1, ..., 9, +, —, *, /, **, (,), = ) вместе с символом пробела составляют алфа- вит рассматриваемого нами языка. Нетерминальные символы, стоящие в левой части каждого правила (т. е. (идентификатор', (число-), (слож/выч), (умн/дел), (степ), <(>, <) >, (=) н т. д.), представляют лексические классы, которые должен идентифици- ровать сканер. Как только в исходном тексте выделен лексический класс, имя класса (или некоторый объект, представляющий имя класса) вместе с соответствующей исходной формой передается синтаксическому анализатору, который выполняет фазу синтак- сического анализа. Имена классов выступают как терминалы в грамматике, описывающей предложения языка. Именно эта грамматика образует основу для выполнения фазы синтаксиче- ского анализа. Для рассматриваемого языка при разборе может быть использована следующая грамматика: (оператор присваивания) : : — (идентификатор) (=>( выражение) (выражение) : : = (терм) | (выражение) (слож/выч) (терм'< 173
(терм) : : = (форма) | (терм) (умн/дел) (форма) (форма) : : = (первичный) | (форма) (степ) (первичный) (первичный) : : == (идентификатор) | (число) | (() (выражеиие)()) В большинстве случаев нецелесообразно передавать синтак- сическому анализатору имя лексического класса, такое как (иден- тификатор). Вместо этого с каждым классом ассоциируется неко- торое уникальное представляющее число *, и это число вместе с соответствующей исходной формой (т. е. идентификатором) передается синтаксическому анализатору. 'Для нашего языка установим следующее соответствие между классами и представ- ляющими числами: (идентификатор) обозначим числом^!, (число)—2, (слож/выч)'—3, (умн/дел) —4, ((> —5, <), } —6, <=) —7 и (степ) —8. Сканер должен выделять также и класс символов, называемых разделителями. Но эти символы не передаются синтаксическому анализатору. В рассматриваемом языке, содержащем только опе- раторы присваивания, таким разделителем служит символ про- бела (т. е., например, предложение X = A* Z синтаксически эквивалентно предложению X = А * Z). Теперь мы можем описать алгоритм сканирования исходного предложения, принадлежащего описанному выше языку. Алгоритм SCAN. Задано исходное предложение SOURCE/ Алгоритм выделяет в предложении составляющие его лексические классы и выводит на печать их исходные формы вместе с соответ- ствующими, представляющими^числами. Переменная CHAR обо- значает^ исследуемый символ исходной строки. FORM содержит текущую выделяемую исходную форму, a RЕР хранит пред- ставляющее число этой исходной формы. LETTERS — это сим- вольная строка ’ABCDEFGHIJKLMNOPQRSTUVWXYZ’, а DIGITS — символьная строка ’0123456789'. CURSOR содер- жит текущую позицию символа в строке SOURCE. DUMMY,S,F — рабочие переменные. 1. [Инициализация.] Установить CURSOR-*-!, печатать строку SOURCE. 2. [Сканирование исходной строки.} Повторять шаги 3—9 до тех пор, пока CURSOR LENGTH (SOURCE). 3. [Удаление пробелов.! Если SPAN (SOURCE, ’b’, CURSOR, false), то вернуться к шагу 2. 4. [Выделение очередного символа. ] Установить CHAR SUB (SOURCE, CURSOR, 1). 5. [Проверка на идентификатор.) Если INDEX (LETTERS, CHAR) 5^ 0, то установить DUMMY ч-SPAN (SOURCE, LETTERS О DIGITS, CURSOR, FORM, ”, false), REP и перейти к шагу 9. * Такое число называют внутренним представлением соответствующего класса. — Прим. ред. 174
6. [Проверка на число.) Если INDEX (DIGITS, CHAR) =f= =# 0, то установить DUMMY ч- SPAN (SOURCE, DIGITS, CURSOR, FORM, ”, false), если MATCH (SOURCE, CURSOR, false), то (Ьсли SPAN (SOURCE, DIGITS, CURSOR, F, ”, false), то установить • FORM ч-FORM Q Q F;Jb противном случае установить ;FORM ч- FORM О ’.’J установить REP4t-<-2 и перейти к шагу 9; в противном случае если CHAR = то установить CURSOR ч-CURSOR + 1, DUMMY ч— SPAN (SOURCE, DIGITS, CURSOR, FORM,”, false), FORM-*- r» FORM, REP # ч- 2 и перейти к шагу 9. 7. [Проверка на оператор возведения в степень. ] Если MATCH (SOURCE, CURSOR, FORM, ”, false), то установить REP4^4-8 и перейти к шагу 9, 8. [Прочие операторы.] Установить S ч- INDEX (’4- *()=’, CHAR), FORM ч-CHAR. Если S > 0, то установить REP ч- S 4- 2; в противном случае установить S ч- INDEX (’—/’, CHAR), если S = 0, то печатать ’НЕДОПУСТИМЫЙ СИМВОЛ В ИСХОДНОЙ СТРОДЕ’, установить CURSOR ч- CURSOR 4- 1, и вернуться к шагу 2; в противном случае установить REP ч- ч— S + 2. Установить CURSOR <-CURSOR 4- 1. 9. [Печать представляющего числа и исходной формы. ] Пе- чатать REP # , FORM. 10. [Конец. I Выход. Если алгоритм SCAN выполняется над исходным предложе- нием ’ Z1 — 2 4- У’> то в шаге 1 будет напечатано Z1 = 2 4- Y. В шаге 3 из этого предложения с помощью функции SPAN вы- черкиваются два начальных пробела и осуществляется возврат к шагу 2, в котором проверяется условие продолжения цикла при значении курсора, равном 3, и длине исходного предложения, равной 10. Поскольку условие продолжения цикла имеет истин- ное значение, то снова выполняется шаг 3. Но теперь функция SPAN возвращает значение false (так как отсутствует пробел в курсорной позиции 3), и в шаге 4 переменной CHAR присваи- вается значение ’Z’. В шаге 5 ’Z’ распознается как буква, и про- изводится сцепление символов ’Z’ и ’Г. Далее.’Z1’ присваивается переменной FORM, и CURSOR становится равным 5. Переменная REP# принимает значение 1, после чего управление передается шагу 9. В шаге 9 печатаются значения ’1’ и ’Z1’ и осуществляется возврат к шагу 2 для повторения цикла. При исходном предложении ’ Z1 — 2 4- Y* работа алгоритма отобразится в виде следующей последовательности данных, вы- водимых на печать: Zl = 24-Y 1 Z1 7 = 175
2 2 3 + 1 Y Результаты лексического анализа исходных предложений ’ТАХ = = RATE * (INCOME — DEDUCTIONS)’ и ’1 = XY Z + **2’ будут таковы: TAX = RATE * (INCOME—DEDUCTIONS) I TAX 7 = I RATE 4 * 5 ( 1 INCOME 3 — 1 DEDUCTIONS 6 ) 1 = XY Z+ **2 2 1 7 1 XY 1 Z 3 + 8 ** 2 2 » Последний пример иллюстрирует тот факт, что в задачу ска- нера входит только распознавание должным образом представ- ленных лексических единиц, а не идентификация предложений исходного языка. Задача разбора предложений языка программи- рования будет более подробно рассмотрена в п. 5-2.3. В некоторых компиляторах (особенно в однопроходных ком- пиляторах «учебных» языков) представляющее число и исходная форма непосредственно передаются синтаксическому анализатору. Синтаксический анализатор использует представляющее число класса в программах разбора. Номер продукции, в соответствии с которой синтаксический анализатор осуществляет редукцию оператора, передается фазе генерации кода. На этой фазе исход- ная форма и номер продукции применяются при генерации объект- ного кода. В данном процессе используются имена определенных переменных, значения констант, арифметические операторы н т. д. В многопроходном компиляторе представляющее число и исходная форма часто передаются программе генерации таблиц. Эта программа во время первого прохода исходных предложений формирует ряд таблиц, таких как таблица имен идентификаторов, таблица номеров исходных операторов, таблица функций, опре- деленных программистом, таблица макрооператоров или таблица препроцессрров и т. д. 176
Шаги 5—8 в алгоритме SCAN зависят от конкретного языка. Информацию, которая имеет отношение к логическим решениям, принимаемым на этих шагах, можно представить в табличном виде. В этом случае, если потребуется новый сканер, нужно бу- дет изменить лишь содержание таблиц, чтобы оно соответствовало новому языку, а общий алгоритм сканирования остается неизмен- ным. Эти «таблично-управляемые» сканеры особенно полезны в случаях, когда компилятор использует более одного сканера. Такая ситуация возникает, если компилятор должен обрабаты- вать входную информацию, поступающую с двух илн более раз- личных устройств ввода, имеющих разные наборы символов (например, с телетайпа и устройства ввода перфокарт). Заканчивая материал по лексическому анализу, следует за- метить, что мы не коснулись в нашем обсуждении двух очень важных функций сканера, а именно, удаления комментариев и формирования последовательных номеров предложений. Этим функциям посвящены два из следующих ниже упражнений. Упражнения к п. 2-5.2 1. Напишите на языке ПЛ/1 программу, которая сканирует исход- ные предложения языка операторов присваиваний, рассмотренного в этом пункте. Программа должна обрабатывать комментарии, подобные тем, которые исполь- зуются в языке ПЛ/1. Комментарии должны печататься сканером вместе с опера- торами присваивания, но синтаксическому анализатору не должен передаваться никакой признак наличия комментариев. Поэтому для исходного предложения X = / * КВАДРАТ ВЕЛИЧИНЫ X*/ Х**2 программа должна формировать лишь следующие строки: •| Х’,’6 =’,’1 Х’,’8 **’,’2 2’. 2. Добавьте в сканер, заданный в упр. 1, средство автоматической нуме- рации исходных предложений. В соответствии с этим, если, например, первыми двумя предложениями программы являются ’X = Y’ и ’Y = Z * 4’, то они должны быть напечатаны сканером в виде 1 X = Y 2 Y = Z*4 3. В качестве курсового проекта разработайте сканер для подмножества языка ПЛ/1, содержащего операторы присваивания строковых и числовых зна- чений, процедуры, циклы DO, группы DO, операторы DO с фразой WHILE, оператор IF-THEN-ELSE и оператор ввода-вывода типа LIST. 4. Обобщите упр. 3, добавив в сканер средство автоматического разделения текста на абзацы. При автоматическом разделении на абзацы для тел процедур и конструктов ЕЮ автоматически устанавливается абзацный отступ на опреде- ленное число символьных позиций, скажем, на четыре позиции. Фразы IF, THEN и ELSE автоматически выравниваются по вертикали. Поэтому если па одной строчке, или перфокарте, задается, например, исходный текст ’IF X = Y THEN DO; X = Z; Y = 0; END; ELSE X = 0;’ 177
он должен быть напечатан сканером в виде IF X == Y THEN DO; X = Z; Y — 0; END; .. ELSE X 0; 2-5.3. Индексирование типа KWIC Одной из быстроразвивающихся областей применения вычислительной техники является поиск информации. В системах информационного поиска база данных, которая может содержать большую совокупность структур данных, поддерживается в онлай- новом режиме с использованием больших файлов прямого доступа (которые могут располагаться, например, на дисках). Поиск требуемой информации осуществляется в этих файлах с помощью указателей, сформированных на основе запроса пользователя. Одной из задач, относящихся к проблематике информационно- поисковых систем (и особенно автоматизированных библиотечных систем), является разработка эффективной схемы индексирования. Один из методов индексирования, который широко применяется в библиотечных системах, называется пермутационным индекси- рованием, или индексированием типа KWIC (Key Word In Con- text — ключевое слово, взятое в контексте). Указатель типа KWIC дает контекст каждого слова. На практике этот метод индексирования наиболее часто применяют для фраз и особенно заголовков, выбранных из индексируемых документов. Обеспечи- вая возможность быстрого определения роли слова, индексирова- ние типа KWIC в то же время требует больших затрат памяти, необходимой для хранения массивов контекстуальной информации. Чтобы проиллюстрировать это обстоятельство, рассмотрим пример, в котором в качестве предложения фигурирует загла- вие данной книги х ”An Introduction to Data Structures with Applications”. В соответствии co схемой индексирования KWIC каждый эле- мент фразы (в данном случае — заглавия книги) проверяется на ключевые слова и воспроизводится в пермутированиом виде для каждого ключевого слова. Для нашего примера список перму- тированных указателей будет таким: Introduction to Data Structures with Applications//An Data Structures with Applications// An Introduction to Structures with Applications// An Introduction to Data Applications// An Introduction to Data Structures Подчернутое первое слово в каждом из указателей мы называем индексирующим словом. Совокупность индексирующих слов, 1 «Введение в структуры данных и их применения». 178
выбранных из названия книги, образует множество ключевых слов этого названия (т. е. Introduction, Data, Structures, Appli- cations — ключевые слова в рассматриваемом примере). Ключе- выми считаются те слова, которые несут определенный смысл о природе документа. Такие общие, английские слова, как ’а”, ’for’, ’to’, ’the’, ’ап’, ’and’, ’with’, ’its’ и т. д.1, мало что говорят о существе документа. В соответствии с методом индексирова- ния KWIC ключевыми считаются те слова, которые не являются предлогами, союзами, местоимениями и, во многих случаях, на- речиями, прилагательными и глаголами, составляющими список общих слов. В приводимом ниже описании генератора указателей типа KWIC предполагается, что информация, необходимая для генера- ции указателей, хранится в четырех одномерных массивах. Первый массив ORD_WORDS содержит упорядоченный список общих слов, по одному слову на каждый элемент массива. В рассматри- ваемых далее примерах предложим, что ORD—WORDS содержит слова ’a’, ’an’, ’and’, ’for’, ’its’, ’the’, ’to’, ’with’. Второй массив TITLE содержит полные заглавия книг, являющиеся входными данными для генератора. В третьем массиве KEYWORD находится упорядоченный список ключевых слов, имеющихся в заглавиях, которые помещены в память системы. С этими ключевыми сло- вами ассоциирован и помещен в четвертый массив TITLE 4Ф S список индексов массива. Эти индексы указывают на заглавия, находящиеся в массиве TITLE. Если данное ключевое слово находится в позиции i массива KEYWORD, то TITLE 4Ф S li] хранит символьную строку, которая содержит индексы массива TITLE для всех заглавий, в которых имеется это ключевое слово. В символьной строке индексы массива отделяются друг от друга пробелами. Чтобы проиллюстрировать взаимоотношения между масси- вами TITLE, TITLE S и KEYWORD, предположим, что зна- чениями элементов TITLE 11 J, TITLE I2J, TITLE 15) яв- ляются следующие пять заглавий 2: ’AN INTRODUCTION ТО DATA STRUCTURES WITH APPLICATIONS//’ ’AN INTRODUCTION TO PROGRAMMING//’ ’PL/1 PROGRAMMING WITH APPLICATIONS//’ 'A SNOBOL4 PRIMER//’ ’A PRIMER FOR LISP PROGRAMMING//’ Тогда элементы KEYWORD [i] и TITLE^S [il содержат данные, представленные в табл, 2-5.1 для значений i, изменя- ющихся от единицы до общего числа ключевых слов. 1 Т. е. артикли, предлоги, союзы и местоимения. — Прим. пер. 2 ’ВВЕДЕНИЕ В СТРУКТУРЫ ДАННЫХ И ИХ ПРИМЕНЕНИЯ//» ’ВВЕДЕНИЕ В ПРОГРАММИРОВАНИЕ//’ ’ПРОГРАММИРОВАНИЕ НА ПЛ/1 С ПРИМЕНЕНИЯМИ//’ ’УЧЕБНИК ПО ЯЗЫКУ СНОБОЛ 4//’ 'УЧЕБНИК ПО ПРОГРАММИРОВАНИЮ НА ЯЗЫКЕ ЛИСП//’ 179
Таблица 2-5.1 Содержимое массивов KEYWORD. TITLE # S Следовательно, ключевое слово PROGRAMMING содер- жится в заглавиях TITLE [2], TITLE [3] и TITLE [5]. А теперь мы рассмотрим алгоритм KWIC OUT, который 1 KEYWORD TITLEttS 1 2 3 4 5 6 ’APPLICATIONS’ ’DATA’ ’INTRODUCTION’ ’LISP’ ’PL/i ’ ’PRIMER’ ’PROGRAMMING’ ’SNOBOL4’ ’STRUCTURES’ ’1 3’ ’1’ ’1 2’ ’5’ ’3’ ’4 Б’ ’2 3 5’ ’4’ ’Г может генерировать список ука- зателей типа KWIC для всех заглавий, имеющихся в данный момент в памяти системы. Алгоритм KWIC—OUT. При заданных массивах TITLE, KEYWORD и TITLE #S этот алгоритм генерирует указатели типа KWIC, упорядоченные лек- сически по ключевым словам. KEYSTRING — рабочая переменная, используемая для времен- ного хранения строки индексов массива TITLE, являющейся элементом массива TITLE S. IND содержит текущее значение индекса массива TITLE. LAST—KEY — это число хранящихся в системе ключевых слов, а строка Т используется при формиро- вании пермутационного указателя. 1. [Организация цикла, в котором ключевые слова исполь- зуются в качестве индексирующих слов. I Повторять шаги 2—5 при i = 1, ..., LAST_KEY. 2. [Установка содержимого KEYSTRING.] Установить KEYSTRING ч- TITLE #S [i Jo ’b’. 3. [Повторение, пока не будут исчерпаны все индексы в KEYSTRING.] Повторять шаги 4 и 5 до тех пор, пока LENGTH (KEYSTRING) > 1. 4. [Обращение к следующему индексу массива TITLE.] Установить IND ч- SUB (KEYSTRING, 1, INDEX (KEYSTRING, ’b’) —1), KEYSTRING ч- SUB (KEYSTRING, INDEX (KEYSTRING, ’b’) + 1). 5. [Установка T и вывод в формате KWIC. ] Установить Т ч- ч- TITLE [IND], CURSOR ч- 1. Если FIND (Т, KEYWORD [i], CURSOR, MATCH—STR, ”, true), то установить T ч- KEYWORD [i ] О SUB (T, CURSOR) G ’b’ О MATCH—STR и печатать T; в противном случае печатать ’СИСТЕМНАЯ ОШИБКА — КЛЮЧЕВОЕ СЛОВО В ЗАГЛАВИИ НЕ ОБНАРУЖЕНО’. 6. [Конец. ] Выход. Проследим частично работу алгоритма KWIC_OUT, исполь- зуя в качестве исходных данных приведенные ранее пять загла- вий. В шаге 1 организуется цикл по индексу i, изменяющемуся от 1 до 9. При первом выполнении шага 2 KEYSTRING имеет значение ’13*. В шаге 3 делается проверка использования всех индексов массива TITLE, соответствующих данному ключевому слову. В шаге 4 переменная IND устанавливается равной 1, 180
a KEYSTRING становится ’3’. В шаге 5 значением переменной Т временно становится строка ’AN INTRODUCTION ТО DATA STRUCTURES WITH APPLICATIONS//’, а затем она с помощью функции FIND, описанной в п. 2-3.2, преобразуется к виду ’APPLICATIONS// AN INTRODUCTION ТО DATA STRUCTURES WITH После проверки в шаге 3 условия продолжения цикл повторяется при значении строки KEYSTRING, равном ’3’. В результате второго выполнения цикла будет получено ‘APPLICATIONS// PL/1 PROGRAMMING WITH *. Если проследить работу алгоритма и дальше, то можно полу- чить список, в котором первые три и последние два указателя имеют следующий внд: ’DATA STRUCTURES WITH APPLICATIONS// AN INTRODUCTION TO ’ ’INTRODUCTION TO DATA STRUCTURES WITH APPLI- CATIONS// AN ’ ’INTRODUCTION TO PROGRAMMING// AN ’ ’SNOBOL4 PRIMER// A ’ ’STRUCTURES WITH APPLICATIONS// AN INTRODUCTION TO DATA ’ Алгоритм KWIC___OUT составляет надлежащий список ука- зателей типа KWIC, если только в трех массивах TITLE, KEYWORD и TITLE#S содержится соответствующая инфор- мация. Алгоритм KWIC____FORM, который мы теперь опишем, анализирует входные фразы и формирует содержимое трех мас- сивов, требуемых для алгоритма KWIC_OUT. При этом KEYIND используется в качестве индекса массива KEYWORD. Алгоритм KWIC___FORM. В качестве исходных данных за- дается последовательность фраз (например, заглавий книг) в виде символьных строк. Алгоритм формирует массивы TITLE, KEYWORD и TITLE4±S. PHRASE содержит текущую входную фразу. О RD—SEARCH — это алгоритм, осуществляющий поиск в массиве ORD__WORDS для выяснения, является ли рассматри- ваемое слово общим (в этом случае алгоритм возвращает нену- левой индекс массива) или не является таковым (при этом возвра- щается нулевое значение индекса). KEY—SEARCH — алгоритм 181
поиска в массиве KEYWORD для определения того факта, что ключевое слово встречалось ранее. Если это так, то алгоритм воз- вращает индекс ключевого слова в названном массиве. Если же ключевое слово ранее не встречалось, то алгоритм заносит его в надлежащий элемент массива и возвращает индекс, соответ- ствующий этому элементу. LAST TITLE — индекс очередного доступного элемента в массиве TITLE для занесения новой фразы, a WORD — рабочая переменная для временного хране- ния словоформ. 1. [Организация цикла.] Повторять шаги 2—7 до тех пор, пока есть входные данные. 2. [Чтение входной фразы. ] Читать PHRASE. 3. [Вычеркивание начальных пробелов и присоединение -ко- нечных маркеров //. ] Установить DUMMY ч-SPAN (PHRASE, ’b’, 1, true), PHRASE ч- PHRASE O’//’. 4. [Занесение фразы в массив TITLE.] Установить LAST—TITLE ч- LAST-TITLE 1, TITLE [LAST—TITLE ]ч- 4- PHRASE. 5. [Обработка ключевых слов.] Повторять шаги 6 и 7 до тех пор, пока SUB (PHRASE, 1, 2)=/=’ //’. 6. [Вычеркивание слова.] Установить WORD ч— SUB (PHRASE, 1, INDEX (PHRASE, ’b’) — 1), PHRASE4- 4- SUB (PHRASE, INDEX (PHRASE, ’b’) + I). 7. [Ключевое слово?] Если ORD__SEARCH (WORD) = 0, то установить KEYIND 4- KEY—SEARCH (WORD), TITLE#S [KEYIND] 4- TITLE#S [KEYIND) О ’b’ О LAST—TITLE. 8. [Конец..] Выход. Если входной фразой является заглавие ’A SNOBOL4 PRIMER’, то в шаге 2 рассматриваемого алгоритма оно присваи- вается в качестве значения переменной PHRASE. Если в начале входной фразы имеются пробелы, то в шаге 3 они вычеркиваются с помощью функции SPA N, как описано в п. 2-3.2, и затем к концу строки PHRASE присоединяются маркеры 7/’. В шаге 4 строка PHRASE заносится в первый свободный элемент массива TITLE. Шаги 6 н 7 повторяются до тех пор, пока не будут классифициро- ваны (на ключевые и общие) все слова исходной фразы. Если слово не является ключевым, как, например, артикль ’А’, то про- изводится возврат на оператор повторения цикла в шаге 5. Если же слово ключевое, как, например,- ’SNOBOL4’, то алгоритм О RD—SEARCH возвращает ненулевое значение, и ключевое слово заносится алгоритмом KEY_____SEARCH в надлежащую позицию массива KEYWORD (отметим, что описание обоих алго- ритмов О RD— SEARCH и KEY. -SEARCH предлагается в качестве упражнений в конце этого пункта). Наконец, в шаге 7 модифи- цируется массив TITLE S путем присоединения индекса мас- сива TITLE к концу списка других индексов, соответствующего данному ключевому слову. 182
Алгоритмы KWIC—OUT и KWIC FORM позволяют нам запоминать заглавия или любой другой тип дескриптивных фраз н получать список указателей типа KWIC. Генератор указателей типа KWIC является простым, но практически важным примером применения методов манипулирования символьными строками. Одно из следующих ниже упражнений содержит любопытное раз- витие базового генератора указателей, описанного в этом пункте. Упражнения к п. 2-5.3 1. Пусть задана словоформа, присвоенная переменной WORD. Со- ставьте алгоритм О RD—SEARCH, который осуществлял бы поиск в массиве ORD—WORDS с целью обнаружения в нем заданной словоформы. Если такое слово в массиве имеется, то должен быть возвращен его индекс в этом массиве. В противном случае должно быть возвращено нулевое значение. (Существует ряд процедур поиска, которые могут быть использованы в алгоритме О RD—SE- ARCH. Они обсуждаются в гл. 6. Для данного упражнения достаточен простой линейный поиск.) 2. В качестве входного параметра задано ключевое слово, присвоенное переменной WORD. Составьте алгоритм KEY—SEARCH, который произво- дил бы поиск в массиве KEYWORD с целью обнаружения в ием заданного клю- чевого слова. При наличии искомого ключевого слова алгоритм должен возвра- тить его индекс в массиве KEYWORD. В противном случае заданное ключевое слово должно быть занесено в надлежащую позицию массива, определяемую лексическим упорядочением ключевых слов. Отметим, что должно быть также предусмотрено место в определенной позиции массива TITLE # S для занесе- ния строки индексов массива TITLE, соответствующих ключевому слову. Для этого упражнения также достаточно применить простой линейный поиск и ли- нейную процедуру включения. Более эффективные процедуры будут описаны в гл. 6. 3. Алгоритм KWIC—OUT не учитывает ситуацию, когда то или иное клю- чевое слово появляется в заглавии более одного раза. Например, заглавие1 ’TECHNIQUES FOR SYSTEM DESIGN AND SYSTEM IMPLEMENTATION’ должно быть пермутировано дважды? ‘SYSTEM DESIGN AND SYSTEM IMPLEMENTATION//TECHNIQUES FOR ’ SYSTEM IMPLEMENTATION//TECHNIQUES FOR SYSTEM DESIGN AND ’ Модифицируйте алгоритм KWIC—OUT таким образом, чтобы ои учитывал крат- ные ключевые слова. 4. По мере того, как в систему генерирования''указателей типа KWIC вводятся все новые и новые документы, становится неудобным выводить на печать полный список документов. В этой ситуации предпочтительней поу- чать список указателей KWIC только "для тех документов, которые содержат в своем заглавии (или некоторой другой представляющей фразе) определен- ные избранные индексирующие слова Для этой цели мы могли бы использо- вать команду СПИСОК KWIC ДЛЯ (индексирующее выражение), где (индексирующее выражение) определяется так: (индексирующее выражение) : : = индекс | ((индексирующее выражение) (опер) индекс) ] (индекс (опер) (индексирующее выражение)) (опер ) : : — ИЛИ | И 1 ’МЕТОДЫ РАЗРАБОТКИ И РЕАЛИЗАЦИИ СИСТЕМ’. 183
Терминал «индекс» может быть любым индексирующим словом, которое мы выбираем для поиска. Следовательно, примерами правильных команд яв- ляются : СПИСОК KWIC ДЛЯ УЧЕБНИК СПИСОК KWIC ДЛЯ (ПРИМЕНЕНИЯ И ПРОГРАММИРОВАНИЕ) СПИСОК KWIC ДЛЯ ((ДАННЫЕ И СТРУКТУРЫ) ИЛИ ПЛ/1) Напишите программу на ПЛ/1, которая интерпретирует команды «СПИ- СОК KWIC» и выдает только указатели KWIC, задаваемые посредством ин- дексирующего выражения в команде. 2-5.4. Использование битовых строк при информационном поиске До сих пор в этой главе мы обсуждали главным обра- зом символьные строки, операции над ними и их представление в памяти. Особым типом строки в языках ПЛ/1 и АЛГОЛ W является строка битов /в языке АЛГОЛ W данные типа строки битов задаются посредством описателя BITS). Строка битов — это строка, содержащая только символы из алфавита |0, 1}. Примерами констант типа строки битов в ПЛ/1 являются ’00010’ В и ’Г В. Буква В ставится после строки нулей и единиц для обо- значения различия между символьной строкой (например, ’010’) и битовой строкой (например, ’010’ В). Обычно при запоминании битовых строк используется по одному двоичному разряду на каждый элемент строки. Поэтому в вычислительных машинах Системы IBM 360-370 битовая строка длиной 32 бита может по- меститься в одном машинном слове. С помощью двоичного коди- рования величин,- применяемого при использовании битовых строк, может быть получено весьма компактное представление информации в машинной памяти. В языке ПЛ/1 битовые строки могут быть фиксированной или изменяющейся длины. Методы обработки битовых строк изме- няющейся длины подобны тем, которые были описаны для сим- вольных строк изменяющейся длины. Над битовыми строками можно выполнять те же самые действия, что и над символьными строками (т. е. операцию конкатенации, функции SLJBSTR, INDEX, LENGTH, VERIFY и т. д.). Кроме того, над битовыми строками можно выполнять операцию «И» (используя логический оператор &) и операцию «ИЛИ» (используя логический опе- ратор | ). Применение этих двух операций мы проиллюстрируем при обсуждении системы информационного поиска, к которому теперь и перейдем. В различных областях применения вычислительных машин часто возникает задача выборки определенной информации из большой базы данных (т. е. большого информационного пула, отражающего многие аспекты определенной области применения). Например, в системе резервирования мест на самолеты, содержа- 184
щей громоздкую базу данных, приходится делать выборку инфор- мации о рейсах между двумя городами, например о количестве мест, имеющихся для каждого рейса. Это и есть пример задачи информационного поиска. В этом пункте мы рассмотрим задачу разработки системы информационного поиска, которая не так сложна, как задача разработки системы резервирования мест на самолеты. Тем не менее эта задача нетривиальна, представляет практический инте- рес и дает нам возможность предложить решение, связанное с ис- пользованием битовых строк. Впрочем, не во всех системах ин- формационного поиска приходится применять битовые строки. И даже в тех системах, где это возможно, такое решение не всегда окажется наилучшим. В гл. 7 мы познакомимся со многими дру- гими методами организации данных, которые можно применять вместо битовых строк. Задача, которую мы теперь рассмотрим, касается компьюте- ризации процедур обработки платежных ведомостей и учета в некоторой транспортной компании, действующей на террито- рии горных национальных парков пров. Альберта. На основе предварительных соображений и анализа задачи было решено создать файл служащих, который содержал бы следующую информацию о каждом служащем: 1. Номер служащего в системе социального страхования (НОМЕР) — поле из девяти десятичных цифр. 2. Имя служащего (ИМЯ)—поле дли ной двадцать пять символов. 3. Пол служащего (ПОЛ) — односимвольное поле, в которое помещается ’М’ для мужчины и ’F’ для женщины. 4. Вид работы служащего (ТИП), который кодируется одно- символьным полем, причем код ’А’ представляет агента, ’D’ — водителя, ’Н’ — помощника водителя и ’Р’ — прочих. 5. Зарплата служащего (ЗАРПЛАТА) — кодируется полем, содержащим одну десятичную цифру, причем код ’1’ означает 3.00 долл/ч, ’2’ — 3.80 долл/ч, ’3’ — 5.00 долл/ч и ’4* — 7.00 долл/ч. 6. Местожительство служащего (МЕСТО) — представляется односимвольным полем, причем ’В’ означает Банф, ’J’ — Джаспер и ’L* — Лейк-Луис. Имена в скобках, содержащиеся в приведенном описании, бу- дут далее использоваться для ссылки на соответствующие поля в записи о служащем. Мы будем задавать также значения, кото- рые могут принимать эти имена. Далее в этом пункте будет использоваться следующий список записей о служащих (для большей ясности между полями записей добавлены пробелы и, кроме того, поля обозначены своими име- нами): НОМЕР ИМЯ ПОЛ ТИП ЗАРПЛАТА МЕСТО 693121053 ПАТРИЦИЯ Л ФОКС F D 2 J 686725001 ЛЭРРИ Р БРАУН М D 2 В 185
591146235 ЛИНДА 661301964 РОЙ 529270792 ДЭВИД 637263675 СЮЗАН Л ГАРДНЕР • F Н- Б АНДЕРСОН М А Н ПАРКЕР М Р К ФРОСТ F Р 1 3 2 3 В J В J При подсчете выплат за каждую половину месяца компания может пожелать получать из этого файла некоторую особую информацию. Например, компании может потребоваться список всех служащих, зарабатывающих 5 долл/ч, список всех водителей, работающих в Лейк-Луисе, список всех служащих-женщин, ра- ботающих в Джаспере, и т. д. Для решения этой задачи информационного поиска будем использовать 13 битовых строк. Длина каждой из них равна общему числу служащих, записи о которых в настоящий момент содержатся в файле (здесь мы используем термин «файл» в общем виде; в гл. 7 мы дадим для него точное определение). Пусть при этом одна строка представляет служащих-мужчин, другая — служащих-женщин, третья — служащих, работающих в Джас- пере, и т. д. Тринадцать битовых строк* соответствуют 13 воз- можным значениям полей: ПОЛ (2 значения), ТИП (4 значения), ЗАРПЛАТА (4 значения) и МЕСТО (3 значения). Символ ’Г в i-й позиции битовой строки для служащих, работающих в Джа- спере, свидетельствует о том, что i-й служащий работает в Джа- спере. Символ ’0’ в i-й позиции битовой строки для водителей означает, что i-й служащий не является водителем. Для нашего файла, состоящего из 6 записей, битовые строки будут таковы: КЛЮЧЕВОЕ СЛОВО СТРОКА КЛЮЧЕВОЕ СЛОВО СТРОКА Г БИТОВ .БИТОВ МУЖЧИНА ’010110’В ЗАРПЛАТА—ТИПЗ ’000101’В ЖЕНЩИНА ’101001’В ЗАРПЛАТА—ТИП2 ’110010’В АГЕНТ ’000100’В ЗАРПЛАТА—ТПП1 ’001000’В ВОДИТЕЛЬ ’ПОООО’В БАНФ ’ОИОЮ’В помощник ’001000’В ДЖАСПЕР ’100101’В ПРОЧИЕ ’00001 ГВ ЛЕЙК-ЛУИС ’000000’в ЗАРПЛАТА—ТИП4 ’000000’В Чтобы упростить использование системы работниками бухгал- терии (многим из которых придется переходить от ручных опера- ций’ к машинным), целесообразно разработать непроцедурный язык команд, который был бы почти подобен обычному русскому языку 1. Пусть команда такого языка имеет формат СПИСОК СЛУЖАЩИХ [спецификации]. Поле спецификаций содержит логическую комбинацию клю- чевых слов, представляющих подмножество желаемых записей о служащих. Например, чтобы запросить список всех служащих, являющихся водителями и живущих в Банфе, мы используем команду СПИСОК СЛУЖАЩИХ ВОДИТЕЛЬ & БАНФ. В подлиннике — английскому языку. — Прим. пер. 186
В ответ на эту команду в информационной системе, в памяти которой имеется заданный выше набор записей, будут выпол- нены следующие действия. 1. После анализа команды выбираются битовые строки, соот- ветствующие ключевым словам ВОДИТЕЛЬ и БАНФ (строки ’НОООО’В н ’011010’В соответственно). 2. Выполняется логическое умножение (конъюнкция) этих строк, определяемое оператором & (’110000’В&*011010’В = = ’010000’В). 3. Осуществляется выборка соответствующей записи (записей) из файла и выдается с надлежащими заголовками: НОМЕР ИМЯ ПОЛ ТИП ЗАРПЛАТА МЕСТО 686725001 ЛЭРРИ Р. БРАУН М D 2 В Значит, наша общая стратегия при интерпретации команд состоит в том, чтобы выделить в команде ключевые слова, выбрать соответствующие им битовые строки и выполнить над этими стро- ками заданные логические операции для получения результи- рующей битовой строки. Затем последовательно просмотреть эту строку, и если в некоторой ее позиции есть символ единицы, то выдать из файла соответствующую этой позиции запись. Отме- тим, что при интерпретации команд следует проверять их на на- личие ошибок, и если они имеются, то необходимо выдать сооб- щение об ошибке и проигнорировать ошибочную команду. В поле спецификаций команд могут быть использованы сле- дующие логические операторы: ' (логический оператор дизъюнкции — ИЛИ); (логический оператор конъюнкции — И); —] (логический оператор отрицания — НЕ). Оператор —| имеет приоритет над оператором &•, который, в свою очередь, приоритетней оператора j . Порядок применения опера- торов можно изменить с помощью скобок. Поэтому команды СПИСОК СЛУЖАЩИХ МУЖЧИНА | ЖЕНЩИНА &“| БАНФ. СПИСОК СЛУЖАЩИХ МУЖЧИНА | (ЖЕНЩИНА & (“| БАНФ)). эквивалентны. Для обработки команд нужен алгоритм, который выделял бы очередной элемент команды, будь то оператор или ключевое слово. Такой анализ команд выполняет алгоритм NEXTSYM. Алгоритм NEXTSYM. При заданной строке S, состоящей из оператора и ключевых слов команды, этот алгоритм выделяет в S самый левый элемент, исключает его из S и присваивает его значение переменной NEXTSYM. X используется как рабочая переменная. 187
1. [Инициализация. ] Установить NEXTSYM -*— ”, CURSOR-*— -*— 1. 2. [Вычеркивание начальных пробелов. J Установить DUMMY ч- SPAN (S, ’b’, CURSOR, true). 3. [Проверка того, что следующий символ является опера- тором.] Установить X ч- SUB (S, CURSOR, 1). Если INDEX (’ | &П- X) 0, то установить NEXTSYM-*- X, S+- -*- SUB (S, 2) и закончить выполнение алгоритма. 4. [Выделение слова.] Если BREAK (S, ’b | & ]. ’, CURSOR, NEXTSYM, ”, true), то установить S-*-SUB (S, CURSOR), и закончить выполнение алгоритма; в противном случае печатать ’КОМАНДА СИНТАКСИЧЕСКИ ОШИБОЧНА’, установить S -*— ” и закончить выполнение алгоритма. В шаге 2 алгоритма NEXTSYM мы используем функцию по- иска по образцу SPAN, описанную в п. 2-3.2, для вычеркивания всех начальных пробелов строки. Остальную часть алгоритма понять нетрудно: она отражает обычное применение ранее рас- смотренных функций обработки символов, за исключением функ- ции BREAK. Эта функция описана в упражнениях к п. 2-3 и очень похожа на функцию поиска по образцу BREAK в СНОБОЛе. В данном примере функция BREAK используется для нахожде- ния конца ключевого слова путем выделения следующего опе- ратора, пробела между символами или конца команды. Если алгоритм NEXTSYM выполнить для строки S — ’& БАНФ.’, то будет получен символ а строка S примет новое значение ’БАНФ.’. Если алгоритм выполнить еще раз, то будет получен результат ’БАНФ’, а строка S станет равна ’.’. Интерпретация команды получается в результате выполнения алгоритма EVALUATE. При работе этого алгоритма предпола- гается, что операторы команды и их операнды, являющиеся клю- чевыми словами, предварительно преобразованы в постфиксную форму записи. В п. 3-7.2 мы подробно опишем метод перехода от инфиксных выражений, т. е. выражений, в которых оператор записывается между его операндами, к постфиксным выражениям, т. е. выражениям, в которых оператор записывается после своих операндов. Следующие примеры иллюстрируют постфиксную форму записи заданных инфиксных выражений: ЖЕНЩИНЫ | МУЖЧИНЫ &~| ДЖАСПЕР в постфиксной форме записывается ЖЕНЩИНЫ МУЖЧИНЫ ДЖАСПЕР “|& | ; (ПРОЧИЕ [ АГЕНТ) & БАНФ в постфиксной форме записы- вается ПРОЧИЕ АГЕНТ | БАНФ&. Постфиксные выражения имеют два преимущества перед инфиксными: их значение можно определить за один проход слева направо, и в них отсутствуют скобки. Определение значения постфиксного выражения заключается в том, что отыскивается самый левый оператор в строке, который выполняется над сосед- 188
ними слева операндами х. Результат такой операции может затем использоваться в качестве операнда следующего самого левого оператора. Поэтому в записи ЖЕНЩИНЫ МУЖЧИНЫ ДЖАСПЕР —]У I сначала определяется значение выражения ДЖАСПЕР-1. Это значение, скажем R1, используется как опе- ранд при вычислении выражения МУЖЧИНЫ RI У. Результат, например R2, применяется' в свою очередь, при вычислении вы- ражения ЖЕНЩИНЫ R2 | . Предполагая, что читатель усвоил сущность постфиксных вы- ражений, изложим теперь алгоритм EVALUATE. Алгоритм EVALUATE. По команде, находящейся во входной строке REQUEST и содержащей выражение, зависящее от аргу- ментов типа битовых строк, алгоритм формирует результирующую битовую строку, которая указывает записи, удовлетворяющие данной команде. BITS — это массив, используемый для хране- ния промежуточных результатов, a i — индекс этого массива. Строка SYM предназначена для запоминания элементов команды, a KEYS — символьная строка, содержащая конкатенацию всех ключевых операндов команды. Функция POSTFIX формирует польскую постфиксную запись инфиксного выражения, содер- жащегося в команде. Постфиксная запись хранится в строке COMMAND. Функция REFERENCE для данного ключевого слова возвращает битовую строку, соответствующую этому ключе- вому слову. 1. [Инициализация.] Установить i ч-О, COMMAND ч— ч-POSTFIX (REQUEST). 2. [Вычислительный цикл.] Повторять шаги 3—5 до тех пор, пока LENGTH (COMMAND) > I. 3. [Выделение следующего элемента.] Установить SYM ч- NEXTSYM (COMMAND). 4. [Проверка на ключевое слово и занесение соответствующей . битовой строки в BITS.] Если INDEX (KEYS, SYM) 0, то установить i ч-i + 1, BITS [i]^- REFERENCE (SYM) и вер- нуться к шагу 2. 5. [Проверка на оператор и выполнение соответствующей ему операции.] Если SYM = ’ | то установить BITS [i — 1 ] ч— BITS [i] | BITS [I — 1), i ч- I — 1 и вернуться к шагу 2. Если SYM = то установить BITS [I — 1 ] ч- BITS [i] & BITS [i — 1 ], i ч- i — 1 и вернуться к шагу 2. Если SYM = |’, то установить BITS(i) ч-“]BITS[i] и вернуться к шагу 2. Если SYM то печатать ’ОШИБКА’, установить EVALUATE ч— ’О’В, и закончить выполнение алгоритма. 6. [Возврат.] Если 1 = 1, то установить EVALUATE ч- ч— BITS [1 ] и закончить выполнение алгоритма; в противном * У унарного оператора имеется только один операнд. — Прим, ред. 189
EVALUATE: PROCEDURE (REQUEST); . /< THIS PROCEDURE IS USED TO EVALUATE THE INPUT COMMAND ANO RETURN -*/ /* IN A BIT STRING THE RESULTS OF THE EVALUATION. *! to evaluate the command» the input from is converted to a polish *•/ /*• POSTFIX FORM AND THEN THIS FORM IS EVALUATED WITH THE HELP OF «/ /“ THE REFERENCE PROCEDURE. “/ /° THE KEY VARIABLES ARE: Z® REQUEST: HOLOS THE INPUT COMMAND */ Z® BITS: AN ARRAY USED TO EVALUATE THE POLISH POSTFIX FORM.'*/ Z® S: A POINTER INTO BITS. ' *7 /* COMMAND: A STRING USED TO TEMPORARILY HOLO THE POSTFIX FORM »/ DECLARE REQUEST CHARACTER!®) VARYING. BITS(IO) BIT1L), SYMBOL CHARACTER!Il) VARYING, OPERATORS CHARACTER(5 ), KEYWORDS CHARACTER! 108) INI T I AL { ’ MAC E FEMALE JASPER' II ’BANFF LAKE_LOUISE PAYROLL DRIVER HELPER AGENT r И ’WAGE_TYPE1 UAGE_TYPE2 l>JAGE_TYPE3 WAGE_TYPE4') ; POLISH CHARACTER (120) VARYING INITIAL!”), S FIXED BINARY INFUALClb I FIXED BINARY? REFERENCE: PROCEDURE (K); /* this procedure is used to simulate a case statement to insert */ /p THE DESIRED BITSTPJNG INTO THE BITS ARRAY DURING EVALUATION OF THE*/ Zc POSTFIX FORM OF THE COMMAND. */ /г> K: THE INPUT NUMBER WHICH DETERMINES WHICH CASE. и-/ DECLARE К FIXED BINARY, LAB<84) label; GO TO LAB IK); LAB( 1 ) : BITS< S) = MALE; RETURN; LA8(6): BITS(S) - FEMALE: RETURN; . LAB!13): BITSfS) = JASPER; RETURN; LAB(20 ) : BITS!S) = BANFF; RETURN; LAB(26): BITSIS) = LAKE.LOUISE: RETURN; LAB!38): BITS(S) - PAYROLL; RETURN; LAB(A6): BITS(S) = DRIVER; RETURN; LAB(53): 61TS!S) = HELPER; RETURN; LAB(60): BITS(S) = AGENT; LA8(66): BITS(S) = WAGE_TYPE1; RETURN; LAB177): 8ITS(S) = WAGE_TY₽E2; RETURN; LA8(88): BITS(S) = WAGE_TYPE3? RETURN; LA0199): BITS(S) « UACE_TYPEA; RETURN: END REFERENCE: Рис, 2-5.3. Процедура, реализующая алгоритм EVALUATE случае печатать ’ОШИБКА’, установить EVALUATE ч-’О’В и закончить выполнение алгоритма. В этом алгоритме знаки | , & и —] представляют собой логи- ческие оператора ИЛИ, И и НЕ соответственно, выполняемые над аргументами типа битовых строк побитно. Это значит, что из ’0100’В [ ’НОГ В получается ’ПОГ В, из ’0100’ В & ’ПОГ В получается ’0100’ В, а нз —| ’ПОГ В — ’0010’ В. \9о
/° THIS PORTION OF EVALUATE EVALUATES THE POLISH POSTFIX FORM. •/ s = o; COMMAND = POSTFIX(REQUEST); OD WHILE (LENGTH(COMMAND) > 1): SYMBOL = NEXTSYMtCOMMANQl; IF 1 NOEXIKEYWORDS»SYMBOL) -•= 0 THEN DO: S = S + 1; . 7° STORE APPROPRIATE BIT STRING «/ CALL REFERENCE!INDEX!KEYWORDS,SYMBOL)); END; ELSE DO: 1 ~ INDEXJOPERATORS,SYMBOL); IF I = 1 THEN 00; bits(S-i) = Busts) I BiTsis-i); S = S - 1; end; ELSE IF I = 2 THEN 00; BITSIS-1 ) = BITSIS) 6. BITS(S-l); S = S - 1; END; ELSE IF I = 3 THEN BITSIS) = ->BITS( S) 5 ELSE DO; CALL error; RETURN!’O’B) ; END; ENO J END; CF DO WHILE </ IF S = 1 THEN RETLRN(BITS IS)); ELSE 00 CALL ERROR; RETURN!’O’B); END; ENO EVALUATE; Гис. 2-5.3. Продолжение На рис. 2-5.3 представлены процедуры языка ПЛ/1, реализу- ющие алгоритмы EVALUATE и REFERENCE. В этих программах ряд переменных описаны и заданы как глобальные. Переменная L имеет значение общего количества записей, содержащихся в файле, о служащих. S — это индекс массива BITS, в котором находится польская постфиксная форма, анализируемая про- цедурой EVALUATE. Переменные типа битовых строк MALE, FEMALE, JASPER и т. д., каждая длиной 13, имеют отлича- ющиеся друг от друга значения битовых строк, т. е. MALE — -’1000000000000’ В, FEMALE = ’0100000000000’ В и т. д. Эти переменные используются для кодирования операндов команд. В качестве примера, иллюстрирующего работу алгоритма EVALUATE, рассмотрим случай, когда строка REQUEST со- держит команду СПИСОК СЛУЖАЩИХ ВОДИТЕЛЬ & БАНФ. В шаге 1 функция POSTFIX формирует выражение’ВОДИТЕЛЬ БАНФ Шаги 3 и 4 выполняются дважды, в результате чего битовые строки, соответствующие ключевым словам ВОДИТЕЛЬ 191
и БАНФ, помещаются в первую и вторую позиции массива BITS. После третьего выполнения шага 3 символ ’ & ’ помещается в SYM. В шаге 5 вычисляется конъюнкция битовых строк ВОДИТЕЛЬ и БАНФ, а результат заносится в первую позицию массива BITS. Результатом выполнения алгоритма является зна- чение первого элемента массива BITS. Теперь мы представим основной алгоритм выборки информа- ции, которая определяется командой. При выполнении шага на- чальной установки в алгоритме RETRIEVE предполагается созда- ние и инициализация файла служащих и 13-битовых строк. В реальной системе этот файл создается и хранится во внешней памяти (например, на диске). Алгоритм RETRIEVE. На основании множества записей о служащих алгоритм выводит на печать те записи, которые удовлетворяют входному запросу, находящемуся в строке REQUEST. RECORD — это битовая строка, используемая для выбора записей, подлежащих печати. Ее значение получается в результате обращения к алгоритму EVALUATE, анализиру- ющему входную команду. 1. [Инициализация.! Установить RECORD’О’В. 2. [Ввод запроса REQUEST, отбрасывание хвостовых пробе- лов, проверка правильности команды.) Читать REQUEST. Повторять до тех пор, пока SUB (REQUEST, LENGTH (REQUEST), 1) = ’b’: установить REQUEST SUB (REQUEST, 1, LENGTH (REQUEST)—1). Если-[MATCH (REQUEST, ’СПИСОК СЛУЖАЩИХ’, 1, ”, ”, true), то печатать ’ОШИБКА’ и перейти к шагу 5. 3. [Печать всех записей, если в команде нет списка пара- метров; в противном случае начать вычисления.] Если LENGTH (REQUEST) sg 1, то установить RECORD ч- ’ll 11 ... 1’ В (для всех записей установить единицы); в противном случае установить RECORD х- EVALUATE (REQUEST). 4. [Печать заголовков и записей.! Печатать заголовки,, повто- рять до тех пор, пока INDEX (RECORD, ’1’ В) 0: установить 1 ч- INDEX (RECORD, ’1’ В), печатать i-ю запись и установить SUB (RECORD, i, 1) +- ’О’ В. 5. [Конец запросов?) Если конец запросов, то закончить вы- полнение алгоритма; в противном случае вернуться к шагу 2. В этом пункте мы рассмотрели решение конкретной задачи информационного поиска. В гл. 7 мы определим ряд структур записей и файловых структур, которые могут быть применены при разработке информационно-поисковой системы. Одна нз таких структур записей основана на битовых строках, которые исполь- зуются подобно тому, как было только что описано. Упражнения к п. 2-5.4 1. Для описанного в этом пункте файла, содержащего шесть запи- сей, определите результаты выполнения следующих команд: а) СПИСОК СЛУЖАЩИХ БАНФ | ДЖАСПЕР. 192
б) СПИСОК СЛУЖАЩИХ ПРОЧИЕ & “I ЖЕНЩИНА | МУЖЧИНА. в) СПИСОК СЛУЖАЩИХ ВОДИТЕЛЬ & ЛЕЙК-ЛУИС & ЗАР- ПЛАТА-ТИП!. 2. Определите польскую постфиксную форму записи для выражений, содер- жащихся в команде упр. 1. 3. Что будет напечатано в результате выполнения следующих операторов языка ПЛ/1. DECLARE (X, Y, Z) BIT (5) VARYING; Х=’0010’В, Y = ’1’В; Z = ’10010’B; PUT LISTfX I Y = ’,X I Y, ’Z&X = ’.Z&X, ’1 Y = 1 Y); 4. В задаче информационного поиска, рассмотренной в этом пункте, мы предполагали четыре фиксированных значения зар- платы. В действительности следовало бы допустить целый спектр значений зарплаты в виде X.YZ (например, 3.62 долл/ч). Учет данного обстоятельства при описании системы приводит к необ- ходимости запоминания конкретной почасовой оплаты каждого служащего в записи об этом служащем наряду с его именем и номером в системе социального страхования. Но и в этом случае можно использовать битовую строку, чтобы ответить на такие запросы, как СПИСОК СЛУЖАЩИХ ЗАРПЛАТА > $ 4.75.. Для этого надо закодировать сетку зарплаты, например, так» чтобы ’1’ означало зарплату меньше 3.50 долл/ч, ’2’ — зарплату от 3.50 до 4.49 долл/ч включительно, ’3’ — от 4.50 до 5.49 долл/ч,. ’4’ — 5.50 долл/ч и выше. Для ответа на вопрос о служащих» у которых зарплата выше 4.75 долл/ч, необходимо отыскать за- писи обо всех лицах, имеющих признак зарплаты ’3’, и добавить их к записям о лицах с признаком зарплаты ’4’*. Составьте алгоритм для обработки таких запросов, который использовал бы битовые строки, выражающие сетку зарплаты (а не точную почасовую оплату). Список литературы I. Марков А. А. Теория алгорифмов. — Труды математического института им. В. А. Стеклова, вып. 42. М. •— Л.: изд. АН СССР, 1954, 375 с. 2. Gaiter В. A., Perils A. J. A view of programming languages. Reading: Addison-Wesley, 1970. 3. Алгоритмический язык АЛГОЛ 60/Под ред. П. Наура. Пер. с аигл.» М.: Мир, 1965. 79 с. 4. IBM System/360 Operating System. PL/I (F) Language Reference Manual, File No. S 360—29, Order No. GC28—6594. 5. Criswold R. E., Griswold M. T. A SNOBOL primer. Englewood Cliffs*. Prentice—Hall, 1973 U 6. Harrison M. C. Data Structures and Programming. Glenview: Scott» Foresman and Co., 1973. 7. Грис Д. Конструирование компиляторов для цифровых вычислительных машин: Пер. с англ. М.: Мир, 1975. 544 с. * В действительности в это число войдут и служащие, получающие зарплату от 4.5 до 4.75 долл/ч. — Прим. ред. 1 См. также: Грисуолд Р., Поудж Дж., Полонски И. Язык программирова- ния СНОБОЛ-4: Пер. с англ. Мл Мир, 1980. 268 с. — Прим. ред. 7 Трамбле Ж., Соренсон П. 193
Глава 3. ЛИНЕЙНЫЕ СТРУКТУРЫ ДАННЫХ И ИХ ПОСЛЕДОВАТЕЛЬНОЕ ПРЕДСТАВЛЕНИЕ В ПАМЯТИ В предыдущих главах были рассмотрены примитивные структуры данных, такие как целые числа, действительные числа и строки, а в настоящей главе мы будем иметь дело с непримитивными линейными структурами данных. Будет приведен ряд возможных представлений таких линейных структур в па- мяти. Все эти представления основаны на последовательном распределении. В первой части главы приводится терминология, связанная с непримитив- ными структурами данных. Эти структуры разделяются на пакеты, массивы и списки Описан ряд соответствующих операций над конкретными линейными структурами. В п. 3-5 дается представление массивов в памяти в виде размеще- ния по строкам и по столбцам. В и. 3-6 обсуждается важная линейная структура — стек. Излагаются про- граммные аспекты стека на ПЛ/1, использующие управляемую память и соот- ветствующие операторы ALLOCATE и FREE. В и. 3-7 описываются некоторые классические области применения стеков, а именно, для анализа рекурсий, компиляции арифметических выражений и в стековых машинах. Рекурсия используется во многих языках программиро- вания, таких как АЛГОЛ 60 и ПЛ/1. Важным ее применением при создании компиляторов является перевод инфиксных выражений в польскую запись и их последующее преобразование в некоторый выходной язык. Поскольку ряд вы- числительных машин, например PDP-11 и Burroughs 5000, имеют стековую па- мять, в данном параграфе описаны некоторые из ее свойств. В п. 3-9 описана другая важная структура данных — очередь. Упоминается также о некоторых видоизменениях обычной очереди. В п. 3-9 приводится про- стой пример применения очередей. Он связан с моделированием вычислительной системы реального времени и сопровождается дискуссией о приоритетных оче- редях. 3-1, понятия о НЕПРИМИТИВНЫХ СТРУКТУРАХ ДАННЫХ В п. 0-1 была кратко обсуждена важность структуры при решении задач. Были также отмечены часто смешиваемые различия между структурой данных и структурой памяти. В гл. 1 рассматривались описание примитивных структур данных, таких как целые числа, действительные числа, символы и ука- затели, и их соответствующие представления в памяти. В гл. 2 мы видели, как путем объединения символы можно представить в виде строки —- структуры данных, основной для многих про- граммных применений. Теперь мы переходим к обсуждению более сложных структур данных. Непримитивные структуры данных могут быть разделены на пакеты, массивы н списки. Пакет является неупорядоченным 194
множеством объектов, имеющим фиксированный или перемен- ный размер. Здесь по определению размер структуры есть число элементов данных в ней. Пакеты часто используются при решении задач обработки данных. Подробно пакеты будут обсуждаться в гл. 7. Массив есть упорядоченное множество, состоящее из фиксированного числа объектов. Над массивами не существует операций исключения или включения. В лучшем случае элементы можно заменитьнекоторой величиной, представляющей признак игнорирования. Например, исключению элемента из массива может соответствовать установка его в нуль. Список же является упорядоченным множеством, состоящим из переменного числа элементов, к которым применимы операции включения и исклю- чения. Список, отражающий отношение соседства между элементами, называется линейным. Любой другой список называется нелиней- ным» В данной главе рассматриваются только линейные списки. 3-2. ОПЕРАЦИИ НАД НЕПРИМИТИВНЫМИ| СТРУКТУРАМИ ДАННЫХ Операции, выполняемые над списками, включают опе- рации, выполняемые и над массивами. Однако имеется одно суще- ственное различие, заключающееся в том, что размер списка со временем может изменяться (в отличие от массива). Действительно, наряду с изменением существующих элементов элементы списка со временем могут включаться или исключаться из него. Вклю- чение элементов в список или исключение из него определяется их позицией. Например, мы можем пожелать исключить i-й эле- мент списка и включить новый элемент до или после i-ro элемента. Часто требуется включить или исключить элемент, позиция кото- рого в списке определяется значениями других элементов списка (например, при сортировке). Может также потребоваться вклю- чить в список или исключить из него заданный элемент. Такой элемент может предшествовать элементу, имеющему определенное значение или удовлетворяющему заданному отношению, или следовать за ним. Помимо включения и исключения существуют и другие важ- ные операции, которые обычно выполняются над списками. Каждый элемент списка содержит одно или несколько полей. Поле может рассматриваться как минимальная единица данных, на которую можно ссылаться в языке программирования." В пере- чень операций над списками входят следующие. 1. Объединение двух или более списков для формирования нового списка. 2. Расщепление списка на ряд других списков. 3. Копирование списка. 4. Определение числа элементов в списке. 7* 195
5. Сортировка (упорядочение) элементов списка в восходящем или нисходящем порядке в зависимости от конкретных значений одного или нескольких полей внутри элемента. 6. Поиск элемента списка, содержащего поле с определенным значением. Приведенные операции будут обсуждаться для различных структур на протяжении всей книги. 3-3. ПОСЛЕДОВАТЕЛЬНЫЕ СТРУКТУРЫ ХРАНЕНИЯ При обсуждении последовательных структур хранения мы будем главным образом рассматривать основную память стандартной цифровой вычислительной машины. Основная память организована в виде упорядоченной последовательности слов. Как было указано в гл. 1, каждое слово содержит от 8 до 64 бит, а к его содержимому можно обращаться с помощью адреса. Исходя из эффективности вычислений, данные следует располагать таким способом, чтобы к конкретному элементу данных можно было обращаться путем вычисления его адреса, а не путем поиска самого элемента. В гл. 1 мы рассмотрели два метода определения адреса эле- мента. Первый метод состоит в использовании описания отыски- ваемых данных по адресу, известному как вычисляемый адрес. Этот метод очень часто используется во многих языках програм- мирования для вычисления адреса элемента массива и выделения следующей команды в объектной программе, подлежащей выпол- нению. Второй метод состоит в сохранении адреса элемента в па- мяти вычислительной машины. Такой тип адреса называют ссыл- кой или указателем. В ФОРТРАНе адреса фактических аргумен- тов подпрограммы хранятся в памяти вычислительной машины. Адрес возврата из подпрограммы в вызывающую программу также хранится, а не вычисляется. Некоторые структуры тре- буют комбинации вычисляемых адресов и ссылок. В настоящей главе будут обсуждены структуры хранения, основанные на принципе вычисляемого адреса. В следующей главе описаны структуры хранения, базирующиеся на методе адресации с использованием ссылки. При обсуждении файлов в гл. 7 мы будем рассматривать в основном структуры хранения на дисках и магнитных лентах. Существует много структур данных, допускающих обращение к любому элементу на основании его позиции в структуре. Гово- рят, что связанному с таком структурой селектору соответствует функция адресации. Функция адресации для структуры данных, содержащей п элементов, является функцией, которая i-му эле- менту структуры данных ставит в соответствие целое число, лежащее между 1 и п. Для вектора функция адресации f, ставящая в соответствие i-му элементу целое число i, т. е. f (i) = i. 196
является линейной функцией адресации. Структуры данных, име- ющие простую с точки зрения вычислений линейную функцию адресации, представляют особый интерес. Функции адресации обсуждаются ниже в последующих параграфах данной главы. Очень важный класс структур данных, имеющих линейную функ- цию адресации, рассматривается в гл. 5. Другой подобный класс функций, известных как функции хеширования, подробно об- суждается в гл. 6 и в меньшей степени в п. 4-3.2. Перейдем теперь к следующему параграфу, в котором рассма- триваются описание, представление и действия над самой три- виальной непримитнвной структурой данных — комплексным числом. 3-4. ПРОСТЫЕ СТРУКТУРЫ ДАННЫХ, ОПРЕДЕЛЯЕМЫЕ ПРОГРАММИСТОМ Подробное обсуждение некоторых примитивных структур данных (целых чисел, действительных чисел и символов) было дано в гл. 1. Был также описан ряд возможных представле- ний этих примитивных структур (т. е. возможных структур хра- нения) в основной памяти типичной ЭВМ. Вернемся теперь к пред- ставлению в памяти более сложных структур данных. Фактически можно считать, что сложная (непримитивная) структура данных состоит из структурированного множества примитивных структур данных. Например, вектор может быть представлен упорядочен- ным множеством целых чисел. Напомним, что целые числа, действительные числа и символы можно назвать примитивными элементами, поскольку система команд вычислительной машины содержит команды, опериру- ющие непосредственно с ними. Над числами можно выполнять обычные арифметические операции. Кроме того, слово, содержа- щее число или символы, может быть изменено с помощью ряда команд машинного языка. Рассмотрим непримитивиую, но очень простую структуру данных — комплексное число. Комплексное число не считается примитивом, поскольку вряд ли существуют вычислительные машины, имеющие в машинном языке команды сложения, вычи- тания, умножения и деления комплексных чисел. Тем не менее многие языки высокого уровня, такие как ПЛ/1 н ФОРТРАН, позволяют оперировать с комплексными числами. Прежде чем об- суждать программные аспекты арифметики комплексных чисел в та- ких языках, приведем определения четырех комплексных операций. Пусть u = x + yiHV = m-|-ni — два комплексных числа, где i = 1, а х, у, m и n — действительные числа. Тогда ариф- метические операции, которые можно выполнять над этими чис- лами, определяются следующим .образом: и ф v = (х + т) + (у + n) i, u Q v = (х — m) -f- (у — n) i, 197
u 0 V = (x*y — m*n) + (x*n + y*m) i, n 0 v = (x*m 4- y*n)/(m*n 4- n*n) + + (y*m — x*n)/(m*m + n*n) i, где операторы ф, 0, 0 и 0 означают комплексное сложение, вычитание, умножение и деление соответственно. Отсюда ясно, что над комплексными числами могут выполняться арифметиче- ские операции с помощью обычных арифметических операторов. Заметим, однако, что, например, умножение двух комплексных чисел содержит последовательность из многих (семи) команд машинного языка, в то время как в случае действительных чисел оно состоит из единственной команды. То же самое относится к остальным арифметическим операторам. Комплексное число состоит из упорядоченной пары действительных чисел, причем с каждым числом из этой упорядоченной пары обращаются по- разному. В ПЛ/1 комплексная переменная Z формируется с помощью следующего предложения описания: DEGLARE Z COMPLEX; а присваивание Z = 1.5 + 3.21 может быть выполнено оператором Z = 1.5 4-3.2i; К действительной и мнимой частям любого комплексного числа можно обращаться по отдельности с помощью встроенных функций REAL и IMAG соответственно. Например, действительную часть Z можио обозначать REAL (Z), а мнимую IMAG (Z). Таким образом, REAL и IMAG действуют в качестве селекторов для двух частей комплексного чнсла. Структура хранения комплексного числа с обычной точностью представляет собой два слова, последовательно расположенных в памяти, первое из которых содержит значение действительной части числа, а второе-—мнимой. Оператор А = В « С приводит к различному коду машинного языка в зависимости от типа переменных А, В и С. Если А, В и С — действительные чнсла, то получится одна совокупность команд, если же перемен- ные являются комплексными числами, то в результате будет сформирована совершенно другая совокупность. В некоторых современных языках программирования суще- ствует тенденция предоставления программисту ряда примитив- ных операторов и структур данных. Затем программист исполь- зует их для определения дополнительных более мощных операторов и структур данных. К языкам, обладающим такими средствами, относятся АЛГОЛ 68 и MAD-1. 198
'ПЛ/1 не позволяет программисту определять дополнительные операции в терминах примитивных операторов, однако он допу- скает определение структур данных с помощью примитивных структур данных. В ПЛ/1 содержатся некоторые базовые струк- туры данных, такие как строки, целые и действительные чнсла, массивы и неоднородные совокупности данных, называемые •структурами. Некоторые аспекты структур данных, определенных программистом, будут рассматриваться на протяжении всей книги, а также в п. 3-6.2 данной главы. Ниже, в настоящем пара- графе мы собираемся моделировать арифметику комплексных чисел, используя структуру языка ПЛ/1. Эти структуры можно распространить на массивы структур. Предложение описания .DECLARE 01 02 REAL FLOAT BINARY, 02 IMAG FLOAT BINARY; «создает структуру, которая содержит две действительные пере- менные REAL и IMAG. Структура Z может быть истолкована как комплексное число, где переменные REAL и IMAG представ- ляют собой действительную и мнимую части этого числа соответ- ственно. Например, Z можно присвоить значение 1,5 4- 3.2i с помощью следующей последовательности операторов присваи- вания: REAL = 1.5; IMAG = 3.2; '.Предположим, что мы описываем еще два комплексных числа X и Y, используя предложение DECLARE 01 X, 02 REAL FLOAT BINARY, 02 IMAG FLOAT BINARY, 01 Y, 02 REAL FLOAT BINARY, 02 IMAG FLOAT BINARY; Допустим теперь, что требуется обратиться к переменной REAL в структуре X. Каким образом это можно осуществить? Простое обращение к REAL приводит к неоднозначности, так как REAL является элементом трех структур X, Y и Z. Для достижения одно- значности используется имя X.REAL, где X указывает на обра- щение к REAL в структуре X. Точка предназначена для отделе- ния такого указателя от самой переменной. Данное свойство ука- зания имени устраняет необходимость присваивания разных имен элементам одного и того же класса несмотря на то, что каждый элемент этого класса может находиться в нескольких подобных ‘Структурах. В качестве упражнения напишем процедуру, моделирующую сложение двух комплексных чисел. Как и ранее, предположим, что 199
эта процедура имеет три структурных переменных: А, В и С~ Ниже приведена процедура, моделирующая это сложение: CADD: PROCEDURE (А, В, С); DECLARE 01 А, 02 R FLOAT BINARY, 02 I FLOAT BINARY, 01 В, 02 R FLOAT BINARY, 02 I FLOAT BINARY, 01 C, 02 R FLOAT BINARY; 02 I FLOAT BINARY; C.R = A.R + B.R; C.I = A.I-J- B.I; END CADD; Оператор основной программы CALL CADD (X, Y, Z); вызывает процедуру, а полученный при этом результат помещается в области REAL и IMAG структуры Z. Аналогичные процедуры можно написать и для других операций над комплексными чис- лами. В данном примере каждая из структур имеет две переменные таких же типов, что и соответствующие переменные двух других структур. Этот факт не всегда имеет место. Например, упрощен- ную структуру для хранения информации о каком-либо служащем создает следующее предложение: DECLARE 01 EMPLOYEE, 02 NAME CHARACTER (20), 02 ADDRESS CHARACTER (50), 02 RATE—OF—PAY FIXED BINARY, 02 DEPENDENTS FIXED DECIMAL; Эта структура содержит четыре элементарные переменные: NAME, ADDRESS, RATE—OF—PAY и DEPENDENTS. Заметим, что первые две переменные являются строками, а последние две - — числами разных типов. В некотором смысле приведенная струк- тура подобна массиву, за исключением одного очень важного об- стоятельства — в отличие от массива в такой структуре не тре- буется идентичности типов ее составных частей. В ПЛ/1 допустимы также массивы структур. Например, описание DECLARE 01 EMPLOYEE (50), 02 NAME CHARACTER (20), 02 ADDRESS CHARACTER (50), 02 RATE—OF—PAY FIXED BINARY, 02 DEPENDENTS FIXED DECIMAL; формирует массив из пятидесяти элементов, каждый из которых содержит четыре переменные. К переменной NAME в i-м элементе можно обратиться как NAME (I) или, более полно, как 200
EMPLOYEE (I).NAME, либо во избежание возможной неодно- значности обращения как EMPLOYEE.NAME (I). Данное здесь описание структур будет использоваться и обра- щаться в ряде приложений на протяжении всей книги. Ниже мы продолжим обсуждение представления массивов в памяти вычисли- тельной машины. 3-5. СТРУКТУРЫ ХРАНЕНИЯ МАССИВОВ Самой простой структурой данных, в которой вы- числяемый адрес используется для определения позиций ее эле- ментов, является одномерный массив, называемый вектором. Обычно для вектора выделяется ряд смежных последовательно расположенных ячеек памяти. Предположим, что для каждого элемента требуется одно слово памяти; тогда вектор из п элемен- тов будет занимать в памяти п последовательно расположенных слов. Размер вектора фиксирован и, следовательно, требует фикси- рованного числа ячеек памяти. В общем виде вектор А с нижней границей индексов, равной единице, может быть представлен графически, как это показано на рис. 3-5.1, где Lo является адре- сом первого слова, выделенного для первого элемента А, а с пред- ставляет число слов, выделенных для каждого элемента. Адрес Aj вычисляется следующим образом 1 : [ос (Ai) = Lo + с* (1 — 1). Рассмотрим более общий случай представления вектора А с нижней границей индексов, задаваемой некоторой переменной Ь. Теперь адрес А{ определяется выражением loc (А,) = Lo Т- с* (1 — Ь). В ФОРТРАНе память распределяется во время компиляции, когда память вместе с начальным адресом резервируется для вектора, размер которого определяется в предложении DIMEN- SION. Размер вектора не может быть задан во время выполнения 1 Здесь 1ос происходит от англ, location — определение местоположения.— Лрим. пер. Рис. 3-5.1. Графическое представление вектора в памяти Выделено с слов для каждого элемента 201
Программы на ФОРТРАНе, как в АЛГОЛе и ПЛ/1. Говорят, что в языке программирования, в котором имеется возможность счи- тывания величины п с перфокарты и описания вектора из и эле- ментов во время выполнения программы, память распределяется. динамически. Многомерный массив может быть представлен эквивалентным одномерным массивом. Например, в ФОРТРАНе двумерный мас- сив, состоящий из двух строк и четырех столбцов, хранится в па- мяти последовательно по столбцам в виде А II, И А [2, 1] А[1, 2] А [2, 2] А П, 3] А [2, 3] Т А [1, 4] А [2, 4] Ц Адрес элемента А П, j] может быть получен вычислением вы- ражения Lo + (j ~ 1) * 2 + i ~ 1- Для элемента А 12, 31 адрес равен Lo ф- 5. В общем виде для двумерного массива (хранящегося в памяти по столбцам), состоя- щего из п строк и m столбцов, адрес элемента A [i, j ] определяется линейным выражением Lo + (j - 1) *n + (i - 1). Во многих языках программирования двумерные массивы хранятся построчно (размещение по строкам), а не по столбцам (размещение по столбцам). При этом массив с нижней границей индексов, равной единице, содержащий и строк и m столбцов* будет храниться в виде А [1, И А [1, 2] ... А [1, щ] А [2, 1] А [2, 2] ... А [2, щ] ... A [n, 11 А [п, 2] ... А [п, щ] Адрес матричного элемента A (i, j] определяется выражением Lo + (i - 1) * m + (j - 1). Теперь представление двумерного массива можно обобщить на случай произвольных нижних и верхних границ индексов. Пред- положим, что bx i Ui и b2 j sg u2. Тогда адрес элемента AlJ определяется выражением 1ос (Ац) = Lo Ц(1 - - b±) *’(u2 — ba + 1) + (j — b2), где каждая строка массива А содержит п2 — Ь2 4- 1 элементов. Например, адрес элемента А03 при bt = —2, Ь2 = 2 и и2 — 3 1ос (А03) = Lo 4- (0 (—2)) * (3 — 2 + 1) 4- (3 — 2) = — Lo ф- 5. Аналогичная формула может быть получена при размещении дву- мерного массива по столбцам. 202
Рассмотрим хранение трехмерного массива В, элементы кото- рого обозначаются В [i, j, к], а границы индексов определяются неравенствами 1 i 2, 1 j :•<: 3 и 1 к 4. Размещение данной матрицы в памяти по столбцам будет иметь вид В [1, 1, 1] В [1, 1, 2] В [1, 1, 3] В [1, 1, 4] В [1, 2, 1] В [1, 2, 2] В (1,2, 3]В[1,2, 4] В [1, 3, 1} В [1, 3, 2] В 11, 3, 3] В [1, 3, 4] В (2, 1, 11В [2, 1, 2] В [2, 1, 3] В [2, 1,4] В [2,2,1] В [2,2, 2] В [2,2,3] В [2,2,4] В [2, 3, 1]В[2, 3, 2] В [2, 3, 3] В [2, 3, 4] Данный массив изображен на рис. 3-5.2 в виде куба, в верхней и нижней гранях которого содержится по 12 точек. Без учета базового адреса Lo функция адресации для элемента массива В [i, j, к] определяется выражением Hi, j, к) = (i — 1) * 12 + (j — 1) * 4 + к — I, линейным относительно i, j и к. Если нижние границы индексов нулевые, то при тех же самых верхних границах функция адре- сации f (i, j, k) - 20 * i + 5 * j + k. Обобщим теперь сказанное на n-мерный массив, элемент ко- торого обозначен через A [sj, s2, ..., snI, а диапазоны изменения индексов определяются неравенствами 1 sx ux, 1 s2 < u2, 1 < sn < un. Представление такого массива в памяти по строкам имеет вид А [1, 1, ..., 1, И А [1, 1, ..., 1, 2] ... А [1, 1, ..., 1, un] А [1, 1, ..., 2, 1] А [1, 1, ..., 2, 2] ... А [1, 1, ..., 2, unI A[ux, U2,---,Un-i, 1] A[ubu2,-• un_x,2]--* A(ux, u2,-• •, un-i, unl 203
Функция адресации для элемента A Isi, s2, sn] f («1. s2,• • - ,Sn) = u2u3• ♦ ’ un (Sj — 1) + u3u4- ” un (S2 — 1) + • • • + 4 Un (sn-l — 1) (sn — 1). Эта функция может быть переписана в более удобном виде: f (%, s2, • • • ,sn) = £ pi (Si — 1), • l<j<n где р5 = Пиг является константной, а символы S и П обозна- чают математические суммирование и произведение соответственно. И на этот раз функция адресации линейна относительно индексов. В общем случае функция адресации при размещении по строкам имеет вид s2,---,sn)= 2 Pi (Si —bi), где pj= П (Uj — bj-f-l) при bjCSiCUj, i<J<n Очевидно, что для получения функции адресации п-мерного массива при размещении его по столбцам можно использовать тот же самый метод. Некоторые компиляторы распределяют па- мять в порядке последовательного уменьшения адресов, начиная с определенного адреса в памяти. Здесь же мы предполагали, что память распределяется в сторону увеличения адресов. Оба метода эквивалентны за исключением того, что во втором методе значение функции адресации вычитается из базового адреса Lo. Для представления структурных связей в данных можно ис- пользовать массивы, однако сейчас появляется все больше слу- чаев, когда массивы непригодны. Обсудим кратко такие варианты. Рассмотрим известную задачу символьной обработки при выпол- нении различных операций над многочленами (сложение, вычи- тание, умножение, деление, дифференцирование и т. д.). В част- ности, обратимся к операциям над многочленами с двумя пере- менными. Пусть, например, требуется составить программу вы- читания многочлена х2 + Зху у2 + у — х из 2х2 4- 5ху + у2, в результате чего должно получиться х2 + 2ху — у + х. Вызывает интерес нахождение такого представления много- членов, чтобы указанные выше операции могли выполняться до- статочно эффективным способом. Очевидно, что при манипулиро- вании многочленами необходимо иметь возможность выбирать каждый член в отдельности. При этом мы должны в каждом члене различать переменные, коэффициенты и показатели степени. Многочлен можно представить в виде строки знаков, и тогда решение задачи будет заключаться в поиске соответствующих членов, а затем в выделении различных составляющих этих чле- нов. Однако этот способ является сложным, особенно если такая программа составляется на ФОРТРАНе или ПЛ/1. 204
Для представления многочлена, зависящего от двух перемен- ных, можио использовать двумерный массив. В языке программи- рования, допускающем нулевые значения индексов, коэффициент члена х'у1' может храниться в качестве элемента массива, стоя- щего на пересечении i-й строки и j-ro столбца. Если мы ограничим массив пятью строками и пятью столбцами, то показатели сте- пени х н у не должны превышать значения 4. Тогда массив для представления многочлена 2х2 + 5ху + у2 будет иметь вид 00100 05000 20000 00000 00000, а массив для представления х2 4- Зху + у2 + у — х — 01100 —13000 10000 00000 00000. Теперь, имея один алгоритм для преобразования входных данных в массив, представляющий многочлен, а другой для преобразова- ния массива в подходящую выходную форму, сложение и вычи- тание многочленов можно свести к сложению и вычитанию соот- ветствующих элементов этих двух массивов. В таком представлении очевиден ряд недостатков. Во-первых, массив чаще всего содержит мало ненулевых элементов. Во-вто- рых, показатели степеней в каждом члене многочлена ограничены по величине. В следующей главе будет дано более эффективное представление таких многочленов. Это может быть достигнуто использованием распределения памяти, отличного от последова- тельного. В ряде случаев использования матриц имеет смысл хранить только какую-либо часть каждой матрицы (из-за их особых свойств). Рассмотрим один из таких примеров. Предположим, что требуется найти решение системы уравнений специального вида: AuXi = bi AajXi + А22х2 = b2 AgiXl Ag2X2 4" AggXg = Ьд AniXj. 4- An2X2 + АцзХз 4---j- Ann Xn — bn. Эту задачу можно решить обычными методами с использованием хранимого в памяти двумерного массива из п2 элементов. Однако 205
при таком способе хранения почти половина коэффициентов ма- трицы не используется. Эту систему'можно представить в памяти более компактно, если организовать рассматриваемый «треуголь- ный» массив в виде эквивалентного одномерного массива. В дан- ной системе имеется всего [п (п + 1) ]/2 коэффициентов. Следова- тельно, соответствующий вектор должен содержать по крайней мере такое же число элементов. В векторе элементы треугольного массива могут располагаться в следующем порядке: А11-Л-21А22А31А3ЙАзз, ..., Апп, т. е. строка за строкой. Легко проверить, что функция адресации элемента Ап (в предположении, что Аи содержится по адре- су 1) определяется выражением Например, для элемента А31 функция адресации 1(3 — 1) (3)1/2 + + 1=4. Хотя эта функция адресации является квадратичной, она достаточно проста. Таким же образом можно представить и симметричные массивы, в которых всегда Ау = Ап. Используя рассмотренное представление треугольных матриц, можно сформулировать алгоритм решения системы уравнений указанного вида. Алгоритм TRIANGULAR. Задана система из п уравнений с треугольной матрицей коэффициентов А, хранящейся в виде вектора R, и вектором В правых частей. Требуется получить вектор решения X. SUM является рабочей переменной. 1. [Вычисление и печать X [1 ].] Установить X [1]<— ч-В [1]/R [1] и печатать X [1]. 2. [Повторение для последовательных индексов строк тре- угольной матрицы.] Повторять шаги 3 и 4 при i = 2, 3, .... п и закончить выполнение алгоритма. 3. [Обнуление SUM и повторение для последовательных ин- дексов столбцов.] Установить SUM<— 0. Повторять при m = 1, ..., i — 1: установить SUM«— SUM + R [i* (i — l)/2 + m] * X [mJ. 4. [Вычисление и печать X [i].] Установить X [i] ч-(В [i] — — SUM)/R [i* (i + l)/2] и печатать X [i]. Нетрудно проследить выполнение этого алгоритма. Сначала из первого уравнения вычисляется Xi и полученное значение подставляется затем во второе для вычисления Х2 и т. д. Встречается случай, когда большинство (более 95 %) элементов матрицы равно нулю. При этом необходимо хранить лишь ненуле- вые элементы вместе с их индексами. Представление матрицы, основанное на такой идее, будет дано в п. 5-3.1. 206
3-6. СТЕКИ Одной из наиболее важных линейных структур с пере- менным размером является стек. В первом пункте данного пара- графа вводятся понятия, относящиеся к этой структуре. В сле- дующем пункте даются алгоритмы включения и исключения из стека. Описывается векторное представление стека. Обсуждаются программные аспекты реализации стеков на языке ПЛ/1 с исполь- зованием класса памяти типа CONTROLLED и операторов ALLOCATE и FREE. 3-6.1. Определения и понятия В случае самого общего вида линейного списка у иас имеется возможность включать элемент в любую позицию списка и исключать его. Важный подкласс списков позволяет включать или исключать элемент лишь в конце списка. Линейный список, принадлежащий этому подклассу, называется стеком. Операция включения называется «проталкиванием», а операция исключе- ния — «выталкиванием». Наиболее и наименее доступные элементы стека называются верхом и низом стека соответственно. Поскольку операции включения и исключения выполняются в конце стека,, исключать элементы можно лишь в порядке, противоположном тому, в котором они были включены в стек. Это явление будет иллюстрироваться в п 3-7.1 в связи с обсуждением рекурсивной функции, причем такой линейный список часто называют списком типа LIFO (последним пришел, первым вышел). Общеизвестным примером стека, когда допускается выбор лишь крайнего его элемента, является стопка подносов в кафе- терии. Они поддерживаются пружиной таким образом, что для желающего доступен только один верхний поднос. Если убрать, верхний поднос, нагрузка на пружину уменьшится н на поверх- ности появится следующий поднос. Если поднос поместить в стопку, то он протолкнет ее вниз, а сам окажется верхним. Такое устройство для подносов показано на рис. 3-6.1. Еще одним известным примером стека является система же- лезнодорожного разъезда, показанная на рис. 3-6.2. В этой си- Вход Выход (включение} (исключение} Сюек Рис. 3-6.1. Держатель под- носов в кафетерии Рис. 8-6.2. Железнодорожная система пе- ревода вагонов, представленная в виде стека 20?
стоме последний железнодорожный вагон, загоняемый в стек, покидает его первым. Многократное применение операций вклю- чения и исключения позволяет выстраивать вагоны на выходной железнодорожной линии в различном порядке. Операция обработки, связанная со стеком, может быть огра- ничена проверкой верхнего элемента стека с целью его изменения (кроме включения или исключения). Такая операция иногда рас- пространяется и на другие элементы структуры. 3-6.2. Операции над стеками В простейшем случае операции над стеком можно имитировать путем использования вектора, содержащего фикси- рованное число элементов, достаточно большое для осуществле- ния всех возможных включений в стек. Такая схема распределе- ния памяти для стека представлена на рис. 3-6.3. В указателе ТОР хранится информация о позиции верхнего элемента в стеке. Сначала, когда стек пуст, содержимое ТОР равно нулю. Если же стек содержит единственный элемент, то значение ТОР равно единице и т. д. Каждый раз при включении в стек нового элемента указатель увеличивается на единицу, прежде чем элемент будет помещен в стек. И наоборот, указатель уменьшается на единицу всякий раз, когда производится исклю- чение из стека. Другое, более удобное для наших целей представление стека дано на рис. 3-6.4. Крайний правый занятый элемент стека соот- ветствует верхнему элементу, а крайний левый — нижнему. Ниже приведен алгоритм включения элемента в стек. Алгоритм PUSH. При заданных векторе S, содержащем п элементов и используемом в качестве последовательно органи- зованного стека, и указателе ТОР, определяющем верхний эле- мент стека, алгоритм включает в стек элемент X. 1. [Переполнение? ] Если ТОР п, то печатать сообщение Рис. 3-6.3. Представление стека в виде вектора о переполнении и закончить выполнение алгоритма. 2. [Приращение значения ТОР. 1 Установить ТОР-<—ТОР+ + 1- Исключеиие Включение рис. 3-6.4. Альтернативное представление стека 208
3. [Включение элемента.] Установить S [ТОР] ч-Х и за- кончить выполнение алгоритма. В первом шаге данного алгоритма проверяется условие пере- полнения. Если такое условие выполняется, то включение ие может быть произведено, а результатом является соответству- ющее сообщение об ошибке. Алгоритм исключения элемента из стека имеет следующий вид. Алгоритм POP. При заданных векторе S, содержащем п эле- ментов и используемом в качестве последовательно организован- ного стека, н указателе ТОР, определяющем верхний элемент стека, алгоритм запоминает верхний элемент стека в ячейке POP. 1. [Стек пуст? ] Если ТОР < 0, то печатать сообщение о пу- стом стеке и закончить выполнение алгоритма. 2. [Исключение элемента из стека.] Установить POP ч^ ч-S [ТОР]. 3. [Уменьшение указателя. ] Установить ТОР <— ТОР — 1 и закончить выполнение алгоритма. В первом шаге алгоритма проверяется, пуст ли стек. Если пуст, то предпринимаются соответствующие действия. Во многих случаях стек может становиться пустым неоднократно, поэтому проверка проводится и при включении и при исключении элемен- тов из стека. Существует еще один алгоритм, который обычно используется для считывания i-ro относительно верха стека элемента без его исключения. Алгоритм PEEP. При заданных векторе S, содержащем п эле- ментов и используемом в качестве последовательно организован- ного стека, и указателе ТОР, определяющем верхний элемент стека, алгоритм считывает i-й элемент стека, не исключая его. 1. [Нет элемента?] Если ТОР — i -р 1 с 0, то печатать сооб- щение об отсутствии элемента в стеке и закончить выполнение, алгоритма. 2. [Считывание i-ro элемента стека. ] Установить PEEP ч— ч- S [ТОР — i + 1 ] и закончить выполнение алгоритма. Отме- тим, что в первом шаге алгоритма проверяется возможность отсут- ствия требуемого элемента в стеке из-за некорректности значения i. Полезен также четвертый алгоритм, изменяющий содержимое i-ro относительно верха стека элемента. Алгоритм CHANGE. При заданных векторе S, содержащем и элементов и используемом в качестве последовательно организо- ванного стека, и указателе ТОР, определяющем верхний элемент ртека, алгоритм заменяет значение i-ro элемента стека содержи- мым X. 1. [Нет элемента?] Если ТОР — i | 1 с 0, то печатать сооб- щение об отсутствии элемента в стеке и закончить выполнение ал- горитма. 209
2. [Изменение i-ro элемента стека. I Установить S [ТОР — I -|- ] ] ч- X и закончить выполнение алгоритма. Рассмотрим теперь простой пример использования стека. Множество L = {wcwRw|£ {а, b) * } (где wR — обратная перестановка w; например, если w = ab, то wR = ba) определяет язык, содержащий бесконечное число строк. Грамматика, генерирующая этот язык, выражается следующей записью: G = (VN, Vt, S, Р), где VN=(S}, VT = {a, b, cj, P = ]S->aSa,S^-bSb,S->-c}. Эта грамматика генерирует, в частности, такие строки, как с, аса, bcb, abcba, bacab, abbcbba, abacaba, aabcbaa и т. д. Тре- буется составить алгоритм, который при заданной входной строке над алфавитом {а, Ь, с} будет определять, принадлежит ли эта строка языку L. Для решения этой задачи в алгоритме необходимо использовать стек. Предположим, что входная строка дополнена .справа пробелом (обозначенным Jb ). Например, строка aabcbaa будет представлена как aabcbaa £ . Алгоритм RECOGNIZE. Задана строка с именем STRING над алфавитом {а, Ь, с}, содержащая пробел в позиции крайнего правого символа, и функция NEXTCHAR, выбирающая очередной символ из STRING. Требуется определить, принадлежит ли содер- жимое STRING указанному выше языку. Вектор S используется в качестве стека, а ТОР является указателем верхнего элемента стека. 1. [Инициализация стека. 1 Установить ТОР ч- 1 и S [ТОР] ч— ч— ’с’. (Символ ’с’ помещается в стек.) 2. [Выбор и включение в стек символов из строки до тех пор, пока не встретится символ ’с’ или пробел. ] Установить NEXT ч- ч— NEXTCHAR (STRING). Повторять до тех пор, пока NEXT =/= ’с’: если NEXT = ’ то печатать сообщение ’НЕКОРРЕКТ- НАЯ СТРОКА’ и закончить выполнение алгоритма; в противном случае вызвать процедуру PUSH (S, TOP, NEXT) и установить NEXT ч— NEXTCHAR (STRING). 3. [Сканирование символов, следующих за ’с’, и сравнение их с символами из стека. 1 Повторять до тех пор, пока S [ТОР ] Ф ‘с’: установить NEXT ч- NEXTCHAR (STRING) и X ч- POP (S,TOP); если NEXT X, то печатать ’НЕКОРРЕКТНАЯ СТРОКА’ и закончить выполнение алгоритма. 210
4. [Следующий символ пробел?] Если NEXT = ’ то печа- тать ’КОРРЕКТНАЯ СТРОКА’; в противном случае печатать ’НЕКОРРЕКТНАЯ СТРОКА’. Выход. Данный алгоритм выполняется следующим образом. Сначала в стек помещается символ ’с’. Затем в стек включаются все сим- волы входной строки до тех пор, пока не встретится символ ’с’. Если во входной строке встречается символ ’с’, то осуществляется переход к шагу 3, в котором оставшиеся входные символы сравни- ваются с символами стека. При этом всякий раз из стека исклю- чается символ ’а’, если входной символ ’а’, и ’Ь’, если входной символ ’Ь’. Если же верхний элемент стека не совпадает со вход- ным символом, выполнение алгоритма заканчивается, и дальней- шая обработка входной строки не проводится. Если все символы стека соответствуют входным, то символ ’с’, бывший внизу стека, станет верхним элементом. Все символы 1 могут быть исключены из •стека только в том случае, если часть строки после символа ’с* яв- ляется обратной перестановкой части строки перед символом ’с’. Изменение содержимого стека для ряда входных строк отобра- жено в табл. 3-6.1. Таблица 3-6.1 Изменение содержимого стека при выполнении алгоритма RECOGNIZE Входная строка Скани- руемый знак Содержимое стека (крайний правый символ является верхним элементом стека) Входная строка Скани- руемый знак Содержимое стека (крайний правый символ является верхним элементом стека) abcba Д Нет с а са Ъ cab с cab b са а с с Корректная строка aabcaa Д Нет с а са а саа b caab caab а саа Строка некорректна, поскольку а =£ b aabcbaa Д Нет а а b с b а а а с са саа caab caab саа са с с Строка некорректна, поскольку ’с’ — верхний элемент стека, a NEXT =£= ’ До сих пор в данном пункте мы иллюстрировали операции над стеками, представленными в виде векторов. Теперьдлы рассмотрим альтернативный метод представления стека. 1 Кроме символа ’с’» — Прим, ред» 2U.
В некоторых языках программирования, таких как ПЛ/1 и АЛГОЛ W, имеется возможность непосредственного управления (т. е. управления с помощью операторов) объемом памяти и време- нем ее выделения для определенных переменных программы. В ПЛ/1 память, которой можно управлять таким способом, соот- ветствует классу CONTROLLED. Всякий раз. когда требуется память класса CONTROLLED для новой копии некоторой пере- менной или множества переменных, эта память может быть полу- чена посредством оператора распределения ALLOCATE. Если для последней копии переменной память больше не нужна, ее можно освободить с помощью оператора освобождения FREE. Рассмо- трим пример, иллюстрирующий программирование таких кон- струкций. Предположим, что мы описываем в программе на ПЛ/1 струк- туру с именем IDENTIFICATION, состоящую из двух элементов: имени студента NAME и его номера IDNO. Если желательно, чтобы память для IDENTIFICATION выделялась при непосред- ственном управлении программиста, следует использовать следу- ющее описание этой структуры: DECLARE 1 IDENTIFICATION CONTROLLED, 2 NAME CHARACTER (30) VARYING, 2 IDNO FIXED DECIMAL (6); Чтобы получить память для новой копии структуры IDENTIFICATION, используется оператор ALLOCATE IDENTIFICATION; Отметим, что оператор ALLOCATE лишь выделяет память, и что элементам NAME и IDNO при этом не присваивается никаких значений. Значения могут быть присвоены путем выполнения операторов NAME = ’RICK BUNT’; IDNO = 673129; Посмотрим, что случится, если вслед за этими операторами присваивания будут выполнены оператор распределения и два или более операторов присваивания. Например, ALLOCATE IDENTIFICATION; NAME = ’ROBT. KAVANAGH’; IDNO = 641785; После выполнения этих операторов любое обращение к NAME или IDNO обеспечивает доступ к значениям, присвоенным вслед за последним распределением памяти (т. е. ’ROBT.KAVANAGH’ и 641785). Заметим, что память, выделенная для значения ’RICK BUNT’, не теряется, однако в данной точке это значение недоступно. Чтобы получить доступ к этой переменной, необходимо освободить последнюю выделенную для IDENTIFICATION память. Это достигается путем выполнения оператора FREE IDENTIFICATION; 212
Теперь обращение к NAME соответствует использованию значения: ’RICK BUNT’. Если оператор FREE IDENTIFICATION выпол- нить еще раз, то для структуры IDENTIFICATION память не будет выделена, и любое обращение к NAME или IDNO приведет к ошибке. Отсюда должно быть очевидно, что распределение и освобож- дение памяти класса CONTROLLED проводится по принципу «последним пришел, первым вышел». В стеке элементы включаются и исключаются по тому же самому принципу, и, следовательно, используя операторы ALLOCATE и FREE, легко реализовать стековые операции. Проталкивание (PUSH) элемента в стек S- (который должен быть описан с классом памяти CONTROLLED)> осуществляется последовательностью операторов ПЛ/1: /*PUSH (S, X) — ПРОТОЛКНУТЬ ЭЛЕМЕНТ X В СТЕК S*/ ALLOCATE S; S = X; Выталкивание элемента из стека реализуется оператором: /*РОР (S) — ВЫТОЛКНУТЬ ЭЛЕМЕНТ ТОР ИЗ S*/ FREE S; Отметим, что существуют некоторые важные различия между представлением стека с использованием управляемой памяти и представлением его в виде вектора. Если используется управляе- мое распределение памяти, то толкование стека как возможной многоэлементной структуры данных менее очевидно. При этом общее описание структуры типа стека содержит описание элемента- прототипа, который используется при распределении и освобожде- нии управляемой памяти для каждой новой копии описанной пере- менной. При реализации стека с использованием механизма управ- ляемой памяти не только включение и исключение должно осущест- вляться сверху стека, но и ссылка возможна только на верхний элемент (т. е. алгоритмы PEEP и CHANGE неприменимы в случае использования управляемой памяти). Позиция верхнего элемента стека, представленного с использо- ванием управляемой памяти, непосредственно обрабатывается ком- пилятором языка ПЛ/1. Следовательно, как мы уже видели, при использовании управляемой памяти для операций PUSH и POP не требуется указатель ТОР. Хотя в оставшейся части данной гла- вы мы будем использовать алгоритмы PUSH и POP в том виде, как они были описаны для векторного представления, следует пом- нить, что их реализация с использованием управляемой памяти обеспечивает те же самые возможности включения и исключения без необходимости явной модификации указателя ТОР. В п. 3-7.1 мы приведем пример, иллюстрирующий представление стека с ис- пользованием управляемой памяти и операторов ALLOCATE и FREE. 213*
С>ФФФ< 1жихм III I !Низ1 Bep/j Вер*г Низг Рис. 3-6.5- Схема последовательного распре- деления памяти для двух стеков Очень часто в программе используется несколько сте- ков. В подобной ситуации может случиться так, что один стек переполнился, в то время как в других еще имеется значительный запас места. Вместо выделения мак- симального объема памяти для каждого стека предпочтительнее выделять общий пул, или максимальный блок памяти, который могут использовать все стеки. В частном случае двух стеков это приводит к простому рас- пределению памяти, допускающему их сосуществование, как пока- зано на рис. 3-6.5. Первый стек растет вправо, а второй — влево. При таком распределении один из стеков может занимать больше .половины памяти, отведенной для обоих стеков. Переполнение может произойти только в том случае, если суммарный объем двух стеков превосходит выделенный объем памяти. Сосуществование более двух стеков, совместно использующих общую область памяти при сохранении указанного условия пере- полнения и при фиксированном нижнем элементе каждого стека, невозможно. Для контроля условия переполнения необходимо пренебречь свойством фиксированного низа стека. С точки зрения вычислений хранение позиций верхнего и нижнего элементов по- следовательно распределенных стеков, использующих общую память, неэффективно. Для этого иногда требуется переписать .целые стеки, чтобы сохранить свойство последовательного рас- ’ пределения. Более удобная схема распределения памяти для такой -ситуации рассмотрена в следующей главе. 3-7. ПРИМЕНЕНИЕ СТЕКОВ В данном параграфе содержится трн примера использования стеков. Первый пример посвящен рекурсии. Рекурсия является важным средством во многих языках программирования, та- ких как АЛГОЛ 60 и ПЛ/1. Существует много задач, алгоритмы которых лучше всего описывать рекурсивным методом. Ряд таких задач приведен в этой книге (особенно много в гл. 5). Второе клас- сическое применение стека связано с компиляцией инфиксных выражений в объектный код. Это применение стека является, ве- роятно, одним из самых ранних примеров. Параграф заканчи- вается кратким обсуждением стековых машин. В некоторых вы- числительных машинах операции над стеками выполняются на .аппаратурном (нли машинном) уровне, и такие операции, как включение и исключение, производятся очень быстро. 3-7.1. Рекурсия В математике некоторое свойство (или множество Р) часто вводится с помощью индуктивного определения. Индуктнв- 214
ное определение может быть представлено с помощью заданного конечного множества элементов А и следующих трех предпо- ложений. 1. Базисное предположение - элементы множества А принад- лежат множеству Р. 2. Индуктивное предположение — элементы множества В, по- лученные из элементов множества А, принадлежат Р. 3. Экстремальное (ограничивающее) предположение — мно- жество Р состоит только из тех элементов, которые образованы в соответствии с предположениями I и 2. Индуктивное определение натуральных чисел, использующее операцию непосредственного следования S (х) = х 4- 1 для полу- чения новых целых чисел, может быть записано так: 1. О есть целое число (базисное предположение). 2. Если х — целое, то целым является и его последователь, (индуктивное предположение). 3. Натуральными числами являются юлько такие числа, ко- торые построены в соответствии с предположениями 1 и 2. Тем самым натуральные числа были рекурсивно определены пос- редством процесса индукции. Рекурсия есть метод определения множества или процесса в терминах самого себя. Функция факториала, областью определения которой являются все натуральные числа, может быть рекурсивно определена в виде . Г 1 при N = 0; FACTOR1AL(N)=|N.FACTORIAL(N_1) пррн N>h Здесь FACTORIAL (N) определяется через функцию» FACTORIALS—1), которая, в свою очередь, определяется через FACTORIAL (N — 2) и т. д., пока, наконец, не будет достиг- нута функция FACTORIAL (0), значение которой равно единице- Рекурсивное определение множества или процесса должно вклю- чать явную спецификацию значения (значений) аргумента (аргу- ментов); в противном случае определение никогда не закончится.. Основная идея состоит в том, чтобы конструктивно определить- функцию для всех значений ее аргументов, используя метод ин- дукции. В этом случае значение функции для конкретного значе- ния аргумента можно вычислить за конечное число шагов, исполь- зуя рекурсивное определение, при этом каждый шаг рекурсии приближает нас к решению. Важным средством, имеющимся в распоряжении программиста, является процедура (функция или подпрограмма). Процедуры в языке программирования удобны для программиста, поскольку они позволяют ему лишь один раз запрограммировать алгоритм, многократно встречающийся в программе. В некоторых языках программирования, таких как АЛГОЛ, ПЛ/1 и СНОБОЛ 4 (ио не в ФОРТРАНе), имеется аналогия рекурсивному шагу при определении функции, что очень удобно при использовании про- 215
цедуры, содержащей обращение к любой другой процедуре (вклю- чая саму себя). Процедура, содержащая обращение к самой себе, илн процедура, обращающаяся к другой процедуре, которая при известных обстоятельствах может обратиться к первой, называется рекурсивной процедурой. Существуют два важных условия, которые должны удовлетво- ряться в любой рекурсивной процедуре. Во-первых, каждый раз, когда процедура обращается к самой себе (прямо или косвенно), она должна в некотором смысле «приблизиться» к решению. В случае факториала каждый раз, когда функция обращается к са- мой себе, ее аргумент уменьшается на единицу. Во-вторых, дол- жен существовать критерий для прекращения процесса или вы- числения. В случае факториала таким критерием является равен- ство нулю значения п. По существу имеются два типа рекурсии. К первому типу от- носятся рекурсивно определенные функции (или примитивно-ре- курсивные функции г); примером этого типа функций является •факториал. Второй тип рекурсии связан с рекурсивным использо- ванием процедуры (непримитивная рекурсия). Типичным примером этого типа является функция Аккермана, определяемая как Г N 4- 1, если М = 0; A(M,N)=J А(М—1,1), если N = 0; ( А(М — 1,А(М, N — 1)) в остальных случаях. Рекурсия в функции Аккермана возникает вследствие того, что •функция А встречается в качестве аргумента при обращении к А; и это характерно для такого типа рекурсии. Многие полагают, что рекурсия является ненужным излишест- вом в языке программирования. Это основывается на том факте, что любая примитивно-рекурсивная функция, а следовательно, и любая числовая функция, которая обычно встречается на практике, может быть вычислена итеративным методом. Итеративный процесс можно проиллюстрировать с помощью схемы, приведенной на рис. 3-7.1. Этот процесс состоит из четырех частей: инициализации, принятия решения, вычисления и моди- фикации. Функции этих частей следующие. 1. Инициализация. Параметрам функции и параметру принятия решения присваиваются начальные значения. Параметр принятия решения используется для определения момента выхода из цикла. 2. Принятие решения. Значение параметра принятия решения используется для продолжения или завершения цикла. 3. Вычисление. Выполняются требуемые вычисления. 4. Модификация. Изменение параметра принятия решения и переход к следующей итерации. * Примитивно-рекурсивная функция является частным случаем рекурсив- ной функции. — Прим. ред. Л216
Рис. 3-7.1. Схема итеративного процесса Любую примитивно-рекурсивную функцию можно механически преобразовать в эквивалентный итеративный процесс. Однако это не всегда возможно для непримитивно-рекурсивных функций1. Хотя, например, для функции Аккермана существует итеративное решение, имеется много таких задач, итеративное решение которых либо не существует, либо его нелегко найти. Некоторые рекур- сивные процессы могут быть реализованы на языках программи- рования, ие допускающих рекурсии, лишь путем имитации рекур- сивных средств. У нас будет повод возвратиться к этой теме позд- нее. Роль рекурсии становится все более важной в задачах сим- вольной обработки и в других нечисловых приложениях. Далее в книге мы будем постоянно сталкиваться с задачами» в которых рекурсия неизбежна из-за рекурсивной природы про- цесса или рекурсивной структуры данных, подлежащих обработке. Даже в случаях, когда структура сама по себе нерекурсивна, ре- курсивный подход к решению задачи может оказаться более про- стым (хотя иногда и более медленным), чем его итеративный экви- валент. В отличие от нерекурсивной процедуры существуют особые проблемы, связанные с использованием рекурсивной процедуры. К рекурсивной процедуре можно обращаться как из нее самой,, так и извне, и поэтому для обеспечения правильного функциони- рования она должна сохранять адреса возврата в таком порядке, чтобы возврат из нее производился в надлежащее место вслед за оператором вызова. Процедура должна также сохранять формаль- ные параметры, локальные переменные и т. д. при входе и восста- навливать все эти параметры и переменные при выходе из проце- дуры. Общая схема алгоритма для реализации любой рекурсивной: процедуры содержит следующие шаги. 1 Можно говорить просто о рекурсивной нлн общерекурсивной функции. — Прим. ред. 217
«Рис. Зе7.2. Схема рекурсивной процедуры 1. [Пролог.] Сохранить параметры, локальные переменные н адрес возврата. 2. [Тело. ] Если выполнен базисный шаг, то произвести заклю- чительные вычисления и перейти к шагу 3; в противном случае произвести промежуточные вычисления и перейти к шагу 1 (ини- циировать рекурсивное обращение). 3. [Эпилог. ] Восстановить параметры, локальные переменные и адрес возврата, сохраненные в последний раз. Перейти по адресу возврата. Обобщенная схема такого алгоритма приведена иа рис. 3-7.2. Она состоит из пролога, тела и эпилога. Цель пролога состоит в том, чтобы сохранить формальные параметры, локальные переменные и адрес возврата, а цель эпилога —• восстановить их. Отметим, что восстанавливаются параметры, локальные переменные и адрес возврата, которые были сохранены самыми последними, т. е. 218
сохраненные последними восстанавливаются первыми (принцип? «последним пришел, первым вышел»). Тело процедуры содержит обращение к самой себе; фактически в некоторых процедурах мо- жет существовать более одного обращения к самой себе. Рекурсивную процедуру довольно трудно понять по ее схеме,, и лучшее, на что мы можем надеяться, это интуитивно уяснить ее.. Ключевым блоком тела процедуры является блок, содержащий обращение к ней самой. Штриховая линия этого блока показывает,, что внутри самой процедуры инициируется обращение к самой себе. Всегда, когда производится обращение процедуры к самой себе, пролог процедуры сохраняет всю информацию, необходимую’ для ее правильного выполнения. Тело процедуры содержит два блока вычислений, а именно* блоки промежуточных и окончательных вычислений. Нередко* блок промежуточных вычислений объединяется с блоком обраще- ния к процедуре. (Так происходит, например, при вычислении факториала). В блоке окончательных вычислений производится явное определение функции для конкретного значения (значений)* ее аргумента (аргументов). В блоке проверки определяется, яв- ляется ли значение (значения) аргумента (аргументов) таким, для* которого имеется явное определение функции. С каждым обращением (или входом) к рекурсивной процедуре ассоциируется номер уровня. Вход в процедуру при первоначаль- ном обращении из основной программы характеризуется номером' уровня «единица», при этом предполагается, что основная про- грамма имеет номер уровня «нуль». Каждый последующий вход в процедуру имеет номер уровня на единицу больше, чем номер уровня процедуры, из которой производится это обращение. Дру- гой характеристикой рекурсивной процедуры является глубина; рекурсии, определяемая числом рекурсивных обращений к проце- дуре в процессе вычисления при заданном аргументе или аргу- ментах. Обычно эта величина неочевидна за исключением; очень простых рекурсивных функций, таких как FACTORIAL (N),. для которых глубина равна N. Присущий рекурсивной процедуре принцип «последним при- шел, первым вышел» обусловливает тот факт, что стек является самой подходящей структурой данных, которую следует исполь- зовать при выполнении шагов 1 и 3 этой процедуры. При каждом- обращении к процедуре (или на каждом уровне рекурсии) проис- ходит «проталкивание» в стек для сохранения необходимых вели- чин; при выходе из данного уровня осуществляется «выталкива- ние» из стека для восстановления хранимых величии предыдущего (или вызывающего) уровня. Рекурсивный механизм лучше всего проиллюстрировать на примере. Рассмотрим алгоритм рекурсивного вычисления функции FACTORIAL (N), который явно показывает природу рекурсии. Рекурсивное определение функции FACTORIAL дано в начале- п. 3-7.1. 21
Алгоритм FACTORIAL. Алгоритм вычисляет N! при заданном целом N. Для сохранения информации при каждом рекурсивном обращении к FACTORIAL используется стек А. Эта информация содержит два элемента записи — текущее значение N и текущий адрес возврата RET_ADDR. TEMP—REC является рабочей струк- турой (состоящей из двух элементов PARM и ADDR), которая не- •обходима для должной передачи управления от одной активации процедуры FACTORIAL к другой. При каждом проталкивании TEMP_REC в стек А в него заносятся значения PARM и ADDR, присваиваемые N и RET_ADDR соответственно. ТОР указывает на верхний элемент в А и имеет нулевое начальное значение. Вна- чале адрес возврата устанавливается равным основному адресу обращения (т. е. ADDR ч— основной адрес), и предполагается, :что PARM имеет исходное значение N. 1. [Сохранение N и адреса возврата.] Вызов процедуры PUSH (A, TOP, TEMPER ЕС). 2. [Достигнут базисный шаг процедуры? ] Если N = О, то установить FACTORIAL 1 и перейти к шагу 4; в противном случае установить PARM-ч— N—1, ADDR -ч-шаг 3 и перейти к шагу !. 3. [Вычисление N! 1 Установить FACTORIAL ч—N* FACTORIAL (факториал N — 1). 4. [Восстановление предыдущих N и адреса возврата. 1 Устано- вить TEM-REC'- POP (А, ТОР) (т. е. PARM-e-N, ADDR ч— RET—ADDR и затем вытолкнуть из стека) и перейти по адресу ADDR. Шаги 1 и 4 являются соответственно прологом и эпилогом данного алгоритма. Шаги 2 и 3 образуют тело; проверка и конеч- ные вычисления проводятся в шаге 2. Шаг 3 содержит промежуточ- ные вычисления и рекурсивное обращение процедуры к самой себе. Последовательность выполнения алгоритма FACTORIAL при N = 2 приведена на рис. 3-7.3. Рекурсивная программа вычисления факториала на языке ПЛ/1 вместе с основной процедурой для контроля результатов дана на рис. 3-7.4. Отметим, что все переменные в этой программе заданы двоичными числами в форме с фиксированной точкой с точностью (31, 0). Это необходимо для данного выполнения процедуры. Поскольку идентификатор FACTORIAL описывается по принципу умолчания как десятичное число в форме с плавающей точкой, то атрибут RETURNS должен' использоваться и при описании функ- ции в вызывающей процедуре и в операторе PROCEDURE. Данная процедура, как и все процедуры, вызывающие сами себя, должны описываться с использованием атрибу- та RECURSIVE. Рассмотрим теперь пример более сложной рекурсии. Хорошо известным алгоритмом для нахождения наибольшего общего де- лителя двух целых чисел является алгоритм Евклида. Функция 220
Номер уровня Описание Содержимое стека Вход уровня 1 Шаг 1: PUSH (А, 0, (2, (основной вызов) основной адрес)) Шаг 2: N =Д 0, PARM-<- 1, ADDR-e-шагЗ 2 основной адрес }тор Вход уровня 2 Шаг 1: PUSH (A, I, (I, (первый рекурсив- шаг 3)) ный вызов) Шаг 2: N Ц= 0, PARM-e-0, ADDR-c-шаг 3 2 1 основной адрес шаг 3 t ТОР Вход u уровня 3 Шаг 1: PUSH (А, 2, (0, (второй рекурсив* шаг 3)) ный вызов) Шаг 2: N = 0, FACTORIAL-*-! 2 1 ! ° основной адрес шаг 3 шаг 3 | ТОР Шаг 4: POP (А, 3) перейти к шагу 3 2 1 основной адрес шаг 3 ТОР Возврат к уров- Шаг 3: FACTORlAL-<-I * I -ню 2 Шаг 4: POP (А, 2) перейти к шагу 3 2 основной адрес р ОР Возврат к уров- Шаг 3: FACTO R IAL-e-2 * I ню 1 Шаг 4: POP (А, I), перейти по основному адресу t ТОР Рис. 3-7.3. Последовательность выполнения алгоритма FACTORIAL при N = 2 для вычисления наибольшего общего делителя определяется сле- дующим образом^ {GCD (n,m), если 1Т>т; гл, если и = 0; GCD(n, MOD (in,и)) в остальных случаях. 221
RUNFACT: . PROCEDURE OPTIONS(MAIN); , /* TEST THE RECURSIVE FACTORIAL FUNCTION #/ DECLARE FACTORIAL RETURNS(BINARY F1XED<31>)» II,K) BINARY FIXEDOl); DO I = 3 TO 7 BY 2; ' . PUT SKIP(2) EDIT ( ’FACTORI AL (’ j I , ’ ) IS * ? F ACTOR I AL (I ) (A(10)»F<l),A(5),F(5)); ENO; PROCEDURE (N) RECURSIVE RETURNS(BINARY FIXE0(31))5 DECLARE N BINARY FIXEDOl) ; IF N * О THEN RETURN(1); ELSE RETURN(N * FACTORIALS - 1} J 5 END factorial; ENO RUNFACT; FACTOR IAL<_3J IS b FACTORIALS) IS 120 FACTORIAL(7) IS 5040 Рис. 3-7.4. Рекурсивная программа вычисления факториала Здесь MOD (m, п) есть ш, взятое по модулю п, т. е. остаток от деления m на п. Первая часть определения изменяет порядок сле- дования аргументов, если п > т. Если же второй аргумент равен нулю, то наибольший общий дели- тель равен первому аргументу (это определяет базисное значение функции). Наконец, в третьей части функция GCD определяется через саму себя. Отметим, что процесс вычисления должен рано или поздно завершиться, так как MOD (m, п) уменьшится да нуля за конечное число шагов. Например, GCD (20,6) вычисляется следующим образом: 20 = 6*3 + 2. Согласно алгоритму Евклида значение GCD (20,6) равно значению GCD (6, 2). Следовательно,' 6 = 2*3 + 0, и GCD (6, 2) равно GCD (2, 0), т. е. 2. Кроме того, вычисление значения GCD (6,20) сводится к вычислению GCD (20,6). Про- грамма на ПЛ/1 для вычисления функции GCD приведена на рис. 3-7.5. Для процедуры GCD справедливы те же самые замеча- ния, что и для процедуры FACTORIAL. Отметим также использо- вание в последней программе предложения ON ENDFILE (SYSIN), определяющего действия по концу файла на перфокартах. В ПЛ/1 имеется также функция MOD, удобная при программировании процедуры GCD. 222
Другой сложной рекурсивной задачей является задача о ха- нойской башне, история происхождения которой берет начало в древних ритуалах буддизма. Эта задача состоит в следу- ющем. Даны N дисков с уменьшающимися размерами, расположенные на одном колышке, и два свободных колышка; требуется переме- стить все диски на третий колышек в порядке уменьшения разме- ров. Второй колышек может использоваться для временного хра- нения дисков. Перемещение дисков ограничивается следующими правилами. 1. Каждый раз можно перемещать лишь один диск. 2. Перемещение диска можно производить с любого колышка на любой другой колышек. 3. Не допускается, чтобы диск большего размера лежал на диске меньшего размера. Графическое представление задачи приведено на рис. 3-7.6. Проще всего решить эту задачу можно, используя индуктив- ный метод. В случае одного диска его просто перемещают с ко- лышка А на колышек С. В случае двух дисков первый диск пере- мещают на колышек В, второй — с колышка А на колышек С, а затем первый диск перемещают с колышка В на колышек С. RUN_GCD: PROCEDURE OPTIONS(MAIN); /« TEST THE EUCLIDEAN ALGORITHM */ DECLARE II,J» B1NAFY FIXEDOL), _GCO RE TURN St BINARY F1XED(31H: ON ENDFILE(SYS IN) GO TO END; ?(EAD: /« GET SCME VALUES TO TEST THE GCD FUNCTION */ GET SKIP LISTCI,ji; _ , , ..... , , PUT SKIP12) ED IT(’THE GREATEST COMMON DIVISOR OF ANO ' 1J1 • IS ’ ,GCD<bJ)HAl31),F(5),A(5hFI5),Al4),Ft5)K GO TO READ; PROCEDURE (M5N) RECURSIVE RETURNS(BINARY FIXED131))1 DECLARE IM,N) BINARY FIXEDOl); IF H> « /» REVERSE THE CALL «X THEN RETURN(GCD(N,M)1; IFN=OX*MIS THE GREATEST COMMON DIVISOR ♦/ THEN RETURN(M): RETURN IGCO (N.MODtM.N)) I ; Xй EQUIVALENT VALUF *<* END: END RUN_GC0? THE GREATEST COMMON DIVISOR OF 84 ANC 246 IS $ THE GREATEST COMMON DIVISOR OF 6 AND 20 1$ £ The GREATEST COMMON DIVISOR OF 121 ANO 33 1$ 34 Рис. 3-7.5. Рекурсивная программа вычисления функции CCD 223
КолышекА. Колышек в Колышек С {начало задачи) (промежуточный) (решение задачи) Рис. 3-7.6. Задача о ханойской башне В общем случае решение задачи перемещения N дисков с колышка А на колышек С включает три шага: 1. Перемещение N — 1 дисков с А на В. 2. Перемещение N-ro диска с А на С. 3. Перемещение N — 1 дисков с В на С. Детальный анализ первого и третьего шагов показывает, что они имеют рекурсивную природу, т. е. первый шаг является реше- нием, использующим колышки А и В вместо колышков А и С и А — 1 дисков, а третий шаг является решением, использующим колышки ВиС вместо А и С и N — 1 дисков. Следовательно, для решения задачи о ханойской башне можно использовать рекур- сивную процедуру. На рис. 3-7.7 приведена рекурсивная проце- дура на языке ПЛ/1, обеспечивающая такое решение, вместе с ос- новной программой для проверки ситуации с тремя дисками. Про- грамму легко понять; операторы 10, 11 и 12 соответствуют трем шагам общего решения, описанным выше: Метки Н_ADDR1, Н—ADDR2 и MAIN_________ADDR необязательны; цель их введения — помочь читателю понять соответствие между рекурсивно-процедурным решением, представленным на рис. 3-7.7, и имитацией рекурсивно-процедурного решения, которая будет обсуждаться ниже. Для того чтобы более ясно показать рекурсивиость процесса решения задачи о ханойской башне, опишем алгоритм (и про- грамму), имитирующий рекурсивно-процедурное решение. При этом в явном виде формируется рекурсивный механизм, использу- ющий стек. Алгоритм HANOI. При заданных числе перемещаемых дисков N_VALUE, исходном колышке SN—VALUE, промежуточном ко- лышке IN—VALUE и результирующем колышке DN—VALUE алго- ритм HANOI выполняет шаги, необходимые для перемещения N—VALUE дисков с колышка SN—VALUE на колышек DN—VALUE согласно правилам задачи о ханойской башне. Стек ST используется для обеспечения рекурсивности алго- ритма; каждый элемент ST содержит пять полей: N, SN, IN, DN и RET—ADDR,предназначенных для хранения значенийN—VALUE, SN—VALUE, IN—VALUE, DN—VALUE и адреса возврата соответ- ственно. TEMPREC является пятиэлементной рабочей структу- рой, содержащей элементы N_VALUE, SN—VALUE, IN—VALUE 224
DM__VALUE и ADDRESS и-необходимой для обеспечения передачи управления от одной активизации алгоритма HANOI в другую. ТОР указывает на верхний элемент в ST. Для работы со стеком ST используются описанные ранее алгоритмы PUSH и POP. Первона- чально переменной ADDRESS присваивается значение основного адреса обращения, а указателю ТОР присваивается нулевое зна- чение. 1. [Сохранение параметров н адреса возврата.] Вызов PUSH (ST, TOP, TEMPREC). Результат выполнения PUSH сле- дующий: TOP TOP + 1, N [TOPI — N—VALUE, SN [TOP] «- SN VALUE, IN [TOP]«— IN—VALUE, DN [TOP] <-DN„ VALUE, RET—ADDR [TOP ] — ADDRESS. i HAN2i procedure optionsimainj; RECURSIVE PROCEDURE SOLUTION OF TOWERS OF HANOI PROBLEM 2 DECLARE N FIXED BINARY131), HANOI ENTRY (FIXED BINARY(31)•CHARACTER!1), CHARACTER(11,CHARACTER(1)>* /« INPUT NUMBER OF DISCS ON STARTING NEEDLE 3 GET LIST4M); /* INITIATE CALL TO HANOI 4 PUT EDIT(’TOWERS OF HANOI PROBLEM WITH ’,N,’ DISCS’! (SKIP,A,F(2),A); 5 CALL HANOI(M»’A’,’B’,»C’J; 6 HANOI; PROCEDURE IN,SN,1N,DNI RECURSIVE; . J* PROCEDURE ’HANOI’ MOVES ’N’ DISCS FROM NEEDLE ’SN’ TO /* NEEDLE ’DN’ USING NEEDLE ’IN’ AS AN INTERMEDIATE Я 7 DECLARE N FIXED BINARYI31)» (SN,IN,DNJ CHARACTERil); 8 IP N = О . 9 THEN RETURN; /* MOVE N-l DISCS FROM START TO INTERMEDIATE NEEDLE 10 ELSE CALL HANOI(N-L,SN>DN,IN)5 11 H_ADOR1: MOVE DISC N FROM START TO DESTINATION NEEDLE; MOVE /* DISCS FROM INTERMEDIATE TO DESTINATION NEEDLE PUT EDIT(’MOVE DISC ’,N,’ FROM ’.SN,’ TO *,DNI (SKIP,A,F12 I?A,A(1),A,A{1)J; 12 CALL HANCI(N-l,IN,SN,ON)5 13 H_ADDR2: END HANOI; 14 MAIN_ADDR: ENO han2; TOWERS OF HANOI PROBLEM WITH 3 DISCS MOVE MOVE NOVE MOVE MOVE NOVE MOVE DISC DISC DISC DISC DISC DISC DISC 2 3 1 2 1 FROM A FROM A FROM C FROM A FROM В FROM В TO C TO в то в то с TO A TO C TO c FROM A Рис. 3- 7.7. Рекурсивная программа решения задачи о ханойской башне 8 Трамбле Ж.. Соренсон ГЕ 225
Выпол- няемый шаг Описание Содержимое стека 1 PUSH(ST,0,(2,A,B,C, N 2 основной)) SN А 2 N =И= 0, имитировать вызов IN В HANOI(I,A,C,B) DN С с ADDRESS-шаг 3 RET— Основной ADDR flop I PUSH(ST,I,(I,A,C,B,3)) 2 | 1 2 N 5^ 0, имитировать вызов А А HANOI(0,A,B,C,) В с с ADDR ESS-шаг 3 С в Основной 3 hop 1 PUSH(ST,2,(0,A,B,C,3)) 2 1 0 2 N — 0, перейти к шагу 3 А А А В с В С в С Основной 3 3 TOP t 3 POP(ST,3) , переместить 2 1 диск 1 с А на В, имитиро- А А вать вызов В с HANOI(0,C,A,B) С в с ADDRESS-шаг 4 Основной 3 t ТОР 226
Выпол- няемый шаг Описание Содержимое стека I PUSH(ST,2,(0,C,A,B,4)) 2 I 1 0 2 N — 0, перейти к шагу 4 А А с В С А С В В Основной 3 4 TOP t 4 POP(ST, 3), перейти к ша- 2 I 1 I гу 3 А А В С С В Основной 3 1'ГОР 3 POP(ST, 2), переместить 2 1 диск 2 с А на С, имитировать А 1 вызов В 1 НА NO 1(1,В,А,С) С 1 с ADDRESS-шаг 4 Основной 1тор I PUSH(ST,I,(1,B,A,C,4)) 2 1 1 2 N 0, имитировать вызов А в I НА NO 1(1,В,С,А) В А I с ADDRESS-шаг 3 С С I Основной 4 I |'ГОР 8* 227
Выполня- емый шаг Описание Содержимое стека 1 PCSH(ST,2,(O,B.C,A,3)) 2 1 0 2 N = 0, перейти к шагу 3 в в в А с с с А Основной 4 3 TOP t 3 POP(ST,3), переместить 2 1 диск I с В на С, имитировать А в вызов в А HANOI(0,A,B,C) с с с ADDRESS-шаг 4 Основной 4 t ТОР I PUSH(ST,2t(0,A,B,C,4)) 2 1 0 2 N = 0 , перейти к шагу 4 А в А В А В с с С Основной 4 4 TOP t 4 POP(ST,3), перейти к ша- 2 1 1 гу 4 А В 1 В А 1 С С 1 Основной 4 1 t ТОР 228
।Рис. 3-7.8. Последовательность выполнения алгоритма HANOI (2,'А','В','С') г 2. [Проверка конечного значения N; если конечное значение Гне достигнуто, то перемещение N — 1 дисков с исходного колышка ;на промежуточный.] Если* N = 0, то перейти по адресу =RET._ADDR; в противном случае установить N_VALUE N — 1, SN„VALUE 4- SN, IN_VALUE 4- DN, DN-VALUE 4- IN, ADDRESS 4— шаг 3 и перейти к шагу 1. ! 3. [Перемещение N-ro диска с исходного колышка на резуль- тирующий; перемещение N — 1 дисков с промежуточного ко- лышка на результирующий.] Установить TEMPREC-4- POP (ST, ТОР), напечатать сообщение ’ПЕРЕМЕСТИТЬ ДИСК’ N ’С КОЛЫШКА’ SN ’НА КОЛЫШЕК’ DN, установить, N-VALUE N — 1, SN-VALUE IN, IN—VALUE SN, DN—VALUE DN, ADDRESS шаг 4 и перейти к шагу 1. 4. [Возврат на предыдущий уровень. ] Установить TEMPREC ч- POP (ST, ТОР) и перейти по адресу RET—ADDR. Алгоритм сравнивает N [ТОР ] с нулем. Если N [ТОР ] не равно нулю, имитируется рекурсивное обращение к процедуре перемещения N [ТОР] — 1 дисков с SN [ТОР] на IN [ТОР]. После ее завершения диск с номером N [ТОР ] перемещается с ко- лышка SN [ТОР] на колышек DN [ТОР]. Наконец, имитируется рекурсивное обращение к процедуре перемещения N [ТОР ] — 1 Дисков с IN [ТОР] на DN [ТОР]. На рис. 3-7.8 приведена после- довательность выполнения (трасса) алгоритма HANOI (2, ’А’, ’В’, ’С’) при исходных данных N = 2, SN = ’A’, IN = ’В’, DN = = ’С’ и ADDRESS = MAIN. Программа на ПЛ/1, реализующая алгоритм НА NO I, представлена на рис. 3-7.9. В программе исполь- зуется неявный стековый механизм, обеспечиваемый операторами ALLOCATE и FREE языка ПЛ/1, вместо имитации стека, исполь- зуемого в описании алгоритма. Операторы ALLOCATE ACTIVATION-RECORD и FREE ACTIVATION—RECORD обес- печивают выполнение операций PUSH и POP. 229
HANI: PROCEDURE OPTIONS(MAIN!: /* EXAMPLE 3.7.1 •/ /* RECURSIVE PROCEDURE SIMULATION SOLUTION OP TOWER OF HANOI */ /« ' PROBLEM */ DECLARE M FIXED BINARY(31b 1 ACTIVATION-RECORD CONTROLLED, i RET.ADDR LABEL 7 2 N FIXED BINARY(31b 2 SN CHARACTER (lb 2 IN CHARACTER lib 2 DN CHARACTER <11, N VALUE FIXED BINARY(31), (SN—VALUE,1N_VALUE?DN_VALUE) CHARACTER (lb ADDRESS LABEL» /« READ IN NUMBER OF DISCS ON NEEDLE GET LIST(Mb /« INITIATE CALL TO HANOI - INITIALIZE CALL PARAMETERS »Z /* MOVE 'N—VALUE* DISCS FROM NEEDLE 'SN_VALUE' TO NEEDLE */ /« 'DN-VALUE' USING NEEDLE 'IN-VALUE' AS AN INTERMEDIATE */ PUT EDIT C'TCWERS OF HANOI PROBLEM WITH *,M. ' DISCS’! <SKlP,A,F(2bAb N_VALUE = M? SN-VALUE = ’A'? IN-VALUE = ’0’? DN—VALUE = * C’ ? ADDRESS = MAIN—ADDR; GO TO HANOI_CALL5 MAIN—ADDRs STOP? HANOI.CALL: ALLOCATE ACTIVATION-RECORD? N = N—VALUE? SN = SN—VALUE? IN = IN_VALUE? DN = DN_VALlJE5 RET-ADDR = ADDRESS? IF N = О THEN GO TO RET-ADDR? ELSE DO? MOVE N-l DISCS FROM START NEEDLE TO INTERMEDIATE NEEDLE*/ N—VALUE = N - 1? SN—VALUE - SN? IN_VALUE = DN? DN—VALUE = IN? ADDRESS = H-AOORl? GO TO HANOI-CALL? END? Соответствие между программами, приведенными на рнс. 3-7.7 и 3-7.9, очевидно из сопоставления комментариев и меток опера- торов. Переменные N—VALUE, SN—VALUE, IN—VALUE и DN____VALUE на рис. 3-7.9 используются в качестве передаваемых параметров в операторах CALL HANOI на рис. 3-7.7. Применение стека в настоящем пункте касалось лишь сохране- ния значений параметров и локальных переменных при вычисле- нии рекурсивных функций. Стеки, однако, можно использовать и при управлении распределением памяти во многих языках с блоч- ной структурой (см. п. 5-6). 230
H ADDR15 FREE ACT IVAT10N_RECORD» /* MOVE DISC N FROM START TO DESTINATION NEEDLE * /« MOVE N-l DISCS FROM INTERMEDIATE TO DESTINATION NEEDLE • PUT EDIT!‘MOVE DISK ',N,' FROM ',SN,’ TO ’,DNI (SKIP,A,F(2bA,AllI,A,A<D): N_VALUE = N - 1? SN_ VALLIE = IN? IN_VALUE » S№ DEVALUE = D№ ADDRESS « H_ADDR2t GO TO HANOI_CALLt H_ADDR2: FREE ACTIVATION_RECORO: GO TO RET„ADDR; END HANI; TOWERS OF HANOI PROBLEM WITH 3 D.ISCS MOVE DISK 1 FROM A TO C MOVE DISK 2 FROM A TO В MOVE DISK 1 FROM С TO В HOVE DISK 3 FROM A TO C HOVE DISK 1 FROM В TO A MOVE DISK 2 FROM В TO C MOVE DISK IF ROM A TO C Рис. 3-7.S- Реализация алгоритма HANOI на ПЛ/1 Упражнения к п. 3-7.1 1. Известный метод вычисления многочлена вида P„w = + OjX"-1 -I- с2х"-2 + ... + I ап состоит в использовании схемы Горнера Этот метод является итеративным и описывается следующим образом: А) ~ &0> *г.|-1 = + °(+i; 1 = 0.1... л — 1, откуда можио получить bn — Рп (х). Альтернативное решение этой задачи может быть записано в виде Р„(Ч = >р„.|(х) + ап, где P„_1W = ч,/-' + П|х" = + ... + о„ J | o„_v Ойо соответствует рекурсивной постановке задачи. Напишите программу вы- числения такого многочлена в виде рекурсивной процедуры. Используйте при этом следующие исходные данные: п — 3, а0 ~ 1, at = 3, а2 ~ 3, а3 = I нх= 2. 2. Рассмотрим множество всех корректных скобочных инфиксных ариф- метических выражений, состоящих из однобуквепных переменных, неотрица- тельных целых чисел и четырех операторов: +, —, * н /. Дадим рекурсивное определение всех таких корректных выражений. 1. Любая однобуквенная переменная (А—Z) или неотри- цательное целое число являются корректным инфиксным выражением. 2. Если в н Р суть корректные инфиксные выражения, то (а + р), (а — р), (сс * Р) и (а/p) также являются кор- ректными инфиксными выражениями. 231
3. Корректными инфиксными выражениями являются только такие выражения, которые определяются пп. 1 и 2. Напишите программу в виде рекурсивной процедуры, входными данными которой будет служить строка символов, а выходом — сообщение «КОРРЕКТНОЕ ВЫРАЖЕНИЕ», если входная строка является корректным инфиксным выра- жением, или сообщение «НЕКОРРЕКТНОЕ ВЫРАЖЕНИЕ» в противном случае. Напишите также основную программу для ввода исходных данных н обращения к указанной процедуре. 3. Напишите программу в виде рекурсивной процедуры для. вычисления квадратного корня числа. В качестве исходных данных используйте тройку чисел N, А и Е, где N — число, из которого требуется извлечь квадратный ко- рень, А — приближенное значение корня, Е —• допустимая ошибка резуль- тата. Используйте функцию ROOT(N, А, Е) = А, если ]Аа — N] <; Е; N, —g-jj-—, Е. \ в противном случае. используйте следующие тройки чисел: 1,0 1,5 2,5 14,2 0,001 0,001 0,001 0,001 В качестве контрольных данных 2 3 8 225 4. Еще одна обширная область применения рекурсии относится к задаче генерации всевозможных перестановок из множества символов. Для множества, состоящего из символов А, В и С, существует шесть перестановок, а именно, АВС, АСВ, ВАС, ВСА, СВА н САВ. Множество перестановок из N символов генери- руется путем выбора каждого очередного символа и помещения его перед каждой из перестановок оставшихся N — 1 символов. Следовательно, можно определить перестановки не множества символов через перестановки из меньшего множества символов. Напишите программу в виде рекурсивной процедуры для генерации всевозможных перестановок из множества символов. 5. Во многих задачах требуется определить число различных разложений заданного целого числа N, т. е. сколькими способами можно представить N в виде суммы целых слагаемых. Если мы обозначим через Qmn число способов, с помощью которых можно представить целое число М в виде суммы, каждое слагаемое которой не превосходит N, то число разбиений целого N опреде- ляется Qnn- Рекурсивное определение функции Qmn имеет вид Весли М= I, при всех N; 1,если N— 1, при всех М; Qmm, если M<N; I + Qm, м-i, если М — N; Qm, N-i, + Qm-n. n. если М> N- Напишите программу в виде рекурсивной процедуры и используйте в качестве исходных данных N = 3, 4, 5 н 6. Qmn — при этом 3-7.2. Выражения в польской .записи и их компиляция В данном пункте мы главным образом будем рассма- тривать машинное вычисление и компиляцию инфиксных выраже- ний. Мы увидим, что наиболее эффективное вычисление инфикс- ного логического выражения состоит в предварительном преобра- 232
зовании его в суффиксное выражение и последующем вычислении. Такой подход исключает повторное сканирование инфиксного выражения для получения его значения. В данном пункте мы бу- дем использовать примеры выражений из теории языков програм- мирования, хотя изложенная ниже методология применима для выражений любого типа. В п. 2-2 было показано, что язык, состоящий из множества всех корректных инфиксных выражений, может быть точно описан его грамматикой. То же самое относится и к соответствующему множеству всех корректных выражений в польской записи (суф- фиксных или префиксных). При преобразовании предложения на языке, скажем, Lx в предложение на другом языке L2 с помощью некоторого отображения требуется, чтобы L2 обладал многими свойствами, присущими Lp При преобразовании выражений из одного типа в другой должны быть сохранены такие свойства, как «правильность» выражений, ассоциативность и коммутатив- ность операторов. В п. 3-7.2.2 формулируется теорема, позволяющая определить, корректно ли выражение в польской записи (и, следовательно, его соответствующий инфиксный эквивалент). Затем подробно опи- сывается перевод инфиксных выражений в польскую запись, а также рассматривается одно классическое применение стека. Наконец, довольно подробно обсуждается генерация команд языка ассемблера на основе выражений в польской записи. При этом свойства оператора языка могут использоваться для достижения некоторой оптимизации программы (т. е. получения программы, содержащей меньше команд, чем в неоптимизированном варианте). 3-7.2.1. Польская запись В этом подпункте мы изложим систему обозначений для выражений в польской записи. Эта система обеспечивает опреде- ленные преимущества по сравнению с традиционной инфиксной системой. Вначале дается индуктивное определение множества корректных суффиксных1 польских выражений. Затем обсуж- дается простая теорема, которую можно использовать для опре- деления корректности выражений н польской записи. Кратко рас- сматривается также вычисление таких выражений. Рассмотрим множество всех корректных полностью скобочных арифметических выражений, содержащих однобуквенные пере- менные, неотрицательные целые числа и четыре оператора 4-, —, * и /. Приведем следующее рекурсивное определение всех таких корректных выражений. 1. Любая однобуквенная переменная (а—z) или неотрицатель- ное целое число является корректным инфиксным выражением. ' Используется также термин «постфиксная польская запись». — Прим, ред. 233
2. Если аир суть корректные инфиксные выражения, то (а + 0), (а — р), (а * р) и (а/p) также являются корректными инфиксными выражениями. 3. Корректными инфиксными выражениями являются только те выражения, которые определяются в пп. 1 и 2. В соответствии с этим индуктивным определением такие выра- жения, как например, (а + Ь), ((а + Ь) — 5) и (10 4- ((b * c)/d)) считаются корректными, а выражения а + 5, (а 4- b * с) — не- корректными. При написании корректного выражения необходимо использо- вать полностью скобочную форму. Поскольку это требование до- вольно жесткое, для того чтобы число скобок не было чрезмерно большим, примем определенные соглашения. Одно очевидное со- глашение состоит в исключении внешних скобок выражения, с тем чтобы (р 4- q) * г было корректным выражением вместо ((р 4- q)* * г), как этого требует исходное определение. Еще один метод уменьшения числа скобок заключается в прис- ваивании операторам приоритета. После этого дальнейшее умень- шение числа скобок можно осуществить, если потребовать, чтобы из любых двух бинарных операторов, имеющих одинаковый при- оритет, левый выполнялся первым. То же самое требование может быть обеспечено соглашением о том, что эти бинарные операторы ассоциативны слева. Такое соглашение обычно используется в арифметике; например, 4 + 6 X 3 — 7 означает 4 + (6x3) — 7. Если вычисление выражения выполняет машина, то важно умень- шить число скобок во избежание чрезмерного числа его сканиро- ваний. Рассмотрим сначала машинное вычисление бесскобочных ариф- метических выражений, состоящих из однобуквенных переменных, неотрицательных целых чисел и четырех операторов (+, —, * и /). Предполагается, что операторы * и /имеют одинаковый прио- ритет, более высокий, чем приоритет операторов 4- и —. Приме- ром такого бесскобочного арифметического выражения является выражение а + b * с + d * е i ' ' 5“ з' Согласно нашим соглашениям приведенное выражение должно означать (а 4- (Ь * с)) + (d *е). При вычислении выражения его необходимо неоднократно сканировать слева направо. Чнсла под частями выражения указывают иа шаги такого вычисления. Этот процесс вычисления неэффективен из-за необходимости по- вторных сканирований. Если в выражении имеются скобки, то они влияют на приори- теты его составляющих. Например, в выражении (а 4- Ь)*с сна- чала вычисляется а + Ь, а затем (а 4- Ь)*с. В действительности 234
имеется возможность записать выражение так, чтобы вычисление его составляющих не зависело от приоритета операторов. Это осу- ществляется путем заключения составляющих выражения в скобки, причем таким образом, чтобы каждому оператору соответствовала пара скобок. Такая пара скобок содержит оператор с операндами. Определим теперь скобочный уровень оператора как полное число окружающих его пар скобок. Пара скобок, непосредственно за- ключающих оператор, имеет тот же самый скобочный уровень, что и сам этот оператор. Такое выражение называется полностью скобочным выражением. Например, в полностью скобочном выра- жении (а + ((b*c)*(d + е))) 1 3 2 3 целые числа под операторами означают их скобочные уровни. При вычислении такого выражения его часть, содержащая оператор с самым высоким скобочным уровнем, вычисляется в первую очередь. При наличии нескольких операторов с наивысшим ско- бочным уровнем (как в указанном примере) они вычисляются один за другим слева направо. После того как вычислены составляю- щие выражения, содержащие операторы с наивысшим уровнем, таким же способом вычисляются составляющие, содержащие операторы с более низким уровнем. Для приведенного примера вычисления производятся в следующем порядке: (b*c), (d + е), ((b*c)*(d + е)), (а + ((b*c)*(d Д- е))). Как уже говорилось, для полностью скобочных выражений отсут- ствует необходимость в соглашении о приоритетах операторов. При вычислении частично скобочных выражений с операто- рами, которым присвоен приоритет, или полностью скобочных выражений необходимо повторно сканировать выражение слева направо. Это обусловлено тем, что операторы со своими операн- дами могут появляться не только в начале выражения. Использо- ванная до сих пор система предполагает запись оператора между операндами, например, а*Ь. Такая система называется инфиксной записью. Повторное сканирование можно исключить, если инфикс- ное выражение преобразовать сначала в эквивалентное бесско- бочное суффиксное или префиксное выражение, в котором части выражения имеют форму операнд операнд оператор или оператор операнд операнд вместо инфиксной формы операнд оператор операнд Такой тип записи известен под названием записи Лукашевича (по имени польского логика Яна Лукашевича) или «польской записи» 235
и «обратной польской записи» соответственно. Например, выраженияприведенные в каждой строке табл. 3-7.1, экви- валентны. Т а б л и ца 3-7.1 Инфиксная, суффиксная и префиксная записи выражений Инфиксная Суффиксная {обратная польская) Префиксная (польская) а а а а 4- b ab 4- 4- ab а 4- b 4~ с ab 4 с 4- 44~ abc а + (Ь 4- с) аЬс 4—к 4 а + Ьс а 4~ b *с abc *4- 4 а *Ьс а * (Ь 4- с) abc 4_ * *а 4- Ьс a *b*c ab»c * # #abc Отметим, что и в су(рфиксном и в префиксном вариантах записи инфиксного выражения положение переменных относительно друг друга одинаковое. Выражения в суффиксной или префиксной фор- мах являются бесскобочными, а операторы располагаются со- гласно их приоритетам. Полностью скобочное инфиксное выражение можно непосред- ственно преобразовать в суффиксную запись, начав с внутреннего скобочного подвыражения и продвигаясь к краям всего выраже- ния. В случае полностью скобочного выражения (а + ((b»c)*d)) 1 3 2 внутоенним скобочным подвыражением уровня 3 является (Ь*с), которое преобразуется к виду be*. Это суффиксное подвыражение становится первым операндом оператора * на уровне 2. Следова- тельно, подвыражение bc**d уровня 2 преобразуется в суффикс- ную форму bc*d* и, наконец, на уровне 1 форма а + bc*d* преобразуется в результирующую суффиксную запись abc*d*+. Программисты, конечно, не используют выражений в полностью скобочной форме. Некоторые компиляторы с ФОРТРАНа (и дру- гих языков) сначала преобразуют частично скобочные выражения в полностью скобочную форму, а затем выполняют преобразование в суффиксную форму. Суффиксная форма, таким образом, наиболее удобна для компиляторов. 236
Рассмотрим задачу машинного преобразования полностью бес- скобочного выражения (содержащего операторы +, —, * и /) в суффиксную форму. Как было сказано выше, при переводе в суф- фиксную польскую запись меняется порядок следования лишь операторов. При сканировании выражения слева направо край- ний левый оператор, имеющий наивысший приоритет, станет первым в суффиксной строке. Следующий по приоритету оператор будет вторым оператором в выражении. Отметим, что для инфикс- ных выражений суффиксная форма будет не единственной, если мы не укажем, что крайний левый оператор имеет более высокий или равный приоритет по сравнению с другими операторами. Например, выражение а + b + с может быть преобразовано в ab 4- с + или abc -J- 4-, если не указано, что крайний левый оператор 4- в инфиксной строке имеет более высокий приоритет по сравнению с другим оператором. Отсюда ясно, что при скани- ровании суффиксного выражения слева направо операторы встре- чаются в том же порядке, в котором они вычислялись бы в соответ- ствии с соглашением о приоритетах операторов в инфиксном вы- ражении. Например, суффиксная форма abc* 4- выражения а 4 Ь*с, в которой при сканировании слева направо оператор * встречается раньше оператора 4-, указывает на то, что умножение выполняется раньше сложения. На практике часто бывает необходимо вычислять выражения, т. е. определять их значения при заданных значениях имеющихся в иих переменных. Это можно легко сделать с помощью суффикс- ного представления выражения, поскольку при этом требуется сканирование только в одном направлении (слева направо) и только один раз, в то время как для инфиксного выражения скани- рование должно быть произведено несколько раз и в обоих на- правлениях. Например, при вычислении суффиксного выражения abc* 4~ данная строка сканируется слева направо до тех пор, пока не встретится оператор *. Его операнда, а именно b н с, находятся непосредственно слева от этого оператора, и выражение Ьс* заменяется его значением. Обозначим его Ti- Это сводит исходную суффиксную строку к виду aTi 4~- При дальнейшем сканировании вправо от Ti встречается следующий оператор -Н операндами ко- торого являются а и 14, а вычисление приводит к результату, кото- рый мы обозначим Т2. Этот метод вычисления суффиксных выражений можно обоб- щить с помощью следующих четырех правил, которые должны применяться к выражению до тех пор, пока не будут обработаны все его операторы. 1. Найти в выражении крайний левый оператор. 2. Выбрать два операнда, стоящих непосредственно слева от найденного оператора. 3. Выполнить указанную операцию. 4. Заменить оператор и операнды полученным результатом. 237
В качестве следующего примера ниже вычисляется суффиксное выражение abc/d*+, соответствующее инфиксному выражению а + (b/c)*d при а = 5, b = 4, с = 2 и d = 2. Суффиксное выражение abc/d * -|- aTjd * 4' аТ24~ Т3 Текущий выполняемый оператор * + Текущие операнды Ь, с 4,d а, Т8 Отметим, что в этом примере Тг — 2, Т2 = 4, Т3 — 9. Как было сказано выше, существует ряд компиляторов, кото- рые преобразуют инфиксные арифметические выражения в поль- скую запись. В таких компиляторах на этапе синтаксического ана- лиза должны быть обнаружены некорректные польские выраже- ния и, следовательно, их некорректные инфиксные эквиваленты. Ниже приводится описание метода, который можно использовать для обнаружения таких некорректных выражений. Ограничимся суффиксными польскими выражениями, хотя аналогичный метод можио разработать и для префиксных польских выражений. Сначала с помощью индукции опишем множество корректных суф- фиксных польских выражений. Пусть S — множество символов sx, s2, ...» sq (обычно они пред- ставляют имена переменных и литералы) и пусть множество Ох» о2, от состоит из операторов, с помощью которых строятся выражения, содержащие элементы из S. Число операндов опера- тора называется степенью оператора; например, степень оператора умножения равна двум. Суффиксное выражение определяется следующим образом.' 1. Любой символ Sj является выражением. 2. Если Хд, х2, ..., хп являются выражениями, и оператор о, имеет степень п, то xtx2 ... xn Oj является выражением. 3. Корректными выражениями являются только такие выра- жения, которые определяются согласно пп. 1 и 2. Чтобы определить, корректно илн выражение, поставим в соот- ветствие каждому выражению ранг, определяемый следующим образом. 4. Ранг символа Sj равен 1. 5. Ранг оператора Oj равен 1 — п, где п — степень оператора oj. 6. Ранг произвольной последовательности символов и операто- ров равен сумме рангов отдельных символов и операторов. Пусть, например, множество символов и операторов состоит из однобуквенных переменных (а—z) и четырех арифметических опе- раторов соответственно. Определим функцию ранга г в виде г (sj) = 1 при 1 с j 26; г (4-) = г (-) = -1; г (*) = г (/) =• -1. 238
В частности, ранг выражения ab 4- cd —* г (а) + г (Ь) + г (+) + г (с) 4 г (d) + г (—) + г (*) = 1. В следующем подпункте будет приведена теорема, имеющая важное значение, так как ее можно использовать для определения корректности выражений. Перед тем как формулировать эту тео- рему, условимся о необходимости терминологии. Если z = хОу — строка, то х называется головой строки z. Кроме того, х — пра- вильная голова, если у не пусто (у не является пустой строкой Л) Ч 3-7.2.2. Преобразование инфиксных выражений в польскую запись Рассмотрим сначала алгоритм перевода бесскобочных инфиксных выражений в суффиксные польские. Позднее этот алго- ритм будет модифицирован для обработки и скобочных выражений. Отметим, что можио также использовать и полностью скобочные инфиксные выражения, не требующие никаких специальных пра- вил относительно приоритетов, кроме обычных правил для скобок. Но такие выражения неудобно использовать из-за большого числа требуемых скобок. Вычисление инфиксных и суффиксных польских выражений обсуждалось в предыдущем подпункте. Напомним, что в случае бесскобочного инфиксного выражения вычисление проводится таким образом, что оператор с наивысшим приоритетом выпол- няется раньше других. Если в выражении имеется несколько опе- раторов с одинаковым приоритетом, то первым из них выполняется крайний левый оператор (для ассоциативных слева операторов) или крайний правый (для ассоциативных справа операторов, таких как возведение в степень и отрицание). Конечно, при таком про- цессе вычисления необходимо повторно сканировать выражение, что приводит к снижению эффективности процесса. Такой же под- ход можно применить и к частично скобочным инфиксным выра- жениям. При сканировании вычисляемое в первую очередь под- выражение локализуется в результате нахождения первой правой скобки и последующего перемещения влево до тех пор, пока ие будет обнаружена соответствующая левая скобка. Далее это под- выражение может быть вычислено с использованием правила при- оритетов. Теорема Польская суффиксная (префиксная) формула кор- ректна тогда и только тогда, когда ранг этой формулы равен еди- нице, а ранг любой правильной головы польской формулы больше (меньше) или равен единице. Эта теорема имеет важное значение для компиляции инфиксных выражений, поскольку она позволяет обнаруживать некорректное 1 Здесь у называется хвостом строки, а если х —- не пустая строка, то у называется правильным хвостом. — Прим. ред. 239
выражение в польской записи (и, следовательно, соответствующее некорректное инфиксное выражение). Табл. 3-7.2 содержит ряд корректных и некорректных выражений. Рассмотрим далее машинное преобразование инфиксных выражений в польскую запись. Таблица 3-7.2 Пример корректных и некорректных выражений Инфиксное Суффиксное польское Ранг выра- жения К орректность а 4" *Ь ab * + 0 Некорректное а — b * с abc * — I Корректное ab 4' с abc 4- 2 Некорр ектиое (а 4- Ь) * (с — d) ab /4- cd — * I Корректное а 4~ b/d — abd/4-— 0 Некорректное Определим на метаязыке БНФ инфиксное выражение, которое может содержать однобуквенные переменные, константы в форме натуральных чисел и четыре бинарных арифметических оператора + , —. *, /: (идентификатор) : : — а | Ъ | с... | z (цифра) : : = 0 | I | 2 | ... | 9 (строка цифр) : : = (строка цифр) (цифра) ] (цифра) (первичный) : : = (идентификатор) | (строка цифр) | ((инфиксное выражение)) (терм) : : — (первичный) [ (терм) * (первичный) | (терм)/(первичный) (инфиксное выражение) : : = (терм) | (инфиксное выражение) ф-(терм) j (инфиксное выражение) —(терм) Приведенная грамматика определяет, что операторы * и / имеют одинаковый приоритет, который выше приоритета операто- ров + и —. С другой стороны, в бесскобочном суффиксном выражении его подвыражения имеют форму: (операнд 1) (операнд 2) (оператор), а суффиксное (или обратное) польское выражение может быть определено следующими правилами: (обратное польское) : : = (обратное польское) (обратное польское) (оператор) j (идентификатор) [ (строка цифр) (оператор) | * | / Например, следующие выражения эквивалентны: Инфиксное Ь a -j- Ъ а -р Ь 4- с а Ь * с а * (Ь 4- с) а/b * с Обратное польское Ь ab -р ab 4- с 4- abc * 4- abc 4- * ab/c * 240
Нетрудно разработать алгоритм преобразования инфиксного бесскобочного выражения в польское. Такое преобразование осно- вывается на приоритетах операторов и предполагает использова- ние стека. Польское выражение может храниться в виде некоторой выходной строки, используемой в дальнейшем при генерации объектного кода. Напомним, что при преобразовании инфиксного выражения в польское порядок всех переменных и констант не меняется. Однако порядок операторов в выходной строке меняется в зависимости от их относительных приоритетов, и именно по этой причине при таком преобразова- нии требуется использовать Таблица 3-7.3 стек. Присвоим сначала значения приоритетов четырем арифмети- ческим операторам, приведен- ным в табл. 3-7.3. Приоритет, соответствующий умножению и делению, выше приоритета сложения и вычитания. В таб- лице включены также значения приоритета для переменных (ограничиваемых для простоты одной буквой) и функция pai Значения приоритетов н рангов символов выражений Символ Приоритет Ранг- 1 2 —1 - 1 а, Ь, с, ... 3 1 И 0 — I. Причина этого будет объяс- нена ниже. Предположим, что первоначально стек содержит некоторый символ (символ [— в табл. 3-7.3), приоритет которого ниже всех других приоритетов в табл. 3-7.3. Алгоритм BASIC. Заданы входная строка INFIX, содержащая инфиксное выражение, приоритеты и ранги символов которого приведены в табл. 3-7.3, вектор S, используемый в качестве стека, и функция NEXTCHAR, при обращении к которой выбирается следующий символ из входной строки. Требуется преобразовать строку INFIX в форму выражения в обратной польской записи и запомнить его в векторе POLISH. Переменная RANK исполь- зуется для вычисления рангов польской строки. Специальный сим- вол, обозначенный добавляется в конце строки INFIX. 1. [Инициализация стека. ] Установить ТОР 1 и S [ТОР ] 2. [Начальная установка указателя выходной строки и пере- менной RANK. 1 Установить RANK ч- I ч- 0. 3. [Выбор первого входного символа. ] Установить NEXT ч- ч-NEXTCHAR (INFIX). 4. [Сканирование инфиксного выражения. 1 Повторять шаги 5 и 6 до тех пор, пока NEXT 5. [Удаление символов с большим или равным приоритетом из стека. ] Повторять до тех пор, пока 1 f (NEXT) f (S [TOP]): x Функция f (x) определяет здесь приоритет символа х. — Прим. ред. 241
установить i ч- i + 1, TEMP ч- POP (S, TOP) (присваивание зна- чения верхнего элемента переменной ТЕМП), POLISH [Пч- Ч- TEMP, RANK ч- RANK + г (TEMP), если RANK <1, то печатать сообщение 'НЕКОРРЕКТНОЕ' и закончить выполнение алгоритма. 6. [Проталкивание NEXT в стек и выбор следующего вход- ного символа. ] Вызвать процедуру PUSH (S, TOP, NEXT) и установить NEXT ч- NEXTCHAR (INFIX). 7. [Удаление оставшихся элементов из стека. ] Повторять до тех пор, пока S [ТОР ] =#установить i ч- j -R 1, TEMP ч~ ч- POP (S, TOP), POLISH [i] ч- TEMP, RANK *- RANK + г (TEMP), если RANK < 1, то печатать сообщение 'НЕКОР- РЕКТНОЕ' и закончить выполнение алгоритма. 8. [Выражение корректно?] Если RANK = 1, то печатать сообщение 'КОРРЕКТНОЕ'; д противном случае печатать сооб- щение 'НЕКОРРЕКТНОЕ'. Выход. Алгоритм очень прост. Первоначально в стек помещается специальный символ *1—'. Назначение данного символа состоит в том, чтобы, обнаружив его в конце строки INFIX, переместить в POLISH все элементы стека (за исключением ’|— ’) В основной части алгоритма значение приоритета очередного входного сим- вола NEXT сравнивается с приоритетом верхнего элемента стека. Если приоритет символа NEXT больше приоритета верхнего эле- мента стека, то символ NEXT помещается в стек, а затем выбира- ется следующий входной символ. С другой стороны, если прио- ритет символа NEXT меньше или равен приоритету верхнего эле- мента стека, то этот элемент удаляется из стека и помещается в строку POLISH, после чего опять сравниваются приоритеты, символа NEXT и нового верхнего элемента стека и т. д. Всякий раз при переписи символа в POLISH модифицируется ранг поль- ской строки. Отметим, что поскольку переменная имеет наивысший приори- тет, она будет помещаться сначала в стек. При выборе очередного символа переменная будет вытолкнута из стека и помещена в POLISH (так как в корректном инфиксном выражении недопу- стимы две последовательные переменные). Фактически очень легко изменить алгоритм так, чтобы сначала проверять приоритет символа NEXT. Если он равен трем, то это значит, что символ NEXT — переменная и может быть переписан сразу в POLISH (минуя стек). Однако мы не делаем этого ради общности, которая станет важна в случае более сложных инфиксных" выражений по сравнению с теми, которые" мы сейчас рассматриваем. Входной символ с приоритетом, большим приоритета верхнего элемента стека, является оператором (или переменной), помеща- емым в стек. Это и понятно, так как операция, соответствующая такому оператору, должна выполняться раньше любых других операций, соответствующих остальным операторам в стеке. Дан- ный факт находит отражение в том, что последний оператор, поме- 242
щенный в стек, будет первым, переписанным в строку POLISH. От- метим, что если приоритет входного оператора равен приоритету оператора на верху стека, то оператор из стека заносится в строку POLISH. В результате этого обеспечивается требование, согласно которому в выражении, содержащем операторы с одинаковым приоритетом, в первую очередь выполняется крайний левый опе- ратор. Поэтому приведенный выше алгоритм будет преобразовы- вать выражение a -j- b 4 с к виду ab L с у , а не abc 4 |-. Польская строка ab 4 с 4- соответствует инфиксному выражению (а 4 Ь) 4 с, а строка abc -[- 4 соответствует а 4 (Ь 4 с). Про- цесс изменения содержимого стека и польской строки POLISH для инфиксного выражения а 4 Ь*с — d/e*h приведен в табл. 3-7.4. Рассмотрим теперь задачу преобразования инфиксного выра- жения, содержащего подвыражения в скобках. При написании выражения, содержащего скобки, программист обычно не исполь- зует полностью скобочную форму. Интуитивно ясно, что при по- явлении в инфиксном выражении левой скобки ее следует поме- стить в стек независимо от его текущего содержимого. Однако, если она уже находится в стеке, то при появлении в инфиксном вы- ражении соответствующей правой скобки левую скобку следует удалить из стека и больше не принимать во внимание, при этом •правая скобка тоже игнорируется. Левую скобку можно поместить в стек путем присваивания ей приоритета, превосходящего при- оритет любого другого оператора или символа. Находясь в стеке, левая скобка должна иметь другой приоритет (так называемый стековый приоритет), кото- рый меньше приоритета любого другого оператора. Мы можем избавиться от присутствия левой скобки Таблица 3-7.5 Преобразованная таблица значений приоритетов и рангов арифметических операторов Таблица 3-7.4 Перевод инфиксного выражения а4Ь * с— —d/e*h в польскую запись | Сканируемый символ Содержимое стека (крайний правый символ — верхний элемент стека) Обратная польская запись № & 1— а На ~г- L4 а 1 ь н+ь а 1 * н+* ab 2 с i-+*C ab 2 — 1 abc * -|- 1 d 1—<1 abc * 4 1 t 1—/ abc*-| d 2 е I /е abc * -j-d 2 * 1 * abc * -j-de/ 2 h | * h abc * -j-de/ 2 Н Н abc # -j-de/b * — i Переменные 7 8 1 ( 9 0 — ) о - - 243
в стеке, проверяя наличие в инфиксном выражении правой скобки. Правая скобка никогда ие включается в стек. По существу мы можем модифицировать предыдущий алгоритм таким образом, чтобы левые и правые скобки выполняли ту же роль, что н исполь- зованный ранее специальный символ . Исходная таблица при- оритетов (табл. 3-7.3) может быть преобразована для получения сравнительного (входного) и стекового приоритетов каждого опе- ратора и операнда. Кроме избавления от символа это поз- воляет сделать алгоритм более общим без значительного усложне- ния при введении операторов отношения, логических операторов и операторов с одним и тремя операндами. Табл. 3-7.5 является преобразованной таблицей, дополненной символами скобок. В ней каждый символ имеет сравнительный и стековый приори- теты, за исключением правой скобки, не имеющей стекового при- оритета, так как она никогда не помещается в стек.Табл. 3-7.5 содержит также оператор возведения в степень, обозначенный символом f . Все арифметические операторы, за исключением возведения в степень, имеют сравнительный приоритет меньше стекового. Это сохраняет порядок слева направо при обработке операторов с одинаковым приоритетом в выражении. В матема- тике оператор возведения в степень является ассоциативным справа. Поэтому выражение a f b f с эквивалентно скобочному выражению a f (b | с), а не (a j b) | с. Алгоритм преобразования инфиксного выражения, содержа- щего скобки, в обратную польскую запись во многом сходен с пре- дыдущим алгоритмом. Первоначально в стек помещается левая скобка, а инфиксное выражение дополняется справа правой скоб- кой. Новый алгоритм формулируется следующим образом. Алгоритм REVPOL. Задана входная строка INFIX, содержащая инфиксное выражение и дополненная справа символом * )'. Символы этой строки имеют приоритеты, приведенные в табл. 3-7.5. Заданы также вектор S, используемый в качестве стека, и функция NEXTCHAR, при обращении к которой выбирается следующий символ аргумента. Требуется преобразовать строку INFIX в обратное польское выражение н запомнить его в векторе POLISH. Переменная RANK используется для вычисления рангов польской записи. 1. [Инициализация стека. ] Установить ТОР ч- 1 и S [TOP J ч- 2. [Начальная установка указателя выходной строки и пере- менной RANK.] Установить RANK<-i4-0. 3. [Выбор первого входного символа. ] Установить NEXT ч~ Ч- NEXTCHAR (INFIX). 4. [Сканирование инфиксного выражения.] Повторять шаги от 5 до 7 до тех пор, пока NEXT пустая строка. 5. [Удаление из стека символов с большим или равным прио- ритетом. ] Повторять до тех пор, пока f (NEXT) g (S [ТОР]): установить TEMP ч- POP (S, TOP), если f (NEXT) <g (TEMP), 244
то установить i ч- i 4- 1, POLISH [j] ч- TEMP, RANK ч- ч-RANK + г(ТЕМР); если RANK < 1, то печатать сообщение 'НЕКОРРЕКТНОЕ' и закончить выполнение алгоритма; в про- тивном случае перейти к шагу 7 (NEXT имеет значение правой скобки). 6. [Проталкивание в стек.] Вызвать процедуру PUSH (S, TOP, NEXT). 7. [Выбор следующего входного символа. ] Установить NEXT ч- NEXTCHAR (INFIX). 8. [Выражение корректно?] Если ТОР О или RANK 1, то печатать сообщение 'НЕКОРРЕКТНОЕ'; в противном случае напечатать 'КОРРЕКТНОЕ'. Выход. Процесс изменения содержимого стека н польской записи POLISH для инфиксного выражения (а + b | с f d)*(e + f/d) отображен в табл. 3-7.6. Читателю предлагается проследить алгоритм для некорректного инфиксного выражения ((а*х + + Ь) + с)). Таблица 3-7.6 Перевод инфиксной строки (a-]-b | с f d) * (e-J-f/d) в польскую запись Скани- руемый анак Содержимое стека (крайний правый сим- вол — верхний элемент стека) Обратная польская ааиись йа & ( а Ъ I I d ) * ( е Г / d ) ) ((! -1 t d (*(е (*(+ (*(-Н (*(+/ (* (+/d а а ab ab abc abc abed f t + abed f f + abed f f -|- abed f f -1- abed f f -j-e abed j j Ц-е abed f f -|-ef abed j f -pef abed f f -{-eId/4- abed f f -j-efd/+* Функции приоритетов можно расширить для обработки опера- торов отношения, условных операторов, безусловных переходов (go to), индексированных переменных и многих других средств, 245
имеющихся в современных языках программирования. Такое рас- ширение предлагается в некоторых упражнениях в конце данного пункта. До сих пор мы рассматривали преобразование инфиксных вы- ражений в форму обратной польской записи. Такое преобразование необходимо потому, что обратная польская запись может быть тран- слирована в объективный код путем ее однократного линейного сканирования. Задача генерации объектного кода на основе польской записи будет рассмотрена в следующем подпункте. В настоящем подпункте не будет обсуждаться задача преобра- зования инфиксного выражения в префиксную польскую запись. Для этой цели довольно легко построить простой алгоритм, ос- нованный на сканировании инфиксного выражения справа налево. Во многих случаях инфиксная строка недоступна целиком, а предъявляется по одному символу слева направо (поскольку имен- но так пишутся программы). Следовательно, любой практиче- ский алгоритм преобразования инфиксного выражения в префикс- ное должен базироваться на сканировании инфиксного выражения слева направо. Однако для упрощения такого алгоритма можно использовать два стека вместо одного. Данная задача предлагается в качестве упражнения. 3-7.2.3. Преобразование выражений в польской записи в код Ниже будет предполагаться, что желаемый объектный код является последовательностью команд на языке ассемблера. Не углубляясь в описание какой-либо гипотетической машины, предположим, что вычислительная машина, исполняющая команды в объектном коде, сгенерированном в процессе компиляции, пред- ставляет собой одноадресную машину с одним накапливающим регистром н основной памятью с последовательной организацией слов. Команды программы выполняются последовательно, если нет команд перехода. Ниже приведен ряд команд, имеющихся в упомянутом языке ассемблера. LOD а — загрузка значения переменной а в регистр без изменения содержимого а. STO а — запоминание содержимого регистра в слове памяти, обозначенном а. Содержимое регистра при этом не меняется. ADD а — сложение значения переменной а с содержимым регистра; результат остается в регистре. Содержимое а не меня- ется. SUB а — вычитание значения переменной а из содержимого регистра; результат запоминается в регистре. Содержимое а не меняется. MUL а — умножение значения переменной а на содержимое регистра; результат запоминается в регистре. Содержимое а не меняется. 246
DIV a — деление содержимого регистра на значение перемен- ной а; результат помещается в регистр. Содержимое а не меня- ется. JMP b — команда безусловного перехода. Следующая коман- да, которая должна быть выполнена, находится по адресу (в слове), обозначенному меткой Ь. BRN b — команда условного перехода. Слово, содержащее следующую команду, определяется меткой Ь, если содержимое регистра отрицательно; в противном случае должна выполняться команда, следующая за BRN. Для наших целей достаточно перечисленных команд. В пре- дыдущем подпункте был приведен простой пример вычисления польской записи. Рассмотрим сначала алгоритм «грубого» пре- образования польских выражений, образуемых четырьмя основ- ными арифметическими операторами и однобуквенными перемен- ными, на язык ассемблера. Предположим, что основным арифме- тическим операторам соответствуют следующие'фрагменты кода: X + у (ху +) LOD X ADD у STO Tt х — у (ху —) LOD х SUB у STO Tj х*у (ху *) LOD х MUL у STO Т, х/у (ху/) LOD х DIV у STOTj На языке ассемблера соответственно каждому оператору ге- нерируются три команды. Третья команда, в каждой группе имеет вид STO Th где Tj - адрес ячейки (слова) памяти, в которой должен запоминаться результат. Эти адреса формируются с по- мощью алгоритма преобразования польского выражения на язык ассемблера, приведенного ниже. Алгоритм CODE. Задан вектор POLISH, состоящий из п символов, представляющих выражение в обратной польской за- писи (образуемое четырьмя основными арифметическими опера- торами н однобуквеннымн переменными), эквивалентное некоторо- му инфиксному выражению. Требуется транслировать строку POLISH на язык ассемблера, как было указано выше. Алгоритм, как обычно, использует стек S. Переменная j является индексом символа в строке POLISH. 1. [Инициализация.] Установить ТОР ч-1ч-О и j ч- 1. 2. [Выбор следующего символа из польской записи.] Уста- новить NEXT ч-POLISH [j]. 247
3. [NEXT — оператор сложения?] Если NEXT = * + ’, то установить OPCODE ’ADD # * и перейти к шагу 10. 4. [NEXT — оператор вычитания?] Если NEXT = '—’, то установить OPCODE ’SUB 'i ’ и перейти к шагу 10. 5. [NEXT — оператор умножения?] Если NEXT =то установить OPCODE ’MUL# ’ и перейти к шагу 10. 6. [NEXT — оператор деления? ] Если NEXT = то уста- новить OPCODE ’DIV 6 ’ и перейти к шагу 10. 7. [Включение в стек переменной NEXT. 1 Вызвать процедуру PUSH (S, TOP, NEXT). 8. Приращение j. ] Установить j ч- j + 1. 9. [Вся строка просмотрена? ] Если j sc п, то перейти к шагу 2; в противном случае закончить выполнение алгоритма. 10. [В стеке два операнда?] Если ТОР < 2, то печатать сообщение ’НЕКОРРЕКТНОЕ ВЫРАЖЕНИЕ’ н закончить вы- полнение алгоритма. 11. [Исключение двух операндов из стека.] Установить RIGHT 4- POP (S, ТОР) и LEFT ч-POP (S, ТОР). 12. [Вывод команды загрузки.] Печатать ’LOD # ’ О LEFT. 13. [Вывод арифметической команды.] Печатать OPCODE О RIGHT. 14. [Формирование промежуточного индекса памяти. ] Уста- новить 1ч—1 + 1 и TEMP ч— ’Т* О 1- 15. [Вывод промежуточной команды запоминания.! Печатать 'STO ’ О TEMP. 16. [Включение в стек промежуточного результата. ] Вызвать процедуру PUSH (S, TOP, TEMP), перейти к шагу 8. Следует отметить один очень важный момент. Обычно перемен- ные и константы (а также и операторы) могут обозначаться более чем одним символом. Поэтому вместо хранения в строке POLISH имен переменных, значений констант и т. д. используют целые указатели (определяющие индекс переменной, константы или оператора) вектора, содержащего эти имена и константы. В этом случае все элементы POLISH имеют одинаковый размер. Сим- вол ’О’ в шагах от 12 до 15 обозначает операцию конкатенации. Метка Tj на практике является указателем вектора, содержащего все сформированные имена переменных при запоминании проме- жуточных результатов. Алгоритм производит одно линейное сканирование строки POLISH, отыскивая какой-либо оператор. Как только оператор найден, из стека исключаются два символа, выводится на печать указанная операция, н промежуточный результат ее выполнения помещается в стек. Этот процесс повторяется до тех пор, пока в POLISH не останется ни одного оператора. Если в каком- нибудь шаге обработки оператора в стеке не окажется двух опе- рандов, то печатается сообщение об ошибке. 248
Процесс трансляции обратной польской записи abc* 4- de/h* — отображен в табл. 3-7.7. Таблица 3-7.7 Код, генерируемый алгоритмом CODE для польской записи abc*4-de/h*— Сканируемый знак Содержимое стека (крайний правый символ — верхний элемент стека) Левый операнд Правый операнд Сгенериро- ванный код а а ь ab abc * аТг ь С LOD b MUL с STO Tt + Т2 а Т1 LOD а ADD Tt STO Ts d T2d е T2de • Т2Т3 d е LOD <1 DIV e STO T3 h T2T„h * Т..1, т3 h LOD T3 MUL h STO T4 Тв Т, Т4 LOD T2 SUB T4 STO TB Генерируемая программа на языке ассемблера приведена в крайнем правом столбце таблицы. Полученный код имеет ряд очевидных недостатков. Во-первых, в выходной последователь- ности команд имеется избыточная пара STO Т3 LOD Т3 Во вторых не используется свойство коммутативности опера- торов сложения и умножения. Это видно на примере последова- тельности команд, являющейся результатом трансляции под- выражения а + Ь*с, а именно, LOD Ъ MUL с STO т, LOD а ADD Т, STO Т2 249
которую, очевидно, можно заменить эквивалентной последова- тельностью LOD b MUL с ADD а STO Тх так как правый операнд в операции сложения уже имеется в ре- гистре, а значения а + Ь*с и Ь*с -Ь а совпадают. Во второй последовательности с успехом используется свойство коммута- тивности операции сложения. Наконец, при трансляции не делалось попытки сэкономить число рабочих ячеек для хранения промежуточных результатов. В самом деле, если вычисляется ш таких результатов, то исполь- зуется такое же число рабочих переменных. В то же время после- довательность команд LOD b MUL с STO тх LOD а ADD Тх STO Т2 очевидно, можно заменить такой же последовательностью, за исключением последней команды, вместо которой используется STO Tv Число требуемых рабочих переменных можно легко уменьшить путем выполнения следующей простой проверки. Перед генера- цией команд для арифметического оператора проводится проверка содержимого левого и правого операндов рассматриваемого оператора. Для каждого операнда', соответствующего промежу- точной переменной счетчик, i рабочих переменных уменьшается на единицу. Избыточные пары команд запоминания и загрузки и необяза- тельное промежуточное запоминание н последующая перезагрузка правого операнда коммутативных операторов могут быть исклю- чены следующим образом. Вместо того чтобы всегда запоминать частный результат в рабочей памяти, как это делается в шаге 15 алгоритма CODE, можно задерживать генерацию соответствующей команды до тех пор, пока это не станет совершенно необходимым. При этом шаг 15 в предыдущем алгоритме изменяется так, чтобы вместо генерации команды запоминания в стек заносился маркер промежуточного результата Если этот маркер не заталкива- ется в стек глубже верхнего элемента, то промежуточный резуль- тат не требуется сохранять при генерации команды запоминания. Построение алгоритма с учетом этих замечаний предлагается в ка- честве упражнения. 250
Упражнения к п. 3-7.2 1. Напишите рекурсивную стандартную подпрограмму определе- ния того, является ли данное выражение корректным выражением в обратной польской записи. Допустите при этом, что выражение может содержать одно- буквенные переменные и четыре основных арифметических оператора. 2. До сих пор мы рассматривали лишь бинарный оператор вычитания. В математике же существует три типа употребления знака минус, а именно: для обозначения бинарного оператора вычитания, унарного оператора минус (как, например, —х) и знака константы (например, х 4- (—5)). Сформируйте таблицу приоритетов для обработки операторов присваивания, содержащих унарный оператор минус (обозначаемый —) и оператор присваивания (обозначаемый *-). (Подсказка: Различать разные смыслы использования минуса нетрудно. Сим- вол минуса означает бинарный оператор, если он не стоит в начале выражения или после левой скобки. Минус в начале выражения илн после левой скобки представляет унарный оператор, если за ним не следует цифра или десятичная точка). 3. Рассмотрите выражения, которые могут содержать операторы отноше- ния или логические операторы. Сформируйте функции приоритета, необходимые для преобразования таких выражений в форму обратной польской записи. 4. Как упоминалось в тексте, в некоторых приложениях сканирование ин- фиксного выражения ограничено выбором символов по одному в порядке слева направо. При преобразовании инфиксного выражения в префиксное требуется два стека вместо одного (как в случае преобразования инфиксного выражения в суффиксное), а именно, стек операторов и стек операндов (для временного хранения промежуточных операндов). Напомним, что согласно п. 3-7.2.1 все переменные и константы при преобразовании инфиксного выражения в префикс- ную форму сохраняют свой относительный порядок. Порядок операторов, однако, изменяется согласно их сравнительным приоритетам, а именно для этой цели служит стек операторов. Стек операндов используется для временного хранения промежуточных операндов таким образом, чтобы при обнаружении соответствую- щего им оператора его можно было поместить перед операндами. Составьте алго- ритм трансляции, предполагая, что инфиксные выражения могут содержать однобуквеиные переменные и четыре арифметических оператора. 5. Запрограммируйте алгоритм CODE и используйте при этом подходящие данные для его проверки. 6. Основываясь на результатах обсуждения алгоритма CODE, сформули- руйте алгоритм для генерации более эффективного кода, учитывая свойство коммутативности операторов * и 7. Запрограммируйте алгоритм из упр. 6 и используйте при этом подхо- дящие данные дли его проверки. 8. Модифицируйте алгоритм из упр. 6, включив в него оператор присваи- вания. 9, Модифицируйте алгоритм из упр. 8 так, чтобы генерировался код для шести операторов отношения. 10. Модифицируйте и запрограммируйте алгоритм из упр. 6 так, чтобы он обрабатывал также и унарный оператор минус. 11. Опишите, как можно представить условные и безусловные операторы в виде обратной польской записи. 3-7.3. Стековые машины В предыдущем подпункте мы обсуждали генерацию кода для выражений в обратной польской записи применительно к быстрым регистровым машинам. Одна из главных проблем, связанных с использованием машин, имеющих весьма ограничен- ное число регистров, заключается в способе управления памятью 251
для промежуточных результатов. В частности, мы должны по- стоянно помнить о генерации бесполезной последовательности команд запоминания и загрузки. В данном пункте мы покажем, как наличие простых стековых операций POP и PUSH может по- высить эффективность процесса генерации кода иа.основе обратной польской записи. Многие из машин, появляющихся иа рынке, содержат в своей конструкции аппаратные стеки или средства работы со стеками. Такими являются машины PDP-11 и Burroughs 5000*. Эти ма- шины особенно хорошо приспособлены для стековой организации локальных переменных и параметров, сопутствующих вызову про- цедур в языках с блочной структурой. Аспекты реализации таких процедур обсуждались в п. 3-7.1, посвященном рекурсии. ЭВМ Burroughs 5000 и их последующие модификации имеют команды с нулевой адресацией, которые очень удобны для генерации кода иа основе суффиксной польской записи выражений. Вместо опи- сания конкретных машин мы представим гипотетическую стеко- вую машину, которая даст возможность проиллюстрировать важ- ные понятия, связанные с генерацией кода на основе обратной польской записи арифметического выражения. В этой машине имеются команды, мнемоника которых имеет следующий вид. 1. PUSH (имя)— загрузка из памяти в стек. По этой команде содержимое операнда": в ячейке памятнее ^именем (имя) поме- щается в стек. 2. POP (имя) — запоминание верхнего элемента стека в па- мяти. Содержимое верхнего элемента исключается из стека и запоминается в ячейке памятнее именем (имя). 3. ADD, SUB, миьЛ DIV —^арифметические операции сло- жения, вычитания, умножения и деления соответственно. Заданная операция применяется к верхним двум элементам стека, а результат записывается во второй после верхнего элемен- та стека. Затем верхний элемент стека исключается. Поэтому ADD означает: установить S [ТОР — 1] *- S [ТОР — 1] S [ТОР] и ТОР*- ТОР — 1. SUB означает: установить S [ТОР — 1 ] *- S [ТОР — 1 ] — S [ТОР ] н ТОР*- ТОР — 1. MUL означает: установить S [ТОР— 1]*- S [ТОР— 1] * S [TOPJ и ТОР^- ТОР — 1. DIV означает: установить S [ТОР—1]*-S[TOP — 1]/S.[TOP] и ТОР *- ТОР — 1. В качестве примера рассмотрим команды стековой машины» сгенерированные для исходного оператора языка ПЛ/1, А = В*С + А Этот оператор может быть преобразован в форму обратной поль- ской записи 'АВС* А * Средства работы со стеками имеются и в целом ряде микро-ЭВМ. — Прим. ред. 252.
Рис. 3-7.10- Выполнение последовательности команд стековой машины, соответ- ствующих записи 'АВС* А + — * ииши Стек руст PUSH В PUSH С MUL. PUSH A ADD POP А "(стек густ> Ниже приведено множество команд стековой машины (слева) н множество команд регистровой машины (справа) для вычисления польской записи ’АВС«А + =’, осуществляемого в соответствии с алгоритмом CODE из п. 3-7.2: PUSH в PUSH с MUL PUSH * ADD POP А LOD В MUL С STO TI LOD Ti ADD A STO T2 STO A Рис. 3-7.10 иллюстрирует процесс выполнения дайной после- довательности команд в стековой машине. Множество команд регистровой машины можно уменьшить до LOD В MUL С ADD А STO А если исключить последовательность команд запоминания и за- грузки переменной Тх н ненужное запоминание в Т2. Однако ал- горитм генерации такого кода для регистровой машины довольно сложен по сравнению с алгоритмом генерации кода стековой ма- шины. Рассмотрим алгоритм генерации кода в стековой машине. Алгоритм STACK-CODE. Задана строка POLISH, состоя- щая из символов, представляющих выражение в обратной поль- ской записи, формируемое из четырех основных арифметических операторов и однобуквенных переменных. Требуется трансли- ровать строку POLISH в команды на языке ассемблера стековой машины. OPERATION является четырехэлементным вектором с элементами ’ADD’, ’SUB’, ’MUL’, ’DIV’ при значениях индекса 1 До 4 соответственно. LETTERS — строка букв ABCDEFGHIJKLMNOPQRSTUVWXYZ’. IND и i — проме- жуточные переменные. 1. [Начальная установка.] Установить POP—SYMB ч- SUB (POLISH, 1, 1) и 1ч-2. 253
2. (Сканирование строки. ] Повторять шаги от 3 до 5 до тех пор, пока i LENGTH (POLISH). 3. [Выбор следующего символа польской записи. ] Установить NEXT ч- SUB (POLISH, i, 1). 4. [NEXT является оператором?] Установить IND ч- ч—- INDEX (’-г--*/*, NEXT). Если IND 0, то печатать OPERATION [IND], установить i ч-i + 1 и перейти к шагу 2. 5. [NEXT является операндом?] Если INDEX (LETTERS, NEXT) то печатать 'PUSH' О NEXT и установить i ч- ч-i + 1; в противном случае, если NEXT = ’ = * н INDEX (LETTERS, POP—SYMB) ф 0, то печатать ’POP’ О POP.SYMB, установить i ч-i + 1; в противном случае печатать сообщение ’НЕСУЩЕСТВУЮЩШ'1 СИМВОЛ’ и закончить выполнение алгоритма. 6. [Конец. ] Выход. В алгоритме STACK—CODE обратная польская запись пред- ставляется в виде символьной строки, а не вектора, как в алго- ритме CODE в п. 3-7.2. Различие в структурах данных для POLISH приводит к существенному различию алгоритмов с то- чки зрения используемых операций. Можно использовать любой из этих алгоритмов, в зависимости от языка программирования, на котором записывается алгоритм (например, ФОРТРАНа или ПЛ/1). В шаге 1 алгоритма STACK—CODE сохраняется первый сим- вол в предположении о том, что он содержит имя, которому дол- жно быть присвоено значение арифметического выражения. Если это имя не сохранить, а вместо этого в стек поместить его значение (как в случае имен других переменных), то сгенерирован- ная команда PUSH является излишней. В шаге 3 для анализа выбирается следующий символ строки POLISH. В шаге 4, если NEXT является арифметическим оператором, формируется соот- ветствующая арифметическая команда. Если значением NEXT является имя переменной, то это имя определяется как операнд команды PUSH. Наконец, если NEXT — оператор присваива- ния, то первый символ выражения POLISH определяется как операнд команды POP. Следует обратить внимание на тот важный факт, что алгоритм STACK___.CODE, будучи весьма простым, не генерирует лишние команды (такие, как запоминание и загрузка промежуточных данных). Из прослеживания работы алгоритма STACK—CODE становится ясно, что стековые операции PUSH и POP отлично приспособлены для процесса генерации объектного кода из об- ратной польской записи. 3-8. ОЧЕРЕДИ Существует еще один важный подкласс списков, в ко- тором допускается исключение элементов из одного конца списка и включение в другой конец. Информация в таком списке обраба- 254
Голова Хвост очереди. очереди Включение Рис. 3-8.1. Представление очереди тывается в порядке ее поступления, т. е. по принципу «первым пришел—первым вышел» (FIFO) или «первым пришел—первым обслуживаешься» (FCFS). Такой тип списка называют очередью. На рис. 3-8.1 представлена очередь, иллюстрирующая включение в нее элементов справа и исключение элементов слева. Операцию модификации очереди можно ограничить рассмотрением ее край- него элемента. Если такого ограничения нет, то можно выбирать любой элемент списка. Известным традиционным примером оче- реди является очередь к кассиру в большом магазине самообслу- живания. Первый человек в очереди (обычно) первым и обслужи- вается. Другой, возможно более уместный, пример очереди может быть обнаружен в вычислительной системе с разделением времени, с которой одновременно работает множество пользователей. По- скольку такая система обычно имеет единственный центральный обрабатывающий блок (называемый процессором} и одну основную память, то эти ресурсы должны разделяться среди пользователей путем выделения короткого интервала времени на выполнение про- граммы одного пользователя, за которым следует выполнение про- граммы другого пользователя и т. д. до тех пор, пока не будет вновь выполняться программа первого пользователя. Программы пользователей, ожидающие своего выполнения, образуют очередь ожидания. Управление такой очередью необязательно должно основываться на принципе «первым пришел—первым вышел», а может использовать некоторую сложную приоритетную схему, учитывающую такие факторы, как используемый компилятор, требуемое время выполнения, желаемое число строк, выводимых на печать, н т. д. Результирующая очередь, называемая иногда приоритетной очередью, является предметом обсуждения в п. 3-9. Еще одним примером очереди является очередь автомобилей, ожидающих открытия проезда в каком-либо фиксированном на- правлении на уличном перекрестке. Исключение автомобиля соответствует проезду первого автомобиля из очереди через пере- кресток, а включение — присоединению нового автомобиля в ко- нец очереди, ожидающей проезда через перекресток. Этот кон- кретный пример рассматривается в упражнении к п. 3-9. Опншем теперь алгоритмы включения и исключения элемента из очереди. Предполагается, что вектор для размещения очереди содержит достаточно большое число элементов, чтобы можно было 255
Исключение । । Включение ГЯ КЖЕГТЯГ Iр f г Рис. 3-8.2. Представление очереди в виде вектора имитировать свойство непостоянства длины очереди. В п. 4-3.1 будет дано другое представление очереди, которое в действитель- ности изменяется по длине. Векторное представление очереди требует использования указателей {иг, определяющих позиции ее первого и последнего элементов соответственно. Такая схема представления очереди приведена на рис. 3-8.2. Алгоритм включе- ния элемента в очередь имеет следующий вид. Алгоритм QINSERT. При заданных значениях указателей f и г, определяющих первый (головной) и последний (хвостовой) элементы очереди соответственно, векторе Q, содержащем и элементов, и элементе у алгоритм включает у в очередь вслед за последним ее элементом. Первоначально указателям Гиг присвоены нулевые значения, 1. [Переполнение?] Если г > п, то вывести сообщение о переполнении очереди и закончить выполнение алгоритма. 2. [Приращение указателя хвостового элемента. ] Установить г ♦— г + 1. 3. [Включение элемента.] Установить Q [г] <— у- 4. [Правильно ли установлен указатель головного элемента? ] Если Г = 0, то установить f ч- 1. Выход. Следующий алгоритм исключает элемент из очереди. Алгоритм QDELETE. Заданы те же самые исходные условия, что в алгоритме QINSERT, за исключением того, что значение исключаемого элемента необходимо присвоить переменной у. 1. [Очередь пуста?] Если Г = 0, то вывести сообщение о пу- стой очереди и закончить выполнение алгоритма. 2. [Исключение элемента.] Установить у <— Q [Г]. 3. [Очередь стала пустой? ] Если f = г, то установить f <—• <— г <— 0 и закончить выполнение алгоритма. 4. [Увеличение указателя головного элемента.] Установить Г <— Г + 1 и закончить выполнение алгоритма. Эти два алгоритма могут оказаться очень неэффективными с точки зрения затрат памяти, если указатель головного элемента никогда не достигнет значения указателя хвостового элемента. В самом деле, при этом для размещения элементов может потре- боваться неопределенно большой объем памяти. Поэтому такой метод выполнения операций над очередью следует использовать только в тех случаях, когда очередь в некоторые периоды времени становится пустой. 256
I L ' 1 (Переполнение) Рис. 3-8.3. «Процесс выполнения операций над простой очередью Рис. 3-8.4. Векторное представление цик- лической очереди Рассмотрим пример, когда очередь может содержать не более четырех элементов. Первоначально очередь пуста. Требуется включить в нее символы ’А’, ’В’ и ’С’, затем исключить ’А* и ’В’ и включить ’D’ и ’Е*. Процесс изменения содержимого очереди отображен на рис. 3-8.3. Отметим, что при попытке вклю- чить символ ’Е’ происходит переполнение очереди, несмотря на то, что первые две позиции при этом не заняты. Ниже будет рассмотрен другой вариант представления очереди, который позволяет преодолеть указанные трудности. Этот более приемлемый метод представления очереди, предотвращающий чрезмерные затраты памяти, состоит в том, чтобы расположить элементы Q [1 ], Q [2], ..., Q [п] циклически так, чтобы за Q [п] следовал Q [1 ]. Графически это представлено на рис. 3-8.4. Теперь могут быть сформулированы алгоритмы включения н исключения для циклической очереди. Алгоритм CQINSERT. Заданы значения f и г, определяющие начало и конец очереди соответственно, вектор Q, содержащий п элементов, расположенных, как описано выше, циклическим спо- собом, и элемент у. Требуется включить элемент у в очередь вслед за хвостовым элементом. Первоначально указателям очереди присвоены нулевые значения. 2S7 9 Трамбле Ж-. Соренсон П.
1. [Восстановить указатель конца очереди?] Если г = п, то установить г <— I; в противном случае установить г <— г Ь 1. 2. [Переполнение? ] Если f = г, то вывести сообщение о пере- полнении очереди и закончить выполнение алгоритма. 3. [Включение элемента.] Установить Q [г] «—у. 4. [Правильно ли установлен указатель начала очереди?] Если f = 0, то установить f «— 1. Выход. Алгоритм CQDELETE. Заданы те же самые исходные данные, что и в алгоритме включения CQINSERT, за исключением того, что теперь необходимо значение исключаемого элемента при- своить переменной у. 1. [Очередь пуста?] Если f = 0, то вывести сообщение о пу- стой очереди и закончить выполнение алгоритма. 2. [Исключение элемента.] Установить у <— Q [f]. 3. [Очередь стала пустой? ] Если f = г, то установить f <— <—г <—0 и закончить выполнение алгоритма. 4. [Приращение указателя начала очереди]. Если f = п, то установить f <— 1; в противном случае f <— f +• 1. Выход. Рассмотрим пример циклической очереди, содержащей макси- мально четыре элемента. Требуется выполнить ряд операций вклю- чения и исключения над первоначально пустой очередью. Про- цесс изменения содержимого этой очереди, которая для нагляд- 1>ис. 3-8.5. Последовательность выполнения операций над циклической очередью 258
Головной Хвостовой элемент элемент Исключение^- [vTvTvbHVlS/l ^—Включение Включение -^—Исключение Рис. 3-8.6» Дек нести представлена в нециклической ~ форме, отображен на рис. 3-8.5. Таким образом, была описана простая очередь, основанная на принципе «первым пришел—первым вышел» в том смысле, что при каждом исключении удаляется наиболее старый из имеющихся в структуре элементов. Еще одним типом очереди является дек (очередь с двумя кон- цами), представляющий линейный список, в котором включения и исключения элементов выполняются с любого конца струк- туры. Такая структура представлена на рис. 3-8.6. Ясно, что дек является более общей структурой, нежели стек или очередь. Существуют два варианта дека, а именно: дек с ограниченным вхо- дом и дек с ограниченным выходом. Дек с ограниченным входом допускает включение элементов только на одном конце, в то время как дек с ограниченным выходом допускает исключения только с одного конца. Упражнения к п. 3-8 1. Составьте алгоритм включения в дек с ограниченным входом. 2. Составьте алгоритм исключения из дека с ограниченным входом. 3. Выполните упр. 1 и 2 применительно к деку с ограниченным выходом. 4. Некая фирма Саскачеван Бакалея (SASKGROC) рассматривает вопрос о внедрении новых кассовых аппаратов в одном из своих магазинов. В настоя- щее время в этом магазине имеется три кассовых аппарата, но поток покупате- лей возрос до такой степени, что необходим еще один аппарат. Чтобы определить, должен ли добавочный аппарат быть обычным или специальным (т. е. предназна- ченным для выдачи чеков иа восемь или менее предметов покупки), требуется промоделировать поток покупателей через кассы. Первоначально модель кассовой системы содержит один специальный и три обычных кассовых аппарата. Предполагается, что все покупатели с восемью или менее предметами покупки проходят через специальный аппарат. Покупа- тели, имеющие более восьми предметов, идут к стандартному кассовому аппа- рату с наименьшей очередью ожидания. Поступление покупателей в кассовую систему имитируется на основе гене- рации приращения времени прихода следующего покупателя. Значения этих приращений мы выбираем случайными в диапазоне [0,360] с. (При этом 0 интер- претируется как одновременное появление двух покупателей.) Число предметов у каждого покупателя также может имитироваться путем выбора целого слу- чайного числа в диапазоне от 1 до 40. Время (с момента, когда кассир начинает «отбивать» стоимость бакалеи), затрачиваемое на обслуживание покупателя, можно оценить исходя из среднего интервала, равного 80 с на один предмет (сюда входит время подсчета стоимости плюс время упаковки). При организации моделирования мы должны обеспечить, чтобы сначала из очереди ожидания исключались все обслуженные, покупатели, и только после 9* 259
этого в системе появлялся новый покупатель. Предположим, что в каждый мо- мент времени в очереди ожидания к обычному аппарату стоит не более 10 поку- пателей, а в очереди к специальному аппарату—-не более 15. Необходимо разработать алгоритм, моделирующий только что описанный процесс обслуживания у кассовых аппаратов. Желательно, чтобы выходные результаты содержали число покупателей, прошедших через каждый аппарат за час, общее число покупателей, обслуженных за час, среднее время ожидания у каждого аппарата, общее среднее время ожидания в минутах, число предметов покупки, пропущенных через каждый аппарат за час, общее число предметов, пропущенных за час. Временем ожидания является время, которое покупатель затратил, находясь в очереди к кассе. Желательно также, чтобы выводимые результаты имели следующий формат; 1 Число покупателей-за 1ч 10 Среднее время ожидания, мин 2,80 Число предметов покупки за 1 ч 192 2 3 Специаль- По всем ный аппаратам 11 14 20 55 3,01 2,96 0,22 1,94 261 210 65 728 Промоделируйте также ситуацию с четырьмя стандартными аппаратами, используя тог же самый метод генерацию интервалов поступления покупателей и числа купленных предметов. Этот второй алгоритм должен быть построен так, чтобы моделирование можно было выполнить с минимальным изменением пре- дыдущего алгоритма. Запрограммируйте оба алгоритма на ПЛ/1. Для получения чисел в диапазоне от 0 до значения VALUE—1 предлагается использовать процедуру RANGE. Процедура RANDOM генерирует случайное число с плавающей точкой в интервале (0,1): RANGE: PROCEDURE(VALUE); DCL SEED FIXED BINARY (31, 0) STATIC INITIAL (51771); DCL VALUE FIXED DEC; DCL RANDOM ENTRY (FIXED BINARY (31, 0)) RETURNS (FLOAT DEC); DCL NUM FIXED DEC (3, 0); RANDOM: PROCEDURE (IX) RETURNS (FLOAT DEC); DCL (IX, IY) FIXED BINARY (31, 0); YFL FLOAT DEC; (NOFI XEDOVERFLOW): IY= IX *65539; IF IY<0; THEN IY — IY Ж 2147483647 ф- 1; YFL — IY; YFL = YFL* . 4656613E—9; IX = IY; RETURN(YFL); END RANDOM; NUM = RANDOM(SEED) * VALUE; RETURN(NUM), END RANGE; Какие выводы вы можете сделать на основании результатов обоих процес- сов моделирования? Какие из принятых нами допущений могут привести к не- корректности модели? 3-9. МОДЕЛИРОВАНИЕ СИСТЕМЫ С РАЗДЕЛЕНИЕМ ВРЕМЕНИ Моделирование является одной из классических обла- стей, в которой могут применяться очереди. В данном параграфе рассмотрены вопросы использования очередей при моделировании 260
Центральный процессор 8>ис. 3-9.1. Типичная вычислительная система с разделением времени: 1 — блок управления; 2 — арифметико-логический блок; 3 — селекторный канал; 4 — мультиплексор; 5 — основная память; б — внешняя память с устройствами управле- ния; 7 — польаователи вычислительной системы с разделением времени. Большинство студентов в ходе обучения познакомились с тем или другим вари- антом такой системы. В конце параграфа кратко рассмотрены приоритетные очереди. С начала 60-х годов постоянно возрастал объем вычислений с помощью терминалов, подключенных к вы- числительным системам с разделением времени и работающих в онлайновом (on-line) режиме. Типичная конфигурация такой вычислительной системы показана на рнс. 3-9.1. Очень важно отметить, что в таком режиме множество поль- зователей вычислительной машины работают с ней одновременно. Поскольку имеется лишь один центральный процессор (или просто процессор) и одна основная память, эти ресурсы необходимо раз- делять между п пользователями. Разделение времени работы процессора можно обеспечить путем выделения короткого интер- вала времени для выполнения программы одного пользователя, затем программы другого пользователя, третьего, и так до тех пор, пока вновь не начнет выполняться программа первого поль- зователя. Этот цикл продолжает повторяться со всеми активными программами пользователей. Метод разделения времени работы центрального процессора среди многих пользователей часто на- зывают разделением времени. Очевидно, что программы пользова- телей могут разделять также и основную память путем простого -ее разбиения на области так, чтобы при назначении процессора каждая программа пользователя выполнялась в своей собственной •области. (Существуют более хитроумные способы разделения времени процессора н основной памяти, однако здесь мы не будем их рассматривать.) 261
В описываемой системе каждый пользователь не подозревает о присутствии других пользователей. Фактически здесь каждый терминал выступает для пользователя в роли отдельной вычисли- тельной машины. Рассмотрим простой пример работы системы с разделением времени. Предположим, что три пользователя — Трамбле, Со- ренсон и Бант усаживаются на свои терминалы и начинают он- лайновые сеансы связи с ЭВМ. Об этих сеансах собирается следу- ющая статистика: Относительное время Идентификатор Запрошенные интервалы начала сеанса программы процессорного пользователя времени О TREMBLAY 4,8,3 1 SORENSON 2, 1,2, 2 2 BUNT 4, 6, 1 Эти результаты истолковываются следующим образом. После начала регистрации в момент времени О (первое приведенное выше число является относительным временем в секундах начала общения каждого пользователя с ЭВМ) пользователю Трамбле сначала было выделено 4 с времени процессора до получения им иа терминале напечатанного ответа системы. После получения от- вета и короткого обдумывания он вводит в память новые данные для своей программы. После этого требуется 8 с времени про- цессора, прежде чем на терминале Трамбле будет напечатан сле- дующий ответ системы. Затем снова наступает период обдумыва- ния и ввода новых данных для программы. (Полный интервал обдумывания и ввода данных мы будем называть временем задер- жки пользователя.). Наконец, программа Трамбле использует еще три дополнительных секунды времени центрального процес- сора, после чего Трамбле, закончив свой сеанс, отключается от системы. В приведенном обсуждении мы игнорировали тот факт, что в моменты I и 2 к системе подключались соответственно Сорен- сон и Бант, и их программы также потребовали внимания цен- трального процессора. Поскольку имеется лишь один процессор, программы Соренсона и Банта будут ждать до тех пор, пока не закончится интервал процессорного времени, равный 4 с, выде- ленный для программы Трамбле. Для того чтобы показать, что программы Соренсона и Банта ожидают процессор, идентифика- торы этих программ (SORENSON и BUNT) включаются в очередь вслед за идентификатором программы Трамбле (т. е. TREMBLAY). Следовательно, в момент 2 очередь будет иметь вид, показанный на рис. 3-9.2. При разумном планировании целесообразно после окончания первого интервала процессорного времени, выделенного для про- граммы Трамбле, предоставить процессор для программы Сорен- сона, в то время как Трамбле будет обдумывать ответ в течение интервала задержки. Естественно, когда программа Соренсона 262
•Начало очереди Конец очереди Рис. 3-9.3. Временная диаграмма выпол- нения программ в системе с разделением времени «Рис. 3-9.2. Очередь к процессору в мо- даент времени 2 использует выделенное время, для выполнения на процессоре наз- начается программа Банта. Такой тип планирования часто назы- вают планированием по принципу «первым пришел — первым -обслуживаешься» (FCFS). В рассматриваемой задаче для стратегии типа FCFS существуют следующие правила: 1. Если программа требует времени процессора, то она по- мещается в конец очереди к процессору. 2. В текущее время выполняется первая программа из очереди к процессору. Она остается первой в очереди на весь выделенный ей интервал времени процессора. 3. Когда программа истратит запрошенный интервал вре- мени процессора, она исключается из очереди к нему и не будет включена в очередь до тех пор, пока не будет сделан следующий запрос (в соответствии с прави- лом 1). Согласно приведенным правилам три рассматриваемые про- граммы пользователей будут вести себя так, как это показано иа рис. 3-9.3. Отметим, что мы везде предполагаем пятисекунд- ную задержку пользователя. Перед нами стоит задача промоделировать эту систему с раз- делением времени, если заданы моменты начала работы пользова- телей и запрашиваемые интервалы времени процессора. В про- цессе моделирования программы пользователей должны включа- ться в очередь к процессору согласно правилу 1 и исключаться из нее в соответствии с правилом 3. Целью моделирования является получение информации об эффективности системы с разделением времени. С этой целью на- капливаются данные, необходимые для вычисления следующих показателей. 1. Коэффициент использования процессора (для каждого поль- зователя) = сУмыаРное вРеыя Работы процессора х j суммарное время сеансов связи 2. Суммарное время ожидания пользователя (для каждого пользователя) = полное время — суммарное время работы про- цессора — суммарное время задержки пользователя; 263
3. Суммарное время задержки пользователя (для каждого пользователя) = 5 х (число запросов на использование .процес- сора —г I). Для моделирования системы мы должны тщательно изучить происходящие в ней действия. Затем эти действия должны быть проимитированы таким образом, чтобы определенные характери- стики модели соответствовали характеристикам реальной системы. Например, моменты начала работы пользователей в модели должны быть такими же, как и в реальной системе с разделением времени. Опишем основные действия в системе и способ их имитации. 1. Запрос пользователя на обслуживание. В реальной системе этот запрос является сигналом телетайпа, вызывающим реакцию процессора. В модели мы для этого используем переменную со значением, равным моменту времени, в который пользователь затребует обслуживания. Когда текущее время моделирования достигнет этого момента, пользователь будет включен в очередь к процессору. 2. Выполнение программы пользователя. Для имитации этого процесса время моделирования увеличивается на величину, за- прошенную пользователем. 3. Завершение текущего интервала времени, запрошенного пользователем. В этот момент пользователь исключается из оче- реди иа время задержки, т. е. на время обдумывания и ввода дан- ных. Предположим, что интервал задержки для каждого поль- зователя составляет пять единиц времени. В этот момент моди- фицируется значение переменной, соответствующей запросу на обслуживание и описанной в п. 1, указывая тем самым на состоя- ние задержки пользователя. Если в какой-либо момент времени все пользователи окажутся в состоянии задержки, а очередь к процессору пуста, то время моделирования устанавливается равным времени наиболее ран- него запроса на обслуживание. Рассмотрим теперь более детально алгоритм, реализующий изложенные идеи. Алгоритм TIMESHARE. Данный алгоритм моделирует про- стую систему с разделением времени, использующую стратегию планирования, основанную на принципе FCFS. При этом исполь- зуются следующие массивы запросов пользователей и статистиче- ской информации (символ * означает инициализацию соответству- ющей переменной перед выполнением алгоритма): ID* — имя пользователя. START_TIME*—время начала работы пользователя. END TIME — время конца работы пользователя. NEXTT1ME* — момент времени, в который пользователь обращается с очередным запросом к процессору (в начале алгоритма NEXT-TIME = START-TIME). 264
CPU.TIME* — накапливаемое полное запрашиваемое время процессора (в начале равно 0). TIME_SLICE* — очередь, содержащая длины интервалов процессорного времени, запрошенных пользователем. FRONT*, REAR* — указатели начала и конца очереди TIME________________________SLICE соответственно. DONE_FLAG* — флажок, показывающий, удовлетворены ли все запросы пользователя (первона- чально устанавливается в нуль, чтобы показать, что запросы неудовлетворены). READY_FLAG* — флажок, показывающий готовность ноль- , зователя для включения в очередь к процессору PROQUEUE. Первона- чально флажок устанавливается в нуль, чтобы показать состояние неготовности. Для управления очередью к процессору PROCQUEUE данный алгоритм использует алгоритмы CQINSERT н CQDELETE, описанные в п. 3-8. Элементы очереди PROCQUEUE являются индексами массива, соответствующими находящимся в очереди пользователям, т. е. если первый элемент очереди PROCQUEUE равен 2, то пользователь, взаимодействующий в данное время с процессором, определяется как ID 12]. F и R —указатели на- чала и конца очереди PROCQUEUE соответственно. CLOCK — текущее время моделирования; USERS — число пользователей в системе (предполагается, что оно инициализи- руется до начала выполнения алгоритма TIME___________SHARE); NOTJDONE — флажок, указывающий на то, что все запросы пользователей удовлетворены; EXEC-CLOCK — интервал вре- мени, запрашиваемый пользователем, находящимся в начале очереди; MIN — минимальное значение NEXT_TIME среди всех пользователей, готовых к включению в очередь к процессору; MINWAIT —минимальное значение NEXT_TIME среди всех пользователей. Заметим, что если NEXT_TIME устанавливается равным —1, то пользователь уже присутствует в очереди к процессору. 1. [Инициализация; установка цикла.] Установить NOT_ DONE «— 1 и F R «— CLOCK «— 0. Повторять шаги от 2 до И до тех пор, пока NOT.DONE = 1. Печатать статистическую информацию и закончить выполнение алгоритма. 2. [Установка флажка завершения цикла, если все пользова- тели обслужены полностью; цикл по пользователям.] Установить NOT_DONE «— 0 и MINWAIT «— 9999. Повторять шаги 3 и 4 при i = 1, 2, ..., USERS. 3. [Если пользователь не обслужен полностью, то установка флажка цикла в исходное состояние и проверка, не ожидает ли пользователь обслуживания процессора.] Если DONE____FLAG[i]=; 265
— О, то установить NOT.DONE 1; если NEXT________.TIME [i] с c CLOCK и NEXT_TIME [i ] >• —1, то установить READY— FLAG [i ]-*-!. 4. [Сравнение MIN WAIT и времени очередного запроса поль- зователя.] Если DONE.FALG [i] = 0 и NEXT-TIME [i]< < MINWAIT, то установить MINWAIT NEXT_TIME [i]. 5. [Включение пользователей, ожидающих процессор, в оче- редь PROCQUEUE в порядке увеличения значений NEXT— TIME.] Установить MIN 0. Повторять шаги от 6 до 8 до тех пор, пока MIN 9999. 6. [Поиск очередного запроса пользователя с минимальным временем. ] Установить MIN 9999. Повторять шаг 7 при i = = I, 2, ..., USERS. 7. [Сравнение MIN с временем очередного запроса пользова- теля.] Если NEXT_TIME [i] < MIN и READY_FLAG [i] = = 1, то установить MINNEXT__________TIME [i] и ki. 8. [Включение пользователя в очередь к процессору.] Если MIN <Z 9999, то вызвать процедуру CQINSERT (PROCQUEUE, F, R, k, USERS), установить READY__________FLAG [kj 0 и NEXT—TIME [кН 1. 9. [Если в очереди к процессору нет пользователей, то при- ращение модельного времени и повторение. ] Если F = 0, то уста- новить CLOCK MINWAIT и вернуться к шагу 2. 10. [Обслуживание первого пользователя в очереди к про- цессору. Исключение первого элемента из очереди и модификация статистической информации и NEXT_____TIME. ] Установить j ч- CQDELETE (PROCQUEUE, F, R, USERS), EXEC—CLOCK ч- ч-TIME—SLICE [j, FRONT [j ] ]. CPU TIME [j ]^CPU_TIME [j ]+ 4-EXEC_CLOCK, CLOCK CLOCK+ EX ECCLOCK и NEXT-TIME [j ]-*— CLOCK + 5 (предполагается, что задержка каждого пользователя составляет 5 единиц времени). 11. [Изменение указателей очереди TIME-SLICE; если поль- зователь обслужен полностью, то установка DONE-FLAG в со- ответствующее значение.] Если FRONT [j] << REAR [j], то установить FRONT [j J ч- FRONT [j J + 1; в противном случае установить END-TIME [j] ч—CLOCK и DONE_FLAG [j] ч— 1. В шаге 3 фиксируются все пользователи, включенные в оче- редь к процессору, т. е. READY-FLAG устанавливается в 1. В шагах от б до 8 эти пользователи включаются в очередь в порядке увеличения NEXT____TIME (т. е. момента времени, в который они готовы к включению). В шаге 9 проверяется, пуста ли очередь к процессору. Если очередь не пуста, то обслуживается первый пользователь путем увеличения времени процессора на запрашива- емый интервал. Если очередь пуста, то время устанавливается рав- ным времени наиболее раннего запроса пользователя, готового к обслуживанию, и цикл повторяется. На рис. 3-9.4 приведена программа на ПЛ/1, реализующая алгоритм TIME-SHARE, включающий алгоритмы CQINSERT 266
1 TlH_SHR: PROCEDURE OPTIONS!MAIN); /* APPLICATION 3.9.1 - */ /* SIMULATION OF A SIMPLE TIME-SHARING COMPUTER SYSTEM */ Z DECLARE 1 USER(IO), /* ARRAY TO HOLO USER REQUEST ANO STATISTICAL INFO */ 2 ID CHARACTER(16), 2 TIME_SLICE(10) FIXED DECIMAL, /# USER NAME */ f* TIME_SLICE QUEUE HOLDS USER CPU TIME REQUESTS */ 2 FRONT FIXED BINARY(8,OJ, 2 REAR FIXED BINARY{8,0), /* TIME_SLICE QUEUE FRONT S REAR POINTERS *f 2 START_TINE FIXED DECIMAL» /* CLOCK TIME WHEN USER ENTERED SYSTEM */ 2 END-TIME FIXED DECIMAL, /* CLOCK TIME «HEN USER EXITTED SYSTEM */ 2 NEXT_TIME FIXED DECIMAL, /* CLOCK TIME AT WHICH USER WILL NEXT REQUEST CPU TINE*/ 2 CPU.TIME FIXED DECIMAL, /* HOLDS CUMULATIVE SUM OF CPU’TIME REQUESTS */ 2 READY_FLAG BITCI), /« USED TO ADD USERS TO WAITQUEUE IN PROPER ORDER */ /* READY-FLAG = 1 INDICATES USER TO BE ADDED */ 2 DONE_FLAG ВIT(1> 5 /* DONE_FLAG = I INDICATES ALL USER REQUESTS SATISFIED*/ 3 DECLARE CQDELETE ENTRY ((<4 FIXED BINARY{8,0>, FIXED BINA₽Y(8,D), FIXED 8INARY<8,0),FIXED BINARY(8,0)) RETURNS (FIXED В INARY(8,0)), CQINSERT ENTRY!!*) FIXED 8INARY<8,0), FIXED BINARY{8,0), FIXED BINARY(8,0),FIXED ВI NARY(8,0), FIXED BINARY!8,0>); 4 DECLARE PROC_QUEUE (10) FIXED BINARY(8,0), /* PROC-QUEUE IS A CIRCULAR QUEUE WHICH CONTAINS SUBSCRIPTS*/ I* TO THE USER fcRR₽,Y FOR THOSE USERS RENOY TO USE THE *| /* CPU */ CLOCK FIXED BINARY(8,0) INITIAL(O), /* PRESENT TIME IN SYSTEM *f EXEC_CLOCK FIXED BINARY(8,0) INITIAL(O), MIN FIXED DECIMAL, (USERNUM,NUM,NEXTDIGITS) FIXED DECIMAL, (EOF,OFF) BIT(l) INITIAL!’O’B), ONN BIT(L) INlTIAL(’l’B), II,J,K,USERS,F,R) FIXED 6 INARY!8,0)INITI AL!О I, NOT-DONE BIT(l) INITI AL(’1 *B), (TDELAY,TTIME,UTIME,UT!L) FIXED DECIMAL; Рис. 3-9.4. Программа иа ПЛ/1, реализующая алгоритм TIME_SHARE 267
5 QPRINT; PROCEDURE; /* PROCEDURE ’QPRINT* PRINTS THE ID OF THE USERS IN PROC_QUEUE */ 6 DECLARE J FIXED BINARY(8,0); 7 PUT EDIT (’PROCESSOR QUEUE AT TIME'.CLOCK) " (SKIP,A,F<3)»; 8 put skip; 9 IF R < F 10 THEN 00; 11 DO J = F TO USERS; * 12 PUT EDIT(ID(PROC_QUEUE(J)))(SKIP,X(4I , A)? 15 END; 14 DO J = 1 TO R; 15 PUT EOTT(ID(PROC_QUEUE(J)))(SKIP,X(4),A); lb END; 17 ENO; 18 ELSE DO J = F TO R; 19 PUT EDIT(I0(PROC_QUEUE(J)H ( SK IP, X (4) , A) ; 20 END; 21' PUT SKIP(2); 22 END QPRINT; 23 CQINSERT: PROCEDURE(QUEUE,F,R,I.LAST); /* PROCEDURE ’CQINSERT' INSERTS THE VALUE I AT THE REAR OF ’QUEUE* */ /♦ QUEUE - ARRAY USED AS CIRCULAR QUEUE, BOUNDS 1 TO LAST *7 /* F - FRONT OF QUEUE */ ./« R - REAR OF QUEUE */ /• I - FIXED VALUE TO BE INSERTED IN QUEUE */ /* LAST - UPPER BOUND FOR QUEUE ARRAY */ gA DECLARE (I,LAST,R,FI FIXED-ВINARYt8,0), QUEUE(*I FIXED BINARY(8,0); <25 IF R - LAST 26 THEN R - 1; 27 ELSE R = R ♦ 1; 28 IF R = F 29 THEN RETURN? 1 30 QUEUE(R) = 15 31 IF F’O THEN F a 1; 33 END CQINSERT; 34 CQDELETEi PROCEDURE (QUEUE,F,R.LAST) RETURNS (FIXED BINARY (8,0)); /* PROCEDURE ’CQDELETE* REMOVES THE FRONT ELEMENT FROM ’QUEUE’ AND ♦/ /* RETURNS THE VALUE OF THE FRONT ELEMENT . */ /* QUEUE - ARRAY USED AS CIRCULAR QUEUE, BOUNDS 1 TO LAST */ /* F FRONT OF QUEUE #/ /♦ R REAR OF QUEUE =*/ /* LAST - UPPER BOUND FOR QUEUE ARRAY */ 35 DECLARE (LAST,F,R,KEEP) FIXED BINARY (8,01» QUEUEl*) FIXED BINARY (8,0); 36 'IF F = 0 THEN RETURN (0); 38 KEEP - QUEUE (F I; ' 39 IF F= R THEN F,R = 0; 41 ELSE IF F = LAST 4 42 THEN F = IJ 43 ELSE F = F + I? 44 RETURN (KEEP); 45 , END CQOELETE; Рис. 3-9.4. Продолжение , /*♦**.«»♦,*,**♦* MAIN LINE ♦**♦♦♦♦****«*» /♦ READ IN USER REQUEST DATA AND INITIALIZE USER ARRAY ELEMENTS 4/ 46 ON ENPFILE(SYS’NJ EOF=’1’B; 48 I = IS 49 GET LIST(NUM,NEXTDIGITS); 50 DO WHILEI-,EOFI; 51 NEXT_TIMS<I) = NEXTDIGtTS; 52 START_TIME(I) = NEXTDIGITS; 53 GET LIST 4 ID( Ш ? $4 put skip; 55 PUT EDITl’USER NAME*,’SESSION START TIME’)(SKIP,A(9),X(9),A(18))? $6 PUT EDIT(1D(I)jNEXTOIGITS)(SKIP,A(16),X(10),F(2)); 57 PUT EDIT(’CPU TIME REQUESTS:’ H SK IP Ш ,A( 18) >! 58 USER_NUM - NUM; 59 J = 1; 60 GET L1ST(NUM,NEXTDIGITS); bl 00 WHILE (USER^NUM-NUM £ -«EOF}; 62 PUT EDIT(NEXTOIGITSHSKIP,X(20),F(2I); 63 T IME_SL ICE 11, J ) = NEXTDIGHS; 64 . J=J+1; 65 GET LiSTINUM,NEXTDIGITS!; 66 END; 67 REARCП = J-i; 68 FRONT(I) = 1; 69 CPU_TIME(I) = 0; 70 DONE_FLAG(I)= OFF ; 71 READY_FLAG = OFF; 72 1=1+1? 73 END; 74 USERS = I - 1; 75 PUT SKIP(2I; /* MAIN SIMULATION LOOP */ 76 DO WHILE (NOT_DONE>; 77 NOT_DONE = OFF; /# CHECK EACH USER TO FIND IF WAITING FOR SERVICE */ /* IF WAITING, SET READY_FLAG = ONN TO SIGNAL WAITING ♦/ /* NEXT_TIME = -1 INDICATES USER IN PROC_QUEUE. IF ALL */ /4 USERS DONE <DONE_FLAG = ONN) THEN SET FLAG TO HALT */ /* MAIN LCOP ♦/ 78 DO I = 1 TO USERS; 79 IF -,DONE_FL AG (I I 80 THEN DO; 81 NOT_OONE = ONN; 82 IF NEXT_TIME(I) <= CLOCK £ NEXT_TIME(I) > -1 83 THEN READY_FLAG(I) = ONN; 84 END; 85 END; /* INSERT USERS WITH READY_FLAG = ONN INTO PROC_QUEUE IN */ /♦ ORDER OF INCREASING VALUE OF NEXT_TIME */ 86 MIN = 0; B7 DO WHILE (MIN < 9999); 88 MIN = 9999; Рис. 3-9.4. Продолжение 269 268
89 ' DO I = 1 TO USERS; 90 ’IF NEXT-TIME(I) < WIN € REAOY_FLAG(I) 91 THEN DO; 92 MIN * NEXT_T IME ( IJ ; 93 к = i; 94 END; 95 END; 96 IF MIN -•= 9999 *97 THEN DO; 98 CALL CQINSERT(PROC_QUEUE,F,R,K,USERS!; 99 READY-FLAG(K) = OFF; 100 NEXT_TIME(K) = -1; 101 end; 102 END; 103 IF F -.= 0 104 THEN DO; /< IF THERE IS A USER IN PROC_QUEUE, PRINT PROC-QUEUE =•/ 7* DELETE USER ANO UPDATE CLOCK AND NEXT_TIME OF USER TO /*> INDICATE USER RECEIVED TIME REQUESTED. 105' CALL QPRINT; 106 J ~ CQDELETE (PROC-QUEUE,F,R,USERS); 107 EXEC_CLOCK = TIME-SLICE(J,FRONT!J)>5 108 CPU_TIME (J) = CPU_TIMEtJ) EXEC-CLOCK; 109 CLOCK = CLOCK EXEC_CLOCK; 110 NEXT_TINE(J) = CLOCK * 5; 111 IF FRONT(J I < REAR(J> 112 THEN FRQNTIJ) = FROHT(J) + 1; 113 ELSE DO; 114 END—TIMEIJ) = CLOCK; 115 DONE_FLAG(J) = 0NN5 116 end; 117 END; /* NO USER THEN INCREMENT CLOCK AND REPEAT LOOP */ 118 ELSE CLOCK = CLOCK ♦ 1; -119 END; /# CALCULATE ANO PRINT STATISTICS */ 120 PUT SK1P(2>; 121 PUT EOITI'USER NAME’,’TOTAL CPU','T0TAL USER','TOTAL MATING’, •TOTAL SESSION*,'CPU’1 (SKIP,A(9»,X(lOJ,A{9),X(6J,A(10bX(4bA(13J,X(4bA(13l,X<®b Al3>); 122 PUT EOITl’TIHE REQUIRED’,’DEL AY TIME’,’TIME IN QUEUE'TIME’, 'UT IL HAT ION' J l5KIP,X(i7),A(13),X(4),A{10),X(4),A( 13),X(81,A(4),X(9) 123 put SKIP; 124 00 Jsl TO users; 125 TDELAY = 5*(REAR(J)~li; 126 TTINE = ENO-TIME(J) - START—TIME(J 1j 127 WTXME = TTJHE - TDELAY - C₽U_TIME(J); 128 UTIL * CPU_TIME(J) f. 100 / TTIME; 129 PUT EOITIID(J),CPU-TIME(J),TDELAY,WTIME,TTIME,UTIL,'«•I I SKIP,Al161,X(6),F(3J,X{13),F(31,X(13),F(3),X(14),p(3); Х(13ЬНЗЬА(МВ no ено; 131 END WL.SHR; Рис. 3-9.4. Продолжение 270
USER NAME SESSION START TIME PROCESSOR QUEUE AT TIME 25 TREMBLAY 0 CPU TIME REQUESTS: 8 USER NAME SESSION START TIME SORENSON I CPU TIME REQUESTS: 2 1 2 2 TREMBLAY SORENSON PROCESSOR QUEUE SORENSO^ PROCESSOR QUEUE BUNT AT AT TIME 28 TIME 30 USER NAME SESSION START TIME BUNT 2 PROCESSOR QUEUE SORENSON AT TIME 35. CPU IIHE REQUESTS: PROCESSOR QUEUE AT TIME О TREMBLAY PROCESSOR QUEUE AT TIME <t SORENSON BUNT PROCESSOR QUEUE AT TIME 6 BUNT PROCESSOR QUEUE AT TIME 10 TREMBLAY PROCESSOR QUEUE AT TINE 18 SORENSON BUNT PROCESSOR QUEUE AT TIME 19 BUNT USER NAME TOTAL CPU TIME REQUIRED TOTAL USER DELAY TIME TOTAL HATING TIME IN QUEUE TOTAL SESSION TIME * CPU UTILIZATION Tremblay 15 SORENSON 7 BUNT II 10 3 15. 14 10 8 28 36 29 53% 19% 37X Рис. 3-9.4. Продолжение 271
и CQDELETE, для моделирования небольшой системы с разделе- нием времени. Основная программа состоит из трех частей: 1. Операторы 46—74: считывание исходных данных и инициа- лизация массивов данных пользователей. 2. Операторы 76—119: основной цикл моделирования алго- ритма TIME—SHARE. 3. Операторы 120—130: обработка и печать статистической информации. Имена процедур и переменных в программе на ПЛ/1 и в алго- ритме T1ME_SHARE совпадают, за исключением имени основ- ной процедуры, которая в программе сокращена до TIM_SHR, поскольку в языке ПЛ/1 длина имени основной процедуры огра- ничена . При рассмотрении нашего примера с очередью мы сосредото- чили внимание на моделировании простой системы с разделением времени. Тем не менее должно быть очевидно, что и при функцио- нировании реальной системы должна использоваться очередь имен пользователей ID, подобная очереди PROCQUEUE при моделировании. Очередь является одной из наиболее широко ис- пользуемых структур данных в любой операционной системе, предназначенной для одновременной работы многих пользовате- лей. Причина этого факта заключается в том, что каждая про- грамма пользователя конкурирует с остальными программами и разделяет ресурсы системы (например, процессор, память и уст- ройства ввода-вывода). Если программа пользователя запрашивает некоторое средство, уже выделенное другой или другим пользова- тельским программам, то выполнение запрашивающей программы приостанавливается до тех пор, пока не будет удовлетворен ее запрос на это средство. Разумный метод управления очередью ожидающих программ основан на принципе «первым пришел— первым обслуживаешься». Эта стратегия реализует наилучшую организацию очереди 1. Некоторые вычислительные системы обеспечивают обслужива- ние широкого класса пользователей. Упомянем, например, си- стему, осуществляющую управление в реальном времени, обслу- живание в режиме с разделением времени терминалов пользова- телей, а также пакетную обработку заданий. При этом задачи или задания каждого класса конкурируют за использование централь- ного процессора. Один из практических методов планирования времени процессора, принятый во многих вычислительных систе- мах, заключается в выполнении сначала задач реального времени, затем программ, выполняемых в режиме с разделением времени (если нет нерешенных задач реального времени), и, наконец, па- кетных заданий, если нет запросов на выполнение задач реального 1 Существует целый ряд и других дисциплин обслуживания очередей. — см. Л. Клейнрок. Вычислительные системы с очередями. — Пер. с англ. М.: Мир, 1979. 600 с. — Прим. ред. 272
времени или запросов, обслуживаемых в режиме разделения времени. Рис. 3-9.5, а иллюстрирует очередь к процессору, ос- нованную на такой дисциплине обслуживания. Если становится активной новая задача реального времени (обозначенная R,), то она включается не в конец общей очереди, а в конец списка задач реального времени этой очереди. Аналогично новый запрос из программы, выполняемой в режиме разделения времени (обоз- наченный О,), помещается в конец других подобных запросов. И лишь новые задания на пакетную обработку включаются в ко- нец очереди. Очередь, для которой имеется возможность включать или ис- ключать элементы из определенной позиции в зависимости от не- которых их характеристик, таких как приоритет задачи, подлежа- щей выполнению, часто называют приоритетной очередью. На рис. 3-9.5, а приоритет 1 присвоен задачам реального времени, 2 — задачам, выполняемым в режиме разделения времени, и 3 — пакетным заданиям. Следовательно, при инициировании за- дания с приоритетом i оно включается в конец списка заданий i-ro приоритета при i = 1, 2 или 3. В данном примере задания всегда исключаются лишь из начала очереди. В общем случае это не является необходимым требованием приоритетной очереди. Приоритетную очередь можио представить в виде ряда очередей в тех случаях, когда априорно известны приоритеты ее элемен- тов. На рис. 3-9.5, б показано, как может быть наглядно представ- лена приоритетная очередь в виде трех отдельных очередей, об- служиваемых каждая по дисциплине FIFO. Здесь элементы из второй очереди исключаются для обслуживания только тогда, когда пуста первая очередь, а элементы из третьей очереди ис- Приоритет 1 | R,|RZ| — Ы- —- Bl Приоритет 2_______ |o,|oz| - |РИ| Oj Приоритет 3_______ |в,|в2| —|вм| вк Рис. 3-9.5. Приоритетная очередь в виде: с -» общей очереди с операциями включения в любую позицию; б — ряда очередей 273
Такое разделение одной приоритетной очереди иа ряд очередей обеспечивает также и эффективное представление ее в памяти. При включении элементы добавляются к концу одной из очередей согласно их приоритету. Если же для представления приоритет- ной очереди используется последовательная структура хранения, то включение может означать вставку элемента в середину струк- туры. Это может потребовать перемещения нескольких элементов. Поэтому лучше расщеплять приоритетную очередь на несколько очередей с отдельными структурами хранения каждая. В гл. 7 мы встретимся с другим примером, относящимся к очередям буферов как средству управления данными в большин- стве операционных систем. В следующей главе мы рассмотрим другие виды представления в памяти таких списковых структур, как очереди. Упражнения к п. 3-9 1. Измените алгоритм TIME_ SHARE с учетом использования различных значений времени задержки каждого пользователя. Модифицируйте рис. 3-9.3 для следующих времен задержки: Бант — 2, Соренсон — 6, Трамбле — 9. 2. Машинное моделирование систем трафика часто используют в качестве инструмента при планировании движения транспорта. Моделируемые при этом системы охватывают национальные транспортные сети, городские сети, сети районов города и т. д. вплоть до трафика через мост или перекресток. Соответ- ствующие модели используются для выявления существующих или будущих узких мест, а также для планирования и проверки предполагаемых изменений или создания новых систем. Движение на перекрестке, управляемое с помощью светофора, является одним из примеров транспортной системы (трафика), модель которой относительно проста. Такая модель может использоваться для вычисления характеристик трафика через перекресток. При этом основной измеряемой величиной является длительность задержки водителей у перекрестка. Характеристики трафика через перекресток определяются средним и максимальным временем ожидания води- телей. Мы будем рассматривать конкретную модель перекрестка двух двухрядных улиц, причем движение в каждом ряду управляется трехцветным уличным све- тофором. Используемые ниже обозначения улиц и рядов приведены на рис. 3-9.6. Для упрощения модели мы сделаем некото- рые предположения относительно трафика н поведения водителей. Во-первых, предполо- жим, что входящие потоки продолжают движе- ние прямо вперед; повороты влево н вправо не допускаются. Во-вторых, предположим, что время реакции автомобилей и водителей одно и то же для всех транспортных средств, т. е. при подаче сигнала о том, что путь свободен, все автомобили реагируют на этот сигнал одновременно. Возможные поломки автомоби- лей и несчастные случаи игнорируются. Пред- полагается, что все водители твердо соблю- дают правила движения и, следователь- но, останавливаются на красный н желтый свет. Улица I Ряд J \ I I ! Улица ~Z ржГ Рис. 3-9.6. Перекресток со све- 274
Согласившись с возможным возражением читателя о том, что таких пере- крестков не существует, мы, однако, ради ясности описания будем рассматри- вать именно такой упрощенный перекресток. Предполагается, что светофоры имеют следующие характеристики. 1. Сигналы светофоров для обоих рядов каждой улицы синхронизированы и считаются единым блоком. 2. Длительности циклов обоих блоков светофоров одинаковы. 3. Не существует стрелок правого и левого поворотов и сигнала перехода ® любом направлении для пешеходов. 4. Между переключением одного блока светофоров на красный свет, а дру- гого — на зеленый существует короткий интервал времени, называемый вре- менем задержки. 5. Длительность красного света каждого блока светофоров больше суммар- ных периодов зеленого, желтого сигналов н задержки другого блока светофоров. Рассмотрим теперь эту задачу более детально и изложим попутно ряд идей нз области машинного моделирования. Первый шаг при создании модели состоит в разбиении рассматриваемой системы на составные части, илн объекты. В системе с перекрестком, которую мы изучаем, основными объектами являются светофоры и автомобили. Каждый объект имеет связанные с ним свойства (атрибуты). Например, цвет — свойство светофора; важными свойствами автомобиля могут быть ряд, по которому он .движется, и момент времени, в который он достигает перекрестка. Другими свой- ствами автомобиля могут быть скорость движения, его цвет и т. д., но эти свой- ства не относятся к описываемой модели. При детальном машинном моделировании моделируемые объекты должны как можно более точно соответствовать своим реальным прототипам в пределах возможностей вычислительной машины. Ниже обсуждаются методы достижения этой цели для рассматриваемой системы. В данной модели сигналы светофора должны изменяться в той же временной последовательности, что и сигналы реального светофора на перекрестке. Тогда по заданной начальной цвето-временной конфигурации светофора (т. е. про- должительности каждого цвета) может быть определено состояние светофора в любой последующий момент времени. Например, если в нулевой момент вре- мени горел красный свет, а его продолжительность равна 60 с, то значит, в мо- мент времени 61 с свет будет зеленым. Модель потока автомобилей должна точно соответствовать потоку на реаль- ном перекрестке с точки зрения частоты появления и направления движения автомобилей. Если имеются статистические данные о трафнке через перекресток, такие как число автомобилей, движущихся в каждом направлении, и среднее время между моментами их появления, то при моделнрованнн автомобилей можно использовать процедуру генерации характеристик, статистически подобных характеристикам реального потока. Эта процедура основывается на методе Монте- Карло н генерирует случайные числа в моменты принятия решений в процессе моделирования. Первое решение относительно автомобиля заключается в выборе направ- ления его движения к перекрестку. Существуют четыре возможных исхода та- кого решения, а именно Ег, Ez, Е3 н Е^, представляющих северное, южное, за- падное н восточное направления соответственно. Если известны вероятности pt, Рг, Рз н р4, причем pj + р2 + ps + pt = 1 каждого исхода, то решение о выборе направления можно найти с помощью следующей процедуры, использующей случайное число. С помощью генератора случайных чисел, равномерно распре- деленных на интервале от 0 до 1, формируется случайное число R. Тогда резуль- татом решения является Ef, если R < рх; Ez, если pt с R < pi -J- р2; Eat если Рх + р2«:/?<р1 + р2 + рз; если Рх + р2Ч-Рз</?. 275
Свет светофора [сгенерировать событие X красный появления автомобиля появления автомобиля на улице А в момент времени У Свет светофора X желтый Модифицировать состояние другой улицы до момента бремени У До Остановить I автомобиль | времени V принадлежит интерЬолу красного Свет светофора А зеленый Да Остановить автомобиль; движение автомобилей через перекресток до момента У момент времени У принадлежит интервалу зеленого Передвижение автомобилей до появления зеленого света Да времени г принадпежот интервалу желтого [Остановить автомобиль | I Изменение . текущего бремени до значения Y Рис. 3-9.7. Схема событийного моделирования трафика на перекрестке со светофорами Полагая, что поток, движущийся через перекресток, подчиняется некоторому закону, будем считать, что время между последовательными появлениями ав- томобилей имеет экспоненциальное распределение. Это означает, что функция F (х) — 1 — е~ах есть вероятность того, что интервал времени между появлениями автомобилей меньше или равен х. При этом а равно 1/ATI, где ATI — средний интервал времени между появлениями автомобилей у перекрестка. Для имитации вероят- ности мы можем вычислить соответствующее этой функции значение случайного числа х, представляющее собой длительность интервала между появлениями автомобилей, путем генерирования случайнс-о числа R н использования фор- мулы х—— ATI log/?. Более детальное обсуждение экспоненциального закона распределения и его свойств можно найтн в книге [10] *. Обсудив способ обработки сигналов, соответствующих переключению све- тофоров и появлению автомобилей, обратим внимание на ту часть модели, которая относится к взаимодействию между светсфорами и автомобилями. Именно эта часть самым тесным образом связана со структурами данных, рассматривае- мых в п. 3-8. Рассматриваемая часть модели управляет либо движением автомобилей на зеленый свет (т. е. исключением их из процесса моделирования), либо их оста- новкой лрн желтом или красном свете. Нетрудно понять, что очередь является естественным представлением автомобилей, остановленных красным светом. В нашей модели используются четыре очереди, представляющих собой трафик в четырех направлениях. При остановке автомобиль включается в очередь; после проезда через перекресток он исключается из очереди. Переполнение очереди означает затор трафика. * См. также: Г. Хан, С. Шапиро. Статистические модели в инженерных задачах. — Пер. с англ. М-: Мир, 1969. 395 с. — Прим. ред. 276
Общая модель включает следующие шаги. 1. Ввод автомобиля в процесс моделирования. 2. Модификация данных о светофорах и потоке транспорта? в момент появления автомобиля. 3. Управление поведением автомобиля в соответствии с сиг- налом светофора, т. е. продвижение автомобиля [через перекресток или его включение в очередь ожидания. 4. Переход к шагу I. Этот процесс графически представлен схемой на рис. 3-9.7. Из первого блока событий (помеченного А) исходят три ветви. Выбор ветви зависит от со- стояния светофора (в текущий момент моделирования) на той улице, по которой автомобиль подъезжает к перекрестку. Отметим также, что передача управления из ветви, имитирующей красный свет, в ветаь, имитирующую зеленый, и далее в ветвь, имитирующую желтый свет, представлена штриховой линией. Этот цикл прерывается только тогда, когда должен быть имитирован свет, соответ- ствующий интервалу времени от текущего момента до момента Y. Читателю пред- лагается разработать алгоритмы, реализующие описанную модель движения через перекресток, а затем запрограммировать и отладить их. Результаты моде- лирования должны содержать максимальное и среднее время ожидания, а также максимальную н среднюю длину очереди по каждому направлению движения через перекресток. 3. Измените алгоритмы и программу в упр. 2 для моделирования движения через перекресток, в котором транспортные средства наряду с прямым проездом через перекресток могут совершать также повороты вправо и влево. Исполь- зуйте при этом следующее правило проезда: автомобиль, поворачивающий влево, может покинуть очередь ожидания только в том случае, если пуста очередь авто- мобилей встречного потока или первый автомобиль из этой очереди также пово- рачивает влево. Отметим, что в этой задаче для каждого автомобиля в очереди должен храниться дополнительный параметр — направление поворота. Для определения направления последующего движения можно использовать тот же самый метод случайной выборки, который использовался в предыдущей задаче для выбора полосы проезда. Список литературы 1. Cohen D. J., Brllllnger Р. С. Introduction to Nonnumeric Computa- tion. Englewood Cliffs: Prentfce-Hall, 1970. 2. Forsythe A. 1., Keenan T. A., Organick E. I., Stenberg W. Computer Science: A first Course. New York: Wiley, 1969. 3. Грис Д. Конструирование компиляторов для цифровых вычислительных машин./Пер. с англ. М.: Мир, 1975. 544 с. 4. Harrison М. С. Data-Structures and Programming. Glenview: Scott, Foresman, 1973. 5. D'Imperio M. E. Data Structures and Their Representation in Storage.— Annual Review in Automatic Programming, vol. 5, pp. 1—75, Oxford: Pergamom Press, 1969. 6. Кнут Д. Искусство программирования для ЭВМ. Т. 1. Основные алго- ритмы,/Пер. с англ. М.: Мир, 1975. 735 с. 7. Lee J. A. N. The Anatomy of a Compiler. New York: Reinhold, 1967. 8. Martin F. J. Computer Modelling and Simulation. New York: Wiley, 1968. 9. McKeeman W. M., Horning J. J., Wortman D. B. A Compiler Gene- rator. Englewood Cliffs: Prentice — Hall, 1970. 10. Naylor T. H., Balinty J. L., Chu K., Burdick D. E. Computer Simula- tion Techniques. New York, Wiley, 1968. 11. Tremblay J. P., Manohar R. M. Discrete Mathematical Structures with Applications to Computer Science. New York: McGraw-Hill, 1975. 12. Walker T. M., Cotterman W. W. An Introduction to Computer Science and Algorithmic Processes. Boston: Allyn and Bacon, 1970. 277
Глава 4. ЛИНЕЙНЫЕ СТРУКТУРЫ ДАННЫХ И ИХ СВЯЗАННОЕ ПРЕДСТАВЛЕНИЕ В ПАМЯТИ В предыдущей главе было описано представление линейных струк- тур данных с помощью метода последовательного распределения памяти. Хотя этот метод н пригоден в определенных приложениях, существует большой класс задач, в которых применение этого метода приводит к неудовлетворительным результатам. Задачи этого класса, обычно, имеют следующие особенности. 1. Непредсказуемые требования на размер памяти. Точный размер памяти, необходимый для хранения данных, в программах такого типа часто зависит от конкретного вида обрабатываемых данных и, следовательно, не может быть легко определен при написании программы. 2. Большое число сложных операций над данными. В программах этого типа над данными выполняются многочисленные операции включения н исклю- чения. Метод связанного распределения памяти может прив.естн как к более эффек- тивному использованию памяти, так и к экономии времени вычислительной машины. Поэтому в настоящей главе вводятся понятия связанного распределе- ния применительно к линейным структурам данных. В первом параграфе описаны основные свойства указателей и связанного распределения, приведено использование этих понятий для реализации простых операций над многочленами. В следующем параграфе даны несколько алгоритмов обработки линейных связанных структур. Вопросы программирования некоторых часто используе- мых операций над связанными структурами рассмотрены с использованием мас- сивов. Это обусловлено двумя причинами. Во-первых, некоторые языки програм- мирования (напрнмер, АЛГОЛ 60, ФОРТРАН, БЕЙСИК) не допускают свя- занных структур как таковых, и для их моделирования можно использовать массивы. Во-вторых, использование структур низкого уровня, таких как мас- сивы, для моделирования связанных структур облегчает понимание операций, выполняемых над ними. Для многих алгоритмов приведены программы иа языке ПЛ/1 с использованием операторов ALLOCATE и FREE. Эти операторы позво- ляют программисту управлять размещением данных в памяти и определять свои собственные структуры данных. В последнем параграфе описаны примеры применения связанных списков для построения связанных словарей и реализации арифметических операций многократной точности. 4-1. УКАЗАТЕЛИ И СВЯЗАННОЕ РАСПРЕДЕЛЕНИЕ ПАМЯТИ В предыдущей главе было достаточно подробно рас- -смотрено, как можно непосредственно вычислить адрес любого элемента структуры данных. Рассматриваемая структура данных •была линейно упорядочена, и это отношение порядка сохранялось в соответствующей структуре памяти вследствие использования 278
принципа последовательного распределения. Не было необходи- мости специально определять для каждого элемента положение следующего за ним элемента. Рассмотрим список, состоящий из. элементов с различной дли- ной. Задача получения адреса произвольного элемента прямым вычислением сразу становится намного сложнее. Очевидным спо- собом получения адреса узла (элемента) списка является хране- ние этого адреса в памяти. В гл. 1 мы назвали этот способ адре- сации адресацией с помощью указателей, или ссылочной адреса- цией. Если рассматриваемый список состоит из п узлов, то адрес- каждого узла можно хранить в векторе длины п. Первый элемент вектора содержит адрес первого узла списка, второй элемент — адрес второго узла и т. д. Ряд приложений по своей природе имеет дело с данными, которые непрерывно обновляются (включаются, исключаются и т. д.). Всякий раз изменение связано с достаточно сложными операциями над данными. В некоторых случаях представление данных в виде последовательно организованных списков приводит к неэффективному использованию памяти, потере машинного времени, а для других задач это представление вообще неприме- нимо. Представление многочленов массивами, обсуждавшееся ранее (см. п. 3-5), имеет ряд очевидных недостатков. Такие мас- сивы имеют лишь небольшое число элементов, отличных от нуля, и поэтому подобное представление далеко на самое компактное. Кроме того, объем памяти, необходимый для некоторых операций (например, для деления многочленов), не может быть определен - заранее; следовательно, неизвестно, какой объем памяти необ- г ходимо резервировать для многочлена, получаемого в результате операции. , Возвращаясь к результатам пп. 1-4. II и 1-4.12, можно сделать- вывод, что интерпретация указателя как адреса довольно есте- ственна. В большинстве вычислительных машин адреса исполь- зуются для нахождения следующей подлежащей выполнению- команды и ее операнда (операндов). Во многих аппаратурных , реализациях для хранения таких адресов используются специаль- ные регистры. Указатели всегда имеют одинаковую длину, обычно : не более, чем полуслово, и это позволяет выполнять операции над указателями с. помощью простых методов распределения памяти независимо от характера структуры, ссылки на которую они содержат. При последовательном распределении памяти адрес любого элемента можно вычислить только в том случае, если структура памяти имеет известный и вполне определенный вид. Указатели ► же позволяют осуществлять ссылки на структуру независимо от ее организации. Иными словами, указатели дают воз- можность представления более сложных отношений между элементами структуры, чем отношение линейного по- 'рядка. t 2»
1 2100 & 1Рис. 4-1.1. Связанный список и операции включения и исключения Использование указателей, или ссылок, при обращении к элементам лииейно-упорядоченной структуры данных предпола- гает, что смежные элементы в смысле линейной упорядоченности необязательно физически располагаются в смежных участках памя- ти. Этот тип распределения называется связанным. Перейдем те- перь к задаче представления структур данных с помощью такого распределения. Список определяется как упорядоченная последовательность элементов \ число которых может изменяться. Самый простой способ реализовать линейный список — это добавить к каждому его узлу ссылку, или указатель на следующий элемент. Такое представление называется однонаправленной цепочкой, или односвя- зным линейным списком. Пример такого списка показан на рис. 4-1.1, а. На этом рисунке переменная FIRST содержит адрес, или указатель положения первого узла списка. Каждый узел состоит из двух частей. В первой части содержится ин- формация, хранящаяся в данном элементе, во второй части содер- жится адрес следующего узла. Последний узел списка не имеет следующих за ним узлов, поэтому его указатель не должен со- * Термины «элемент списка» и «узел списка» используются здесь как сино- ’нимы. — Прим. пер. 280
держать никакого реального адреса. Обычно в таком случае ука- зателю приписывается нулевое значение. Стрелка, исходящая : из поля адреса узла, указывает на узел, следующий за ним ! в структуре. Например, связанный список, изображенный на * рис. 4-1.1, б, состоит из пяти узлов; узлы расположены в памяти j по адресам 2000, 2010, 2002, 2012 и 2006 соответственно. Опять- , подчеркнем, что порядок следования узлов определяется только | указателями- Значение указателя NULL, изображенное косой чертой в последнем элементе, является признаком конца списка. ’ NULL не является адресом возможного следующего узла, это ' специальная величина, которая не может быть воспринята как < адрес. По этой причине NULL используется в качестве специаль- , ного ограничителя списка. Возможна ситуация, когда список вообще не содержит ни одного элемента. Такой список называ- ется пустым, и в этом случае переменная FIRST имеет значение- 1 NULL. | Сравним операции, обычно выполняемые над последовательно ; распределенными данными и данными, представленными в виде связанного списка. Рассмотрим операции включения и исключе- i ния элементов в случае последовательной организации данных. Если имеется п-элементный линейный список и необходимо ме- . жду первым и вторым элементами вставить новый элемент, то по- ] следние п — 1 элементов следует сдвинуть, чтобы освободить- I место для нового элемента. Если массив достаточно большой, то , ясно, что это довольно неэкономичный способ реализации опе- рации, особенно, если включения производятся часто. Тот же спо- соб применяется и для исключения элемента: все элементы, рас- положенные после исключаемого, сдвигаются к началу спискау t чтобы заполнить освободившееся место- ’ Для связанного списка операция включения нового узла ре- ; ализуется очень просто. Если новый узел нужно включить после j первого узла списка, то это выполняется простым изменением. 5 указателей. Для списка из пяти узлов эта процедура показана * на рис. 4-1.1, в. Исключение четвертого узла из исходного списка i изображено на рис. 4-1.1, г. Ясно, что операции включения ; и исключения намного эффективнее для связанной организации - данных, чем для последовательной. Можно сравнить также некоторые другие операции над спи- сками со связанной и последовательной организациями распреде- ления памяти. Для нахождения некоторого элемента в связанном • списке необходим последовательный просмотр, начиная с первого' элемента до тех пор, пока не будет найден искомый элемент. В этом случае более эффективно прямое вычисление адреса,. Допустимое при последовательной организации. Однако в тех слу- чаях, когда необходим просмотр всего списка, время выполнения , практически одинаково для обоих методов. Два связанных списка намного легче объединить или разделить j по сравнению с выполнением такой же операции над линейными ; 281
списками. Это осуществляется простой заменой указателей и не требует перемещения данных в памяти. Указатели, или ссылки, требуют дополнительной памяти, од- нако если данные занимают лишь часть слова, то указатель может храниться в оставшейся его части. Можно также группировать узлы так, чтобы на несколько узлов приходился один указатель. Эти два способа позволяют сократить расход памяти для хранения указателей. В предыдущем примере адреса памяти в поле связи были ис- пользованы для наглядности. На практике, однако, эти адреса не интересуют программиста, а кроме того, просто неизвестны ^ему. Поэтому далее для указания порядка следования узлов будет использоваться стрелка. В данном параграфе мы говорили о применении указателей •только для определения линейного порядка (смежности) узлов, однако указатели можно использовать и для определения более «сложных отношений между узлами, таких как описываемые де- ревом или ориентированным графом. Эти отношения трудно, а для некоторых графов даже невозможно, представить в виде линейно-организованной структуры. Эти более сложные структуры легко могут быть описаны списками, содержащими ряд указателей в каждом узле. При таком способе представления одни узел может принадлежать сразу нескольким структурам. Этот вопрос будет -более подробно рассмотрен в гл. 5. Из сказанного выше и примеров, которые будут даны ниже, •становится ясно, что для одних операций более эффективно свя- .заиное распределение данных, для других, наоборот, эффективней .последовательное распределение. Во многих приложениях ис- .-пользуются оба метода. К связанному распределению относится понятие пула, или ^области свободных элементов памяти; мы ее будем называть .списком свободных элементов. При операции включения из списка 'Свободных элементов берется свободный элемент и вносится в ну- жный список И наоборот, после исключения элемента из списка юн возвращается в список свободных элементов и снова может ис- пользоваться для включения в какой-либо другой список. До- стоинство этого способа управления памятью очевидно. В каждый :момент времени используется только тот объем памяти, который .действительно необходим. Управление свободной памятью в случае односвязных спи- сков просто. Для структур, в которых узел может содержать не- сколько указателей, эта простота исчезает. Некоторый узел может входить в несколько списков, и исключение такого узла из одного списка вовсе не означает, что он подлежит возврату в Список свободных элементов. При этом алгоритм управления памятью, часто называемый «сборщиком мусора», становится до- вольно сложным. Подробно этот вопрос рассматривается в п. 5-6. .282
Теперь мы можем' детально рассмотреть некоторые операции, вы- полняемые над линейными связанными списками. Рассмотрим уже знакомую задачу символьной обработки, со- стоящую в выполнении различных операций (сложение, вычитание, умножение, деление, интегрирование, дифференцирование и т. д.) над многочленами. Ограничимся выполнением таких операций над многочленами, содержащими три переменные. Например, может потребоваться сформулировать алгоритм, который при вычитании х2 4- Зху — х 4- у2 4- 2zs из многочлена 2х2 4- 5ху 4- 4 у2 4- yz дает результат х2 4- 2ху 4- х + yz — 2z3. Вызывает интерес найти такое представление многочленов, при котором упомянутые операции выполнялись бы достаточно эффективно. Ясно, что при выполнении операций над многочленом необходимо выделять его отдельные члены. В частности, в каждом члене необ- ходимо выделить переменные, коэффициент и показатели степеней переменных. Обобщая результаты п. 3-5, многочлен можно представить в памяти с помощью трехмерного массива. Но, как следует из обсуждения в начале этого параграфа, такое представление будет неудовлетворительным, поэтому необходимо использовать свя- занное представление. Прежде чем описать, как это можно осу- ществить, рассмотрим различные классы операций, выполняемых над линейными связанными списками. В приложениях любого типа данные должны обрабатываться с помощью соответствующих операций. Если такая обработка выполняется на ЭВМ, то прежде всего необходимо найти аде- кватное представление данных в памяти вычислительной машины. Трудность и сложность этой задачи определяется в основном язы- ком программирования, используемым для реализации алгоритма задачи. Был создан ряд языков, специально предназначенных для обработки связанных списков. Наиболее широко из них известен язык ЛИСП 1.5. В других случаях в язык включаются проце- дуры или функции, оформленные как подпрограммы и выполня- ющие требуемые операции над списками. Примером этого подхода может служить язык обработки списков SLIP, который состоит из набора подпрограмм, написанных на ФОРТРАНе. Мы для представления некоторых общих операций над списками будем Использовать язык ПЛ/1. Операции, выполняемые над связанными списками, можно раз- делить на трн класса. В первом классе содержатся те операции, которые не зависят от информации, хранящейся в узлах списка. К этим операциям относятся создание, включение, исключение и поиск узла. Языки программирования, позволяющие обрабаты- вать списки, обычно имеют встроенные операции этого типа. Ко второму классу относятся операции преобразования ин- формации из внешнего представления в соответствующее внутрен- нее представление в памяти ЭВМ и обратная операция преобра- зования из внутреннего представления в форму, воспринимаемую 283
человеком. Очевидно, что эти операции зависят от данных, поэ- тому мы ограничимся рассмотрением только тех свойств, которые зависят от структуры данных. Языки обработки списков имеют встроенные стандартные программы для выполнения некоторого набора таких операций, но все дополнительные средства програм- мируются отдельно. Наконец, в последний класс входят те операции, которые не- обходимо запрограммировать для выполнения обработки данных в конкретном случае. Например, при обработке многочленов в эту группу входят операции сложения, вычитания, умножения, де- ления, дифференцирования и интегрирования многочленов. Если программист имеет средства для реализации операций всех трех классов, то задача программирования алгоритма становится много проще. Рассмотрим вопрос описания линейного односвязаниого спи- скового представления и операций над ним в системе записи ал- горитмов. Типичный узел списка состоит из нескольких полей, в каждом из которых, кроме одного (обычно последнего), нахо- дится целое число, действительное число и т. д. Последнее поле называется указателем, в нем находится адрес следующего узла списка. В качестве примера рассмотрим представление члена какого- либо многочлена с переменными х, у и z. Типичный узел состоит из пяти полей (рис. 4-1.2, а), которые в совокупности будем называть TERM. В первых трех полях содержатся показатели степеней переменных х, у и z соответ- ственно, в четвертом и пятом полях — значение коэффициента при данном члене и адрес следующего члена. Узел списка, соот- ветствующий члену 3 ху, изображен на рис. 4-1.2, б. Выделение отдельного поля в узле списка, представляющего собой многочлен, выполняется просто. Наша система записи .алгоритмов позволяет обратиться к любому полю узла с помощью указатели этого узла. Так, COEFF (Р) обозначает поле коэффи- циента в узле, адресуемом указателем Р. Аналогично показатели степеней переменных х, у и z записываются в виде POWER_ POWER-X POWER_Y POWER_Z COEFF LINK TERM 12 If 1° I jH7FFI7FH,> 1г 1° I' 1ЯН c H ’ I ’ и 1, ® Фмс. 4-1.2. Списковое представление многочлена с тремя переменными 284
X (P),POWER_Y (P) и POWERJZ (P) соответственно, a к указателю следующего узла можно обратиться, используя поле LINK (Р). В качестве примера рассмотрим представление многочлена 2х2 + 5ху + у2 + yz в виде связанного списка. Предположим, что узлы должны рас- полагаться в порядке убывания значений степеней, т. е. узел, адресуемый указателем Р, предшествует узлу, адресуемому ука- зателем Q, при POWER_X (Р), большем POWER.X (Q); если они равны, то при POWER.Y (Р), большем POWER_Y (Q), а если и они равны, то при POWER_Z (Р), большем POWER Z (Q). Список, соответствующий этому многочлену, показан на рис. 4-1.2, в. Рассмотрим более сложную задачу о включении узла в свя- занный список. Для выполнения этой операции потребуется не- сколько шагов. Во-первых, в результате операции ввода или с по- мощью вычислений получают значения информационных полей нового узла. Во-вторых, выбирают узел из списка свободных эле- ментов. Наконец, значения информационных полей копируются в соответствующие поля нового узла, который затем включается в связанный список. Новый узел связывается с узлом, следующим за ним в списке, ,путем занесения в поле указателя нового узла адреса узла, следующего за ним. Предполагается, что во всех алгоритмах, рассматриваемых в этом параграфе, имеется некоторая область свободной памяти, из которой можно запрашивать узлы. Для алгоритмов обработки многочленов это записывается в виде специального оператора при- сваивания: установить Р TERM. Он создает новый узел, состоящий из пяти описанных выше полей, и присваивает адрес первого из этих полей указателю Р. При создании узла значения полей POWER_X (Р), POWERJY (Р), POWER Z (Р), COEFF (Р), LINK(P) не опре- делены. Если узел больше не нужен, его можно возвратить в сво- бодную память. Это выполняет команда «Вернуть узел, адресу- емый указателем Р, в свободную память». После этой операции узел становится недоступным, а значение указателя Р неопреде- ленным. Более подробно получение новых узлов из свободной памяти и возврат узлов в нее описывается в следующем параграфе. Теперь можно сформулировать алгоритм включения члена многочлена в связанный список. Алгоритм POLY FRO NT. При заданной структуре узла TERM и свободной памяти, из которой можно получать узлы, требуется включить узел в связанный список таким образом, чтобы он пред- шествовал узлу, адрес которого находится в указателе FIRST. Поля нового узла обозначаются переменными NX, NY, NZ, NCOEFF, что соответствует показателям степеней для х, у, z 285
и коэффициенту члена. Р — вспомогательная переменная типа: указателя. 1. [Получение узла из свободной памяти.] Установить Р <= <= TERM. 2. [Формирование значений полей. ] Установить POWER X (Р) +- NX, POWER_Y (Р) NY. POWERS (Р) ч- Ч- NZ, COEFF (Р) ч- NCOEFF. 3. [Связывание элемента со списком. ] Установить LINK (Р) ч-FIRST, POLYFRONT ч-Р и закончить выполнение алгоритма. Алгоритм POLY FRO NT все включения узлов выполняет в один конец связанного списка, хотя их можно включать также и в дру- гой конец или середину списка. Многочлен нулевой степени, не содержащий ни одного члена, представляется значением указа- теля NULL. До тех пор, пока в список ие включен ни один узел, указатель первого элемента списка, называемый POLY, имеет значение NULL. Заметим, что при выполнении сложения или вычитания многочленов можно получить многочлен нулевой сте- пени в результате сокращения подобных членов. Обращение к алгоритму производится, как к функции с именем POLYFRONT. Перед выходом из алгоритма функ- ции POLY FRO NT присваивается значение адреса созданного- узла, и это значение возвращается в точку вызова. Покажем, как из многочлена нулевой степени путем много- кратного вызова алгоритма POLYFRONT можно построить свя- занный список, соответствующий любому многочлену. Для полу- чения многочлена 2х2 4- 5ху + у2 + yz алгоритм должен вы- полняться 4 раза. Покольку мы хотим, чтобы первым узлом списка был узел, соответствующий члену 2х2, включение следует начать с члена yz, затем включается член у® и т. д. На рис. 4-1.3 изображены последовательность и результаты вызовов процедуры Рис. 4-1.3. Построение спискового представления многочлена 2х£ -р 5ху + у2 + Уг алгоритмом POLYFRONT POLY 286
POLYFRONT при условии, что POLY — указатель первого узла , списка. Рассмотрим теперь алгоритм, выполняющий включение узла в конец связанного списка. Алгоритм POLYEND. (Включение узла в конец связанного ; списка.) При заданной структуре узла TERM и свободной памяти, из которой можно получить узлы, требуется включить новый узел в коиец связанного списка, если адрес начала списка содержится ' в указателе FIRST. Поля нового узла обозначены NX, NY, NZ 1 и NCOEFF, что соответствует показателям степеней аргументов х, -j у и z и коэффициенту члена. NEW — указатель, содержащий ; адрес нового узла. 1. [Получение узла из свободной памяти. ] Установить NEW <= i TERM. 2. [Копирование значений полей.] Установить POWER X (NEW) ч-NX, POWER. Y (NEW) ч- NY, , POWER _Z (NEW) ч—NZ, COEFF (NEW) ч- NCOEFF, LINK (NEW) ч-NULL. 3. [Список пуст?] Если FIRST = NULL, то установить ; POLYEND ч— NEW и закончить выполнение алгоритма. 4. [Подготовка к поиску конца списка. ] Установить SAVE ч-FIRST. 5. [Поиск конца списка.] Повторять до тех пор, пока LINK (SAVE) =£ NULL: установить SAVE <- LINK (SAVE). 6. [Связывание нового узла со списком.] Установить 1 LINK (SAVE) ч-NEW. 7. [Возврат адреса начала списка в точку вызова. ] Установить < POLYEND ч-FIRST и закончить выполнение алгоритма. ' В алгоритме POLYEND при включении нового узла в конец ] списка, адрес начала которого определяется указателем FIRST, [ 287 '
Вопросы программной реализации связанного распределения рассматриваются как с точки зрения моделирования с использо- ванием массивов, так и с точки зрения средств* имеющихся в языке ПЛ/1 и позволяющих программисту определять собственные типы данных. Первый подход обычно используется при программирова- нии связанно представленных структур на языках ФОРТРАН, АЛГОЛ 60, БЕЙСИК, не имеющих указательных переменных, тогда как второй подход используется в языках, имеющих указа- тели (ПЛ/1, ЛИСП 1.5, СНОБОЛ, АЛГОЛ 68, АЛГОЛ W). Если ие оговорено обратное, предполагается, что типичный элемент, или узел, списка состоит из двух полей, а именно, из информационного поля INFO и поля указателя LINK- Имя типич- ного узла обозначается NODE. Графически структура узла обо- значается следующим образом: NODE INFO LINK В дальнейшем предполагается, что область свободной памяти для такой структуры узла организована в виде связанного стека сво- бодных узлов, как показано на рис. 4-2.1, а, где указатель AVAIL содержит адрес верхнего элемента стека. Теперь можно сформулировать задачу получения узла из стека свободных узлов. Предположим, что адрес следующего свободного узла должен присваиваться переменной NEW. Так как стек сво- бодных узлов содержит конечное число узлов, то необходимо проверить условие отсутствия свободных узлов. Факт отсутствия узлов определяется значением указателя AVAIL, равным NULL. Если же узлы имеются, то оче- редной верхний элемент стека Ссылка на узел, AVAII^ । 1 NEW । 1 ] следующий - I I | 1 *1 1 1 то данным В списке Л В который р, j . |~|дуд|1, ।—узел 1 । । j а) б) Рис. 4-2.1. Стек свободных узлов до и после получения из него узла-. а — до включения; б — после включения Ссылка на список из которого исклю- • чается узел FREE | —р AVAIL—| 111 i I I Еф J 1 ш ш я) S) Рис. 4-2.2. Стек свободных узлов: а — до возвращения в него узла; б — йосле возвращения 290
обозначается LINK (AVAIL). Далее могут быть заполнены поля . узла, соответствующего значению указателя NEW, а полю ; LINK (NEW) присвоено значение, определяющее положение еле- J дующего узла списка. Стек свободной памяти до и после получе- ния из него узла изображен на рис. 4-2.1, а и б. Аналогичная процедура может быть определена и для возврата исключенного узла в стек свободной памяти. Если адрес исклю- ченного узла находится в переменной FREE, то полю связи этого узла присваивается значение AVAIL, а значение FREE при- сваивается переменной AVAIL. Данный процесс изображен ' иа рис. 4-2.2. . Теперь можно сформулировать алгоритм, который выполняет включение узла в связанный линейный список с использованием ’ стека. j Алгоритм INSERT. Заданы связанный линейный список, узлы ’ которого состоят из ранее описанных полей INFO и LINK, и ука- затель AVAIL, определяющий верхний узел в стеке свободной < памяти. Требуется включить узел в связанный список перед пер- вым узлом, адрес которого задается указателем FIRST. Значение ’ информационного поля нового узла определяется переменной X. , NEW — переменная типа указателя. 1. [Стек пуст? 1 Если AVAIL = NULL, то печатать сообщение ’СТЕК СВОБОДНЫХ УЗЛОВ ПУСТ’ и закончить выполнение • алгоритма. 2. [Получение адреса очередного свободного узла. 1 Установить NEW +- AVAIL. 3. [Исключение узла из стека. ] Установить AVAIL ч- ч- LINK (AVAIL). 4. [Копирование информации в новый узел и связывание его со списком.] Установить INFO (NEW) ч- X и LINK (NEW) ч- ч- FIRST. 5. [Возврат адреса нового узла. ] Установить INSERT ч- NEW i и закончить выполнение алгоритма. INSERT вызывается как функция, которая возвращает зна- 1 чение указателя в переменную FIRST (т. е. FIRST ч— . ч INSERT (X, FIRST). Алгоритм INSERT очень похож на алго- ритм POLYFRONT из п. 4-1, за исключением дополнительных деталей, связанных с обработкой запроса нового узла из области свободной памяти. j Теперь мы приведем алгоритм включения узла в конец свя- занного списка. Он аналогичен алгоритму POLYEND, приведен- | ному в п. 4-1, за исключением деталей запроса нового узла. Алгоритм IN SEND. (Включение узла в конец связанного линейного списка.) Заданы связанный линейный список, типич- ный узел которого состоит из поля INFO и поля связи, и указа- тель AVAIL, определяющий верхний узел в стеке свободной : памяти. Требуется включить узел в конец списка, первый узел I которого задается указателем FIRST. В информационное поле . Ю* 291
нового узла должно быть занесено значение переменной X. NEW и SAVE — указательные переменные. 1. [Стек пуст?} Если AVAIL = NULL, то печатать сообщение ’СТЕК СВОБОДНЫХ УЗЛОВ ПУСТ’ и закончить выполнение алгоритма. 2. [Получение адреса свободного узла. ] Установить NEW ч- ч- AVAIL. 3. [Исключение узла из стека. ] Установить AVAIL ч— LINK (AVAIL). 4. [Копирование информации в новый узел.} Устано- вить INFO (NEW) X и LINK (NEW) ч- NULL. 5. [Список пуст? 1 Если FIRST — NULL, то установить IN SEND ч- NEW и закончить выполнение алгоритма. 6. [Инициализация поиска конца списка. J Установить SAVE ч-FIRST. 7. [Поиск конца списка.} Повторять до тех пор, пока LINK (SAVE) NULL: установить SAVE LINK (SAVE). 8. [Формирование значения поля связи последнего узла. ] Установить LINK (SAVE) ч-NEW. 9. [Возврат указателя первого узла списка.} Установить INSEND ч-FIRST и закончить выполнение алгоритма. Во многих задачах желательно иметь упорядоченные линейные списки. Упорядочение осуществляется по возрастанию или по убы- ванию значения поля INFO. Такое упорядочение часто позволяет намного увеличить эффективность обработки. Например, в задаче с многочленами, обсуждавшейся в предыдущем параграфе, число операций, необходимое для сложения двух многочленов степени п, уменьшается от п2 до п, если оба многочлена упорядочены по убыванию степеней аргументов х, у и z. Следующий алгоритм выполняет включение узла в список, в котором узлы упорядочены по возрастанию значения поля INFO. Алгоритм IN SO RD. Заданы связанный линейный список, узлы которого состоят из полей INFO и LINK, a FIRST — указывает на первый узел списка, AVAIL — указатель верхнего узла в стеке свободной памяти. Требуется включить новый узел, сохраняя упорядоченность узлов в списке по возрастанию значения поля INFO. Значение информационного поля нового узла определяется переменной X. NEW и SAVE — переменные типа указателя. 1. [Список пуст?] Если AVAIL = NULL, то печатать сообще- ние ’СТЕК СВОБОДНЫХ УЗЛОВ ПУСТ’ и закончить выполне- ние алгоритма. 2. [Получение адреса нового узла. 1 Установить NEW ч— AVAIL. 3. [Исключение узла из стека. ] Установить AVAIL ч— ч-LINK (AVAIL). 4. [Копирование информации в новый узел. ] Установить INFO (NEW)4-X, 292
5. [Если список пуст, то присваивание полю LINK значения NULL и выход. 1 Если FIRST = NULL, то установить LINK (NEW) ч—NULL, INSORD ч-NEW и закончить выпол- нение алгоритма, 6, [Если новый узел предшествует первому узлу списка, то включение его в начало списка, 1 Если INFO (FIRST) INFO (NEW), то установить LINK (NEW) +- FIRST, IN SO RD ч- NEW и закончить выполнение алгоритма. 7. [Инициализация рабочего указателя,] Установить SAVE ч- ч-FIRST. 8. [Поиск предшественника нового узла.] Повторять до тех пор, пока LINK (SAVE) 4= NULL и INFO (LINK (SAVE)) < INFO (NEW): установить SAVE ч- LINK (SAVE). 9. [Формирование значений полей связи нового узла и его предшественника.] Установить LINK (NEW)LINK (SAVE), LINK (SAVE) ч-NEW. 10. [Возврат указателя первого узла.] Установить INSORD ч-FIRST и закончить выполнение алгоритма. Сначала в алгоритме рассматривается случай пустого списка, при котором поле LINK нового узла устанавливается равным значению NULL. Затем рассматривается случай, когда новый узел должен быть вставлен перед первым узлом исходного списка. В результате новый узел становится первым узлом обновленного списка, а в поле связи нового узла заносится адрес первого узла исходного списка. В третьем, и последнем случае значение указа- теля FIRST не равно NULL, а значение поля INFO первого узла списка меньше значения поля INFO узла, который должен быть вставлен в список. В рабочую переменную SAVE заносится адрес первого узла списка. Затем с ее помощью последовательно про- сматриваются все узлы списка до тех пор, пока значение INFO (LINK (SAVE)) не станет больше значения поля нового узла либо не будет достигнут конец списка. В любом случае в поле связи нового узла помещается адрес узла из поля связи узла, адресуемого рабочей переменной, а иа его место заносится адрес нового узла. Путем многократного выполнения алгоритма INSORD можно легко построить любой упорядоченный связанный линейный список. Например, последовательность операторов FRONT NULL FRONT*- INSORD (29, FRONT) FRONT*- INSORD (10, FRONT) FRONT*- INSORD (25, FRONT) FRONT*- INSORD (40, FRONT) FRONT*- INSORD (37, FRONT) создает пятиэлементный список. Процесс такого построения отоб- ражен на рис. 4-2.3. 293
Рис. 4-2.3. Построение связанного упорядоченного линейного списка с помощью алго- ритма JNSORD Теперь, после обсуждения нескольких алгоритмов включения, рассмотрим еще один столь же важный алгоритм — алгоритм исключения узла из связанного списка. Алгоритм DELETE. (Исключение узла из связанного линей- ного списка). Заданы переменная FIRST, содержащая адрес пер- вого узла связанного линейного списка, переменная X, содержа- щая адрес узла, который должен быть удален, и переменная NEXT, значение которой представляет адрес следующего узла в списке. Требуется исключить узел с адресом X. 1. [Список пуст?] Если FIRST = NULL, то печатать сообще- ние ’СПИСОК ПУСТ* и закончить выполнение алгоритма. 2. [Исключить первый узел?] Если X = FIRST, то установить FIRST ч- LINK (FIRST) и перейти к шагу 9. 3. [Инициализация поиска предшественниках.] Установить NEXT ч-FIRST. 4. [Изменение PRED. ] Установить PRED ч—NEXT. 5. [Выбор следующего узла.] Установить NEXT ч- ч-LINK (NEXT). 6. [Конец списка? ] Если NEXT NULL, то печатать сообще- ние ’УЗЕЛ НЕ НАЙДЕН’ и закончить выполнение алгоритма. 7. [Найден узел X?] Если NEXTX, то вернуться к шагу 4. 8. [Исключение X.] Установить LINK (PRED) ч-LINK (X). 9. [Возврат узла в стек свободной памяти. ] Установить LINK (X) ч— AVAIL, AVAIL ч- X и закончить выполнение алго- ритма. 294
В первом шаге проверяется ситуация пустого списка. Во вто- ром шаге определяется, не является ли первый узел тем узлом, * который должен быть исключен, и если это так, то обновленный список будет начинаться со второго узла. Если список при этом состоит из единственного узла, то в результате исключения указа- -тель FIRST получит значение NULL. > Если X не является адресом первого узла списка, то ищется узел, предшествующий исключаемому узлу (в алгоритме адрес предшествующего узла обозначен PRED). Поиск осуществляется прохождением списка и запоминанием адреса очередного узла в переменной NEXT до тех пор, пока не будет найдеи узел с адре- сом X. Значение переменной NEXT, равное NULL свидетель- ствует о том, что узел, который требуется исключить, не найден, [и произошла ошибка. Когда переменные NEXT и X будут иметь одинаковые значения, значение поля связи узла, предшеству- ющего узлу с адресом X, можно заменить адресом узла, следу- ющего за исключаемым. Это выполняется в шаге 8 алгоритма. Исключенный узел возвращается в пул свободной памяти в послед- [нем шаге алгоритма. В алгоритме DELETE предполагается, что адрес исключаемого узла известен с самого начала. Но так, однако, бывает не всегда. Очень часто исключаемый узел задается значением поля INFO. В этом случае отыскивается узел с заданным значением. Преды- дущий алгоритм можно легко модифицировать с учетом такой возможности. В качестве последнего примера опишем алгоритм, выполня- ющий копирование связанного линейного списка. Алгоритм COPY. Заданы связанный линейный список, каждый узел которого состоит из информационного поля (INFO) и указа- теля (LINK), и FIRST — указатель первого узла списка. Тре- буется построить алгоритм, копирующий этот список. Новый список должен состоять из узлов, информационные поля и поля указателей которых обозначаются FIELD и PTR соответственно. Адрес нового узла вновь созданного списка должен быть присвоен переменной BEGIN. NEW, SAVE, PRED — переменные типа указатель. 1. [Список пуст?] Если FIRST = NULL, то установить BEGIN ч- NULL н закончить выполнение алгоритма. 2. [Копирование первого узла. ] Если AVAIL = NULL, то печатать сообщение ’СТЕК СВОБОДНЫХ УЗЛОВ ПУСТ’ и за- кончить выполнение алгоритма; в противном случае установить NEW ч- AVAIL, AVAIL ч- LINK (AVAIL), FIELD (NEW) ч- ч— INFO (FIRST), BEGIN ч- NEW. 3. [Подготовка к прохождению списка. ] Установить SAVE ч- FIRST. 4. [Переход к следующему узлу?] Повторять шаги 5 и 6 до тех пор, пока LINK (SAVE) ф NULL. 295
5. [Модификация указателей.] Установить PRED-<-NEW, SAVE ч-LINK (SAVE). 6. [Копирование узла. 1 Если AVAIL = NULL, то печатать сообщение ’СТЕК СВОБОДНЫХ УЗЛОВ ПУСТ’ и закончить выполнение алгоритма; в противном случае установить NEW ч- ч-AVAIL, AVAIL ч-LINK (AVAIL), FIELD (NEW) ч- ч- INFO (SAVE), PTR (PRED) <- NEW. 7. [Оформление конца скопированного списка. ] Установить PTR (NEW) ч- NULL и закончить выполнение алгоритма. В первом шаге алгоритма проверяется, пуст ли копируемый список, во втором копируется первый узел исходного списка. Оставшиеся шаги алгоритма последовательно копируют узлы исходного списка. Заметим, что адрес каждого вновь создава- емого узла должен быть помещен в поле LINK узла, предшеству- ющего данному узлу нового списка. Обратимся теперь к аспектам программирования связанных структур. Вначале смоделируем такие структуры с помощью массивов. Затем обсудим возможность использования для пред- ставления линейных связанных списков средств языка ПЛ/1, позволяющих программисту определять новые типы данных. Итак, рассмотрим задачу реализации связанных линейных списков с помощью массивов. Как уже упоминалось, в ряде язы- ков программирования, таких как ФОРТРАН, АЛГОЛ 60, БЕ11СИК, связанные структуры не могут быть определены про- граммистом, поскольку в этих языках отсутствуют переменные типа указателя. Хотя в языке ПЛ/1 и есть аппарат указателей, тем не меиее ниже этот язык будет использоваться для предста- вления связанного распределения только с помощью массивов. Предлагаемые методы непосредственно применимы к языкам, в которых отсутствуют данные типа указателя. При реализации с помощью массивов узел списка состоит из нескольких полей, каждое из которых, кроме одного (обычно последнего), может содержать целое нли действительное число, символьную строку и т. д. Последнее поле является указателем следующего узла в линейном списке и представляет индекс или номер элемента массива. Индекс указывает на следующий узел, т. е. элемент массива, в линейном списке. Легко видеть, как индекс вместе с име- нем поля позволяют выделить любое поле конкретного узла. Рассмотрим пример представления многочлена от переменных X, Y и Z, обсуждавшийся в п. 4-1. Предполагая, что любой многочлен имеет ие более 25 членов, можно описать пять векторов: POWER—X, POWER—Y, POWER—Z, COEFF и LINK, содержащих индекс следующего узла списка. Рассмотрим представление многочлена 2х2 Ц- 5ху Ц- у2 -|- уz в виде связанного линейного списка. 296
Допустим, что связанное упорядоченное представление этого многочлена с помощью названных пяти векторов будет иметь вид POWER.X (2) =« 2 POWER.Y (2) = О POWERZ (2) = О COEFF (2) = 2 LINK (2) = 10 POWER-X (5) = 0 POWER У (5) = 1 POWER.Z (5) = 1 COEFF (5) = ! LINK (5) = 0 POWER_X (10) = 1 POWER, У (10) = 1 POWER.Z (10) = 0 COEFF (10) = 5 LINK (Ю) = 21 POWER-X (21) = 0 POWER.Y (21) =2 POWER_Z (21) = 0 COEFF (21) =1 LINK (21) = 5 Первый узел этого списка определяется путем занесения индексного значения 2 в переменную FIRST. LINK (2) дает зна- чение индекса второго узла списка. При известном значении поля LINK (2), которое в данном примере равно 10, можно получить доступ к полям второго узла, записывая COEFF (LINK (2)) и т. д. Индекс третьего узла в списке определяется LINK (10) и равен 21. Если известен индекс первого узла, то, следуя указателям, можно легко получить все узлы списка. Последний узел списка имеет индекс 5, а значение его поля связи равно нулю. Заметим, что при использовании массивов NULL представляется нулем. Здесь нужно пояснить одно обстоятельство. Поля в узле по определению размещены в памяти последовательно. Однако в случае приведен- ного выше представления связанных списков это не так. Поле POWERJC (2) не является в памяти смежным с полем POWER_Y (2). Причина этого заключается в последовательном размещении векторов в памяти. Альтернативное представление связанных списков может быть выполнено с помощью двумерных массивов. Опишем таблицу NODE, состоящую из 25 строк и 5 столбцов. Каждая строка такой матрицы является узлом списка, и, в частности, каждый элемент строки соответствует одному полю узла. Представление много- члена из нашего примера в этом случае будет иметь вид NODE (2,1) = 2 NODE (2,2) = 0 NODE (2,3) = 0 NODE (2,4) = 2 NODE (2,5) =10 NODE (5,1) = 0 NODE (5,2) = 1 NODE (5,3) = 1 NODE (5,4) = 1 NODE (5,5) = 0 NODE (10,1) = 1 NODE (10,2) = 1 NODE (10,3) = 0 NODE (10,4) = 5 NODE (10,5) = 21 NODE (21,1) = 0 NODE (21,2) = 2 NODE (21,3) = 0 NODE (21,4)= I NODE (21,5) = 5 где NODE (1,1), NODE (I, 2), NODE (I, 3), NODE (I, 4) и NODE (I, 5) соответствуют полям POWER________X, POWER_______Y, POWER_____Z, COEFF и LINK некоторого узла. Такое представле- ние обеспечивает расположение пяти полей, каждое из которых соответствует строке матрицы, в смежных областях памяти, по- скольку массивы в ПЛ/1 размещаются в памяти по строкам. Однако достоинства этого подхода неочевидны, так как исполь- зование двойной индексации увеличивает время выполнения программы, не упрощая при этом процесс программирования. 297
Используя одномерный вектор из 125 элементов, можно по- строить еще одно представление, которое также сохраняет после- довательное расположение полей узла в памяти. При этом группа из пяти последовательно расположенных элементов вектора пред- ставляет собой узел списка, начальный индекс группы кратен пяти. Если вектор называется POLY, а индекс первого поля узла I, то имеет место соответствие: POLY .(I) >POWE R— X POLY (I -}- !)<-->POWER_Y POLY (I -}- 2)<—>POWER—Z POLY (I 4- 3)<-->COEFF POLY (I + 4)<—>LINK Этот способ представления связанных линейных списков по эффективности близок к способу с пятью векторами. Выделение отдельного поля из узла в нашем примере с много- членами является простой операцией. Используя представление с пятью векторами: POWER_______.X, POWER____Y, POWER______Z, COEFF и LINK, можно выделить любое поле, если задан индекс Р этого узла. Коэффициент члена, определяемого индексом Р, обозначается в виде COEFF (Р), показатель степени аргумента X — в виде POWER______X (Р), а указатель следующего узла — LINK(P). Выделение поля любого узла является полным, т. е. POWER____X (Р), COEFF (Р), LINK (Р) и т. д. могут использо- ваться в качестве как левого, так и правого операнда оператора присваивания. Теперь, используя массивы, попробуем написать программы некоторых алгоритмов, рассмотренных выше в настоящей главе. В каждом из этих алгоритмов новый узел запрашивался из области свободной памяти. Если для программирования связанных линей- ных списков используются массивы, то программист сам должен управлять свободной памятью, которая обычно организуется в виде связанного стека. Всякий раз, когда требуется свободный узел, из стека исключается верхний элемент, который исполь- зуется затем как новый узел. Стек свободной памяти для примера с многочленами может быть построен с помощью процедуры A LIST, написаиной на языке ПЛ/1 и приведенной на рис. 4-2.4. Эта программа создает связанный линейный список, содержащий 100 узлов. Вначале значения полей LINK упорядочены в том смысле, что LINK (I) > I, где I — иидекс (или адрес) I-го узла. Однако по мере того, как узлы запрашиваются и возвращаются в стек, этот порядок нарушается. Указатель AVAIL, установлен- ный сначала в 1, определяет индекс, соответствующий верхнему элементу стека. Предполагается, что вектор LINK, также как и указатель AVAIL, являются глобальными переменными для процедуры. Теперь можно сформулировать задачу получения узла из стека свободной памяти. Предполагается, что адрес очередного свобод- ного узла должен быть помещен в переменную NEW. Напомним, 298
ALIST: PROCEDURE; /<- PROCEDURE TO CONSTRUCT Д LINKED STACK O₽ AVAILABLE MOOES FOR A POLYNOMIAL IN THREE VARIABLES. <*/ DECLARE I FIXED DECIMAL; /* SEI THE .-INK FIELD OF EACH NODE TO POINT IO ITS SUCCESSOR «V DO 1 - 1 TO 99; L INK ( I 1 = I t 1; END; L1NKI100) = 0; /о SET POINTER FIELD OF LAST NODE TO EMPTY V/ AVAIL = 1; /« SET TOP OF STACK POINTER 'AVAIL' TO ONE «/ RETURN! ENO ALIST; Рис. 4-2.4. Процедура построения связанного стека свободных узлов что нз практических соображений список свободной памяти содер- жит конечное число элементов (100 в нашем примере), так что необходимо проверять наличие узлов в стеке. Программа получе- ния свободного узла из стека свободной памяти имеет вид /*PROGRAM ТО OBTAIN A FREE NODE FROM THE AVAILABILITY STACK */ /*CHECK FOR UNDERFLOW */ IF (AVAIL = 0) THEN GO TO—; /* OBTAIN POINTER VALUE FOR A NEW NODE AND STORE IT IN NEW */ NEW = AVAIL; /* OBTAIN ADDRESS OF NEW TOP OF STACK NODE */ AVAIL = LINK(AVAIL); В программе опущена метка перехода при нехватке элементов в стеке. Положение этой метки в программе зависит от того, ка- кого рода действия необходимо предпринять в случае исчерпания стека. Если стек не пуст, могут быть заполнены поля узла, соот- ветствующего указателю NEW, а также установлено значение поля LINK (NEW), представляющее собой адрес узла, который должен следовать за этим новым узлом. Аналогичная программа может быть написана н для возврата узла в стек свободной памяти. Если указателем исключаемого узла служит переменная FREE, то в поле связи этого узла за- сылается текущее значение указателя AVAIL, а значение FREE становится новым значением указателя AVAIL. Программа для выполнения этой операции такова: /*PROGRAM ТО RETURN A DISCARDED NODE ТО AVAILABILITY STACK */ /*CHANGE THE LINK FIELD OF THE DISCARDED NODE TO POIN TO THE PREVIOUS TOP ELEMENT IN STACK */ . LINK (FREE) = AVAIL; /*THE DISCARDED NODE BECOMES THE NEW TOP ELEMENT OF STACK */ AVAIL == FREE; 29Э
POLYFRONT-’ PROCEDURE!NX,Ny,NZ,NCOEFF,F 1R$T) RETURNS(FIXED DECIMAtli A PROCEDURE ТНДТ INSERTS A TERM OF fi THREE VARIABLE POLYNOMIAL AT THE FRONT OF A LINKED LINEAR LISI. »/ DECLARE tNX,NY,NZ,NCOEFF,FIRST,P) FIXED DECIMAL! IF (AVAIL <= 0» /* CHECK FQR AVAILABILITY STACK UNDERFLOW */ TH6N DO: PUT SKIP EDIT!’AVAILABILITY STACK UNDERFLOW'» I Al: STO₽: END; P = AVAIL; /* OBTAIN A NODE FROM AVAILABLE STORAGE «/ avail = link<avail j; /« INITIALISE NUMERIC FIELDS */ POWER_xiP» = nx; POWER_v<₽) = Nv; POWER.ZLPI » NZ; COEFELP» ~ NCOEFF; LINKJPJ = FIRST; Z° FIRST NODE POINTER «/ RETURN!PJ; END POLYFRONT: Рис, 4-2.S. Процедура, реализующая алгоритм POLYFRONT Описанные приемы программирования могут быть объединены в процедуре, реализующей алгоритм POLYFRONT из п. 4-1. В процедуре на языке ПЛ/1, приведенной на рис. 4-2.5, пред- полагается, что векторы POWER____X, POWER____Y, POWER_____Z, COEFF, LINK и переменная AVAIL являются глобальными. Теперь, когда возможно обращение к функции POLYFRONT, для построения связанного линейного списка обращение к ней необходимо начать с многочлена, имеющего нулевую степень, и повторять его до тех пор, пока в список не будут введены все члены многочлена. Для многочлена 2х2 + 5ху + у® -F yz к функ- ции включения необходимо обратиться 4 раза. Поскольку мы хотим, чтобы первый узел списка соответствовал члену 2х2, про- цесс включения должен начинаться с члена yz, за ним следует У2 н т. д. Если указателем первого узла списка является POLY, то данный многочлен будет построен при выполнении следующих шагов программы: /* INITIALIZE LIST POINTER ТО NULL */ DECLARE POLY FIXED DECIMAL INITIAL (0); /* INSERT THE LAST TERM OF POLYNOMIAL */ POLY = POLYFRONT (0, 1, 1, 1, POLY); /*INSERT THE THIRD TERM OF POLYNOMIAL*/ POLY = POLYFRONT (0, 2, 0, 1, POLY); /* INSERT SECOND TERM OF POLYNOMIAL */ POLY = POLYFRONT (1, I, 0, 5, POLY); /* INSERT FIRST TERM OF POLYNOMIAL*/ POLY = POLYFRONT (2, 0, 0, 2, POLY); 300
POLY END: PROCEDURE!NX,NV,NZ,NCOEFF,FIRST I RETURNS(FIXEO DECIMAL); !*> h PROCEDURE THAT INSERTS A TERM OF A THREE VARIABLE POLYNOMIAL AT THE END Op A LINKED LINEAR LIST. DECLARE (NX,NY,NZ,NCOEFF,FIRSI,NEW,SAVE I FIXED DECIMAL; IF (AVAIL <= 0) /* CHECK FOR AVAILABILITY STACK UNDERFLOW e/ THEN DO; PUT SKIP EDIT!'AVAILABILITY STACK UNDERFLOW) I A); STOP: END; NEW = AVAIL; /« OBTAIN A NODE FROM AVAILABLE STORAGE AVAIL = LINK1AVAIL1: /« INITIALIZE FIELDS */ - ' PDWER.XINEW) = NX; POWER_Y(NEW> = NY; POWER_Z(NEW) = NZ; COEFFINEWj = NCOEFF; LINK(NEW) = 0; /* IS LIST EMPTY? «/ IF (FIRST « 01 THEN RETURN(NEW); /« INITIATE SEARCH FOR LAST NODE «-/ SAVE = FIRST; /* SEARCH FOR END OF LIST */ DO WH1LE(LINK(SAVE! -= 0); SAVE - lINMSAVE); END; LINK(SAVE) = NEW; /* SET LINK FIELD OF LAST NODE TO NEW •/ RETURNIFIRST)i /• RETURN FIRST NODE POINTER ♦/ END POLYEND; Рис. 4-2.6. Процедура, реализующая алгоритм POLYEND Аналогично написана процедура на ПЛ/1, реализующая алго- ритм POLYEND (рис. 4-2.6). Здесь опять предполагается, что пять векторов и переменная AVAIL являются глобальными для данной процедуры. Фрагмент программы для формирования многочлена 2х2 4- + 5ху у2 -р yz имеет вид /* INITIALIZE LIST POINTER ТО NULL*/ DECLARE POLY FIXED DECIMAL INITIAL (0); /* INSERT FIRST TERM OF POLYNOMIAL*/ POLY = POLYEND (2, 0, 0, 2, POLY); /* INSERT SECOND TERM OF POLYNOMIAL*/ POLY = POLYEND (1, 1, 0, 5, POLY); /*INSERT THIRD TERM OF POLYNOMIAL */ POLY = POLYEND (0, 2, 0, I, POLY); /* INSERT LAST TERM OF POLYNOMIAL*/ POLY = POLYEND (0, 1, 1, 1, POLY); Процедура на языке ПЛ/1, реализующая алгоритм DELETE, приведена на рис. 4-2.7. Предполагается, что векторы INFO, 301
DELETE: PROCEDURE(X,FIRST); /о A PROCEDURE FOR DELETING A NODE FROM A LINKED LINEAR LIST WHOSE NODES CONTAIN TWO FIELDS NAMED INFO ANO LINK. THE DISCARDED NODE IS RETURNED TO THE AVAILABILITY AREA. «/ (X,FIRST,PRED,NEXT! FIXED DECIMAL: IF {FIRST = OJ /• CHECK FOR EMPTY LIST «/ THEN do; PUT SKIP EDIT!'UNDERFLOW'> (A); RETURN; ENO; IF (X' = FIRST» /• DELETE FIRST NODE? </ THEN DO; FIRST = LINK(FIRST); GO TO retnooe; END; NEXT = FIRST; /* INITIATE SEARCH FOR PREDECESSOR OF X </ UPDATE: /< UPDATE PREDECESSOR VARIABLE */ PRED = NEXT: NEXT = LINKINEXTU /« GET NEXT NODE */ IF (NEXT = O> /♦ ENO OF LIST? */ THEN DO: PUT SKIP EDIT I'NODE NOT FOUND'I <AJ; RETURN; END; IF (NEXT XI THEN GO TO UPDATE: /* IS THIS NODE X? »/ LINKCPREDI = LINKIXI; /* DELETE NODE X #/ RETNOOE: /< RETURN NODE TO AVAILABILITY AREA LINK(X) = avail; -AVAIL = x; end delete; Рис. 4-2.7. Процедура, реализующая алгоритм DELETE LINK и указатель AVAIL определены вне процедуры и являются глобальными по отношению к ней. Указанные процедуры включаются в основную программу с именем TEST, текст которой приведен на рис. 4-2.8. Как уже было сказано, пять векторов и переменная AVAIL являются глобальными по отношению к процедурам ALIST, POLYFRONT, POLYEND, DELETE. Представление связанных Линейных списков с помощью век- торов рассмотрено так подробно для того, чтобы дать представле- ние об их создании и обработке. Предметом оставшейся части на- стоящего пункта является программирование связанных линейных списков на ПЛ/1 с использованием структур, определенных про- граммистом. До сих пор мы имели дело главным образом с такими элемен- тами данных, как простые переменные и массивы*. О средствах, 302
test: PROCEDURE OPTICNSIMAIN): /* declare five vectors representing typical nodes */ DECLARE I₽OWER_X<100),POWER_¥1100 ) ,POWER_Z<100),COEFF!100),LINk;1001. AVAIL) FIXED DECIMAL; ALISTi PROCEDURE; END AL 1ST; POLYFRONT: PRLCEDURE(NX,NY,NZ,NCCEFF,FIRST) RETURNS I FIXED DECIMAL); END POLYFRONT; POLYENO: PROCEDURE(NX,NY,NZ,NCOEFF,FIRST) RETURNS!FIXED DECIMAL I $ END POLYENO; DELETE: PROCEDURE IXSFIRST); END DELETE; CALL ALIST; /a CONSTRUCT AVAILABILITY STACK END TEST; Рис. 4-2.8. Программа тестирования процедур, реализующих алгоритмы включения и исключения позволяющих программисту определять свои собственные типы данных, кратко упоминалось в гл. 3. Более подробно они рассмо- трены в настоящем пункте, в частности, рассмотрено использова- ние их для представления линейных связанных списков. Во многих задачах проблема представления данных часто оказывается довольно сложной. С помощью массивов не всегда можно описать связи, которые существуют между элементами данных. Например, деревья, которые будут рассмотрены в следу- ющей главе, трудно представить, используя массивы. Рассмотрим задачу регистрации студентов в университете. В структурном смысле каждого студента можно рассматривать как некий объект (или запись), который обладает рядом свойств, таких как номер студента, год обучения н т. д. Например, студент имеет имя ’JOHN BROWN’, номер ’89107’ и год поступления в университет ’1975’. По смыслу все свойства, или поля, рас- полагаются рядом таким образом, чтобы отражат их взаимную связь. Для представления такой системы можно использовать массивы, где элемент массива соответствовал бы качению каж- дого свойства. Более приемлемым было бы представление объекта одной структурой, состоящей из нескольких заданных полей. В языке ПЛ/1 программист может создать шаблон для жела- емого объекта путем описания структуры с памятью типа BASED. Этот шаблон позволяет программисту специфицировать, какие 303
поля должны быть сгруппированы вместе и в каком порядке. В описании также задается имя для всей совокупности полей. Например, предложение DECLARE 1 STUDENT BASED (Р), 2 NUMBER FIXED DECIMAL, 2 NAME CHARACTER (20), 2 YEAR FIXED DECIMAL; описывает структуру с именем STUDENT, которая состоит из полей NUMBER, NAME и YEAR. В результате такого описания определяется класс или структура данных, состоящая из упоря- доченной последовательности полей. Именем класса или струк- туры данных в предыдущем примере является STUDENT. Описа- ние непосредственно не отводит память под структуру, а только декларирует структуру объекта. В любом месте программы с по- мощью оператора ALLOCATE может быть создано сколько угодно реальных элементов с такой структурой данных. Если нужен определенный элемент структуры данных, программист должен использовать имя структуры в операторе ALLOCATE. Например, в результате выполнения оператора ALLOCATE STUDENT; отводится область памяти, в которой будут помешены три поля, а именно: NUMBER, NAME и YEAR. Поскольку таким образом может быть создано много копий структуры типа BASED, одного имени поля, например NUMBER, недостаточно для однозначного обращения к конкретному полю. Ссылаясь на поле конкретной структуры, необходимо использовать адрес, или указатель. Этой ссылкой может быть переменная, значение которой является адресом созданной копии. Переменная Р, следующая за BASED в описании структуры STUDENT, по умолчанию определяется как переменная типа указатель, причем адрес последнего создан- ного узла автоматически присваивается Р. Ссылка на конкретный узел, описанный структурой типа BASED, или на поле этого узла производится с помощью указа- теля. Так, Р —> STUDENT обозначает узел, созданный самым последним оператором ALLOCATE. К отдельным полям этого узла можно обратиться следующим образом: Р—> NUMBER, Р —> NAME, Р —>YEAR. (Имитацией стрелки в ПЛ/1 является знак минус, за которым следует знак «больше»). Последовательность операторов присваивания Р — > NUMBER = 89107; Р — > NAME = ’JOHN BROWN’; Р — > YEAR = 1975; задает значения 89107, ’JOHN BROWN’ и 1975 полям NUMBER, NAME и YEAR соответственно. Этот процесс показан на рис. 4-2.9. Адрес созданного узла хранится в указателе Р и представляется на рисунках стрелкой от Р к этому узлу. 304
р -+ж 89107 JOHN BROWN 1975 Рис. 4-2.9. Представление узла связанного списка в памяти Создав в результате выполнения последнего оператора ALLOCATE узел и поместив его адрес в указатель, программист может использовать или изменять поля узла. Это иллюстрирует простая программа на ПЛ/1, приведенная на рис. 4-2.10, которая создает узел, присваивает значения поля NUMBER, NAME и YEAR и выводит результаты на печать. . В языке ПЛ/1 можно не только получать память для структур с базированной памятью, ио и освобождать ее. Это выполняется 1 оператором FREE Р — > STUDENT; 'если узел, адресуемый указателем Р, должен быть возвращен • в свободную память. Уже отмечалось, что указательная переменная обычно задает адрес. Как исключение, указателю может быть присвоено значе- ние, возвращаемое встроенной функцией языка ПЛ/1 NULL. !Это значение не может интерпретироваться как адрес и, следова- тельно, не может быть воспринято как адрес некоторого узла. IФункция NULL при каждом обращении возвращает одно и то же * значение и, следовательно, может использоваться для формирова- ния признака конца списка. Сравнения на равенство и неравен- ‘ ство могут быть выполнены между NULL и указательной пере- [ менной, а также между двумя указательными переменными. | В качестве еще одного примера рассмотрим следующую задачу. ; Требуется написать программу, которая считывает N групп SAMPLE: PROCEDURE ОРТ IONS I HAIN); /9 PROGRAM TO CREATE A NODE ANO OUTPUT ITS CONTENTS «/ DECLARE 1 STUDENT BASEDIP), 2 NUMBER FIXED DECIMAL. 2 NAME CHARACTER)20), 2 YEAR FIXED DECIMAL; ALLOCATE STUDENT? P->NUMBER = 2100; P->NAME = ’HIKE PEARSON1; P->YEAR - 1923; PUT SKIP EDHI'NUMBER IS ' ,₽->NUMBER) IA.FI4) I: PUT SKIP EDIT! 'NAME IS ’,P->NAME1 (A,Al; PUT SKIP EO1TCYEAR IS r,P->YEAR) (A,F(4I) ; ENO SAMPLE; •NUMBER IS 2100 NAME IS MIKE PEARSON YEAR IS 1923 Рис. 4-2.10. Процедура, иллюстрирующая создание узла с помощью средств языка ПЛ/1 305
PAYROLL: PROCEDURE OPTICNS(MAIN); /« SAMPLE PAYROLL PROGRAM */ 01 EMPLOYEE BASED(Pi, REQUIRED DATA STRUCTURE CLASS «7 02 NAME CHARACTER(20U 02 RATE FLOAT DECIMAL, 02 HOURS FLOAT DECIMAL, 02 PAY FLOAT DECIMAL, MEMBER ISO» POINTER, /* REFERENCE ARRAY FOR THE EMPLOYEES »! (I, NUMBER» FIXED DECIMAL; GET LISHNJ; /« READ IN THE VALUE OF « «/ DO 1 = 1 TO N; 7* READ LOOP */ ALLOCATE EMPLOYEE; 7« CREATE A NODE *7 /0 READ AN EMPLOYEE CARO GET LiSTiNUMBER,P->NAME,P->RATE,P->HOURSl; /© PLACE ADDRESS OF CREATED NODE IN THE ELEMENT OF ARRAY MEMBER WITH A SUBSCRIPT GIVEN BY THE EMPLOYEE NUMBER OBTAINED FROM THE CARD. «>/ MEMBER (NlrfW^» = ₽S ₽->PAY ='HCURS * RATES 7* COMPUTE GROSS PAY *7 END; END' payroll; Рис. 4-2.11. Процедура начисления заработной платы данных, состоящих каждая из номера служащего, его имени, по- часовой оплаты и числа проработанных часов. Узел, создаваемый для каждой группы данных, состоит из полей для хранения исход- ных данных и дополнительного поля, содержащего общую на- численную сумму оплаты. Адреса создаваемых узлов должны храниться в массиве ссылок. Предполагается, что каждый служа- щий имеет уникальный номер, находящийся в интервале от 1 до 50. Программа, выполняющая эту задачу, приведена на рис. 4-2.11. Первая часть предложения DECLARE описывает структуру данных с именем EMPLOYEE, состоящую из четырех полей с име- нами NAME (ИМЯ), RATE (ПОЧАСОВАЯ ОПЛАТА), HOURS (ЧАСЫ) и PAY (СУММА). Вторая часть предложения DECLARE определяет 50-элемент- ный массив ссылок с именем MEMBER, индексами которого являются номера служащих. Оператор ALLOCATE создает узел и заносит его адрес в ука- затель Р. Этот адрес затем используется для присваивания зна- чений четырем полям вновь созданного узла. Индексом элемента массива служит номер служащего, считанный с карты. Например, считывание карт, содержащих информацию 2 ’JOHN DOE- 2.50 40 11 ’SAM SMITH’ 3.00 45 20 ’TOM BROWN’ 3.50 44, 306
MEMBER 120) Рис. 4-2.12. Связь между элементами массива MEMBER и отдельными узлами приводит к созданию трех узлов, адреса которых хранятся, в эле ментах массива MEMBER (2), MEMBER (11) и MEMBER (20). Графически это представлено на рис. 4-2.12. Теперь обсудим представление связанных списков. Их можно реализовать путем введения в каждый узел поля, содержащего адрес следующего узла. Программирование связанных списков с помощью массивов требовало управления свободной памятью. Если же используются определяемые программистом типы дан- ных, распределение свободной памяти обеспечивается компиля- тором. Аналогично возврат использованного узла в свободную память также обеспечивается компилятором. Поэтому далее мы не будем рассматривать вопросы управления свободной памятью. В нашем примере с многочленами описание DECLARE 1 TERM BASED (Р), 2 POWER.X BINARY FIXED, 2 POWER.Y BINARY FIXED, 2 POWER.Z BINARY FIXED, 2 COEFF BINARY FLOAT, 2 LINK POINTER; определяет структуру члена многочлена. Величина TERM на уровне 1 представляет совокупность всех пяти полей уровня 2. Поля POWER—X, POWER—Y, POWER—Z, COEFF могут со- держать требуемые числовые данные. Атрибут описания данных POINTER определяет, что поле LINK обычно содержит адрес элемента данных типа BASED. Переменная Р, следующая за атри- бутом BASED, по умолчанию является указательной; ниже будет кратко рассмотрено ее назначение. В результате выполнения оператора ALLOCATE TERM; создается узел, и адрес этого узла автоматически заносится в Р. Заметим, что одновременно могут использоваться много узлов TERM, однако каждый из них должен создаваться в результате выполнения оператора ALLOCATE. Как и ранее, ссылка на узел или отдельное поле внутри узла осуществляется с помощью указателя. Например, Р — s>TERM 307
polyfront: , - PROCEDURE(NX,NY,NX,NCOEFF,FIRST) RETURNS(POINTER); /«INSERT A NODE IN THE LINKED LIST WHICH WILL IMMEDIATELY PRECEDE THE _ NODE WHOSE ADDRESS IS DESIGNATED BV THE POINTER FIRST. INITIALIZE THE NODE'S FIELDS TO NX» NT, ANO NCOEF. RETURN THE POINTER TO THE NEW NODE. e/ DECLARE' (NX,NY,NZ) BINARY FIXED, NCOEFF BINARY FLOAT, FIRST pointer; ALLOCATE TERM? P->POWER_X = NX; P->POWER_Y = NY; P->POIJER_Z = NZ; P-XCOEFF = NCOEFF; , P-XLINK = FIRST; RETURNIP); ENO polyfront; Рис. 4-2.13. Процедура, реализующая алгоритм POLY FRO NT и использующая базиро ванную память означает ссылку на узел, созданный последним оператором ALLOCATE. На каждое поле можно также ссылаться следующим образом: Q — >POWER_X, Q — >POWER__________________________Y, Q — >POWER_Z, Q — >COEFF или Q — >LINK, где Q — указательная переменная. В качестве примера можно вычислить сумму коэффициентов двух членов, адресуемых Р и Q, с помощью выражения X - Q — > COEFF + Р — > COEFF; Эти средства языка ПЛ/1 в данном и следующих пунктах будут являться основой для программ обработки списков. Про- цедура, реализующая алгоритм POLYFRONT, приведена на рис. 4-2.13. В процедуре POLYFRONT, а также в подпрограмме POLYST: PROCEDURE OPTICNS(MAIN I ; /«CONSTRUCT A LINKED LIST REPRESENTATION OF A POLYNOMIAL USING THE INSERT PROCEDURE. «/ DECLARE 1 TERM BASEO(P), 2 POW£R_x BINARY FIXED, 2 PQUER_V BINARY FIXED, 2 POWEP_2 BINARY FIXED, 2 COEFF BINARY FLOAT, 2 LINK POINTER, Q POINTER, POLY POINTER, POLYFRONT ENTRYIBIN FIXED,BIN FIXED,BIN FIXED,BIN FLOAT,PTR) RETUPNSiPTR): POLY = NULL; /« INITIALIZE «/ >OLY = POLYFRONTIO,1,1,1,POLY); /« INSERT LAST TERM OF POLYNOMIAL*/ •POLY = POLYFRONTIO,2,0,1,POLY); /«INSERT THIRD TERM OF POLYNOMIAL*/ POLY = POLYFRONT(1,1,0,5,POLY IJ/MNSERT SECOND TERM OF POLYNOMIAL*/ POLY = POLYFRONT(2,0,0,2,POLY); /«INSERT FIRST TERM OF POLYNOMIAL*/ END POLYST; Рис. 4-2.14. Процедура формирования многочлена 2х2 -j- бху + у2 + yz 308
DELETE: PROCEDURE(X,FIRST); /«FIND AND DELETE NODE X FROM THE POLYNOMIAL LIST POINTED 70 BY FIRST. */ DECLARE I (X,FIRST,NEXT,PRED) POINTER; IF FIRST = NULL THEN ПО:/* INDICATE THAT LIST IS EMPTY </ PUT SKIP LIST!'LIST UNDERFLOW'); RETURN; END; IF X - FIRST THEN DO; /« DELETE THE FIRST NODE «-/ FIRST = first->link; GO TO FREE_NODE; END; » NEXT = FIRST; /«-INITIATE SEARCH */ LOOP: PRED = NEXT; /* UPDATE NEXT AND PRED */ NEXT - NEXT->LINK; IF NEXT = null THEN DO; /* INDICATE NODE WAS NOT FOUND «/ PUT SKIP LIST(’NODE NOT FOUND'); RETURN; END; IF NEXT -»= X THEN GO TO LOOP; PRED->LINK = x->LINK; /* DELETE X */ FR£E_NODE: FREE X->TERM; /* RESTORE NODE X TO THE AVAILABILITY AREA “/ RETURN; END DELETE; Рис. 4-2.15- Процедура, реализующая алгоритм DELETE и использующая базированную память (DELETE) на рис. 4-2.15 структура TERM не описана. Пред- полагается, что эти процедуры вложены в вызывающую их про- цедуру, в которой сделаны все необходимые описания. Если POLY — указатель первого узла списка, то программа, представленная на рис. 4-2.14, сформирует многочлен 2х2 4 бху -J- + у2 + yz. Процедура, реализующая алгоритм DELETE, приведена на рис. 4-2.15. Упражнения к п. 4-2.1 1. Дан связанный список, узел которого состоит из полей INFO и LINK. Сформулируйте алгоритм для определения числа узлов в списке. 2. Постройте алгоритм замены значения поля INFO k-го узла значением переменной Y. 3. Составьте алгоритм включения узла непосредственно слева от 1<-го узла списка. 4. Постройте алгоритм сцепления (конкатенации) линейного списка с дру- гим линейным списком. 5. Дан односвязный линейный список, адрес первого узла которого на- ходится в указателе FIRST. Требуется расщепить этот список на два односвя- зных списка. Указателем узла, который должен быть первым во втором списке, является переменная SPLIT. Постройте алгоритм, выполняющий эту задачу. 6. Предположим, что имеется односвязный линейный список, первый узел которого адресуется указателем FIRST. 309
[kEY | LINK | a) FIRST 8) FIRST f>) Рис. 4-2.16. Упорядоченный линейный список до и после исключения последователь- ности узлов Узел списка изображен на рис. 4-2.16, а, где переменные KEY и LINK представляют информационное поле и поле узла. Список упорядочен по зна- чению поля KEY таким образом, что первый и последний узлы содержат наиболь- шее и наименьшее значения соответственно. Требуется исключить из списка все последовательные узлы, значение поля KEY которых больше или равно KMIN н меньше КМАХ. Например, исходный список при KMIN — 25 и КМАХ = 40 может быть таким, как показано на рис. 4-2.16, б. Этот список после исключения заданных узлов показан на рис. 4-2.16, в. В этом примере исключены узлы со значениями полей KEY, равными 25, 29 и 37. Постройте алгоритм и напишите программу, которые будут выполнять операцию исключения из произвольного связанного списка. 7. Имеется неизвестное число перфокарт, каждая из которых содержит следующую информацию о студенте; номер студента, имя, название колледжа, пол и год обучения. Поля разделены по крайней мере одним пробелом и отпер- форированы в указанном порядке. Пол перфорируется одним символом М (муж- * ской), либо F (женский). Последняя карта в колоде содержит номер студента 999999 и фиктивную информацию в оставшихся четырех полях. Название кол- леджа и год обучения студента могут изменяться. Поэтому за основной колодой следует ряд карт, отображающих эти изменения. Карты изменений касаются только тех студентов, данные о которых изменились. Колода карт изменений тоже завершается картой, содержащей в поле номера студента 999999 и произ- вольную информацию в двух других полях. За картами с изменениями следуют карты, содержащие названия колледжей. Постройте алгоритм для создания связанного списка записей о студентах, который должен быть упорядочен по номерам студентов (от меньшего к большему). Алгоритм должен также считывать- карты с изменениями и обновлять этот спи- сок, После ввода карт с изменениями должны быть считаны карты с названиями колледжей и введена в алфавитном порядке информативная сводка о студентах, обучающихся в этих колледжах. Такая сводка должна быть получена для каж- дого считанного названия колледжа. Обеспечьте печать номера, имени, пола и года обучения каждого студента. Заметим, что ии исходный карточный файл, ни карты изменений не упоря- дочены и что возможна ситуация, когда некоторая карта изменений содержит данные о студенте, который не представлен в основном файле. В этом случае необходимо выдать сообщение об ошибке. 8. При рассмотрении текстового редактора ЕТЕХТЕ в п. 2-5.1 не было предусмотрено средство для включения строки между двумя строками текста, хранящегося в памяти системы. Пусть команда редактора ЕТЕХТЕ $$INSERT/(HOMep начальной строки)Z 310
включает любой текст, находящийся между этой командой и следующей командой типа $$, с указанного номера начальной строки Например, пусть текст строки с номерами 320 и 330 хранится в виде 00320 ЭТО СТРОКИ ВСПОМОГАТЕЛЬНОГО ТЕКСТА 00330 ЭТО СТРОКА, СЛЕДУЮЩАЯ ЗА ВСТАВЛЕННЫМ ТЕКСТОМ и пусть на вход редактора поступило ^INSERT/ 00322/ ИЛЛЮСТРИРУЕТ ВЫПОЛНЕНИЕ КОМАНДЫ ^INSERT, ЕСЛИ ЗАДАННОЕ ЧИСЛО СТРОК ДОЛЖНО БЫТЬ ВСТАВЛЕНО МЕЖДУ ДВУМЯ СТРОКАМИ, УЖЕ НАХОДЯЩИМИСЯ В СИСТЕМЕ Действие команды $$INSERT состоит в том, чтобы в позициях 6—10-й строки, после которой должен быть вставлен новый текст, сформировать ука- затель, адресующий символьный массив FREE_TEXT. Вставленный текст помещается в свободные строки в массив FREE_TEXT, при этом наращивается на количество вставляемых строк номер строки данного массива Поэтому, если первая свободная строка в массиве FREE_TEXT имеет номер 24, то команда $$INSERT изменяет хранящийся текст в строке 320 на 0032000024 ЭТО СТРОКИ ВСПОМОГАТЕЛЬНОГО ТЕКСТА и помещает в массив FREE_TEXT вставленный текст в следующем виде: 00322 ИЛЛЮСТРИРУЕТ ВЫПОЛНЕНИЕ КОМАНДЫ $$INSERT, 00323 ЕСЛИ ЗАДАННОЕ ЧИСЛО СТРОК ДОЛЖНО БЫТЬ 00324 ВСТАВЛЕНО МЕЖДУ ДВУМЯ СТРОКАМИ, УЖЕ НАХОДЯЩИМИСЯ в СИСТЕМЕ Когда текст пролистывается с помощью команды $$LlST, то его строки выводятся в последовательности номеров ..., 320, 322, 323, 324, 330,... Разумеется, команды $$LIST и $$PRINT, описанные в п. 2-5.1, должны быть модифи- цированы с учетом вставок в текст. Разработайте алгоритм $$INSERT, который обеспечивал бы вставление строк в текст. Ваш алгоритм должен при этом осуществлять проверку того, что номер начальной строки, назначаемый для вставляемого текста, не совпадает с уже используемым в системе номером. 9. Составьте алгоритм <j$RENUMB, который заново нумерует весь за- помненный текст, восстанавливая приращение номеров строк, равное десяти (предполагается, что обращение к алгоритму $$RENUMB осуществляется с помощью команды <j$RENUMB в системе ЕТЕХТЕ). Например, если в ре- зультате вставки последовательность номеров строк получилась .,,, 320, 322, 323, 324, 330,.".., то новая последовательность должна быть 320, 330, 340, 350, 360, а текстовая строка, имевшая ранее номер 340, теперь нумеруется числом 370 и т, д. Отметим, что алгоритм $$RENUMB должен физически пере- местить весь вставленный текст из массива FREE_TEXT, где он был размещен, в основной массив, называемый LINE. Массив FREE.TEXT после выполнения команды $$RENUMB должен стать пустым. 4-2.2. Циклически связанные линейные списки В предыдущих пунктах обсуждались исключительно связанные линейные списки, в которых последний узел списка содержал значение указателя NULL. Рассмотрим теперь некото- рую модификацию этого представления, приводящую к упроще- нию операций обработки. Эта модификация заключается в замене значения указателя NULL в последнем узле списка на адрес на- чала списка. Такие списки называются циклически связанными линейными списками или циклическими списками рис. 4-2.17, 311
Рис. 4-2.17. Циклически связанный линейный список Циклические списки имеют ряд преимуществ по сравнению с простыми связанными списками. Одно из них заключается в до- ступности узлов. В циклическом списке можно из любого узла списка попасть в любой другой узел посредством продвижения по списку. Второе достоинство циклических с 'исков относится к операции исключения. Вспомним, что в алгоритме DELETE из п. 4-2.1 кроме адреса узла, подлежащего исключению из односвязного линейного списка, необходимо было задавать также адрес первого узла списка. Это диктовалось тем, что для исключения узла из списка требовалось найти узел, непосредственно предшествующий исключаемому в списке. Для нахождения этого узла необходимо просмотреть все узлы списка, начиная с первого. Очевидно, что это требование можно опустить при использовании циклических списков, поскольку поиск предшествующего узла можно начать с самого исключаемого узла. Наконец, некоторые операции, такие как сцепление и рас- щепление (см. упр. 4 и 5 к п. 4-2.1), для циклических списков становятся эффективнее. Однако у циклических списков имеется и серьезный недоста- ток: если при их использовании не принять соответствующих мер предосторожности, то можно попасть в бесконечный цикл! По- этому при работе с циклическими списками важно уметь обнару- жить конец списка. Можно гарантировать обнаружение конца списка, помещая в список специальный узел, который легко может быть распознан. Такой специальный узел часто называют головой циклического списка. Этот прием полезен еще и потому, что при его использовании не существует пустых списков, а как следует из п. 4-1 и п. 4-2.1, в большинстве алгоритмов необходима про- верка условия пустого списка. Представление циклического спи- ска с головным узлом дано на рис. 4-2.18, где переменная HEAD означает адрес головного узла списка. Заметим, что поле INFO в головном узле ие используется. На рисунке это показано путем штрихования поля. Пустой список соответствует выполнению условия LINK (HEAD) = HEAD. HEAD —гт^ Рис. 4-2.18. Циклически связанный линейный список с головным узлом 312
Алгоритм Включения узла непосредственно после головного узла списка состоит из следующих шагов: Установить NEW -4= NODE, INFO (NEW) <- Y, LINK (NEW) «-LINK (HEAD) и LINK (HEAD) «-NEW. Циклические списки еще несколько раз встретятся нам в даль- нейшем. Упражнения к п. 4-2.2 1. Постройте алгоритм исключения узла из циклического списка с головным узлом. 2. Постройте алгоритм сцепления Двух циклических списков (см. упр. 4 к п. 4-2.1). 3. Разработайте алгоритм расщепления циклического списка на два цикли- ческих списка (см. упр. 5 к п. 4-2.1). 4. Постройте алгоритм включения и исключения элемента из очереди, если очередь представлена в виде циклического списка, 5. Постройте алгоритм возврата циклического списка в область свободной памяти. 4-2.3. Двусвязные линейные списки До сих пор мы ограничивались просмотром связанного списка лишь в одном направлении. В некоторых задачах весьма желательно и даже необходимо иметь возможность продвижения по списку в обоих направлениях. Это легко осуществить, вводя в каждый узел два поля связи вместо одного. В этих полях на- ходятся адреса предыдущего и последующего узлов данного узла. Ссылка, указывающая иа предыдущий узел, называется левой ссылкой, а указывающая иа следующий узел — правой ссылкой. Список, состоящий из узлов такой структуры, называется дву- связным линейным списком, или двунаправленной цепочкой. Гра- фически такой линейный список изображен на рис. 4-2.19, где L и R — указательные переменные, которые содержат адреса самого левого и самого правого узлов в списке. Левая ссылка крайнего левого узла и правая ссылка крайнего правого узла содержат значение NULL, индицирующее конец списка в каждом из двух направлений. Левая и правая ссылки некоторого узла обозна- чаются переменными LPTR и RPTR соответственно. Рассмотрим задачу включения узла в двусвязный линейный список слева от определенного узла с адресом, задаваемым пере- менной М. Здесь возможно несколько случаев. Во-первых, список может оказаться пустым. Этой ситуации соответствует значение переменных L и R, равное NULL. Включение в пустой список выполняется просто присваиванием указателям L и R адреса вклю- чаемого узла, а левой и правой ссылкам этого узла — значения NULL. L - -Ч/l ___1Л-Я Рис. 4-2.19. Двусвязный линейный список 313
Рис. 4-2.20. Включение узла в середину двусвязного линейного списка Во втором случае требуется включение в середину списка. Список дои после операции включения изображен на рис. 4-2.20, где NEW — адрес узла, который включается в список. II, наконец1, включение может выполняться слева от край- него левого списка; в этом случае необходимо изменить значение указателя L. Список до и после включения показан на рис. 4-2.21. Теперь можно дать точное описание алгоритма включения. Алгоритм DOUBINS. (Включение узла в двусвязный линей- ный список.) В заданный двусвязный список, адреса крайних левого и правого узлов которого содержатся в указателях L и R соответственно, требуется включить узел, адрес которого опре- деляется указательной переменной NEW. Правое и левое поля ссылок узла обозначаются соответственно RPTR и LPTR, инфор- мационное поле узла — переменной INFO. Весь узел двусвязпого линейного списка обозначен NODE. Включение должно быть выполнено слева от узла, адрес которого задан указательной 1 Можно также включить узел справа от самого крайнего правого узла списка. — Прим. ред. Рис. 4-2.21. Включение узла с левой стороны двусвязного линейного списка 314
переменной М. Информация, которая должна быть занесена в узел, содержится в переменной X. 1. (Получение нового узла йз стека свободной памяти. ] Уста- новить NEW <= NODE. 2. [Занесение информации в узел. ] Установить INFO (NEW) ч-Х. 3. [Узел включается в пустой список?] Если R = NULL, то установить L -«-R -«-NEW, LPTR (NEW) ч-RPTR (NEW) ч— ч- NULL и закончить выполнение алгоритма. 4. [Узел включается в левый конец списка?] Если М = L, то установить LPTR (NEW) -«-NULL, L ч-NEW, RPTR (NEW) ч— ч-М, LPTR (M) -«-NEW и закончить выполнение алгоритма. 5. [Включение узла в середину списка. ] Уста- новить LPTR (NEW) ч- LPTR (М), RPTR (NEW) M, RPTR (LPTR (M)) -«-NEW, LPTR (M) ч-NEWj закончить вы- полнение алгоритма. Теперь рассмотрим задачу исключения узла из двусвязного Списка. При исключении узла из односвязного линейного списка требовалось вначале найти узел, предшествующий исключаемому. Для этого все узлы проходились последовательно. Этот поиск был необходим, чтобы заменить значение поля ссылки в предшеству- ющем узле на адрес узла, следующего непосредственно за исклю- чаемым. Такой поиск может потребовать больших затрат времени в зависимости от числа исключений и числа узлов в списке. Для двусвязиого линейного списка нет необходимости в этом поиске. При заданном адресе исключаемого узла адреса левого и правого соседей содержатся в его полях ссылок. Операция исключения над двусвязными списками выполняется намного быстрее, чем над односвязными. Здесь также возможен ряд ситуаций. Если список содержит единственный узел, то исключение приводит к получению пустого списка, в котором указатели самого левого и самого правого узлов содержат значение NULL. Исключаемый узел может быть самым левым узлом списка. В этом случае должно быть изменено значение указателя L *. Аналогичная ситуация возникает при исключении узла, находящегося иа правом конце списка. И, наконец, исклю- чение может проводиться из середины списка. Алгоритм DOUBDEL. (Исключение узла из двусвязного ли- нейного списка). Задан двусвязный список, адреса правого и ле- вого концов которого имеются в указателях R и L соответственно; каждый узел списка содержит правую и левую ссылки с именами LPTR и RPTR. Адрес исключаемого узла находится в перемен^ ной OLD. 1. [Список пуст?] Если R = NULL, то печатать сообщение ’СПИСОК ПУСТ’ и закончить выполнение алгоритма. * А также должна быть изменена ссылка узла, расположенного непосред- ственно справа от исключенного узла. — Прим, пер. 315
DOUBtNS: /« procedure for/Inserting a node in a doubly linked linear list */ DECLARE (L,R,M! POINTER, X CHARACTER!»», PREDN pointer; ALLOCATE NODE; NEW->INFO = X; IF IR - NULL) ™ENoo; /* insertion in empty list #/ l « new; r = new; NEW->LPTR * NULL; NEW->RPTR я NULL? RETURN; END? IF IM » U THEN DO; /» LEFTMOST INSERTION »/ NEW->LPTR - NULL; . L = new; ‘ NEW->RPTR = m; m->lptr = new; end; ELSE DO; /« INSERT IN MIDDLE »/ NEW->LPTR = M->LPT₽; N£W->RPTR = m; PREON = n->LPTR; PREOM->RPTR - NEW? M->LPTR - NEW; END; ENO OOUBINS; " Рис. 4-2.22. Процедура, реализующая алгоритм DOUBINS 2. [Список состоит из единственного узла?] Если L = R, то установить L -e-R -ч-NULL и перейти к шагу 6. 3. [Исключается самый левый узел? ] Если L = OLD, то установить L <-RPTR (L), LPTR (L) NULL и перейти к шагу 6. 4. [Исключается самый правый узел?] Если R = OLD, то установить R-<—LPTR (R), RPTR(R)*-NULL и перейти к шагу 6. 5. [Исключение узла из середины списка. ] Установить RPTR (LPTR (OLD)) RPTR (OLD), LPTR (RPTR (OLD)) +- *-LPTR (OLD). 6. [Возврат исключенного узла в стек свободной памяти.] Возвратить OLD в стек свободной памяти и закончить выполнение алгоритма. На рис. 4-2.22 приведена процедура на языке ПЛ/1 включения элемента в двусвязный линейный список в соответствии с алго- ритмом DOLfBINS. Предполагается, что описание Declare 1 NODE BASED(NEW), 2 LPTR POINTER, 2 INFO CHARACTER (20), 2 RPTR POINTER; 316
Рис. ,4-2.23. Представление в памяти и операции над двусвязной очередью: а — двусвязное представление очереди; б — включение в двусвязную очередь: в — исключение из двусвязной очереди является глобальным для процедуры. Аналогичная процедура может быть написана для алгоритма DOUBDEL. Это предлагается читателю в качестве упражнения. Двусвязные линейные списки можно с успехом применять для представления очередей с числом элементов, изменяющимся в широких пределах. Пример такого представления дан на рис. 4-2.23, а, где R и F — указательные переменные, обознача- ющие начало и конец очереди соответственно. Включение элемента с адресом, заданным переменной NEW, в конец очереди показано на рис. 4-2.23, б, где R’ — адрес конца очереди после операции. Такое включение осуществляет последовательность действий: RPTR(R) <- NEW RPTR (NEW)*- NULL LPTR(NEW) R R NEW Исключение элемента из очереди, представленной двусвязным списком, показано на рис. 4-2.23, в, где F’ обозначает адрес пер- вого элемента очереди после операции исключения. Исключение из начала очереди выполняется следующими действиями: F<- RPTR(F) LPTR(F) NULL Попробуем упростить алгоритмы включения и исключения для - двусвязных линейных списков. Отбросим случай пустого списка, считая, что список никогда не бывает пуст. Это можно сделать, используя специальный узел, который всегда имеется в списке. Следовательно, пустой список будет состоять из этого единствен- ного узла, который называется головным узлом списка (HEAD) и уже описывался в предыдущем пункте. С помощью головного узла можно добиться определенной симметрии структуры, сделав список циклическим, как показано на рис. 4-2.24. 317
HEAD Рис. 4-2.24. Двусвязный линейный список с головным узлом Заметим, что правая ссылка крайнего правого узла списка содержит адрес головного узла, а левая ссылка головного узла указывает на крайний правый узел списка. «Пустой» список изображен на рис. 4-2.25, где правая и левая ссылки головного узла указывают сами на себя. Алгоритм включения узла слева от узла с определенным адресом М теперь упрощается до после- довательности шагов RPTR(NEW) <- М LPTR(NEW) ч- LPTR(M) RPTR(LPTR(M)) NEW LPTR(M) •*- NEW Включение узла в пустой список показано на рис. 4-2.26, а и б. Аналогично, соответствующий алгоритм исключения узла с адресом, заданным переменной OLD, сводится к следующим шагам: RPTR(LPTR(OLD)) RPTR(OLD) LPTR(RPTR(OLD)) LPTR(OLD) В дальнейшем метод двусвязного распределения памяти будет использоваться достаточно широко. В частности, в п. 5-1.2 он будет неоднократно применяться для представления деревьев, в п. 5-5.2 —• для представления графов в машинной графике, в п. 5-6—для управления свободной областью динамической памяти, в гл. 6 — в некоторых алгоритмах сортировки и поиска, в гл. 7 — для представления областей переполнения в файловых структурах. Упражнения к п. 4-2.3 1. Постройте алгоритмы включения и исключения для дека, представленного в виде двусвязного линейного списка. 2. Выполните упр. 1 для дека с ограниченным входом. 3. Выполните упр. 1 для дека с ограниченным выходом. Рис. 4-2.25. Пустой двусвязный цик- лический список с головным узлом Рис. 4-2.26. Включение узла в пу- стой двусвязный циклический спи- сок: а — До включения; б — после вклю- чения 318
4-3. ПРИМЕНЕНИЕ СВЯЗАННЫХ ЛИНЕЙНЫХ СПИСКОВ В настоящем параграфе будут рассмотрены несколько случаев применения связанных линейных списков. Из множества примеров, которые можно было бы привести, здесь будет описано лишь несколько. При обсуждении связанных списков в качестве типичного рассматривался узел, представляющий член многочлена. В первом из приведенных ниже примеров будут более подробно обсуждены многочлены. Необходимость автоматического выполнения мани- пуляций над многочленами привела к разработке для этой цели проблемно-ориентированного языка. Над многочленами обычно выполняются операции сложения, вычитания, умножения, деле- ния, дифференцирования и интегрирования. Из этого набора наи- более легко поддаются реализации операции сложения и вычита- ния. В этом параграфе будет рассмотрена только одна операция — сложение. Алгоритмы, реализующие остальные операции, мы бу- дем обсуждать в пп. 5-2.1 и 5-2.3. Во втором пункте представлены организация словаря имен и необходимые алгоритмы ведения такого словаря. Эта задача возникает во многих областях, таких как конструирование ком- пиляторов, информационно-поисковые системы и т. д. С операциями над многочленами тесно связано выполнение арифметических операций с произвольной точностью. В некоторых задачах (например, обращение плохо обусловленных матриц) для получения значащего результата при выполнении промежу- точных вычислений требуется сохранить большое число цифр. При этом число’ требуемых значащих цифр может задаваться во время выполнения программы, и на этой основе должно подсчиты- ваться число сохраняемых цифр. Эта задача будет последней рас- смотренной в данном параграфе. 4-3.1. Манипулирование многочленами До сих пор при обсуждении связанных списков в каче- стве типичного узла часто использовался член многочлена. Те- перь поговорим о многочленах более подробно. В этом пункте рассматривается реализация только операции сложения много- членов, которую можно выполнить с помощью многократного сложения. Для достижения наибольшей эффективности обработки каждый многочлен может храниться в порядке понижения степени членов в соответствии с правилом, кратко описанным в п. 4-1. Напомним, что член многочлена D1xA,yB»zc’ предшествует члену D2xAsyB*zc*, если At > А2, или, если они равны, при Bi > В2, или, если и они равны, при С2 > С2. Такое упорядочение многочленов значительно упрощает сло- жение. В самом деле, два многочлена можно сложить всего за один 319
просмотр их членов. Указанный метод сравнения можно непосред- ственно применить для сложения соответствующих членов много- членов. Если Ах = А2, Вх = В2 и Сх = С2, то коэффициент сум- марного числа получается простым сложением коэффициентов в виде Dx + D,. Описанное упорядочение будет использовано в алгоритме POLY—ADD. На входе этого алгоритма имеется два упорядочен- ных связанных линейных списка, которые представляют собой многочлены, подлежащие сложению. Алгоритм POLY____ADD сум- мирует их. Каждый упорядоченный список строится с помощью алгоритма PINSERT, который очень похож иа алгоритм IN SO RD из п. 4-2.1. Алгоритм POLY__ADD реализует только операцию суммирования многочленов. Поскольку оба суммируемых много- члена упорядочены, результирующий многочлен может быть по- строен с помощью алгоритма, который в основном совпадает (за исключением управления свободной памятью) с алгорит- мом POLY LAST из п. 4-1. Алгоритм POLY_____ADD не изменяет обоих входных многочленов, а адрес суммарного многочлена возвращается в точку вызова. Теперь сформулируем алгоритм сложения двух многочленов. Алгоритм POLY___ADD. (Сложение многочленов.) Заданы два многочлена, адреса первых членов которых находятся в указа- телях Р и Q соответственно, и алгоритм включения POLYLAST, описанный в п. 4-1. Требуется сложить эти ‘ многочлены и за- помнить их сумму в третьем многочлене, адрес которого опре- деляется указателем R. Исходные многочлены должны остаться без изменений. PSAVE и QSAVE — указательные переменные, Ах, А2, Вх, В2, Cj, Сй, Dx и D, - рабочие переменные. 1. [Инициализация.] Установить R ч-NULL, PSAVE ч- ч- Р, QSAVE ч- Q. 2. [Конец одного из многочленов? ] Повторять шаги 3 и 4 до тех пор, пока Р NULL и Q =£ NULL. 3. [Выделение полей из узлов.] Установить А, ч- ч-POWER—X (Р), А2 4-POWER_X (Q), В, -ч-POWER—Y (Р), В2 POWER—Y (Q), Сх ч- POWER_Z (Р), С2 ч- ч-POWER—Z (Q), D, ч-COEFF (Р) и D2 ч-COEFF (Q). 4. [Сравнение членов.] Если (А, = А2), (Вх = В2) и (Сх = С,), то если COEFF (Р) J- COEFF (Q) 0, то установить R ч— ч-POLYLAST (Ах, Вх, Сх, Dx + D2, R), Р ч-LINK (Р) и Q ч- 4-LINK(Q); в противном случае установить Рч-ЕШК(Р) и Q ч-LINK (Q); в противном случае: если (Ах > А2) или ((Ах = Аг) и (В, > В2)), или ((А, = А,), (Bj = Во) и Сх >С2)), то установить R ч-POLYLAST (Ах, В,, Сх, Dx, R) и Р ч— LINK (Р); в противном случае установить R ч- ч-POLYLAST (А2, В,, С,, D2, R) и Q ч-LINK (Q). 320
5. [Достигнут конец многочлена? ] Если Р 5^ NULL, то уста- новить LINK (LAST) ч—Р; в противном случае, если Q =?= NULL, то установить LINK (LAST) ч-Q. 6. [Восстановление начальных значений указателей Р и Q.I Установить Рч-PSAVE, Q ч- QSAVE и закончить выполнение алгоритма. В первом шаге устанавливается начальное значение указателя многочлена, представляющего сумму, а значения указателей Р и Q присваиваются переменным PSAVE и QSAVE соответственно. Во втором шаге проверяется, все ли члены хотя бы одного из многочленов обработаны. Если оба многочлена еще содержат непросуммированные члены, то повторяется выполнение шагов 3—4. В третьем шаге значения полей из узлов, представляющих члены, пересылаются в рабочие переменные Ах, А2, Вх, В2, Ср Q, Dj и Ь2. В шаге 4 сравниваются два члена многочленов Р и Q. Если их показатели степеней совпадают, т. е. (Ах — А2), (Вх = = В2) и (Сх = С2), то коэффициенты складываются, и результат, если он не равен нулю, включается в список, соответствующий многочлену R. Включение выполняется с помощью алгоритма POLYLAST. Заметим, что во избежание прохождения по всему списку при каждом включении для хранения адреса последнего включенного узла используется указательная переменная LAST. Этот прием был описан в п. 4-1. Если показатель степеней члена для Р больше показателей степеней члена для Q, то член из Р включается в конец много- члена R. В противном случае в суммарный многочлен включается член из Q. Последний шаг алгоритма восстанавливает начальные значения указателей Р и Q. Приведем теперь алгоритм, который используется для по- строения упорядоченного связанного представления многочлена. Алгоритм PINSERT. Задан упорядоченный связанный линей- ный список, каждый узел которого состоит из информационных полей POWER—X, POWER—Y, POWER—Z, COEFF и поля ссылки LINK, как было описано ранее в этой главе. Требуется включить новый узел в связанный список, сохраняя при этом его упорядоченность. Поля нового узла обозначаются переменными NX, NY, NZ и NCOEFF, которые соответствуют показателям степеней для х, у, z и коэффициенту члена. Адрес первого узла списка задается указателем FIRST, NEW и SAVE — указатель- ные переменные, а А, В и С — вспомогательные переменные. 1. [Получение нового узла.] Установить NEW <= TERM. 2. [Занесение информации в новый узел.] Уста- новить POWER—X (NEW) ч-NX, POWER—Y (NEW) ч-NY, POWER—Z (NEW) ч-NZ и COEFF (NEW) ч-NCOEFF. 3. [Если список пуст, то присвоение полю ссылки нового узла значения NULL и выход.] Если FIRST = NULL, то установить Ч Трамбле Ж., Соренсон П. 321
LIN KX (NEW) NULL, PINSERT-*-NEW и закончить выполне- ние алгоритма. 4. [Новый узел включается перед первым узлом списка?] Установить A-*-POWER_.X (FIRST), В POWER_Y (FIRST) и C-*-POWER__Z (FIRST). Если (A < NX) или ((A = NX) и (B -< NY)) или ((A= NX) и (B = NX) и (C < NZ)), то уста- новит ь LINK (NEW) ч-FIRST, PINSERT -«-NEW и закончить выполнение алгоритма. 5 [Инициализация рабочего указателя.] Установить SAVE? FIRST. 6. [Поиск места включения нового узла. ] Повторять до тех пор пока LINK (SAVE) NULL: Установить А -«- POWER—X (LINK (SAVE)), В ч-POWER—Y (LINK (SAVE)) и C -e-POWER_Z (LINK (SAVE)). Если (A > NX), или ((A = = N#) и (B > NY)), или ((A = NX) и (B — NY) и (C > NZ)), то установить SAVE LIN К (SAVE); в противном случае перейти к шаГУ 7- 7. [Установка полей ссылок нового узла и его предшествен- ника 1 Установить LINK (NEW) LINK (SAVE) и LINK (SAVE) ч-NEW. 8. [Возврат значения указателя первого узла в точку вызова. ] Установить PINSERT ч-FIRST и закончить выполнение алго- ритма • Эт-от алгоритм очень похож на алгоритм INSORD из п. 4-2.1 и не требует дополнительных комментариев. Последний алгоритм, который будет рассмотрен, осуществляет ввод ^.вух многочленов и с помощью многократных вызовов алго- ритма PINSERT строит их упорядоченное связанное представле- ние. Предполагается, что данные о каждом члене многочлена со- держатся на отдельной карте, имеющей четыре поля. Первые три содержат значения показателей степеней переменных х, у и z соответственно, четвертое представляет коэффициент этого члена. Признаком конца данных о многочлене служит фиктивная карта, содержащая нулевое значение коэффициента. Алгоритм POLYNOMIAL. Требуется ввести два многочлена с карт и с помощью описанных выше алгоритмов POLYLAST, PINSERT и POLY—ADD получить их сумму. POLY1, POLY2 и POLV3 — указатели для каждого из трех многочленов. Пере- менные X, Y, Z и С используются для хранения значений показа- телей степеней для х, у, z и коэффициента члена, введенного с карТ- 1. [Инициализация.] Установить POLY1 4-POLY2 -«-NULL. 2. [Чтение карты. ] Прочитать X, Y, Z, С с карты. 3. [Карта содержит признак конца? ] Если С — 0, то перейти к шаГУ 5. 4. [Включение узла в POLY1.1 Установить POLY1 ч- PINSERT (X, Y, Z, С, POLY1) и возвратиться к шагу 2. 5. [Чтение карты.] Прочитать X, Y, Z и С с карты. 322
6. [Карта содержит признак конца?] Если С =s 0, то перейти к шагу 8. 7. [Включение узла в POLY2. ] Установить POLY2 ч- PINSERT (X, Y, Z, C, POLY2) и возвратиться к шагу 5. 8. [Сложение многочленов.] Вызов POLY________ADD (POLY1, POLY2, POLY3). 9. [Печать полиномов и закончить выполнение алгоритма. I В заключение этого пункта заметим, что во всех описанных в нем алгоритмах можно было бы использовать циклическое свя- занное представление многочленов. В некоторых случаях это привело бы к упрощению алгоритмов. Упражнения к п. 4-3.1 1. Предполагая, что многочлен от трех переменных представлен связанным линейным списком, как описано в этом пункте, постройте алгоритм прохождения связанного списка и вычисления значения многочлена при задан- ных значениях переменных. 2. Составьте алгоритм вычитания двух многочленов с тремя переменными. 3. Постройте алгоритм создания упорядоченного линейного списка без подобных членов. На вход алгоритма подается многочлен с тремя переменными; порядок членов произвольный; могут содержаться подобные члены. 4. О|юрмутируйте алгоритм, создающий копию многочлена с тремя пере- менными. 5. Сформулируйте алгоритм, выполняющий умножение многочленов с тремя переменными. (Подсказка: умножение можно реализовать с помощью много- кратного сложения). 6. Сформулируйте алгоритм деления двух многочленов, адресуемых ука- зателями Р и Q. Многочлен-частное должен быть помещен в связанный список, адресуемый указателем QUOTIENT, остаток — в связанный список, первый узел которого адресуется указателем REMAINDER. 7. Постройте алгоритм интегрирования многочлена с тремя переменными по одной из переменных. Создайте связанный список для результата. 8. Выполните упр. 7 для дифференцирования многочлена по одной из пе- ременных. 9. Постройте алгоритм печати многочленов в шаге 9 алгоритма POLYNOMIAL. 4-3.2. Связанные словари Важной функцией любого компилятора является созда- ние и ведение словаря, содержащего имена и соответствующие им значения. Такие словари называют также символьными табли- цами. Обычно компилятор строит несколько таких таблиц, соот- ветствующих именам переменных, меткам, литералам и т. д. При построении таблиц приходится учитывать ограничения, налагаемые на размер памяти и скорость обработки. Обычно скорость выполнения алгоритмов обработки символьных’ таблиц И размер требуемой памяти связаны зависимостью, близкой к обратной. Использование таблиц символов состоит из нескольких этапов. Здесь будут рассмотрены два основных этапа — построение и до- ступ. На этапе построения в таблицу включаются новые символы и соответствующие им значения, если qhij к данному .моменту и* 32а-
известны, в то время как доступ заключается в выборке или извле- чении значений величин из таблицы. Более детально эти вопросы мы рассмотрим в гл. 6. Очень важным параметром является отношение ожидаемого числа включений к числу доступов. Во многих системах, исполь- зующих таблицы символов, время включения и время доступа тесно связаны, а в тех случаях, когда эта связь отсутствует и число доступов велико по сравнению с числом включений, целесообразно выделить больше времени и больший объем памяти на выполнение функции включения в целях улучшения характеристик процедур доступа. Если имеется достаточно большой объем памяти, то очень легко построить быструю табличную систему. В этом случае можно каждому имени сопоставить свой адрес памяти, причем этот адрес определяется арифметическим значением символов, образующих имя. Если ограничиться корректными именами языка ФОРТРАН, состоящими самое большее из шести алфавитно- цифровых символов, то для такой словарной системы потребуется около двух миллиардов слов памяти, что совершенно неприем- лемо. Наиболее простой способ получения доступа к элементу та- блицы символов — это линейный поиск. В этом методе предпо- лагается, что имена представлены в памяти либо в виде вектора, либо в виде простого линейного связанного списка. Включение при этом легко осуществляется добавлением нового элемента в конец таблицы. Если необходимо выполнить доступ к какому- либо имени, то таблицу последовательно просматривают, начиная с первого элемента, до тех пор, пока имя не будет найдено. Для поиска нужного элемента в таблице, содержащей N элементов, такой метод требует в среднем N/2 сравнений. При этом механизм включения очень быстрый, но процесс доступа чрезвычайно дол- гий. Этот метод эффективен в тех случаях, когда число обращений к элементам таблицы невелико. Если же к таблице обращаются часто, то следует использовать какой-нибудь другой метод. На- пример, в таблице из 1000 элементов для нахождения нужного элемента требуется в среднем 500 сравнений. Другим относительно простым методом доступа в символьную таблицу является двоичный поиск. Элементы в таблице хранятся в алфавитном порядке или в порядке возрастания числовых зна- чений. Процедура поиска элемента в этом случае напоминает поиск имени в телефонном справочнике. При этом берется элемент из середины таблицы, и его значение сравнивается с заданным. Если оно больше, то проверяется первая половина таблицы, и процедура повторяется до тех пор, пока нужный элемент не будет найден. Если же значение меньше, то берется вторая поло- вина таблицы, и поиск продолжается в ней. В среднем для на- хождения нужного элемента потребуется log2/V сравнений. Это значительно лучше, чем в случае линейного поиска. 324
Метод двоичного поиска имеет и некоторые недостатки. Во- первых, оказывается, что объем памяти, отводимой под таблицу, зависит от числа элементов в таблице, а именно, равен наимень- шему значению степени двух, большему или равному числу эле- ментов, подлежащих запоминанию в таблице. Это может привести к неоправданным затратам памяти. Однако это ограничение можно обойти. Во-вторых, включение нового имени приводит к необ- ходимости полной сортировки всей таблицы для определения местоположения или адреса в таблице для включаемого элемента. Этот метод полезен, если выполняется небольшое число включе- ний, так как отношение времени включения ко времени доступа слишком велико. Более подробно двоичный и другие методы поиска рассмотрены в гл. 6. Поскольку метод быстрого доступа к таблице, основанный на возможности вычисления уникального адреса элемента по арифметическому значению имени, практически реализовать не- возможно, рассмотрим его очевидную модификацию. Предполо- жим, что имеется некоторая функция, отображающая имя в не- которое целое число. Функции такого типа называются функ- циями хеширования1 . Рассмотрим множество имен языка ФОРТРАН, которые могут быть использованы в программах, и функцию хеширования Н, отображающую каждое имя в целое число от 0 до 9. Такой функцией является, например, деление внутреннего представления имени (в виде двоично-кодированного десятичного числа) иа 10. Остаток от деления должен быть меньше 10. Очевидно, такая функция отображает много имен в одно и то же число, т. е. реализует отображение «многие в один». Значит, функция хеширования разбивает все имена на несколько классов. Два имени попадают в один класс тогда и только тогда, когда они отображаются в одно и то же число, т. е. при делении на 10 дают одинаковый остаток. Имя не может попасть в два разных класса, поскольку оно отображается в единственное число. Функция, производящая такое разбиение, реализует отношение эквивалент- ности, а образованные таким способом классы называются клас- сами эквивалентности. Эффективным методом организации таблицы символов является представление каждого класса эквивалентности в памяти в виде простого связанного линейного списка. Алгоритм включения осуществляет при этом отображение имени в число. Это число определяет, в какой класс эквивалентности, или в какой связан- ный список, это имя должно быть включено. Если список не со- держит такого имени, то оно добавляется в конец списка. Поиск имени выполняется аналогично. Имя отображается в номер, используемый для локализации списка, которому принадлежит * Их называют также функциями перемешивания или рандимизации. — Прим. ред. 325
это имя- Последовательный просмотр этого списка дает нужное имя. В качестве примера предположим, что имена NODE,. BRAND,. OPERATIONS, PARAMETERS отображаются в число 0; имя STORAGE отображается в 1; имена AN и ADD отображаются в 2; имена FUNCTION и В отображаются в 8. Введем одномерный массив EQUIV, состоящий из 10 элементов. Каждый элемент массива EQUIV содержит адрес первого узла связанного списка, представляющего отдельный класс эквива- лентности. Если некоторый класс эквивалентности не содержит ни одного имени, то соответствующий элемент массива EQUIV имеет значение NULL. На рис. 4-3.1, а показана структура класса эквивалентности, содержащего имена NODE, BRAND, OPERATIONS и PARAMETERS, которые отображаются в число нуль. Аналогичное представление остальных классов эквивалент- ности показано на рис. 4-3.1, б. Важность подходящей функции хеширования трудно пере- оценить. В идеале было бы желательно иметь функцию, выполня- ющую разбиение таким образом, чтобы все классы эквивалентно- сти содержали одинаковое число имен. Наихудший возможный вариант — отображение всех имен в одно и то же число (один класс эквивалентности), при этом алгоритмы включения и выборки не более эффективны, чем для метода линейного поиска. Подбор «подходящей» функции хеширования является нетривиальной задачей, поскольку размер получающихся классов эквивалент- ности зависит от используемых имен (т. е. от области определения функции хеширования). Более полно класс функций хеширования, будет рассмотрен в гл. 6. Другим важным фактором достижения приемлемой эффектив- ности работы с таблицами является поддержание всех классов эквивалентности относительно малыми по размеру. Например, если известно, что в любой момент времени словарь в среднем будет содержать 125 имен, то было бы желательно иметь прибли- зительно 100 классов. В этом случае среднее число сравнений, необходимое для поиска имени, было бы немногим больше еди- ницы. Та же оценка справедлива и для включения. Эти средние-. EQUIV U) | STORAGE~~~[\] EOUIV t?)\ 4—*1 an 14—арр N EOUIV (£}| 4—FUNCTION j -j—♦[ В |\j Рис. -3.1. Разбиение словаря на классы эквивалентности 326
оценки основаны на предположении, что все имена распределены по классам равномерно. Используемые функции хеширования не должны быть слиш- ком сложными, поскольку время, необходимое для вычисления значения функции при определенном аргументе, должно быть добавлено ко времени выполнения включения или выборки. Обычно вследствие высокой скорости выполнения арифметических операций иа ЭВМ это требование легко удовлетворяется. Теперь сформулируем алгоритм включения для системы орга- низации символьной таблицы. Алгоритм ENTER. (Включение нового элемента в таблицу символов.) Даны одномерный массив ссылок, каждый элемент которого содержит указатель класса эквивалентности, и функция хеширования HASH, отображающая имя в целое число. Требуется включить имя, обозначаемое переменной NAME, в конец соответ- ствующего класса эквивалентности (если его там еще нет). Каждый узел списка, представляющего класс эквивалентности, состоит из информационного поля и поля ссылки, обозначаемых SYMBOL и LINK соответственно. Структура, описывающая узел целиком, называется RECORD. 1. [Вычисление значения функции хеширования-I Устано- вить RANDOM — HASH (NAME). 2. [Включение имени в пустой класс эквивалентности. 1 Если EQUIV [RANDOM] == NULL, то установить NEW <= RECORD, EQUIV [RANDOM] «—NEW, SYMBOL (NEW) <—NAME, LINK (NEW) <— NULL и закончить выполнение алгоритма. 3. [Инициализация поиска для NAME. ] Установить POINTER <- EQUIV [RANDOM]. 4. [Поиск для NAME.] Если SYMBOL (POINTER) = NAME, то закончить выполнение алгоритма. 5. [Достигнут конец класса эквивалентности?) Если LINK (POINTER) — NULL, то установить NEW <= RECORD, SYMBOL (NEW) <- NAME, LINK (POINTER) «- NEW, LINK (NEW)«—NULL и закончить выполнение алгоритма; в противном случае установить POINTER <— LINK (POINTER) и возвратиться к шагу 4. Приведенный алгоритм прост и не требует дополнительных пояснений. Программа на языке ПЛ/1, приведенная па рис. 4-3.2, выпол- няет построение таблицы имен. Она содержит процедуру, основан- ную на приведенном выше алгоритме, функцию хеширования и основную программу. Функция хеширования находит остаток от деления двоичного представления переменной NAME (получа- емого с помощью встроенной функции UNSPEC) на N. Для на- хождения остатка от деления NUMBER на N используется встро- енная функция MOD. Обращение к функции UNSPEC имеет вид UNSPEC (s), где s — любое выражение. Значением этой функции является битовая 327
строка,^ содержащая внутреннее представление для s. Длина битовой строки зависит от типа выражения. Например, если s — строковая переменная, состоящая из п символов, то результатом будет битовая строка длиной 8л. Функция хеширования в процедуре HASH использует первый и последний символы имени, подлежащего хешированию, и пре- образует эти два символа в строку из 16 битов. (Если имя состоит из одного символа, то он удваивается.) Затем эта битовая строка умножается иа длину имени. Примеры отображения имей при- ведены в табл. 4-3.1. Таблица 4-3.1 Примеры хеширования имен Имя Часть имени Внутреннее представление (части имени) Значение функции, хеширован ия В вв 11000010 11000010 * 1 = 49858 mod 10 = 8 AN AN 11000001 11010101 *2 = 99242 mod 10 = 2 LINK LK 11010011 11010010 * 4 = 216904 rood 10=4 HASHING: PROCEDURE OPTICNS(MAIN); /«THIS PROGRAM BUILDS A DICTIONARY OF NAMES CONSISTING OF 1 TO 12 ALPHANUMERIC CHARACTERS» EACH NAME IS HASHED INTO AN INTEGER WHICH IS BETWEEN 0 AND N-I INCLUSIVE ANO DENOTES TO WHICH OF N POSSIBLE EQUIVALENCE CLASSES NAME BELONGS- PROCEDURE ENTER PUTS NAME IN THE SYMBOL FIELD OF A NODE WHICH IS PLACED AT THE ENO GF ONE OF THE LISTS POINTED TO BY AN ELEMENT OF THE POINTER ARRAY EQUIV. . DECLARE 1 RECORD BASEDINEWb 2 SYMBOL CHARACTER(12 I, - 2 LINK POINTER, NAME CHARACTERI12> VARYING, (I,N) BINARY FIXED, PTR POINTER; GET LIST(N); /# READ NUMBER OF EQUIVALENCE CLASSES *Z BEGIN; /« AUTOMATIC STORAGE ALLOCATION */ DECLARE HASH RETURNS(BINARY FIXED), EQUIV(0:N - I» POINTER? ON ENDFILEtSYSINI GO TO OUTPUT; DO I = 0 TO N - 1; EQUIVII) - NULL; END; PUT EDIT(•NAME’HASHED INTO’)<X(TO),Al 20),A( 1111 ? READ; /* GET NEXT NAME FOR DICTIONARY*/ GET LIST(NAME); CALL ENTER; /* INSERT NAME IN DICTIONARY*/ GO TO READ; OUTPUT: /♦ OUTPUT CONTENTS OF EACH EQUIVALENCE CLASS • / DC I = 0 TO N - 1; PUT SKIP(2) EDIT!’EQUIVALENCE CLASS NUMBER ’ , I ) ( ХЦ0» , Al 25» ♦ f(2»»; PTR = EQUIV(I); /* SCAN LIST */ j ' DO WHILE (PTR NULL); PUT SKIP EDIT(PTR->SYMBOL)(XI41),A(12)); PTR » PTR->LINK; END; ENOS Рис. 4-3.2. Программа, реализующая алгоритм построения символьной таблицы 328
ENTER= PROCEDURE; /«EACH ELEMENT OF ARRAY EQUIV IS A POINTER TO A LIST Or NAMES WHICH HAVE BEEN MAPPED INTO THE EQUIVALENCE CLASS REFERENCED BY THE SUBSCRIPT OF THE ELEMENT. THIS PROCEDURE PUTS NAME IN A NODE AT THE END OF THE LIST WHICH BELONGS TO THE EQUIVALENCE CLASS DENOTED .BY THE INTEGER RANDOM.*/ DECLARE POINTER POINTER, RANDOM BINARY FIXED; RANDOM = HASHCNAMEI; /«COMPUTE THE HASH NUMBER*/ PUT SKIP EOITINAME,RANDOM) (X(30),A(25) ,F (2> 1 : IF EQU1V(RANDOM) = NULL THEN /* EQUIVALENCE CLASS IS EMPTY */ DO; ALLOCATE RECORD; EQUlV(RANDOM) a NEW; NEU-SSYM80L = NAME; NEW-SLINK = NULL; RETURN; eno; POINTER = EQUIV(RANDOM); /* INITIATE SEARCH FOR NAME */ •Search; /« of list for an occurrence of name ♦ / IF POINTER->SVM6OL = NAME THEN RETURN; ' IF POINTER-SLINK = NULL THEN /“ INSERT A NEW NODE */ DO; ALLOCATE RECORD; NEW->SYMBOL = NAME: POINTER-SLINK = NEW; NED->LINK = NULL; RE TURN; END; POINTER = POINTER-SLINK; /* CONTINUE SEARCH */ GO TO SEARCH; •ENO ENTER; HASH: /* MAP NAME TO A NUMBER BETWEEN 0 AND N-l */ PROCEDURE I NAME> RETURNS(В I NARY FIXED); DECLARE NAME CHARACTER!12) VARYING- PART CHARACTERI2), BITNAME BITII6), NUMBER BINARY FIXEDOl); /v GET FIRST AND LAST CHARACTERS OF NAME */ part = substriname,i-i) 11 substr(name?length<name),d; BITNAHE = UNSPEC!PART); /* OBTAIN BIT REPRESENTATION OF PART "/ NUMBER = BITNAME * LENGTH)NAME); /* MULTIPLY BY LENGTH OF NAME *7 * RETURNIMOO(NUMBER,N)11 END HASH; ENO; /• OF BEGIN BLOCK */ END HASHING; «»ис. 4-3.2. Продолжение Основная программа написана таким образом, чтобы обеспечи- вать одновременно до 10 классов эквивалентности. Каждое вклю- чаемое имя вводится на отдельной карте. При достижении конца файла управление передается на оператор с меткой OUTPUT. В этом месте программы просматривается каждый простой свя- занный линейный список, представляющий класс эквивалентно- сти, и все имена, входящие в него, распечатываются. 329
NAME HASHED INTO AN 2 В в LINK 4 NODE О ADD 2 BRAND О OPERATIONS 0 STORAGE 1 PARAMETERS О FUNCTION В EQUIVALENCE CLASS NUMBER 0 NODE BRAND OPERATIONS PARAMETERS EQUIVALENCE CLASS NUMBER 1 STORAGE EQUIVALENCE CLASS NUMBER Z AN AOO EQUIVALENCE CLASS NUMBER 3 EQUIVALENCE CLASS NUMBER 4 LINK EQUIVALENCE CLASS NUMBER 5 EQUIVALENCE CLASS NUMBER 6 EQUIVALENCE CLASS NUMBER 7 EQUIVALENCE CLASS NUMBER 8 В FUNCTION EQUIVALENCE CLASS NUMBER 9 Рис. 4-3.2. Продолжение Упражнения к п. 4-3.2 1. Функция хеширования, соответствующая методу «середины квад- рата», реализуется следующим образом: а) возводится в квадрат часть значения ключа илк, если возможно, все значение ключа; б) в полученном числе выделяется либо л десятичных цифр из его середины, что дает в качестве значения хеш-фуикции h (key) С {0, 1, •••» Юп— 1)» либо- п битов нз середины, что дает h (key) £ {0, 1, .... 2П —1}. Напишите программу, использующую этот метод для отображения некото- рого множества имен. Метод «середины квадрата» часто дает удовлетворитель- ные результаты, но во многих случаях распределение ключей в заданном диапа- зоне не является равномерным. 2. При часто применяемом методе хеширования, называемом методом свертки, ключ разбивается на несколько частей, которые затем суммируются таким об- разом, чтобы сформировать число в требуемом диапазоне. Например, если мы имеем восьмизначный ключ и хотим отобразить его в трехзиачный адрес, то можно проделать следующие операции: h (97434658) = 974 + 346 + 58 = 378; h (31269857) = 312 + 698 + 57 = 67. 330
(Заметим, что сложение выполняется по модулю 1000). Напик,,. « „ „ использующую этот метод. ит пРогРаммУ» 3. Для одного и того же множества ключей сравните ре^ч нения хеш-функций деления, свертки и «середины квадрата » Пос™ пРиме’ чтобы диапазон значений хеширования был одинаков или почти оДинакпв в^каж’ дом случае. Какой метод дает наиболее равномерное распределе , ключей? отооражении 4-3.3. Арифметика многократной точное^ В некоторых задачах предъявляют особкле требования к точности получаемого результата вычислений. дта точиость однако, зависит от обрабатываемых данных. HanpKjMeD решение системы уравнений требует определенной точность,- вычислений. Программа, решающая эту задачу, может выполну1ться иа с использованием чисел обычной или двойной точ1Яости не менее точность решения может оказаться недостаточной g’o многих случаях желаемая точность достигается только с ПОмощью спе- циальных подпрограмм, выполняющих операции с многократной точностью. Число значащих цифр, используемых в 'таких подпро- граммах, может зависеть от размерности решаемой систеМы урав- нений или от коэффициентов плохо обусловлен!*^ матрицы, заданных в форме с плавающей точкой. Эта же проблема возни- кает и при обращении плохо обусловленной матрицу В настоящем пункте будут предложены алгоритм ы‘ выполнения арифметических операций с многократной точность:ю над целыми числами. Хотя алгоритмы будут даны только для CT-j0;>KeHHJI и вы_ читания, изложенный подход может быть без труда ^асПростраиен на операции умножения и деления. Рассмотрим задачу выполнения арифметичес^.их операций с многократной точностью над целыми числами. Дд1я представле- ния целых чисел большинство вычислительных .машнн может использовать только определенное максимальное число битов или цифр. Это число битов колеблется от 8 до 64,Щ>ичем верхняя граница соответствует приблизительно 20 десятшлиым цифрам. В некоторых задачах значения целых чисел могут зн£1чительН0 цр^ вышать эту границу. Если мы хотим манипулировав такими чис- лами, необходимо найти другой способ их представл^нИЯ в памяти. Пусть ш — 1 — наибольшее целое число, предст авимое в кон- кретной вычислительной машине. Тогда любое целое .число, скажем А, можно выразить в виде многочлена: A — S ajm1, где 0 <: |a,| < m при любом j Например, пусть некоторая ЭВМ может запомишать наиболь- шее целое число, равное 999. Тогда число 12 345 67.g можно пред- ставить, используя приведенную выше формулу, в, Бнде А = 678 X 1000° + 345 X 10001 + 12 X 1000! = 678) + 345 000 + + 12 000 000 = 12 345 678. 331
В этом примере к = 2, ас = 678, 3j = 345, а2 = 12, m = 1000. Если А — отрицательное число, то все ненулевые коэффициенты в разложении будут отрицательными. Например, если А имеет значение —2 000 342, то оно может быть представлено в виде А = (—342 X 1000°) + (0 X 10001) + (—2 х 10002). Произвольно большие целые числа бесполезны до тех пор, пока мы не умеем выполнять над ними операции. В данном пункте мы рассмотрим операцию сложения и приведем алгоритм сложения целых чисел многократной точности со знаком. В алгоритме тре- буется, чтобы описанное выше разложение было представлено в виде связанного линейного списка. Прежде чем описывать ис- пользуемые списки, рассмотрим, какие случаи могут встретиться при сложении двух многочленов в виде целых чисел. Даны целые числа к к А = 53 ^пй; В — 5j bjin1. i=0 i=0 Требуется найти многочлен, представляющий сумму этих чисел, k+i S = Jj Sjm1 = А Д В. i=0 Заметим, что к — наибольший показатель степени члена в раз- ложениях А и В, не равного нулю, т. е. либо ак 0, либо Ьк 0. Наибольшая возможная степень многочлена S, равна к + 1. Некоторые коэффициенты Sj в S могут быть нулевыми. Предельный случай получается, когда А = —В; тогда все коэффициенты многочлена S равны нулю. Перечислим пять различных случаев, которые могут встре- титься при сложении этих чисел с многократной точностью. Для большей наглядности во всех примерах m выбрано равным 1000- Случай Г. А = 0 или В — 0. Если одно из слагаемых равно нулю, то S устанавливается равным ненулевому слагаемому - Если А = В — 0, то н S ~ 0. Случай 2: А »> 0 и В > 0. В этом случае имеем si — (ai 4- bi + Cj—i) mod m при 0 < i < k. и Sk+l = Cfc. Здесь Cj •— «величина переноса» в старший разряд на i-м шаге; c_j равно нулю во всех случаях. Если 3j + bf ct _ L m, то Ci присваивается значение 1, в противном случае 0. Это при- сваивание можно записать в виде с, = | (а, + bi -г Ci-i)/m_| при 0 < i с к, поскольку 0 < а, bj -р Cj—j < (ш — 1) -Е (m — 1) -[- 1 = 2т — 1 < 2т, где запись |_Р__| означает наибольшее целое, не превышающее Р- 332
В качестве первого примера положим А = 12 345 и В = 6890. Чтобы получить S = А Ч- В, запишем А = aom° + а^1 = 345 X 1000° + 12 X 10001, В = Ьот° + Ьхтг = 890 х 1000° + 6 х 1000я. Порядок вычисления следующий: c_i = 0, s0 = (а0 + Ьо 4- с_х) mod т = (345 4- 890) modlOOO = 235» cfl =L(ao + b0 + CjJ/rnJ = j_1235/1000_J = 1, «i = (ai 4- L + c0) mod m = (12 4- 6 + 1) modlOOO = 19» Sg = Ci = L(ai 4- bx + c0)/m | = |_19/1000_J = 0. Таким образом, i S = S sjm‘ = 235 x 1000° + 19x 10001 •= 19235. Во втором примере пусть A = 650 125 и В = 425 975. Вы- числим c_i = 0, s0 = (125 + 975 4- 0) modlOOO = 100, с0 = L1100/1000_J = 1, S1 (650 + 425 4- 1) modlOOO = 76, Ci = LI076/1000J = 1, s2 ' Cj = 1 и S = 100 X 1000° 4- 76 X 10001 + 1 X 10002 = 1 076 100. Случай 5: A < 0 и В < 0. Вычислим Sj =— (| aL 4--bx 4- С;_! ] mod m) при 0 i k И Sk+1 — Cfc. В этом случае принимает значения переноса 0 и —1, так что c_i = 0, q = —| ; 3j + bt 4- Ч-i | /m_J при 0 c i c k. Рассмотрим пример, в котором А = —96 и В = —99934. Вычислим c_i = 0, so = — ( | —96 — 934 4- 0 | modlOOO) = —30, Со = — L I —96 — 934 4- 0|/1000J == —1, Si = —( | 0 — 99 — 1 | modlOOO) = —100, s3 - Ci = — L [ 0 — 99 — 1 | /1000.J = 0. 333
Получаем S = —100 030. Случай 4: А < 0, В > 0, В [ А | или В <f 0, А > 0 и A^s | В]. В этом случае в результате сложения целых чисел проти- воположного знака получается неотрицательный результат. Вы- числения выполняются, как и в случае 2, т. е. = (Si Ь b, + cul) mod m при 0 i < к, c_i = 0, Ct =L(a< + bt + Ci_j)/m_J при 0 i < к. В этом случае q — не «перенос», а «заем», поскольку в действи- тельности здесь положительное число вычитается из большего или равного положительного числа. Так как либо at, либо bt отрицательно, q принимает значение 0 или —1. В качестве примера рассмотрим случай, когда А = —10 700 и В = 12 300. Тогда c_i = 0, $0 = (—700 + 300 + 0) mod 1000 == = —400modl000 = 600, с0 = |_—400/1000_| = —1, q = (10 +12 — 1) mod1000 = 1, s2 = q = |_1/iooo_j = о. Таким образом, S = 1600. Последний заем всегда равен нулю в случае, когда положительное слагаемое больше отрицательного слагаемого или равно ему по абсолютной величине. Случай 5:. А < 0, В > 0 и В <[ А | или В<0, А>0иА< <| В |. Вначале действуем, как и в случав 4. Опять Ct — заем из старшего разряда. Например, пусть А = —789 300 и В = — 400 700. Тогда вычисляем С_! = 0, So == ( -300 + 700 + 0) mod 1000 = 400, Со = L400/1000J = 0, Si = (—789 + 400 + 0) mod1000 = = —389modl000 = 611, ci = L—389/1000J = —1. Здесь потребовался заем из несуществующего старшего разряда. Этот факт указывает на то, что вычисления еще не окончены. Мы получили результат в форме .J - ___rtr-l.0Q0.0O0 + 611 400, ,334
Рис. 4-3.3. Списковое представление це- лого числа с многократной точностью Рис. 4-3.4. Списковое пред- ставление отрицательного целого числа многократной точностью Одним из способов получения окончательного результата яв- ляется повторение этой же процедуры для сложения 0 и —611 40CL В результате получится —1 000 000 -Ь 388 600. Пренебрегая «заемом» —1 и изменяя знак числа 388 600, полу- чаем правильный ответ —388 600. Значит, S = —388 600. На этом завершается обсуждение случаев, которые могут встретиться при сложении двух целых чисел. Во всех случаях, кроме первого, используются формулы (или их модификации), приведенные в случае 2. Необходимо иметь в виду правильность используемых знаков и важность последнего «заема» —1. Прежде чем перейти к построению формального алгоритма, мы должны рассмотреть представление с многократной точностью целых чисел в форме, пригодной для вычислений на ЭВМ. Для любого целого числа А можио определить одиосвязиый список, представляющий разложение этого числа в степенной ряд. Каждый узел списка состоит из коэффициента и указателя иа следующий узел списка. Эти поля обозначаются соответственно COEF и LINK- Списковое представление числа А = 12 345 678 приведено иа рис. 4-3.3. Число —1 280 000 129 может быть представлено спи- ском, изображенным на рис. 4-3.4. Заметим, что в этом числе все ненулевые коэффициенты отрицательны. Данный факт всегда имеет место для отрицательных целых чисел. Число нуль пред- ставляется пустым списком. В основном алгоритме ADDITION используется несколько вспомогательных алгоритмов. Первый из иих, называемый INSERT, подобен алгоритму с тем же именем из п. 4-2.1, но ра- ботает с узлами описанной выше структуры. INSERT помещает узел в начало списка и заполняет поле COEF. Например, в ре- зультате выполнения R INSERT (N, R) из свободной памяти будет получен узел, значение переменной N будет скопировано в поле COEF, значение R будет занесено в поле LINK, а адрес этого узла будет присвоен значению R. 335.
Второй требуемый вспомогательный алгоритм называется SIGN, Пусть R указывает на список, представляющий целое число; тогда N — SIGN (R) присвоит переменной N значение — 1, если число отрицательно, в противном случае (если число неотрицательное) N получает значение 1. Эти два алгоритма, оформленные как процедуры языка ПЛ/1, приведены в конце данного пункта. При заданных двух списках, представляющих два целых числа А и В и адресуемых указателями Р и Q соответственно, требуется построить список, адресуемый указателем SUM и пред- ставляющий число А 4- В. Алгоритм ADDITION строит обрат- ный список для суммы, а затем использует, подалгоритм REVERSE дая изменения порядка следования узлов. В случае необходимости REVERSE также изменяет знак каждого коэффи- циента, как требуется в случае 5. (Причина этого станет понятной при рассмотрении алгоритма ADDITION.) Третья функция, вы- полняемая REVERSE, состоит в исключении всех узлов, имеющих нулевое значение поля COEF, если они представляют крайние •левые нули в числе, например, ООО 125 789. Сформулируем сна- чала этот вспомогательный алгоритм. Алгоритм REVERSE. При заданном указателе R первого узла списка требуется изменить порядок узлов в этом списке на обратный. Каждый узел состоит из полей COEF и LINK. Пере- менная NEGATE имеет значение «true», если каждое поле коэф- фициентов должно быть отрицательно. Все узлы, имеющие нуле- вое значение коэффициента и расположенные в начале списка, исключаются. В алгоритме используются рабочие указательные переменные Р и Q и флажок, обозначаемый SIGNIFICANT. 1. [Инициализация.] Установить SIGNIFICANT false и S NULL. 2. [Конец списка?! Если R = NULL, то установить REVERSE *—S и закончить выполнение алгоритма. 3. [Изменить знак коэффициента?] Если NEGATE, то уста- новить COEF (R) -<---COEF (R). 4. [Реверсирование очередного узла. ] Установить Q ч— S, S R, R LINK (S) и LINK (S) Q. 5. [Исключение начальных нулей.] Если SIGNIFICANT, то возвратиться к шагу 2. Если COEF (S) = 0, то возвратить узел S в стек свободной памяти и установить S NULL, в противном случае установить SIGNIFICANT true и возвратиться к шагу 2. В шаге 1 флажку SIGNIFICANT присваивается значение «false», которое не меняется до тех пор, пока не будет найдено ненулевое значение поля COEF в шаге 5. Все узлы со значением поля COEF, равным нулю, встречаемые до того, как флажок полу- чит значение «true», исключаются. Шаг 2 проверяет, достигнут ли конец списка с указанием R, и если достигнут, выполнение ал го- 336
ритма заканчивается. В шаге 3 в случае необходимости устанавли- ваются отрицательными знаки всех полей COEF. В шаге 4 указа- тель R передвигается вниз по исходному списку, a S указывает на узел, который до этого адресовался указателем R. В поле LINK(S) засылается либо значение NULL, либо адрес узла, следующего за узлом, адресуемым указателем S при новом порядке узлов. К алгоритму REVERSE можно обратиться, записав SUM •*_ REVERSE (SUM, true), если необходимо установить отрицательными знаки коэффициен- тов. В противном случае используется обращение SUM ч- REVERSE (SUM, false). Переменная NEGATE принимает значения true или false. Имея только что описанный подалгоритм, можно перейти к ал- горитму сложения целых чисел с многократной точностью со знаком. Алгоритм ADDITION. При заданных указателях Р и Q двух списков, представляющих целые числа с многократной точностью, требуется построить список, соответствующий сумме этих чисел и адресуемый указателем SUM. Переменная МАХ имеет такое значение, что для всех узлов с указателем R выполняется условие О |COEF (R) | < МАХ. Переменная С представляет перенос или заем, SUMCF используется для временного хранения суммы соот- ветствующих полей COEF и С. NEG имеет значение 1, если хотя бы одно слагаемое неотрицательно, и—1, когда оба слагаемых отри- цательны. SECOND____PASS — это флажок, используемый при выполнении сложения чисел в соответствии со случаем 5. 1. [Сложение с нулем. ] Если Р = NULL, то установить ADDITION -н Q и закончить выполнение алгоритма. Если Q — ~ NULL, то установить ADDITION ч- Р и закончить выполнение алгоритма. 2. [Инициализация. ] Если SIGN (Р) = —1 и SIGN (Q) = = —1, то установить NEG ч---1; в противном случае установить NEG ч- 1. Установить SUM ч- NULL, Сч-0 и SECOND_JPASS ч- ч- false. 3. [Конец списка (списков)? ]’Если Р = NULL, то перейти к шагу 6. Если Q = NULL, то установить Q ч- Р и перейти к шагу 6. 4. [Вычисление коэффициента и включение следующего узла. ] Установить SUMCF ч-NEG * (COEF (Р) + COEF (Q) + С), С ч- NEG* | SUMCF/MAX J, SUM ч- INSERT (NEG*(SUMCF mod MAX), SUM). 5. [Получение адресов следующей пары членов.] Установить Р ч~ LINK (Р), Q ч- LINK (Q) и возвратиться к шагу 3. 6. [Просмотр оставшейся части- списка Q. ] Если Q — NULL, то перейти к шагу 7; в противном случае установить SUMCF ч- NEG*(COEF (Q) + Q, С ч- NEG* L_SUMCF/MAX_], SUM ч- 337
INSERT (NEG* (SUMCF mod MAX), SUM), Q LINK (Q) и повторить этот шаг. 7. [Нужен второй проход?! Если С = - 1 и NEG = 1, то, если SECOND____PASS, то установить С +- 0; в противном случае установить SECOND_____PASS t- true, Q -e- REVERSE (SUM, true), SUM NULL, C 0 и возвратиться к шагу 6. 8. [Последний перенос?] Если С =/= 0, то установить SUM-<— INSERT (С, SUM). 9. [Реверсирование результирующего списка. ] Если SECOND____PASS, то установить ADDITION REVERSE (SUM, true), в противном случае установить ADDITION REVERSE (SUM, false). Закончить выполнение алгоритма. После завершения алгоритма указатели Р и Q имеют значение NULL. Предполагается, что их исходные значения сохраняются вне алгоритма для последующих ссылок. В шаге 1 обрабатывается тривиальный случай, когда одни или оба списка пусты (одно или оба слагаемых равны нулю). В некоторых случаях бывает жела- тельно скопировать непустой список и возвратить значение его указателя, а ие присваивать указателю SUM значение Р или Q, поскольку в дальнейших операциях изменение списка с указате- лем SUM повлечет за собой изменение списка с указателем Р или Q. В шаге 2 необходимым переменным присваиваются начальные значения. Если оба слагаемых отрицательны, то переменной NEG присваивается значение —1, в противном случае 1. NEG исполь- зуется для модификации режима выполнения шагов 4 и 6 таким образом, чтобы учесть случай 3 наряду с другими описанными ранее случаями. Списки с указателями Р и Q необязательно имеют одинаковую длину. В шаге 3 проверяется, достигнут ли конец хотя бы одного из списков, и если списки неодинаковы по длине, в указатель Q заносится адрес оставшейся части более длинного списка. В шаге 4 выполняются вычисления для слагаемых, удовлетворяющих усло- виям случаев 2—5. Обратите внимание, как при этом используется переменная NEG, особенно когда ее значение равно —1. В этом шаге новый узел с вычисленным значением коэффициента вклю- чается в начало списка с указанием SUM. В шаге 5 указатели Р и Q сдвигаются на следующие узлы списков, и управление пере- дается шагу 3 цля проверки значений указателей. В шаге 6 просматривается остаток списка с указателем Q. Вычисления при этом выполняются так же, как и в шаге 4, только не учитывается COEF (Р). При обнаружении конца списка с ука- зателем Q управление передается шагу 7. В этом шаге обрабаты- вается ситуация, описанная в случае 5, т. е. заем равен —1, когда оба списка просканированы и NEG равно 1. Если возникла такая ситуация, флажок SECOND____PASS устанавливается равным true, список с указателем SUM реверсируется и каждое поле COEF получает отрицательный знак. Адрес реверсированного списка 338
Рис. 4-3.5. Списковое представление целых чисел —99 012 и —915 995 Шаг COEF(P) COEF(Q) С SUMCF SECOND -PASS SUM 1 . 4 5 5 5 8 9 -99 Р = NULL -995 -9/5 О=NULL 0 -1 -1 1007 1015 F NULL EZ0 S№EHZ'0 О'ИС. 4-3.6, Трасса алгоритма ADDITION с операндами —99 012 и —915 995 '.Рис. 4-3.7. Списковое представление целых чисел —1 079 239 и 75 198 ЕРис. 4-3.8. Трасса алгоритма ADDITION с операндами 75 198 и —1 079 239 ' 339
заносится в Q, а переменные SUM и С снова принимают начальные значения. Управление передается шагу 6, где отрицательное це- лое число, заданное списком с указателем Q, суммируется с нулем. Во время выполнения шага 7 флажок SECOND______________PASS имеет значение true, а С изменяется с —1 на 0. В шаге 8 последний перенос, если он получится от сложения чисел с одним знаком, включается в список с указателем SUM. Шаг 9 реверсирует список и устанавливает отрицательный знак каждого поля COEF, если SECOND____________PASS имеет значение true (в противном случае мы получили бы абсолютное значение суммы). Поучительно проследить выполнение алгоритма для несколь- ких пар целых чисел и небольшого значения МАХ. Читателю следует внимательно изучить следующие примеры. NULPREC: PROCEDURE OPTICNSIHAINJ; /* MAIN PROGRAM THIS PROGRAM PERFORMS MULTIPLE PRECISION ADO IT ION OF SIGHED INTEGERS. CHARACTER STRING INTEGERS ARE INPUT ANO CONVERTED TO LIST FORfi USING PROCEDURE SETUP. THE FUNCTION ADDITION ADDS THE TWO LISTS, FCRMING A THIRD WHICH REPRESENTS THE SUM. THE LIST FORM OF AN INTEGER IS CONVERTED TO A CHARACTER STRING USING PROCEDURE LIST_TO_STRING- PROCEDURES NOT DIRECTLY INVOKED INCLUDE REVERSE, SIGN, AND INSERT. THROUGHOUT THE PROGRAM ARGUMENTS TO PROCEDURES ADDITION, SIGN, ANO LIST_TO_STR1NG SHOULD BE PASSED BY VALUE. NODE: STRUCTURE CONTAINING COEF ANO LINK FIELDS. MAX: IS SUCH THAT О <= ICOEF| < MAX. I.F. MAX IS THE MODULUS Of COEF. . EXP: THE EXPONENT OF THE POWER OF TEN WHICH EQUALS MAX. ZEROES,BLANKS: FOR PADDING CHARACTER STRINGS. L1,L2: POINT TO LIST REPRESENTED INTEGERS WHICH ARE TO BE ADDED. SUM; POINTS TO SUM OF LI ANO L2- 11,12,13: CHARACTER REPRESENTATIONS OF INTEGERS. */ DECLARE ADDITION RETURNS!POINTER) , SIGN RETURNS!BINARY FIXED). INSERT ENTRYIBINARY FIXEDI 31),POINTER) RETURNS!POINTER>, SETUP RETURNS!POINTER), LIST_TO_STRING RETURNS!CHARACTER!BO) VARYING), REVERSE ENTRYIPOINTER,В IT(1)) RETURNS!POINTER) 1 NODE BASEDCNEW), 2 COEF BINARY FIXE0I31I, Z LINK POINTER, MAX BINARY FIXEDOl), EXP BINARY FIXED, ZEROES CHARACTER!10) INITI AL!!101'O’I, BLANKS CHARACTER1B0) IN ITI AL!180)• ’), !L1,SUM,L2) POINTER, ! 11,12,0) CHARAC TER ! 80) VARYING; ON ENOFIlEISYSIN) GO TO STOP; BET LIST! MAX,EXP) ; /« INPUT MODULUS AND ITS POWER OF TEN */ •O WHILE! • 1'6)5 /* INPUT AND ADD TWO INTEGERS */ GET tISTIIl,l2); LI = SETUP!!!!)); /< CONVERT 11 AND 12 TO LIST REPRESENTATION*/ L2 ~ SETUP I!121); SUM = AUDITlONIILl>.!L2)); /* AOD THE NUMBERS */ /«CONVERT EACH INTEGER TO APPROPRIATE OUTPUT REPRESENTATION*/ 11 = SUBSTRIBLANKS,1,80 - LENGTH!Il)I |f II; PUT SKIPI2) LIST ! I 11 ; 12 = SUBSTR!BLANKS,1,80 - LENGTHII2)) |( 12; PUT SKIP L 1ST I 12) ; PUT SKIP L1ST«REPEAT!•-•,80)}; 13 = LIST_TO_STRlNG!I SUM)>; . PUT SKIP LISTII3); eno; Рис. 4-3.9. Программа сложения целых чисел с многократной точностью 340
Пример 4-1. Пусть Р и Q адресуют целые числа —99012 и —915 995 соответственно. Списки, представляющие эти числа, изображены на рис. 4-3.5. На рис. 4-3.6 показана трасса основ- ных шагов алгоритма ADDITION. МАХ в данном случае равно 1000 и NEG — —1. Конечный список представляет целое число —1 015 007. Пример 4-2. Целые числа 75198 и —1 079 239 представлены' списками с указателями Р и Q; эти списки изображены иа рис. 4-3.7. И иа этот раз МАХ равно 1000, но NEG равно 1. Трасса алгоритма показана на рис. 4-3.8. Результирующий список представляет сумму —1004 041. На рис. 4-3.9 приведена программа на ПЛ/1, реализующая рассмо- тренные алгоритмы сложения целых чисел с многократной точностью. SETUPS „ PROCEDURE» I) RE TURNS (POINTER)• TklTCrCD rtvAN RY /« ’^S E T U P CONVERT THE INTEGER GIVEN BY CHARACTER STRING I TO A LIST REPRESENTED INTEGER. NEG: INDICATES THE SIGN OF I. J,NUMBER: AUX1LLIARY VARIABLES» R: POINTS TO RESULTING LIST. ♦/ DECLARE I CHARACTERIBO) VARYING, NUMBER BINARY FIXEDOl), (NEGsJJ BINARY FIXED, r POINTER; r = NULL; . _ , IF SUBSTR 11,1,1> = /* FINO SIGN OF I •/ THEN DO; I = SUBSTR»I,2) 5 NEG = - i; ENO; МРАоТиПн'ZEROES SO NUMER OF MG1TS IS MULTIPLE OF EXP. «Z J = EXP --F1ODILENGTHII ) ,EXP) 5 IF J -.= EXP O*J 1 I ?oELENo”u>Sb}’eXP! Z.’OBTZIN NUMBERS FOR NODES T5" ; LUNUK№i’ = ’r,iS.S . EQUAL -o ZERO? THEN RETURN(NULL)> R = INSERT(NEG * NUMBER,R); END; RETURN»RI; ENO SETUP; PROCEDURE(R) RETURNS(BINARY FIXEDJ? DETERMINE IF THE INTEGER REPRESENTED BY LIST R IS NONNEGATIVE OR NEGATIVE ANO RETURN 1 OR -1 RESPECTIVELY. DECLARE R POINTER; DO WHILE(R -= NULL); IF R -> COEF < О THEN RETURN(-1); IF R-XCOEF > О THEN RETURNll)5 r = r->link; END; RETURN(l); END SIGN; Pmc. 4-3.9. Продолжение 341
INSERT: PROCEDURE!NUMBER,R» RETURNS(POINTER IJ /* INSERT ALLOCATE A NODE STRUCTURE AND PUT THE VALUES OF NUMBER ANO R IN THE COEF AND LINK FIELDS RESPECTIVELY. RETURN THE ADDRESS OF THE NODE.*/ DECLARE NUMBER BINARY FIXEDOil, r pointer; ALLOCATE NCDE ; NEW-XOEF ~ NUMBER; NEW->LINK » R5 RETURN(NEW»5 END INSERT; REVERSES PROCEDURE(R,NEGATEJ RETURNS(POINTER); /«REVERSE REVERSE THE ORDER OF THE NODES IN LIST R, PLACING THE LAST NODE FIRST ETC. NEGATE EACH COEF FIELD IF NEGATE HAS VALUE *1’B. NODES WHICH REPRESENT UNNECESSARY LEFTMOST ZEROES IN THE INTEGER ARE DELETED, «7 DECLARE <R,S,TJ POINTER, NEGATE BITUh SIGNIFICANT BITIll; SIGNIFICANT = ’O’B; INITIALIZE *7 S = NULL; DO WHILEIR -.= NULL»; 7* REVERSE THE LIST *7 IF NEGATE THEN R-XOEF = -R-XOEF; T = s; S a R; R = S->LINK; S->LINK = T; IF -.SIGNIFICANT THEN IF S-XOEF = О THEN DD; /* DELETE A NODE *7 . FREE S—>NODE; S = null; END; ELSE SIGNIFICANT = •!«©; end; RETURNfS»? ENO REVERSE; ADDITION: PROCEDURE(P,fi) RETURNS!POINTER» ? /о ADDITION THIS PROCEDURE ADDS THE INTEGERS REPRESENTED BY LISTS P ANO Q, FORMING A THIRD LIST POINTED TO BY SUFI WHICH CONTAINS THE RESULT. NEC: HAS VALUE -1 WHEN P ANO Q ARE NEGATIVE; OTHERWISE NEG IS 1. Cs INDICATES THE VALUE OF A CARRY OR BORROW. SECONO_PASS; when equal TO «l»e INDICATES the DO WHILE block BEGINNING AT LABEL SCAN MUST BE EXECUTED TO COMPLEMENT THE FIRST ADDITION RESULT. SUMCF; CONTAINS SUM OF CORRESPONDING COEF FIELDS. *7 Рис. 4-3.9. Продолжение 342
DECLARE !P,Q,SUMI POINTER» SUMCF BINARY FIXED!31», <NEG,C» BINARY FIXED, SECOND-PASS BIT!II; IF P = NULL /« TRIVIAL CASE «/ THEN RETURMQI ; IF Q = NULL THEN RETURNIPIJ IF SIGNUP)» - -1 £ SIGNHQ}) = -1 /* INITIALIZE THEN NEG • -1; /* ADD TWO NEGATIVE NUMBERS */ ELSE NEG = I; /Ф ADD TWO NONNEGATIVE OR OPPOSITE NUMBERS */ SUH = NULL? C = 0; SECOND-PASS - *o'e; DO WHILE(-.(P = NULL I Q « NULL»); /« ADO COEFFICIENTS «/ SUMCF = NEG « (P->COEF ♦ Q->COEF F CIS IF SUMCF < 0 THEN C = -1; ELSE C = NEG * (SUMCF / MAXIJ SUN = INSERT(NEG -ft MODISUMCF,MAX J?SUM»! P = P->LINK; Q = Q->L(Nr; /* NEXT NODES °/ ENO; IF Q = NULL THEN Q = p; SCAN: /* SCAN REMAINDER OF LIST Q DO WHILE IQ -»= NULL» ; SUMCF = NEG « (Q->COEF ♦ CJ5 IF SUMCF < 0 THEN c = -i; ELSE C = NEG о (SUMCF / MAX»? SUM = INSERTING** MOD!SUMCF,MAX),SUMI 5 Q = q->link; end; IF C = -I £ NEG = 1 THEN /* COMPLEMENT OF SUM IS REQUIRED #/ IF S€CCND_PASS THEN C = o; /« SECOND PASS COMPLETED *7 - ELSE /* PREPARE FOR SECOND PASS »/ DO; SECOND_PASS = ’1’B; Q =~REVERSE(SUH,*1’ 6)5 SUN = null; c = o; GO to scan; ENC; IF C -•= 0 /* FINAL CARRY </ THEN SUM = INSERT(C,SUM»; IF SECOND-PASS THEN SUM = REVERSE(SUM-•1’В»; ELSE SUM = REVERSE(SUM,»0*B); RETURN!SUMI; ENO ADDITION; Рис. 4-3.9. Продолжение .343
*LIST_TO_STRING: PROCEDURE!R) RETURNS(CHARACTER(60/ VARYING)5 J LlST_TO_stRING CONVERT THE INTEGER REPRESENTED BY LIST R TO A STRING FORM* •RS PASSED GY VALUE TYPES GIVES SIGN FOR CHARACTER REPRESENTATION. NUMBER: CONTAINS THE CHARACTER REPRESENTATION OF A NUMBER FROM A NODE. ’.STRINGS CHARACTER REPRESENTATION OF THE INTEGER */ . DECLARE R POINTER, TYPE CHARACTER!1), NUMBER CHARACTER! 1А» VARYING, STRING CHARACTER<80) VARTING5 IF SIGNIIR)) = 1 THEN TYPE = • •; ELSE TYPE = •-’) IF R ’ NULL THEN STRING = *0'; ELSE STRING « *’?? DO WHILE(R -= NULL); NUMBER = ABS(R—>COEF); NUMBER - SUBSTRINUMBER,15 - EXP); STRING = TRANSLATE!NUMBER,’0’,» •) II STRING; R » R->LINK; ENO; 00 WHILE!1NDEXISTRING,*0*) = 1 € LENGTH!STRING) > l> ? STRING = SUBSTR!STRING,2); /« DELETE LEADING ZERO */ . END; STRING ’ TYPE I) string; STRING = SUBSTR(BLANKS,l,eO - LENGTH!STR ING)) || STRING; RETURN(STR ING)» END LI$r_TO_STRING; STOPS «END NULPREC; 321045672 0 321045672 12345650 6690123 19235773 650125789654 425579624-759 1075705414413 -1096 -9934 -11030 -2789300579624 1300800555601 -14885000Z4023 -10800 12300 1500 -1403 1399 403 -399 iPnc. 4-3.9. Продолжение 344
Упражнения к п. 4-3.3 1. Постройте списковое представление целых чисел 75 198 и —1 079238 при значении МАХ, равном 128. 2. Проследите выполнение алгоритма ADDITION подобно тому, как пока- зано на рис. 4-3.6 и 4-3.8, использовав списки, сформированные в упр. 1. Преоб- разуйте список с указателем SUM к основанию 10 и проверьте результат непо- средственным вычислением суммы. 3. Разработайте алгоритм для умножения двух целых чисел, представлен- ных списками.^ 4-4. АССОЦИАТИВНЫЕ СПИСКИ В этом параграфе вводится понятие ассоциативного списка, или ассоциативной структуры, и эта структура данных сравнивается с линейными списками, которые мы изучали в гл. 3 и 4. Приведены несколько примеров и дано представление ассо- циативных списков в памяти. Ассоциативный список можно рассматривать как особый вид линейного списка. Его различие заключается в том, что здесь, ни о каком элементе нельзя сказать, что он является первым элементом списка или последним элементом списка, как это имеет место в случае стека или очереди, или п-м элементом списка, как это принято для обычных линейных списков. Вместо этого в ассоциативных списках доступ к конкретному узлу произво- дится по соответствующему имени. В качестве примера ассоциативной структуры рассмотрим задачу анализа текста и подсчета вхождений каждого слова в этом тексте. Если в качестве отрывка текста взять английское пред- ложение As ап example of an associative structure, consider the problem of analyzing text and counting the number of occurrences of each word in the text то его слова и число вхождений слов могут быть представлены: в виде табл. 4-4.1. Таблица 4-4.1 Подсчет вхождений слов в тексте Слово Число вхождений Слово Число вхождений as 1 text 2 ап 2 and I example 1 counting 1 of 4 number 1 associative 1 occurrences 1 structure 1 each 1 consider 1 word 1 the 3 in 1 problem 1 analyzing I Это текст предыдущего предложения иа английском языке. •— Прим, пер- 34&
Ассоциативный список можно рассматривать как линейный список, в котором каждое звено, или узел, содержит два или большее число информационных полей. Одно из этих полей, обычно первое, является уникальным в ассоциативном списке. Это особое поле мы будем называть ассоциативным полем. Остав- шееся поле или поля мы будем называть содержательным полем или содержательными полями. В качестве примера рассмотрим список, составленный из слов текста и соответствующих им чисел вхождений этих слов для приведенного выше английского предложения. В скобочной форме представления списков, которая более широко будет использо- ваться в гл. 5, мы можем задать этот список в виде ((as, 1)(ап, 2) (example, 1) ... (word, l)(in, l)(analyzing, 1)). Слова ’as’, ’an’, ’example’ и т. д. образуют множество значений ассоциативных полей, данного списка. Из-за способа, с помощью которого в общем случае осуще- ствляется обращение к узлам ассоциативного списка, мы рас- сматриваем такой список как особый случай линейного списка. В приведенном примере доступ к счетчику слов осуществляется с помощью соответствующего ему слова. Именно такой метод доступа и применяется главным образом в ассоциативных списках. В системе записи алгоритмов обращение к отдельному узлу ассоциативного списка (например, списка WORDCNT) выра- жается в виде WORDCNT <WORD>, где WORD — переменная, имеющая значение ассоциативного поля. В нашем примере таким значением является определенное слово. Угловые скобки исполь- зуются для обозначения ассоциативного поля и различения ассо- циативно-списковой формы спецификации узлов и спецификации элементов массива. Следовательно, согласно примеру, взятому из табл. 4-4.1, оператор Установить X WORDCNT(’AS’> присваивает переменной X значение 1, а оператор Установить X WORDCNT(’OF’) присваивает переменной X значение 4. Как отмечалось в гл. 3, важным свойством списка является способность увеличиваться или сокращаться по мере включения или исключения узлов из списка. Ассоциативные списки также обладают этим свойством. В нашей системе записи алгоритмов новый узел списка создается оператором присваивания. На- пример, оператор Установить WORD (’NEW’ > 3 создает новый узел со значением 3 и ассоциирует с ним символь- ную строку ’NEW’. 346
Изменение узла также осуществляется оператором присваива- ния. В примере с подсчетом слов текста оператор Установить WORDCNT <’AN’> WORDCNT {’AN’; 4- 1 присвоит новое значение содержательному полю узла в списке- WORDCNT. Если мы попытаемся обратиться к содержательному полю или полям, используя ассоциативное имя, отсутствующее в списке, то в точку вызова будет возвращен либо нуль, либо пустая строка. Это соглашение принято, в частности, в языке программирования СНОБОЛ, который содержит ассоциативные структуры данных. Например, если ’DATA’ является именем, которое отсутствует в списке WORDCNT, то оператор Установить X WORDCNT .(’DATA’) + 1 присвоит переменной X значение 1, а оператор Установить X ’THE WORD COUNT OF NEW IS’ О WORDCNT {’DATA’) присвоит этой же переменной X значение строки ’THE WORD1 COUNT OF NEW IS’. Следующий алгоритм выполняет анализ частоты появления слов в заданном отрывке текста. Слова, имеющиеся в тексте, и соответствующие им счетчики печатаются в конце анализа. Алгоритм WORD_____ANALYSIS. Для заданного входного- текста TEXT проводится анализ частоты появления слов путем помещения в ассоциативную структуру WORDCNT счетчиков; всех слов текста. ALPHABET — символьная строка ’ABCDEFGHIJKLMNOPQRSTUVWXYZ’. 1. [Инициализация.] Установить CURSOR -с-1. 2. [Поиск следующего алфавитного символа. ] Повторять до- тех пор, пока INDEX (ALPHABET, SUB (TEXT, CURSOR, 1)) = = 0: установить CURSOR CURSOR + 1; если CURSOR > > LENGTH (TEXT), то печатать слова и соответствующие счет- чики слов. Закончить выполнение алгоритма. 3. [Выделение слова. ] Установить DUMMY ч-SPAN (TEXT, ALPHABET, CURSOR, WORD, ”,false), WORDCNT (WORD) <- WORDCNT (WORD) ф1и возвратиться к шагу 2. После того как курсор, используемый для сканирования текста TEXT, установлен в исходное состояние 1, в шаге 2 локали- зуется первый буквенный символ (т. е. первая буква некоторого- слова). В шаге 3 выделяется алфавитная строка с помощью функ- ции SPAN, описанной в п. 2-3.2, и звачение этой символьной, строки присваивается переменной WORD. Затем содержательное поле ассоциативного списка, соответствующее WORD, увеличи- вается на единицу. Заметим, что при первом появлении слов»: 347?
s ассоциативном списке для него формируется значение нуль. Алгоритм завершается в шаге 2, если значение указателя CURSOR становится больше длины заданного текста. Перед завершением алгоритма все слова и соответствующие нм счетчики выводятся на печать. Каким образом это конкретно осуществляется, зависит от используемой системы программирования. Поскольку в языке СНОБОЛ имеется ассоциативный тип структуры данных, рас- смотрим, как можно реализовать алгоритм анализа текста с по- мощью этого языка. В языке СНОБОЛ для описания ассоциативной структуры ис- пользуется функция TABLE. Чтобы показать, как с помощью этой функции создаются и используются данные, рассмотрим следующую программу на СНОБОЛе, выполняющую задачу ана- лиза текста, описанную выше в данном параграфе. READIN NEXTWORD BEGINOUT PRINT END LETTER = ’ABCDEFGHIJKLMNOPQRSTUVWXYZ’ PAT = BREAK (LETTER)SPAN(LETTER). WORD WORDCNT = TABLE(5, 4) TEXT = TEXT INPUT : F (BEGINOUT) TEXT PAT = : F (READIN) WORDCNT (WORD) = WORDCNT : (NEXTWORD) (WORD) + I OUTFORM = CONVERT(WORDCNT, -ARRAY’) I = I OUTPUT = OUTFORM (I, 1> ’’OUTFORM (1,2) : F (END) I = I + I : (PRINT) В этой программе WORDCNT инициализируется оператором присваивания WORDCNT = TABLE (5, 4) с использованием функ- ции TABLE. (Назначение параметров 5 и 4 вскоре будет описано.) Текст вводится в переменную TEXT и сопоставляется с образцом РАТ. Этот образец используется для локализации первой буквы в тексте и выделения начинающейся с иее подстроки алфавитных символов. Эта подстрока затем присваивается переменной WORD. После этого модифицируется узел, соответствующий значению WORD, в таблице, созданной с помощью функции TABLE. Анализ продолжается до тех пор, пока не будет обработан весь текст. Функция CONVERT в СНОБОЛе автоматически пре- образует таблицу в двумерный массив. (Прн обсуждении струк- туры хранения ассоциативных списков мы увидим, что такое пре- образование выполняется относительно просто.) Преобразованная таблица затем выводится на печать и выполнение программы за- канчивается. Теперь обсудим представление ассоциативных списков в па- мяти. Будут рассмотрены два метода представления: первый, при- меняемый для представления таблиц в СНОБОЛе (метод очень близок к предложенному Грисуолдом [4]), и второй, основанный на применении функций хеширования и подобный методу, опи- 348
санному в п. 4-3.2 для связанного словаря. Оба метода исполь- зуют связанную форму представления данных в памяти. В СНОБОЛе таблицы могут быть реализованы в виде блоков, содержащих совокупность пар ячеек памяти. Каждая такая пара содержит дескриптор слова, или ассоциативное поле, и содержа- тельное поле. По мере того как данные включаются в таблицу, заполняется блок ячеек памяти. Когда блок заполнится пол- ностью, выделяется новый блок, который связывается ,с концом последнего заполненного блока. В качестве примера рассмотрим представление в памяти первых девяти узлов списка частот по- явления слов в английском предложении, приведенном в начале этого параграфа. Предположим, что исходный блок для та- блицы имеет размер 2 х М 4- 1 при М ~ 5, а любой блок расши- рения имеет размер 2 х N + 1 при N = 4. В приведенной ранее программе на СНОБОЛе мы описали начальный размер таблицы и размер каждого дополнительного блока предложением WORDCNT = TABLE (5, 4) Рис. 4-4.1 иллюстрирует представление в памяти части списка счетчиков слов. На рисунке первая ячейка каждого блока содер- жит длину этого блока, уменьшенную на единицу, и указатель на следующий блок, если ои уже был выделен. Первое поле в каждой из остальных ячеек описывает тип данных, соответствующих этой ячейке. Например, S — означает строку, а I — целое число. Второе поле ячейки содержит либо описатель строки, либо чис- ленное значение в зависимости от типа элемента. Положение узла списка определяется линейным просмотром ассоциативных полей, которые находятся в первой ячейке каждой пары ячеек. Если поиск заданного имени в первом блоке заканчи- вается неудачно, то через поле связи в первой ячейке блока осу-, ществляется переход к следующему блоку, и поиск продолжается. 349
Ассоциа- Содержа- Позиция тивнь/и тельный. Поле в таблице элемент элемент ссылки Рис. 4-4.2. Представление ассоциативного списка в памяти с помощью кеширования Вторым методом представления ассоциативных списков яв- ляется применение функций хеширования для ассоциативных полей списка. В п. 4-3.2 было показано, как функции хеширования могут быть использованы для построения связанного словаря. Воспользуемся рассмотренной там функцией хеширования и по- кажем, как с ее помощью можно представить ассоциативный спи- сок. Эта функция хеширования определяется в виде произведения длины имени и числа, образуемого внутренним представлением первого и последнего символов имени. Это произведение затем делится на размер таблицы, который был выбран равным 10. Остаток от деления и дает значение функции хеширования. Про- цесс хеширования иллюстрируется в табл. 4-3.1. На рис. 4-4.2 показано, как ассоциативный список из нашего примера подсчета слов может быть размещен в памяти с йспользованием описанной функции хеширования, если размер первичной таблицы равен 10. И на этот раз иа рисунке показано размещение лишь первых де- вяти узлов списка. После того как слово просканировано и выделено, оно хеши- руется в первичную таблицу, как показано на рис. 4-4.2. Если позиция таблицы, соответствующая значению хеш-функции, сво- бодна, то слово помещается в нее, а содержательное поле узла устанавливается в нуль. Если соответствующая хеш-зиачению 350
позиция оказалась занята, то проверяется совпадение значения ассоциативного поля в этой позиции с выделенным словом. В слу- чае их совпадения возвращается значение содержательного поля, соответствующего выделенному слову. При несовпадении про- веряется поле указателя, чтобы определить, содержит ли оно значение NULL или адрес таблицы переполнения. Если поле ука- зателя содержит нулевое значение, то слово помещается в ассо- циативное поле первой же свободной позиции в таблице перепол- нения, а в поле указателя соответствующей позиции в первичной таблице помещается адрес в таблице переполнения. Но если поле указателя содержит адрес некоторой позиции в таблице перепол- нения, то ассоциативное поле в таблице переполнения анализи- руется аналогично тому, как это делалось для первичной таблицы. При этом связанный список ассоциативных полей переполнения просматривается до тех -пор, пока не будет найдено поле, содер- жимое которого совпадает с выделенным словом. Если такого поля нет и достигнут конец связанного списка элементов перепол- нения, то слово помещается в конец этого списка. В примере, который иллюстрируется иа рис. 4-4.2, значение функции хеши- рования для слова 'the' равно 0, для слов 'ап' и 'example' — 2, для слов 'of’/consider', 'problem*—4, для слов 'structure' и 'associative' — 6, а для слова 'as' — 8. В этом параграфе в качестве иллюстрирующего примера все время использовался ассоциативный список, относящийся к опе- рации подсчета появления слов в отрывке текста. Ассоциативные структуры применимы в любой задаче, в которой имеет место ассо- циация определенного элемента (такого, как счетчик слов) или элементов (таких, как сведения в записи о студенте) с уникальным идентифицирующим элементом (таким, как некоторое слово или имя студента). В гл. 7 мы рассмотрим частный тип файла, называ- емого индексно-последовательным, в котором также используется свойство списковой ассоциативности. Упражнении к п. 4-4 I. Разработайте алгооитм (который может содержать ряд под- алгоритмов) для обработки записей о группе студентов с использованием ассо- циативного списка, называемого STUDENT. Ассоциативным полем здесь яв- ляется имя студента (инициал н фамилия). В качестве содержательных полей выступают идентификационный номер (ID), название колледжа (COLLEGE), оценки подесятн заданиям (MARK [I] для 1—1, 2, ..., 10) и текущий бал (GRADE). Чтобы сослаться на определенный элемент, такой как оценка сту- дента с именем J. BROWN по шестому заданию, мы используем выражение MARK16] OF STUDENT<’J, BROWN’> Система ведения записей о студентах должна обрабатывать четыре входные команды: а) команду включения в виде INSERT<hmh студента><номер> <колледж>, которая включает номер ID и название колледжа в запись о студенте; 351
б) команду исключении в виде DELETE <имя студента> которая исключает запись о студенте; в) команду обновления в виде UPDATE <имя студентаХиомер заданияХоценка> которая заносит в запись о студенте требуемую оценку по указанному заданию; г) команду вывода в виде OUTPUT <имя студента> или OUTPUT ALL которая печатает либо запись о конкретном студенте, либо запись обо всех сту- дентах. 2. Напишите на языке ПЛ/1 программу, реализующую систему ведения записей о студентах, описанную в упр. 1. Поскольку в языке ПЛ/1 отсутствуют ассоциативные списки, вы должны смоделировать их с помощью базированной памяти и процедуры с именем STUDENT. Вызов процедуры CALL STUDENT (’J.,BROWN', ID#, COL, MKS, GR); позволяет обратиться к элементам записи, соответствующим студенту с именем J. BROWN. Эта процедура возвращает его идентификационный номер в ID#, название колледжа — в COL, его оценки — в массиве MKS и текущий балл — в GR. Используйте либо блочный метод, либо хеширование для моделирования структуры хранения ассоциативного списка. Список литературы 1. Берзтисс А. Т. Структуры данных. — Пер. с англ. М.: Статистика, 1974. 403 с. 2. Brllllnger Р. С., Cohen D. J. Introduction to Data Structures an Non- numeric Computations. Englewood Cliffs: Prentice—Hall, 1972. 3. D’Imperio M. E- Data Structures and Their Representation in Storage.— Annual Review in Automatic Programming, vol. 5, pp. 1—75, Oxford: Pergamon Press, 1969. 4. Griswold R. E. The Macro Implementation of SNOBOL 4: A Case Study of Machine-Independent Software Development. San Francisco: Freeman, 1972. 5. Harrison M. C. Data Structures and Programming. Glenview: Scott, Fo- resman, 1973. 6. Кнут Д. Искусство программирования для ЭВМ. Том 1. Основные алго- ритмы. — Пер. с англ. М.: Мир, 1976. 735 с. 7. Кнут Д. Искусство программирования для ЭВМ. Том 2. Получислениые алгоритмы. — Пер. с англ. М.: Мир, 1977. 724 с. 8. Walker Т. М-, Cotterman W. W. An Introduction to Computer Science and Algorithmic Processes. Boston: Allyn and Bacon: 1970.
Глава 5. НЕЛИНЕЙНЫЕ СТРУКТУРЫ ДАННЫХ В предыдущих главах были рассмотрены линейные списки. С по- мощью таких структур данных могут быть выражены в основном одномерные отношения. В этой главе будут введены структуры данных, носящие нелиней- ный характер и позволяющие благодаря этому выражать более сложные отно- шения, нежели линейное отношение физического соседства. Наиболее важным типом нелинейных структур данных являются, по-видимому, деревья. Деревья часто используются на практике для представления отношений между элемен- тами данных. В связи с этим значительная часть настоящей главы будет посвя- щена описанию принципов работы с деревьями и их представлениям, а также приложениям, в которых используются деревья. В начале главы дано детальное описание деревьев, начиная с основных по- нятий теории деревьев и кончая их представлениями и принципами работы с ними. В п. 5-2 описан ряд важных приложений, в которых часто используются де- ревья. В п. 5-3 рассмотрены многосвязные структуры и их приложения. Более универсальным структурам (графам), которые могут содержать петли и циклы, посвящен п. 5-4. В этом параграфе описаны способы хранения графов и работа с ними на основе бинарных матриц и списковых структур. Ряд приложений, в которых могут применяться графы, описан в п. 5-5. Такие сложные структуры данных, как графы, обусловливают необходимость использования сложных приемов управления памятью; некоторые из них описаны в п. 5-6. 5-1. ДЕРЕВЬЯ Настоящий параграф посвящен очень важному типу структур данных —деревьям. В и. 5-1.1 вводятся основные по- нятия теории графов, которые будут использованы во всей главе. В п. 5-1.2 рассмотрен ряд операции над деревьями, например об- ‘ ход' дерева и установление эквивалентности деревьев. Здесь же даны различные способы представления деревьев в памяти ЭВМ, основанные на последовательном и связанном размещении эле- ментов дерева. Изложение п. 5-1.2 сопровождается описаниями рекурсивных и итеративных алгоритмов. 5-1.1. Определения и основные понятия Рассмотрим сначала граф общего вида и связанную с ним терминологию. Дерево можно рассматривать как граф с не- которыми ограничениями. Ограничения, накладываемые на граф в определении дерева, дают дерево общего вида. 12 Трамбле Ж., Соренсон П. 353
Рис. 5-1.1. Примеры диаграмм графов Рассмотрим диаграммы, изображенные на рис. 5-1.1. С точки зрения нашего изложения эти диаграммы изображают графы. Заметим, что каждая диаграмма содержит множество точек, изображенных кружками и иногда снабженных пометками v1? v2, ... или 1, 2, ... Кроме того на приведенных диаграммах не- которые пары точек соединены линиями или дугами. Остальные детали, например форма дуг, их длина, позиции точек и т. д., в данном изложении роли не играют. Заметим также, что каждая дуга начинается в одной точке и заканчивается в некоторой дру- гой точке. Дадим теперь определение графа, который фактически представляет собой некоторую математическую систему. Такая система является обобщением графов, изображенных на рис. 5-1.1. Граф G включает непустое множество V, называемое множе- ством вершин (точек, узлов) графа, множество Е, представляющее собой множество ребер графа, и отображение Ф множества ре- бер Е на множество пар элементов из V. Везде далее будем полагать, что как множество V, так и мно- жество Е графа конечны. Граф удобно обозначать как G = (V, Е). Заметим, что в определении графа предполагается, что каждому ребру графа х £ Е можно поставить в соответствие пару вершин (u, v) этого графа. Если ребру х £ Е сопоставлена пара вершин (u, v), где и, v £ Е, то будем говорить, что ребро х соединяет вершины и и v. Любые две вершины, соединенные в графе ребром, называются смежными. Ребро графа G = (V, Е), направленное от одной вершины к другой, называется ориентированным ребром, а ребро, не име- ющее определенного направления, называется неориентирован- ным ребром. Граф, каждое ребро которого ориентировано, назы- вается ориентированным графом, или орграфом. Граф, каждое ребро которого не ориентировано, называется неориентирован- 354
ным графом. Если часть ребер ориентирована, а часть нет, то та- кой граф называется сметанным графом. На диаграммах 5-1.1 ориентированные ребра изображены с помощью стрелок, которые задают направления. Графы, пред- ставленные на рис. 5-1.1, б, д и ж, являются ориентированными. Графы на рис. 5-1.1, в й е — неориентированные, а граф на рис. 5-1.1, г — смешанный. Граф, изображенный иа рис. 5-1.1, а, можно рассматривать либо как ориентированный, либо как не- ориентированный. Вершины / и 2, 2 и 5, 8 н lt 2 и 4, 3 и 4 графа на рис. 5-1.1, е являются смежными. Карта города, на которой изображены только улицы с одно- сторонним движением, представляет собой пример ориентирован- ного графа; его вершинами служат перекрестки, а ребрами — улицы. Карта, на которой изображены только улицы с двусто- ронним движением, является примером неориентированного графа, а карта с изображением всех улиц — пример"’смешанного графа. Пусть (V, Е) — граф, а х С Е — ориентированное ребро® соответствующее упорядоченной паре вершин (u, v). Тогда гово- рят, что ребро х начинается в вершине и (или исходит из вер- шины и) и заканчивается в вершине v (входит в вершину v). Вершины и и v называют также начальной н конечной вершинами ребра х. Если ребро х £ Е соединяет вершины н и v (независимо от того, ориентировано оно или нет), то говорят, что это ребро инцидентно вершинам и и v. Ребро графа, соединяющее некоторую вершину саму с собой, называется петлей (не нужно путать с петлей в программе). Ее направленность никакого значения не имеет, и, следовательно, петлю можно считать как ориентированным, так и неориентиро- ванным ребром. В графах, изображенных на рис. 5-1 1, между любой парой вершин имеется не более одного ребра. В случае ориентированных ребер между некоторой парой вершин возможны два ребра, кото- рые имеют противоположную направленность и считаются раз- ными. В некоторых как ориентированных, так и неориентирован- ных графах допускаются пары вершин, соединенных более чем одним ребром, как это показано на рис. 5-1.2, а и б. Такие ребра называются параллельными. На рис. 5-1.2, а два параллельных ребра соединяют вершины 1 и 2, три параллельных ребра — вершины 2 и 3, кроме того, у вершины 2 имеется две параллельных петли. Любой граф, содержащий параллельные ребра, называется ^ультиграфом. Если же между любой парой вершин имеется не оолее одного ребра (не более одного ребра данного направления в случае ориентированного графа), то такой граф называется простым графом. Все графы, изображенные на рис. 5-1.1, яв- z ляются _ простыми. 12* 366
a) Рис. 5-1.2. Примеры графов с параллель- ными ребрами Рис. 5-1.3. Взвешенные графы Графы, изображенные на рис. 5-1.2, а и б, можио представить в виде диаграмм, изображенных на рис. 5-1.3, а и б, на которых числа, проставленные у ребер, показывают кратность этих ребер. Кратность можно также рассматривать как вес, приписанный соответствующему ребру. Такая интерпретация позволяет обоб- щить понятие веса на произвольные действительные числа (не- обязательно целые). Граф, у которого каждому ребру приписан вес, называется взвешенным графом. Примером взвешенного графа может служить граф, характери- зующий систему трубопроводов, в котором веса, приписанные ребрам, обозначают количество вещества, передаваемого по тру- бам. Аналогично, ребрам графа городских улиц можно приписать веса в соответствии с интенсивностью движения по каждой улице. Вершина графа, у которой иет ни одной смежной вершины, называется изолированной. Граф, содержащий только изолиро- ванные вершины, называется нульграфом (полностью не связан- ным графом). Другими словами, иульграф — это граф, множество ребер которого пусто. Граф, изображенный на рис. 5-1.1, а, пред ставляет собой иульграф, а у графа на рис. 5-1.1, е имеется одна изолированная вершина. На практике изолированные вершины в графе особого интереса не представляют. В определении графа не содержится ссылок на длину, форму и позицию его дуг, а также нет никаких указаний на порядок расположения вершин. Поэтому для некоторого конкретного графа диаграмма, представляющая этот граф, не единственна. Располагая вершины графа на разных позициях и изображая его ребра с помощью дуг или линий различной формы, можно полу- чить разнообразные диаграммы этого графа. Такой произвол в изображении графа может, в свою очередь, привести к тому, что две диаграммы, внешне совершенно не похожие, будут предста- влять один и тот же граф, как это показано, например, на рис. 5-1.4, й, б. Для ориентированного графа число ребер, исходящих из некоторой начальной вершины v, называется полустепенью 356
1 Рис. 5-1-4. Различные диаграммы одного и того же графа исхода этой вершины. Число ребер, для которых вершина v является конечной, называется полустепенью захода вершины v, а сумма полустепеней исхода н захода вершины v называется полной степенью этой вершины. Для неориентированных графов полная степень, или просто степень вершины v, равна числу ребер, связанных с этой вершиной. Полная степень петли равна 2, а полная степень изолированной вершины равна 0. Введем теперь ряд дополнительных терминов, необходимых для описания простых орграфов. Пусть G = (V, Е) — простой орграф. Рассмотрим такую последовательность ребер графа G, в которой конечная вершина любого ребра является начальной вершиной следующего ребра, если таковое имеется. Примером такой последовательности служит последовательность ((Vi„ ViJ, (vi2, Vi,),-., (V’k-2’ (Vik-p Vik))’ для которой предполагается, что все вершины и ребра, имеющиеся в ней, включены в V и Е соответственно. Обычно такую после- довательность записывают в виде (vi„ V|k). Отметим, что не все ребра и вершины в рассматриваемых по- следовательностях должны быть различными. Отметим также, что ие всякое произвольное множество вершин данного графа, запи- санных в некотором порядке, образует требуемую последователь- ность. В самом деле, каждая вершина в такой последовательности, кроме первой и последней, должна быть смежна с вершиной, стоящей непосредственно перед ней или после нее. Любая последовательность ребер орграфа такая, что конечная вершина любого ребра этой последовательности является началь- ной вершиной следующего за ним ребра, если таковое имеется, задает путь в графе. Говорят, что путь в графе обходит все вершины данной последовательности, если начало пути находится 357
в начальной вершине первого ребра, а конец — в конечной вер- шине последнего ребра последовательности. Число ребер в после- довательности, задающей некоторый путь в графе, называется длиной этого пути. Рассмотрим простой граф, изображенный на рис. 5-1.5. Не- которые из его путей, начинающихся в вершине 2 и заканчива- ющихся в вершине 4, можно записать в виде Pi = ((2, 4)); Р2 = ((2, 3), (3, 4)); Рз = ((2, 1), (1, 4)); Р4 = ((2, 3), (3, 1), (1, 4)); Рв = ((2, 3), (3, 2), (2, 4)); Ре = ((2, 2), (2, 4)). Путь в графе, все ребра которого различны, называется прос- тым путем (простым относительно ребер). Путь в графе, в ко- тором все необходимые им вершины различны, называется эле- ментарным путем {простым относительно вершин). Естественно, что все элементарные пути являются также н простыми. Пути Pi, Р2, Рз и Р4, выделенные на диаграмме, изображенной на рис. 5-1.5, — это элементарные пути, а пути Р5 и Р6 — это простые, но не элементарные пути. Ниже мы покажем, что если в графе существует путь от одной вершины, например и, к другой вершине v, то в нем должен также существовать и эле- ментарный путь из и в V. Путь, начинающийся и заканчивающийся в одной и той же вер- шине, называется циклом {контуром). Цикл называется простым, если соответствующий ему путь простой. Цикл называется элемен- тарным, если он проходит через любую вершину не более 1 раза. Заметим, что начальная вершина появляется в цикле не менее 2 раз; в элементарном цикле — ровно 2 раза. Ниже выписан ряд циклов графа, изображенного на рис. 5-1.5: Q = ((2, 2)); О, = ((1, 2), (2, 1)); Сз = «2, 3), (3, 1), (1, 2)); С4 = ((2, 1), (1, 4), (4, 3), (3, 2)). Рис. 5-1.6. Диаграмма про- стого орграфа Отметим, что любой неэлементар- иый путь содержит циклы, проходя- щие через те вершины, которые по- являются в нем более одного раза. Исключая такие циклы, можно полу- чить элементарный путь. Например, если из пути Р5 исключить цикл ((2,3), (3, 2)), то получится путь Р1э кото- рый так же, как и Р6, начинается в вершине 2 и заканчивается в вер- шине 4, и в то же время является 358
элементарным. Аналогично, если из пути Рв исключить цикл ((2, 2)), то получится элементарный путь Рь Таким же образом из’произвольного цикла, замыкающегося в некоторой вершине, можно получить элементарный цикл, замыкающийся в этой вер- шине. Из-за описанного выше свойства путей ряд авторов исполь- зуют термин «путь» только для обозначения элементарных путей и относят определение длины пути только к элементарным путям. Простой орграф, не имеющий ни одного цикла, называется ациклическим орграфом. Такие графы, естественно, не могут иметь петель. В данном параграфе мы будем рассматривать только ациклические графы. Ниже будут представлены важный класс орграфов — ориен- тированные деревья — и соответствующая им терминология. Де- ревья нужны для описания любой структуры с иерархией. Тра- диционные примеры таких структур: генеалогические деревья, десятичная классификация книг в библиотеках, иерархия долж- ностей в организации, алгебраическое выражение, включающее операции, для которых предписаны определенные правила при- оритета, структура настоящей главы в том виде, в каком она дана на рис. 5-1.6, и т. д. Здесь мы опишем способы представления деревьев с помощью диаграмм и других средств. Способы пред- ставления деревьев в ЭВМ обсуждаются в п. 5-1.2; приложения деревьев приведены в п. 5-1.3. Ориентированное дерево — это такой ациклический орграф, у которого одна вершина, называемая корнем, имеет полустепень захода, равную 0, а остальные — полустепени захода, равные 1. Отметим, что ориентированное дерево должно иметь по крайней мере одну вершину. Изолированная вершина также представляет собой .ориентированное дерево. Вершина ориентированного дерева, полустепень исхода кото- рой равна нулю, называется концевой (висячей) вершиной нли листом; все остальные вершины дерева называют вершинами ветвления. Длина пути от корня до некоторой вершины называ- ется уровнем (номером яруса) этой вершины. Уровень корня ори- ентированного дерева равен нулю, а уровень любой другой вер- шины равен расстоянию1 между этой вершиной и корнем. Отме- тим, что, поскольку ориентированное дерево есть ациклический граф, то все пути в нем элементарны, и длина пути от одной вер- шины до другой, если таковой существует, равна расстоянию между этими вершинами. На рис. 5-1.7 изображены три разные диаграммы одного и того же ориентированного дерева. Другие диаграммы этого дерева можно получить, располагая различными способами вершины от- носительно корня дерева. Рассматриваемое ориентированное де- рево имеет две вершины на первом ярусе. На рнс. 5-1.7, а это 1 Под расстоянием между вершинами здесь понимается модуль разности номеров уровней вершин. — Прим. пер. - 359

рис. 5-1.7. Различные способы изображения деревьев дерево изображается так, как оно растет — от корня вверх, за- канчиваясь листьями на разных ярусах. На рис. 5-1.7, б то же дерево изображено в перевернутом виде. Этот способ удобен для изображения ориентированных деревьев, и его обычно используют в литературе. Диаграмма на рис, 1-5.7, в отличается от диаграммы на рис. 5-1.7, б порядком расположения вершин на каждом из ярусов. Этот порядок в силу нашего определения ориентирован- ного дерева значения не имеет. Мы, однако, рассмотрим некоторые модификации этого определения, в которых упорядоченность вершин в дереве становится существенной. Во многих приложениях относительный порядок следования вершин на каждом отдельном ярусе имеет определенное значение. При представлении дерева в ЭВМ такой порядок вводится авто- матически, даже если он сам по себе произволен. Порядок следо- вания вершин на некотором ярусе можно легко ввести, помечая одну вершину как первую, другую — как вторую и т. д. На ди- аграмме порядок следования вершив можно задать, например, слева направо. Вместо упорядочивания вершин можно задавать порядок иа ребрах. Если в ориентированном дереве на каждом ярусе задан порядок следования вершин, то такое дерево назы- вается упорядоченным деревом. Согласно этому определению диаграммы на рис. 5-1.7, бив изображают одно и то же ориенти- рованное дерево, но разные упорядоченные деревья. Заметим, что упорядоченные деревья как таковые не являются ориентиро- ванными графами, поскольку понятие порядка не входит в оп- ределение ориентированного графа. В настоящем параграфе мы в основном будем заниматься упорядоченными деревьями, поэтому далее термин «дерево», если не оговорено противное, бу- дет означать упорядоченное дерево. При изображении как упорядоченных, так и неупорядоченных ориентированных деревьев важно решить, где должен быть ко- рень: вверху или внизу дерева, так как это влияет на смысл тер- минов, описывающих взаимное расположение вершин, например «над» и «под». Далее будем полагать, что корень расположен вверху дерева, а все остальные его вершины — ниже корня. Легко видеть, что структура ориентированного дерева такова, что каждая его вершина является корнем некоторого поддерева, 361
входящего в это дерево. Действительно, если из дерева убрать его корень и ребра, соединяющие корень с вершинами первого яруса, то получится набор поддеревьев, кории которых — это вершины первого яруса. Так, для дерева на рис. 5-1.7, а—е вершина v7 является корнем поддерева {v7, v8, v8, vM|, вершина va — кор- нем поддерева {vx, v2, v3, v4, v5, v6|, вершина v.2—корнем поддерева {v2, v5, v€|, вершина v5 — корнем поддерева вершина v9 — корнем поддерева {v0, v10| и т. д. Число поддеревьев данной вершины называется степенью этой вершины. Очевидно, что степень любой висячей вершины равна 0. Степень вершины v2 равна 2, так как к ней присоединены под- деревья {v5} и {v6}, а степень вершины vx равна 3, так как к ней присоединены поддеревья {v2, v6, v6,} |v9} и jv4}. Если из дерева убрать корень и ребра, соединяющие корень с вершинами первого яруса, то получится некоторое множество несвязанных деревьев. Множество несвязанных деревьев назы- вается лесом. Мы уже отмечали, что вершина ориентированного дерева является корнем некоторого поддерева; следовательно, поддеревья, расположенные под некоторой [вершиной, обра- зуют лес. Дадим теперь другое — рекурсивное определение ориенти- рованного дерева. В соответствии с этим определением дерево содержит одну или несколько вершин, причем одну из них назы- вают корнем, а остающиеся объединяют в конечное множество деревьев, называемых поддеревьями. Такое определение, как будет показано в п. 5-1.2, позволяет упростить формулирование алгоритмов, работающих с деревьями. В данном выше определении дерево с п вершинами определяется через деревья, содержащие меньше, чемп, вершин. Дерево {v0, ..., ..., Vxo|, изображенное на рис. 5-1.7, определяется через деревья {v„ v6> ve| и )v„ .... vMJ, дерево (v,.. v6. vs}, в свою очередь, может быть определено через деревья [v2, v6, ve}, и |v4} и т. д. В конце концов мы получим деревья, каждое из кото- рых содержит по одной вершине, представляющей собой висячую вершину исходного дерева. Помимо диаграмм имеется ряд других способов графического изображения ориентированного дерева. Применение этих спосо- бов к ориентированному дереву, изображенному на рнсЛ 5-1.7, показано на рис. 5-1.8, а—г. Первый способ заключается в ис- пользовании для изображения поддеревьев известного метода диаграмм Венна, второй — метода вкладывающихся друг в друга скобок, третий способ — это способ, применяемый при состав- лении оглавлений книг. Последний способ, базирующийся на формате с нумерацией уровней, сходен с методами, используемыми в языках программирования ПЛ/1 и КОБОЛ. При применении этого формата каждой вершине приписывается числовой номер. Номер, приписанный данной вершине, должен быть меньше но- меров, приписанных корневым вершинам присоединенных к ней 362
Рис. 5'1.8. различные способы представления деревьев Vj? г) 3 поддеревьев. Отметим, что корневые вершины всех поддеревьев дайной вершины должны иметь один и тот же номер. Метод представления деревьев, иллюстрируемый рис. 5-1.7, б, показывает, как любое алгебраическое выражение с полностью расставленными скобками можно представить в виде древовидной структуры. На самом деле, если для алгебраических операций имеются правила приоритета, то, как это было показано в п. 3-7.2, в алгебраическом выражении иет необходимости расставлять все скобки. В качестве примера рассмотрим выражение V1 * V2 - (V, + V, t v5). Дерево, соответствующее этому выражению, изображено на рис. 5-1.9. Как уже отмечалось выше, нами был выбран такой способ изо- бражения деревьев на диаграммах, при котором корень располага- ется в верхней части дерева и ребра направлены сверху вниз. Все вершины любого заданного яруса изображаются на одной го- ризонтальной линии. В случае упорядоченного дерева порядок следования вершин любого яруса 'считается заданным слева на- право. Задание порядка отличает упорядоченное дерево от дру- гих ориентированных деревьев. Иногда при описании деревьев Удобно применять отдельные термины, принятые в описании гене- алогических деревьев. Так, каждая вершина, к которой есть путь из некоторой другой вершины, например и, называется потомком вершины и. Все вершины, к которым есть пути из и, состоящие только из одного ребра, называются сыновьями вершины и. До сих пор мы не накладывали никаких ограничений на зна- чения полустепеней исхода ориентированных и упорядоченных Деревьев. Если полустепень исхода каждой вершины некоторого ориентированного дерева меньше или равна т, то такое дерево 363
Рис. 5-1.9. Дерево алгебраического выражения о Г Рис. 5-1.10. Примеры бинарных и полных бинарных деревьев называется т-арным деревом. Если же полустепень исхода каж- дой вершины в точности равна либо т, либо нулю, то такое дерево называется полным т-арным деревом. При т = 2 такие деревья называются соответственно бинарными, или полными бинар- ными. На рис. 5-1.10, а изображено бинарное дерево, на рис. 5-1.10, б— полное бинарное дерево, а на рис. 5-1.10, в показаны все четыре возможных расположения сыновей некоторой вершины бинарного дерева. Бинарные деревья, изображенные на рис. 5-1-10, а и г, представляют собой разные позиционные деревья, хотя они не являются разными упорядоченными деревьями. В позиционном бинарном дереве каждая вершина представлена единственным образом посредством строки символов над алфа-, витом {0, 1}, при этом корень характеризуется пустой строкой. Любой сын вершины и характеризуется строкой, префикс (на- чальная часть) которой является строкой, характеризующей и. Строка, приписанная любой висячей вершине и, не является пре- фиксом ни для каких строк, характеризующих другие вершины дерева. Множество строк, соответствующих висячим вершинам некоторого дерева, образует префиксный код этого дерева. Так, префиксный код бинарного дерева, изображенного на рис. 5-1.10, б, представляется в виде {00, 010, ОН, 10, 11}. Аналогично, вершины m-арного позиционного дерева можно пред- ставлять посредством строк символов над алфавитом {0, 1, .... ..., т — 1}. Изображение вершин позиционного бинарного дерева с по- мощью строк символов сразу подсказывает естественный способ представления бинарного дерева в ЭВМ. Пока нам достаточно знать, что такое естественное представление существует. Под- робно оно будет описано в п. 5-1.2. 364
Бинарные деревья оказываются удобными для целого ряда при- ложений. Покажем теперь, что любое дерево может быть единст- венным образом представлено бинарным деревом (это дает воз- можность вместо изучения способов представления в ЭВМ про- извольных деревьев рассматривать способы представления соот- ветствующих бинарных деревьев). Более того, целый лес может быть представлен в виде некоторого бинарного дерева. На рис. 5-1.11 в два этапа показано, каким образом можно получить бинарное дерево, представляющее заданное упорядочен- ное дерево. На первом этапе для каждой вершины уничтожаются все исходящие из нее ребра, кроме самого левого ребра. Вместо них рисуются ребра, каждое из которых соединяет некоторую вер- шину с вершиной, расположенной справа от нее на том же ярусе, что и рассматриваемая (такая операция выполняется только для вершин, которые ранее были братьями — сыновьями одной и той же вершины). После этого для каждой вершины дерева осуществля- ется выбор ее левого и правого сыновей по следующему правилу. Левым сыном является вершина, расположенная непосредственно ниже данной вершины, а правым сыном — вершина, расиоло- Рис. 5-1.12. Представление леса в виде бинарного дерева 365
женная непосредственно Справа от данной и на одном ярусе с ней. Отметим, что полученное бинарное дерево не будет иметь правого поддерева. Описанный выше метод представления произвольных упоря- доченных деревьев посредством бинарных деревьев можно, как это показано на рис. 5-1 12, обобщить на представление произволь- ного упорядоченного леса. Как метод представления дерева, так и метод представления леса можно задать алгоритмически. Описанные выше соответствия между упорядоченным и позици- онным бинарным деревьями, а также между лесом и позиционным бинарным деревом называются естественными. Алгоритм, перево- дящий произвольное дерево общего вида в эквивалентное ему бинарное дерево, будет описан в п. 5-1.2. Упражнения к п. 5-1.1 1. Покажите, что сумма полустепеией захода всех вершин произ- вольного простого орграфа равна сумме полустепеней исхода всех его вершин и что эта сумма равна числу ребер графа. 2. Нарисуйте все возможные орграфы с тремя вершинами. Покажите, что среди них имеется только один орграф без ребер, один орграф с одним ребром, четыре — с двумя ребрами, четыре — с тремя ребрами, четыре — с четырьмя ребрами, один — с пятью ребрами и один — с шестью ребрами. Предполагается, что петли в орграфе отсутствуют. 3- Укажите три разных элементарных пути из вершины Vj в вершину v3 для орграфа, изображенного иа рис. 5-1.13. Каково кратчайшее расстояние между V]. и v3? Имеются ли в этом орграфе циклы? 4. Определите полустепени захода и исхода всех вершин графа, изображен- ного на рис. 5-1.14. Укажите все элементарные циклы этого орграфа. Получите из этого орграфа путем удаления одного из его ребер ациклический орграф. Перечислите все вершины, достижимые из каждой вершины этого графа. 5. На примере покажите, что простой орграф, в котором имеется одна вер- шина с полустепенью захода, равной нулю, а полустепени захода остальных вершин равны 1, необязательно является ориентированным деревом. 6. Сколько имеется разных видов ориентированных деревьев с тремя вер- шинами? Сколько имеется разных видов упорядоченных деревьев с тремя вер- шинами? 7. Представьте выражение (а + Ь) * (с + d) f е в виде ориентированного дерева. Рис. 5-1.13. Ориентирован- ный граф к упражнению 3 366 Рис, 5-1.14. Ориентированный граф к упражнению 4
8. Покажите, что в полном бинарном дереве общее число ребер задается формулой 2 (nt — 1), где nt — число висячих вершин. 9. Получите бинарные деревья, соответствующие ориентированному дереву и лесу, заданным на рис. 5-1.15 и 5-1.16 соответственно. 5-1.2. Представление деревьев в памяти и манипулирование деревьями В предыдущих главах обсуждались методы представле- ния в ЭВМ некоторых элементарных структур данных, таких как линейные списки и массивы. Теперь мы хотели бы применить опи- санные методы к более сложным структурам, таким как деревья и графы Поскольку деревья, по-видимому, являются наиболее важным для приложений типом нелинейных структур данных, то в настоящем разделе основное внимание будет уделено рассмотре- нию способов представления их в ЭВМ и операций над ними. Древовидные структуры могут быть представлены с помощью методов последовательного и связанного размещения их элемен- тов. Преимущества и недостатки метода связанного размещения элементов по сравнению с методом последовательного их размеще- ния при представлении в ЭВМ простейших структур данных об- суждались в п. 4-1. Хотя методы представления деревьев, базиру- ющиеся на последовательном размещении элементов, и суще- ствуют, мы не будем описывать их так же подробно, как методы, основанные на связанном размещении элементов. Последние более удобны, так как обеспечивают возможность легко вставлять вер- шины в дерево или убирать их из него, кроме того, для них ие важен тот факт, что древовидные структуры могут разрастаться до произвольного размера, предсказать который часто невозможно. Мы ограничимся рассмотрением бинарных деревьев, поскольку легко представляются в ЭВМ и с ними легко оперировать. Произвольное дерево общего вида можно легко преобразовать в эквивалентное ему бинарное дерево с помощью алгоритма есте- ственного соответствия. Этот алгоритм будет сформулирован ниже, 367
В настоящем пункте вводится понятие «прошивания» дерева. Это понятие имеет важное значение при выборе способа машинного представления дерева, поскольку способ представления, осно- ванный на его использовании, обладает эффективностью как с точки зрения уменьшения объема памяти, так и с точки зрения увеличения быстродействия. Кроме того, будет описан ряд спо- собов, с помощью которых можно осуществлять обход (или «про- хождение») бинарных деревьев. Займемся теперь проблемой использования метода связанного размещения элементов для представления бинарных деревьев. Напомним; что бинарное дерево имеет одну корневую вершину, у которой либо нет потомков, либо есть только левый потомок- поддерево, либо есть только правый потомок-поддерево, либо есть и левый и правый потомки-поддеревья. Каждый потомок- поддерево, в свою очередь, представляет собой бинарное дерево, и мы различаем у него левую и правую ветви. Удобный способ представления бинарных деревьев дает использование метода связанного размещения элементов дерева, в котором вершины имеют структуру вида LPTR DATA RPTR где поля LPTR и RPTR содержат указатели на левое и правое поддеревья рассматриваемой вершины соответственно, а поле DATA содержит информацию, связанную с данной вершиной. При этом любой из указателей может иметь значение NULL (пусто). На рис. 5-1.17, а, б показаны графическое изображение бинар- ного дерева и соответствующая ему связанная структура в памяти ЭВМ. Сопоставляя эти рисунки, мы видим, что между ними имеется большое сходство, свидетельствующее о близости связанного пред- ставления дерева и логической структуры вложенных в него данных. Это свойство связанного представления дерева может быть использовано при разработке алгоритмов, обрабатывающих древовидные структуры. Рис. 5-1.17. Графическое и связанное представления бинарного дерева 368
Рассмотрим теперь ряд операций, выполняемых над деревьями. Одна из наиболее типичных операций — обход дерева. Обход — это процедура, при выполнении которой каждая вершина обраба- тывается ровно один раз некоторым единым образом. Произволь- ное бинарное дерево, как отмечает Кнут, можно обходить тремя способами, а именно — нисходящим, смешанным и восходящим 1. Ниже даны рекурсивные определения этих обходов. Нисходящий обход: Обработка корневой вершины. Нисходящий обход левого поддерева. Нисходящий обход правого поддерева. Смешанный обход: Смешанный обход левого поддерева. Обработка корневой вершины. Смешанный обход правого поддерева. Восходящий обход: Восходящий обход правого поддерева. Восходящий обход левого поддерева. Обработка корневой вершины. Если некоторое дерево оказывается пустым (т. е. если соответ- ствующая вершина не имеет ни левого, ни правого потомков), то для его обхода не требуется никаких действий. Иными словами, считается, что обход пустого поддерева полностью выполнен уже в тот момент, когда оно встретилось. Если в предыдущих определениях поменять местами слова «левый» и «правый», то мы получим определения трех новых мето- дов обхода, называемых обратным нисходящим, обратным смешан- ным и обратным восходящим методами соответственно. При обходе дерева, изображенного на рис. 5-1.17, нисходящим, смешанным и восходящим методами его вершины будут обраба- тываться в следующем порядке: ABCDEFG (нисходящий), CBAEFDG (смешанный), CBFEGDA (восходящий). (Соответствующие обратные обходы будут иметь вид ADGEFBC, GDFEABC и GFEDCBA.) Хотя процедуры обхода бинарных деревьев, по-видимому, проще всего записывать в виде рекурсивных алгоритмов, мы все же сформулируем для них как рекурсивные, так и нерекурсивные алгоритмы. 1 Принятые в оригинале названия методов обхода связаны с временем об- работки корневой вершины: до того, как обработаны оба ее поддерева (ргеог- аег), после того, как обработано левое поддерево, но до того, как обработано правое (inorder), после того, как обработаны оба поддерева (postorder). Исполь- зуемые в переводе названия методов отражают направление обхода в дереве: °т корневой вершины вниз к листьям — нисходящий обход, от листьев вверх к корню -— восходящий обход, и смешанный обход — от самого левого листа Дерева через корень к самому правому листу. — Прим. пер. 369
RINORDER: PROCEDURE(T) RECURSIVE? /* RECURSIVE PROCEDURE FOR PRINTING THE DATA CONTENT'OF EACH NODE IN A BINARY TREE WHICH IS TRAVERSED IN INORDER, *( DECLARE /# FORMAL PARAMETER */ T POINTER? IF (T = NULL) /* CHECK FOR INVALID TREE */ THEN DO; PUT SKIP LI ST(’INVALID TREE’); RETURN: ENO; PRINT THE LEFT BRANCH OF NODE T IF ONE EXISTS. V IF (T->LPTR -= NULL) THEN CALL RINOROER(T->LPTR); /* PRINT THE DATA CONTENT OF NODE T */ PUT LIST(T->OATA); /> PRINT THE RIGHT BRANCH OF NODE T IF ONE EXISTS. */' IF (T->RPTR -i= NULL) THEN CALL R1NO6DER(T->RPTR) 5 END RINORDER: Рис. 5-1.18. Рекурсивная процедура для смешанного обхода бинарного дерева Рассмотрим сначала рекурсивные программы обхода бинарных деревьев. На рис. 5-1.18 приведена рекурсивная программа для смешанного обхода бинарных деревьев на алгоритмическом языке ПЛ/1. Здесь предполагается, что структура вершины является глобальной по отношению к рассматриваемой процедуре и опре- деляется в основной программе оператором DECLARE 01 NODE BASED (Р), 02 LPTR POINTER, 02 DATA CHARACTER (1), 02 RPTR POINTER; Процедура на рис. 5-1.18 имеет один параметр, принимающий адрес корневой вершины обходимого дерева. Если переменная- указатель Т принимает значение NULL, то считается, что дерева не существует. Остальная часть процедуры соответствует данному выше рекурсивному определению смешанного обхода. Аналогичная программа для нисходящего обхода бинарного дерева приведена на рис. 5-1.19. Рассмотрим теперь обход бинарных деревьев с помощью итера- ции. Поскольку при обходе дерева требуется сначала опускаться, а затем последовательно подниматься по частям деревьев, то для проведения обхода необходимо временно хранить информацию об указателях, которая обеспечит возможность движения вверх по дереву. Заметим при этом, что информация, которая изначально существует в структуре дерева, автоматически обеспечивает^воз- 370
можиость движения вниз по дереву от его корня. Поскольку дви- жение вверх по дереву должно осуществляться в порядке, обрат- ном принятому при движении вниз, то для его проведения необ- ходим стек, в котором в процессе обхода запоминаются указатели. Приведем теперь алгоритм обхода дерева нисходящим спо- собом. Алгоритм PREORDER. Входом алгоритма является бинарное дерево, адрес корневой вершины которого задается переменной Т и структура вершин которого совпадает с описанной выше. При этих условиях данный алгоритм осуществляет обход дерева ни- сходящим методом. При работе алгоритма используется вспомога- тельный стек S, индекс верхнего элемента которого обозначен через ТОР, и вспомогательная переменная Р, указывающая, в каком месте дерева находится алгоритм. 1. [Инициализация.] Если Т = NULL, то завершить выпол- нение алгоритма (структура не имеет корня и поэтому не является бинарным деревом); 2. [Обработка каждого стекового адреса. ] Повторять шаг 3 до тех пор, пока ТОР >[0. 3. [Выборка адреса и спуск по левой ветви. ] Установить Р S [ТОР] и ТОР ч—ТОР — 1. Повторять до тех пор, пока Р #= NULL: обработать вершину Р, если PTRP (Р) #= NULL, то установить ТОР -е-ТОР 4- 1 и S [TOP] -«-RPTR (Р). Уста- новить Р LPTR (Р). 4. [Выход. ] Завершить выполнение алгоритма. RPREORDERS PROCEDURE!Г) recursive; RECURSIVE PROCEDURE FOR PRINTING DATA CONTENT OF EACH NODE IN A BINARY TREE WHICH IS TRAVERSED IN PREORDER. »/ DECLARE /« FORMAL PARAMETER */ T POINTER; IF IT « NULL) CHECK FOR INVALID TREE *7 THEN do; PUT SKIP LIST!’INVALID TREE*); RETURN; END; /* PRINT THE DATA CONTENT OF NODE T */ PUT SKIP LIST(T-SDATA); /* PRINT THE LEFT BRANCH OF NODE T IF ONE EXISTS */ IF (T—>LPTR -» NULL) THEN CALL RPRECRDERIT->LPTR); /* PRINT THE RIGHT BRANCH OF NODE T IF ONE" EXISTS */ IF (T—>RPTR NULL) THEN CALL RPRECROER(T-XRPTR); END RPREORDER; Рис. 5-1.ig. Рекурсивна» процедура для нисходящего обхода бинарного дерева 371
В шаге 3 алгоритма осуществляется заход в вершину и ее обработка. Адрес правой ветви этой вершины запоминается в стеке, после чего осуществляется просмотр цепочки левых ветвей вер- шины до тех пор, пока она не кончится. В этот момент вновь осу- ществляется обращение к шагу 3, из стека выбирается адрес кор- невой вершины последнего из встретившихся правых поддеревьев и осуществляется ее полная обработка в соответствии с шагом 3. Трасса этого алгоритма для “Ч дерева, изображенного на рис. 5-1.17, показана в табл. 5-1.1, в которой в записи содержи- мого стека самый правый элемент соответствует верхнему элементу стека, а запись типа NE обозначает адрес вершины Е. Заход в вершину в данном случае заключается просто в выводе на печать ее метки. Таблица 5-1.1 Трасса алгоритма PREORDER при нисходящем обходе дерева, представленного на рис. 5-1.17 Содержимое стека p Заход в P Выходная строка NA NA A A ND NB В AB ND NC c ABC ND NULL ND D ABCD NG NE E ABCDE NG NF NULL NG NF F ABCDEF NG NULL NG NULL G ABCDEFG На рис. 5-1.20 приведена процедура, реализующая алгоритм PREODER на языке ПЛ/1. Отметим, что в этой процедуре исполь- зован настоящий стек, а не его векторное представление, как это сделано в алгоритме. Как уже отмечалось ранее, в п. 3-6, такой стек легко реализовать на языке ПЛ/1 путем использования управляемого резервирования памяти. Состояние пустого стека в шаге 2 алгоритма определяется путем использования функции ALLOCATION языка ПЛ/1. Эта функция возвращает значение false («ложь»), если стек пуст. Ниже приведен алгоритм обхода дерева восходящим методом. Алгоритм POSTORDER. Предполагается, что структура вер- шин та же, что и в предыдущем случае, и что Т — вновь перемен- ная, содержащая адрес корня дерева. В этом алгоритме также требуется стек S и указатель его верхнего элемента ТОР, но в от- личие от предыдущего алгоритма здесь каждая вершина запоми- нается в стеке дважды, а именно: первый раз — когда обходится 372
PREORDERS PROCEDURE IT); /if ITERATIVE PROCEDURE FOR tHE PREORDER TRAVERSAL OF A BINARY TREE </ DECLARE T POINTER, /<f FORMAL PARAMETER - TREE ROOT А/ P POINTER, /* CURRENT NODE DURING TRAVERSAL ®/ S POINTER CONTROLLED; /Р STACK FOR BRANCH ADDRESSES »/ !* CHECK FOR VALID TREE ANO INITIALIZE */ IF T = NULL f THEN 00; PUT SKIP FILE(SYSPRI NT) LI ST(’INVALID TREE’)? RETURN; END; ELSE DO; ALLOCATE 55 s = T: END; /« PROCESS EACH STACKED BRANCH ADDRESS </ DO WHILE(ALLCCATICNIS)) ; P - S; /о UNSTACK ADDRESS *>/ FREE s; DO WHILE (P->= NULL); /* PROCESS WHILE DESCENDING LEFT CHAIN </ PUT SKIP FILE(SYSPRINT) LIST(P->DATA)5 IF P->RPTR -.= NULL THEN DO; ALLOCATE S; S' = P->RPTR? END; p = p->lptr; END; /* OF LEFT CHAIN DESCENT LOOP *=/ END; /* OF TREE TRAVERSAL LOOP •>/ END PREORDER; Рис. 5-1.20. Процедура для алгоритма PREORDER левое поддерево, и второй раз — когда обходится правое под- дерево. Сама вершина обрабатывается после выполнения обоих этих обходов. Таким образом, в алгоритме должна быть обеспе- чена возможность различать два типа стековых записей. Первый тип записи означает, что в данный момент обходится левое под- дерево, а второй — что обходится правое поддерево. Удобно обозначать второй тип записей с помощью отрицательных указа- телей. При этом, конечно, предполагается, что истинные указа- тели всегда ненулевые и положительные. 1- [Инициализация.] Если Т = NULL, то завершить выпол- нение алгоритма (структура без корня не является бинарным де- ревом); в противном случае установить Р -ч—Т и ТОР 0. - [Запоминание адресов вдоль левой цепи. J Повторять до тех Пор, пока Р NULL; установить TOP -с-TOP I- L S [TOP] ч- Р и Р ^-LPTR (Р). 373
3. [Подъем вверх по дереву с обработкой правых ветвей.] Повторять до тех пор, пока ТОР > 0: установить Р ч- S (ТОР] и ТОР ч-ТОР —- 1. Если Р > 0, то установить ТОР ч-ТОР -|- R S [ТОР] ч----Р (обработка новой правой ветви), Р -ч-RPTR (pj и перейти к шагу 2; в противном случае установить Р ч------Р (обработка правой ветви закончена) и обработать вершину Р. 4. [Выход. ] Завершить выполнение алгоритма. В шаге 2 алгоритма осуществляется просмотр цепочки левых ветвей, при котором в стек заносятся адреса всех встретившихся вершин дерева. В конце этого просмотра стековая запись для последней из встретившихся вершин сравнивается с нулем. Если адрес в этой записи положителен, то он вновь запоминается в стеке с обратным знаком, и производится обработка правой ветви соот- ветствующей вершины по алгоритму шага 2. Если же стековая запись отрицательна, то это означает, что правая ветвь соответ- ствующей вершины уже обработана. В этом случае обрабатывается сама вершина и осуществляется сравнение с нулем следующей стековой записи. Если в предыдущих алгоритмах поменять местами термы 'RPTR* и 'LPTR', то получатся алгоритмы соответственно обрат- ного нисходящего и обратного восходящего обходов. Рассматривая предложенное выше представление бинарных деревьев, легко обнаружить, что в нем имеется много указателей типа NULL. Действительно, с помощью индукции нетрудно по- казать, что в представлении бинарного дерева с п вершинами имеется и -р 1 указателей типа NULL. Это незанятое пространство можно использовать для изменения ранее приведенных предста- влений бинарных деревьев. Пустые указатели заменяются ука- зателями-«нитями», которые адресуют вершины дерева, располо- женные выше. При этом дерево «прошивается» с учетом определен- ного способа обхода- Например, если в поле LPTR некоторой вершины Р стоит NULL, то его можно заменить на адрес, указы- вающий на предшественника Р. в соответствии с тем порядком обхода, для которого прошивается дерево. Аналогично, если поле RPTR пусто, то в нем указывается преемник Р в соответствии с используемым порядком обхода. Поскольку после прошивания дерева поля LPTR и RPTR могут характеризовать либо струк- турные связи, либо «нити», возникает необходимость различать их. Если использовать предыдущее предположение, что истинные указатели являются положительными и ненулевыми, то тогда структурные связи можно представлять с помощью положитель- ных адресов (как обычно), в то время как «нити» будут предста- вляться отрицательными адресами. Кроме того, при создании и обходе деревьев удобно использовать дерево с головной верши- ной HEAD, что мы уже обсуждали в п. 4-2.3. Головная вершина — это просто дополнительная вершина, служащая прн смешанном обходе дерева предшественником первой его вершины и преемни- 374
ком всех его концевых вершин. Юна, в сущности, вводит цикли- ческую структуру в дополнение к уже имеющейся древовидной структур^)- До создания дерева головная вершина имеет вид Здесь пунктирная стрелка определяет связь по «нити». Дерево подвешивается к левой ветви головной вершины и создается указатель на корень этого дерева LPTR (HEAD). На рис. 5-1.21 приведена прошивка бинарного дерева для смешан- иого^обхода. Если имеется прошитое для заданного порядка обхода дерево, то довольно легко разработать алгоритмы для получения пред- шественника или потомка любой заданной вершины Р. Такие алго- ритмы приведены" ниже для смешанного обхода. Алгоритм INS. Входом алгоритма является адрес X вершины прошитого описанным^выше способом бинарного дерева. Данный алгоритм выдает адрес преемника вершины X в соответствии со смешанным порядком обхода дерева. 1. 1Нлть?] Установить INS [RPTR (Х)|. Если RPTR (Х)< С 0. то завершить выполнение алгоритма. 2. [Ветвь левая?] Если LPTR (INS) <0, то завершить вы- полнение алгоритма; в противном случае установить INS ч- ч-LPTR (INS) и повторить настоящий шаг. Алгоритм INP. Этот алгоритм аналогичен предыдущему, но вы- дает адрес предшественника вершины X в соответствии со смешан- ным порядком обхода дерева. 1. [Нить?] Установить INP ч- [ LPTR (X) |. Если LPTR (X) <. 0, то завершить выполнение алгоритма. 2. [Ветвь левая? ] Если RPTR (INP) <0, то завершить выпонение алгоритма; в про- тивном случае установить INP ч-RPTR (INP) и повто- рить настоящий шаг. Использование нитей имеет Два основных преимущества. Во-первых, обход прошитого Дерева выполняется несколько быстрее, чем непрошитого, поскольку в первом случае не требуется стека. Обход дерева -Начинается с головной вер- шины осуществляется путем Рис. S-1.21. Смешанная прошивка бинар- ного дерева 375
упорядоченной генерации всех ее преемников и обработки их по мере генерации. При этой операции используется алгоритм 1NS. Второе преимущество более тонкое. Прошитое представле- ние дерева позволяет эффективно определять предшественника и преемника любой вершины Р при любом порядке обхода. Для непрошитого представления дерева эта задача много труднее. Здесь требуется стек, обеспечивающий информацию для движения вверх по дереву; эту информацию в прошитом дереве несет про- шивка. Таким образом, использование прошитого представления дерева позволяет генерировать предшественника и преемника любой произвольно взятой вершины без излишних затрат на использование стека. Естественно, что указанные преимущества влекут потери в другом плане. В частности, в прошитых деревьях не может быть общих поддеревьев, что допускается в непрошитых деревьях. Кроме того, включение новой вершины в прошитое дерево зани- мает больше времени, чем включение ее в непрошитое дерево, поскольку при этом необходимо поддерживать не только струк- турные связи, но и связи по нитям. Приведенный ниже алгоритм вставляет новую вершину в про- шитое бинарное дерево слева от некоторой заданной вер- шины. Алгоритм LEFT. Этот алгоритм вставляет вершину Р в каче- стве левого поддерева заданной вершины X в случае, если вер- шина X не имеет левого поддерева. В противном случае новая вершина вставляется между вершиной X и вершиной LPTR (X). При выполнении этой операции поддерживается правильная Рис. 0-1.22. Включение вершины слева от заданной вершины 376
структура прошивки, соответствующей смешанному обходу. Для определения предшественника здесь используется алгоритм INP. 1. [Установка полей указателей.} Установить LPTR (Р) ч- ^-LPTR (X), LPTR (Х)<-Ри RPTR (Р) «----------X. 2. [Переустановка, если требуется, связи с предшественни- ком. 1 Если LPTR (Р) > 0, то установить RPTR (INP (Р)) —Р. 3. [Выход. 1 Завершить выполнение алгоритма. На рис. 5-1.22, а показано включение вершины Н слева от вершины Е, у которой левое поддерево пусто. На рис. 5-1.22, б приведен пример включения вершины второго типа; здесь вер- шина I вставляется слева от вершины А. Вернемся к формулировке алгоритма для преобразования леса из деревьев в эквивалентное бинарное дерево. Прежде чем об- суждать этот алгоритм, дадим описание входного формата. Одним из наиболее удобных и естественных способов задания дерева (или леса) общего вида является использование нотации, аналогичной той, которую используют при записи структур в ПЛ/1. Например, два дерева, изображенных на рис. 5-1.23, а, могут быть заданы следующим образом: 01 А 02 В 02 С 02 D ОЗЕ 01 F 02 G 03 1 03 J 02 Н Эквивалентное этому лесу бинарное дерево изображено на' рис. 5-1.23, б. Входом описываемого алгоритма является последовательность вершин, заданная в нисходящем порядке. Для каждой вершины ее Рис. 5-1.23. Лес из двух деревьев (а); представление леса в виде бинарного дерева (6) 377
первый и второй элементы характеризуют номер уровня и имя, связанное с этой вершиной соответственно. Для реализации этого алгоритма необходим стек, каждый элемент которого состоит из двух полей. Первое поле характеризует номер уровня, связанный с данной вершиной, а второе поле задает ее адрес. Алгоритм CONVERT. Входом алгоритма является лес, формат которого был описан ранее. Данный алгоритм преобразует лес в эквивалентное бинарное дерево. В алгоритме используется стек, типовой элемент которого состоит из двух полей. Этот стек со связанной с ним переменной ТОР представляется в виде пары векторов. Элементы векторов L и ADD используются для задания соответственно номера уровня и адреса, связанных с конкретной вершиной. Конструируемое бинарное дерево имеет головную вер- шину HEAD, адрес которой задается переменной Т. Переменные LEVEL'и NAME используются для задания соответственно но- мера уровня и имени вершины. Переменная X используется как вспомогательная указательная переменная. Переменные PRED____LEVEL и Р задают соответственно номер уровня и имя ранее встретившейся вершины. I. [Инициализация.} Установить Т <= NODE, LPTR (Т) ч— ч- RPTR (Т) -ч— NULL, INFO (Т) -ч- 'HEAD', L [1] -ч- О, ADD [1] ч-Т и ТОР ч-1. 2. [Ввод вершины. ] Читать значения переменных LEVEL и NAME из входных данных, при этом,-если считался символ конца данных, то завершить выполнение алгоритма. Установить X <= NODE, LPTR (X) ^-RPTR (X) ч-NULL и INFO (X) ч- -ч-NAME. 3. [Сравнение уровней.] Установить PRED____LEVEL ч- ч—L [TOP], Р ч-ADD [ТОР]. Если LEVEL > PRED_____LEVEL, то установить LPTR (Р) ч— -ч-Х и перейти к шагу 5. 4 [Выборка верхнего элемента из стека.] Повторять до тех пор, пока PRED—LEVEL > LEVEL: установить ТОР ч—TOP — 1. PRED—LEVEL -ч-L [TOP], P ч-ADD [TOP]. Если PRED—LEVEL < LEVEL, то печатать 'СМЕШАНЫ НОМЕРА УРОВНЕЙ' и завершить выполнение алгоритма. Уста- новить RPTR (Р)’ч-Х и ТОР +-ТОР — 1. 5. [Проталкивание в стек новой вершины. ] Установить ТОР ч- +-ТОРЧ- 1, L [ТОР] ч-LEVEL, ADD [ТОР] -ч-Х и перейти к шагу 2. В шаге 1 алгоритма создается головная вершина, номер' ее уровня и имя помещаются в стек. В шаге 2 осуществляется считы- вание пары величин, связанных в структуре с некоторой верши- ной. Алгоритм заканчивает свою работу в шаге 2 после того, как будет обнаружен символ конца данных. Шаг 3 переносит номер уровня и адрес из самого верхнего элемента стека в переменные PRED____LEVEL и Р соответственно. Если номер уровня обраба- тываемой вершины больше номера уровня вершины из верхнего 378
элемента стека, то левая связь последней устанавливается на адрес первой. Затем управление передается шагу 5. В шаге 4 элементы выбираются из стека до тех пор, пока номер уровня самого верхнего элемента стека ие станет меньше или равным но- меру уровня обрабатываемой вершины. Если в результате такого сравнения окажется, что первый номер меньше второго, то это означает наличие ошибки нумерации в древовидной структуре; иначе, т. е. в случае равенства, правая связь вершины из стека устанавливается равной X и убирается из стека. Последний шаг алгоритма помещает в стек номер уровня и адрес обрабатываемой вершины и передает управление шагу 2. Трасса алгоритма, обрабатывающего лес, изображенный на рис. 5-1.23, а, приведена в табл. 5-1.2, где NA обозначает адрес вершины A, NB — адрес вершины В и т. д. Стековая запись вида 1NA означает, что в стек помещен номер уровня (1) и. адрес (NA) некоторой вершины с именем А. Вершина стека изображается справа. Заметим, что если в заданном лесе имеется несколько вершин с одинаковым именем (скажем, В),’ то все эти вершины будут иметь разные адреса, несмотря на то, что наша нотация ссылается на каждый из них одинаково (как на NB). В таблице отображены только изменения, возникающие в результате преды- дущих шагов алгоритма. Таблица 5-1.2 Трасса алгоритма преобразования леса, изображенного на рис. 5-1.23, а Теку- щий вход Стек Уро- вень X PRED_ LEX7 EL P LPTR (P) RPTR (P) ONT 0 NULL NULL 1, А ONT1NA 1 NA NT NA NULL 2, В ONT 1NA'2NB 2 NB 1 NA NB NULL 2, С СШТ JNA 2NC 2 NO о NB NULL NC 2/D ONT1NA2ND 2 ND 2 NC NULL ND 3*Е ONT 1NA 2ND 3NE 3 NE 2 ND NE NULL 1, F 1 NF 3 NE 2 ND ONT INF 1 NA NF 2, G ONT INF 2NG 2 NG 1 NF NG NULL -3, I ONT 1NF2NG 3N1 3 NI 2 NG NI NULL 3, J ONT 1NF2NG 3NJ 3 NJ 3 NI NULL NJ 2, Н ONT 1NG2NH 2 NN 3 NJ 2 NG NG Рассмотрим теперь представление деревьев с помощью метода последовательного размещения элементов. Такое представление Удобно и эффективно в том случае, если древовидная структура в Течение времени своего существования не подвергается значи- тельным изменениям, например за счет включения вершин, уда- 379
Рис. 5-1.24- Диаграммы бинарного дерева б) ления вершин и т. д. Выбор метода последовательного представле- ния деревьев определяется также набором тех операций, которые должны быть выполнены над древовидными структурами. Пример статической древовидной структуры будет описан в гл. 6 в связи с обсуждением пирамидального метода сортировки. Простейший метод представления дерева в виде последователь- ной структуры заключается во введении вектора FATHER, за- дающего отцов для всех его вершин. Например, представление дерева, изображенного на рис. 5-1.24, а, задается так: i 123456789 10 FATHER [i ] 0111233744 где ветви дерева определяются как {(FATHER [i], i)}, i = 2, 3, 10. Числовые поля данных используются здесь для того, чтобы упростить представление дерева. Заметим, что корневая вершина не имеет отца, поэтому вместо отца для нее задается нулевое значение. Общее правило: если Т обозначает индекс корневой вер- шины дерева, то FATHER [Т ] =0. Описанный метод можно использовать также и для представле- ния леса. Очевидный недостаток этого метода состоит в том, что он никак не отображает упорядочения вершин дерева. Так, если в предыдущем примере в дереве поменять местами вершины 9 и 10, то его последовательное представление останется тем же самым. Другой распространенный метод последовательного предста- вления деревьев заключается в использовании отношения физи- ческой смежности элементов машинной памяти вместо одного нз полей LPTR или RPTR описанного выше связанного представле- ния дерева. Рассмотрим способ опускания полей LPTR из обыч- ного двухсвязного представления дерева и исследуем, каким обра- зом с его помощью можно представить дерево. Одна из возмож- ностей заключается в последовательном представлении дерева так, чтобы его вершины появлялись в нисходящем порядке. С по- мощью такого подхода дерево, изображенное на рис. 5-1.24, б, 380
RPTR DATA TAG RANGE DATA r 1 6 7 8 4 9 1 1 11 (a) 10 I r4l г-il Д| Hr 25367 84 9 10 (S) Рис. 5-1.25. Представления дерева с последовательным размещением его вершин в нис- ходящем порядке можно описать так, как показано на рис. 5-1.25, а, где RPTR, DATA и TAG представляют собой векторы. Заметим, что в этом представлении не требуется указатель LPTR, поскольку, если он не был бы пуст, то указывал бы на вершину, стоящую непосред- ственно справа от данной. Вектор TAG представляет собой бинар- ный вектор, в котором единицы отмечают концевые вершины исходного дерева. При использовании такого представления имеются значительные потери памяти, поскольку свыше половины указателей RPTR оказываются пустыми. Эти пустые места можно использовать путем установки указателя RPTR каждой данной вершины на вершину, которая следует непосредственно за под- деревом, расположенным под ней. В таком представлении поле RPTR переименовывается в RANGE, что и показано на рис. 5-1.25, б. Заметим, что в этом случае поле TAG не требуется, поскольку концевой узел определяется условием RANGE (Р) = = Р ~г 1. Третий, и последний, метод состоит в представлении дерева общего вида на основе его восходящего обхода. Такое представле- ние состоит из двух векторов: один вектор описывает все вершины дерева в восходящей последовательности, а второй — задает полустепени исхода этих вершин. Пример такого представления дерева приведен на рис. 5-1.26. Напомним, что восходящий метод представления дерева удобен для вычисления функций, заданных на определенных вершинах дерева. В п. 3-7.2 был приведен пример использования таких функций для генерации объектного кода по обратной польской записи некоторого выражения. В заключение рассмотрим два важных понятия теории бинар- ных деревьев, а именно подобие и эквивалентность бинарных деревьев. Два дерева называются подобными, если они имеют одинаковую структуру (форму). Два дерева называются эквива- лентными, если они подобны и если, кроме того, соответствующие вершины у них содержат одинаковую информацию. Пример ис- пользования понятия подобия деревьев дан в п. 5-2.1. 381
517 Рис. 5-1.26. Представление дерева с последовательным размещением его вершин в восходящем порядке Рис. 5-1-27. Г Диаграмма бинарного дерева к упражнению 4 Выше мы в основном уделяли внимание способам представления деревьев и манипулирования ими. В следующем параграфе мы обсудим некоторые приложения, в которых используются деревья. Упражнения к п. 5-1.2 1. Докажите, что бинарное дерево с п вершинами имеет ровно n + 1 пустых ветвей. 2. Проследите работу алгоритма POSTORDER, используя бинарное де- рево, изображенное на рис. 5-1.27, и постройте таблицу, аналогичную табл. 5-1.1. 3. Сформулируйте итеративный алгоритм для смешанного обхода бинарного дерева. 4. Для бинарного дерева, изображенного на рис. 5-1.27, определите поря- док, в котором будет осуществляться заход в его вершины, если дерево обхо- дится смешанным, нисходящим и восходящим методом. Повторите это упраж- нение для обратных обходов. 5. При описании прошитых для смешанного обхода бинарных дереввев были разработаны алгоритмы определения для произвольной вершины Р ее предшественника и преемника в соответствии со смешанным порядком обхода дерева. Разработайте подобные алгоритмы определения для произвольной вер- шины Р предшественника и преемника в соответствии с нисходящим и восходя- щим порядками обхода дерева со смешанной прошивкой, имеющего головную вершину HEAD. 6. Сформулируйте алгоритм RIGHT, подобный алгоритму LEFT, но вставляющий новую вершину не слева, а справа от заданной вершины Р- 7. Сформулируйте алгоритмы, подобные алгоритмам INP и INS, но пред- назначенные для нисходящей и восходящей прошивки деревьев. 8. Разработайте алгоритм, копирующий данное дерево. 9. Разработайте рекурсивный алгоритм, преобразующий лес в эквивалент- ное ему бинарное дерево. 10. Сформулируйте алгоритм, который для последовательного представ- ления дерева с использованием вектора FATHER определяет путь между вер- шинами X и Y. 11. Разработайте алгоритм для определения подобия двух деревьев А и В на основе описанных в данном пункте методов обхода. 5-2. ПРИЛОЖЕНИЯ ДЕРЕВЬЕВ В настоящем параграфе описываются четыре разных приложения деревьев. В п. 5-2.1 описаны методы работы с ариф- метическими выражениями, п. 5-2.2 посвящен очень интересному 382
приложению деревьев, связанному с созданием и ведением таблиц символов. Деревья играют также важную роль в области синтак- сического анализа, где их используют для отображения структуры предложений различных языков и при определении однозначных грамматик. Эти вопросы обсуждаются в п. 5-2.3 наряду с описа- нием нисходящего алгоритма синтаксического анализа с исполь- зованием метода рекурсивного спуска. Кроме того, как показано в п. 5-2.4, с помощью древовидных структур можно реализовать транслятор таблиц решений с исходного языка в объектный код. 5-2.1. Манипулирование арифметическими выражениями Вначале мы рассмотрим соотношение между бинарными деревьями и алгебраическими выражениями в префиксной и суф- фиксной нотации. Затем будут описаны-способы автоматического выполнения операций над выражениями, представленными в виде бинарных деревьев. Напомним, что в п. 4-3.1 уже были описаны формальные операции над полиномами. Здесь мы продолжим это описание способов Оперирования выражениями, используя, однако, их древовидные представления. В связи с вышеуказанным кон- кретные свойства таких выражений и операций над ними будут описаны в терминах соответствующих им древовидных представ- лений. По ходу изложения материала будут приведены примеры программ. В п. 3-7.2 было показано, что для компиляции удобно исполь- зовать выражения в обратной польской записи. Между бинарными деревьями и выражениями в префиксной и инфиксной записи имеется тесная связь. Запишем инфиксное выражение в виде бинарного дерева, в котором любой оператор является пометой при некоторой вершине дерева, а его левый и правый операнды выступают в качестве левого и правого поддеревьев этой вершины. Листья такого дерева представляют собой переменные и константы исходного выражения. Для обозначения унарного минуса будем использовать символ 0. В упражнениях к п. 3-7.2 приводились правила, позволяющие отличить унарный минус от бинарного минуса и отрицательного знака константы. Операнд оператора О рассматривается как правое его поддерево. Бинарное дерево на рис. 5-2.1 описывает выражение a*0b4-c/d. Если мы обойдем это дерево смешанным методом, то заход в вершины будет осу- ществлен в порядке ф- *a0b/cd, что в -точности представляет собой префиксную форму данного инфиксного выражения. Если -же мы обойдем это дерево восходящим методом, то заход в его вершины будет осуществлен в порядке ab6*cd/4-, что соответ- ствует записи выражения в суффиксной нотации. Заметим, что если приведенное выражение представить в виде дерева общего виДа, как это изображено на рис. 5-2.2, а, а затем применить к нему алгоритм естественного соответствия из п. 5-1.2 для пре- 383
образования его в эквивалентное бинарное дерево, то получится структура, изображенная на рис. 5-2.2, б. Префиксная форма данного выражения получается путем нисходящего обхода би- нарного дерева, а инфиксная — путем смешанного обхода его (но не путем восходящего обхода!). Ниже будут описаны способы автоматического выполнения операций над выражениями, представленными в виде бинарных деревьев Поскольку данный вопрос уже обсуждался в гл. 4, то должно быть ясно, что операции выполняются над самими вы- ражениями (символические операции), а не над их значениями. Такие выражения можно символически складывать, перемножать, вычитать одно из другого, дифференцировать, интегрировать, сравнивать на эквивалентность и т. д. Некоторые из этих опера- ций описаны в данном пункте, другие, например дифференцирова- ние и интегрирование, описываются в п. 5-5.3. Имеется несколько языков программирования, в которых до- пускается описание выражений. Таким языком, например, яв- ляется расширенный вариант АЛГОЛ 60, называемый FORMULA ALGOL; в нем к основному варианту АЛГОЛа добавлен новый тип данных, называемый FORM. Выражения в этом языке пред- ставляются в виде деревьев, и в нем имеется возможность выпол- нять над выражениями ряд специальных операций. СНОБОЛ 4 представляет собой другой пример языка, допускающего исполь- зование в программе «невычисляемых выражений». Такое невы- числяемое выражение может представлять некоторый образец или выражение, значение которого будет позже вычислено в про- грамме. Если невычисляемое выражение описывает некоторый образец, то оно определяется только в процессе поиска по об- разцу (см. п. 2-3.4) или путем использования функции EVAL. Если же невычисляемое выражение описывает не образец, а не- которое арифметическое или строковое выражение, то значение этого выражения определяется путем ссылки на EVAL (EXPR), Рис. 6-2.1. Представление выражения а*е b + c/d в виде бинарного дерева Рис. 6-2.2. Представление выражения а *0 b + c/d в виде дерева: а — общего вида, б — бинарного де- рева 384
Рис. 5-2.3. Представление выражения в виде бинарного дерева где EXPR задает строку с символьным описанием выра- жения. Опишем теперь представление символьных выражений с по- мощью бинарных деревьев. На рис. 5-2.3 приведено такое пред- ставление выражения a*6b+c { d, где 6 и | обозначают операции унарного минуса и возведения в степень соответственно. На этом рисунке буквой Е обозначена указательная переменная, адресующая корень дерева, каждая вершина которого состоит из левого указателя (LPTR), правого указателя (RPTR) и инфор- мационного поля (TYPE). Для неконцевой вершины поле TYPE задает арифметическую операцию, связанную с этой вершиной. Значение поля TYPE вершин, связанных с операторами 4-, —•, *, /, 6 и |, равны 1, 2, 3, 4, 5 и 6 соответственно. Для конце- вых же вершин поле TYPE имеет значение 0, что означает кон- втанту или переменную. В этом случае правый указатель вер- шины задает адрес таблицы символов, который соответствует Данной константе или переменной. Заметим, что в дереве указы- вается тип оператора, а не он сам, что позволяет упростить об- работку таких деревьев. Используемая здесь таблица символов содержит имя переменной или константы (SYMBOL) и его зна- чение (VALLE). Рассмотрим сначала процесс вычисления выражения, пред- ставленного в виде бинарного дерева, т. е. процесс определения значения этого выражения. Простейший способ решения этой задачи состоит в формулировании рекурсивной процедуры. Такая процедура на языке ПЛ/1 приведена на рис. 5-2.4, где она оформ- 13 Трамбле Ж-, Соренсон П. 385
EVAL: о PROCEDURE(E) RECURSIVE RETURNS(FIXED BINARY); DECLARE CF»E) POINTER» STAT(0:6) LABEL INITIAL(VC,PLUS,MINUS,NULT,DIV,EXP,UNARY)5 GO TO STAT(F->TYPE); VC: F = E->RPTR; RETURN(F->VALUF>; PLUS: RETURN(EVAL(E->LPTR) + EVAl(E->RPTRI); MINUS: RETURN(EVAL<E->LPTR) - EVAL(E—>RPTR))- MULT: RETURN(EVAL(E—>LPTR) * EVAL(E->RPTR)); DIV: RETURN(EVAL(E->LPTR) / EVAL(F—>RPTR)}; EXP: RETURN(EVAuIE—>LPTR) EVAL(E->RPTR)); UNARY: RETURN(-EVAHE->LPTR)); END EVAL? Рис. 5-2.4. Процедура реализации функции EVAL лена в виде функции EVAL. Используемые в ней структуры опре- деляются операторами: DECLARE 01 NODE BASED (Р), 02 LPTR POINTER, 02 TYPE FIXED DECIMAL (1), 02 RPTR POINTER; к DECLARE 01 RECORD BASED (Q), 02 SYMBOL CHARACTER (20), 02 VALUE FIXED BINARY. Предполагается, что эти структуры описаны в основной программе, поэтому для данной процедуры они рассматриваются как глобаль- ные. Процедура, представленная на рис. 5-2.4, довольно проста. Вначале оператором DECLARE создается семиэлементный мас- сив меток и его элементам задаются требуемые значения. Ссылка на этот массив осуществляется из следующего оператора, который генерирует метку, исходя из значения поля TYPE корневой вершины. В результате исполнения оператора GOTO осуществ- ляется передача управления оператору с соответствующей меткой. Если данная вершина представляет собой концевую вершину, то в качестве значения функции выдается значение переменной или константы, обозначенной этой вершиной. Эта операция выпол- няется путем использования правого указателя данной вершины для ссылки на нужную запись в таблице символов. Для некон- цевой вершины инициализируются рекурсивные вычисления ее поддеревьев, характеризующих операнды текущего оператора. Эти вычисления осуществляются с помощью обращений к функции EVAL с левым и правым указателями вершины данного опера- 386
тора в качестве аргументов в случае бинарного оператора или только с правым указателем в случае унарного оператора «минус». Этот процесс продолжается до тех пор, пока не будут достигнуты листья дерева. Для полученных листьев значения выбираются из соответствующих записей таблицы символов. Теперь, когда мы уже умеем вычислять выражение, представ- ленное бинарным деревом, рассмотрим операцию символьного сложения двух выражений. Предположим, например, что тре- буется сложить два выражения EXPR1 и EXPR2. Пусть Е1 и Е2 — указательные переменные, задающие корни бинарных деревьев для выражений EXPR1 и EXPR2 соответственно. Тре- буемое сложение легко выполнить путем создания новой корне- вой вершины для суммарного выражения и использования значе- ний Е1 и Е2 в качестве значений соответственно левого и правого указателей этой вершины. Поле TYPE новой вершины устанав- ливается равным единице. Последовательность операторов ПЛ/1 для выполнения этих действий имеет вид ALLOCATE NODE; Р — >LPTR = El; Р — >RPTR = Е2; Р- >TYPE = I; Здесь P содержит адрес корневой вершины выражения EXPR 1 + 4- EXPR2. Описанный алгоритм является тривиальным по сравнению с алгоритмом, разработанным в п. 4-3.1 практически для тех же целей. Однако на практике бывает желательно, чтобы алгоритм сло- жения выполнял ряд упрощений получаемого выражения. На- пример, если складываемые выражения представляют собой кон- станты, то необходимо создать новую вершину-константу, описы- вающую сумму этих констант. Более того, если одно из склады- ваемых выражений равно нулю, то новой корневой вершины создавать не требуется. Аналогичные правила можно разработать и для других арифметических операций; их формулировки остав- лены для упражнений. В качестве последнего примера рассмотрим проблему опреде- ления подобия двух выражений, представленных в виде бинарных деревьев. Два бинарных дерева, соответствующих арифметическим выражениям, называются подобными, если они совпадают для вершин всех типов, кроме тех вершин, которые описывают комму- тативные операторы 4- и *. В этом последнем случае необходимо допускать вариант, когда левое поддерево первого дерева совпа- дает с правым поддеревом второго дерева и правое поддерево пер- вого дерева совпадает с левым поддеревом второго дерева. На рис. 5-2.5 приведена процедура на ПЛ/1 для проверки по- добия двух выражений, представленных в виде бинарных де- ревьев, структура вершин которых приведена на рис. 5-2.3. Заметим, что операторы в этой процедуре поделены иа три класса, 13* 387
SIMILAR: PROCEDURE! Ao , RECURSIVE RETURNS(BIT!1)I» DECLARE (A, B) POINTER, STAT!O:6) LABEL INITI AL (L EAFoCOMMUTo IDENToCOMMUT, IDENT > IDENT > UNARY); /» CHECK THE ROOT NODES */ IF A->TYPE -1= B->TYPE THEN RETURN!’018) ; GO TO STAT(A->1YPE); /« COMPARE LEAF NODES LEAF: IF A->RPTR -,= B->RPTR THEN RETURN!’O’B)? ELSE RETURN!’1’B); COMMUT: /* CHECK FCR COMMUTATIVITY OF + AND • </ RETURN!SIM1LAR!A->LPTR,B->RPTR) £ SIMILAR(A->RPTR,B->LPTR) I SIMILAR I A->LPTR,B->LPTR) € SINILAR(A->RPTR,B->RPTR»»J TDENTt /♦ CHECK FOR IDENTICAL BINARY SUBTREES */ RETURN!SIMILAR(A->LPTR,B->LPTR) Е SIMILAR!A->RPTR,B->RPTRJJ? UNARY: /* CHECK FOR IDENTICAL UNARY SUBTREE */ return(similar(a->lptr,b->lPTR)i; END SIMILAR; Рис, 5-2.5. Процедура проверки подобия а именно: бинарные коммутативные операторы, бинарные неком- мутативные операторы и унарный оператор. Упражнения к п. 5-2.1 1. Сформулируйте правила упрощения, аналогичные тем, которые даются в тексте для операции сложения, для всех остальных арифметических операций. 2. Разработайте алгоритм, осуществляющий символическое дифференциро- вание данного выражения на основе представления этого выражения в виде бинарного дерева. Предполагается, что выражение содержит переменные, кон- станты и обычные арифметические операторы. 3. Разработайте алгоритм, осуществляющий символическое интегрирова- ине данного выражения на основе его представления в виде бинарного дерева. Предполагается, что выражение содержит переменные, константы н обычные арифметические операторы. 5-2.2. Формирование таблиц символов В качестве примера приложения бинарных деревьев сформулируем алгоритм ведения древовидно-структурированной таблицы символов. Один из критериев, которому должна удовле- творять программа ведения таблицы символов, состоит в макси- мальной эффективности поиска в этой таблице. Это требование 388
возникает на этапе компиляции, когда осуществляется много ссылок на записи таблицы символов Необходимо, чтобы над таблицей символов можно было проводить две операции — вклю- чение записей в таблицу и поиск их в ней, причем каждая из этих операций содержит операцию просмотра таблицы. Древовидное представление таблицы символов выбирают по двум причинам. Первая из них состоит в том, что если записи символов по мере их возникновения равномерно распределяются в соответствии с лексикографическим порядком, то при хранении записей в дереве в таком же порядке табличный просмотр становится почти эквивалентным двоичному просмотру. Вторая причина заклю- чается в том, что в древовидной структуре легко поддерживать лексикографический порядок, поскольку при включении в нее новой записи необходимо изменить лишь несколько указателей. Для простоты предположим, что при ведении таблицы симво- лов используется достаточно развитая система записей, допуска- ющая символьные строки переменной длины. Программу управ- ления такими строками составить довольно легко. Кроме того, предположим, что подпрограмма ведения таблицы символов используется при создании дерева для переменных локальных относительно данного блока программы. Это предположение ведет к тому, что попытка повторного включения записи вызывает ошибку. В глобальном контексте повторные записи допустимы, если только они соответствуют разным уровням блочной струк- туры программы. В некотором смысле таблица символов пред- ставляет собой множество деревьев — по одному для каждого уровня блочной структуры программы. Вершины бинарного дерева таблицы символов имеют формат: LPTR SYMBOLS INFO RPTR где LPTR и RPTR—поля указателей, SYMBOLS — поле сим- вольной строки, задающей индентификатор или имя переменной (отметим, что для обеспечения фиксированной длины описания вершин здесь можно хранить не саму строку, а лишь ее описа- тель, но мы предполагаем, что такая возможность достаточно очевидна пользователю), a INFO — некоторое множество полей, Содержащих дополнительно информацию об этом идентификаторе, например его тип данных. Новая вершина создается путем ис- полнения оператора Р <= NODE, при этом ее адрес запоминается в переменной Р. Предполагается, наконец, что перед любым исполнением про- граммы ведения таблицы символов на некотором частном уровне блочной структуры уже имеется соответствующая головная вер- шина дерева, в поле SYMBOLS которой занесено значение, лекси- кографически большее, нежели любой допустимый идентификатор. Эта вершина адресуется указателем HEAD [nJ, где п означает 389
номер уровня блочной структуры. Иначе говоря, предполагается, что при входе в блок осуществляется обращение к основной про- грамме, управляющей созданием головных вершин деревьев. Поскольку как операция включения записи в таблицу, так и операция поиска в таблице содержат значительное количество одинаковых действий (например, просмотр), мы создадим только одну программу TABLE, а различать включение и поиск будем по значению глобальной логической переменной FLAG. Если при вызове алгоритма TABLE значение FLAG есть истина, то требуемая операция является включением; при этом NAME и DATA содержат соответственно имя идентификатора и дополни- тельную информацию для него. Если включение новой записи было выполнено успешно, то FLAG сохраняет свое первоначальное значение; в противном случае FLAG принимает значение, проти- воположное первоначальному, что указывает на ошибку, возни- кающую из-за того, что искомый идентификатор уже присутст- вует в таблице данного уровня, и выполнение алгоритма завер- шается. Если при вызове алгоритма переменная FLAG имеет значение ложь, то требуется выполнить операцию поиска записи. В этом случае переменная NAME содержит имя идентификатора, который необходимо найти, а значение переменной DATA нере- левантно. При успешном поиске переменная DATA устанавли- вается на поле INFO соответствующей записи таблицы символов, FLAG сохраняет свое значение, н осуществляется возврат к вы- звавшей программе. При неудаче операции поиска значение пе- ременной становится противоположным первоначальному, и осу- ществляется выход из алгоритма. В этом случае основная про- грамма должна осуществить поиск записи в таблицах более низ- ких уровней (деревья с головными вершинами HEAD [ri — 1 ], HEAD [п -2] и т. д.). Алгоритм TABLE. На вход подается глобальная перемен- ная л, идентифицирующая номер уровня текущего блока, и гло- бальная переменная FLAG, задающая требуемую операцию. Описываемый алгоритм выполняет эту операцию над древовидно- структурированной таблицей символов, локальной относительно блока уровня п. Параметры DATA и NAME используются для передачи данных между алгоритмом и вызывающей его програм- мой. Переменная FLAG дополнительно используется алгоритмом как индикатор успешного или неудачного проведения операции. I. [Инициализация.] Установить Т HEAD [п]. 2. [Сравнение. ] Если NAME < SYMBOLS (Т), то, если LPTR (Т) -ф NULL, установить T-e-LPTR(T) и перейти к шагу 2; в противном случае, если И FLAG, установить FLAG ч— FLAG и завершить выполнение алгоритма; в противном случае установить Р<= NODE, SYMBOLS (Р) ч-NAME, INFO (Р) DATA, LPTR (Р) ч-RPTR (Р) ч-NULL. LPTR (Т) ч-Р и завер- шить выполнение алгоритма; 390
Если NAME > SYMBOLS (T), то, если RPTR (Т) == NULL, установить Т RPTR (Т) и перейти к шагу 2; в противном слу- чае, если ~|FLAG, установить FLAG ч--]1 FLAG и завершить выполнение алгоритма; в противном случае установить Р^= NODE, SYMBOLS (Р) ч-NAME, INFO (Р) ч-DATA, RPTR (Р) ч-LPTR (Р) ч-NULL, RPTR (Т) «-Р и завер- шить выполнение алгоритма. 3. [Установка. ] Если FLAG, то установить FLAG ч--|FLAG; в противном случае установить DATA ч-INFO (Т). 4. [Выход. ] Завершить выполнение алгоритма. Понять описанный алгоритм довольно легко. В шаге 2 про- исходит сравнение содержимого переменной NAME с одной из записей таблицы символов. Если они совпадают, то при поиске это означает, что мы нашли требуемую запись, а при включе- нии — что мы пытаемся создать запись с уже имеющимся именем, ь обоих случаях выход из алгоритма осуществляется после шага 3. Если совпадения не обнаружено, то в зависимости от того, больше или меньше значение NAME кода исследуемой записи таблицы, осуществляется установка указателя на левый или на пра- вый потомок данной вершины и возврат к шагу 2 для дальнейших сравнений. Поскольку дерево упорядочено таким образом, что код каждой вершины левого (правого) поддерева лексикографи- чески меньше (больше), чем код корневой вершины, то попытка спуска по пустому дереву означает, что требуемая запись в таб- лице отсутствует; при этом определяется место, где данная запись должна быть расположена. В этом случае, если требовалось найти запись, то выдается сообщение об ошибке, в противном случае создается новая вершина, в нее заносится нужная информация и она включается в уже существующую древовидную структуру слева или справа от исследуемой вершины. Упражнения к п. 5-2.2 1. Проследите работу алгоритма TABLE, используя в качестве данных следующую последовательность имен: do, else, ffet, put, then, declare, fixed, float, binary, character, based, pointer. 2. Напишите па ПЛ/1 процедуру для алгоритма TABLE и используйте для ее проверки данные из предыдущего упражнения. 5-2.3. Синтаксический анализ В п. 2-2.2 было введено понятие грамматики как мате- матической системы, служащей для определения языков и для придания некоторой полезной структуры предложениям этих языков. Там же была поставлена задача синтаксического анализа предложения данного языка. Напомним, что синтаксический ана- лиз состоит в нахождении последовательности продукций, которая будет порождать заданное предложение из начального символа грамматики. В том же пункте коротко упоминалось о понятии 391
синтаксического дерева и о его соотношений с сййТаксИчёсййм анализом предложения. Синтаксическое дерево данного предложения можно получить нисходящим или восходящим способом. Настоящий пункт посвящен проблеме использования грамма- тик для,синтаксического анализа. Таким образом, здесь мы будем рассматривать этап синтаксического распознавания в компиля- ции. Основной дискретной структорой в синтаксическом анализе является синтаксическое дерево. По такому дереву с привлечением семантической информации можно получить значение соответ- ствующего предложения. Кроме того, синтаксическое дерево является удобным средством для определения многих важных свойств некоторых классов грамматик. Сначала вводится важное понятие синтаксической неодно- значности (омонимии) и описывается его связь с проблемами синтаксического анализа. Затем более подробно рассматривается введенный ранее нисходящий метод синтаксического анализа. При этом используется неформальный и очень общий подход с низким быстродействием из-за необходимости постоянных воз- вратов. Возможны некоторые модификации этого метода, позво- ляющие уменьшить число возвратов. В конце пункта приводится пример реализации нисходящего метода синтаксического анализа с помощью рекурсивных процедур. Синтаксическое дерево, определенное ранее в п. 2-2.2, является удобным средством для выявления синтаксиса предложения. Синтаксическое дерево любого предложения некоторого языка имеет выделенную вершину, называемую корнем и помеченную начальным символом грамматики этого языка. Концевые вер- шины синтаксического дерева соответствуют терминальным сим- волам разбираемого предложения. Все неконцевые вершины соответствуют нетерминальным символам. Каждая нетерми- нальная вершина имеет набор идущих вниз ветвей, каждая из которых соответствует некоторому символу правой части продукции, используемой в данной точке синтаксического дерева. В общем случае любой сентенциальной форме можно сопоста- вить синтаксическое дерево. Концевые вершины в таком дереве могут задавать как терминальные, так и нетерминальные символы. Пусть А — корень поддерева для сентенциальной формы о = = ФгрФ2, где р образует строку концевых вершин данного под- дерева. При этом р называется фразой для корня А в сентенциаль- ной форме о. Строка р называется простой фразой, если подде- рево, соответствующее корню А, образуется применением лишь одной продукции А —* р. Рассмотрим пример грамматики: G( == {(врж) , (множитель), (терм)}, {i, -}-, (, )}, (врж) , Ф}, 392
Рис. 5-2.6. Синтаксическое дерево предложения <врж> -р <терм> * » <множитель) в грамматике С, Рис. 5-2.7. Два разных синтаксических дерева для предложения а $ а # а в грамматике Cs где Ф состоит из продукций (множитель)":): = i | ((врж‘) (множитель) : :'=’?(множитёль) | (терм)» (множитель) (врж) :\=дтерм) ] (врж) Щ ;терм) a i используется для обозначения идентификатора или имени пере- менной. На рис. 5-2.6 приведено синтаксическое дерево для сен- тенциальной формы (врж) (терм) * (множитель); здесь строки (врж) + • терм) * (множитель) и (терм) * (множи- тель) представляют собой фразы этого дерева, а фраза (терм) * * (множитель4 является простой. В теории формальных языков большое зачение имеет ответ на вопрос — единственное ли синтаксическое дерево имеет дан- ная сентенциальная форма. Рассмотрим простейшую грамма- тику G2 с продукциями: S : : = S*S S : : = а где а — терминальный символ. Найдем в ией вывод для предло- жения а *а*а. Один вариант вывода имеет вид S =>[S *S=^S*S*S=>a*S*S=>a*a*S=>a*a*a, в котором на втором шаге левое вхождение S преобразуется к виду S*S. Другой вариант вывода получится, если на том же шаге преобразовать в S*$ правое вхождение S. Оба варианта изображены на рис. 5-2.7. Легко видеть, что изображенные на рис. 5-2.7 деревья различны. Это означает, что имеется два раз- личных варианта синтаксического анализа одного и того же предложения. Наличие нескольких вариантов синтаксического анализа для некоторых предложений языка может привести к тому, что компилятор будет вырабатывать различные множе- ства машинных команд (объектный код) для разных вариантов синтаксического анализа. Обычно такая ситуация недопустима. Если от компилятора требуется правильная трансляция предло- жений языка, то этот язык должен быть определен однозначно. Описанная ситуация приводит к следующему формальному опре- делению. Если для предложения, порожденного некоторой граммати- кой, имеется несколько синтаксических деревьев, то такое пред- 893
ложение называется неоднозначным. Если грамматика порождает хотя бы одно неоднозначное предложение, то она называется неоднозначной; в противном случае она называется однозначной. Заметим, что мы называем неоднозначной грамматику, а не язык, который она порождает. Имеется множество грамматик, порождающих один и тот же язык; часть из них неоднозначна, а часть —однозначна. Одна ко имеется ряд языков, для которых однозначную грамматику найти нельзя. Такие языки называются существенно неоднозначными. Например, язык {x*yJzk | i = j или j является существенно неоднозначным контекстно-свобод- ным языком. В этой связи возникает вопрос: существует ли алгоритм, ко- торый, получая на вход произвольную контекстно-свободную грамматику, за конечное время определяет, однозначна она или нет. Ответ на этот вопрос отрицательный Однако можно разра- ботать некоторое множество достаточных условий, таких что грамматика будет считаться однозначной, если она удовлетворяет этим условиям. Подчеркнем, что Эти условия являются достаточ- ными, но не необходимыми. Иными словами, если грамматика и не удовлетворяет этим условиям, то она все равно может быть однозначной. Приведем другой пример неоднозначной грамматики. Рас- смотрим грамматику G3 для описания арифметических выражений, состоящих из операторов 4- и * и однобуквенных переменных: (врж) : : = i | (врж) 4 (врж) ] '.врж' * (врж) | ((врж)). Предположим, что необходимо разобрать предложение i 4 i*i. Для него имеются следующие два вывода: (врж) => (врж) -Р (врж) => (врж) -Р (врж) * (врж) => i -р (врж) * (врж) => i ~р i * (врж) \ => i 4 i * i (врж) => (врж\ « (врж, => (врж) 4 (врж) * (врж) => i 4 '(врж) * (врж) => i 4 i « (врж) => i 4 i * i. Соответствующие этим выводам синтаксические деревья изобра- жены на рис. 5-2.8, Поскольку для выражения i -J- i*i суще- ствует два различных синтаксических дерева, то приведенная грамматика является неоднозначной. Что это грамматика неодно- значна, ясно интуитивно, так как неизвестно, когда требуется выполнять операцию * —до операции 4 или после. Эту грам- матику можно переписать в таком виде, в котором умножение бу- дет предшествовать сложению. Такое изменение внесено в ниже- следующую. грамматику G4: (врж) : : = (терм) [ (врж) -р (терм) (терм) : : — (множитель) | (терм) * (множитель) (множитель) : : = i | (врж) 394
Напомним, что в п. 2-2.2 былй описаны два метода построения дерева синтаксического анализа предложения, порожденного дан- ной грамматикой, а именно: вос- ходящий анализ и нисходящий анализ. Ниже мы более подробно рассмотрим нисходящий метод синтаксического анализа. Термин «нисходящий» мотивируется тем, что попытка построить дерево для заданной входной строки при указанном методе осуществляется путем спуска от корня вниз к символам этой строки. Эта за- дача осуществляется посредством предварительной нумерации про- извольным способом различных правых частей продукций (аль- тернатив), соответствующих каж- дой метапеременной грамматики. Так, если S : : = ctj|[ ... |ап — все продукции грамматики с об- щей левой частью S, то на всех задается некоторый порядок. В качестве примера рассмот} Рис. 5-2.8. Два синтаксических дерева для предложения i -}- j % i в грамма- тике G8 альтернативах a, (i — 1, ...» п) грамматику = <b + <е> I (t> = .В * <t> I <0 <е> <*; . <f> : = ((e)) I i и упорядочим ее альтернативы в соответствии с линейным распо- ложением их в записи. Так, (t> + (е) является первой альтер- нативой для (е>, a <t> —второй, <Г * <t> —первой альтерна- тивой для (t>, a (t> —второй и т. д. В нижеследующем неформальном описании нисходящего метода синтаксиче- ского анализа используется также указатель входной строки, который первоначально установлен на самый левый ее СИМВОЛ. Напомним, что нисходящий анализатор осуществляет попытку построить для входной строки синтаксическое дерево следующим образом. Исходное дерево состоит из вершины, помеченной симво- лом S и называемой начальной активной вершиной дерева. Затем рекурсивно выполняются следующие шаги. 1. Если активная вершина обозначает переменную А, то выби- рается ее первая альтернатива XiXs. ... Хг, и дерево расширяется путем создания для А ее прямых потомков, помеченных симво- лами Xi, Х2, .... Хг. Затем в качестве новой активной вершины рассматривается символ Xi- 395
2. Если активная вершина обозначает терминальный символ, например а, то осуществляется сравнение его с текущим входным символом. Если они совпадают, то активной становится вершина, стоящая непосредственно справа от данной, и входной указатель передвигается на один символ вправо. В противном случае проис- ходит возврат к вершине, к которой применялось предыдущее правило, изменяется значение указателя входной строки (если это необходимо) и делается попытка использовать для этой вершины следующую альтернативу. Если такой альтернативы нет, то про- исходит возврат назад еще на один шаг и т. д. В процессе синтаксического анализа концевые вершины по- рожденной части синтаксического дерева всегда соответствуют обработанной части входной строки. Проследим действие описываемого алгоритма, используя при- веденную выше грамматику и приняв в качестве входной строки i 4- i- Синтаксический анализ начинается с дерева, состоящего из одной вершины, помеченной символом <е). Затем применяется первая альтернатива <е), расширяющая дерево так, как показано на рис. 5-2.9, а. Поскольку активной вершиной теперь стал сим- вол <t>, то далее применяется первая альтернатива этого символа, приводящая дерево к виду, показанному на рис. 5-2.9, б. Актив- ной вершиной в данном случае является символ (f ?, но его первая альтернатива отбрасывается, поскольку символ *(’ не совпадает с символом i входной отроки. Далее испытывается вторая альтер- натива -,f} (т. е. i) и обнаруживается ее совпадение с символом i 396
входной строки. Входной курсор передвигатся на второй символ входной строки, а новой активной вершиной становится символ * , сравнение которого со вторым входным символом приводит к не- удаче, что иллюстрирует рис. 5-2- 9, в. Курсор поэтому вновь полу- чает значение 1, вершина (t) вновь становится активной и исполь- зуется ее вторая альтернатива (т. е. Д}), что приводит к дереву, изображенному на рис. 5-2.9, г. Активной вершиной теперь ста- новится символ \f>, при этом использование его первой альтер- нативы приводит к неудаче. Поэтому выбирается вторая альтер- натива этой вершины (т. е. i), которая совпадает с первым симво- лом входной строки. Курсор опять передвигается на следующую позицию, и новой активной вершиной становится символ 4- (ко- торый совпадает с текущим символом входной строки), что изоб- ражено на рис. 5-2.9, д. После этого активной вершиной становится символ (е), и выбирается его первая альтернатива (рис. 5-2- 9.е). Этот процесс продолжается до тех пор, пока не будет построено окончательное дерево синтаксического анализа, изображенное на рис. 5-2.9, ж. Ниже рассмотрена программная реализация описанного выше нисходящего метода синтаксического анализа. Приводимый ана- лизатор для каждой метапеременной А имеет соответствующую рекурсивную процедуру, анализирующую фразы этой метапере- менной. Такой процедуре сообщается, с какого места входной цепочки следует начать поиск фразы для соответствующей пере- менной. Этот метод ориентирован на цель. Процедура осуществ- ляет поиск фразы путем сравнения входной цепочки, начиная с указанного места, с альтернативами символа А, вызывая, если нужно, другие процедуры для опознавания подцелей. Дерево синтаксического анализа строится описанным выше способом. В качестве примера на рис. 5-2.10 приведены про- цедуры разбора выражений с помощью рассмотренной ранее про- стейшей грамматики. При этом предполагается, что синтаксиче- ский анализ производится без возвратов. Это легко выполнить путем использования односимвольного контекста, следующего непосредственно за обработанной частью фразы. Исследуемый контекстный символ может быть либо ’-У’, либо Отметим, что в программе имеется четыре процедуры: SCAN, EXPR, FACTOR и TERM. Все они, кроме процедуры SCAN, рекурсивны. По поводу этой программы заметим следующее. 1. Переменная NEXT является глобальной и содержит символ одной строки, следующий непосредственно за обрабатываемым символом. При вызове процедуры для поиска новой цели первый символ, подлежащий рассмотрению, уже находится в переменной NEXT. Перед возвратом из процедуры после успешного опознава- ния цели в переменную NEXT заносится символ, следующий не- посредственно за подстрокой, выделенной этой процедурой. 2 Процедура SCAN выделяет следующий входной символ и помещает его в переменную NEXT. 397
3. Для запуска синтаксического анализа основная программа вызывает процедуру SCAN, которая присваивает первый символ входной строки переменной NEXT. 4. Переменная CURSOR, так же как и NEXT, является гло- бальной для всех процедур и обозначает текущую позицию сим- вола во входной строке. Заметим, что грамматика, приводимая здесь в качестве при- мера, содержит правила с правой рекурсией вместо более попу- лярных и естественных правил с левой рекурсией, использован- ных нами ранее в грамматиках для того же самого языка. Исполь- зование грамматики, содержащей правила с левой рекурсией, в алгоритме нисходящего синтаксического анализа, подобном опи- санному в настоящем пункте, приводит к зацикливанию. Напри- мер, леворекурсивная продукция вида (е) : : = (е) (t> при- RECDSNT: PROCEDURE OPTIONS (HAtNH DECLARE (J, CURSOR) BINARY FIXED, INPUT CHARACTER(BO) VARYING, (NEXT, STRING(BO)) CHARACTER! I) , FACTOR RETURNS ( BIT (11 Г TERM RETURNS(BIT(1)), EXPR RETURNS < SIT (.1))5 ON ENDFILE!SYSIN) GO TO FIN" READ: GET LIST!INPUT); DO I = 1 TO LENGTH!INPUT); STRING(I) = SUBSTR(INPUT, I, 1)? ENO; CURSOR = 1; CALL SCAN; IF EXPR £ NEXT = THEN PUT SKIP EOITUNPUT, ’VALID’) (A, XtlO), A); ELSE PUT SKIP EDITI1NPUT, ’INVALID’} (A, X(1O), A), GO TO READ; EXPR: PROCEDURE RECURSIVE RETURNS I BIT{1)); IF -.TERM THEN RETURN!’O’B); IF NEXT = ’+• THEN DO; CALI SCAN; IF NEXT = THEN RETURN (’O’ B); IF -«EXPR THEN RETURN(’0’B); ELSE RETURN!11’B); end; ELSE RETURN!•1’B); END EXPR", SCAN: ' PROCEDURE; NEXT = STR ING(CURSOR»; CURSOR - CURSOR ♦ 1; END SCAN; Рис. 5-2.10. Процедура синтаксического анализа арифметических выражений 398
ведет к тому, что анализатор осуществит попытку опознать (е), которая, в свою очередь, вызовет попытку опять опознать <е> и т, д., в результате чего образуется бесконечный цикл. Упражнения к п. 5-2.3 1. Покажите путем построения всех возможных деревьев для пред- ложений i р i I и i 4- i * i, что грамматика с правилами ‘<е>::== (<е>)|<е> + <е>[<е> — — <е>|<е>*<е>|<е>/<е> |i является неоднозначной. TERM: PROCEDURE RECURSIVE RETURNS!BIT!IM; IF -FACTOR THEN RETURN!'0'Bl 3 IF NEXT = ’«» THEN DO; CALL SCAN; IF NEXT = THEN RETURN!'O’BI) IF -TERM then return;’o’Bi; ELSE RETURN!'l'B>3 eno; ELSE REWRNVl'B) ? END TERM; FACTOR: PROCEDURE RECURSIVE RETURNS!BIT<1)); IF NEXT = ’«• THEN RETURN!’O’B)5 IF NEXT = •(' THEN DO; CALL SCAN; IF NEXT « THEN RETURN!’O'В»5 IF -EXPR THEN RETURN!'O'Bl 3 IF NEXT THEN RETURN<’O'BJ; ELSE do; CALL SCAN; RETURN!’1'Bl; FND; ENO; IF NEXT -= ’I' THEN RETURN!’О«В»; ELSE do: CALL SCAN; RETURN!'I'B)5 END; END FACTOR; FIN: END RECDSNT; I«I+Ifi VALID <I*I)«!I»I)0 VALID Ml VALID !(I*I*=I« INVALID <l*I*I«!!ltlJ«-in>0 ~ INVALID INVALID Рис. Б-2.1(). Продолжение 399
2. Выполните вручную программу, изображенную на рис. 5-1.10, для про- ведения синтаксического анализа строк: J L+ 1 * i; 9 1 + (i + 0: в) i » (i + О- 3. Сформулируйте грамматику, порождающую множество арифметических выражений, включающих скобки, бинарные операторы , *, / н f (воз- ведение в степень), унарный оператор минуса 6 и односимвольную переменную 1. Напишите нисходящий алгоритм синтаксического анализа, работающий по методу рекурсивного спуска. 5-2.4. Трансляция таблиц решений Общепризнано, что анализ задачи является наиболее важным и наиболее трудным этапом процесса разработки хорошей машинной системы. Изучающим вычислительную науку в каче- стве основного метода разработки программ обычно предлагается метод составления блок-схем. Однако такой метод оказывается удобным только для простых задач, блок-схемы которых отно- сительно не запутаны. Поскольку большинство реальных машин- ных приложений не являются простыми, блок-схемы исполь- зуются довольно редко, так как сложность графического изобра- жения решения реальной задачи сводит на нет наглядность блок- схемы, характерную для простых задач. Альтернативным методом анализа является метод таблиц решений, формат которых хорошо приспособлен для описания сложных отношений и выводов. Таблица решений в явном виде и в компактной форме показывает, как условия связаны с дей- ствиями. Пример таблицы решений дан на рис. 5-2.11. Это прямо- угольная таблица, разделенная двойной вертикальной и двойной горизонтальной линиями на четыре сектора. Одинарными гори- зонтальными линиями в ней выделен ряд строк; строки над двой- ной линией называются строками условий, а строки под двойной линией — строками действий. В двух правых секторах рассма- триваемой таблицы вертикальными линиями выделен ряд столб- цов. Каждый столбец представляет собой правило. Верхний левый сектор таблицы называется перечнем условий; записи в перечне условий представляют собой вопросы, на кото- рые Необходимо ответить, чтобы получить решение задачи (на- ВЫБОР ВЕРХНЕЙ ОДЕЖДЫ На'улице дождь? У Y у У N N N 'н В прогнозе дождь? у У N N у у N N На улице тепло? У N У N У N У N Наденьте плащ X X X X Возьмите зонт X X X X X X Наденьте легкое пальто X X Наденьте теплое па пыл о X X X X Рис. 5-2.11. Таблица решений ВЫБОР ВЕРХНЕЙ ОДЕЖДЫ 400
пример, «На улице дождь»). Нижний левый сектор таблицы представляет собой перечень действий; он содержит описания действий или событий, релевантных для данной задачи (например, «Наденьте плащ»). Верхний правый сектор таблицы решений содержит записи значений условий, образующие комбинации ответов на вопросы из перечня условий. Каждая такая запись состоит из символа «У» (yes — да), «N» (not — нет) или «—» (безразлично) и озна- чает, что данное условие выполнено (true), не выполнено (false) или безразлично соответственно. Нижний правый сектор таблицы решений содержит индикаторы действий, которые необходимо выполнить. Здесь символ «X» означает, что соответствующее действие нужно выполнить; пробел — что данное действие вы- полнять не нужно. Совокупность действий, описанная некоторым правилом таблицы решений, выполняется тогда и только тогда, когда удовлетворена соответствующая комбинация условий. Каждое правило таблицы решений можно понимать как вы- ражение «if» («если») естественного языка (или языка программи- рования, допускающего такие выражения). Например, первое правило на рис. 5-2.11 описывает следующую инструкцию: «Если на улице дождь, в прогнозе дождь и на улице тепло, то наденьте плащ и возьмите зонтик». Заметим, что таблица на рис. 5-2.11 имеет имя—заголовок (ВЫБОР ВЕРХНЕЙ ОДЕЖДЫ), Это имя позволяет отличать данную таблицу решений от других таблиц в случае, если решение задачи описывается с помощью нескольких таблиц решений. Например, действия в другой таблице решений могут быть обо- значены как «ПЕРЕЙТИ К ВЫБОРУ ВЕРХНЕЙ ОДЕЖДЫ». Таблица решений должна задавать действия, соответствующие каждой из допустимых комбинаций значений условий. Так, для полного задания таблицы, допускающей для условий только записи «X» и «У» и имеющей п условий, требуется 2П правил. Легко видеть, что таблицы решений такого типа даже для про- стых задач могут оказаться слишком большими. (Заметим, что :На рис. 5-2.11 имеется 23 = 8 правил для трех условий.) Вновь обращаясь к рис. 5-2.11, отметим, что первое и’третье правила соответствуют одной и той же комбинации действий, т. е. одни и те же действия в указанных правилах обозначены символом «X». Заметим также, что значения всех условий, кроме второго, в этих правилах совпадают. Это означает, что если на вопросы первого и третьего условия получены ответы «У», то ответ На вопрос второго условия значения не имеет. Данный факт можно отобразить путем объединения этих правил'4 с использованием символа «—» (прочерк) в качестве значения второго условия. Символ"'я«—» “означает ответ «безразлично», т. е. нерелевантный ответ. Иными словами, если имеются два правила, которые опреде- ляют одну н ту же комбинацию действий н значения условий 401
ВЫБОР ВЕРХНЕЙ ОДЕЖДЫ На улице дождь 7 || У Y н N N N В прогнозе дождь 7 | Y Y N N На улице тепло7 К Наденьте плащ 1 X N Y N Y N Возьмите зонт || X X Л X Наденьте легкое пальто X X Наденьте теплое пальто || X X X Рис. 5-2,12. Редуцированный вариант таблицы ВЫБОР ВЕРХНЕЙ ОДЕЖДЫ которых различаются лишь в одной строке, причем в этой строке ни в одном из правил не стоит символ «—», то эти два правила можно объединить в одно путем простановки в различающей их строке символа «—». Правила, не содержащие знака «—-», назы- ваются простыми правилами, а правила, включающие хотя бы один такой знак, называются составными правилами. Таблица решений, полученная из таблицы, изображенной на рис. 5-2-11, путем использования составных правил, представлена на рис. 5-2.12. Размер таблиц решений можно уменьшить также путем ис- пользования правила ELSE (иначе). При этом предполагается, что действия для всех правил, комбинации условий которых явным образом не заданы в таблице, задаются правилом ELSE. Этот метод особенно удобен в случае совпадения комбинаций дей- ствий у довольно большого числа правил. Введение значения «безразлично» и правила ELSE создает новые проблемы при работе с таблицами решений. Многие разра- ботчики таблиц решений вместо того, чтобы сначала описать все возможные комбинации условий, а затем редуцировать таблицу, сразу создают редуцированный вариант таблицы, действуя сооб- разно ситуации. Такой подход часто приводит к неоднозначности таблицы решений, которая содержит либо избыточные или пере- крывающиеся правила, либо внутренне противоречивые правила. Избыточные и перекрывающие правила возникают тогда, когда некоторое составное правило путем конкретизации 1 может быть приведено к правилу, уже имеющемуся в таблице; последнее мо- жет быть либо простым правилом, либо конкретизированным ва- риантом другого составного правила. Если вариант составного правила совпадает с правилом, уже имеющимся в таблице, До 1 Конкретизацией некоторого составного правила называется правило, получаемое из этого составного правила путем замены ряда прочерков на «Y» или «N». — Прим. пер. 402
последнее является избыточным и может быть элиминировано. Если же он совпадает с конкретизированным вариантом другого составного правила, то одно из этих составных правил должно быть изменено так, чтобы избежать перекрывания. Если два пере- крывающихся правила принадлежат разным комбинациям дей- ствий, то в таблице имеется противоречие, которое необходимо устранить. Пример таблицы с перекрывающимися правилами дан на рис. 5-2.13. Здесь конкретизация первого и второго правил приводит соответственно к правилам Y Y Y Y Y N и Y Y N N N Y Заметим, что правило Y Y N получается в обоих случаях и создает, таким образом, перекры- вание указанных составных правил. Внутренне противоречивое правило возникает в том случае, если оно содержит логически недопустимую комбинацию условий. Например, в первом правиле на рис. 5-2.14 недопустимо, чтобы условие «Возраст ^>65» и условие «Возраст 40» были истинны одновременно. Проблема внутренней непротиворечивости лучше всего ре- шается путем тщательной разработки таблицы. Если в таблице желательно иметь описания недопустимых комбинаций условий, то в ней все такие комбинации должны быть явно описаны и при их удовлетворении должно вызываться действие, идентифици- рующее ошибку. Таблицы решений, изображенные на рис. 5-2.11 и 5-2.12, характеризуются тем, что в них значения условий ограничены символами «Y», «N» и «•—». Такие таблицы называются таблицами решений с ограниченным входом. На практике используются также таблицы с расширенным входом и таблицы со смешанным входом. ТАБЛИЦА Условие 1 Y Y N N N Условие 2 — у N Y - Условие 3 Действие 1 N Л X Y X Y N X Действие? X X X СТРАХОВАНИЕ Возраст Y N N N N Пол мужской - у у N N Возраст > 65 - Y N у N Страхование жизни X X X Страхование машины X X X Рис. 5-2.13, Пример таблицы с перекры- Рис. 5-2.14. Таблица СТРАХОВАНИЕ веющимися правилами 403
Выбор верхней, одежды Погода на улице Дождь Дождь Ясно Ясно Ясно Ясно Прогноз погоды - - Дождь Дождь Ясно Ясно Температура Тепло Прохладно Тепло Прохладно Тепло Прохладно Наденьте > сверху Пла<ц Плаш. Возьмите Зонт Зонт Зонт Зонт Наденьте пальто Легкое Легкое Теплое Легкое Теплое Рис. 5-2.15. Таблица ВЫБОР ВЕРХНЕЙ ОДЕЖДЫ в формате с расширенными вхо- дами В таблице с раширенным входом допускаются значения^условий и индикаторы действий произвольного вида. Таблицы со смешан- ным входом объединяют в себе свойства таблиц двух предыдущих типов. На рис. 5-2.15 изображена таблица с расширенным входом, представляющая собой видоизмененный вариант таблицы,^при- веденной на рис. 5-2.12 На--практике предпочитают таблицы с расширенным или сме- шанным входом, поскольку обычно любая проблема описывается с помощью таких таблиц гораздо короче (т. е. меньшим числом условий, действий и правил), чем с помощью таблицы с ограни- ченным входом. Одиако любую таблицу со смешанным^или рас- ширенным входом можно преобразовать в эквивалентную таблицу с ограниченным входом. Поэтому в большей части имеющейся литературы основное внимание уделяется таблицам с ограничен- ным входом. Ниже будут рассмотрены только таблицы с ограни- ченным входом, в которых допускаются составные правила, но не допускаются правила ELSE. Интересующимся описанием дру- гих аспектов таблиц решений советуем обратиться к работе 120 ]. Важное свойство таблиц решений состоит в том, что их можно легко транслировать в программы. Если таблица решений точно и полно описывает некоторую задачу, то ее можно стандартным способом транслировать в машинную программу, которая будет успешно решать эту задачу. Использование автоматической трансляции таблиц решений представляется нужным и рацио- нальным, поскольку все специальные вопросы, связанные с по- ниманием и анализом задачи, к моменту создания таблицы решений уже отработаны. Имеется два общих метода трансляции таблиц решений: метод маскирования и метод блок-схем или деревьев решений. В методе 404
маскирования используется схема двоичного кодирования, с по- мощью которой каждое правило переводится в два вектора. Один вектор для каждого условия указывает, нужно ли его проверять (обозначается единицей) или оно нерелевантно (нуль). Второй вектор для каждого проверяемого условия указывает требуемое значение Y или N. Оттранслированная программа обеспечивает проверку выполнения всех условий и создание вектора, харак- ‘ теризующего отрицательные и положительные ответы на соответ- ствующие вопросы. Этот вектор по очереди сравнивается с векто- . рами правил до тех пор, пока не будет обнаружено соответствие I между ним и некоторым правилом. Однако при использовании F такого метода для решения некоторых задач затрачивается не- I оправданно много времени, так как в создаваемых с помощью .. данного метода программах осуществляется проверка всех усло- вий, несмотря на то, что не все из них влияют на конечный ре- зультат. j При использовании метода блок-схем или деревьев решений ' условия проверяются по одному до тех пор, пока не станет ясно, какое из правил соответствует имеющимся данным. После каждой проверки осуществляется переход либо к проверке следующего условия, либо к выполнению соответствующей комбинации дей- , ствий; при этом следующая операция определяется выполнением или невыполнением данного условия. Этот процесс можно изобра- ’ зить в виде блок-схемы (или в виде бинарного дерева). Именно этот, второй метод представляет интерес в связи с темой данной главы и ниже описывается более подробно. Общий принцип блок-схемной трансляции таблиц решений i заключается в следующей последовательности шагов. В 1. Выбрать условие. | 2. Создать по исходной таблице две подтаблицы, из которых “ исключено выбранное условие. В одну подтаблицу включить те . правила, в которых выбранное условие имеет значение true, I а во вторую •— те правила, в которых данное условие имеет зна- |чение false. Правила, содержащие для данного условия нереле- [ вантный символ «—•», включаются в обе подтаблицы. । 3. Для каждой подтаблицы повторять шаги 1 и 2 до тех пор, ₽ пока не получится таблица, состоящая лишь из одного столбца. ; Условие, выбранное на первом шаге, изображается на блок- схеме в виде решающего блока (в виде вершины в древовидном представлении этой блок-схемы). Если проверка данного условия • Дала значение true, то осуществляется переход по правой ветви ' блок-схемы (т. е. правой связи бинарного дерева). Если же его проверка дала значение false, то происходит переход по левой его ветви. Терминальные узлы или листья изображают множество действий, соответствующих данному пути в дереве условий. Простейший подход для выбора условий при построении блок- схемы заключается в выборе условий в порядке появления их в таблице. При применении такого подхода к приведенной на 405
Рис. 5-2.16. Блок-схема для таблицы, приведенной на рис. 5-2.12, получаемая при вы- боре условий в порядке 1, 2, 3 рис. 5-2.12 таблице получается блок-схема, изображенная на рис. 5-2.16. На рис. 5-2.17 изображена блок-схема, образующаяся из приведенной на рис. 5-2.12 таблицы при выборе условий в по- рядке 1, 3, 2. Между блок-схемами, изображенными на рис. 5.2.16 и рис. 5-2.17, имеется два существенных различия. Первое — на рис. 5-2.16 семь решающих блоков, в то время как на рис. 5-2.17—- только пять. Второе различие — при использовании блок-схемы, изображенной на рис. 5-2.16, для достижения любого действия необходимо принять три решения, а при использовании блок- схемы, изображенной на рис. 5-2.17, для достижения действий, соответствующих правилам 1 и 2, необходимо принять только два решения. Таким образом, рассмотрение даже простейшего примера таблицы решений показывает, что порядок выбора усло- вий может оказать существенное влияние на структуру блок- схемы. Рис. S-2.17. Блок-схема для таблицы, приведенной на рис. 5-2.12, получаемая при выборе условий в порядке 1, 3, 2 406
Процесс трансляции таблиц решений можно оценивать с по- мощью двух критериев. 1- Время обработки—время, необходимое для выполнения проверок, требуемых для достижения вершины, описывающей действия. 2. Затраты памяти на программу — память, необходимая для хранения программы, выполняющей проверку условий. При трансляции таблицы решений программа минимизируется по одному из этих критериев, поскольку обычно минимизировать ее по обоим критериям невозможно. Мы будем рассматривать оптимизацию только по первому критерию. Поскольку во многих вычислительных системах цена памяти постоянно уменьшается, при оптимизации программ важно учитывать в первую очередь фактор времени обработки. Для оценки времени обработки Оттранслированных таблиц решений обычно используется ожидаемое, или среднее время обработки. Если задано время, необходимое для проведения каждой проверки, то время обработки для каждого пути реша- ющего дерева определяют как сумму интервалов времени, затра- чиваемых на все проверки, встречающиеся на пути от корня до вершины действий. Если для каждого пути задана вероятность его появления, то среднее гремя обработки определяют как взвешенную сумму времени 'обработки путей, где в каче- стве весов выступают вероятности появления соответствующих путей. Непосредственный метод нахождения дерева решений с мини- мальным средним временем обработки состоит в том, что сначала вычисляют это время для всех возможных деревьев, эквивалент- ных данной таблице решений, а затем выбирают то из них, ко- торое имеет минимальное время обработки. Этот метод рассмотрен в работе Рейнуолда и Солэнда [241, предложивших алгоритм, который обеспечивает получение оптимальной блок-схемы. Однако этот алгоритм (несмотря на оптимальность) малопригоден на прак- тике из-за большого времени трансляции, кроме того, при его применении требуется наличие в таблице решений с ограничен- ным входом всех возможных простых правил. Поэтому мы здесь рассмотрим менее точный алгоритм Верхельста [301, Применение этого алгоритма сопряжено с меньшими затратами времени, он более прост для понимания, но создает программы, время обра- ботки которых только приближается к оптимальному. В алгоритме Верхельста предполагается, что между усло- виями таблицы не существует никаких логических взаимосвя- зей, отличных от тех, которые выражены с помощью записей «—», «У» и «№ в секторе значений условий. Можно показать, что при таком предположении наибольшее время требуется тогда, когда должны быть проверены все нерелевантные условия. Если вероятность использования j-ro правила обозначить через Р,- 0 ~ 1, ..., п), а время проверки j-ro условия — через t, (i — 1, ..., 407
m), то нижнюю границу среднего времени обработки можно выразить в виде S-f.lt.SPj), 1=1 j где суммирование ведется по всем правилам j, у которых зна- чение i-го условия не равно «—». Рассмотрим таблицу, изображенную на рис. 5-2.12. Если для условий 1, 2, 3 задать время проверок, равным 1, 3 н 2 соответ- ственно, а для правил 1—-6 — вероятности появления равными 0.3, 0-3, 0.1, 0.1, 0.1, 0.1 соответственно, то для этой таблицы S опре- делится так: S = 1 (0.3 4- О.з 4 0.1 4- 0.1 4- 0.1 4 0.1) 4- 4- 3 (0.1 4- 0.1 4 0.1 4- 0.1) 4 2 (0.3 4 0.3 4 4 0.1 4- 0.1 4- 0.1 4 0.1) = 1 4 1.2 4 2 = 4.2. Следовательно,- среднее время проверок для этой таблицы не может быть меньше 4.2. В качестве примера обратимся к рис. 5-2.17, на котором изо- бражена блок-схема, созданная по приведенной на рис. 5-2.12 таблице. Среднее время для этой блок-схемы равно 0.1 (6) 4 4- 0.1 (6) 4 0.1 (6) 4 0.1 (6) 4 0.3 (3) 4- 0.3 (3 ) = 4.2. Если для блок-схемы, изображенной на рис. 5-2.16, предположить, что вероятность 0.3 для правил 1 и 2 делится пополам между отве- тами «да» и «нет» на вопрос условия 2, то среднее время проверок составляет 0.1 (6) + 0.1 (6) + 0.1 (6) + 0.1 (6) + -+ 0.15 (6) + 0.15 (6) + 0.15 (6) + 0.15 (6) = = 2.4 + 3.6 = 6.0. Алгоритм Верхельста вначале для каждого условия Q таб- лицвг решений вычисляет величину T| = t(£pj, где tj — время, необходимое для проверки условия Q, a Pj — вероятность, связанная с j-м правилом. Суммирование ведется по всем правилам j, у которых значение условия Q равно «—». Затем выбирается условие Q, значение Tj у которого минималь- ное. Если имеется несколько условий, значение Tj у которых равно минимуму (Т = min Tj),To из них выбирается то, которое имеет минимальное число прочерков. Если же совпадает и число прочерков, то минимальное условие выбирается произвольно. Значение Tj по исходной таблице вычисляют для того, чтобы найти корень дерева решений (т. е. первое проверяемое условие). Каждую следующую вершину выбирают посредством вычисления значений Tj по соответствующей подтаблице. 408
3 3 1111 рис." 5-2,18. Процесс построения блок-схемы по алгоритму Верхельста Для примера рассмотрим снова таблицу решений, изображен- ную на рис. 5-2.17. Если использовать ранее заданные значения вероятностей и времени обработки, то значения Т, для всех усло- вий определятся следующим образом: Tt = 0 ... (нет записей «—»); Т2 = 3 (0.3 + 0.3) = 1.8; Т3 — 0 ... (нет записей «—»). Число прочерков для условий 1 и 3 одинаково. Для первой про- верки мы выбираем условие 1, так как время проверки условия 1 меньше, чем время проверки условия 3. При этом, как показано иа рис. 5-2.18, исходная таблица делится на две подтаблицы. Заметим, что на рис. 5-2.18 для простоты опущены перечни усло- вий и действий и индикаторы действий. Рассмотрим теперь две подтаблицы, отмеченные на рис. 5-2.18 номерами 1 и 2 в кружках; для подтаблицы 1 величины Т определяются как Т2 = 3(0.3 -Ь 0.3) -- 1.8; Т3 = 0. Величина Т3 оказывается минимальной, поэтому для следующей проверки мы выбираем условие 3, в результате чего образуются подтаблицы 3 и 4. Для подтаблицы 2 обе величины Т равны 0, поэтому мы для проверки можем выбрать либо условие 2, либо условие 3. На рис. 5-2.18 изображены подтаблицы, возникающие в результате выбора условия 3. Блок-схема, полученная как результат описанного процесса, изображена на рис. 5-2.19, Заметим, что если подтаблица имеет лишь одно правило, содержащее только записи «—», то дальней- шие проверки не требуются. Среднее время проверки для создан- ной блок-схемы составляет 4.2, что совпадает с ранее вычислен- 409
Рис. 5-2.19. Блок-схема, соответствующая рис. 5-2.18 ной его нижней границей. Таким образом, для рассмотренной простейшей таблицы алгоритм Верхельста дал оптимальную блок-схему. Для более сложных таблиц этот алгоритм обычно дает блок-схему, для которой среднее время проверки условий лишь приближается к нижней границе. Описанный метод можно коротко резюмировать следующим образом. В первую очередь для проверки обычно выбираются условия с минимальным числом нерелевантных записей, что уменьшает на каждом этапе размер образующихся подтаблиц и, следовательно, получающихся на их основе поддеревьев. Если в некоторой точке процесса конструирования блок-схемы окажется, что несколько условий имеют одинаковое минималь- ное число нерелевантных записей, то выбирают условие с мини- мальным временем проверки, так как это позволяет отсрочить проверку тех условий, для которых требуется больше времени. Описанный выше алгоритм позволяет создать дерево решений, среднее время обработки которого приближается к минимуму при условии, что на его вход подается таблица решений с ограничен- ным? входом без правил ELSE и без неоднозначности, но в которой допускается уменьшение числа правил путем введения нереле- вантных записей. Ниже дано формальное описание основанного на методе Дайела [7] алгоритма формирования по таблице решений бинарного дерева; однако подалгоритм выбора условия базируется здесь на описанном выше методе Верхельста. Для простоты предполо- жим, что каждому правилу соответствует одно действие, т. е. идентичные группы действий объединяются так, чтобы на них можно было ссылаться как на одно действие. Алгоритм трансляции таблицы решений с ограниченным вхо- дом в приближающееся к оптимальному дерево решений состоит из основного алгоритма DT—TRAN и трех подалгоритмов SELECT—СО ND, SPLIT—TABLE и SHIFT. Алгоритм SELECT___СО ND выбирает условие, удовлетворяющее описан- ному ранее критерию, алгоритм SPLIT—TABLE строит дерево 410
решений, а алгоритм SHIFT создает подтаблицы. Путем обхода полученного дерева можно получить программу на любом языке программирования при условии, что на этом языке уже написана расшифровка символьного представления всех условий и дей- ствий. Таблица решений запоминается в двух массивах. Двумерный массив D—TAB содержит значения условий таблицы; элемент (i, j) таблицы D_TAB имеет вид «У», «N» или «—» в соответствии со значением i-ro условия в j-м правиле. Второй массив RULE представляет собой вектор, содержащий целые числа, характе- ризующие множества действий, соответствующие отдельным пра- вилам. Значения элементов вектора RULE используются в каче- стве индексов элементов вектора, содержащего символьные пред- ставления всех множеств действий. Для работы алгоритма необходимы еще два вектора, обозна- чаемые через TIME и Р. Величина TIME [i ] характеризует время проверки i-ro условия, а величина Р {j ] — вероятность использования j-ro правила. Значения элементов вектора Р оце- ниваются разработчиком таблицы решений, который хорошо знаком с решаемой задачей и может для каждого правила уста- новить относительное значение частоты его использования. Если для создания подтаблиц копировать части массива D—TAB, то будут лишние затраты памяти и времени. Для того чтобы избежать этих затрат, обрабатываемая подтаблица выде- ляется из D__TAB с помощью двух векторов ROW и COL. Пер- вые LROW элементов вектора ROW являются индексами, выде- ляющими из D___TAB условия, соответствующие обрабатываемой таблице; элементы вектора COL от COL [FCOL] до COL [LCOL] являются индексами, выделяющими из D____TAB правила обра- батываемой таблицы. Используемая в качестве примера таблица решений, изобра- женная на рис. 5-2.12, представляется следующими значениями элементов описанных векторов и массивов: D—TAB Y Y N N N N Y Y N N YN---------Y N Y N RULE 1 2 3 4 5 6 (Примечание. Различающиеся элементы вектора RULE содержат разные числа, поскольку разным правилам таблицы соответствуют разные группы действий) TIME I Р 0.3 0.3 0.1 0.1 0.1 0.1 Начальные значения элементов векторов ROW и COL имеют ВИД ь ROW I 2 3 LROW = 3 ' COL 1 2 3 4 5 6 FCOL == I LCOL = 6 £ 411
Ниже описывается алгоритм выбора условия для очередной проверки в соответствии с описанным выше критерием. Алгоритм SELECT—СО ND. На вход подаются величины LROW (максимальный индекс вектора ROW), FCOL и LCOL (индексы вектора COL). Алгоритм SELECT—COND выдает индекс элемента вектора ROW. Этот элемент задает индекс условия, которое удовлетворяет используемому критерию выбора условий. Переменные SUM1 и SUM2 характеризуют соответственно вели- чину — tjV, Р. и число прочерков. MIN обозначает минимум Т„ а переменная LOC задает такой индекс вектора ROW, что TrowelocJ — MIN'. DASH______СТ представляет собой вектор, задающий для каждой строки таблицы число имеющихся в ней прочерков; i и j — индексные переменные. 1. [Инициализация.] Установить MIN-<—999999, LOC-*-0, i <-0. 2. [Установка режима просмотра очередной строки.] Уста- новить i ч-i 4- 1. Если i > LROW, то перейти к шагу 6. 3. [Определение числа прочерков и У Р ] Установить SUMI 4-SUM2, повторять при j = FCOL, FCOL 4 1, ...» LCOL: если D TAB [ROW [i I COL [j ] ] — *—то установить SUMI ч— ч-SUMl 4- P[COL []']], <SUM2 4-SUM2 4- 1. 4. [Конец вычислений; сравнение с минимумом]. Установить SUMI ч-SUMl * TIME [ROW [i ] ], DASH—CT [i ] 4-SUM2+1. Если SUMI < MIN, то установить MIN ч-SUMl, LOC -<-i и перейти к шагу 2. 5. [Сравнение числа прочерков при SUMI =MIN.] Если SUMI = MIN, го если DASH___СТ [i ] < DASH__СТ [LOC], установить LOC-e-i. Перейти к шагу 2. 6. [Проверка на нерелевантность всех элементов строки. ] Установить SELECT—COND ч-LOC, если LOC Д> О, то если DASH—СТ [LOC] = LCOL - - FCOL 4- 1, установить SELECT—COND ч-0. 7. [Выход. ] Завершить выполнение алгоритма. В шаге 1 осуществляется установка начальных значений ин- дексов; MIN устанавливается равным 999999 в предположении, что значение Т никогда не превышает величины 999999. В шаге 2 происходит увеличение индекса i и проверка конца цикла. В шаге 3 задаются начальные значения рабочих переменных SUM1 и SUM2. Далее осуществляется просмотр всех столбцов строки; при этом SUM1 увеличивается на значение Р столбца, a SUM2 увеличивается^на^единицу для каждого нерелевантного элемента 412
данной строки. В шаге 4 заканчивается вычисление величины Т, и число прочерков заносится в вектор DASH____СТ. В этом же шаге вычисленное значение Т сравнивается с минимумом, и указатель минимума, если необходимо, изменяется. Если вы- численное значение Т совпадает с минимумом, то выбирается строка с наименьшим числом прочерков, что выполняется в шаге 5. В шаге 6 устанавливается значение выдаваемой алгоритмом ве- личины и осуществляется проверка наличия в выбранной строке только символов «—»; в последнем случае алгоритм выдает ну- левое значение. Следующий алгоритм SHIFT создает подтаблицы для выбран- ной строки условия, что осуществляется реорганизацией элемен- тов вектора COL. Первые (FDASH 1—1) записи вектора COL содержат индексы правил, у которых в выбранной строке стоит «N». Элементы от FDASH до (FYES 1—1) содержат индексы правил с записью «—» в выбранной строке, остальные элементы содержат индексы правил с записью «У» в выбранной строке. Для выделе- ния элементов, характеризующих записи «—», алгоритм рекур- сивно вызывает самого себя. Для примера выберем в таблице на рис. 5-2.12 второе условие. Само оно характеризуется следующим набором значений: правило: 1 2 3 4 56, значение: — — YYNN, а элементы соответствующего ему вектора COL имеют вид COL 1 2 3 4 5 6. После исполнения алгоритма SHIFT вектор COL будет иметь вид COL 6 5 1 2 4 3. Алгоритм SHIFT. Заданы параметры: ROW (номер условия, на основе которого строятся подтаблицы), FIRST и LAST (гра- ницы элементов COL [FIRST], ..., COL [LAST] вектора COL, определяющих исходную таблицу), FDASH и FYES (переменные, значения которых устанавливаются соответственно на позицию первой записи «—» и позицию первой записи J«Y» в векторе COL) и KEY (символ «Ы», «—» или «Y», определяющий, номера пра- вил, которые нужно переместить к левой границе вектора COL). Алгоритм SHIFT реорганизует элементы в векторе COL и уста- навливает значения параметров FDASH и FYES. В предыдущем примере FDASH и FYES первоначально были установлены рав- ными 3 и 5 соответственно. 1. [Инициализация.] Установить i ч-FIRST, j ч-LAST. 2. [Пропуск левых записей, совпадающих с KEY.] Повто- рять до тех пор, пока D_TAB [ROW, COL li ] ] = KEY и i j: установить i ч-i -|- 1. 3. [Поиск самой правой записи, совпадающей с KEY. 1 По- вторять до тех пор, пока D___TAB [ROW, COL Ij ] 1 =£ KEY и j > i: установить j ч- j — 1. 413
4. [Взаимный обмен.] Если i < j, то установить COL [i] COL [j], j ч-j —1 и перейти к шагу 2. 5. [Установка граничного маркера и при необходимости по- вторный вызов алгоритма.] Установить FDASH -*~i. Если KEY то вызвать SHIFT (ROW, i, LAST, FYES, FDASH, -')• 6. [Выход.] Завершить выполнение алгоритма. В шаге 1 задаются начальные значения индексов вектора COL. В шаге 2 пропускаются путем увеличения i самые левые записи вектора COL, задающие индексы тех элементов D_TAB, которые совпадают с KEY. В том случае, когда встречается запись, не совпадающая с KEY, выполняется шаг 3 и j уменьшается до тех пор пока не совпадет с индексом самого правого элемента COL, указывающего на запись D__TAB, совпадающую с KEY. В шаге 4 i-й и j-й элементы вектора COL меняются местами, в результате чего элемент вектора COL, индексирующий запись D______TAB, совпадающую с KEY, передвигается в нем влево. Заметим, что если слева от i-ro элемента COL отсутствует указатель на запись KEY, то проверка соответствующего условия в шаге 4 даст отри- цательный результат, и замены элементов не будет. Шаг 5 выпол- няется после проверки всех элементов вектора COL. Параметр FDASH устанавливается равным i и указывает на первый эле- мент, индексирующий в D__TAB запись, не совпадающую с KEY. Если KEY неравен ”—”, то алгоритм SHIFT рекурсивно вызы- вает сам себя для перемещения индексов записей ”—” таким обра- зом, чтобы они оказались непосредственно справа от индексов записей KEY. На рис. 5-2.20 дана последовательность шагов преобразований, возникающая в результате применения алгоритма SHIFT к сле- дующей исходной ситуации: индекс 1 2?3 45 6 D—TAB — Y — YNN COL 1 23456 Параметры алгоритма SHIFT при первоначальном его вызове имеют вид FIRST = 1; LAST = 6; KEY = ’№. Рассмотрим теперь алгоритм SPLIT___TABLE. Для создания дерева решений он использует два описанных выше алгоритма. Алгоритм SPLIT_TABLE работает по следующей схеме: выби- рается некоторое условие и создается соответствующая ему вер- шина дерева, в которой устанавливаются указатели левого и пра- вого поддеревьев; поддеревья, соответствующие каждому выбран- ному условию, строятся рекурсивным способом. 414
Первый вызов: SHIFT (ROW, I, 6, X,Y, 'N')> D—TAB [ROWI-- — Y — Y N N, COL = 1 2 3 4 5 6. Шаг Описание 1. i*- 1» j<- 6. 2. D—TAB [ROW, COL [!]]«£»'№, переход к шагу 3. D—TAB [ROW, COL [6|] = 'N', переход к шагу 4. Взаимозамена COL [I] и COL [6], j <- 5, 1 з. t 4. i- Вектор COL 6 Л j 2 3 4 5 переход к шагу 2. 6 t 2 3 4 5 j 1 2. D—TAB [ROW, COL [1]] = 'N', i <- 2, 6 2 3 4 1 D—TAB [ROW, COL [2]] =£= 'N', переход к шагу 3. t t 3. D—TAB [ROW, COL [5] J == 'N', переход к шагу 4. i j 4. Взаимозамена COL [2] и COL [5], j <— 4, переход к шагу 2. 6 5 3 4 2 1 2. D—TAB [ROW, COL [2]] = 'N', i <- 3. 6 i 5 3 j 4 2 1 D—TAB [ROW, COL [3]] =r= 'N', переход к шагу 3. 1 t 3. D— TAB [ROW, COL [4] J = 'N', j — 3, 6 5 3 i 4 2 I i = j, переход к шагу 4. t 4. i = j, переход к шагу 5. i.j 5. X <- 3, KEY-7= вызов SHIFT (ROW. 3. 6, Y, X, '—') и возврат Второй вызов: SHIFT (ROW, 3, 6, Y, X, '). 1. i-3, ]ч-6. 6 5 3 4 2 1 t i j 2. D—TAB [ROW, COL [3]] = i <- 4, 6 5 3 4 2 [ D—TAB [ROW, COL [4]] -p , переход к шагу 3. t 3. D—TAB [ROW, COL [6] J = '—', переход к шагу 4. i j 4. Взаимозамена COL [4] и COL [5], j <- 5 и переход к шагу 2. 6 5 3 1 2 4 f f 2. D—TAB [ROW, COL [411 = i <- 5, 6 5 3 1 2 4 D—TAB [ROW, COL [5]] =£='—', переход к шагу 3. I 3. i = j, переход к шагу 4. IJ 4. i = j, переход к шагу 5. 5. Y 5, KEY -= '—', то возврат. Рис. 5-2.20. Пример трассы алгоритма SHIFT 415
Вершина дерева имеет формат LPTR NODE—VAL RPTR Поле NODE____VAL может содержать либо индекс условия или действия, либо нуль, означающий, что индекс соответствующего действия не был введен (ситуация ошибки). Поля LPTR и RPTR представляют собой указатели поддеревьев «N» и «Y» соответ- ственно. Ситуация RPTR = LPTR = NULL означает терми- нальный узел, или узел действий. Алгоритм SPLIT__TABLE. Заданы параметры”LROW (индекс вектора ROW), FCOL и LCOL (индексы вектора COL). Алго- ритм SPLIT___TABLE рекурсивно строит дерево решений, соответ- ствующее таблице, определяемой векторами ROW и COL, и вы- дает указатель на корень построенного дерева. Переменная SUB задает индекс выбранного элемента вектора ROW, а переменная COND —выбранное условие, т. е. COND — ROW [SUB]. SAVE представляет собой вектор, в котором запоминаются значения элементов вектора Р; при этом вероятности, соответствующие за- писям «—», делятся для каждой из подтаблиц на две. PTR пред- ставляет собой указатель вершины, выбранной из стека AVAIL последней; FIRSTDASH и FIRSTYES задают индексы элементов COL, характеризующих первую запись «—» и первую запись «Y» соответственно. 1. [Выбор условия.] Установить SUB SELECT________COND (LROW, FCOL, LCOL), если SUB 0, то установить PTR -4= -4= NODE, COND NODE__VAL(PTR) <- ROW [SUB ], ROW [SUB] -^ROW [LROW]; в противном случае перейти к шагу 5. 2. [Создание подтаблиц и запоминание вектора вероятностей.] Вызвать SHIFT (COND, FCOL, LCOL, FIRSTDASH, FIRSTYES, lN‘). Повторять при i = FCOL, FCOL + 1, ..., LCOL: SAVE [COL [i]] = P [COL [i]]. 3. [Деление на два вероятностей для записей «—»; создание поддерева, соответствующего подтаблице 'NL I Повторять при i = FIRSTDASH, FIRSTDASH + 1, .... FIRSTYES — I: Уста- новить P [COL [i]] -<-P [COL [i]]/2. LPTR (PTR) SPLIT—TABLE (LROW—1, FCOL, FIRSTYES — 1). 4. [Рекурсивная реорганизация вектора COL; создание под- дерева, соответствующего подтаблице ‘Y‘; и восстановление век- тора вероятностей]. Вызвать SHIFT(COND, FCOL, FIRSTYES — 1, FIRSTDASH, FIRSTYES, ‘Y‘). Установить RPTR (PTR) ч- ч-SPLIT—TABLE (LROW—1, FIRSTDASH, LCOL), SPLIT- TABLE PTR, and ROW [SUB] ч-COND. Повторить при i = FCOL, FCOL + 1, ..., LCOL: P [COL [i ] ] = SAVE [COL [i ] 1. Завершить выполнение алгоритма. 416
5. [Создание терминального узла (узла действий). ] Устано- вить PTR NODE, SPLIT—TABLE ч-PTR, and LPTR (PTR)ч- RPTR (PTR) -«—NULL. Если FCOL 2> LCOL, то установить NODE____VAL (PTR) ^-0; в противном случае установить NODE—VAL (PTR) ^COL [FCOL]. 6. [Выход. ] Завершить выполнение алгоритма. В шаге 1 описываемого алгоритма осуществляется вызов алго- ритма SELECT—COND для выбора условия, которое должно быть проверено на следующем шаге. Если не все записи выбранного условия нерелевантны, то создается вершина, соответствующая проверке данного условия. Поле NODE___VAL здесь задает номер условия. Выбранный элемент вектора ROW замещается послед- ним его элементом, что позволяет сжать вектор ROW. (Если все записи выбранного в шаге 1 условия являются нерелевантными, то в шаге 5 создается соответствующая терминальная вершина). В шаге 2 вызывается алгоритм SNIFT, который реорганизует элементы 'N', '—‘ и выбранного условия. Вектор вероят- ности Р копируется в векторе SAVE. В шаге 3 вероятности элементов '—' делятся на два; затем рекурсивно вызывается алгоритм SPLIT___TABLE, который соз- дает поддерево, соответствующее тем правилам таблицы решений, которые содержат значения 'N' и ‘‘ для условия, выбранного в шаге 1. После построения поддерева 'N* выполняется шаг 4. В этом шаге происходит обращение к алгоритму SHIFT для реоргани- зации вектора COL, структура которого была изменена во время рекурсивного построения поддерева '№. Затем происходит ре- курсивное обращение к алгоритму SPLIT___TABLE для построе- ния поддерева, соответствующего записям '—' и *Y*. При построе- нии этого поддерева переменная SPLIT__TABLE устанавливается на вершину, созданную в шаге 1, а элементу ROW [SUB] при- сваивается его начальное значение. Затем восстанавливаются начальные значения вектора Р. после чего работа алгоритма за- канчивается. В шаге 5 создается терминальная вершина, соответствующая строке, все записи которой равны '—или полному отсутствию строки (последнее означает, что все условия удовлетворены). Нулевое значение терминальной вершины указывает на то, что для последовательности значений условий, ведущей к данному узлу, никаких действий не существует. Ненулевое же значение равно номеру того столбца исходной таблицы решений, который соответствует последовательности значений условий, ведущей от начальной вершины к данной терминальной вершине. Приводимый ниже алгоритм D______TRAN осуществляет ввод таблицы решений и вызывает алгоритм SPLIT_______TABLE для создания соответствующей ей древовидной структуры. Алгоритм D—TRAN. Этот алгоритм производит ввод таблицы решений и строит эквивалентное ей дерево решений со средним 14 Трамбле Ж., Соренсон П. 417
временем обработки, приближающимся к оптимальному. Пере- менная HEAD указывает на корень дерева решений; параметр ACTION# задает число множеств действий. Векторы COND___PHRASE и ACT____PHRASE содержат символьное пред- ставление элементов перечня условий и перечня действий соот- ветственно. Значения элементов этих векторов зависят от языка программирования, на который должна транслироваться данная таблица. В нашей реализации элементы вектора COND—PHRASE должны содержать описания отношений между некоторыми объек- тами программы (например, X > Y, NAME = 'BOB', NAME = = 'JIM'); элементы вектора ACTION_PHRASE должны содер- жать предложения языка ПЛ/1 (например, 'GOTO STOP_ACT', 'CALL MAKE-NEW', 'Q = 3*Y'). 1. [Ввод размеров таблицы условий. Читать LROW, LCOL, ACTION# и установить FCOL ч-1, 2. [Построчный ввод таблицы.] Повторять при i = 1, 2, ..., LROW: повторять при j = 1, 2, ..., LCOL: читать D_TAB [i, j]. 3. [Ввод условий и действий.] Повторять при i = 1, 2, ..., LROW: читать COND—PHRASE [i]. Повторять при i = 1, 2, ..., LCOL: читать ACT_PHRASE [i]. 4. [Ввод времени обработки и вероятностей. ] Повторять при i = 1, 2, ..., LROW: читать TIME [i]. Повторять при i — 1, 2, ..., LCOL: читать Pjli]. 5. [Инициализация векторов ROW и COL. ] Повторять при i = 1, 2, ..., LROW: установить ROW [i] ч—i. Повторять при i = 1, 2, ..., LCOL: установить COL [i ] ч-i. INTERPRET: PROCEDURE(ROOT,1) RECURSIVE; /< "'procedure interpret recursively traverses THE binary tree with /« ROOT NODE 'ROOT’ AND CREATES PL/1 CODE WHICH IS EQUIVALENT */ -/« TO THE FLOWCHART THE TREE REPRESENTS. . °' DECLARE ROOT POINTER» I FIXED BINARY» CLAUSE CHARACTERISE) VARYING; IF ROOT->RPTR ->= NULL THEN DO; CLAUSE = COND_PHRASE(ROOT->NOOE_VALi; PUT SKIP EDIT 1'IF I,CLAUSE) (COLUMN!I)»A(3)»A)? PUT SKIP EDIT MTHEN’) (COLUMNtI)»A CALL INTERPRET (ROOT->RPTR, ! *- 5> 5 PUT SKIP EDIT (’ELSE») (COLUMN(I),A) 1 CALL INTERPRET (ROOT~>LPTR, Г 5) 5 ' END; ELSE DO; IF ROOT->N0DE_VAL = 0 then pu^ EDiTt’;’) (Ad)); ELSE PUT EDIT(ACT_PHRASE(RULE(ROOT->NODE_VAL)),';’1 (COLUMN(T)»A»A(1)); END; END INTERPRET; Рис. 5-2.21. Программа трансляции дерева решений на язык ПЛ/1 418
6. [Построение дерева. ] Установить HEAD *— SPLIT—TABLE (LROW, FCOL, LCOL) и завершить выпол- нение алгоритма. После того как дерево решений построено, можно выполнять его обходы с помощью различных программ, транслирующих это дерево в эквивалентные программы на разных языках. Одна из таких программ дана на рис. 5-2.21. Процедура INTERPRET здесь осуществляет рекурсивный обход дерева и создает предло- жение языка ПЛ/T, описывающее все ветвления этого дерева. Параметр I здесь используется для формирования нужных от- ступов в создаваемой программе. J jSl Программная реализация остальных алгоритмов данного пункта не приводится, поскольку соответствующие программы очень похожи на описанные алгоритмы. 5-3. МНОГОСВЯЗНЫЕ СТРУКТУРЫ Структуры данных, которые мы рассматривали до сих пор, содержали два поля указателей. За исключением древо- видных структур, структуры с двумя полями указателей задают только отношение физического соседства. h В данном параграфе мы сделаем обобщение структур в том смысле, что в структуре описания вершины может быть исполь- зовано более двух полей указателей. Первое их приложение отно- сится к представлению и обработке разреженных матриц, которым посвящен п. 5-3.1. В п. 5-3.2 обсуждается задача создания пред- метного указателя книги. 5-3.1. Разреженные матрицы Важным приложением линейных списков является представление матриц, содержащих большое количество нулевых элементов. Такие матрицы называются разреженными. Обычно их используют в научных приложениях. Разреженные матрицы содержат сотни и даже тысячи строк и столбцов. Если для запо- минания этих матриц использовать методы последовательного распределения, описанные в гл. 3, то представление таких боль- ших матриц приводит к расточительному расходу памяти и не- эффективности выполняемых над ними действий. В данном пункте мы опишем представление разреженных матриц, используя свя- занное распределение, и сформулируем алгоритмы ввода и умно- жения для представленных в такой форме матриц. Вначале, однако, проанализируем различные варианты метода после- довательного распределения для представления разреженных 14* 419
Рассмотрим матрицу ~ 0 0 6 0 9 0 0“ 2 0 0 7 8 0 4 К) О О О О О О = II о 12 О О О О ‘ ооооооо 000300 5_ Из 42 элементов этой матрицы размерности 6‘<7 только 10 отличны от нуля, а именно А Ц.З] = 6, А [1,5] =9, А [2,1 ] = 2, А [2, 4] = 7, А [2,5] = 8, А [2, 7 ] — 4, А [3, 1 ] = 10, А [4, 3] = 12, А [6, 4] = 3, А [6, 7] = 5. Один из основных способов хранения подобных разреженных матриц заключается в запоминании ненулевых элементов в одно- мерном массиве и идентификации каждого элемента массива индексами строки и столбца, как это показано на рис. 5-3.1, а. Элемент с номером i вектора А является элементом матрицы с индексами строки ROW [i] и столбца COLUMN [i]. Отметим, что элементы матрицы запоминаются в порядке возрастания номеров строк, причем ненулевые элементы удалены. Более эффективное представление, с точки зрения требований к памяти и времени доступа к строкам матрицы, показано на рис. 5-3.1, б. Вектор ROW изменен так, что его i-й элемент является индексом первого из индексов столбцов для элементов строки i дайной матрицы. Мы предполагаем, что векторы ROW и COLUMN состоят из полуслов. Представление матрицы А, данное на рис. 5-3.1, со- Рис. 5-3.1. Последовательное представление разреженных матриц 420
кращает требования к объему памяти более чем в 2 раза. Для больших матриц экономия памяти очень важна. Способ последо- вательного распределения имеет также то преимущество, что операции над матрицами могут быть выполнены быстрее, чем это возможно при представлении в виде последовательного двумер- ного массива, особенно если размер матрицы велик. Следующий алгоритм осуществляет сложение двух матриц, представленных в виде, изображенном на рис. 5-3.1, б. Алгоритм MATRIX____ADDITION. Даны разреженные матрицы А и В, представленные векторами А и В с индексами строк и столб- цов. соответственно AROW и ACOL, BROW и BCOL; требуется сформировать матричную сумму С = А 4~ В. Матрица С должна быть представлена в том же виде, следовательно, необходимо сформировать индексы CROW и CCOL. А и В имеют одинаковую размерность mxn и содержат соответственно г и s ненулевых элементов. Число ненулевых элементов С по завершению алго- ритма составляет t. Вспомогательная переменная i используется для индексации строк матрицы, j и к индексируют матричные элементы соответственно векторов А и В. Переменные SUM и COLUMN используются для хранения каждого нового элемента матрицы С и его положения в столбце. 1. [Инициализация.! Установить i ч—1 и t ч-0. 2. [Сканирование строк.] Повторять шаги 3—7 до тех пор, пока i ш. Завершить выполнение алгоритма. 3. [Формирование индекса строк и начальных позиций сле- дующих строк.] Установить j ч-AROW [i], k ч-BROW [i], CROW[i] < t k 1, AMAX ч-ВМАХ ч^О. Если i < m, to повторять при p = i + 1, i + 2, ..., m до тех пор, пока AMAX = = 0: если AROW [р] Д 0, то установить АМАХ ч-AROW [р ]; повторять при р — i ф- 1, i 4- 2, ..., до тех пор, пока ВМАХ = 0: если BROW [р] =А 0., то установить ВМАХ ч-BROW [р ]. Если АМАХ — 0, то установить АМАХ ч-г+ 1. Если ВМАХ = 0, то установить ВМАХ s 4- 1. 4. [Сканирование столбца в данной строке. 1 Повторять шаги 5—9 до тех пор, пока j 0 или к 0. 5. [Конец какой-либо строки? ] Если j = 0, то установить SUM^-B [k], COLUMN ч-BCOLlk], к ^к + 1, и перейти к шагу 8. Если к = 0, то установить SUM ч- A [j ], COLUMN ч- -*-ACOL[ j], j -<-j 4- 1, и перейти к шагу 8. 6. [Элементы в тех же столбцах? ] Если ACOL [j ] = BCOL [к ], то установить SUM ч-A [j] 4- В [к], COLUMN ч-ACOL [j], J + 1, к ч—к 4- 1, и перейти к шагу 8. 7. [Столбец А предшествует столбцу В? ] Если ACOL [j 1 < BCOL [к ], то установить SUM ч- A [j ], COLUMN <- ACOL [j ], JVJ Л? J “Ь 1; в противном случае установить SUM ч-В [к], C°LUMN -“BCOL [к], и к М + 1 о. [Добавление нового элемента к сумме матриц. [Если SUM у-0, то установить t ч-t + 1, С It J . SUM, и CCOLJtJ ч-COLUMN. 421
9. (Конец какой-либо строки?] Если j = АМАХ, то устано- вить j ч-0. Если k = ВМАХ, то установить кч-О. 10. [Установка индекса матрицы С и увеличение значения индекса строки на 1.] Если t < CROW Lt], то установить CROW [t] ч-0. Установить i -*-i 4- 1. Для каждой пары соответствующих строк матриц А и В в ша- гах 3—10 производится сложение матричных элементов этих строк. Когда j или к равны 0, все ненулевые элементы _в строке матрицы А или В соответственно просмотрены. Если j и'к равны нулю, алгоритм может продолжать сложение следующих строк матриц. Если j или к первоначально не являются нулями, они устанавливаются в ноль при достижении значений соответственно АМАХ или ВМАХ. АМАХ и ВМАХ являются позициями в век- торах ACOL и A, BCOL и В, с которых начинаются следующие строки. Однако, если следующая строка отсутствует, АМАХ и ВМАХ имеют значения соответственно”г |-ь1Ги_з 1 (см. шаги 3 и 9). ' ’ Шаги 5—7 включительно обеспечивают выполнение требуе- мого сложения элементов матриц. В зависимости от того, про- смотрена или нет до конца строка одной из матриц, равны ли между собой индексы столбцов или индекс одного столбца меньше другого, возникает несколько вариантов. Прежде чем прибавить элемент к матрице С, в шаге 8 проверяется, не равен ли он нулю. Если никакие элементы не добавляются в строке i матрицы С, CROW li ] в шаге 10 устанавливается в нуль. Методы последовательного размещения для представления раз- реженных матриц обычно позволяют быстрее выполнять операции над матрицами и более эффективно использовать память, чем методы со связанными структурами. Однако последовательное представление матриц имеет недостатки, которые мы обсуждали раньше. Включение и исключение новых элементов матрицы вы- зывает необходимость перемещения большого числа других эле- ментов. Если включение новых элементов и их исключение осу- ществляются часто, то должен быть выбран описываемый ниже метод связанных структур. Для представления разреженных матриц требуется базовая структура вершины (рис. 5-3.2), назы- ваемая MATRIX_____ELEMENT («элемент матрицы»). Поля V, R LEFT | UP V j R | С и С каждой из этих вершин содержат соответственно значение, индексы строки! иДгголбц а элемента матрицы. Поля LEFT и UP являются указателями на сле- дующий элемент для строки и столбца в цикли- ческом списке, содержащем элементы матрицы. Поле LEFT указывает на вершину со сле- matrix element дуюшим наименьшим номером строки. На рис. 5-3.3 приведена многосвязная струк- Рис. 5-3.2. формат вершины для пред- ставления разрежен- ных матриц в виде связанных структур тура, в которой используются вершины такого типа для представления матрицы А, описанной ранее в данном пункте. 422
Рис. 5-3.3. Многосвязная структура для представления матрицы А Циклический список представляет все строки и столбцы. Список столбца может содержать общие вершины с одним списком строки или более. Для того чтобы обеспечить использование более эффективных алгоритмов включения и исключения элементов, все списки строк и столбцов имеют головные вершины. Головная вершина каждого списка строки содержит нуль в поле С; анало- гично каждая головная вершина в списке столбца имеет нуль в поле R. На головные вершины строк указывают соответству- ющие элементы из массива указателей ARROW. Элементы из ACOL указывают па головные вершины столбцов. Строка или столбец, содержащие только нулевые элементы, представлены головными вершинами, у которых поле LEFT или UP указывает само на себя. ^Может показаться странным, что указатели в этой многосвяз- ной структуре направлены вверх и влево, вследствие чего при сканировании циклического списка элементы матрицы встре- чаются в порядке убывания номеров строк и столбцов. Такой метод представления используется для упрощения включения новых вершин в структуру. Мы предполагаем, что новые вершины, которые должны быть добавлены к матрице, обычно распола- гаются в порядке убывания индексов строк и индексов столбцов. Если это так, новая вершина всегда добавляется после головной и не требуется никакого просмотра списка. Этот случай иллю- триц^^67051 В конце данного пункта алгоритмом умножения ма- Теперь приведем алгоритм построения многосвязной струк- Уры» представляющей матрицу, изображенную на рис. 5-3.3. 423
Предполагается, что входные записи для алгоритма состоят из строки, столбца и ненулевых матричных элементов, располо- женных в произвольном порядке. Дублированные элементы не поступают^на вход алгоритма. Алгоритм CONSTRUCT—MATRIX. Требуется сформировать многосвязное представление матрицы, используя ранее описан- ную узловую структуру MATRIX—ELEMENT. Размеры ма- трицы m и п, задающие число строк и столбцов, известны до начала исполнения алгоритма. Массивы AROW и ACOL содержат указатели на головные вершины циклических списков; X и Y используются как вторичные указатели. Для чтения индекса строки, индекса столбца и значения элемента матрицы исполь- зуются переменные ROW, COLUMN и VALUE. 1. [Инициализация структур матриц. ] Повторять при i = = 1, 2, ..., т: установить AROW -^MATRIX____ELEMENT, С (AROW [i ]) ч-0 и LEFT (AROW [i])^AROW (il. Повто- рять при i = 1,2, ..., n: установить ACOL-<=MATRIX_ELEMENT, R(ACOL [i[)^-0 и UP(ACOL [i J) ч- ACOL[i J. 2. [Получение элемента матрицы и его индексов строки и столбца. ] Читать ROW, COLUMN и VALUE. Если входные записи исчерпаны, то завершить выполнение алгоритма. 3. [Выделение и инициализация узла. ] Установить Р <= <= MATRIX—ELEMENT, R (Р) ROW, С(Р) ч-COLUMN и V (Р) VALUE. 4. [Поиск положения новой вершины в списке столбца. 1 Установить Q -(-ARROW [R (Р)]. Повторять до тех пор, пока С (Р) < LEFT (Q); установить Q -(-LEFT (Q). Установить LEFT (Р) -«-LEFT (Q) и LEFT (Q) ^Р. 5. [Поиск новой позиции вершины в списке столбца. ] Уста- новить Q ч—ACOL [С (Р) ]. Повторять до тех пор, пока R (Р)< R (UP (Q)): установить Q-(-UP(Q). Установить UP(P)-«-UP(Q) и UP (Q) ч-Р. Перейти к шагу 2. В шаге 1 алгоритма CONSTRUCT__MATRIX назначаются и инициализируются требуемые головные вершины. Назначение и инициализация вершины осуществляются для каждого считы- ваемого позднее триплета ROW, COLUMN и VALUE. Шаги 4 и 5 включают новую вершину в соответствующие списки строки и столбца. Сформулируем теперь алгоритм умножения двух матриц, представленных на рис. 5-3.3. Если две матрицы А и В перемно- жаются и формируют матрицу С, т. е. С = А В, необходимо, чтобы число столбцов в А равнялось числу строк в В. Если А содержит m строк и п столбцов, а В — п строк и t столбцов, тЪ матрица С, являющаяся их произведением, будет содержать m строк и t столбцов. Элементы матрицы С: C[i,j) = У (A(i, k]xB[k, j|), 1 ci cm, 1 c j ct. k=l 424
Алгоритм MATRIX MULTIPLICATION. Заданы массивы указателей AROW, ACOL, BROW и BCOL, ссылающиеся на много- связное представление разреженных матриц А и В размерностью шХП и nXt. Требуется сформировать представление матричного произведения С -- А X В. Массивы указателей CROW и CCOL используются для того, чтобы адресовать строки и столбцы ма- трицы С, имеющей размерность mxt. Переменные i и j исполь- зуются в качестве счетчиков соответственно строк матрицы А и столбцов матрицы В; А и В используются как указатели для сканирования строк матрицы А и столбцов матрицы В; Р яв- ляется вспомогательным указателем. 1. [Создание головных вершин для стеков строк.] Повторять при! = 1,2, ..., т: установить CROW [i] <=MATR1XELEMENT, C(CROW [i ]) -<—О и LEFT(CROW [i 1) ч-CROW [i ]. 2. [Создание головных вершин для списков столбцов. ] Повто- рять при j = 1, 2, . . ., t: установить CCOL(jl<= <=MATRIX_ELEMENT, R(CCOL[j]) -<-0 и UP(CCOL[j])+- 4-CCOL [j]. 3. [Просмотр m строк матрицы A. ] Повторять шаги 4—7 при i = 1, 2, ..., m. 4. [Просмотр t столбцов матрицы В. ] Повторять шаги 5- -7 при j — 1, 2, ..., t. 5. [Подготовка сканирования строки i матрицы А и столбца j матрицы В.1 Установить А LEFT(AROW[i 1), В *- UP(BCOL [j ]) и PRODUCT ч-0. 6. (Выполнение при необходимости сдвига указателей и пе- ремножение соответствующих элементов. ] Повторять до тех пор, пока R {В) 4 0 и С (А) 4= 0: Если С (А) > R (В), то устано- вить А *- LEFT (А); в противном случае: если R (В) > С (А), то установить В ч- UP (В); в противном случае установить PRODUCT -«-PRODUCT -[- V (А) * V (В), А ч- LEFT (А) и В ч-ЛЗР (В). 7. [Добавление произведения к матрице С, если оно не равно нулю.] Если PRODUCT 0, то установить Р <= MATRIX- ELEMENT, Р (Р) ч-i, С (Р) ч-j, V(P) PRODUCT, LEFT (Р) LEFT (CROW [i]), UP (P) ч-UP (CCOL [j]), LEFT (CROW(iJ) +-P, UP(CCOL[j]) *-P. 8. [Окончание. ] Завершить выполнение алгоритма. В шагах 1 и 2 инициализируются головные вершины для произведения С матрицы. Шаги 3 и 4 обеспечивают повторения, необходимые для умножения каждой строки матрицы А на каждый столбец матрицы В, осуществляемого шагами с 5-го по 7-й вклю- чительно. Шаг 5 подготовляет указатели А и В для сканирования циклических списков соответственно строки i матрицы А и столбца j матрицы В. В этом шаге инициализируется переменная PRODUCT, используемая далее для подсчета значений произведений соответ- ствующих элементов строк и столбцов. 425
MATRTX,MULT1РЦICATION: PROCEDURE{AROW, ACOL, BROW, BCDL, CROW, CCOL, M, N, I); /* THIS PROCEDURE MULTIPLIES TWO SPARSE MATRICES REPRESENTED BY MULTILINKED STRUCTURES. MATRICES A AND В WITH DIMENSIONS M X N AND N X T RESPECTIVELY ARE MULTIPLIED TO FORM MATRIX С ЫГЖ DIMENSIONS M X T. */ DECLARE (M, N, Г, I, JI BINARY FIXED, (ARGWI#), ACOLC*), BROWts-J, BCOL(«), CROW(«), CCOL(«), A, B, Q) POINTER; /=* INITIALIZE .MATRIX STRUCTURE */ DO I = 1 TO M; /* ALLOCATE ROW HEAD NODES */ ALLOCATE MATRIX_ELEMENT; p->c = o; P->LEFT = P; CROW!11 = P; eno; DO J = 1 TO T; /* ALLOCATE COLUMN HEAD NODES «/ ALLOCATE MATRIX.ELEMENT; P->R - O’, P->UP = p; CCOL(J) = p; end; DO I ~ 1 TO M; /* SCAN ROWS OF MATRIX A */ DO J = 1 IC т; /* SCAN EACH COLUMN OF MATRIX В */ /* INITIALIZE FOR SCANNING ROW*I OF MATRIX A AND COLUMN J OF MATRIX В */ Q = AROW11) ; A ~ 0->LEFT; 0 = BCOL(J); В = Q-MJP; PRODUCT = O; /* MOVE POINTERS AS NECESSARY AND MULTIPLY MATCHING ELEMENTS */ DO WHILE (B->R -.= 0 6 A->C -*= 0); IF A~>C > 6->R THEN A = A->LEFT? ELSE IF B->R > A->C THEN В = B->UP; ELSE DO? PRODUCT » PRODUCT t A->V * a = a->lEft; e = b->up; ' END; END; IF PROCUCT ->= 0 /* THEN ADD IT TO MATRIX C «/ THEN DO; ALLOCATE MATRIX_ELEMENT; P->R = i; ₽->c = J; ₽->V = PRODUCT; A = CROW(I); В = CCOL{J); P->LEFT = A->LEFTS P->UP = B->UP; A->LEFT = P; B->UP = P; END; ENO; END; END MATRIX_MULTTPLICATION; Рис. 5-3.4. Процедура для алгоритма MATRIX_MULTIPLICATION на языке ПЛ/ 426
Обратите внимание, что в шаге 6 строка i и столбец j просма- триваются в порядке убывания номеров столбцов и строк. Если номер столбца и номер строки вершины, на которую указывают соответственно А и В, ие равны, то один из указателей сдвигается в циклическом списке на следующую вершину. Если, однако, эти номера равны, то обновляется переменная PRODUCT, и оба указателя А и В изменяются таким образом, чтобы указывать на следующий элемент в том нее списке. Требуемое произведение строки i и столбца j считается вычисленным, если в любом из двух списков достигается головная вершина. Если PRODUCT не является нулем, шаг 7 выделяет и подго- товляет новую вершину. Так как строки и столбцы просматри- ваются в порядке возрастания номеров и так как указатели в вер- шинах списковой структуры направлены влево и вверх, новая вершина может быть включена вслед за головными вершинами строки i и столбца р На рис. 5-3.4 представлена реализация алгоритма MATRIX—MULTIPLICATION на языке ПЛ/1. Так как индек- сированные указатели не могут быть использованы в ПЛ/1 в ка- честве определителей указателей, необходимы некоторые допол- нительные операторы. Сначала должно быть проведено проме- жуточное назначение элементов указателя переменной. Упражнения к п. 5-3.1 I. Напишите и проверьте машинную программу для алгоритма MATRIX—ADDITION. Проследите за выполнением этого алгоритма для при- веденной в начале пункта матрицы А и матрицы ” 0 0 0 4 0 0 1 0~ 9 0 0 0 0 0 0 3 0 0 0 0 0 0 0 0 в = 0 6 7 8 0 0 0 0 20 0 0 0 0 0 2 0 0 0 0 0 1 0 0 0 5 7 0 0 0 13 0 0 без последних строки и столбца. 2. Составьте на языке ПЛ/1 программы для алгоритма MATRIX—MUL- TIPLICATION. 3. Проследите за выполнением алгоритма, используя матрицу А и при- веденную в упр. 1 матрицу В. 5-3.2. Генерация предметного указателя При написании книги сталкиваются с задачей состав- ления ~ предметного указателя. Основные термины, используемые во всей книге, должны быть представлены в предметном указателе в лексико-графическом порядке. Некоторые термины могут иметь подтермины, которые записываются в лексико-графическом по- 427
рядке сразу после основного термина. За каждым основным тер- мином и подтермином следует набор чисел, указывающих стра- ницы, на которых встречаются соответствующие термины. Ниже представлены алгоритмы, которые обрабатывают набор неупоря- доченных основных терминов и подтерминов и связанные с ними номера страниц в книге и затем печатают требуемый предметный указатель. Данные для обработки должны передаваться алгоритмам в стандартном формате. Для описания этого формата удобно выбрать нотацию БНФ, которая обсуждалась в п. 2-2.2. Элемент (термин) представляет собой строку из любых знаков, за исклю- чением ф|г и В большинстве случаев (термин/ состоит из букв алфавита. Используя (термин), мы можем описать входной формат следую- щим образом: (номер страницы', ::~1|2|3|...|п — 2 | п — 1 | п (мы предполагаем, что в книге п страниц) (основной термин) : : = (термин) (строка основного термина) : = ’основной термин, | (строка основного термина)^} (номер страницы; /Строка подтермина) : : = 41= (термин) | (строка под термина) (g), номер страницы) /основная строка и строка подтермина ? : : = 'основной термин) /строка подтермина) Каждый раз входным для алгоритма генерации предметного ука- зателя является один элемент строка основного термина'/, \ строка подтермииа) или .основная строка и строка подтермина). (Строка подтермина'- всегда соответствует последнему встретившемуся при входе ^основному термину';. Например, могут быть такие входные строки: ОБЪЕКТНЫЙ КОД 483 @ 478 (а, 484 ^ОПТИМИЗАЦИЯ 531 ОБЪЕКТНЫЙ КОД # ГЕНЕРАЦИЯ 549 539 Подтермины ОПТИМИЗАЦИЯ и ГЕНЕРАЦИЯ соответствуют основному термину ОБЪЕКТНЫЙ КОД. Для того чтобы напечатать требуемый предметный указатель, необходимо сначала представить его в памяти компьютера. Каждому основному термину соответствует содержащая че- тыре поля вершина, которую мы обозначим как MAJORNODE (см. рис. 5-3.5). Имя основного термина запоминается в поле TERM. Поле MJLINK является указателем на вершину, содер- жащую следующий основной термин в порядке возрастания основных терминов (в соответствии с последовательностью сопо- ставлений, выполняемых компьютером). MJPAGE является ука- зателем на связанный линейный список номеров страниц, на которых встречается основной термин. SUBLIST является ука- зателем на связанный линейный список, состоящий из вершин, каждая из которых обозначается как SUBNODE. 428
Рис. 5-3.5. Многпсвязная структура для представления предметного указателя В каждой вершине SUBNODE поле SUBTM содержит термин, являющийся дополнительным к основному термину в предше- ствующей вершине MAJORNODE. Поле SUBLINK указывает на следующую вершину SUBNODE. Этот связанный линейный список, содержащий подтермины, упорядочен по возрастанию лексических значений подтерминов. SUBPAGE является указа- телем на связанный линейный список номеров страниц, на которых встречается подтермин. Списки номеров страниц состоят из вершин, обозначаемых PAGENODE. Поле PGNO содержит номер страницы для пред- шествующего основного термина или подтермина. Поле PGLINK является указателем на следующую вершину PAGENODE. Вер- шины упорядочены в соответствии с возрастанием номеров страниц. Используя эти структуры вершины, мы можем представить основной термин ОБЪЕКТНЫЙ КОД и его подтермины в виде, изображенном на рис. 5-3.6. Составим теперь алгоритмы для назначения и инициализации структур вершин MAJORNODE, SUBNODE и PAGENODE. Алгоритм ALLOCATE______MJ. Дан основной термин MA.IORTER1M; требуется сформировать структуру MAJORNODE И присвоить начальные значения: полю TERM — значение MAJOR TERM, всем полям, содержащим указатели. — NULL; Р является локальной переменной типа указателя. 1. [Назначение вершины.] Установить Р -<= A1AJORNODE. 429
2. [Инициализация вершины]. Установить TERM (Р) «- «-MAJORTERM, SUBLIST (Р) MJ PAGE (Р) ч-MJLINK (Р) «-NULL. 3. [Возврат указателя на новую вершину.] Установить ALLOCATE____MJ -t-P и завершить выполнение алгоритма. Алгоритм ALLOCATE____SUB. Дан подтермин SUBTEPM; тре- буется сформировать структуру SUBNODE и присвоить началь- ные значения: полю SUBTM— значение SUBTERM, полям, содержащим указатели, — значение NULL; Р является локаль- ной переменной типа указателя. I. [Назначение вершины.] Установить Р <= SUBNODE. 2. [Инициализация вершины. ] Установить SUBTM (Р) «— «-SUBTERM и SUBPAGE (Р) «-SUBLINK (P)«-NULL. 3. [Возврат указателя на новую вершину. ] Установить ALLOCATE____PG«-P и завершить выполнение алгоритма. Алгоритм ALLOCATE_PG. Дан номер страницы PAGE; требуется назначить структуру PAGENODE и присвоить на- чальные значения ее полям. Р -является локальной переменной типа указателя. 1. [Назначение вершины. ] Установить Р -<= PAGENODE. 2. [Инициализация новой вершины. ] Установить PGNO (Р) «- -«-PAGE и PGLINK (Р) «-NULL. 3. [Возврат указателя на новую вершину.] Установить ALLOCATE____PG «— Р и завершить выполнение алгоритма. Эти три алгоритма вызываются с помощью операторов при- сваивания PTR ALLOCATE_MJ(MAJORTERM), PTR-*- ALLOCATE— SUB(SUBTERM), PTR-<- ALLOCATE—PG (PA GE). Во всех случаях PTR присваивается адрес выделенной вер- шины с установленными начальными значениями полей. Существует вероятность того, что при вводе подтерминов и основных терминов для генерации предметного указателя встре- тятся дублирующие термины. В таких случаях должен быть обновлен существующий список номеров страниц. Это выполняет алгоритм, PAGING, используя который, можно также составить новый список номеров страниц. Алгоритм PAGING. Дан указатель PAGEPTR линейного списка номеров страниц, который может быть пустым. Из строки PAGES к этому списку требуется добавить номера страниц, которых в нем нет. Предполагается, что PAGES имеет следу- ющую форму: PAGES : : =‘(номер страницы’» | PAGES @ (номер страницы) | ” Для индексации разделителей вводится вспомогательная переменная Р. 430
1. [Все номера страниц обработаны?] Если PAGES = ”, то завершить выполнение алгоритма. 2. [Получение номера страницы. ] Установить Р ч- ч-INDEX (PAGES, ’@.’). Если Р = 0, то установить PAGE ч- ч— PAGES и PAGES ч-”; в противном случае PAGE ч- ч-SUB (PAGES, 1, Р — 1), PAGES ч-SUB (PAGES, Р + 1). 3. [Линейный список пуст?] Если PAGEPTR = NULL, то установить PAGEPTR ч- ALLOCATE____PG (PAGE) и перейти к шагу 1. 4. [Должен ли номер PAGE быть первым в списке? ] Если PAGE < PAGENO (PAGEPTR), то присвоить SAVE ч- Ч- ALLOCATE—PG (PAGE), PGLIN К (SAVE) ч- PAGEPTR и перейти к шагу 1. 5. [Получение первой вершины. ] Присвоить SAVE ч- ч-PAGEPTR. 6. [Дублированный иомер страницы? ] Если PAGE = = PAGENO (SAVE), то перейти к шагу 1. 7. [Конец списка?] Если PGLINK (SAVE) = NULL, то присвоить PGLINK (SAVE) ч-ALLOCATE—PG (PAGE) и перейти к шагу 1. 8. [Получение следующей вершины. ] Присвоить LAST ч- ч-SAVE и SAVE ч-PGLINK (SAVE). 9. [Должна ли новая вершина быть впереди вершины SAVE? ] Если PAGE < PGNO (SAVE), то присвоить PGLINK (LAST) ч- -t-ALLOCATE—PG (PG), PGLINK (PGLINK (LAST)) SAVE и перейти к шагу 1; в противном случае перейти к шагу 6. В алгоритме PAGING знак ” означает нулевую строку. В шаге 2 в строке PAGES выделяется первый номер страницы и удаляется из этой строки. В шагах 3—9 просматривается спи- сок, на который указывает PAGEPTR, и вершина, содержащая номер PAGE, помещается в список в соответствии с возрастанием номеров страниц. Однако, если номер PAGE уже добавлен к списку, он игнорируется, и обрабатывается следующий номер страницы. Этот алгоритм может быть вызван с помощью заявки PAGING (PAGEPTR, PAGES). Так как предполагается, что параметры PAGEPTR и PAGES передаются по имени и струк- туры PAGENODE являются глобальными, то в алгоритме, вы- зывающем алгоритм PAGING, действительны изменения пара- метров PAGEPTR, PAGES и также вершинных структур PAGENODE. Нам нужен алгоритм, добавляющий подтермины к стеку под- терминов основного термина. Необходимо также, чтобы для соз- дания или обновления списка номеров страниц для подтермнна вызывался алгоритм PAGING. Алгоритм INSERT. Дан указатель SUBPTR линейного списка вершин подтермина, который может быть пустым. Алгоритм Добавляет к этому списку новый элемент SUBTERM, если он не был ранее добавлен. С помощью алгоритма PAGING номера стра- 431
ниц из строки PAGES добавляются к списку номеров страниц для подтермина. I. (Список подтерминов пуст?] Если SUBPTR = NULL, то присвоить SUBPTR ч-ALLOCATE____SUB (SUBTERM), SAVE ч- ч-SUBPTR и перейти к шагу 8. 2. [Должен ли подтермин быть первым в списке?] Если SUBTEPM с SUBTM(SUBPTR), то присвоить SAVE ч— ч- ALLOCATE— SUB(SUBTERM), SUBLINK(SAVE)4-SUBPTR, SUBPTR ч-SAVE и перейти к шагу 8. 3. (Получение первой вершины. ] Присвоить SAVE ч- ч-SUBPTR. 4. [Подтермин является дубликатом? ] Если SUBTERM — = SUBTM (SAVE), то перейти к шагу 8. 5. (Конец списка?] Если SUBLINK (SAVE) = NULL, то присвоить SUBLINK (SAVE) ч- ALLOCATE—SL’B (SUBTERM), SAVE 4—SUBLINK (SAVE) и перейти к шагу 8. 6. [Получение следующей вершины. ] Присвоить LAST ч- ч-SAVE и SAVE ч— SUBLINK (SAVE). 7. [Должна ли новая вершина быть впереди вершины SAVE?] Если SUBTERM < SUBTM (SAVE), то присвоить SUBLINK (LAST) ч— ALLOCATE— SUB (SUBTERM), LAST ч- 4- SUBLINK (LAST), SUBLINK (LAST) ч-SAVE, SAVE ч- ч-LAST; в противном случае перейти к шагу 4. 8. [Обновление списка номеров страниц. ] Вызвать PAGING (SUBPAGE (SAVE), PAGES) 'и завершить выполнение алгоритма. Этот алгоритм вызывается путем формирования заявки INSERT (SUBPTR, SUBTERM, PAGES). Из-за глобальности структур и в связи с тем, что аргументы передаются путем вызова по ссылке, в вызывающем алгоритме допустимы любые изменения параметров SUBPTR, PAGES и вершинных структур. Теперь можно привести окончательный алгоритм, необходи- мый для формирования структуры предметного указателя. Этот алгоритм вводит данные, имеющие описанный ранее формат, и определяет основной термин, подтермин и соответствующие им номера страниц. Список основных терминов создает алгоритм CONSTRUCT___INDEX. Для построения списков подтерминов и номеров страниц вызываются алгоритмы INSERT и PAGING. Алгоритм CONSTRUCT____INDEX. Этот алгоритм читает (строку основного термина., {строку подтермина^ или (строку основного термина и подтермина) в строку STRING. Строка STRING просматривается с тем, чтобы получить составляющие MAJOR TERM, SUB TERM и PAGES, Строится многосвязная структура, представляющая предметный указатель. FIRSTMJ является указателем на первую вершину MAJOR NODE в линей- ном списке основных терминов; вначале он имеет значение NULL, но в конечном счете указывает на существующую структуру предметного указателя. 432
1 [Чтение строк терминов.! Читать STRING. Если данные исчерпаны, то завершить выполнение алгоритма. 2. [Введение специальных символов. ] Присвоить А 4— INDEX (STRING, ’#’) и В 4-INDEX (STRING, ’(©’). 3. [Получение номеров страниц. ] Если В =А 0, то присвоить PAGES 4-SUB (STRING, В -|- 1) и STRING +-SUB (STRING, 1, В — I); в противном случае установить PAGES 4-”. 4. [Получение подтермииа.] Если А 0, то установить SUBTERM 4- SUB (STRING, А I), STRING 4- -«-SUB (STRING, I, A—1); в противном случае установить SUBTERM 4-”. 5. [Получение основного термина.] Если STRINGS”, то установить MAJOR TERM 4-STR ING; в противном случае пе- рейти к шагу 13. 6. [Список пуст?] Если FIRSTMJ = NULL, то установить FIRSTMJ «-ALLOCATE—MJ (MAJ OR TERM), SAVE 4- 4-FIRSTMJ и перейти к шагу 13. 7. [Должен ли MAJOR TERM быть первым в списке?] Если [MAJORTERM с TERM (FIRSTMJ), то установить SAVE «- ALLOCATE—MJ (MAJORTERM), MJ LINK (SAVE) 4- ч-FIRSTMJ, FIRSTMJ -«-SAVE и перейти к шагу 13. 8. [Получение первой вершины. ] Присвоить SAVE 4- 4-FIRSTMJ. 9. [Основной термин является дубликатом? Если MAJOR TERM •= TERM (SAVE), то перейти к шагу 13. 10. [Конец списка?] Если MJLINK (SAVE) = NULL, то установить MJ LINK (SAVE) -«—ALLOCATE—MJ (MAJORTERM), SAVE 4-MJ LINK (SAVE) и перейти к шагу 13. 11. [Получение следующей вершины.] Установить LAST 4- -«-SAVE и SAVE ч-MJ LINK (SAVE). 12. [Должен ли новый узел быть впереди узла SAVE?] Если MAJOR TERM < TERM (SAVE), то установить MJLINK (LAST)4- 4- ALLOCATE—MJ (MAJOR TERM), LAST 4- MJ LINK (LAST), MJLINK (LAST) 4-SAVE, SAVE 4-LAST и перейти к шагу 13; в противном случае перейти к шагу 9. 13. [Обновление списка подтерминов и номеров страниц.] Если SUBTERM -А”, то вызвать INSERT (SUBLIST (SAVE), SUBTERM, PAGES); в противном случае вызвать PAGING (MJPAGE (SAVE),PAGES). Перейти к шагу I. В шаге 13 алгоритма SAVE является указателем на ту основ- ную вершину MAJOR NODE, у которой должны быть обновлены списки подтерминов и номеров страниц. Если после шага 5 управление будет передано шагу 13, то в силу локальности переменной SAVE во время предыдущей итерации ей уже будет присвоено значение, указывающее на славную вершину MAJOR TERM с последним введенным основ- ным термином. Предполагается, что в первой входной строке одним из элементов должен быть основной термин. 433
С помощью некоторых модификаций может быть по- вышена эффективность алгоритмов PAGING, INSERT и CONSTRUCT—INDEX. Использование различных многосвяз- ных структур также могло бы увеличить эффективность построе- ния предметного указателя. Некоторые модификации указанных алгоритмов предлагается привести в упражнениях к данному пункту. Теперь мы приведем алгоритмы, осуществляющие просмотр предметного указателя, представленного в виде многосвязной структуры, и производящие распечатку его терминов. Примером выходной информации, полученной в результате работы этих алгоритмов с терминами, данными на рис. 5-3.6, является сле- дующий фрагмент указателя: ОБЪЕКТНЫЙ КОД 478, 483, 484 ’ГЕНЕРАЦИЯ 539, 549 ОПТИМИЗАЦИЯ 531 Первый алгоритм производит форматизацию номеров страниц. Алгоритм PAGE__STRING. Дан указатель PAGEPTR на линейный список, связывающий структуры РAGENODE. Тре- буется сформировать строку PAGES, которая состоит из номе- ров страниц, содержащихся в списке. Номера должны быть рас- положены в порядке возрастания слева направо, перед каждым номером должен стоять знак */. 1. (Инициализация строки и вспомогательного указателя.] Установить PAGES ч-” и SAVE ч-PAGEPTP. 2. (Просмотр линейного списка.] Повторять шаг 3 до тех пор, пока SAVE Ф NULL. 3. (Добавление к строке номера страницы. ] Установить PAGES ч-PAGES Г: .j PGNO (SAVE) и SAVE ч- ч-PGLINK (SAVE). 4. [Передача строки.] Установить PAGE—STRING -«—PAGES и завершить выполнение алгоритма. Алгоритм PAGE—STRING вызывается с помощью заявки PAGE___STRING (PAGEPTR). Выходной информацией алгоритма является, строка номеров страниц. Следующий алгоритм исполь- зует PAGE STRING для печати всего предметного указателя. Алгоритм PRINT—INDEX. Дай указатель FIRSTMJ на многосвязпую структуру, представляющую предметный указа- тель. Требуется отпечатать предметный указатель. 1. (Инициализация.] Установить SAVE-«-FIRSTMJ. 2. (Просмотр основных терминов. ] Повторять шаги 3—5 до тех пор, пока SAVE Ф NULL. Завершить выполнение алго- ритма. 3. [Распечатка основного термина. ] Отпечатать TERM (SAVE) О PAGE STRING (MJPAGE (SAVE)). 4. (Распечатка подтермина.] Установить SUBSAVE ч- ч-SUBLIST (SAVE). Повторять до тех пор, пока SUBSAVE 434
#= NULL: PRINT ’ ’ О SUBTM (SUBSAVE) q PAGE STRI NG (SUBPAGE (SUBSAVE)) и установить SUBSAVF SUBLINK (SUBSAVE). 5. [Обновление указателя на структуры MAJORNODE 1 Устя довить SAVE ч-MJLINK (SAVE). | Выполняется этот алгоритм просто. Переменная SAVE исполь- зуется для просмотра вершин MAJORNODE, a SUBSAVE________ для просмотра подчиненных вершин SUBNODE в списке подтер- мииов. Упражнения к п. 5-3.2 1. Термины из предметного указателя часто приводятся с диапазо- ном номеров страниц, например, 37- 41, 29—70, 23- 24 и т. д. Измените алго- ритм PAGING таким образом, чтобы он воспринимал номера страниц,'записан- ные в следующей форме: (список номеров страниц) : : = ''номер страницы) — (номер страницы) Для данного списка номеров страниц номер страницы, если он лежит в задан- ном диапазоне номеров, считается дубликатом. Например, если номера страниц 37—41 уже имеются в структуре предметного указателя, страница 40 является дубликатом. Измените так алгоритм PAGE—STRING, чтобы находящиеся в списке два или более последовательных номеров страницы распечатывались в виде диапазона номеров 2. Алгоритм CONSTRUCT—INDEX становится более эффективным, если основные термины организованы в виде бинарного дерева. Модифицируйте дан- ный алгоритм, используя указанный факт- Измелите также алгоритм PRINT- INDEX таким образом, чтобы он мог просматривать бинарное дерево основных терминов. 3. Для алгоритмов данного пункта предложите и испол!>зуйте средство контроля ошибок. В частности, как может быть проверена в них правильность входных данных? 5-4. ГРАФЫ И их ПРЕДСТАВЛЕНИЕ В п. 5-1.1 были даны основы теории графов и введены такие понятия, как граф, вершина, ребро, путь, цикл и т. д. Данный параграф посвящен дальнейшему рассмотрению графов и их применениям. Представление графа в виде рисунка в некоторой степени удобно. Однако такое представление невозможно, если число вершин и ребер в графе велико. В п. 5-4.1 описывается альтер- нативный метод представления графов, основанный на исполь- зовании матриц. Этот метод имеет несколько достоинств. Ма- трицы и, следовательно, представляемые ими графы легко хранить и обрабатывать в ЭВМ. Для получения путей, циклов и других характеристик графа могут быть использованы некоторые хорошо известные операции матричной алгебры. Второй пункт посвящен представлению графа с помощью спи- сковой структуры. Для облегчения обработки структур, анало- гичных тем, которые будут описаны, создано неск' 1ько языков программирования. Необходимость в обработке списками обус- ловлена высокой стоимостью основной машинной памяти и не- предсказуемой природой требований к памяти программ и данных. 435
Показано, как списковая структура может быть использована для представления ориентированного графа. В остальных пунктах дается краткое введение в теорию пред- ставления обобщенных графовых структур. Это представление основано не только на учете природы данных, но также и на учете специфики операций, которые будут выполняться над этими данными. В п. 5-5 обсуждается несколько приложений графов и их специфических представлений. 5-4.1. Матричное представление графов Пусть дан простой орграф G = (V, Е). Необходимо задать некоторый вид упорядоченности вершин графа, понимаемой в том смысле, что некоторая конкретная вершина названа первой вершиной, другая — второй и т. д. От упорядочения вершин зависит матричное представление графа G. Пусть G — (V, Е) простой орграф, в котором V = {vi, v2, Vn} и вершины предполагаются упорядоченными от vr до Vn. Матрица А размера п < п, элементы ап- которой задаются выражением а.. = ( если <VJ’ Ы С Е’ lj [ 0, в противном случае, называется матрицей смежности графа G. Каждый элемент матрицы смежности либо 0, либо 1. Любая матрица, элементы которой либо 0, либо 1, называется битовой матрицей или булевой матрицей. Заметим, что i-я строка в ма- трице смежности определяется ребрами, начинающимися в вер- шине у,. Число элементов в i-n строке, значение которых 1, равно полустепени исхода вершины vt. Аналогично число элементов, значение которых 1, в некотором столбце, допустим в j-м, равно полустепени захода вершины Vj. Матрица смежности полностью определяет простой орграф. Матрица смежности данного орграфа G = (v, Е) зависит от упорядочения элементов V. Для различных упорядочений элемен- тов V получаются различные матрицы смежности одного и того же графа. Тем не менее любая матрица смежности графа G может быть получена из другой матрицы смежности этого же графа путем перестановки некоторых строк и соответствующих им столбцов. Можно игнорировать отсутствие произвольности, внесенное в ма- трицу смежности вследствие упорядочения элементов V. Поэтому любая матрица смежности графа будет удовлетворять поставлен- ной цели. В самом деле, если два орграфа таковы, что матрица смежности одного может быть получена из матрицы смежности другого путем перестановки некоторых строк и соответствующих столбцов, то эти орграфы эквивалентны. 436
Рис. 5-4.1. Орграф и его матрица смежности В качестве примера рассмотрим орграф, изображенный на рис. 5-4.1, а, в котором задан следующий порядок вершин: vt, v2, v3 и v4. Матрица смежности этого орграфа представлена на рис. 5-4.1, б. Понятие матричного представления можно расширить иа муль- тиграфы и взвешенные графы. Для простых неориентированных графов эта матрица смежности симметрична. В случае мульти- графа или взвешенного графа полагается ац = Wq, где wj — означает либо кратность, либо вес ребра (уА, vj). Если (vb Vj) Е. то Wjj = 0. Для нулевого графа, который содержит только п вершин, но не содержит ребер, все элементы матрицы смежности нулевые, т. е. матрица смежности является нулевой матрицей. Если в каж- дой вершине графа имеются петли и пет других вершин, матрица смежности идентична единичной матрице. Рассмотрим теперь степени матрицы смежности. Единица в i-й строке и j-м столбце исходной матрицы А указывает на нали- чие ребра (уь Vj), т. е. на путь длины 1 из vf в Vj. Обозначим эле- менты А2 через af/\ Тогда Дц’ } «iktZfcj. k=l Для каждого фиксированного к условие alk akj = I выпол- няется тогда и только тогда, когда оба элемента aik и ак)- равны 1, т- е. в графе имеются ребра (vls vk) и (vk, Vj). Для каждого такого к мы получаем единичный вклад в сумму. Наличие ребер (vt, vr) и (vr, vj означает, что существует путь из Vj в vk длины 2. Тем самым а^’ равно числу различных путей длины 2 из Vi в Vj. Ана- логично, диагональный элемент Зц2) указывает число циклов Длины 2 в вершине v, для i = 1,2, ..., п. С помощью подобной аргументации мы можем показать, что элемент в i-й строке и j-м столбце матрицы А3 задает число путей Длины 3 из Vj в у,. В общем случае может быть доказано следующее Утверждение. 437
Пусть А — матрица смежности орграфа G. Элемент на пере- сечении i-й строки и j-ro столбца An (n> 1) равен числу путей длины п из i-й вершины в j-ю вершину. Матрицы А2, А® и А4 для графа, изображенного па рис. 5-4.1, имеют следующий вид: из v3 в v2, откуда следует запись числа 2 на пересечении третьей строки и второго столбца А2. Аналогично имеется три пути длины 4 из vs в v2, что приводит к соответствующей записи в А4. Дан простой орграф G == (V, Е); пусть Vj и Vj являются лю- быми двумя вершинами в G. По матрице смежности А мы можем непосредственно определить, существует ли в G ребро из VjB vj. Аналогично по матрице Аг, где г — некоторое положительное целое число, мы можем установить число путей длины г из v( в Vj. Если сложить матрицы А, А2, ..., Аг, то, получив матрицу Д = А + А2+. 4-Аг, по ней можно определить число путей из v4 в Vj длины, меньшей или равной г. Если бы требовалось определить, достижима ли вершина vj из v,, было бы необходимо исследовать, существует ли путь любой длины из V, в Vj. Чтобы решить этот вопрос с помощью матрицы смежности, мы должны были бы рассмотреть все возмож- ные Аг для г = 1, 2, ... Как будет показано ниже, этот метод не является рациональным. Легко показать, что в простом орграфе с п вершинами длина элементарного пути или цикла не превышает п. Элементарный путь между двумя любыми вершинами можно найти, отбросив некоторые участки пути между этими вершинами, являющиеся циклами. Аналогично, мы всегда можем найти элементарный цикл в заданном цикле Если мы хотим определить, существует ли путь из Vj в Vj, нам необходимо проверить, имеются ли элемен- тарные пути длины, меньшей или равной п — 1. В том случае, когда Vj = Vj и шаг является циклом, нам необходимо проверить все возможные элементарные циклы длины, меньшей или рав- ной п. Такие циклы и пути легко определить с помощью матрицы Вп, где В„ = А + А* + А’+. . + Л". Элемент в i-й строке и j-м столбце Вп показывает число суще- ствующих путей из Vj в vj длины, меньшей или равной п. Если этот элемент не нулевой, то очевидно, что вершина v достижима из Vj. Естественно, для определения достижимости нам доста- точно установить только существование пути, а не число путей 438
между любыми двумя вершинами, Во всяком случае матрица Вп содержит всю требуемую информацию о достижимости любой вершины графа из любой другой вершины. Пусть G = (V, Е) — простой орграф, содержащий п вершин, которые предполагаются упорядоченными. Матрица Р размера п в, элементы которой задаются выражением _ JI, если существует путь из v, в vj; P'j — (О, в противном случае, называется путевой матрицей {матрицей достижимости) графа G. Заметим, что путевая матрица только показывает, имеется или отсутствует по крайней мере одии путь между парой точек, а также имеется или отсутствует цикл в любой вершине. Вместе с тем она ие определяет все пути, которые могут существовать. В этом смысле путевая матрица не дает столь полной информации о графе, как матрица смежности. Путевая матрица важна сама по себе. Путевую матрицу можно получить из матрицы Ви, положив Рц = 1, если элемент на пересечении i-й строки и j-ro столбца Вп не нулевой, и рц = 0 в противном случае. Применим этот метод вычисления путевой матрицы для графа, изображенного на рис. 5-4.1. Матрица смежности А и ее степени А2, А3 и А4 уже полу- чены. Таким образом, мы имеем В,, н путевую матрицу Р: 3 5 0 3 3 3 0 2 6 8 0 5 2 3 0 1 110 1 110 1 110 1 110 1 Следует отметить, что если нас интересуют сведения о дости- жимости одной вершины из другой, достаточно вычислить Ви_г, поскольку путь длины п не может быть элементарным. Единствен- ная разница между Р, вычисляемой по Вп_г, и Р, вычисляемой по Вп, состоит в диагональных элементах. При исследовании дости- жимости каждая вершина полагается достижимой из самой себя. Некоторые авторы предлагают вычислять путевую матрицу по Bn_i> другие — по Вп. Метод определения путевой матрицы Р графа сначала путем вычисления А, А2, ..., Ап, а затем В(1 — громоздок. Опишем Другой метод, основанный на аналогичных идеях, но более эф- фективный. Заметим, что нас не интересует число путей любой конкретной длины из вершины, скажем vf, в вершину Vj. Эта информация, получаемая в процессе вычисления степеней А, Далее игнорируется. Для того чтобы сократить объем вычислений, можно отказаться от получения указанной информации и исполь- зовать в вычислениях просто реализуемые булевы матричные операции, которые мы сейчас определим. 439
В табл. 5-4.1 представлены операторы Д и \/ над В. Таблица 5-4.1 Значения операторов Д и '/ Л О I ООО 1 0 1 Булева сумма и булево произведение двух любых булевых матриц А и В размера n '< п записываются соответственно как А V В и А Д В. Они также являются булевыми матрицами, ска- жем С и D. Элементы С и D задаются соотношениями п Cjj = 3jj V bjj, = V (3ik A bkj) для всех i, j = 1,2,..., n. Заметим, что элемент дц легко получается путем просмотра i-й строки А слева направо и одновременно j-ro столбца В сверху вниз. Если k-й элемент в строке А и k-й элемент в столбце В равны 1 для какого-либо к, то djj — 1. В противном случае d,j ~ 0. Матрица смежности, так же как и путевая матрица, является булевой матрицей. Запишем, что А Д А = А(2), А Д А(г-1) = ~ А<г) для любых г = 2, 3, ... Единственная разница между А2 и А(2) заключается в том, что А(2) является булевой матрицей и элемент на пересечении i-й строки и j-го столбца А(2) равен 1 в том случае, когда существует по крайней мере один путь длины 2 из Vj в Vj. Аналогичное положение имеет место для А3 и А<3) и в общем случае для Аг и А(г) при любом целом положитель- ном г. Из этих рассуждений ясно, что путевая матрица Р задается выражением Р = А V А'г> V А<з> V • • V A(n) = V А<к). к=1 Если взять сумму от k = 1 до к — п — I, то получится матрица, которая может отличаться (в общем случае) от Р только диагональ- ными элементами. Для графа, изображенного на рис. 5-4.1, имеем ”1 1 0 о” ”110 1 1 0 0 0 0 1 0 1 440
Д(4) = 10 1| 1 0 1 1 0 1 1 о о 1 1 А V А‘"> V А(3) = О 1 О 1 О 1 О 1 = A v А<2) V А(8) V А(4> = Р. 1 1 Данный метод получения путевой матрицы простого орграфа может быть легко реализован на ЭВМ путем использования алгоритма WARSHALL, предложенного Уоршаллом. Алгоритм WARSHALL. Задана матрица смежности А. Путе- вая матрица Р вычисляется^в результате следующих шагов. 1. (Инициализация.] Установить Р А. 2. [Выполнение прохода.] Повторять шаги 3 и 4 при к = = 1, 2, ..., п. 3. [Обработка строк.] Повторять шаг 4 при i = 1. 2, п. 4. [Обработка столбцов.] Повторять при j = 1, 2, ..., п: установить рн рц V (pik A pkj)- 5. [Конец.] Завершить выполнение алгоритма. Для того чтобы убедиться, что данный алгоритм позволяет вычислить требуемую матрицу, сначала заметим, что в шаге 1 матрица, в которой рц = 1, формируется тогда, когда имеется путь длины 1 из Vj. в Vj. Предположим, что для фиксированного к промежуточная матрица Р, получаемая в шагах 3 и 4 алгоритма, такова, что элемент на пересечении i-й строки и j-ro столбца ра- вен 1 тогда и только тогда, когда либо имеется путь из v, в vj через вершины vb v2, ..., Vk, либо имеется ребро из Vj в vj. Теперь для модифицированного значения к мы получаем, что pij ~ 1, если либо pij = 1 в предыдущем шаге, либо существует путь из Vj в Vj, проходящий через Vk+i- Это означает, что рц — = I тогда и только тогда, когда имеется путь из Vj в Vj через вер- шины vx, v2, ---, Vk+1, либо имеется ребро из vs в Vj. На рис. 5-4.2 приведена программа этого алгоритма на языке ПЛ/1. Заметим, что в ней используются битовые строки, а также входная матрица смежности графа, изображенного на рис. 5-4.1. Алгоритм WARSHALL может быть модифицирован с целью получения матрицы, содержащей длины кратчайших путей между вершинами. Для этого предположим, что А —матрица смежности графа. Заменим все нулевые элементы А на бесконечность, которая указывает на отсутствие ребра между вершинами. Требуемая матрица длин минимальных путей формируется нижеследующим алгоритмом. 441
MARSHAL Li /« GIVEN THE ADJACENCY MATRIX A, PRODUCE THE PATH MATRIX P */ t DECLARE (A, P)(*,*) BIT(D> (N, K, I, J) BINARY fixed; P = a: do k = i to n; end; ENO; END; END MARSHALL? Рис. 5-4.2. Программа алгоритма WARSHALL на языке ПЛ/1 Алгоритм MINIMAL. Задана матрица смежности, в которой нулевые элементы заменены на бесконечность или на некоторое очень большое число, которое в данной матрице обозначим через В. Матрица С, формируемая путем описанных ниже шагов, содержит минимальные длины путей между вершинами. Функция MIN осуществляет выбор алгебраического минимума из двух своих аргументов. I. [Инициализация.] Установить С*- В. 2. [Выполнение прохода.] Повторять шаги 3 и 4 при к = 1, 2, ..., п. 3. [Обработка строк.] Повторять шаг 4 при i = 1. 2, .... п. 4. [Обработка столбцов.] Повторять при j = 1, 2, .... п: установить сц MIN (сч, с,к сщ). 5. [Конец.] Завершить выполнение алгоритма. Здесь знак плюс в шаге 4 означает обычное сложение целых чисел. На практике часто интересуются не только длиной мини- мального пути между двумя вершинами, но и самим путем. Ал- горитм нахождения такого пути может быть легко получен про- стой модификацией предыдущего алгоритма, поэтому мы оставляем его в качестве самостоятельного упражнения. Мы закончим этот пункт, показав, как путевая матрица ор- графа может быть использована для решения вопроса, являются ли определенные процедуры в программе рекурсивными. В некоторых языках программирования программист Сможет задать рекурсивность процедуры в явной форме. Например, в ПЛ/1 может быть указан описатель RECURSIVE. В других языках, в которых не заложено подобного указания, для того чтобы определить, какие из процедур являются рекурсивными, можно использовать концепции теории графов. Рекурсивная про- цедура необязательно вызывает себя непосредственно. Если про- цедура рх вызывает р2, процедура р2 вызывает р3, ..., процедура рп_х вызывает рп и процедура рп вызывает ръ то процедура рх является рекурсивной. Пусть Р = {рх, р3, р3, ..., рп] —набор процедур в програм- ме. Построим ориентированный граф, состоящий из вершин, соот- 442
a) Pz Рз Py 0 0 10 0 10 0 0 0 0 1 110 1 0 0 0 0 6) Рис- 5-4.3. Взаимные вызовы процедур Plt Р2, Ps, Р4 и Р6 ветствующих элементам Р, ребро из pi в pj, в котором проводится в том случае, когда процедура pj вызывает pj. На рис. 5-4.3 изображены ориентированный граф и его матрица смежности, соответствующие вызовам, производимым набором процедур Р = {рт, рЕ, ....-р6). Процедура pi является рекурсивной, если в графе имеется цикл, содержащий р,. Такие циклы могут быть обнаружены путем анализа диагональных элементов путевой матрицы Q графа. Так, Р; является рекурсивной процедурой, если qH = 1. Ма- трица Q может быть построена с помощью алгоритма WARSHALL. В приведенном выше примере она имеет вид 1 1 1 1 1 1 1 ООО 1 1 1 ООО 1 1 1 1 о откуда видно, что процедуры ра, р2 и р4 являются рекурсивными. Упражнения к п. 5-4.1 1. Постройте матрицу смежности А орграфа, изображенного на рнс. 5-4.4. Найдите элементарные пути длины 1и 2 из Vj в v4. Проверьте резуль- таты путем вычисления А8, А3 и А4. 2. Покажите, что для любой булевой мат- Yf рицы А размера п ;< п имеет место (I + Л)00 = (I ц- Л) Л П+А) = I + А + Л<2), где I — единичная матрица размера- n х. п 2 Г --------------- И = А Д А. Покажите также, что для \ \ любых целых положитепьных г имеет место \ \ (1 + Л)(г)-1+Л + А<2) + +А(Г). 3. Используя результат, полученный в задаче 2, покажите, что путевая матрица Рис. 5-4.4. Пример ориентиро- ванного графа 443
простого орграфа задается выражением Р = О -[- А)(п>, где А — мат- рица смежности орграфа, который имеет п вершин. 4. Матрица расстояний простого орграфа G — (V, Е), матрица смежности которого обозначается через А, задается выражениями djj = оо, если (vi, vj) ф Е; djj = 0 для всех j — 1.2,—, п; du=k, где к — является наименьшим целым, для которого 0. Определите матрицу расстояний орграфа, изображенного на рис. 5-4.4. Что означает djj = I? 5. Модифицируйте алгоритм MINIMAL для вычисления всех минимальных путей. 6. Предположим, что в орграфе все ребра помечены. Напишите программу, которая выявит все минимальные пути между каждой парой вершин. 5-4.2. Списковые структуры В данном пункте рассмотрены способы представления структур, называемых списковыми структурами. Манипулиро- вание такими структурами известно как обработка списков. Будет показано, что списковые структуры можно использовать для представления ориентированных графов. Развитие теории структур, методов и языков программирова- ния обработки списков вызвано в основном требованиями к част- ной области применения ЭВМ, а именно — к обработке символов. Эта область включает такие проблемы, как искусственный интел- лект, алгебраические преобразования, обработка текста и теория графов. Напомним, что все эти проблемы имеют следующие общие свойства. 1. Непредсказуемые требования памяти. Точный суммарный объем памяти под данные для программ из этой области часто за- висит от конкретных значений обрабатываемых данных, и, следо- вательно, это требование не может быть легко сформулировано при написании программы. 2. Высокая интенсивность обработки хранимых данных. Про- граммы из этой области обычно предъявляют многократные тре- бования выполнения таких операций, как вставка и исключение элементов в используемых структурах данных. Ввиду высокой стоимости быстрой памяти ЭВМ и машинного времени требуемая структура данных должна использовать пространство памяти так, чтобы обеспечить максимальные воз- можности для решаемой задачи, и, кроме того, она должна быть снабжена эффективными (в смысле использования машинного времени) обрабатывающими алгоритмами. Рассматриваемые спи- сковые структуры удовлетворяют обоим этим критериям. В контексте обработки списков мы определяем список как любую ограниченную (от нуля и более) последовательность ато- мов и списков, где в качестве атома берется любой объект (на- 444
пример, строка символов), который при обработке отличается от списка тем, что он структурно неделим. Если мы заключим списки в круглые скобки, а элементы списков разделим запятыми, то в качестве списков можно рассматривать следующие последо- вательности: (а, (Ь, с, d), е, (f, g)) ( ) ((a)) Первый список (рнс. 5-4.5, а) содержит четыре элемента, а именно, атом а, список (b, с, d), содержащий атомы b, с, d, атом е и список (f, g), элементами которого являются атомы f и g. Второй список (рис. 5-4.5, б) не содержит элементов, тем не менее нулевой список, в соответствии с нашим определением, все еще является действительным списком. Третий список (рис. 5-4.5, в) состоит из одного элемента, списка (а), который, в свою очередь, содержит атом а. Другой способ представления, часто используемый для иллю- страции списков, аналогичен способу представления, применя- емому при связанном изображении деревьев. Каждый элемент списка обозначается прямоугольником; стрелки, или указатели, показывают, являются ли прямоугольники элементами одного и того же списка или элементами подсписка. Каждый прямоуголь- ник разделен на две части. Вторая часть содержит либо указатель на следующий элемент в том же списке, либо косую черту, соот- ветствующую нулевому указателю и отмечающую конец списка. «Горизонтальный» указатель задает отношение физического со- седства в списке. Первая часть содержит либо имя атомарного элемента, либо указатель на списковое представление элемента списка. Для неатомарного элемента указатель определяет «вер- тикальное», или иерархическое положение в списке. На рис. 5-4.6, а—в, дано представление с помощью прямо- угольников и стрелок примеров списков, приведенных выше. Символ => означает корень или первый элемент в списке. ₽ис. 5-4.5. 445
Рис. 5-4.6. Представление списковых структур в памяти Нулевой указатель Списковые структуры описываются следующими тремя харак- теристиками . 1. Порядок. Над элементами списка задано транзитивное от- ношение, определяемое последовательностью, в которой элементы появляются внутри списка. В списке (х, у, z) атом х предшествует у и у предшествует г. При этом подразумевается, что х предше- ствует z. Данный список не эквивалентен списку (у, z, х). При представлении списков прямоугольниками и стрелками порядок определяется горизонтальными .стрелками. Горизонталь- ные стрелки истолковываются следующим образом: элемент, из которого исходит стрелка, предшествует элементу, на который она указывает. 2. Глубина. Это максимальный уровень, приписываемый эле- ментам внутри списка или виутри любого подсписка в списке. Уровень элемента предписывается сетью списков внутри списка, т. е. числом пар круглых скобок, окаймляющих элемент. В списке, изображенном на рис. 5-4.5, а, элементы а и е находятся на уров- не 1, в то время как оставшиеся элементы b, с, d, f и g имеют уровень 2. Глубина входного списка равна 2. При представлении списков прямоугольниками и стрелками концепции глубины и уровня облегчаются для понимания, если каждому атомарному или списковому элементу приписать неко- торое число I. Значение I для элемента х, обозначаемое как I (х), является числом вертикальных стрелок, которые необходимо пройти для того, чтобы достичь данный элемент из первого эле- мента списка. На рис. 5-4.6, а I (а) — О, I (b) = 1 и т. д. В об- щем случае уровень любого элемента задается соотношением I (х) + 1, а глубина списка является максимальным значением уровня среди уровней всех атомов списка.. 3. Длина. Это число элементов уровня 1 в списке. Например, длина списка (a (b, с), d) равна 3. Рассмотрим в качестве примера общий случай списковой струк- туры, которая редко распознается как таковая. В конструкции ан- глийского предложения содержатся подлежащее, сказуемое и дополнение. Любое такое предложение можно интерпретировать как трехэлементный список, элементами которого могут быть атомы 446
(одиночные слова) или списки (фразы из нескольких слов). В ка- честве примеров могут служить следующие предложения и соот- ветствующие им представления в виде списков: Man bites dog == (Man, bites, dog) (человек) (кусает) (собаку) The man bites the dog = ((The, man), bites, (the, dog)) The big man is biting the smail dog ((The, big, man), (is, biting), (the, small, dog)) В последнем примере в подлежащем и дополнении могут быть далее выделены существительные и получено следующее представ- ление: (((The, big), (man)), (is, biting), ((the, small), (dog))). На рис. 5'4.7 это предложение представлено в виде прямо- угольников и стрелок. Рассматриваемый список имеет следующие характеристики: длина равна 3; < глубина равна 3; уровень элемента 'man' равен 3, уровень 'is' равен 2 и т. д. Между списковыми структурами и орграфами имеется прямая связь. В частности, список является ориентированным графом с одной исходной вершиной (вершина, полустепень захода которой равна 0), соответствующей входу в список. Каждая вершина не- посредственно связана с исходной вершиной, соответствующей элементу списка, —либо с вершиной с полустепенью исхода 0 (для атомов), либо с вершиной, имеющей исходящие ветви (для элементов, которые сами являются списками). Каждая вершина, за исключением исходной, имеет полустепень захода 1. Ребра, исходящие из вершин, можно рассматривать как упорядоченные списки. Это означает, что мы различаем первое ребро, второе ребро ит. д., которые соответствуют упорядоченным списковым элемен- там для первого элемента, второго элемента и т. д. Более того, в графе отсутствуют циклы. Графы некоторых списков представ- лены на рис. 5-4.5. Данное ранее определение списка в равной степени может быть применено к деревьям. На самом деле списки являются расшире- ниями деревьев, в которых список в качестве элемента может 447
Рис. 5-4.8. Граф списковой структуры М = (a, b, М) содержать сам себя, в то время как у деревьев это свойство отсутствует. Следовательно, имеются списки, кото- рые не могут быть представлены как деревья, однако каждое дерево может быть представлено в виде некоторого списка. Списки могут иметь виутренне- рекурсивную ячеечную структуру, которой не обладают деревья. Таким образом, есть ряд списков, имеющих вполне определенное представление в нашей системе обозначений, включа- ющей круглые скобки и запятые, но соответствующих бесконечным графам. В качестве иллюстрации на рис. 5-4.8 показан граф списковой структуры М = (a,' b, М). Рассмотрим представление списко- вых структур в памяти ЭВМ. Анали- зируя критерии, которым такие структуры должны удовле- творять, можно сделать вывод, что связанное представление в памяти безусловно более эффективно. Оно обеспечивает при не- обходимости динамическое размещение вершин, легкость обработ- ки и способность разделять подсписки. Список в общем случае задается некоторой вариацией бинарного представления натураль- ных деревьев, точнее говоря, путем использования двух полей связи — одного для указания членства внутри списка и другого — для указания ячейки. Типичная структура элемента («узла») списка изображена на рис. 5-4.9. Поле DPTR является связкой, указывающей на первый элемент в подсписке; поле RPTR указывает на следующий эле- мент в данном списке, a INFO содержит информацию о списке (например, буквенное имя). Атомарный узел индицируется пустым полем RPTR, а поле INFO содержит информацию об атоме. Данный формат узла удобен для списков, атомарная информа- ция которых занимает небольшой объем памяти. В этом случае теряется незначительный объем памяти в узлах списка, для кото- рых не требуется большое поле INFO. В более общем случае для атомарной информации необходим относительно большой объем памяти. Наиболее распространенный в данной ситуации формат структуры узла представлен на рис. 5-4.10, где RPTR выполняет те же самые функции, что и в предыдущем формате. Поле DPTR указывает либо на первый элемент подсписка, входящего в узел списка, либо на информацию, связанную с атомом в атомарном узле. Предполагается, что ато- марная информация содержит некоторую уникальную характери- стику, как, например, поле маркера, позволяющее различать атомарные и списковые узлы. 448
DPTR INFO RPTR DPTR RPTR рис. 5-4.8. Формат узла списка Рис. 5-4.10. Второй вариант формата узла списка Этот формат дает возможность сделать все узлы внутри спи- сковой структуры одинаковыми по размеру и позволяет избежать излишних потерь информационного пространства в узлах. С уче- том этого обстоятельства, а также в целях графической и алгори- тмической простоты далее в данном пункте используется именно этот, второй формат. Атомарные узлы иа рисунках будут изобра- жаться в том виде, как это сделано на рис. 5-4.11, где 'х' в поле DPTR означает указатель на информацию, связанную с атомом х. В качестве примера на рис. 5-4.12 представлен список (a, (b, с), d). Вспомним рис. 5-4.8, на котором дано графическое представле- ние рекурсивного списка, соответствующего бесконечному пов- торению вершин графа. Оно не имеет практического интереса в вычислительных приложениях. Более целесообразно использовать список М = (a, b, М), изображенный на рис. 5-4.13. Данное представление рекурсивного списка без сомнения эффективно; од- нако большее внимание должно быть уделено тому, как избежать бесконечных циклов в программах, манипулирующих такими структурами. Атрибут сохранения пространственных отношений в списках не ограничивается только рекурсивными структурами. Двойные списки позволяют отобразить более общую ситуацию, часто встречающуюся на практике. Примером может служить список (у, (z, w), (2, (z, w) а)), представленный на рис. 5-4.14. Рассмотрим, какие требуются изменения для исключения эле- мента z из подсписка (z, w), с тем чтобы получить список (у, w), (2, (w), а)). Необходимо найти и изменить оба указателя на эле- мент z. Но это не лучший метод, поскольку поиск по обратному пуТи, необходимый для нахождения указателей, требует исклю- чительно много времени. Поскольку операция удаления первого элемента списка является достаточно общей, желательно создать структуру памяти, на которой такие манипуляции можно осуще- ствлять более эффективно. В этом пересмотренном представлении с каждым списком свя- зывается головной узел списка. Если использовать тот же самый Рис. 5-4.11. Фор- Узда атомаРного 15 Рис. Б-4.12. Представление в па- мяти списка (a, (b, с), d) Рис. 5-4.13. Представление в памяти рекурсивного списка Трамбле Ж-. Соренсон П- 449
Рис. 5-4.14. Представление в памяти списка (у, (z, w), (2, (z, w) a)) формат узлов, что и ранее, то головной узел будет содержать указатель RPTR на первый элемент списка, a DPTR будет уста- новлен в NULL, чтобы показать, что этот узел является головным. Пустой список индицируется путем DPTR = RPTR = NULL. В таком представлении любое указание на список сначала исхо- дит из головного узла этого списка, а не из его первого элемента. При использовании этого представления для удаления первого элемента списка требуется только изменить поле RPTR в его головном узле. Если применить представление с головным узлом*, то вместо списка иа рис. 5-4.14 получим список, показанный на рис. 5-4.15. Процесс разрушения списка более сложен, нежели процесс разрушения дерева. Список не может быть автоматически воз- вращен в область имеющейся в распоряжении памяти \ так как на список, который должен быть разрушен, могут ссылаться другие списки. Эта тема детально обсуждается в п. 5-6. В оставшейся части данного пункта рассмотрим операции, яв- ляющиеся уникальными для списков, а именно, операции рас- щепления списка и конкатенации. Обычно рассматриваемый список содержит две логические еди- ницы: HEAD (голову) и TAIL (хвост). Рассмотрим список ((а, Ь), с, (d, е)), изображенный на рис. 5-4.16, в котором головой 1 Имеется в виду перенос списка во внешнюю память и ликвидация его в основной. — Прим. пер. Рис. 5-4.15. Представление в памяти списка (у, (z, w), (2, (z, w), а)) с использова- нием головного узла списка 450
рис. В-4.16. Представление в памяти списка ((a, b), с, (d, е)) является список (а, Ь), а хвостом —(с, (d, е)). На рис. 5-4.17 дано графическое представление головы и хвоста. Отметим в ре- зультирующем списке действие операции расщепления на уровни элементов. Уровень элементов головы списка уменьшился на 1, в то время как уровень элементов хвоста не изменился. Ниже приведены алгоритмы, которые выдают указатели на голову и хвост списка при применении заданной списковой Струк- туры, использующей головные узлы. Алгоритм HEAD. Задан указатель ROOT головного узла списковой структуры. Данный алгоритм выдает указатель на узел списка. Если узел является атомом, указатель идентифицирует описатель этого атома; Р является переменной типа указатель. 1. [Проверка корректности списковой структуры.] Если ROOT = NULL или DPTR (ROOT) =/= NULL, то печатать. 'НЕКОРРЕКТНЫЙ СПИСОК' и завершить выполнение ал- горитма. 2. [Список нулевой?] Установить Р RPTR (ROOT). Если Р = NULL, то печатать 'НУЛЕВОЙ СПИСОК' и завершить вы- полнение алгоритма. 3. [Формирование указателя на HEAD. ] Установить HEAD DPTR (Р) и завершить выполнение алгоритма. Алгоритм TAIL. Задан ROOT, указатель головного узла списковой структуры. Данный алгоритм формирует головной узел списка и выдает его адрес. Поле RPTR этого головного узла указывает на хвост данного списка; Р является временной пере- менной типа указателя. Имя структуры узла —NODE. HEAD Ц/] -Н а | >| Ь |/| Рис. 5-4.17. представление головы и хвоста списка ((a, b), с, (d, е)) 15* 451
1. [Проверка корректности списковой структуры.] Если ROOT = NULL или DPTR (ROOT) NULL, то печатать 'НЕКОРРЕКТНЫЙ СПИСОК' и завершить выполнение алго- ритма. 2. [Список нулевой?! Установить Р RPTR (ROOT). Если Р == NULL, то печатать 'НУЛЕВОЙ СПИСОК' и завершить вы- полнение алгоритма. 3. [Построение и выдача головы списка. ] Установить TAIL <= <= NODE, DPTR (TAIL) NULL, RPTR (TAIL) - RPTR (P) и завершить выполнение алгоритма. Операция, дающая результат, противоположный операциям HEAD и TAIL, является операцией формирования. Если даны два списка — некоторый атом и некоторый список, то эта операция позволяет построить список, используя первый аргумент в ка- честве головы создаваемого списка, а второй - в качестве его хвоста. Например, если А является списком (q), а В —спи- ском. (d, е), то результатом операции формирования —конструк- том А и В —является список ((q), d, е). Другой пример: если А является атомом s, а В — списком (t, и), то конструктом А и В является список (s, I, ц). Отметим, что уровень каждого элемента в первом аргументе увеличивается в результате операции формирования на 1, в то время как уровень элементов второго аргумента остается неизмен- ным. Операция формирования некорректна, если второй аргумент не является списком. Тем не менее второй аргумент может быть пулевым списком. Например, если А —это атом х, а В —спи- сок ( ), то конструктом А и В является (х). Алгоритм CONSTRUCT. Заданы указатель А головного узла списковой структуры или атома и указатель В головного узла списковой структуры. Алгоритм строит головной узел и узел, соответствующий первому элементу результирующего списка. Выдается адрес головного узла. В создаваемом списке L вершина HEAD (L) = А, а хвост TAIL (L) = В. Символом Р обозначена переменная типа указателя. 1. [Проверка корректности списковой структуры.] Если А « = NULL или В = NULL, то напечатать 'НЕКОРРЕКТНЫЙ СПИСОК' и завершить выполнение алгоритма. 2. [В является списком?] Если DPTR (В) =?= NULL, то пе- чатать 'НЕКОРРЕКТНЫЙ СПИСОК В' и завершить выполне- ние алгоритма. 3. [Построение узлового элемента для А]. Установить Р <= <= NODE. Если DPTR (А) = NULL, то DPTR (Р) ч- А; в про- тивном случае DPTR (Р) (значение атома А). 4. [Построение головного узла списка.] Установить CONSTRUCT <= NODE, DPTR (CONSTRUCT) ч- NULL, RPTR (CONSTRUCT) ч- P и завершить выполнение алгоритма. Операции формирования, выполняемой над списковой струк- турой, аналогична операция конкатенации, которую мы будем 452
в °1/1 ~Ыd I -НДР7! Рис. 5-4.18. Пример применение операции конкатенации над списками обозначать APPEND. Например, если А является списком (а, (Ь, с)), а В —списком (d, f), то их конкатенацией является спи- сок (a, (b, с), d, f). Заметим, что уровень каждого из элементов в обоих списках остается неизменным. Это графически показано на рис. 5-4.18. Алгоритм APPEND. Заданы указатели А и В головных уз- лов списка двух списковых структур. Данный алгоритм выдает адрес головного узла списка, в котором элементами первого уровня являются элементы первого уровня обоих списков А и В. Элементы списка А предшествуют элементам списка В. Исполь- зуются алгоритмы CONSTRUCT, HEAD и TAIL. Кроме того, требуется промежуточный стек указателя Q; в нем верхний эле- мент обозначен через ТОР. Переменная i является счетчиком. 1. [Проверка корректности списковой структуры.] Если А = NULL или В = NULL, то печатать 'НЕКОРРЕКТНЫЙ СПИСОК' и завершить выполнение алгоритма. 2. [Инициализация стека и включение в стек следующих друг за другом узлов списка А ]. Установить ТОР ч- О.Повторять До тех пор, пока RPTR (А) NULL (т. е. пока А не станет нуле- вым списком): Установить ТОР ч- ТОР +1, Q [ТОР] ч- - HEAD (А) и А ч- TAIL (А). 3. [Извлечение из стека элементов А с последующим добав- лением их к списку В]. Установить APPEND-*- В. Повторять при i = ТОР, ТОР —1, . . . , Г. Установить APPEND ч- *- CONSTRUCT (Q [i]. APPEND). 4. [Выход. ] Завершить выполнение алгоритма. Как уже отмечалось выше, для облегчения обработки списко- вых структур создано несколько языков программирования. Од- Гплг И3 наи^олее мощных среди них является ЛИСП 1.5. В ЛИСП 1.5 имеется пять базовых, или «простых» функции ЛИСП, 453
Рис. 5-4.16. Пример ориен- тированного графа а именно: CAR, CDR, CONS, EQ и АТОМ. Функций CAR, CDR и CONS эквивалентны списковым операциям HEAD, TAIL и CONSTRUCT, рассмотренным выше в данном пункте. EQ и АТОМ являются логическими функциями (т. е. предикатами). Функция EQ предназначена для проверки эквивалентности двух ато- мов, а функция АТОМ позволяет опреде- лить, является ли списковый элемент ато- мом или нет. Любая функция, обеспечи- ваемая ЛИСП 1. 5, может быть выражена в терминах «простых» функций ЛИСП. Для подробного изучения языка ЛИСП 1.5 чита- телю предлагается прочесть работу [19]. Упражнения к п. 5-4.2 1. Задайте представление в памяти следующих списков: (а, (Ь, (с, d)), е, f) ((х), у, A, z), где А == (a, b, (с, d)). 2. Представьте граф, изображенный на рис. 5-4.19, с помощью списковой структуры. Нарисуйте его представление в памяти. 3. Постройте алгоритм для функции EQ, которая позволяет установить, являются ли два атомарных аргумента эквивалентными. 4. Запишите алгоритм для функции АТОМ, которая позволяет установить, является ли аргумент атомарным (отметим, что пустой список не является ато- марным). 5. Используя алгоритмы EQ и АТОМ, а также HEAD, TAIL и CONSTRUCT, постройте алгоритмы объединения (UNION) и пересечения (INTERSECTION) двух списков. Например, UNION, ((a, b, (с, d)), (а, Ь))) — это (a, b, (с, d), (b)) и INTERSECTION — ((a, b, (с, d)), (а, (Ь))) — это (а). 6. Постройте алгоритм REVERSE реверсии заданного списка. Например, REVERSE ((a, b, (с, d))) — это ((с, d), а, Ь). В вашем алгоритме используйте «простые» функции ЛИСП. 5-4.3. Другие способы представления графов Рассмотрим другие способы хранения графов. Выбор наилучшего представления в памяти для некоторого обобщенного графа зависит от природы данных и операций, выполняемых над этими данными. Более того, на выбор подходящего представления влияют такие факторы, как число вершин, суммарное число ре- бер, исходящих из вершины, является ли граф направленным, частота вставок и (или) удалений и др. Для представления графов иногда могут быть использованы массивы (когда имеется по крайней мере одно ребро между каж- дой парой вершин и не содержится петель). В этом случае вершины нумеруются с 1 до п, и для представления графа используется массив с п строками и и столбцами. Тем самым в таком представ- 454
Рис. 5-4.20. Структура хранения графа с небольшим числом связей лении для хранения данных о вершинах могут потребоваться век- торы. Данный подход не очень удобен для графов, содержащих небольшое число вершин или вершины, связанные лишь с неболь- шим числом ребер, а также для графов, которые должны непре- рывно изменяться. Если между парами вершин имеется несколько ребер и значи- тельное число вершин связано только с несколькими вершинами, то структура представления в памяти указанных графов может быть взята такой, как это показано на рис. 5-4.20. Заметим, что граф является взвешенным, а представление в памяти включает справочник массива вершин и связанный с каждой записью этого справочника список ребер. Стандартная запись справочника вер- шин содержит иомер вершины, данные, связанные с ней, число ребер, исходящих из нее, и поле указателя, которое задает адрес списка вершин, связанных с этой вершиной. В данном случае каждый список ребер хранится как последовательный массив, стандартная запись которого включает вес ребра и номер вер- шины, в которой заканчивается это ребро. Для непрерывно изме- няемого графа Способ представления, в котором каждый список ребер хранится как присоединенный список, наиболее удобен. В данном случае нет необходимости иметь в справочнике массива вершин поле, которое указывает на число ребер. В качестве другого примера рассмотрим представление в па- мяти грамматики. Такое представление становится удобным в тех случаях, когда грамматику необходимо рассматривать и обраба- тывать путем нисходящего синтаксического анализа (см. п. 5-2.3). 455
456
В данном приложении эффективное представление грамматик в па- мяти необходимо в связи с тем, что различные варианты правил должны проверяться на их применимость на каждом шаге анализа конструкции фразы. В качестве примера эффективного представления грамматики используется многосвязная структура. В данном представле- нии структура узла имеет формат NAME TYPE ALTER NEXT Каждый узел представляет некоторый символ X в правой части правила и содержит четыре поля: NAME, TYPE, ALTER и NEXT, где: 1. NAME — сам символ X. 2. TYPE — переменная типа указателя, которая равна нулю, если X является терминальным символом, в противном случае, поскольку X уже не является терминалом, эта переменная ука- зывает на узел, который соответствует первому символу в первой правой части правила для X. 3. ALTER — переменная типа указателя, которая указы- вает на первый символ альтернативной правой части правила, следующей за частью правила, применяемой к данному узлу. Эта переменная используется только для первого символа правой части. В других случаях значение переменной устанавливается в NULL. 4. NEXT — переменная типа указателя, которая задает сле- дующий символ правой части, либо имеет значение NULL. Далее каждая метапеременная представляется с помощью пере- менной типа указатель, которая идентифицирует первый символ в соответствующей ей первой правой части правила. На рис. 5-4.21 представлен граф следующей грамматики: (е) <t, (Г. , <₽' (аоР> (mop) : = 'е\ (aop)-t) | <t) = ;t\ 'mop) <f> ] <f> = <0 t <p> i :p> = (<e>) ] i Упражнения к п. 5-4.3 1. Представьте с помощью списковой структуры граф, изобра- женный на рис. 5-1.5. Начертите его представление в памяти. 2. Начертите представление в памяти следующей грамматики: Е ; : = а | b 1 (Е 4- Е) В : : ==R | (В) R : :=Е =Е 457
5-5. ПРИМЕНЕНИЯ ГРАФОВ В предыдущем параграфе рассмотрено несколько воз- можных структур хранения графов. В данном параграфе мы выбрали четыре приложения, в которых структуры графов широко используются. Одно из первых приложений, когда для обработки графов применялись ЭВМ, было связано с составлением расписаний с использованием широко известных методов PERT (Program Evaluation and Review Technique — метод про- смотра и оценки программы) и СРМ (Critical Path Method — метод критического пути). Этой теме посвящен первый пункт. Одна из разновидностей структур данных — структура комплексных Данных—часто используется в системах машинной графики. Эти структуры описаны в п. 5-5.2. К наиболее ранним приложе- ниям обработки символов, где в этих целях использовались ЭВМ, относится символическое дифференцирование. Ему посвящен третий пункт данного параграфа. В п. 5-5.4 описывается еще одна задача, в которой используются структуры данных, — тополо- гическая сортировка. 5-5.1. PERT и связанные с ним методы Направленные грфаы являются естественным средством описания, представления и анализа сложных проектов, которые содержат большое количество взаимосвязанных работ. Проект может заключаться, например, в конструировании мощной пло- тины или проектировании и сборке жилого здания. В данном пункте мы заинтересованы в определении критического пути в ориентированном графе. Такой критический путь является весьма важным инструментом управления, который может быть применен во многих ситуациях. Имеется множество методов уп- равления, таких как PERT и СРМ, в которых граф используется как структура, на которой базируется анализ. Задйча нахождения минимального пути между двумя вершинами рассмотрена в п. 5-4.1. Однако поиск критического пути включает нахождение наиболее длинного пути между двумя вершинами на взвешенном орграфе. В данном пункте будет введена основная терминология, свя- занная с нахождением критического пути орграфа. Будет дан неформальный алгоритм вычисления критического пути (s) взве- шенных графов. Формально PERT-граф является конечным орграфом без па- раллельных ребер или циклов, в котором имеется только один источник (т. е. вершина, полустепень захода которой равна нулю) и один сток (т. е. вершина, полустепень исхода которой равна нулю). Кроме того, каждому ребру графа приписано весовое (временное) значение. Направленные ребра служат для представления работ; вершины, соединенные с направленными ребрами, отображают 458
РИС. 5-5.1. PERT-граф TES3 VJ ТЕ = 2 Рис. 5-5.2. PERT-граф с приписанными событиям наиболее ранними временами вы- г полпенни моменты начала и конца работы. За весовое значение каждого ребра принимается время, необходимое для выполнения работы. Несмотря на то, что в граф будет входить набор независимых работ, между ними обычно имеется существенная взаимосвязь, зависящая от фактора времени и выражающаяся в том, что не- которая работа at должна быть выполнена до того, как начнется работа aj. Если такие временные зависимости имеют место, их можно условно отобразить в ориентированном графе так, как это показано на рис. 5-5.1. Здесь проект содержит восемь работ, которые следуют в определенном порядке, в том смысле, что не- которые из них должны быть выполнены до того, как могут на- чаться некоторые другие работы. Каждая вершина называется событием и представляет момент времени. В частности, вершина vx означает начало всего проекта (его источник), a v6 — завершение (его сток). Числа, связанные с ребрами, представляют число дней, требуемое для выполнения данной конкретной работы. Из графа мы видим, что перед тем, как может начаться работа <v3, v4), должна быть выполнена работа v3). Аналогично, перед тем, как может начаться работа (v4, veH должны быть выполнены две работы <v2, v4) и <v3, v4) и т. д. Наконец, для того чтобы закончить проект, должны быть выполнены работы <v5, Ve,) (v3, v6> и <V4, v6). Путем обработки PERT-графа мы можем вычислить для всех работ наиболее ранее возможное время их выполнения при огра- ничении, что, перед тем как работа может начаться, должны быть закончены все работы, от которых она зависит. В терминах графов это соответствует приписыванию временных значений всем вер- шинам таким способом, что приписываемое вершине значение явля- ется интервалом времени, необходимым для завершения работ, находящихся на наиболее длинном пути, ведущем в данную вер- шину. Тем самым: мы приписываем вершине значение, которое Равно максимальной сумме двух величин — весу ребра на мно- жестве всех входящих ребер и значению времени, приписанного веРшине, из которой исходит данное ребро. По определению, зна- чение 0 приписывается вершине-источнику. Таким образом, мы 459
можем связать с любой произвольной вершиной значение времени следующим способом: ТЕ (vj = 0; ТЕ (Vj) — max {t (р)}, j Ф 1, где t (Р) означает суммарную продолжительность времени для пути Р, а максимум берется на множестве всех путей из v4 в vj. Приписав на последнем шаге значение вершине-стоку, мы получим значение наиболее раннего времени выполнения всего проекта. На рис. 5-5.1 вершина v2 имеет только одно входящее ребро, и, следовательно, данной вершине приписывается значение 3 (=0 -j- 3). Вершина v4 имеет два входящих ребра, и мы должны выбрать более длинный путь (vx, v3, v4) ( ~2 + 4), а не путь (vv v2, v4) (=3 + 2), и приписать вершине v4 значение 6. Вершина vc имеет четыре входящих пути (vx, v3, v6), (vlf v3, v4, v6), (Vj, v2, vs, ve) и (Vj, v2, v4, ve). Для первого пути значение времени равно 5, для второго 8, для третьего и четвертого 7; сле- довательно, вершине v6 приписывается 8. Поскольку ve является стоком, приписываемое ему значение 8 указывает на то, что для выполнения проекта потребуется по крайней мере восемь дней. Сеть с наиболее ранними временами "выполнения ТЕ, приписан- ными всем вершинам, изображена на рис. 5-5.2. Далее, мы можем вычислить наиболее позднее время выпол- нения, связанное с каждой вершиной. Оно представляет собой наиболее позднее допустимое время, за которое может быть вы- полнена работа, без отсрочки наиболее ранней даты выполнения проекта в целом (т. е. это наиболее позднее время завершения, связанное с работами, которые не приводят к увеличению зна- чения ТЕ стока). Указанное наиболее позднее время завершения TL приписываются вершинам следующим образом. В качестве при- писываемого значения TL берется наибольшее значение времени, которое все еще позволяет завершиться каждой работе, начинаю- щейся в данной вершине, без увеличения полного времени. В терминах графов мы приписываем некоторой вершине значение, равное минимуму на множестве всех исходящих из дайной вер- шины ребер разности значения TL концевой вершины и веса ребра. По определению, значение стока полагается равным значению его ТЕ. Таким образом, мы можем связать с любой произвольной вершиной значение времени следующим способом: TL (vn) = ТЕ (vn); TL (vj) - ТЕ (v„) - max {t (P)}, j n, где t (P) означает суммарную продолжительность времени для пути Р из Vj в vn, а максимум берется на множестве всех таких путей и вычитается из ТЕ (vn). Возвратимся к рис. 5-5.2. Поскольку v6 имеет только одно исходящее ребро, мы приписываем v5 значение TL, равное 460
q (=8 -~1). Аналогично вер- шина v4 Имеет только одно исходящее ребро, и, следова- тельно, ей приписываем 'значе- ние TL, равное 6 (=8 — 2). Вершина v3 имеет два исходя- щих ребра: (v3, v4) и <v3, v6). Данной вершиие приписывается минимальное значение, равное2. Процесс продолжается до тех пор, пока значение TL не до- стигнет нуля в вершиие-источ- нике. PERT-граф для нашего примера со значениями ТЕ и TL изображен на рис. 5-5.3. После вычисления значений ТЕ и TL для всех вершин графа можно определить критический путь (пути). Критическим путем является путь из вершины источника в вершину-сток, такой, что при задержке любой работы на этом пути на значение t весь проект задержится также на t. Для каждой вершины на критическом пути ее значение TL равно значению ТЕ. Это означает, что если проект должен завершиться в его наиболее раннее время выполнения, то вершины на критическом пути должны достигаться в их наи- более раннее время выполнения. Для графа нашего примера вершинами на критическом пути являются Vj, v3, v4, v6, а крити- ческий путь — (vj, v3, v4, v6). Вершины, не находящиеся на критическом пути, обладают связанным с ними свободным вре- менем. Свободное время вершины является разностью между ее значениями TL и ТЕ и задает экономию времени, которая может быть достигнута при выполнении некоторой частной работы. В нашем примере вершина v2 имеет свободное время, равное одному дню. Это означает, что работы, которые должны закончи- ться в вершине v2, могут быть при необходимости задержаны на один день, и это не приведет к задержке проекта в целом. Для того чтобы снизить самое ранее время выполнения проекта, Должны быть ускорены только те работы, которые находятся на критическом пути. Поскольку на практике количество работ, ле- жащих на критическом пути в больших графах, составляет не- большой процент от суммарного количества работ, скажем 10%, то только эти 10 % работ необходимо рационализировать. Рассмотрим представление PERT-графа в соответствии с пред- ложенным в п. 5-4.3 методом с использованием массивов. Справоч- ник доступа к вершинам графа будет содержать четыре одномерных массива: DATA, ТЕ, TL и POINTER. Для вершины i массив DATA [i ] содержит метку и информацию об j; ТЕ [i 1 и TL [i ] — наиболее раннее и наиболее позднее время завершения для вершины i, a POINTER [i ] является индексом в массиве ребер, в котором на следующих одна за другой позициях занесена ин- 461
(формация о ребрах, начинающаяся в вершине i. Массив ребер будет включать две одномерные таблицы TIME и DEST, где TIME [j ] является весом ребра j в единицах времени, a DEST [j] — конечной вершиной ребра j. Начальной вершиной ребра j я-вляется вершина i, где j—это наибольший номер, для которого POINTER Структура хранения, которую мы сейчас описываем, иллюстрируется рис. 5-5.4. Ниже приводится алгоритм построения желаемой структуры хранения путем чтения данных о ребрах из входного файла. Для удобства в этом алгоритме предполагается, что входные данные хранятся в соответствии с исходным номером вершины в порядке возрастания, начиная с вершины 1, являющейся вершиной-источ- ником (начало проекта), до вершины с наибольшим номером, являющейся стоком, соответствующим завершению проекта. Ре- бро описывается следующими статьями данных: ORIGIN — номер вершины, из которой исходит ребро; INFO — данные или инфор- мация об исходной вершине; WEIGHT—вес ребра в единицах времени; END -— иомер вершины, в которую входит ребро. Отметим, что для некоторой частной исходной вершины статью INFO можно задавать только один раз, а затем это поле может заменяться нулевой строкой. Поскольку полустепень исхода вершины-стока проекта равна нулю, Для ввода информации и идентифицирующих данных этой вершины будут использоваться вырожденные статьи данных, описывающих ребра. В этом случае иомер вершины, в которую входит ребро, будет равен нулю. Та- кая вводимая запись функционально аналогична «конечной перфокарте», что обеспечивает второй способ завершения ввода данных. Алгоритм CREATE. Заданы обобщенные структуры ^массивы) DATA, ТЕ, TL, POINTER, TIME и DEST. Алгоритм считывает данные о ребрах из входного файла и приписывает значения эле- ментам этих массивов таким образом, чтобы отразить взаимосвязи, существующие в PERT-графе. Кроме того, в качестве обобщенных переменных рассматриваются также переменные NO—NODES 462
и NO—EDGES, задающие соответственно число различных вер- щйн и ребер, которые могут встретиться в графе. 1. [Инициализация.] Установить ТЕ -«-POINTER О, DATA ”, NO—NODES <— NO—EDGES 0, TL oo (или некоторое очень большое число). 2. [Считывание статей данных для ребер. ] Если входной файл исчерпан, то завершить выполнение алгоритма. В противном случае читать ORIGIN, INFO, WEIGHT и END. 3. [Приписывание значений элементам массива. ] Если ORIGIN < NO- NODES, то печатать: ’ДАННЫЕ ВНЕ ПОСЛЕДОВАТЕЛЬНОСТИ’ и завершить выполнение алгоритма. Если INFO =# ”, то установить DATA [ORIGIN] INFO. Если ORIGIN NO--NODES (новая начальная вершина), то установить NO—NODES ч-NO—NODES + 1, POINTER [NO—NODES] NO—EDGES + 1; если END Щ 0, то завер- шить выполнение алгоритма; в противном случае установить NO—EDGES <- NO-EDGES 4-1, TIME [NO—EDGES] +- WEIGHT и DES [NO -EDGES] END; в противном случае (некоторая начальная вершина)5 если END 0, то печатать 'НЕСТОКОВАЯ ВЕРШИНА ИМЕЕТ РЕБРО С НЕДЕЙСТВИ- ТЕЛЬНЫМ АДРЕСАТОМ’ и завершить выполнение алгоритма: в противном случае установить NO_EDGES -«-NO_EDGES 4- 1 TIME [NO EDGES] ^-WEIGT и DEST [NO—EDGES] <- -(-END- Перейти к шагу 2. Шаг 1 устанавливает необходимые начальные значения мас- сивов и счетчиков. В шаге 3, если необходимо, хранится иденти- фицирующая информация. Затем, если встретилась новая началь- ная вершина, обновляется счетчик вершин, и вход заносится в POINTER. Эти действия опускаются, если данная вершина встре- чалась ранее/ В этот же момент, если ребро, информация о кото- ром анализируется, является действительным, обновляется счет- чик ребер, а соответствующие данные сохраняются в массивах. Для того чтобы определить критический путь, для каждой вершины должны быть вычислены значения ТЕ и TL. Приводимый ниже алгоритм производит эти вычисления и затем выводит на печать вершины, находящиеся на критическом пути. Алгоритм PROCESS. Описанным выше способом задань’1 обоб- щенные массивы." Также задано число ^различных вершин NO—NODES и ребер NO_____EDGES в PERT-графе. Данный ал- горитм вычисляет и запоминает для каждой вершины значения ТЕ и TL и затем выводит на печать вершины, находящиеся на крити- ческом пути. NODE# и EDGEfp указывают, какие именно вер- шина и ребро анализируются в текущий момент. 1. (Инициализация для вычисления значений ТЕ.] Устано- вить NODE# ^-1 и EDGE =£ POINTER [1]. 2. [Вычисление значений ТЕ для всех вершин. ] Повторять До тех пор, пока NODE# <*NO—NODES: повторять до тех ПОР, пока EDGEN# < POINTER [NODE# 4- 1 ]- установить 463
ТЕ [DEST [EDGE#]) ч-МАХ (ТЕ [DEST[EDGE#] 1, ТЕ [NODE#]# TIME [EDGE#]) и EDGE# -«-EDGE# # 1. Установить NODE# -e- NODE# 4- 1. 3. (Инициализация для вычисления значений TL. 1 Устано- вить TL [NODE#! -t-TE [NODE#], NODE#-*-NO—NODES— — 1, и EDGE# NO—EDGES. 4. [Вычисление значений TL для всех вершин, ] Повторять до тех пор, пока NODE# 1: повторять до тех пор, пока EDGE# POINTER [NODE#]: установить TL (NODE#]-e- -*-MlN (TL [NODE# ], TL [DEST [EDGE# 1] —TIME [EDGE#]), и EDGE# -«-EDGE# — 1. Установить NODE# NODE# — — 1. 5. [Вывод вершин на критическом пути. ] Повторять для NODE# 1, 2......NO—NODES: если ТЕ [NODE#] = = TL (NODE# ], то печатать NODE# и DATA (NODE# J. 6. [Выход. 1 Завершить выполнение алгоритма. Операции данного алгоритма совершенно очевидны. В шаге 2 вычисляются элементы ТЕ [vj 1 = max # (Р) }, j Ф 1. Посред- ством этого каждой вершине Vj приписывается время, взятое вдоль наиболее длинного пути Р из вершины у, в вершину Vj. В шаге 3 для конечной вершины проекта наиболее позднее допу- стимое время завершения устанавливается равным наиболее ран- нему возможному времени завершения, и переустана'вливаются счетчики. В шаге 4 вычисляются значения TL [vj 1 = ТЕ [vn 1 — — max jt (Р)|, j #= п. Посредством этого каждой вершине vj (кроме стоковой вершины vn) приписывается значение наиболее позднего времени завершения. Это значение определяется из условия, что его добавление ко времени, связанному с наиболее длинным путем из Vj в vn, не должно привести к удлинению срока окончания проекта. В шаге 5 выводятся на печать вершины, на- ходящиеся на критическом пути (т. е. те вершины, для которых значения ТЕ и TL равны), а также соответствующая идентифици- руемая информация. Имеется также возможность использовать в качестве метода представления PERT-графа связанные списки. Например, каждая произвольная вершина графа может быть представлена посредством вершинной структуры вида OUT DATA ТЕ TL IN LINK где OUT — указатель первой вершины в списке ребер, начинаю- щихся в данной произвольной вершине; IN — указатель первой вершины в списке ребер, Входящих в данную произвольную вер- шину; ТЕ и TL — самое ранее и самое позднее время заверше- ния для данной вершины; LINK —• указатель следующей вершин- 464
ной структуры в списке произвольных вершин. Для ребер может быть использована структура вида | OUTL SOURCE TIME DEST INL где OUTL — указатель следующего ребра в списке ребер, начина- ющихся в произвольной вершине, идентифицируемой посредст- вом SOURCE; INL —указатель следующего ребра в списке ре- бер, которые входят в произвольную вершину, идентифицируе- мую посредством DEST, a TIME — вес в единицах времени ребра, соединяющего вершины, идентифицируемые посредством SOURCE и DEST. Построить алгоритмы создания такого представления PERT-графа и определения вершин на критическом пути отно- сительно легко. Логика вычислений такая же, как и использован- ная в алгоритмах представлений массивов, однако логика обра- ботки структур хранения будет различной. Упражнения к п. 5-5.1 1. Проследите выполнение алгоритмов CREATE и PROCESS, используя граф, представленный на рис. 5-5.4. 2. Задав структуры для произвольных вершин и ребер PERT-графа так, как это описано в конце п. 5-5.1, сконструируйте алгоритм, который будет строить представление данного графа в виде связанного списка. Входные данные ребер -могут иметь вид: ORIGIN — метка исходной вершины для некоторого ребра; END — метка конечной вершины для некоторого ребра; WEIGHT — вес ребра в единицах времени. Этот алгоритм должен строить вершины ORIGIN и END, заносить их в список вершин событий, если указанных вершин еще нет в этом списке. Затем алгоритм должен построить вершину ребра и занести ее в надлежащие собственные списки входящих и исходящих ребер, а также правильно установить все указатели. 3. Задано связанное представление PERT-графа, конструируемое с помощью алгоритма из упражнения 2. Напишите алгоритм, который будет дополнять это представление, вычисляя значения ТЕ и TL для произвольной вершины. Данный алгоритм затем должен вывести на печать вершины, которые находятся на кри- тическом пути. 4. Напишите на языке ПЛ/1 процедуры, выполняющие алгоритмы CREATE и PROCESS в п. 5-5.1 и алгоритмы упражнений 2 и 3. Оцените,.два представле- ния, табличное и связанными списками, с точки зрения использования памяти и скорости выполнения для малых и больших PERT-графов. 5-5.2. Применение в машинной графике „ «Машинная графика — это общий термин, применяе- мый к использованию цифровых вычислительных машин для фор- мирования внутренней модели воспринимаемого извне графиче- ского объекта» [1]. Такое моделирование делает возможными модификацию, обработку или другие подобные действия с объек- том и последующее отображение объекта в удобном формате. 465
Типичные применения машинной графики — это построение диа- грамм, составление топографических карт, изображение карика- тур, а также разработка эскизов зданий, улиц, самолетов и ав- томобилей. В машинной графике различается два метода — интерактивный и пассивный. В качестве введения в обсуждаемую тему мы рас- смотрим системы, типичные для каждого метода. Читатель дол- жен отдавать себе отчет в том, что такое различие делается в ос- новном в методологических целях; типичные практические систе- мы будут гибридом этих двух крайностей. Интерактивная, машинная графика — это такой метод гра- фики, при котором пользователь и ЭВМ взаимодействуют или ведут диалог в онлайновом (on-line) режиме. При этом используются средства ввода и отображения, работающие в онлайновом режиме, которые обеспечивают весьма быстрый отклик ЭВМ на команды пользователя. Наиболее распространенным средством отображения является электронно-лучевая трубка (ЭЛТ). Подобное средство использу- ется также в невычислительном оборудовании, таком, как инди- каторы радара и телевизионные экраны. Метод, при котором ЭЛТ может отображать информацию ЭВМ, весьма прост. Цифровые электрические сигналы, генерируемые ЭВМ, преобразуются с по- мощью цифре а налогового преобразователя в непрерывный сигнал, который управляет количеством и направлением движения элект- ронов, излучаемых в одном такте работы ЭЛТ; эти электроны со- здают видимое пятио иа фосфоресцирующей поверхности трубки. Главный недостаток ЭЛТ состоит в том, что бомбардировка электронов создает точки иа весьма короткое время, т. е. они бы- стро затухают. Для тогсГчтобы поддержать изображение, про- цесс его создания должен непрерывно повторяться. Этот процесс называется «восстановлением»"изображения, и для того чтобы из- бежать мерцания, он должен повторяться около 30 раз в секунду. Были созданы также и другие средства вывода, в которых изо- бражение не исчезает. Среди них — трубки с прямым осмотром памяти и плазменные панели. В настоящее время реализация этих средств сопряжена с определенными техническими трудно- стями, поэтому они меиее распространены, нежели обычные ЭЛТ. В интерактивной системе пользователь должен взаимодейст- вовать с ЭВМ быстро и эффективно. Используемое устройство ввода должно предоставлять возможность задавать команды, размещать символы на экране и специфицировать фрагменты изображения, которые должны быть устранены или изменены. В созданные с этой целью средства входят клавиатура, световое перо и планшет. На рис. 5-5.5 изображена типовая интерактивная система. В качестве средств ввода используется клавиатура и планшет, в^качестве средств вывода — ЭЛТ. Особо отметим дисплейный процессор. Этот процессор взаимодействует с центральным про- 466
Рис. 5-5.5. Типичная интерактивная графическая система цессором и осуществляет доступ в основную память Для полу- чения инструкций, описывающих изображение, которое должно быть создано. Дисплейный процессор должен интерпретировать эти инструкции и передавать соответствующие сигналы для ЭЛТ. Дисплейный процессор также ответствен за восстановление ЭЛТ, тем самым освобождая центральный процессор от этих требующих много времени задач. Мощный дисплейный процессор может об- ладать способностью генерировать простые геометрические фи- гуры. Второй метод машинной графики неинтер активный, т. е. пассивный. Согласно этому методу система обычно функционирует в пакетном режиме; средствами ввода обычно являются перфока- рточное устройство, диск или магнитная лента, а средства вывода обычно обеспечивают «твердую копию», которая представляет собой постоянное изображение. Примерами таких средств вывода являются посимвольные и построчные печатающие устройства и графопостроители. Наиболее распространенное пассивное устрой- ство вывода — это чертежное устройство с движущимся пером. Это устройство передвигает перо в плоскости ху под управлением ЭЁМ. Устройства с твердой копией строят изображение очень мед- ленно, и поэтому их редко используют в интерактивных системах. Единственным исключением могут быть ситуации, когда жела- тельно получить зафиксированную запись конечного результата. На рис. 5-5.6 изображена типичная конфигурация системы, работающей в офлайновом (off-line) режиме. Процессор генери- рует аналоговые сигналы, которые управляют работой графо- построителя с леиты, полученной основным вычислителем. „А теперь мы обсудим аспект машинной графики, который в дай- ной книге представляет наибольший интерес, а именно — аспект структур данных. Сначала рассмотрим некоторые основные по- нятия. Для всех средств вывода машинной графики предусматрива- лся наличие двумерной поверхности, на которой должно быть построено изображение. Эта поверхность рассматривается как v 467
Рис. 5-5.6. Типичная конфигурация офлайновой графической системы декартова система координат; при этом могут быть заданы только точки, адресуемые координатами х и у в данной системе коор- динат. Все устройства могут отображать любую точку, адресуе- мую указанным способом. В некоторых наиболее сложных устрой- ствах предусмотрена возможность строить непрерывную линию между двумя адресуемыми точками; вместе с тем многие устрой- ства строят линию путем изображения точек, расположенных на равных интервалах вдоль линии. Первые системы машинной графики были созданы для построе- ния графиков и отображения данных. Графическому изображению соответствовали простые структуры данных, такие как одно- и двумерные массивы. Структура могла быть либо вектором коор- динат (х, у) всех точек, которые должны быть отображены, т. е. вектором координату, соответствующих независимым координа- там х, либо двумерным массивом А, в котором элемент A [i, j 1 должен быть установлен так, чтобы указать, что должна быть отображена точка (i, j). Этот тип структуры адекватен приложениям, в которых изоб- ражения относительно статичны; однако большинство последую' щих усовершенствований машинной графики относилось к при- ложениям, требующим более подвижных многосторонних изобра- жений. Одной из многообещающих областей было проектирование зданий, самолетов и автомобилей. Это привело к развитию универ- сальных систем графики, которые можно было использовать для любых целей. В таких приложениях необходимы подвижные изображения, которые могут быть легко изменены, т. е. должны допускаться исключение элементов из изображения, преобразование и вставка новых элементов. Рассмотренные ранее простые структуры данных неадекватны этим операциям. Для того чтобы обеспечить желаемые свойства изображения, необходимы более многогранные струк- туры данных. Адекватная структура данных должна удовлетво- рять следующим критериям. 1. Структура должна обеспечивать концептуальную модель отображаемого предмета, т. е. должны быть сохранены порядок и связи между частями предмета. 468
2. Структура должна обеспечивать отображение, обработку, преобразование и анализ предмета. 3. Структура должна быть удовлетворительна с точки зрения требований памяти и скорости обработки. Сведения, полученные читателем о структурах данных, в на- стоящий момент должны позволить понять, что этим требованиям может удовлетворить определенный тип связанной структуры. Рассмотрим два примера списковой структуры простейшего вида, а именно два линейных списка, первый из которых должен содержать координаты отображаемых точек, а второй — коорди- наты крайних точек отображаемых линий. С использованием связанного представления устранение и до- бавление точки или линии тривиально и требует только изменения небольшого числа указателей, что более предпочтительно, нежели замена многих элементов массива. Объединение двух изображений также производится просто — необходимо только связать списки линий и точек каждого из них. Эти структуры будут удовлетворять трем требованиям на прос- тую систему, подобную интерактивному построению графиков. Однако для более сложных приложений необходима более развитая структура данных, удовлетворяющая трем указанным крите- риям. Для того чтобы удовлетворить первому критерию, структура данных должна обеспечивать возможность задания взаимосвязей между элементами внутри изображения. Одна из важных концеп- ций в графике — это понятие подкартины, т. е. фрагмента изоб- ражения. Рассмотрим рис. 5-5.7, на котором изображена электри- ческая цепь, составленная в виде комбинации двух простых цепей. Эта иерархическая структура иллюстрируется древовидной ди- аграммой на рис. 5-5.8. Рассмотрим эту структуру с точки зрения третьего критерия. Очевидно, что эта структура данных приводит к неэффективному использованию памяти, поскольку имеются повторения некоторых вершин дерева. Эта неэффективность может быть устранена путем использования ориентированного графа. Такая структура, эк- Рис. 5-5.7. Комбинированная электрическая цепь 469
цепь Рис. 5-5.8. ДревовиЛна11 структура электрической цепи Бивалентная структуре на рис. 5-5.8, изображена на рис. 5-5.9. Подобная структура экономит пространство памяти. При исполь- зовании понятия подкартииы, она также обеспечивает свойство независимости подпрограмм. Каждая копия изображения или картины строится путЛм передачи параметров подпрограмме, которая ответственна за генерацию всех вариантов объекта. На- пример, каждая вершина на рис. 5-5.9 может содержать размер н позиционную характеристику данных такого частного варианта подкартины. Структура данных, аналогичная описанной выше, используется в системе GRAPHIC-2 фирмы Bell Telephone Laboratories. Такая структура удовлетворяет некоторым ограничениям построения онлайновых систем, однако она не годится для приложений, в ко- торых производятся частый поиск и модификация. В качестве примера рассмотрим задачу отыскания всех вхождений конкрет- ного базового элемента, подобного элементу линии на рис. 5-5.9. В этом случае потребуется обход всего графа, что свидетельствует о значительном суммарном машинном времени. Задача обеспечения более эффективных методов поиска и об- новления может быть решена путем введения структуры данных, которая предоставляет быстрый доступ к информации о топологии Рис. 5-5.9. Представление электрической цепи в виде ориентированного графа 470
рис. 5-5.10. иерархическая циклическая структура электрической цепи графа. Эти средства в некоторой степени обеспечивает иерархи- ческая циклическая структура. Иерархическая циклическая стру- ктура имеет уровни, аналогично дереву или ориентированному графу, рассмотренным ранее в данной главе, однако элементы на любом уровне циклически связаны. Логически связанные эле- менты соединены таким образом, что любая статья данных может быть доступна из любой другой статьи. На рис. 5-5.10 изображена циклическая структура, эквивалентная ориентированному графу, представленному иа рис. 5-5.9. Отметим, что вхождения сходных базовых элементов, т. е. элементов самого нижнего уровня, связаны друг с другом. Это та особенность, которая повышает эффективность доступа к элемен- там структуры. Включение в такую структуру относительно про- сто; одиако удаление должно осуществляться внимательно, с тем чтобы гарантировать правильное изменение всех соответствующих указателей. Примерами систем, в которых используются структуры, по- добные описанной, являются графическая система фирмы Gene- ral Motors Graphics Sistem [311, система 3DPDP [29] и система SKETCHPAD [27]. Основным недостатком иерархических циклических систем является большой суммарный объем памяти, требуемый для большого числа указателей. Для большинства приложений это компенсируется повышенной гибкостью и доступностью струк- туры. В качестве детального примера возможной структуры данных Для интерактивной системы графики рассмотрим структуру, ана- логичную структуре, используемой в системе SKETCHPAD, но меиее сложную и меиее общую. Базовыми элементами этой системы являются точки и линии, и предполагается, что имеется устройство вывода, способное отображать элементы этих двух типов. Такая структура данных является одним из вариантов иерархической 'Чй’клической структуры и использует двойные связи, что дает возможность легко манипулировать списками. В структуре данных предполагается использование узлов че- тырех типов, которые представлены на рис. 5-5.11. В каждом 471
BULINK . LLINK1 -Т*- LLINK2 — LLINK3 LLINK4 Узел BUCKET Следующий узел пакета > Связанные линча Рис. 5-5.11. Форматы узлов изображения структуры изображении или подкартине (объекте) имеется узел MASTER, который содержит четыре поля. Поля PLINK, LLINK и ELIN К являются указателями цепочек точек, линий и других элементов, составляющих изображение. Поле TMATRIX является главной матрицей преобразования, назначение которой поясняется ниже. Узлы POINT, LINE и ENTITY — это узлы цепочек, и поэтому каждый из ннх содержит поля LLINK и RLINK, указывающие соответственно не предыдущий и последующий узлы списка. Каждый узел POINT имеет поля X и Y, которые содержат координаты х и у точки. Кроме того, имеются трн поля— LL1NK1, LLINK2 и LLINK3, которые могут быть нулями (NULL) или могут указывать на узел линии, если точка яв- ляется крайней. Когда точка является крайней для трех линий и более, используется поле LLINK4; это поле содержит адрес узла BUCKET, состоящего из полей — LLINK1, LLINK2, LLINK3, LLINK4 и BULINK и указателя на следующий узел. Поле MLINK узла ENTITY указывает на узел MASTER структуры, представляющей подкартину. До сих пор не рас- 472
смотрено только одно поле — TMATRIX. Оно содержит данные, относящиеся к конкретному варианту подкартины, представлен- ному узлом ENTITY. Чтобы пояснить использование поля TMATRIX, мы должны несколько отклониться от темы и обсудить задачу преобразова- ния изображений. Три наиболее общих преобразования изобра- жений — это вращение вокруг точки, сдвиг или перенос в направ- лении х и (или) у и масштабирование, которое расширяет или сужает изображение, по оси х и (или) у. Перенос, или сдвиг точки (х, у), может быть алгебраически записан в виде где Тх и Ту — величины переноса в направлениях х и у соот- ветственно. На рис. 5-5.12, б показан перенос диаграммы, изобра- женной на рис. 5-5.12, а, при Тх = 5 и Ту = 3. Поворот точки (х, у) по часовой стрелке относительно начала координат на угол 0 может быть записан в виде х' = х cos 0 + у sin 0; (2) у' =— х sin 0 -J- у cos 0. Поворот точки (х, у) относительно другой точки (р, q) может быть представлен как следующие комбинации вращения н пере- носа. 473
1. Перенос точек, при котором точка (р, q) попадает в на- чало координат: х' = х — р; у' = у — q- 2. Вращение смещенной точки относительно начала координат? х" = х' cos 0 4- у' sin 0; у" — —х' sin 0 + у' cos 0. 3. Перенос точек, при котором точка (р, q) возвращается в первоначальную позицию: х,п = х" + р; У'" = У" + q- На рис. 5-5.12, в показан поворот исходной диаграммы отно- сительно точки (4,0) иа 10°. Масштабирование точки — это изменение координат (х, у) с коэффициентами Sx и Sy. Ойо может быть записано следующим образом: х' = xSx;l У' = ysy.j Для того чтобы расширить изображение в 2 раза, принимаем Sx — Sy — 2. Если Sx ф Sy, то получится искаженное изображе- ние. На рис. 5-5.12, г показано масштабированное исходное изоб- ражение с коэффициентами масштабирования Sx = Sy = 2. Приведенные двумерные преобразования могут быть представ- лены в матричной форме. Преобразование точки (х, у) в точку (х', у') может быть представлено в виде [х'у'1] = 1ху1] a d b е с f 0 0 1 Матрица 3x3 полностью задает преобразование, содержащее любую последовательность переносов, поворотов и масштабиро- ваний. В матричной форме указанные выше преобразования имеют вид: перенос [x'y'l] = [xyl] 1 0 0 1 Тх т, о о 1 474
поворот cos 6 — sin О О [x'y'l] = [xyl] sin6 0 cos0 О 0 I масштабирование [x'y'l] = [xyl] 0 0 0 Sy 0 0 1 0 Подчеркнем, что матрица 3X3 требуется также для того, чтобы задать переносы точки. Последовательность преобразований может быть специфици- рована путем умножения независимых матриц, задающих каждое из отдельных преобразований. Если матрица А задает первое пре- образование, а матрица В — второе, то матрица А- В будет пред- ставлять первое преобразование, за которым следует второе. Матрица В-А—это не то же самое преобразование, что А-В, так как она представляет второе преобразование, за которым сле- дует первое. Такое умножение может быть повторено; таким об- разом, любая последовательность преобразований может быть представлена с помощью одной матрицы. В качестве примера рассмотрим поворот точки относительно Другой точки. Последовательность необходимых для этого преобра- зований рассматривалась ранее. Эта последовательность может быть представлена в виде 1 0 0 cosO [х'"у'"1] = [ху1] 0 1 0 V sinG — т х _Ту I 0 — sin 0 О cos О О О 1 1 О О X о 1 о Тх Ту 1 Рассмотрим теперь некоторые операции, необходимые для эф- фективного преобразования изображений. Основное внимание мы акцентируем на структурах данных, необходимых для вы- полнения этих преобразований. На рис. 5-5.14 показана структура данных, соответствующая изображению, представленному на рис. 5-5.13. Узел с меткой PICTURE является главным узлом изображения объекта; отме- тим, ЧТО в полях PLINK и LLINK записан NULL, указывающий, 475
что в изображении иет независимых линий или точек. Изображение соста- влено из двух подкартин SQ и TRI. Каждая подкартина составлена из трех линий. Отметим, что ии одна из подкартин не имеет независимых точек. Матрицы преобразований во втором и четвертом узлах цепочки элементов PICTURE содержат информацию, не обходимую для переноса и увеличения размеров верхней фигуры до размеров нижией фигуры. рис. 5-блз. пример изображе- Основными операциями, выполняе- мыми над структурами данных в гра- фике, являются: 1) включение (вставка) новых элементов; 2) удаление элементов; 3) преобразование струк- туры или подструктуры; 4) отображение элементов. Алгоритмы выполнения этих операций представлены ниже, однако для их понимания необходимо некоторое предварительное обсуждение. Мы предполагаем существование функции AVAIL, которая при заданных описателях параметров узлов, таких как POINT -NODE, LINE—NODE и т. д., задает указатель узла, взятого из имеющегося в наличии списка. Этот узел определяет скорректированную сторону, полученную в соответствии с пара- метрами функции AVAIL. Мы также предполагаем, что оператор 'установить AVAIL' помещает узел в соответствующую ячейку имеющегося списка. Мы используем три алгоритма вставки: PINSERT, L1NSERT и ENTINSERT, которые вместе с алгоритмом MASTER могут использоваться для построения и дополнения изображений. Алгоритм PINSERT. Задан указатель HEAD главного узла структуры, в которую должны быть включены точка и координаты этой точки Хл и У3. Алгоритм P1NSERT строит узел, представ- ляющий точку, если эта точка ие входила в изображение ранее. Выходом алгоритма является указатель Р узла точки. 1. [Начало цепочки точек?] Установить Р -«-PLINK (HEAD). 2. [Проверка, имеется ли узел, представляющий точку.] Если Р = HEAD, то перейти к шагу 3 (узел не найден); в про- тивном случае, если X (Р) = Xj и Y (Р) == Х3, то установить PINSERT -<-Р и завершить выполнение алгоритма; в противном случае установить Р ч- RLINK (Р) и повторить шаг 2. 3. [Построение узла.] Установить Р-«-AVAIL (POINT— NODE), RLINK (Р) ч- PLINK (HEAD), LLINK (P) HEAD, PLINK (HEAD)-е-Р, X(P) Хъ У(Р)ч-Ух, LLINK I.LINK2 LLINK3 LLINK4 NULL, LLINK (RLINK (P)) -<-P, PINSERT-e-P и завершить выпол- нение алгоритма. 476
Рис. Б-5.14. Структура данных, соответствующая изображению, представленному на рис. 5-5-13 477
Алгоритм LINSERT Заданы указатель HEAD главного узла структуры и крайние точки линии (Хь Y,) и (Х2, Y2). Ал- горитм LINSERT включает линию, представленную этими край- ними точками. Если заданные точки отсутствуют в представлении структуры, то создаются новые соответствующие им узлы. В ре- зультате выполнения алгоритма формируется указатель узла линии; Р, Q, R, S и Т являются переменными типа указателя. 1. [Построение новых узлов крайних точек с использованием алгоритма PINSERT. 1 Установить РPINSERT (HEAD, Xj, Yx), Q ч-PINSERT (HEAD, X2, Y2). 2. [Проверка, задана ли уже линия.] Установить R ч-LLINK (HEAD). 3. [Согласование узла линии с точками. 1 Если R = HEAD, то перейти к шагу 4 (нет линии). Если POINT1 (R) = Р и POINT2 (R) = Q или POINT1 (R) = Q и POINT2 (R) = Р, то перейти к шагу 5; в противном случае установить R <— RLINK (R) и повторить шаг 3. 4. Построение узла, соответствующего линии. 1 Установить R AVAIL (LINE—NODE), RLINK (P) LLINK (HEAD), LLINK (HEAD) R, LLINK (R) HEAD, LLINK (RLINK (R)) <- R, POINT1 (R) P, POfNT2 (R) Q. 5. [Проверка, указывают ли узлы точек на узел линии.] Установить Т -<-Р, Р -<-Q, Q -«-NULL. Если Т = NULL, то установить LINSERT R и завершить выполнение алгоритма. Если LLINKI (Т) = R или LLINK2 (Т) = R, пли LLINK3 (Т) = = R, то повторить шаг 5. Если LLINKI (Т) = NULL, то установить LLINKI (Т) ч-R и повторить шаг 5. Если LLINK2 (Т) = NULL, то установить LLINK2 (Т) ч-R и повторить шаг 5. Если LLINK3 (Т), то установить LLINK (Т) ч- R и повторить шаг 5. Установить S <-LLINK4 (Т) (контроль пакетов, относящихся к точке). Повторять до тех пор, пока S ф NULL: если LLINKI (S) = R или LLINK2 (S) == R, или LLINK3 (S) = R, или LLINK4 (S) = R, то повторить шаг 5; в противном случае: если LLINKI (S) = NULL, то установить LLINKI (S) ч-R и повторить шаг 5; если LLINK2 (S) = NULL, то установить LLINK2 (S) ч- R и повторить шаг 5; если LLINK3 (S) — NULL, то установить LLINK3 (5) и повторить шаг 5; если LLINK4 (S) = NULL, то установить LLINK4 (S) ч-R и повторить шаг 5, установить Тч-S, S ч-BULINK (S). 478
6. [Построение нового пакета. 1 Установить S AVAIL /BUCKET—NODE), LLINKI (S)-«-R. BULINK (S) 3- NULL, LLINK2 (S) 3- LLINK3 (S) - LLINK4 (S) з- NULL. Если POINT1 (R) — T или POINT2 (R) = T, то установить LLINK4 (T) з-S; в противном случае установить BULINK (Т) з-S. Перейти к шагу 5. Алгоритм ENT1NSERT. Заданы: указатель HEAD головного узла структуры, в которую должен быть включен элемент, ука- затель ENT подструктуры, представляющей элемент, и матрица преобразований TRANS, выполняемых над элементом. Алгоритм ENTINSERT строит узел, представляющий элемент, и связывает его с существующим набором узлов. 1. [Построение узла.] Установить Р з-AVAIL (ENTITY- NODE), RLINK (Р) з- ELINK (HEAD), LLINK (P) з- HEAD, MLINK (P) з-ENT.jTMATRIX (P) ^TPANS, ELINK (HEAD) з- 3-P, LLINK (RLINK) (P)) 3-P, ENTINSERT , P и завершить выполнение алгоритма. Алгоритм MASTER. Данный алгоритм строит и выдает указа- тель главного узла, начальная установка которого такова, что матрица TMATRIX является единичной, а связи указывают на этот же узел. 1. [Построение узла.] Установить Р з-AVAIL (MASTER__ NODE), PLINK (P) 3- LLINK (P) з- ELINK (P) з-P, TMATRIX (P) 3- '1 0 0 1 _0 0 0* 0 1. , MASTER < - P. В качестве примера использования этих алгоритмов рассмотрим построение структуры, изображенной на рис. 5-5.14, путем следу- ющей последовательности алгоритмических шагов. 1. [Построение структуры для представления треугольника.] Установить TRI з-MASTER, вызвать LINSERT (TRI, Хъ Yb Х4, Y4), вызвать LINSERT (TRI, Х„, Y„ X6, Y6), вызвать LINSERT (TRI, Xi, Yt, X6, Y„). 2. [Построение структуры для представления трех сторон прямоугольника. 1 Установить SQ з-MASTER, вызвать LINSERT (SQ, Xi, Ylt Х2, Y2), вызвать LINSERT (SQ, Xs, Y2, Xs, Y3), вызвать LINSERT (SQ, X3, Y3, X4, Y4). 479
3. [Построение изображения путем композиции четырех вхо- ждений элементов. I Установить PICTURE -«-MASTER, 1 О О вызвать ENTINSERT (PICTURE, SQ, ° 1 ° Ь '1 0 0" вызвать ENTINSERT (PICTURE, TR1, 0 1 0 ) .0 0 I. "1 0 О’ вызвать ENTINSERT (PICTURE, SQ, 0 2 0 .0—30 1. ). вызвать [ENTINSERT (PICTURE, TRI, "1 О О' 0 10). .0—10 1. Алгоритмы удаления основаны на следующих предположе- ниях: 1. Для удаления точки требуется, чтобы кроме того были удалены все линии, для которых данная точка является крайней (отметим, что другая крайняя точка, соответствующая дайной, не удаляется, несмотря на то, что она теперь не должна относиться к уничтоженному узлу линии). 2. Удаление линии ие приводит к удалению крайней точки. 3. Удаление элемента приводит только к удалению одного узла элемента. Для выполнения этих удалений используются четыре алго- ритма. Алгоритм LINEDELETE удаляет линию, на которую де- лается ссылка с помощью указателя; алгоритм LDELETE удаляет линию, на которую делается ссылка с помощью ее крайних точек; алгоритм PDELETE удаляет точку, представляемую ее координа- тами; алгоритм ENTDELATE удаляет узел элемента, идентифици- руемого с помощью указателя подструктуры элементов и матрицы преобразования элемента. Алгоритм LINEDELETE. Заданы главный узел HEAD струк- туры и указатель Р узла линии, которая должна быть удалена. Алгоритм LINEDELETE удаляет идентифицируемый узел и исключенную линию из соответствующих полей узлов; Q, Р, S/ Т и V являются вспомогательными переменными типа указателя. 1. [Удаление узла.] Если RLINK (Р) HEAD, то устано- вить LLINK (RLINK (Р)) LLINK (Р). Если LLINK (Р) = то установить LLINK (HEAD) ч-~ RLINK (Р); в противном случае установить RLINK (LLINK (Р)) RLINK (Р). 2. [Удаление ссылок на линию из крайних точек. ] Повторить шаги 3—5 для Q = POINT1 (Р), POINT2 (Р). 480
3. [Поиск S, узла или пакета, содержащих ссылку на линию.’ установить S ч- Q. Если LLINK1 (S) - Р или Jg LLINK2 (S) = р, или LLINK3 (S) — Р, то перейти к шагу 4; в противном случае установить Т ч- LLINK4 (S) (подго- товка входа в пакеты). Повторять, пока Т =f= NULL: установить S ч— Т, Т BULINK (Т), если LLINK1 (S) = Р или LLINK2 (S) = Р, или LLINK3 (S) — Р, или LLINK4 (S) = Р, то перейти к шагу 4. Перейти к шагу 7 (ошибка — не найдена ссылка на линию). 4. [Замена ссылки на линию в узле S, содержащем последнюю ссылку на линию.] Если S = Q (подготовка поиска последнего пакета Т), то установить Т ч- LLINK4 (S): в противном случае установить Т ч- BULINK (S). Если Т — NULL, то установить Т ч- S; в противном случае повторять до тех пор, пока BULINK (Т) NULL: установить Т ч— BULINK (Т) (просмотр для последней ссылки R). Если Т и LLINK4 (Т) =# NULL, то установить R ч- LLINK4 (Т), LL1NK4 (Т) ч- NULL и перейти к шагу 5. Если LLINK3 (Т) =0=NULL, то установить R ч- LLINK3 (Т), LLINK3 (Т) ч- NULL и перейти к шагу 5. Если LLINK2 (Т) NULL, то установить R ч- LLINK2 (Т); LLINK2 (Т) ч- NULL и перейти к шагу 5. Если LLINK1 (Т) NULL, то установить R ч- LL1NK1 (Т), LLINK1 (Т) ч-NULL; если S = Q и Т = Q, то перейти к шагу 5 (ие исключать узел точки); в противном случае уничтожить пустой пакет; если LLINK4 (Q) = Т, то установить LLINK4 ч- NULL, AVAIL ч— Т и перейти к шагу 5; в противном случае установить V ч- LLINK.4 (Q), повторять до тех пор, пока BULINK (V) Т: установить V ч- BULINK, BULINK (V) ч— NULL, AVAIL ч- Т и перейти к шагу 5. Перейти к шагу 7 (ошибка — мог быть найден непустой па- кет). 5. [Выполнение, если необходимо, реальной замены. I Если R =/= Р (отметим: если R ~ Р, то мы уже исключили данный эле- мент), то если LLINKI (S) = Р. то LLINK 1 (S) ч- R; в противном случае, если LLINK2 (S) = Р, то LLINK2 (S) ч- Р; в противном случае, если LLINK3 (S) = Р, то LLINK3 (S) ч- R, в противном случае, если S =# Q и LLINK4 (S) = Р, то LLINK4 (S) = R; (ошибка — в S отсутствует ссылка на линию), в противном случае перейти к шагу 7. Трамбле Ж., Соренсон П. 481
6. [Возврат узла линии после исключения ссылки иа нее.] Установить AVAIL ч- Р и завершить выполнение алгоритма. 7. [Возврат по ошибке — не найдены предполагаемые точки и узлы. ] Напечатать соответствующее сообщение и остано- виться. В шаге 1 данного алгоритма узел линии, которая должна быть удалена, исключается из цепочки узлов линий, входящих в за- головок HEAD. Затем, в шагах 3—5 удаляются ссылки на данный узел из двух узлов, соответствующих крайним точкам. В шаге 3 устанавливается указатель S, указывающий иа узел точки или па- кета, в котором найдена ссылка на необходимую линию. Если ссылка на найдена, то это является сигналом об ошибке. В шаге 4 устанавливается указатель S, указывающий на последний узел (точки или пакета), содержащий любые ссылки на линии. При наличии установленного указателя Т в нем отыскиваются поля ссылок на линии, соответствующие последней ненулевой ссылке. Эта ссылка запоминается в R, а поле устанавливается в NULL. Если в результате происходит опустошение узла памяти, этот узел пакета возвращается в имеющуюся область памяти. Если в Т ссылок не найдено, то это свидетельствует об ошибке. В шаге 5 ссылка на линию в узле S заменяется последней ссылкой на ли- нию R, полученной из узла Т. Заметим, что, если последняя ссылка в Т была на самом деле ссылкой иа удаленную линию, никакой замены делать не нужно. Ссылке уже было присвоено значение NULL. Данный шаг затем повторяется для другой крайней точки. В шаге 6 несвязанные узлы лииий возвращаются в имею- щуюся область памяти, и управление возвращается вызывающей процедуре. Шаг 7 служит ловушкой ошибок, которая останавли- вает выполнение алгоритма, если структура данных не удовлетво- ряет необходимым требованиям (возможно, до тех пор, пока поль- зователь установит корректно обновляемые указатели). Выбранный метод исключения ссылок на линии, а именно пере- запись ссылок с использованием последней ссылки иа линию в на- боре узлов точек, обладает определенными преимуществами. Во- первых, он позволяет аннулировать пустые места в остающемся списке ссылок иа линии в узле точки и, во-вторых, улучшить уп- равление памятью, поскольку могут быть выданы пустые пакеты, когда они в дальнейшем не используются. Алгоритм LDELETE. Заданы главный узел структуры HEAD и координаты крайних точек Yj, Х2, Y2. Алгоритм LDELETE отыскивает узел, соответствующий данной линии, и использует функцию LINEDELETE для удаления этого узла и ссылок на него; Р является переменной типа указателя. I. [Поиск крайних точек. Остановить Р ч— LLINK (HEAD). 2. [Проверка крайних точек, соответствующих аргументам. ] Если Р = HEAD, то напечатать ’ОШИБКА — ОТСУТ- СТВУЕТ ПАРНАЯ КРАЙНЯЯ ТОЧКА’и завершить выполне- ние алгоритма; 482
в противном случае, если X (POINTI (Р)) = X, Y (POINT! (Р)) = Yj, X (POINT2 (Р)) = Х2 и Y (POINT2 (Р))= = У2 или X (POINTI (Р)) = Х2, Y (POINTI (Р)) = к X (POINTS (Р)) = Хг и Y (POINT2 (Р)) = Yn то перейти к шагу 3; в противном случае установить Р ч- LLINK (Р) и повторить шаг 2. 3. [Удаление узла.] Вызвать LINEDELETE и завершить выполнение алгоритма. Алгоритмы PDELETE и ENTDELETE оставлены в качестве упражнений. При использовании приведенной структуры данных преобразо- вание изображения или подкартины является относительно про- стым. Для этого требуется лишь умножить матрицу TMATRIX главного узла на матрицу преобразования, соответствующую желаемому преобразованию, и занести новое значение в поле TMATRIX. Тем самым мы устанавливаем TMATRIX-е- TMAT- RIX * [новое преобразование]. Для того чтобы отобразить изображение, хранящееся в этой структуре данных, вектор каждой точки из набора точек умно- жается на поле TMATRIX главного узла изображения, и создается соответствующая линия или точка требуемого изображения. Элементы подкартины отображаются рекурсивно; матрица пре- образования, применяемая к точке, является произведением поля TMATRIX главного узла подкартины и поля TMATRIX главного узла изображения. Алгоритм DISPLAY. Задан главный узел PICTURE изобра- жения, которое должно быть отображено, и матрица преобразо- вания МАТ, соответствующая представляемому уровню. Алго- ритм DISPLAY рекурсивно просматривает структуру данных, с тем чтобы построить визуальное представление изображения. Предполагается наличие двух стандартных программ: UNEWRITE (Х15 Yj, Х«, Y2), создающей на графопостроителе линию с крайними точками (Х„ Y,) и (Х.21 Ya), и POINTWRITE (L. V), создающей иа том же самом устройстве точку с коордииа- тами (U, V). Ссылка основного уровня в DISPLAY в качестве ар- гумента для МАТ должна иметь единичную матрицу. 1. [Построение соответствующей матрицы преобразования.] Установить МАТ <- TMATRIX (PICTURE) * MAT. 2. [Просмотр цепочки линий для отображения линий. ] Уста- новить Р . - LLINK (PICTURE). Повторять до тех пор, пока Р yt PICTURE: установить (Х„ Y„ п] [X (POINTI (Р)), Y (POINT2 (Р)), 11 * МАТ; если п /= I, то напечатать 'НЕКОРРЕКТНАЯ МАТРИЦА’ и завершить выполнение алгоритма; установить [Х2. Y., п] [X (POINT2 (Р)), Y (POINT2 (Р)), 11 * МАТ; 483
Если n #= 1, то напечатать ’НЕКОРРЕКТНАЯ МАТРИЦА’ и завершить выполнение алгоритма; вызвать LINEWRITE (Хц, Ylf Х2, Y2) и установить Р ч-RLINK (Р). 3. [Просмотр цепочки точек для отображения точек, не яв- ляющихся крайними-! Установить Р •<- PLINK (PICTURE). Повторять до тех пор, пока Р =£ PICTURE: если LLINK1 (Р) = NULL, то установить [U, V, n] [X (Р), Y (Р), 11* МАТ; если’п =# 1, то напечатать ’НЕКОРРЕКТНАЯ МАТРИЦА’ и завершить выполнение алгоритма; вызвать POINTWRITE (U, V); установить РRLINK (Р). 4. [Просмотр цепочки элементов с их рекурсивным отображе- нием.] Установить Р EL INK (PICTURE). Повторять до тех пор, пока Р =/= PICTURE: вызвать DISPLAY (MLINK (Р), TMATRIX (Р) * МАТ); установить Р RLINK (Р). 5. [Выход.] Завершить выполнение алгоритма. Упражнения к п. 5-5.2 1. Разработайте алгоритм, который при заданных указателе глав- ного узла HEAD и координатах точки X] и Y, удаляет узел, соответствующий точке, и все линии, для которых данная точка является крайней. 2. Заданы главный узел подкартины ENT и-матрица преобразования TRANS. Сконструируйте алгоритм, отыскивающий узел элемента, на который делается ссылка по ENT и матрица преобразования которого TMATRIX=TRANS, и уда- ляющий этот узел. 3. Сформулируйте алгоритмы PDELETE и ENTDELETE, на которые де- лается ссылка в конце п. 5-5.2. 5-5.3. Символическое дифференцирование Одним из наиболее ранних приложений обработки сим- волов на базе ЭВМ является алгебраическое дифференцирование. Программы получения производной алгебраического выражения были написаны в начале 50-х годов. В данном пункте рассматри- вается формулировка алгоритма для данного приложения. Кратко задача может быть описана следующим образом. За- даны правила дифференцирования по х: D (X) = I; D (а) = 0; а — константа или переменная, отличная от х; D (In u) = D (и)/и; D (—и) = —D (и); D (и + v) « D (и) + D (v); D (и — v) = D (и) — D (v); D (и * v) — D (и) * v 4- D (у) * и; D (u/v) — D (u)/v — (u#D (v))/v2. 484
рис. 5-5.15. Структура вершины для символи- ческого дифференцирования P~DPTR | SYM | TYPE | RPTR [ Необходимо построить алгоритм, воспринимающий алгебраи- ческое выражение, являющееся комбинацией корректных опера- торов и операндов, и строящий производную этого выражения по заданной переменной. В. данной задаче мы предполагаем, что корректными операто- рами являются LN (натуральный логарифм), 6 (унарный минус), , — , * и 7 ; корректными операндами являются переменные длиной от одного до пяти символов, первым из которых должна быть буква; константы содержат от одной до пяти цифр, они мо- гут включать в себя десятичную точку и знак минус. Главным аспектом решения указанной задачи являются вы- бор структуры данных, подходящей для представления алгебраи- ческих выражений. Для представления таких выражений будем использовать списковую структуру, что позволит избежать ко- пирования некоторых элементов выражения, необходимого по правилам дифференцирования для умножения и деления, по- скольку эти общие элементы могут быть разделены. В рассматриваемом приложении используется списковая структура без головных узлов. Головные узлы не требуются, по- скольку данная задача не включает удаление элементов. На рис. 5-5.15 представлен типовой узел такой структуры. В данном узле поле DPTR — иерархический указатель, задающий под- списки, RPTR — указатель, задающий отношение физического соседства, SYM — символьное представление элемента, задавае- мого с помощью данного узла (в узле указателя списка это поле является пустой строкой, которая условно изображается на диа- граммах в виде ’@,’), a TYPE является кодовым номером, указы- вающим тип элемента. TYPE имеет следующие значения: Значение Смысл О Узел указателя списка 1 константа 2 переменная 3 4" (сложение) 4 — (вычитание) 5 * (умножение) 6 / (деление) 7 # (оператор возведения в степень) 8 0 (унарный минус) 9 NL (натурный логарифм) Заметим, что символ фф, означающий возведение в степень, отсутствует во входном выражении, однако он используется в про- изводной и поэтому приведен здесь. При использовании указанного спискового представления аждый список содержит на уровне 1 два или три элемента. Пер- «и элемент представляет оператор, второй означает первый one- 485
и ЙЛПМЗзИ Й4-|31 +~*Иа1г1+- Рис. 5-5.16. Списковое представление выражения (а 4- х) * 3.1 -j- In (бу) ранд, а третий элемент, существующий только для бинарных опе- раций, означает второй операнд. На рис. 5-5.16 показано представление в виде списковой струк- туры выражения (а + х) * 3.1 -j- In (бу). Приведем несколько простых алгоритмов, которые будут ис- пользованы в основной программе дифференцирования. Первый из них — алгоритм копирования списковой структуры, представ- ляющей одиночный операнд. Алгоритм COPY. Задан указатель ROOT элемента списковой структуры. Алгоритм COPY строит новый узел, поля которого устанавливаются равными значениям соответствующих полей уз- ла ROOT, за исключением поля RPTR, которому присваивается значение NULL. Значением COPY является адрес заново построен- ного узла; Р — переменная типа указателя. 1. [Контроль корректности операнда ] Если ROOT = NULL, то установить COPY ч- NULL и завершить выполнение алгоритма. 2. [Построение нового узла.] Установить Р «= NODE, SYM (Р) ч- SYM (ROOT), TYPE (Р)ч-ТУРЕ’(РООТ), DPTR (Р)ч- ч-’DPTR (ROOT), RPTR (P) ч- NULL и COPY ч- P. Если, операнд — это константа или переменная, т. е. одиноч- ный узел, то он просто копируется. Если операнд не является ато- мом, то, тем не менее, содержимое узла списка, за исключением поля RPTR, копируется во вновь созданный узел. Поле RPTP нового узла устанавливается в NULL. Результат реализации дан- ного алгоритма для структуры, изображенной на рис. 5-5.17, показан штриховой линией. ROOT COPY И+131 —I—*•! 11I ° I Я—d/Ы2 И ((а’Ь)+х> Рис. 5-5.17. Списковое представление результата выполнения алгоритма COPY 486
Алгоритм MAKE—NODE формирует узел списка и присваи- вает полю TYPE значение, которое зависит от типа символа (кон- станта, переменная и т. п.), передаваемого в алгоритм. Алгоритм МАКЕ—NODE. Задана переменная VAL, которая включает символьное представление алгебраического элемента типа константы или оператора. Данный алгоритм формирует узел, в котором указателям DPTR и RPTR присваивается значение NULL. Поле TYPE этого узла определяется по VAL. Адрес но- вого узла присваивается идентификатору МАКЕ_____NODE; X — переменная типа указатель; OPS —- строка, являющаяся буквен- ным представлением корректных операторов, a DIGITS — строка, содержащая буквенное представление контстант. 1. [Инициализация.] Установить OPS ч- ’ 4----* AftOLN* и DIGITS ч- ’ — . 0123456789’. f 2. [Построение узла. ] Установить X <= NODE, DPTR (X) ч- <- RPTR (X) ч- NULL, SYM (X) ч- VAL, TYPE (X) ч- 0. 3. [Определение типа узла. ] Если VAL ", то установить TYPE (X) ч- INDEX (OPS, l/AL); . если TYPE (X) ф 0, то установить TYPE (X) ч- TYPE (Х)4- 2: в противном случае, если INDEX (DIGITS, SUBTR (VAL, 1, 1)) =/= 0, то TYPE (X) ч- 1, в противном случае TYPE (X) ч-2. 4. [Конец.] Установить МАКЕ___NODE ч- X и завершить выполнение алгоритма. Два приведенных ниже алгоритма представляют собой спе- циальные алгоритмы построения списков с двумя или тремя эле- ментами. Алгоритм MAKELIST2. Заданы два указателя N1 и N2 списко- вых структур. Данный алгоритм объединяет их так, что в резуль- тирующем списке N1 предществует N2. Строится также узел ука- ^|^еля списка, который указывает на N1. Используется алгоритм L [Объединение узлов. 1 Установить RPTR (N1) ч— N2. 2. [Построение головного узла. ] Установить MAKELIST2 ч~ MAKE . NODE ("), DPTR (MAKELIST2) ч- N1 и завершить выполнение алгоритма. Алгоритм MAKELIST3. Заданы три указателя NI, N2 и N3 Исковых структур. Данный алгоритм объединяет их так, что N1 вредществует N2, a N2 предшествует N3. Строится также узел указателя списка, который указывает на N1. Используется ал- Г0Рнтм MAKE JMODE. г>1- [Объединение узлов.] Установить RPTR (N1) ч- N2, ^PTR (N2) 4-N3. 487
2. [Построение головного узла.] Установить MAKELIST3 ч- ч-МАКЕ_____NODE ("), DPTR (МАКELIST3) ч-N1 и завершить выполнение алгоритма. Из приведенных ранее правил дифференцирования легко ви- деть, что прежде, чем применить правило, соответствующее не- которому частному оператору, необходимо найти производную операнда (операндов). Этот процесс легко описывается рекурсив- ным способом внутри процедуры обработки списка. Общая стра- тегия дифференцирования заключается в следующем: 1) поиск оператора; 2) дифференцирование первого операнда; 3) дифференцирование второго операнда, если он имеется; 4) применение подходящего правила. Алгоритм DIFFER. Заданы адрес HEAD корневого узла спис- ковой структуры (подобно данному на рис. 5-5.16), представляю- щей алгебраическое выражение, и переменная VAR, по которой должно быть продифференцировано выражение. Данный алго- ритм строит списковую структуру, представляющую производную. Значение, возвращаемое через имя алгоритма, является адресом списковой структуры производной. SAVE является переменной- указателем на узел одиночной константы, либо узел переменной в выражении, либо на операторный узел. КЕЕР1 и КЁЕР2 — указатели на производные первого и второго операндов, если опе- ранды имеются. ОР2 — указатель на копию второго операнда (если он имеется). В данном алгоритме используются описанные ранее алгоритмы COPY, MAKE—NODE и MAKELIST2. 1. [Контроль нулевого выражения.] Если HEAD_______NULL, то установить DIFFER NULL и завершить выполнение алго- ритма. 2. [Является ли текущая вершина вершиной указателя списка? Если TYPE (HEAD) = 0, то установить SAVE ч— DPTR (HEAD); в противном случае установить SAVE ч- HEAD. 3. [Текущая вершина является константой? ] Если TYPE (SAVE) — 1, то установить DIFFER ч- MAKE—NODE (’О’) и завершить выполнение алгоритма. 4. [Текущая вершина является переменной?] Если TYPE (SAVE) - 2, то если SYM (SAVE) ~ VAR, то установить DIFFER4-MAKE_____NODE (’1’) и завершить выполнение алгоритма. 5. [Получение операнда (операндов) оператора. 1 Установить КЕЕР1 ч- DIFFER (RPTR (SAVE), VAR). Если RPTR (RPTR (SAVE)) y= NULL, то установить KEEP2 ч- ч- DIFFER (RPTR (RPTR (SAVE)), VAR). 6. [Сложение? I Если TYPE (SAVE) = 3, то установить DIFFER 4- MAKELIST3 (MAKE—NODE (’+’). KEEP1, KEEP2) и завершить выполнение алгоритма. 7. [Вычитание? ] Если TYPE (SAVE) = 4, то установить DIFFER ч- MAKELIST3 (MAKE-NODE (’—’), КЕЕР1, КЕЕР2) и завершить выполнение алгоритма. 488
8. (Умножение?! Если TYPE (SAVE) = 5, то KEEP! <- MAKELIST3 (MAKE NODE (’*’), КЕЕР1, COPY (RPTR (RPTR (SAVE)))). KEEP2 4-MAKELIST3 (MAKE NODE (’»’) KEEP2, COPY (RPTR (SAVE))), установить DIFFER <- 4_MAKELIST3 (MAKE—NODE (’-[-’), KEEP1, KEEP2) и за- вершить выполнение алгоритма. 9. [Деление?] Если TYPE (SAVE) = 6, то установить ОР2ч- COPY (RPTR (RPTR (SAVE))), KEEP1 MAKEUST3 (MAKE-NODE (7’), KEEP1, OP2), TEMPMAKELIST3 (MAKE—NODE (’*’), COPY (RPTR (SAVE)), KEEP2), ТЕМР2ч- -F- MAKELIST3 (MAKE—NODE (’#’), OP2, MAKE—NODE (’2’)), KEEP2 4- MAKELIST3 (MAKE—NODE (7’), TEMP, TEMP2), DIFFER MAKELIST3 (MAKE—NODE (’—’), KEEP1, KEEP2) и завершить выполнение алгоритма. 10. [Унарный минус? ] Если TYPE (SAVE) = 8, то устано- вить DIFFER ч - MAKELIST2 (MAKE—NODE (’О’), КЕЕР1) и завершить выполнение алгоритма. 11. [Натуральный логарифм?] Если TYPE (SAVE) =9, то установить DIFFER ч- МАKELIST3 (MAKE—NODE (7’)> КЕЕР1, COPY (RPTR (SAVE))) и завершить выполнение алго- ритма; в противном случае напечатать ’ОШИБКА’ и завершить вы- полнение алгоритма. Данный алгоритм является прямым и отвечает введенным ра- нее правилам дифференцирования. Шаги 3 и 4 соответствуют слу- чаям константы и переменной. В шаге 5 получается операнд (операнды), связанный с частным оператором. В случае унарного минуса требуется только один операнд. Дифференцирование для четырех арифметических операторов осуществляется в шагах 6—9. Шаг 10— это обработка в случае унарного минуса, а последний шаг соответствует натуральному логарифму. Упражнения к п. 5-5.3 1. Модифицируйте алгоритм DIFFER так, чтобы ввести правило дифференцирования для возведения в степень, задаваемое следующим образом: D (и t v) = D (и) X (v >: и f (v — 1))) -F (1ч (и) X D (v)) X (и f v). 2. Модифицируйте алгоритм, полученный в упр. I, так. чтобы включить тригонометрические функции sin (u), cos (и), tang (и) и т. д. 3. Оптимизируйте алгоритм, полученный в упр. 2, таким образом, чтобы Упростить выражения типа (х 4 0), (х— 0), (0 — х), (1 X х), (х/1), (х f 1), (X Г0) И др. 5-5.4. Топологическая сортировка Наиболее раннее применение топологической сорти- ровки с использованием ЭВМ было связано с анализом сетей, например, с такими методами, как метод сетевого планирования PERT. PERT-граф содержит направленные ребра, соответствую- щие активным фазам работ, и вершины, соответствующие событиям. 489
Рис. 5-5.18. Пример топологической сортировки Рис. 5-5.19. Взаимосвязь между определениями тер- Для перемещения из одной вершины в другую должна быть выпол- нена определенная активная работа. PERT-граф используется для анализа взаимосвязанных активных работ комплексных проектов. Активные работы могут быть выполнены параллельно, а одно событие может быть начальной или конечной точкой для некоторого числа активных работ. Целью топологической сорти- ровки является линейное упорядочение событий, т. е. событиям должны быть приданы номера: первое, второе, третье и т. д., с учетом ограничения, что событие не может предшествовать другому событию, которое должно иметь место ранее. В общем случае при топологической сортировке предполагается линейное упорядочение тех вершин ориентированного графа, которые обладают следующим свойством: если вершина Р является предшественником вершины Q, то Q не может быть предшествен- ником вершины Р. Это свойство должно иметь силу, даже если Р = Q, т. е. Р не может быть своим собственным предшествен- ником или преемником. Другими словами, топологическая сорти- ровка ие может связывать вершины в циклы. В качестве примера результата топологической сортировки рассмотрим рис. 5-5.18. На рис. 5-5.18, а изображен граф, не со- держащий циклов, а на рис. 5-5.18, б дано линейное упорядоче- ние, которое могло бы быть возможным результатом топологиче- ской сортировки, примененной к данному графу. Преемник лю- бой вершины Р всегда появляется справа от Р (отметим, что мо- гут быть определены другие варианты линейного упорядочения вершин). Другим применением топологической сортировки является упорядочение терминов, которые должны быть определены в книге. Предположим, что мы имеем набор терминов Tj, Т2, ...» Тп, ко- торые связаны в пары (Т5, lj) таким образом, что Tj используется при определении Т3-. Мы хотим упорядочить термины так, чтобы 490
любой термин Tj появлялся раньше термина Tj, и затем использо- вать Tj прямо или косвенно для определения Tj. Возможно цик- лическое определение, например (Tj, Tj), (Tj, Tk) и (Tk, Т>). В этом случае полная линейная упорядоченность не может быть установлена. Рассмотрим для примера рис. 5-5.19; на нем показана взаимосвязь между определениями записи, файла, поля, ключа и операции (найденные в двух различных книгах.) Ребро из терми- на Tj в Tj проводится в том случае, если Tj используется в оп- ределении Tj. На рис. 5-5.19, а линейное упорядочение не может быть установлено. На рис. 5-5.19, б топологическая сортировка может установить следующее линейное упорядочение: запись—поле—файл—ключ—транзакция Альтернативным упорядочением является за пись—файл—поле—ключ—транзакция Несмотря на то, что мы не можем удачно применить тополо- гическую сортировку, полезно попытаться провести ее для рис. 5-5.19, с целью обнаружения циклических опеределений. В общем случае с помощью топологической сортировки мы хотим установить линейное упорядочение для набора вершин, идентифицируемых дескрипторами, при заданном наборе ребер, идентифицируемых упорядоченными парами дескрипторов. Ли- нейное упорядочение заключается в том, что преемники дескрип- торов появляются после этого дескриптора. В том случае, когда невозможно задать полное линейное упорядочение, должны быть найдены пружины возникновения циклов среди вершин и де- скрипторов. В данном пункте мы приведем алгоритм топологиче- ской сортировки вершин с дескрипторами любого типа (строки символов или числа). Мы приведем также алгоритм, обнаружи- вающий цикл, приводящий к неудачной сортировке.Несмотря на то, что здесь мы имеем дело только с примерами небольшой раз- мерности, количество дескрипторов или вершин в стандартных приложениях топологической сортировки может быть порядка нескольких тысяч. Граф, к которому применяется топологическая сортировка, может быть представлен с помощью структуры типа той, которая показана на рис. 5-5.20. На этом рисунке дано представление графа, изображенного на рис. 5-5.19, б. Каждому дескриптору, заданному на рис. 5-5.19, б, приписана величина i, лежащая в диапазоне от 1 до 9. Это приписывание выполнено с использованием процедуры хеширования, которая будет кратко описана ниже. Значение i используется для органи- зации ссылок па массивы элементов DESPTR П ], PREDCOUNT ГН, TAG 11] и SUCLIST [i ]. Элемент DESPTR 11] представляет собой указатель на вершину DESCRIPTOR, содержащую дес- криптор, соответствующий i. PREDCOUNT li 1 задает количество непосредственных предшественников, которые имеют данный де- 491
descriptor DESCRIPTOR NODE 'SUCCESSOR NEXTSDC, SUCCESSOR-MODE Рис. 5-5.20. Структура данных для представления графа скриптор. SUCLIST li] является указателем на односвязный спи- сок структуры SUCCESTOR__NODE, содержащий номера, соот- ветствующие непосредственным преемникам дескриптора, которому присвоен номер i. Использование TAG [i ] будет описано ниже. Заметим, что позиции 2, 4, 6 и 9 в массивах не используются. Эти пустые ячейки необходимы, так как эффективный алгоритм сорти- ровки основан на том, что функция хеширования должна иметь возможность адаптироваться при выборе позиции в массиве для каждого дескриптора. Алгоритм формулируется следующим об- разом. Алгоритм STORE. Мы задаем структуру, примером которой служит структура на рис. 5-5.20. Структура содержит m элементов массива и в текущий момент времени имеет п дескрипторов. Задан дескриптор STRING, который требуется для поиска строки STRING в структуре и возврата позиции в массиве, соответствую- щей этой строке. Если строка STRING не может быть найдена, то она назначается на неиспользуемую позицию в массиве, п увеличивается на 1, а позиция в множестве возвращается. В про- цессе поиска используется функция хеширования HASH. Пере- менные дик являются вспомогательными индексами. 492
1. [Инициализация поиска.] Установить <1 < - к-HASH (STRING) (HASH возвращает значение, лежащее в диапазоне от 1 ДО ш включительно). 2. [Проверка, не указывает ли указатель дескриптора на не- используемую ячейку массива. | Если DESPTR [k] = NULL то установить Р <= DESCRIPTOR—NODE, DESCRIPTOR (Р) Д STRING, DESPTR [k]«-P, ni-n + 1, STORE к и за- вершить выполнение алгоритма. 3. [Проверка, соответствует ли дескриптор строке STRING.] Если DESCRIPTOR (DESPTR [k] = STRING, то установить STORE k и завершить выполнение алгоритма. 4. [Обновление и проверка индекса.] Установить к ч-к 4- 1. Если к > т, то установить к к ч- 1. Если к = d, то печа- тать ’ПЕРЕПОЛНЕНИЕ ДЕСКРИПТОРНОГО Л1АССПВА — ЗАДАЙТЕ БОЛЬШИЙ МАКСИМАЛЬНЫЙ ПРЕДЕЛ ДЛЯ ЧИСЛА ДЕСКРИПТОРОВ’ и завершить выполнение алгоритма; в противном случае перейти к шагу 2. Функции хеширования, которые могут быть использованы для установления соответствия между дескриптором STRING и чис- лами от 1 до гп, описаны в п. 4-3.2. Механизм поиска, исполь- зуемый в данном алгоритме, называется линейным опробованием. При этом последовательно сканируются ячейки массива до тех пор, пока не будет найден STRING или пока не встретится пустая ячейка массива. Если индекс массива к когда-либо превысит гп, то он устанавливается в 1. В этом случае, если необходимо, мо- жет сканироваться входной массив. Если все гп позиций массива просмотрены и дескриптор STR ING или пустая ячейка не найдены, то печатается сообщение об ошибке, и выполнение алгоритма прекращается. В том случае, когда число дескрипторов п при- мерно равно гп, линейное опробование не является эффективным методом поиска. Целесообразно выбирать значение гп по крайней мере на 25% больше, чем значение п. Алгоритм STORE используется совместно с приводимым ни- же алгоритмом, входами которого являются пары дескрипторов и который базируется на структуре, иллюстрируемой рис. 5-5.20. Алгоритм STORE вызывается с помощью присваива- ния: i < STORE (STRING). Предполагается, что структура, представленная на рис. 5-5.20, и параметры тип являются общими для всех алгоритмов, рас- сматриваемых в данном параграфе. Алгоритм CONSTRUCT. Задан набор пар дескрипторов (PRED, SUC) такой, что PRED является непосредственным пред- шественником SUC. Требуется построить структуру, подобную представленной на рис. 5-5.20. Число уникальных дескрипторов п вычисляется в процессе выполнения алгоритма. Пользователь дан- ного алгоритма должен гарантировать, чтобы количество ячеек 493
массива m было больше максимального значения п или равно ему. 1. [Инициализация массива.] Установить п 0. Повторять при к = 1, 2, ш: установить DESPTR (к) ч— SUCLIST [к] ч— NULL и PREDCOUNT (к) 0. 2. [Считывание следующей пары предшественник — преем- ник. ] Считать PRED и SUC. Если больше нет входных данных, то завершить выполнение алгоритма. 3. [Сохранение или поиск дескрипторов и определение их индексов. ] Установить i ч— STORE (PRED) и j ч- STORE (SUC). 4. [Модификация счетчика предшественников преемника. ] Установить PREDCOUNT [j ] <- PREDCOUNT [j]-|-l. 5. [Модификация списка преемников предшественника. I Установить Q <= SUCCESSOR—NODE, SUCCESSOR (Q) ч- j, NEXTSUC (Q) <- SUCLIST [i ], and SUCLIST [i ] 0. Перейти к шагу 2. Для того чтобы построить структуру типа структуры, изобра- женной на рис. 5-5.20. алгоритмом CONSTRUCT должны быть считаны приведенные ниже пары дескрипторов. Эти пары соответ- ствуют ребрам графа, представленного на рис. 5-5.19, б: ключ ТРАНЗАКЦИЯ ПОЛЕ ключ ЗАПИСЬ ПОЛЕ ЗАПИСЬ ключ ЗАПИСЬ ФАЙЛ ФАЙЛ ключ ЗАПИСЬ ТРАНЗАКЦИЯ ПОЛЕ ТРАНЗАКЦИЯ Теперь, когда имеется структура представления ориентирован- ного графа, алгоритм топологической сортировки получается весьма простым. Дескрипторы должны быть выведены на печать в соответствии с линейным порядком, получаемым при сортировке. Если значение счетчика предшественников дескриптора равно нулю, то оно выводится на печать. После этого значения счетчиков предшественников каждого преемника должны быть уменьшены на единицу. Каждый раз, когда значение счетчика дескрипторов предшественника достигает нуля, его надлежит вывести на печать. Поскольку одновременно значение счетчика предшественников может быть равным нулю для нескольких дескрипторов, возни- кает необходимость в механизме контроля, назначением которого является сохранение следов этих дескрипторов. Мы используем для этого связанные очереди. Для того чтобы добавить элемент в конец этой очереди, можно применить алгоритм, приведенный ниже. Алгоритм LQINSERT. Задано число к, соответствующее не- которому дескриптору таким образом, как это было определено в алгоритме CONSTRUCT. Требуется добавить к к концу связан- ной очереди, указателями начала и конца которой являются F и R 494
соответственно. Каждый элемент очереди QUEUE____NODE имеет информационное поле V и поле связи LINK. 1. [Локализация элемента очереди.] Установить Р ч- ч- QUEUE—NODE, V (Р) +- к и LINK (Р) ч- NULL. 2. [Добавление элемента в конец очереди.] Если R = NULL, то установить F ч- R Р; в противном случае установить LINK (R) Р и R +- Р. 3. [Выход.] Завершить выполнение алгоритма. Данный алгоритм может быть вызван путем использования обра- щения LQINSERT [к]. Уничтожение первого элемента очереди является весьма простой задачей и должно включаться в ниже- следующий ал гор и™. Алгоритм TOPOLOGICAL—SORT. Задана описанная выше структура представления ориентированного графа. Требуется вывести на печать'дескрипторы вершин в линейном порядке так, чтобы каждый дескриптор печатался перед его наследником. Для реализации этого используются очередь и алгоритм LQINSERT; п является глобальной переменной. 1. [Начальная установка указателей очереди.] Установить F ч- R ч- NULL. 2. [Просмотр дескрипторов без предшественников и добавле- ние их к очереди.] Повторять при k = 1, 2, ..., т: если DESPTR [к] =£ NULL и PREDCOUNT [к] = О, то вызвать LQINSERT (к). 3. [Обработка каждого дескриптора из очереди.] Повторять шаги 4 и 5 до тех пор, пока F NULL. 4. [Вывод на печать и исключение из очереди первого дескрип- тора. ] Установить к ч— V (F), п ч- п — 1, F ч- LINK (F) и печа- тать DESCRIPTOR (DESPTR [к]). Установить DESPTR [к] ч- NULL. Если F ч- NULL, то установить R ч- NHLL. 5. [Просмотр списка преемников напечатанного дескриптора. ] Установить Q ч-SUCLIST [к ] и SUCLIST [к ] ч-NULL. По- вторять до тех пор, пока Q NULL: установить PREDCOUNT [SUCCESSOR (Q) ] ч- PREDCOUNT [SUCCESSOR (Q) ] — 1; если PREDCOUNT [SUCCESSOR (Q) ] = 0, то вызвать LQINSERT [SUCCESSOR (Q) ]. Установить Q <- NEXTSUC (Q). 6. [Выход. ] Завершить выполнение алгоритма. • В шаге 4 значение п уменьшается на единицу каждый раз, когда дескриптор выводится на печать. Если после завершения работы алгоритма п не равно нулю, то это означает, что п дескрипторов не включены в линейное упорядочение и между ними существует' по крайней мере один цикл или имеется петля. Для отыскания дескрипторов, входящих в цикл, используется алгоритм DETECT_ CYCLE. Алгоритм DETECT _ .CYCLE. Задана структура, аналогичная изображенной на рис. 5-5.20, с одним или несколькими дескрип- торами, входящими в один или несколько циклов. Требуется об- наружить и вывести на печать дескрипторы, образующие цикл. 495
1. [Инициализация массива.] Повторять при i = 1, 2, : установить PREDCOUNT [i 1 ч- О и TAG [i ] ч- false. 2. [Проверка дескрипторов и обработка тех из них, которые не выведены на печать.] Повторять шаг 3 при 1 = 1,2, ..., ш. 3. [Засылка индекса предшественника в элемент PREDCOUNT наследника.] Если DESPTR [i ] =Д NULL, то установить Р ч— SUCLIST [i ], SUCLIST [i ] ч— NULL и повторять до тех пор, пока Р NULL: если PREDCOUNT [SUCCESSOR (Р) ] = 0, то установить PREDCOUNT [SUCCESSOR (P)]^-i. Установить Р ч-NEXTSUC (Р). 4. [Поиск первого 1, для которого дескриптор не был выведен на печать. ] Повторять при i — 1,2, ..., m: если PREDCOUNT [i ]=t= 0, то перейти к шагу 5. 5. [Маркирование предшественников с чистыми полями TAG. ] Повторять до тех пор, пока“[ TAG [i ]: установить TAG [i ] *— ч-true и i ч-PREDCOUNT [i ]. 6. [Оборачивание списка, определенного над маркированным массивом элементов, начиная с PREDCOUNT [i ]. 1 Установить j ч-0. Повторять до тех пор, пока PREDCOUNT [i 1 ф 0; уста- новить к ч- j, j ч-i, i ^PREDCOUNT [j] и PREDCOUNT [j 1 +- -ч-к. Установить PREDCOUNT [j ] ч-j. 7. [Вывод на печать дескрипторов, образующих цикл. 1 По- вторять до тех пор, пока логическое значение TAG [i ]: печатать DESCRIPTOR (DESPTR [i ]), установить TAG [i ] «- false, PREDCOUNT [i ]. 8. [Вывод первого (и последнего) дескриптора цикла.] Печа- тать DESCRIPTOR (DESPTR [i ]) и завершить выполнение алго- ритма. Данный алгоритм выполняется по окончании работы алго- ритма TOPOLOGICAL_____SORT, обусловленного наличием цикла между дескрипторами. Если в шаге 3 указатель SUCLIST [i ] ненулевой, то он указывает на список номеров ненапечатанных дескрипторов. Элемент PREDCOUNT массива дескриптор а-пре- емника устанавливается на данном шаге равным номеру I пред- шественника. Это действие позволяет построить связанный список, в котором доступ к предшественнику может быть обеспечен по значению PREDCOUNT. В шаге 4 отыскивается индекс массива, соответствующего еще не напечатанному дескриптору. Просма- тривается список, содержащий данный индекс, и с использованием элементов TAG маркируются все вершины, которые были изобра- жены на рис. 5-5.20. Если в шаге 5 встречается элемент TAG со значением true, то это означает, что индекс i соответствует перво- му дескриптору полностью маркированного цикла. В шаге 6 осуществляется оборачивание списка, соответствующего данному циклу, с тем, чтобы в шаге 7 дескрипторы могли быть выведены на печать, при этом предшественник должен находиться перед 496
преемником. В шаге 8 печатается первый дескриптор цикла или петли. На рис. 5-5.21, а изображена структура данных, построенная с помощью алгоритма CONSTRUCT, применительно к следующим парам дескрипторов: ПОЛЕ КЛЮЧ ЗАПИСЬ ПОЛЕ ЗАПИСЬ КЛЮЧ ЗАПИСЬ ПОЛЕ ЗАПИСЬ ТРАНЗАКЦИЯ ФАЙЛ ТРАНЗАКЦИЯ КЛЮЧ ЗАПИСЬ ПОЛЕ ЗАПИСЬ Указанные пары соответствуют ребрам графа на рис. 5-5.19, а. Эта структура данных после выполнения алгоритма TOPOLO- GICAL___SORT не изменяется, поскольку ни одно значение счет- чиков предшественников элементов не становится равным нулю. На рис. 5-5.21, б показано состояние массивов структуры данных после выполнения шага 3 алгоритма DETECT____CYCLE. На данном рисунке значения true и false полей TAG заданы как 1 и 0 соответственно. Ненулевые значения в массиве PREDCO- UNT индицируют предшественника дескрипторов, соответствую- щих ячейкам массива. На рис. 5-5.21, показано состояние мас- сивов после шага 5 данного алгоритма. TAG [1 ] и TAG [51, уста- новленные в 1, индицируют элементы цикла. После того как выпол- нен шаг 6 алгоритма DETECT_CYCLE, массивы остаются преж- ними. Поскольку обнаруженный цикл содержит только два дескриптора, нет необходимости в изменении их порядка следо- вания, так как в двухэлементном цикле некоторый элемент всегда является как предшественником, так и преемником другого эле- мента. В шагах 7 и 8 выводится на печать КЛЮЧ ЗАПИСЬ ключ и поля TAG сбрасываются в состояние false, как это показано иа рис. 5-5.21, б. Последний алгоритм иллюстрирует применение предыдущих алгоритмов для топологической сортировки с обнаружением цик- лов. Алгоритм TOP SORT. Заданы массивы DESPTR, PREDCOUNT, TAG и SUCLIST и структуры элементов DESCRIPTOR____NODE, QUUE____NODE и SUCCESSOR____NODE в том виде, как эго опреде- лялось ранее. В процессе выполнения алгоритма делается попытка произвести топологическую сортировку, и если не удается уста- новить полного линейного упорядочения, то отыскивается цикл среди дескрипторов. 1. [Инициализация структуры данных.] Считать ш и размес- тить в каждом массиве m элементов. Вызвать CONSTRUCT. 497
Рис. 5-5.21. Результаты обработки структуры данных, представленной на рис. 5-5.19, а 2. [Попытка сортировка дескрипторов.] Вызвать TOPOLO- GICAL—SORT. 3. [Необходим поиск циклов? ] Если п > 0, то вызвать DETECT .CYCLE. 4. [Выход.] Завершить выполнение алгоритма. Необходимо напомнить, что массивы, структуры элементов и переменные тип являются глобальными для всех алгоритмов данного пункта. 498
Упражнения к п. 5-5.4 1. Перечислите все верные линейные упорядочения, которые мо- гут быть получены в том случае, когда топологическая сортировка проводится по отношению к графу, представленному на рис. 5-5.18, а. 2. Проследите работу алгоритма TOPOLOGICAL—SORT, используя ори- ентированный граф рис. 5-5.18, а, в предположении, что ребра читались алго- ритмом в следующем порядке: (7,5), (5,3), (3,6), (5,6), (2,1), (2,6), (4,2). 3. Исключите ребро, соединяющее элементы КЛЮЧ и элемент ЗАПИСЬ на рис. 5-5.19, и постройте трассу алгоритмов CONSTRUCT, TOPOLOGICAL— SORT и DETECT—CYCLE, как это показано на рис. 5-5.21. 4. Сможет ли алгоритм TOPOLOGICAL—SORT произвести полное упоря- дочение набора дескрипторов, если две пары дескрипторов, читаемых алгорит- мом CONSTRUCT, дублируют друг друга? Поясните это (предполагается, что соответствующий ориентированный граф дескрипторов не содержит циклов). 5-6. ДИНАМИЧЕСКОЕ УПРАВЛЕНИЕ ПАМЯТЬЮ В ряде предыдущих параграфов мы часто обращались к структурам, для которых требуется некоторого рода управление памятью, удовлетворяющее запросы на память, а также осуществ- ляющее последующее ее освобождение. Так, при описании списков в гл. 4 и деревьев в настоящей главе мы просто создавали новые узлы (вершины), когда они были нужны, не указывая, когда и каким образом освобождать от них память в случае, если они ста- новятся ненужными. Такой же подход был принят при описании строк в гл. 2, ио здесь мы даже не запрашивали память, а просто действовали так, как будто создание любой новой строки всегда сопровождается выделением достаточного для этой строки объема памяти. Указанные упрощения при работе со структурами, естественно, облегчают работу прикладного программиста. Более того, в на- стоящее время без них практически не обойтись в силу большой сложности реализуемых программ. Однако за все приходится чем-то платить, и в данном случае плата заключается в необходи- мости разработки системных программ, которые обеспечивали бы требуемое динамическое управление памятью, а также по-видимому, в увеличении времени исполнения прикладных программ. В связи с этим в настоящем параграфе мы рассмотрим методы и алгоритмы, которые могут быть использованы для создания разных уровней управления памятью. Рассмотрим кратко две простейшие системы. Обсуждаемая проблема оказывается очень простой в языке ФОРТРАН, так как все потребности в памяти здесь точно определены уже во время компиляции исходной программы. Так, на языке ФОРТРАН нельзя написать программу, в которой размеры массива опреде- ляются во время ее исполнения. Необходимо задать границы по всем измерениям массива в самой программе. Рекурсия в ФОРТ- РАНе не допускается. Нет в нем и определяемых программистом структур данных, которые формируются в некоторые моменты ис- полнения программы на основе имеющихся данных. Такие струк- 499
туры, например, создаются в языке СНОБОЛ оператором DATA и оператором присваивания, а в языке -ПЛ/1 — оператором DECLARE и операторами ALLOCATE- В языке ФОРТРАН нет и операций со строками, и поэтому в нем фактически нет средств, которые бы обеспечивали возможность манипулировать ими. Все эти ограничения создают ситуацию, при которой компи- лятор с ФОРТРАНа при трансляции исходной программы может точно определить, какой объем памяти потребуется для ее испол- нения. Задача динамического управления памятью при использо- вания ФОРТРАНа не возникает, так как объем памяти, нужный для каждой программы или подпрограммы, выделяется во время загрузки. Память освобождается только после того, как завершено исполнение всей задачи. По сравнению с ФОРТРАНОМ, в котором нет динамического управления памятью, АЛГОЛ ,60 представляет собой более слож- ную систему, обеспечивающую управление памятью, хотя реально этот процесс не контролируется программистом. Для хранения блоков памяти здесь используется стек; при этом каждый из бло- ков памяти представляет собой активизацию вызова некоторой программы или программного блока. Из-за того, что вызовы программ и программных блоков образуют гнездовую структуру, их активизация устроена по принципу «последним вошел — пер- вым вышел», н поэтому стек является идеальным средством для хранения блоков памяти, используемых в каждый момент испол- нения программы. Программы могут вызываться рекурсивно, что благодаря использованию стека не создает дополнительных труд- ностей (этот вопрос обсуждался в п. 3-7.1). Память для объектного кода программы выделяется во время ее загрузки и остается занятой до тех пор, пока программа не вво- дится нз системы. Стек используется для хранения данных и оперативной управляющей информации о блоках и процедурах, активизируемых во время исполнения программы. Поскольку в АЛГОЛе 60 требуется, чтобы все структуры данных были опи- саны в начале блока, все описания массивов обрабатываются при входе в соответствующую подпрограмму или блок. Такой подход обеспечивает получение информации о размерах массивов до на- чала исполнения блока; последняя совместно с информацией о потребностях в памяти, полученной на этапе компиляции, служит для точного определения объема памяти, необходимого для дан- ного блока. Требуемая область памяти резервируется в оперативном стеке, после чего соответствующий блок программы может испол- няться. После окончательного выхода из блока указатель стека передвигается назад, освобождая тем самым память, зарезерви- рованную для этого блока. Использование стека для обеспечения динамического управ- ления памятью оказывается достаточно удобным, так как вычис- ления, необходимые для определения размеров блоков памяти и обновления указателей, не требуют больших затрат времени. Та- 500
кой стековый механизм довольно часто используется для языков, подобных АЛГОЛу, когда язык имеет блочно-гнездовую структуру, в которой активизация подпрограмм происходит по принципу «последним вошел — первым вышел», и когда при входе в блок во время исполнения программы можно точно определить его потребности в памяти. Имеется, однако, много приложений (обработка списков, гра- . фов или деревьев), в которых сам программист должен отвечать 1'за создание и уничтожение вершин в зависимости от поступающих данных. Кроме того, для многих приложении необходимо распо- лагать более широкими возможностями по управлению строками, чем те, которые обеспечиваются в языке ФОРТРАН или АЛГОЛ; в частности, могут понадобиться строки переменной длины. Как отмечалось в п. 2-4, для строк переменной длины”требуется не- которая специальная форма динамического управления памятью. К рассмотрению этих вопросов мы сейчас и переходим. Существуют многочисленные факторы, влияющие на выбор методов динамического управления памятью, например; статисти- ческое распределение размеров запрашиваемых областей памяти; распределение длин временных интервалов, в течение которых нужно хранить блоки памяти; частота запросов "на память и чис- ток памяти. Кроме того, важно учитывать те свойства языка, которые определяют необходимость динамического управления памятью, а также взгляды разработчика этого языка на степень свободы, предоставляемой пользователю для управления памятью. Большая часть используемых иа практике методов динамического управления памятью представляет собой разновидности описы- ваемых ниже алгоритмов. В простейшем случае всегда запрашивается одно и то же число слов памяти. Такая ситуация может возникнуть в языках |.типа ЛИСП, предназначенных для работы со списковыми струьту- ’ рами, все ячейки (блоки) которых имеют одинаковый формат и раз- мер. В этом случае всю доступную динамическую память можно [ представить в виде последовательности блоков требуемого размера. , Эти блоки можно связать в линейный одноотсылочный список с помощью полей LINK, расположенных в первом слове каждого из блоков. Тогда запрос на блок памяти удовлетворяется с помощью программы, сходной с алгоритмом GET BLOCK. Алгоритм GET___BLOCK. На вход подается указатель на пер- tвый блок описка свободных блоков памяти. Алгоритм выдает ад- fpec Р свободного блока. j 1. [Переполнение?] Если HEAD—NULL, то перейти к про- грамме обработки ошибок. 2. [Резервирование блока. ] Установить Р ч- HEAD, HEAD ч- | •«- LINK (Р) и завершить выполнение алгоритма. | Если в шаге 1 оказывается, что мы уже израсходовали всю па- | Мять (HEAD--NULL), то можно либо закончить выполнение 501
программы, либо попытаться восстановить некоторую область памяти с помощью описанных ниже методов. Возвращение блока в список свободных блоков («свободный список») осуществляется тривиальным образом: он просто добав- ляется в этот список в качестве нового первого блока. Мы, однако, пока не будем останавливаться на вопросе, в каких случаях блоки должны возвращаться в свободный список, а лишь отметим, что в описанной выше схеме для возвращения блока нужен только ад- рес блока Р и указатель на первый блок свободного списка HEAD. Ситуация усложняется в том случае, когда появляются запросы на блоки различного размера. Такая ситуация стандартна при управлении строками или при использовании определяемых поль- зователем структур данных с произвольным размером. В этом слу- чае мы больше не можем рассматривать свободную память как связанный список блоков «подходящего» размера, поскольку та- кого размера в данной ситуации не существует. Вместо этого наш связанный список может содержать блоки различной длины, каждый из которых либо непосредственно резервируется, либо делится на два подблока меньшего размера, один из которых пред- назначен для немедленного резервирования, а другой - - для воз- вращения в свободный список. Однако при использовании блоков переменной длины сталки- ваются с проблемой фрагментации (дробления) памяти; эта про- блема не возникает при использовании блоков постоянной длины. Фрагментация памяти может быть двух типов — внутренней и внешней; речь о них пойдет ниже. Если большое число блоков было сначала запрошено, а затем возвращено, то связанный список свободных блоков может стать довольно длинным (особенно тогда, когда при возврате последо- вательно расположенные блоки не сливаются в один блок). Это означает, что средний размер блока становится небольшим и что скорее всего в свободном списке остается лишь незначительное число больших блоков. В этом случае при получении запроса на большой блок памяти система может выдать отказ, так как в сво- бодном списке может не оказаться нн одного блока достаточно большого размера, хотя общий объем свободной памяти может превышать требуемый. Разбиения всей доступной памяти на боль- шое число относительно небольших блоков называется внешней фрагментацией. Можно попытаться предотвратить внешнюю фрагментацию, осуществляя время от времени резервирование блоков, имеющих размер, больше запрошенного (т. е. путем отказа отделения блока на части, одна из которых может оказаться совсем малой). Если пойти таким путем, то возможна ситуация, когда на некоторый за- прос на память необходимо дать отказ из-за отсутствия блоков тре- буемого размера. Это может случиться даже тогда, когда заре- зервированной, но не используемой памяти более чем достаточно для удовлетворения запроса. Возникновение при таком способе 502
Рис. 5-6.1. Структура головы списка AVAIL—+ S|ZE L|NK резервирования неиспользуемых, но недоступных областей памяти, называется внутренней фрагментацией. Для любого алгоритма управления памятью, применяемого в условиях использования блоков переменной длины, необходимо стремиться тем или иным способом минимизировать неэффектив- ность использования памяти, возникающую вследствие фрагмен- тации. Именно фрагментация делает эти алгоритмы более слож- ными, чем алгоритмы, работающие с блоками фиксированной длины. Кроме того, из-за произвольности размеров блоков каж- дый блок, за исключением поля LINK, обеспечивающего связы- вание блоков в списковую структуру, содержит еще поле SIZE, в котором записывается его текущий размер. Из соображений удобства будем считать, что это поле либо находится в первом слове каждого блока, либо непосредственно доступно, если изве- стен адрес первого слова блока. Пусть голова свободного списка имеет вид, изображенный на рис. 5-6.1, где AVAIL представляет собой адрес головы списка, LINK (AVAIL) — указатель на первый блок свободного списка, а поле SIZE (AVAIL) — установлено в нуль; тогда один из ме- тодов обслуживания запроса на блок размера п можно сформули- ровать следующим образом. Алгоритм ALLOCATE_____FIRST. При подаче на вход адреса AVAIL головы списка и размера п запрашиваемого блока алго- ритм выдает адрес Р блока размера большего или равного п, при этом в поле SIZE (Р) записывается фактическая длина блока. В переменной MIN хранится размер области памяти, которую мы согласны потерять, чтобы уменьшить внешнюю фрагментацию; в процессе работы' алгоритма блоки размером MIN или меньше путем расщепления создаваться ие будут. 1. [Инициализация. 1 Установить Q <- AVAIL, Р ч— LINK (Q). 2. [Поиск блока, размер которого удовлетворяет запросу.] Повторять до тех пор, пока Р =£ NULL: если SIZE (Р) п, то установить k SIZE (Р) — п; если к MIN, то установить LINK (Q)-e-LlNK IP) и завершить выполне- ние алгоритма; в противном случае установить SIZE (Р) <- к , Р ч- Р к, SIZE (Р) ч- п и завершить выполнение алгоритма; в противном случае установить Q -*~Р, Рч-LINK (Q). 3. [Отсутствие подходящего блока. | Перейти к программе обработки ошибок. 503
В этом алгоритме предполагается, что над адресами можно производить арифметические операции. Если желательно исклю- чить внутреннюю фрагментацию, то нужно установить MIN в нуль, хотя не ясно, для чего могут использоваться блоки раз- мера 1, поскольку это единственное слово (а возможно и большее число слов) будет занято полями SIZE и LINK- Описанный выше алгоритм обычно называют алгоритмом «первого подходящего», поскольку резервируемый блок является первым найденным блоком (или его частью), размер которого не меньше запрашиваемого. Можно предположить, что наилучшим методом резервирования является метод «наиболее подходящего», но практически это не всегда верно. При применении метода «наи- более подходящего» первый найденный подходящий блок не ис- пользуется а просмотр списка продолжается до тех пор, пока не будет найден наименьший из подходящих блоков. Этот метод обес- печивает сохранение более крупных блоков до тех пор, пока они не потребуются для удовлетворения запросов на большие объемы памяти. Однако применение этого метода ведет к созданию боль- шого числа очень малых свободных блоков, которые часто не удовлетворяют ни один из запросов, что, конечно, нежелательно. Более того, при использовании метода «наиболее подходящего» требуется просмотр всего свободного списка, в то время как сред- няя длина поиска для метода «первого подходящего» при наличии в свободном списке и блоков составит п/2 или менее в зависимости от размера требуемого блока; эта величина стремится к п/2 в том случае, когда размер используемых блоков со временем ста бил и- зируется. Последний результат получен Кнутом [16] из серии экспериментов. Выбор метода в любом случае возможен лишь при наличии не- которого представления о типах запросов, которые будут посту- пать в систему. Прн некоторых условиях метод «наиболее подходя- щего» может дать лучшие результаты, чем. метод «.первого подходя- щего», несмотря на большие по сравнению с последним затраты времени. Интересно отметить, однако, что при проведении упомя- нутой выше серин экспериментов по моделированию способов использования памяти, выполненных с целью сравнения различ- ных стратегий управления памятью, Кнут обнаружил, что метод «первого подходящего» превосходит метод «наиболее подходящего» во всех исследованных случаях. *** До сих пор ничего не было сказано о порядке, в котором рас- полагаются элементы в списке. Порядок может оказать сущест- венное влияние на качество выполнения задачи. Если блоки сво- бодного списка упорядочены по возрастанию их размеров, то за- траты времени на поиск для метода «наиболее подходящего» можно существенно уменьшить. Упорядочение по возрастанию размеров ведет к превращению метода «первого подходящего» в метод «наиболее подходящего». Упорядочение по убыванию размеров блоков снижает время поиска для метода «первого под- 504
ходящего» до единицы, так как первый обнаруженный блок будет в этом случае наибольшим из имеющихся. Однако такое упорядо- чение может привести к превращению метода «первого подходя- щего» в метод, известный под названием «наименее подходящего», при котором всегда используется наибольший блок независимо от размера запрошенного блока, а также породить большое число ненужных малых блоков. Можно, однако, ввести упорядочение другого типа. Оно со- стоит в том, что блоки в свободном списке упорядочиваются в со- ответствии с их адресами (например, по возрастанию адресов). Упорядочение такого типа может и не уменьшать времени поиска блоков заданного размера, так как между адресом блока и его раз- мером никакой связи нет. Однако оно позволяет уменьшить сте- пень внешней фрагментации, а также затраты времени на поиск блоков, так как при таком упорядочении свободный список может существенно уменьшиться. После освобождения блоков, упорядо- ченных по адресам, можно провести их анализ, и, если обнару- жится, что два последовательных блока свободного списка зани- мают смежные области, то их можно слить в один, более крупный блок. Алгоритм, в котором используется такое упорядочение, поз- воляет максимально укрупнить блоки свободного списка и тем самым максимально уменьшить число блоков в нем. Более под- робно процесс слияния блоков мы рассмотрим при формулиро- вании алгоритмов освобождения зарезервированной памяти. Имеется, однако, достаточно свидетельств в пользу того, что при отсутствии каких-либо специальных противопоказаний для ре- ализации вполне приемлем метод «первого подходящего», применяе- мый к свободному списку, упорядоченному по адресам, и, следо- вательно, существенно не упорядоченному по размерам блоков. Еще одна модификация алгоритма «первого подходящего», поз- воляющая существенно сократить время поиска, состоит в следую- щем: вместо того, чтобы каждый раз поиск подходящего блока на- чинать с первого блока списка, его следует начинать с точки, в ко- торой закончился поиск предыдущего блока. Такой подход при- водит к распределению мелких блоков равномерно по всему списку и предотвращает их группировку в начале списка. Описываемый ниже алгоритм ALLOCATE______FIRST__М представляет собой ал- горитм «первого подходящего», модифицированный таким образом, чтобы реализовать преимущества указанного способа изменения начальной точки поиска. Алгоритм ALLOCATE____FIRST—М. На вход подаются описан- ные ранее параметры AVAIL, п и MIN и указатель М на последний обследованный во время предыдущей активизации этого алго- ритма блок свободного списка. Алгоритм выдает адрес Р подходя- щего блока и устанавливает новое значение параметра М. Пред- полагается, что перед первым обращением к этому алгоритму зна- чение параметра М устанавливается равным AVAIL. Переменная TIME представляет собой флаг, связанный с просмотром списка. 505
При достижении конца списка этот флаг устанавливается в еди- ницу. I. [Инициализация.] Установить Q ч-М, Р ч-LINK (Q), TIME ^-0. 2. [Поиск достаточно крупного блока. 1 Повторять до тех пор, пока TIME = 0 или Q М: если Р = NULL, то установить Q ч-AVAIL, Р -(-LINK (Q) и TIME ч-1; в противном случае, если SIZE (Р) 5? и, то установить к ч SIZE (Р) — п; если к ==s MINE то установить LINK (Q) -‘-LINK (Р), М ч-Q и завершить выполнение алгоритма; в противном случае установить SIZE (Р) ч— к, М ч- Р, Р ч— Р + к, SIZE (Р) ч-n и завершить выполнение ал- горитма; в противном случае установить Q ч- Р, Р ч— ^-LINK(Q). 3. [Отсутствие подходящего блока. ] Перейти к программе обработки ошибок. Кнут в своих экспериментах, по крайней мере в одном случае, обнаружил уменьшение среднего времени поиска со 125 проверок (составивших, как и ожидалось, п/2) при обычном методе «первого подходящего» до 28 проверок при модифицированном методе «первого подходящего». Такое уменьшение свидетельствует о су- щественном улучшении качества алгоритма. Указанная модифи- кация алгоритма «первого подходящего», использующего свобод- ный список, упорядоченный по адресам, может быть успешно при- менена в качестве механизма резервирования памяти во многих приложениях. Рассмотрим теперь процесс освобождения зарезервированного блока и его возвращения в свободный список. При этом мы все еще будем игнорировать вопрос о том, где и каким образом прини- мается решение об освобождении некоторого блока памяти, и будем просто предполагать, что блок памяти, начинающийся по адресу К В, ’с настоящего момента считается неиспользованным и подлежит перерезервированию. Этот процесс для случая исполь- зования блоков фиксированного размера был рассмотрен выше, поэтому сейчас ограничимся рассмотрением случаев с блоками переменной длины. При этом будем считать, что поле SIZE (КВ), расположенное в первом слове освобожденного блока, содержит фактический размер этого блока. Простейший способ решения рассматриваемой задачи состоит в том, чтобы включать каждый освобожденный блок в неупорядо- ченный свободный список в качестве его нового первого элемента. Такой подход, хотя и характеризуется минимальным числом опе- раций, имеет следующий серьезный недостаток. Очень вероятно, что в результате действия механизма резервирования и освобожде- 50(5
ния памяти в течение некоторого времени в свободном списке ока- жется большое число малых блоков. Время поиска в программе резервирования, а также вероятность отказа на некоторые запросы будут увеличиваться из-за того, что все блоки окажутся слишком малыми. Трудность здесь состоит в том, что в простейшем методе освобождения памяти не предусмотрен механизм, кото- рый мог бы противодействовать механизму расщепления блоков, имеющемуся в программе резервирования памяти. Иными словами, здесь никогда не восстанавливаются большие блоки. Очевидно, что решить данную проблему можно путем формиро- вания единого блока из двух смежных свободных блоков. Если каждый вновь освобожденный блок проверять на смежность с пред- шествующим и последующим блоками списка свободных блоков и сливать этот первый с последними в случае, когда смежность имеет место, то указанный список будет всегда содержать мини- мальное число блоков. При этом каждый блок списка имеет макси- мально возможный размер для текущего состояния резервирова- ния памяти. Однако для того чтобы добиться этого, в свободном списке необходимо поддерживать упорядочение блоков по воз- растанию их адресов, а это, в свою очередь, обусловливает необхо- димость поиска в свободном списке места для включения вновь освобожденного блока. Для включения блоков в свободный список со слиянием их по мере необходимости с соседями можно использовать алгоритм, приведенный ниже. Поскольку нам доступен второй указатель свободного списка — переменная точка начала поиска, мы можем в ряде случаев использовать его вместо указателя AVAIL в ка- честве начальной точки. Можно показать, что такая замена сни- жает среднюю длину поиска с п/2 до n/З. В описываемом алгоритме такое дополнение имеется. Алгоритм FREE—BLOCK. Заданы AVAIL — адрес головы списка, М — переменная начальная точка поиска программы ре- зервирования и RB — адрес включаемого в список блока. Алго- ритм включает блок RB в свободный список и сливает, если это возможно, смежные блоки. При этом предполагается, что AVAIL имеет значение меньшее, чем адрес любого из блоков. Это заведомо выполняется, если голова списка (поле SIZE (AVAIL) которой установлено в нуль) является первым словом всей области памяти, выделенной для резервирования. Предполагается также, что величину NULL можно сравнивать с реальным адресом с по- мощью любой из операций сравнения, при этом значение true возникает только при использовании операции Ниже через Q обозначен указатель на предшественника освобождаемого блока. 1. [Оптимальная инициализация.] Если RB > М, то устано- вить Q ч—М; в противном случае установить Q ч—AVAIL. Уста- новить Р ч-LINK (Q). 507
2. [Поиск в упорядоченном списке предшественника и преем- ника.] Повторять до тех пор, пока Р =/= NULL и RB > Р: устано- вить Q^P и Р ч-LINK (Q)- 3. [Слияние с преемником?] Если Р = NULL или RB + 4- SIZE (RB) =г Р, то установить LINK (RB) ч-Р; в противном случае установить LINK (RВ) -*-LINK (Р) и SIZE (RB) ч- <—SIZE (RB) 4-SIZE (Р). 4, [Слияние с предшественником?] Если Q = AVAIL нли Q 4- SIZE (Q) Ф RB, то установить LINK (Q) ч-RB; в против- ном случае установить LINK (Q) ч-LINK (RВ) и SIZE (Q) ч- ч-SIZE (Q) j- SIZE (RB). 5. [Выход. ] Завершить выполнение алгоритма. Отметим, что слияние блока RB с головой списка при Q — — AVAIL ие допускается; это обеспечивается тем, что SIZE (AVAIL) = 0, и, поскольку AVAIL < RB, то всегда AVAIL 4- 4- О/ RB. В заключение отметим, что мы теперь располагаем методом ре- зервирования памяти с очень малой средней длиной поиска и ме- тодом освобождения памяти, для которого средняя длина поиска приблизительно равна п/3. Использование такого метода освобож- дения памяти снижает степень внешней фрагментации, поскольку размеры блоков поддерживаются максимально большими. Степень внутренней фрагментации регулируется значением переменной MIN, но необходимо помнить, что между степенью внутренней фраг- ментации и временем поиска имеется обратная зависимость. Однако, рассматривая два описанных выше механизма управле- ния памятью с точки зрения времени поиска, можно заключить, что программа разрезервирования будет узким местом всего меха- низма управления памятью, так как средняя длина поиска для нее, равная п/3, гораздо больше средней длины поиска программы ре- зервирования (последнее обеспечивается благодаря использова- нию перемещаемого указателя начальной точки поиска). В связи с этим целесообразно рассмотреть программу разрезервирования, ие требующую столь большого времени поиска. В качестве при- мера такой программы выбрана реализация метода «граничных признаков» Кнута. В только что описанном алгоритме освобождения памяти блок- предшественник и блок-преемник определяются путем просмотра блоков упорядоченного списка. После их определения выяснить, являются ли они смежными с данным освобожденным блоком, довольно просто. Все, что можно сделать без такого поиска, — это проанализировать два слова, расположенных непосредственно спереди и сзади освобожденного блока. Но если указанные слова не отмечены специальными флажками, то непосредственно узнать, принадлежат ли блоки, составными частями которых эти слова являются, свободному списку, не представляется возможным. Поэтому при использовании метода граничных признаков первое и последнее слова каждого блока отводятся под поля флажков, 508
1 FLAG Ж£ | sue 1 PRED | ... 1 g «J О) 1SIZE! ... е) —в» у । у । К первому К последнему . блоку блоку Рис. 5-6.2. Структура блока для метода .граничных признаков»: а — свободный блок; б — зарезервированный блок; в — голова списка в которых указывается, зарезервирован или не зарезервирован данный блок. Желательно также, чтобы в последнем слове блока хранилась и его длина. В этом случае по последнему слову блока можно непосредственно определить адрес первого его слова. И наконец, поскольку необходимо исключить поиск в свободном списке, последний организуется как двусвязный список. Таким образом, исключение поиска приводит к тому, что под управляю- щие поля отводится больший объем памяти. Это может оказаться нецелесообразным в случаях, когда из-за некоторых особенностей прикладной программы свободный список оказывается довольно коротким или средняя длина блоков небольшой. В прочих случаях предлагаемая модификация может служить очень удобным спосо- бом повышения скорости разрезервирования памяти. Детали этого подхода даны в книге Кнута. Представление блока в памяти имеет формат, изображенный на рис. 5-6.2, а и б, где показаны соответственно блок свободного списка и зарезервированный блок. Если блок, начинающийся по адресу Р, свободен, то его поля FLAG и FL установлены в О, если же этот блок зарезервирован, то они имеют положительные значения. Поля SIZE и SZ содержат размер блока Р. Поля SUC (Р) и PRED (Р) содержат указатели соответственно на преем- ник и предшественник блока Р в свободном списке блоков, упоря- доченных по возрастанию адресов. Предполагается также, что ген лова списка имеет вид, изображенный на рис. 5-6.2, в, а ее адрес задается переменной AVAIL. Голова списка считается преемником н предшественником соответственно последнего и первого блоков свободного списка. Удобно также считать, что голова списка на- ходится вие пределов сегмента управляемой памяти, а флажки первого и последнего слов этого сегмента показывают, что эти слова зарезервированы. Сформулируем алгоритм разрезервирования, базирующийся на описанном представлении. Алгоритм FREE_____BLOCK___Т. Используя описанную выше структуру блоков, данный алгоритм включает блок с адресом RB 509
в свободный список, сливая его при необходимости со смежными блоками. Алгоритм задает также (если требуется) новое значение параметру М (переменная точка начала поиска). Предполагается, что адрес первого слова блока достаточен для непосредственного доступа к полям FLAG, SIZE, SUC и PR ED и что адрес последнего слова блока дает непосредственный доступ к полям SZ и FL. Сам алгоритм лишь немного сложнее базисной операции вклю- чения нового элемента в двусвязный список. 1. [Удаление предшественника Q и слияние? ] Если FL (RB — —1)=0, то установить Q ч-RB — SZ (RB — 1), PRED (SUC (Q)) ^-PRED(Q), SUC (PRED (Q)) ч-SUC (Q), SIZE (Q) ч- SIZE (Q) + SIZE (RB) и RB ч-Q. 2- [Удаление преемника Q и слияние?! Если FLAG (RB -|- SIZE (RB)) = 0, то установить Q <-RB -|- SIZE (RB), PRED (SUC (Q)) ч-PRED (Q), SUC (PRED (Q))^-SUC (Q), SIZE ((RB)<- Ч- SIZE (RB) -J- SIZE (Q); если M = Q, то установитьM ч-RB, 3. [Добавление нового первого блока, образуемого ячейками, расположенными между RB и Q]. Установить Q ч-RB + SIZE (RB) — 1, FLAG (RB) ч-FL (Q) 4-0,SZ (Q). +-SIZE (RB), SUC (RB) ч-SUC (AVAIL), SUC (AVAIL) ч-RB, PRED (RB) <- ч- AVAIL и PR ED (SUC (R B)) ч-RB. 4. [Выход.] Завершить выполнение алгоритма. До сих пор размер блоков был либо постоянным, либо полно- стью произвольным (хотя и большим, чем некоторый минимум). В некоторых методах управления памятью используется другой подход, состоящий в том, что размеры блоков ограничиваются некоторым фиксированным набором допустимых размеров. Все блоки данного размера можно связать вместе с целью повышения скорости поиска блока подходящего размера. Для запроса на блок размера п определяется число т, являющееся наименьшим из зафиксированных размеров, равных или больших п, и резерви- руется блок размера щ. Если доступных блоков размера m нет, то более крупный блок расщепляется на два блока (называемых «приятелями» J), каждый из которых имеет один из фиксированных размеров, и этот процесс продолжается до тех пор, пока не будет создан блок размером гл. Такой метод кроме уменьшения времени поиска имеет еще одно преимущество: он позволяет достаточно легко объединить два небольших блока в один, более крупный, поскольку в один блок может объединяться только пара «приятелей», образованных ранее путем расщепления этого блока. Так как соотношения между размерами блоков фиксированы, адрес блока-приятеля данного блока определяется довольно легко. 1 Термин «приятель» (англ, buddy) является более общим, чем используемый в работе [16] термин «близнец». Последний соответствует случаю расщепления блоков на две равные части. — Прим. пер. 510
Этот метод имеет, однако, ряд недостатков; при его использо- вании увеличивается степень внутренней фрагментации, что про- исходит из-за частого резервирования блоков, размер которых не- сколько превышает требуемый; может увеличиваться также и сте- пень внешней фрагментации вследствие того, что смежные блоки, не являющиеся приятелями, никогда не сливаются. Однако, не- смотря на указанные недостатки, этот метод управления памятью оказывается достаточно эффективным, и он вполне сравним с ме- тодами, описанными выше. Этот метод управления памятью обычно реализуется путем за- дания фиксированных размеров Fo, Fx, F^ для блоков в со- ответствии с некоторой формулой, например формулой рекуррент- ного соотношения: lFn == Fn_j Ч- Fn_k, k < n с max; lF0 = a,F1 = b,. . . , Fk-1 = c, где a, b, c — минимальные размеры используемых блоков, a k = == 1, 2, 3, ... . Например, если к = 1 и Fo = 1, то размеры блоков, равные 1, 2, 4, 8, 16, ..., представляют собой последовательные степени числа 2, и этот метод называется тогда «методом близнецов». Если к = 2, a Fo = Fi = I, то размеры в точности представляют собой последовательные члены ряда Фибоначчи: 1, 1, 2, 3, 5, 8, I3-, ... Однако в реальных приложениях термы Fo, Ft и т. д., по- видимому, не стоит определять с такими малыми значениями. Блоки размером 1 используются редко, особенно если учесть, что в них должна быть еще включена управляющая информация, обеспечивающая работу механизма резервирования и освобожде- ния памяти. Основное свойство, обеспечивающее работоспособность этой системы, состоит в том, что любое слияние отдельных блоков долж- но в точности соответствовать расщеплению некоторого блока. Более того восстановлению некоторого блока должно предшество- вать восстановление каждого из его подблоков. Схема блоков па- мяти, полученных в результате расщеплений, имеет вид бинарного дерева. Здесь возникает задача маркировки позиции каждого блока внутри этой деревовидной структуры. Довольно элегант- ное решение этой проблемы применительно к управлению памятью предлагает Хиндс 113]. Его решение состоит в приписывании каждому блоку кода, с помощью которого можно восстановить последовательность рас- щеплений, создавшую этот блок. Рассмотрим рекуррентное соот- ношение Fn = Fn_j 4- Fn_k. Если положить, что терм Fn_j со- ответствует блоку, образующему левую ветвь расщепления (Т- е- предполагается, что он имеет меньший адрес), а терм Fn_k обра- зует правую ее ветвь, то для каждого блока достаточно записать размер блока, число расщеплений, понадобившихся, чтобы полу- чить этот блок, и указать, является ли этот блок правым илн ле- 511
Рис. 5-6.3. Дерево управления памятью вым. Поскольку при каждом расщеплении образуется одни ле- вый блок и один правый блок, то необходимо записывать только число левых блоков. Фактически, это позволяет кодировать све- дения о левом или правом ветвлении совместно с числом расщепле- ний в одном поле. Левый блок кодируется относительным числом расщеплений (число, большее нуля), а каждый правый блок имеет нулевой код. Код родительского блока оказывается, таким обра- зом, связанным только с кодом левого блока. Используемые для кодирования формулы имеют следующий вид: исходное состояние: CODEmAX = О, где FA1AX — весь сегмент, рассматриваемый как правый блок; расщепление: CODELEFt — CODEPARent ф 1; CODERiGHT = 0; слияние: CODEPAPej>jf = CODEeeep — 1. В качестве примера рассмотрим блок памяти из 114 элементов и рекуррентное соотношение F„ = F„_, + Г-„. 2, 2:;п<6; F„ = 8; F, = 13. Прямое дерево возможных расщеплений представлено на рис. 5-6.3, где числа при вершинах задают размер блока, а верх- ние субиндексы к ним представляют собой коды, вычисленные по приведенным выше формулам. Рассмотрим поперечное сечение этого дерева, состоящее из множества потенциальных блоков раз- мером 13. Любой блок размером 13° представляет собой правый блок, образованный при расщеплении некоторого блока размером 34. Является ли этот блок размером 34 левой или правой ветвью предыдущего расщепления, определяется по «приятелю» блока размером 13. Если левый приятель последнего имеет код 1, то дан- ный блок размером 34 представляет собой правый блок некоторого блока размером 89; если.же левый его приятель имеет код боль- 512
ший 1, то блок размеромТ34 будет левым блоком некоторого боль- шего, чем он, блока. Любой блок размером 13 с кодом, большим чем 0, представляет собой левый блок расщепления некоторого блока размером 21. Числовое значение кода для левого блока раз- мером 13 задает число расщеплений некоторого вышестоящего правого блока, которые необходимо выполнить, чтобы получить этот блок. Иными словами, значение кода любого левого блока представляет собой число слияний, в которых он должен участво- вать для того, чтобы составить первые 13 слов некоторого более крупного блока, являющегося, в конечном счете, правым блоком. Для описываемого ниже алгоритма предполагается, что струк- тура блока такая же, как на рис. 5-6.4, а и б, где изображены со- ответственно свободный и зарезервированный блоки. Поле FREE (Р) равно 0 илн больше 0, в зависимости от того, свободен или занят блок Р; поле SIZE (Р) содержит значение i размера F, блока Р, а поля SUC (Р) и PRED (Р) для свободного блока Р содержат прямой и обратный указатели на другие блоки размером Fj двухсвязного списка свободных блоков размеров Fj. Голова такого списка с адресом AVAIL П ] изображена на рис. 5-6.4, в. Поле CODE (Р) блока Р содержит код, определяющий, левый он или правый, а также относительное число расщеплений, необхо- димых для его получения, если это левый блок. Кроме массива голов списков AVAIL 10 : МАХ ], заводится также массив F [0 : МАХ Г, содержащий размеры блоков Ft 0 I МАХ, соответствующие рекуррентному соотношению. Предполагается, что Fo имеет достаточно большое значение, чтобы вмещать все управляющие поля. Ниже приводятся два подалгоритма, обеспечивающие включе- ние блоков в описанные выше списки и исключение блоков нз этих списков. Алгоритм INSERT. Используя описанные выше массивы и структуры блоков, алгоритм при задании параметра i и адреса Р блока размером Fj включает этот блок в список с головой AVAIL [i]. | FREE | 57Z7F | | PRED | free | Size [ сож | itwH i AVAIL Рис. 5-6.4. Структура блока для управления памятью: а — свободный блок; б — зарезервированный блок; в — голова списка 17 Трамбле Ж-, Соренсон П. 513
1. [Включение в начало списка.] Установить FREE (Р) ч- ч- О, SIZE (Р) !, SUC (Р) ч- SUC (AVAIL (i I, SUC (AVAIL li J) ч- P, PRED (P) AVAIL li ], PRED (SUC (P)) — P. 2. [Выход. ] Завершить выполнение алгоритма. Алгоритм DELETE. Используя описанные выше массивы и структуру блоков, алгоритм при задании адреса блока Р исклю- чает этот блок из списка, в котором он находится. 1. [Исключение блока Р путем выделения его из списков.! Установить SUC (PRED (Р)) SUC (Р), PRED (SUC (Р)) PRED (Р). 2. [Выход. I Завершить выполнение алгоритма. Следующие алгоритмы обслуживают запросы на блоки памяти и осуществляют разрезервирование блоков. Для каждого из них предполагается, что значение к задано в соответствии с используе- мым рекуррентным соотношением. Алгоритм ALLOCATE____BLOCK REC. Используя описанные „выше массивы и структуру блоков, этот алгоритм принимает за- прос на блок размером п и выдает указатель Р, адресующий блок размером Ft (наименьший из размеров, больший или равный п). I. [Определение кода размера.] Если n>F [МАХ], то пе- рейти к подпрограмме обработки ошибок, в противном случае установить i ч— 0; повторять до тех пор, пока п > F li ]: уста- новить i ч- i -р 1. 2. [Поиск первого доступного блока.] Установить j ч-1. Повторять до тех пор, пока SUC (AVAIL Ij]) = AVAIL [j]: установить j ч- j j- I; если j > MAX, то перейти к подпрограмме обработки ошибок. Установить Рч—SUC (AVAIL [j]) н вызвать DELETE (Р). 3. [Расщепление блоков до тех пор, пока не будет получен необходимый размер.] Повторять до тех пор, пока j > i: устано- вить Q ч- Р + F [j — 1 ], CODE (P)4-CODE (Р) + 1, CODE (Q)4- ч—0; если i = j — 1, то вызвать INSERT (Q, j — k) и установить j ч—SIZE (P) ч-i; в противном случае вызвать INSERT (Р, j — 1) и установить j ч— SIZE (Q) ч— j — k, Р Ч- Q. 4. [Резервирование блока Р размером Fb ] Установить FREE (Р) ч- 1. 5. [Выход. ] Завершить выполнение алгоритма. Алгоритм FREE___BLOCK—REC. Задан адрес Р начала блока. Данный алгоритм включает этот блок (или блок, получающийся путем слияния данного блока со смежными приятелями) в нужный список. 1. [Выполнение всех возможных слияний.] Повторять шаг 2 до тех пор, пока SIZE (Р) < МАХ: 514
2. [Р — левый блок. ] Если CODE (Р) > 0, то установить Q4-P4-F [SIZE (Р)]: ' если FREE (Q) > 0 или SIZE (Q) =# SIZE (Р) — k -|- 1, то вызвать INSERT (Р, SIZE (Р)) и завершить выполнение алгоритма; в противном случае установить CODE (Р) ч—CODE (Р) — 1, SIZE (Р) ч- SIZE (Р) + 1, и вызвать DELETE (Q); в против- ном случае (Р — правый блок), установить Q ч-Р — — F [SIZE (Р) + k— 1]; если FREE (Q) > 0 нли SIZE (Q) SIZE (Р) -j- к — 1, то вызвать INSERT (Р, SIZE (Р)) н завершить выполнение алгоритма; в противном случае установить CODE (Q) CODE (Q) — 1, SIZE (Q) ч- SIZE (Q) + 1, вызвать DELETE (P) и уста- новить P ч— Q. 3. [Переход к этому шагу возможен только тогда, когда Р — максимальный блок.] Вызвать INSERT (Р, МАХ). 4. [Выход. ] Завершить выполнение алгоритма. Следует отметить, что при k == 1 (т. е. когда стратегия алго- ритма соответствует «методу близнецов») адреса приятелей отли- чаются друг от друга на величины, представляющие собой целые степени числа 2. Поскольку в ЭВМ все адреса представлены в дво- ичном виде, то описанный выше метод вычисления адреса можно заменить другим методом, в котором этот факт используется для повышения быстродействия приведенных алгоритмов. Эксперименты Кнута показали, что метод близнецов имеет довольно хорошие показатели, сравнимые с показателями раз- работанного им метода граничных признаков. И, действительно, эта система хорошо зарекомендовала себя на практике. Резуль- таты статистического анализа метода, основанного на системе чисел Фибоначчи, показали, что он превосходит по эффективности метод близнецов (скорее всего благодаря более широкому диапа- зону размеров блоков, допустимых для данного сегмента памяти). Преимущество метода Фибоначчи, которое делает его предпочти- тельнее метода близнецов, состоит в том, что средние потери па- мяти для первого меньше, чем для второго. Это происходит потому, что количество чисел Фибоначчи, меньших, чем любое заданное число п, всегда больше или равно числу к целых степеней двойки, таких, что 2к =< п. В действительности для п >>4 чисел Фибо- наччи, меньших или равных п, всегда больше, чем соответству- ющих чисел, представленных целыми степенями двойки, а для п с 4 количество тех и других равно. Поэтому система Фибоначчи имеет больший набор допустимых размеров блоков, и весьма ве- роятно, что она позволяет достичь лучшего соответствия между зарезервированным и требуемым размерами блоков. Подытоживая сказанное выше, можно отметить, что оба метода оказываются вполне пригодными для использования в системах управления памятью. 17* 515
и ' 1осле того как мы рассмотрели несколько методов выделения г^^обождения памяти, нам необходимо заняться вопросом о том, когда принимается решение об освобождении памяти. м^Л^твет на вопрос — когда необходимо зарезервировать па- —довольно прост. Резервирование памяти осуществляется, программист этого требует, объявляя некоторую структуру Kj| j1 Входе в программный блок или вызывая программу, созда- заданную структуру во время своего исполнения. Резерви- О|| ^^ие осуществляется также и системой, когда при входе в блок в ® Создает поля для временного хранения данных, потребности в^^Торых были выявлены на этапе компиляции, или когда во д^1^ исполнения программы в зависимости от обрабатываемых 1^ях формируются новые временные поля. ч>, Освобождение памяти уже не такая простая задача. Очевидно, ^ри выходе из блока можно освободить всю память, выделен- 4ля локальных переменных при входе в него. Трудности на- дц. при управлении такой памятью, которая распределяется с^^Мически, например, памятью для узлов списковой структуры, К^/^Ваемых программой блоков, или для строк в таких языках, **^НОБОЛ. з^д^Дин из подходов заключается в том, чтобы возложить решение пЬй освобождения памяти полностью на программиста. Но это oiJ^^ceT последнему слишком много лишних забот, поскольку bjL*** легко забыть о необходимости освобождения некоторых Пивных полей, структур и т. д. Некоторые языки (например, л^АЛ) имеют оператор FREE (освободить), позволяющий воз- на программиста (если он хочет этого) ответственность за деторождение памяти, по крайней мере для некоторых из опре- Р^а ^ЙЬ1Х им структур- В большинстве же языков (и систем, их дх ^Чзующих), однако, предусматриваются специальные средства ИЦ решения задачи освобождения памяти, даже если в них и ощ^тся оператор FREE, с помощью которого программист может 2?ождать некоторые блоки памяти. н^ 1 ^ким образом, рассматриваемая проблема сводится к изуче- р ^ого> какие методы могут быть использованы в системе при пЬв ^ии ВОГ1Р°са °® освобождении памяти. Существует несколько с^ Мов. Один из них не освобождать память до тех пор, пока Нц -Ан°й памяти совсем не останется, затем все зарезервирован- Склоки проверяются н освобождаются те нз них, которые не используются. Такой метод называется «сборкой му- ia Блоки памяти, которые во время исполнения программы бк^^ой-то момент понадобились, но позже стали не нужны и не используются, называются «мусором». Сборщик мусора ^атривает память и обнаруживает этот мусор. Другой метод — Т Рождать любой блок, как только он перестает использоваться, метод предотвращает накопление мусора, но требует боль- сЦ,г° числа оперативных проверок во время обработки данных. Метод обычно реализуется посредством счетчиков ссылок — Чб
счетчиков, в которых записывается, сколько указателей изданный блок имеется в данный момент в программе. При рассмотрении вопроса об освобождении памяти возникают две проблемы. Первая проблема, отмеченная ранее, — это на- копление мусора, в результате чего уменьшается объем доступной памяти и, следовательно, повышается вероятность выдачи отказа на запрос памяти. Вторая проблема —- «висящие ссылки». Вися- щая ссылка — это существующий в программе указатель, обеспе- чивающий доступ к уже освобожденному блоку. Если этот блок когда-нибудь будет зарезервирован повторно, а затем будет при- менен такой висящий указатель, то программа вновь обратится к этому блоку, который теперь предназначен совсем для других целей. Результаты могут быть непредвиденными. Висящие ссылки, по общепринятому мнению, являются более опасным явлением, чем накопление мусора, и поэтому на мини- мизацию вероятности их появления затрачивается больше усилий. Такая опасность, между прочим, является второй причиной, по которой во многих системах программисту не дается большой свободы в резервировании его собственной памяти. Так, легко допустить случай типа ALLOCATE (Р); Q = P; FREE (Р): в котором создается висящий указатель Q. Система при освобо- ждении блока может легко установить Р в NULL для того, чтобы прервать связь между идентификатором Р и разрезервированным блоком памяти, но ничего не сможет сделать с идентификатором Q, который теперь хранит адрес свободного блока. Соблюдение строгой дисциплины при программировании — здесь непреложное условие, но поскольку многие программисты любят использовать разные «трюки», разработчики языков стремятся уберечь их от ошибок такими средствами, как ограничение способов, которыми программисты могут освобождать память. Как отмечалось ранее, в методе счетчиков ссылок для каждого зарезервированного блока имеется счетчик, показывающий, сколько различных элементов программы имеют прямой доступ к этому блоку, т. е. указатель на него. Когда некоторый блок резервируется впервые, его счетчик ссылок устанавливается в единицу. Каждый раз, когда создается новая связь между некоторым идентификатором и этим блоком, значение счетчика ссылок увеличивается на единицу; каждый раз, когда такая связь уничтожается, значение счетчика ссылок умень- шается на единицу. Когда значение счетчика становится равным 517'
нулю, соответствующий блок оказывается недоступным и, следо- вательно, неиспользуемым. В этот момент он возвращается в сво- бодный список. Заметим, что такой метод полностью снимает про- блему висящих ссылок. Блок возвращается в свободный список после того, как в программе не останется больше ссылок на него. Однако использование этого метода имеет определенные не- достатки. Во-первых, если зарезервированные блоки образуют циклическую структуру, то их счетчики ссылок будут всегда иметь ио крайней мере единичные значения, и ни один из этих блоков ие будет освобожден, даже если все связи, идущие извне этой циклической структуры к включенным в нее блокам, будут унич- тожены. В этом случае у нас будет цикл из блоков, каждый из которых недоступен программе, и все-таки эти блоки будут оста- ваться зарезервированными, т. е. перманентно создавать мусор. Имеются, конечно, возможности устранить этот недостаток. Пер- вая возможность — запретить циклические или рекурсивные структуры. Однако в ряде приложений циклические структуры являются наиболее приемлемыми и естественными. Вторая воз- можность - отмечать циклические структуры флажками, указы- вая тем самым, что при решении вопроса об освобождении памяти их нужно рассматривать специальным образом. Третья возмож- ность — потребовать, чтобы любая циклическая структура всегда имела специальный головной блок, счетчик ссылок которого учитывал бы только ссылки от элементов, расположенных вне цикла, и чтобы доступ ко всем блокам этой циклической структуры шел только через голову списка. При таком подходе прямой доступ к блокам цикла исключается. Когда значение счетчика головы цикла становится равным нулю, можно освободить как сам головной блок, так и все остальные блоки цикла. Другой недостаток метода счетчика ссылок — лишние затраты на ведение счетчиков ссылок. Это более серьезное препятствие к использованию данного метода, чем циклические структуры, так как указанные затраты могут значительно увеличить время исполнения программы. Нужно будет проверять, какое влияние оказывает каждая операция обработки данных на счетчики ссы- лок, и изменять, если требуется, их значения. Например, простей- шее предложение Р = SUC (PRED (Q)) может потребовать гене- рации объектного кода для выполнения следующих действий (предполагается, что программа рассматривает Р и Q как указа- тели, например, вследствие того, что они были имплицитно объ- явлены таковыми): 1. Осуществить доступ к блоку Р и значение его счетчика ссылок уменьшить на 1. Обозначим это новое значение через t. 2. Сравнить t с нулем. При совпадении освободить блок Р. 3. Вычислить SUC (PRED (Q)). Обозначим результирующий адрес через г. 4. Осуществить доступ к блоку г и увеличить на 1 значение его счетчика ссылок. 518
5. Присвоить переменной Р значение г. исследование еще более простых алгоритмов, для которых желательно использовать динамическое выделение и освобождение памяти, покажет, что цена ведения таких счетчиков может легко стать чрезмерной. Другой метод определения того, когда нужно освобождать память (помимо освобождения его по командам программиста), — это сборка мусора. При данном методе используется специальная подпрограмма, вызываемая тогда, когда запасы имеющейся памяти почти исчерпаны, или когда нет возможности удовлетворить не- который частный запрос на память, или когда размер доступной области памяти стал меньше некоторого заранее заданного числа. Нормальное исполнение программы прерывается на время, в те- чение которого указанная подпрограмма освобождает память от мусора, и возобновляется после того, как сборщик мусора закан- чивает свою работу. Алгоритм сборки мусора обычно бывает двухэтапным. На первом этапе осуществляется просмотр всех путей доступа от всех программных и системных переменных ко всем зарезервированным блокам, Каждый блок, к которому есть такой доступ, отмечается (маркируется) специальной пометой. На втором этапе осуществляется просмотр всего сегмента памяти, при этом пометы у маркированных блоков стираются, а все блоки, которые не были отмечены, возвращаются в свободный список. Этот метод, как и предыдущий, предотвращает порождение висящих ссылок, так как, если имеется хотя бы один путь ссылок, ведущий к некоторому блоку, то этот блок отмечается и не осво- бождается на втором этапе. Однако из-за того, что сборщик мусора должен проследить все пути ссылок от блока к блоку, важно, чтобы каждый раз, когда он включается в работу, все списковые и блочные структуры находились в их нормальной форме, а их указатели были установлены на те блоки, на которые они должны указывать. В противном случае сборщик мусора не сможет пра- вильно просмотреть все пути ссылок, при этом либо часть мусора не будет собрана, либо, что более опасно, будут освобождены некоторые используемые блоки. Поскольку сборщик мусора может быть вызван системой почти в любой точке исполнения программы, необходимо, чтобы использование указателей было подчинено строгой дисциплине. Однако имеются некоторые алгоритмы, которые в процессе исполнения временно искажают структуры, например, так, что указатели начинают указывать по ветвям дерева вверх, а не вниз. Если сборщик мусора вызывается во время исполнения одного из таких алгоритмов, то вполне воз- можно, что он встретит такое искаженное дерево. Тогда на этапе маркировки может оказаться, что нужные блоки не будут отме- чены, и ситуация после возобновления нормального исполнения программы может оказаться фатальной. Устранить такие явления можно, если использовать указатели с максимальной осторожностью, избегая построения алгоритмов, 519
допускающих временные искажения структур. Однако для не- которых приложений алгоритмы такого типа очень нужны; они могут оказаться единственным способом для выполнения требу- емой задачи. В этом случае соответствующий алгоритм должен начинаться с выключения сборщика мусора, чтобы последний не мог быть вызван во время исполнения данного алгоритма. Однако, если алгоритм, отключивший сборщика мусора, в некоторый мо- мент запросит память и получит отказ, то возникнет безвыходная ситуация. Готового рецепта для устранения такой ситуации нет, и, по-видимому, в этом случае следует закончить работу и вернуться к ней, заранее зарезервировав большую область памяти. Один из недостатков метода сборки мусора состоит в том, что расходы на него увеличиваются по мере уменьшения размера свободной области памяти, и происходит это в то время, когда пользователь рассчитывает на эффективное и не требующее боль- ших расходов освобождение памяти. Когда остается мало свобод- ной памяти, естественно ожидать, что сборщик мусора будет вы- зываться очень часто, а пользователь хотел бы, чтобы расходы на его использование были по возможности минимальными. При- чина описанного обратного соотношения, естественно, в том, что при малом объеме свободной памяти объем зарезервированной памяти велик, и, следовательно, в процессе маркировки необхо- димо просмотреть большое число блоков. Для того чтобы сократить расходы на работу сборщика мусора, а также для того чтобы исключить упомянутые выше безвыходные ситуации, в некоторых приложениях сборщик вызывается гораздо раньше того момента, когда запасы памяти подходят к концу. Например, как только свободная область памяти уменьшится более чем в 2 раза, можно вызвать сборщика мусора, прерывая при этом исполнение основ- ной программы. Кроме того, чтобы исключить недопустимо частые вызовы сборщика мусора, которые могут возникать, когда почти вся память занята, в некоторых системах весь объем памяти счи- тается зарезервированным, начиная с того момента, когда усилия сборщика мусора уже не могут довести размер свободной области до некоторой заранее заданной величины. В этих условиях си- стема должна закончить работу, как только поступает следующий за установлением этого факта запрос на память, который она не может удовлетворить. Рассмотрим теперь некоторые алгоритмы, предназначенные для сборки мусора. Мы сконцентрируем внимание на этапе мар- кировки, поскольку этап фактического освобождения памяти, т. е. последовательный просмотр всего сегмента памяти и освобо- ждение немаркированных блоков, является относительно про- стым. Для системы, использующей блоки фиксированного раз- мера п, адреса г блоков, сформированных в области свободной памяти, определяются по формуле Р + i * п, 0 i <Д, где Р —• наименьший адрес свободной памяти. Для системы, использующей 520
блоки переменной длины, адреса Р, выделенных блоков задаются последовательностью вида Pi (=Р), Р3 (•-= Pi + SIZE (Pi)),..., Pm (= Pm_i 4- SIZE (Pm_j)), где Pm + SIZE (Pin) ~ Q + 1, a P и Q — характеризуют первый и последний адреса области свободной памяти. На этапе маркировки сборщик мусора должен отметить все блоки, к которым имеется доступ через любую цепочку ссылок, идущую от программной илн системной переменной. Поэтому алго- ритм сборщика должен иметь доступ к списку всех переменных, которые в данный момент имеют ссылки на элементы динами- ческой памяти. Это можно обеспечить просмотром таблицы симво- лов или таблицы текущих связей идентификаторов. Как только будет найдена переменная, указывающая на некоторый зарезер- вированный блок, необходимо отметить этот блок, а также все остальные блоки, к которым есть доступ, посредством указателей, содержащихся в первом блоке, все блоки, доступные из этих блоков и т. д. После того, как все блоки, доступные из рассматри- ваемой программной переменной, отмечены, тем же способом обрабатывается следующая переменная. После обработки всех переменных осуществляется просмотр всего сегмента памяти и освобождаются неотмеченные блоки. Для упрощения алгоритма маркировки предположим, что в блоках, которые содержат указатели на другие блоки и образуют тем самым структуру типа списка, эти указатели располагаются так, чтобы они были легко доступны прн наличии адреса блока. Указатели могут располагаться в начале блока непосредственно за его управляющими полями. Следовательно, мы предполагаем, что блок Р после резервирования имеет структуру, изображенную на рис. 5-6.5. Поле FREE (Р) содержит увеличенное на единицу число полей LINK; поле SIZE (Р) задает размер блока Р (или код раз- мера блока Р, как в методе близнецов или в методе" Фибоначчи); SAVE (Р) — используемое в процессе маркировки поле, с помощью которого можно допускать временное искажение списковой струк- туры. необходимое для ее об- хода; FIELD (Р) — поле, которое первоначально установлено в со- стояние false, но затем может устанавливаться в состояние true, чтобы показать, что данный блок был отмечен; поля LINKfP)— указатели на другие блоки. От- метим также, что поле SAVE необязательно для первого из FREE SIZE SAVE MARK LINK LINK рис. 5-6.5. Структура блока для сбора мусора 521
приводимых ниже алгоритмов маркировки. При его описании будем считать, что это поле отсутствует. Заметим, что к полям LINK имеется доступ посредством адресов Q, где Р < Q <Р 4- + FREE (Р). (Используемый метод резервирования или освобо- ждения памяти может еще потребовать дополнительных управ- ляющих полей. В этом случае мы предполагаем, что все нужные поля в блоке имеются.) Первый приводимый нами алгоритм очень прост. В нем исполь- зуется стек S (1 : МАХ) для записи необработанных полей LINK, встретившихся в процессе обработки данного блока. Ситуация пустого стека означает, что все блоки данной цепочки ссылок уже обработаны. Алгоритм STACK____MARK. Задан адрес Р блока, к которому есть прямой доступ от некоторой программной или системной переменной. Структура блоков имеет вид, показанный на рис. 5-6.5 (без поля SAVE). Алгоритм STACK_____MARK, используя стек S, отмечает блок Р и все блоки, доступные из него. L [Списковая структура?! Если FREE (Р) — 1, то установить MARK (Р) true и завершить выполнение алгоритма; в противном случае установить ТОР *-1 hS [ТОР] ч-Р. 2. [Обработка следующего блока. ] Повторять до тех пор, пока ТОР >0 : установить Р -*-S [ТОР], ТОР ч-ТОР — 1, MARK (Р) *- true, Q Р -j-1; повторять до тех пор, пока Q < Р 4~ 4- FREE (Р) (в стек заносятся поля LINK): если MARK (LINK (Q)) = false, то если FREE (LINK (Q)) = 1, то установить MARK •*- LINK (Q)) true; в противном случае, если TOP 4- 1 > МАХ, то перейти к подпрограмме обработки ошибок; в противном случае установить ТОР ТОР 4- 1 н S [ТОР ] *- LINK (Q). Установить Q <- Q 4- 1. 3. [Конец. ] Выход. Следует отметить, что во избежание выполнения некоторых бесполезных операций над стеком в него заносятся лишь те не- отмеченные блоки, которые имеют поля LINK. Время работы этого алгоритма пропорционально числу отме- ченных блоков, что является неплохим показателем. Но у этого метода имеется недостаток — необходимость использования до- полнительного стека. Предполагается, что сборщик мусора вызы- вается тогда, когда свободной памяти остается мало. При этом в ней может не оказаться места для стека, особенно если зарезер- вированные блоки образуют достаточно сложную списковую структуру со многими перекрестными связями. Поэтому были предложены другие алгоритмы, в том числе и такие, которые не требуют дополнительного стека. Ниже приводится алгоритм, созданный по образцу, разработанному Шорром и Уэйтом [25]. 522
Алгоритм, предложенный Шорром и Уэйтом, работает следу- • ющим образом. Очередной путь доступа здесь прослеживается до • самого конца. В нашем случае конец пути представляет собой ссылку на блок, который либо уже не имеет необработанных полей LINK, либо уже был отмечен ввиду того, что он включен еще и в некоторый другой путь доступа. При прямом следовании по пути доступа каждое поле SAVE, которое мы проходили, временно • устанавливается на блок, из которого был осуществлен доступ к предыдущему блоку. Здесь имеет место временное искажение структуры, зато обеспечивается повторное прослеживание одно- отсылочного списка. Поле SAVE используется для фиксации того, . в каком из полей LINK, имеющихся в блоке, в данный момент . произведена реверсия. При входе в блок во время обратного про- слеживания пути восстанавливается истинное значение изменен- ного ранее поля LINK, а затем обрабатываются оставшиеся поля LINK этого блока, при этом каждое из ннх рассматривается как j начало некоторого подпути. Далее мы следуем по'одному из этих путей до конца, производя реверсию полей LINK, а затем просле- живаем пройденный путь в обратном направлении, восстанавливая эти поля и обследуя вновь встречающиеся подпути. Из описанного j выше следует, что логическая структура рассматриваемого алго- I ритма имеет значительную степень «вложения» одних фрагментов 1 путей в другие. Если бы этот алгоритм не был предназначен для использования при недостатке свободной памяти, он был бы иде- альным для рекурсивной реализации. Ниже следует описание • этого алгоритма. Алгоритм LINK___MARK- Задан адрес Р блока, к которому имеется прямой доступ. Данный алгоритм отмечает блок Р и все I другие блоки, доступные из Р. Указатели Р и Q адресуют соот- ' ветственно обрабатываемый и предшествующий ему по времени | обработки блоки. j 1. [Инициализация.] Установить Q ч- NULL. 2. [Маркировка начального блока.] Если MARK (Р) == true, ' то перейти к шагу 4; в противном случае установить MARK (Р) -<— i true и SAVE (Р) ч- 0. 3. [Начать прямой обход?! Установить t ч-SAVE (Р) + I. Если t < FREE (Р) (обратить LINK), то установить SAVE (Р) ч- t, TEMP ч- LINK (Р F t), LINK (P + t) Q, Q P, P ’ <- TEMP и перейти к шагу 2. I 4. [Восстановление LINK и возвращение назад на один шаг. ] , Если Q = NULL, то завершить выполнение алгоритма; в против- ном случае, установить t * SAVE (Q), TEMP ч- LINK (Q ~F t), LINK (Q + t) *- P, P*-Q и Q *- TEMP и перейти к шагу 3. ' Следует отметить, что этот алгоритм работает медленнее, чем стековый алгоритм, поскольку он дважды проходит по каждому ; из путей. Такое замедление — это плата за возможность марки- ‘ ровки при недостатке памяти. J 523
Оптимальное решение получается при использований стека. Если имеется некоторая свободная область памяти, то сначала используется стековый алгоритм. Если же в некоторый момент времени оказывается, что стек до конца заполнен и в то же время в него необходимо занести некоторый новый адрес блока, то для маркировки этого блока и всех доступных из него блоков можно использовать второй алгоритм. После того, как второй алгоритм закончит свою работу, стековый алгоритм может продолжать свою, поскольку он уже не должен обрабатывать блок, вызвавший переполнение. Совместное использование двух указанных алго- ритмов позволяет сочетать быстродействие стекового алгоритма и возможность маркировки без стека в случае переполнения последнего. Для слияния этих алгоритмов в алгоритме STACK—MARK достаточно заменить фразу «перейти к подпрограм- ме обработки ошибок» фразой «вызвать LI NK_MARK (LI NK (Q))». В заключение мы коротко рассмотрим иной метод восстановле- ния ранее зарезервированной памяти, называемый уплотнением. Уплотнение осуществляется путем физического передвижения блоков данных из одних областей памяти в другие с целью сбора всех свободных блоков в один большой блок. Резервирование памяти в этом случае значительно упрощается: просто передви- гается указатель начала этого последовательно укорачиваемого блока. Как только размер этого единственного блока становится слишком мал, вновь включается механизм уплотнения для вы- явления неиспользуемых блоков памяти, расположенных между зарезервированными блоками. Механизма освобождения памяти здесь совсем нет. Вместо него используется механизм маркировки, который отмечает блоки, используемые в данный момент. Затем вместо того, чтобы освобождать каждый неотмеченный блок путем введения в действие механизма освобождения памяти, помеща- ющего этот блок в свободный список, используется уплотнитель, который собирает все неотмеченные блоки в один большой блок в одном конце области памяти. Единственная серьезная проблема, возникающая при исполь- зовании этого метода, — это переопределение указателей. Она решается путем организации нескольких просмотров памяти. После маркировки блоков просматривается вся память и для каж- дого из отмеченных блоков определяется его новый адрес. Этот новый адрес запоминается в самом блоке. Затем осуществляется еще один просмотр памяти, на котором указатели, адресующие отмеченные блоки, заново устанавливаются на области, где эти блоки будут храниться после уплотнения. Именно для этого но- вые адреса запоминаются непосредственно в самих блоках — их от- туда легко выбрать. После переустановки всех указателей отме- ченные блоки передвигаются на новые места. Ниже приведен пример алгоритма уплотнения. Алгоритм COMPACT. Задан набор блоков со структурой, изображенной на рис. 5-6.5. Данный алгоритм осуществляет 524
уплотнение неиспользуемой памяти в один большой блок с на- чальным адресом ТОР и конечным адресом STOP (максимальный адрес исходного сегмента памяти); START — означает адрес первого слова исходного сегмента памяти. Поля SAVE отмечен- ных блоков используются программой уплотнения для записи их новых адресов. 1. (Маркировка блоков.] Вызвать для маркировки блоков программу маркировки, разработанную для сборщика мусора. 2. (Вычисление новых .адресов для отмеченных блоков. ] Установить LOC ч- ТОР <- START.^ Повторять до тех пор, пока LOC < STOP: если MARK (LOC) = true, то установить SAVE (LOC) ч- ч- TOP, TOP ч- TOP + SIZE (LOC), LOC LOC 4- + SIZE (LOC). 3. (Переопределение ссылок программных и системных пере- менных-указателей. ] Повторять для каждой переменной: установить Р <- значе- ние переменной, переменная ч-.SAVE (Р). 4. [Определение новых значений указателей, расположенных в отмеченных блоках. ] Установить LOC ч- START. Повторять до тех пор, пока LOC s?: STOP: если MARK (LOC) = true, то установить Р ч- LOC + 1; повторять до тех пор, пока Р < LOC + FREE (LOC): установить LINK (Р) ч- SAVE (LINK (Р)), Р ч- Р 4- 1. Установить ЕОСч- LOC + SIZE (LOC). 5. (Перемещение отмеченных блоков. 1 Установить LOC ч- *- TOP ч- START. Повторять до тех пор, пока LOC < STOP: Если MARK (LOC) = true, то установить t ч- SIZE (LOC) и k ч— О, повторять до тех пор, пока k < t: копировать содержимое слова LOC 4~ I в слово ТОР + к, к ч- к 4~ 1, установить MARK (ТОР) ч- false, ТОР *- TOP -|- t и LOC ч- LOC 4- t; в "противном случае установить LOC ч- LOC 4- SIZE (LOC). 6. [Конец. 1 Выход. Следует отметить, что из-за трех просмотров памяти программа уплотнения является относительно дорогостоящим процессом с точки зрения затрат времени. Однако повышенная скорость ре- зервирования в определенных условиях компенсирует этот не- достаток. Такой алгоритм уплотнения (или его варианты) исполь- зуется во многих реализациях СНОБОЛа, поэтому можно пред- положить, что он достаточно эффективен. На этом мы закончим обзор некоторых проблем, связанных с динамическим управлением памятью. Мы описали здесь ряд различных алгоритмов, довольно типичных для используемых на практике систем. В ряде систем некоторые из них объединяются, что вполне осуществимо, так как эти методы часто совместимы. 525
В любом случае фактические условия работы системы определяют, какой метод необходимо использовать. Практическая эффектив- ность этих методов часто зависит от многих параметров, таких как частота запросов, статистическое распределение размеров запра- шиваемых блоков, способ использования системы (т. е. групповая обработка в режиме on-line) или стратегия обслуживания при управлении вычислительным центром. Список литературы 1. Abrams М. D., Data Structures lor Computer Graphics. Proceedings of a Symposium on Data Structures in Programming Languages, SIGPLAN Notices, vol. 6, No. 2, February 1971, pp. 268—286. 2. Ахо А. В., Ульман Д. В., Теория синтаксического анализа, перевода и компиляции. Том I и 2, М.: Мир, 1978. т. I, 611 с., т. 2. 627 с. 3. Берзтисс А. Т., Структуры данных. Пер. с англ. М.: Статистика, 1974. 403 с. 4. Bri(linger Р. С. and Cohen D. J., Introduction to Data Structures and Nonnumeric Computations,*Englewood Cliffs, Prentice—Hall, 1972. 5. Davidson С. H. and Koenig E. C., Computers: Introduction to Compu- ters and Applied Computing Concepts, New York: J. Wiley and Sons, 1967 6. Davis S.t Computer Data Displays. Englewood Cliffs, Prentice—Hall, 7. Dial R. B., Decision Table Translation. Communications of the ACM. vol. 13, No. 9, September, 1970, pp. 571—572. 8. Elson M., Data Structures, Palo Alto: Science Research Associates' 1975. 9. Gear C. W., Introduction to Computer Science Palo Alto: Science Re- search Associates, 1973. 10. Gray J. C., Compound Data Structures for Computer Aided Design; a Sur- vey. ACM Professional Development Seminar. 11. Грис Д. Конструирование компиляторов для цифровых вычислитель- ных машин. М.: Мир, 1975. 544 с. 12. Harrison М. С., Data Structures and Programming. Scott, Foresirian and Company. 1973. 13. Hinds J. A., An Algorithm lor Locating Adjacent Storage Blocks in the Buddy System. Communications of the ACM, vol. 18, No. 4, 1975, pp. 221—222. 14. HIrschberg D. S., A Class of Dynamic Memory Allocation Algorithms. Communications of the ACM, vol. 16, No. 10, 1973, pp- 615—618. 15. Kahn A. B., Topological Sorting of Large Networks. Communications of the ACM, vol. 5, No. 11, 1962. pp. 558—562. 16. Кнут Д. E. Искусство программирования для ЭВМ, т. 3. Сортировка и поиск. М.: Мир, 1978. 844 с. 17. Levin R. I. and Kirkpatrick С. A., Planning and Control With РЕRT/CPM. New York: McGraw-Hill Book Company, 1966. 18. Lindstrom G., Copying List Structures Using Bounded Workspace. Communications of the ACM, voi. 17, No. 4, 1974, pp. 198—202. 19. McCarthy J. et al., LISP 1.5 Programmer’s Manual. 2nd edition, M.l.T. Press. Cambridge, 1969. 20. Montalbano M., Decision Tables. Palo Alto; Science Research Associa- tes, 1974. 21. Ньюмен У., Спрулл P., Основы интерактивной машинной графики. М.: Мир, 1976. 22. Pollack S. L., Conversion of Limited—Entry Decision Tables lo Com- puter Programs. Memorandum RM—4020PR Santa Monica; The Rand Corpora- tion, May 1964. 23. Пратт T. Языки программирования: разработка и реализация. М.: Мир, 1979. 574 с. 526
24. Reinwald L. T., and Soland R. M., Conversion оГ Limited—Entry Decision Tables to Optimal Computer Programs 1: Minimum Average Processing Time. Journal of the ЛСЛ1, July. 1966, pp. 339—358. 25. Schorr H. and Waite W. M., An efficient Machine—Independent Pro- cedure for Garbage Collection in Various List Structures. Communications of the ACM, vol. 10, No. 8, 1967, pp. 501—506. 26- Shen К. K- and Peterson J. L., A Weighted Buddy Method for Dyna- mic Storage Allocation. Conununications of the ACM, vol. 17, No. it), 1974, pp. 558—562. 27. Sutherland I. F., SKETCHPAD: A Man—Machine Graphical Communi- cation System. Proceedings of the AFIPS 1963 SJCC, vol. 23, New York: Spartan Books. 28. Tremblay J. P. and Manohar R. M., Discrete Mathematical Structures and their Applications to Computer Science. New York: McGraw-Hill, 1975. 29. Van Dam A., Data and Storage Structures for Interactive Graphics. Proceedings of a Symposium on Data Structures in Programming Languages. SIGPLAN Notices, vol. 6, No. 2, February, 1971, pp. 237—267. 30. Verheist M., The Conversion of Limited Entry Decision Tables lo Opti- mal and Near—Optimal Flowcharts — Two New Algorithms. Communications of the ACM, vol. 15, No. 11, November, 1972, pp. 974—980. 31. Williams R., A survey of Data Structures for Computer Graphics Systems. ACM Computing Surveys, vol. 3, No. 1, March, 1971, pp. 1—21.
Глава 6. СОРТИРОВКА И ПОИСК В предыдущих главах мы рассмотрели большое число структур данных и способов их представления в памяти. Детально были описаны алго- ритмы ряда операций .таких как вставка и удаление элементов, которые обычно вы- полняются над этими структурами. В данной главе рассматриваются две допол- нительные операции, которые часто выполняются над структурами данных, — сортировка и поиск. Мы увидим, что эффективные алгоритмы для этих операций могут быть реализованы в том случае, когда данные хорошо структурированы. Рассмотрение начинается с простых алгоритмов с последующим переходом к сложным. Для каждого алгоритма даются его приближенные количественные оценки. Наиболее частые ссылки в дайной главе делаются на книгу Кнута по сортировке и поиску [15]. Методы, описанные в этой главе, -рассчитаны на то, что все данные хранятся в основной памяти ЭВМ, и поэтому оии называются методами внутренней сортировки и поиска. 6-1. СОРТИРОВКА Операции сортировки наиболее часто используются в коммерческих задачах обработки данных. Вместе с тем такие операции находят все более широкое распространение во многих научных приложениях. Методы сортировки, рассматриваемые в данной главе, служат представительной выборкой наиболее часто используемых подходов. . Описываемые методы развиваются от тривиальных (и неэффек- тивных) алгоритмов, таких как сортировка посредством выбора и сортировка методом пузырька, к более сложным (и эффективным) алгоритмам, например быстрая сортировка, пирамидальная сор- тировка и поразрядная сортировка. В целях достижения вычис- лительной эффективности для организации данных используются различные типы структур, например, такие, как деревья и очереди. 6-1.1. Система записи и основные понятия Данные могут встречаться в различной форме. В этой главе предполагается, что нам дан некоторый набор элементов. Каждый элемент представляется записью, которая содержит ряд информационных полей. Записи объединяются в таблицу, зада- ющую информацию, над которой должна быть выполнена операция сортировки. Каждое поле записи в общем случае содержит алфа- 528
витно-цифровую информацию. Организация записи зависит от конкретной задачи и не играет роли в основных алгоритмах, ко- торые будут рассматриваться. Предполагается, что таблица представляет собой упорядочен- ную последовательность п записей Rj, R2, ..., Rn. Каждая запись в таблице содержит Один ключ или более. С этими ключами ассо- циируется вид выполняемых действий. Например, ключ, относя- щийся к записи, может быть номером служащего или его фами- лией. В нашем рассмотрении каждая запись содержит единствен- ное поле ключа Ki и другую дополнительную информацию, не существенную для настоящего рассмотрения. Сортировка является операцией расстановки записей таблицы в определенном последовательном порядке в соответствии с не- которым критерием упорядочения. Сортировка осуществляется в соответствии со значением ключей всех записей. В зависимости от характера ключей записи могут сортироваться либо численным способом, либо в более общем случае алфавитно-цифровым. При численной сортировке записи упорядочиваются в восходящем или нисходящем порядке в соответствии с числовым значением ключа. Примером сортировки такого типа является сортировка таблицы символов в соответствии с внутренним числовым значением алфа- витно-цифрового представления каждого имени переменной. В об- щем случае ключ может быть любой последовательностью симво- лов, и упорядочение, проводимое путем сортировки, зависит от сопоставляющей последовательности, ассоциируемой с кон- кретным используемым набором символов (см. п. 1-4.6 и 2-3.1). В’данном параграфе для удобства предполагается, что ключ, по которому осуществляется сортировка, числовой. Это предположе- ние не является ограничивающим, поскольку все алгоритмы, которые будут сформулированы при заданной конкретной сопо- ставляющей последовательности, применимы также к любой строке символов. Большинство рассматриваемых ниже алгоритмов включает перенос записей из одного места в таблице в другое. Поскольку записи в некоторых приложениях могут быть довольно длинными и, следовательно, «дорогими» с точки зрения переноса, они должны быть организованы таким образом, чтобы минимизировать сто- имость переноса при выполнении сортировки. Одним из. методов значительного сокращения стоимости пере- мещения записей является представление таблицы в виде простого связанного списка. Ясно, что при использовании такого предста- вления перенос записей эффективен. Дополнительное требование памяти для поля указателя становится менее существенным по мере увеличения длины записи. Другим способом снижения стоимости перемещения записей является использование вектора указателей, каждый элемент которого содержит адрес записи. В качестве иллюстрации на рис. 6-1.1 изображена до и после сортировки небольшая та- 529
блица записей о студен- тах в соответствии с их суммарными оценками. Заштрихованная об- ласть в каждой записи содержит информацию типа фамилии и номера курса, которую в нашем рассмотрении можно не принимать во внимание. Во всех рассматри- ваемых ниже методах сортировки предпола- гается, что сортировка исходной таблицы долж- на проводиться в основ- ной памяти ЭВМ. Не- которые из этих мето- дов, например, такие, как сортировка слия- п) If) Рис. 6-1.1. Представление таблицы с использованием НИеМ И СОрТИрОВКа С вектора указателя: ВЫЧИСЛСНИем адреса, МО' а - перед сортировкой, б - после сортировки гуг легк0 адапТИ_ рованы к ситуации, когда таблицы хранятся во внешних запоминающих устройствах, например дисках и барабанах. Каждый из рассматриваемых методов сортировки будет вклю- чать приближенное количественное описание метода с точки зре- ния требуемого числа сравнений и перенссов записей. Поскольку, как уже отмечалось ранее, стоимость переноса записей может быть значительно снижена, более важным фактором является число сравнений, которое требуется в каждом конкретном методе. На последующих страницах рассматриваются методы сорти- ровки от простейших до сложных. Надлежащее структурирование таблицы, например, такое, как при пирамидальной сортировке, поразрядной сортировке и сортировке с вычислением адреса, -может обеспечить эффективные алгоритмы сортировки. 6-1.2. Сортировка способом выбора Одним из наиболее легких способов сортировки таблиц является способ выбора 1. При этом способе, начиная с первой записи в таблице, осуществляется поиск элемента, имеющего наи- меньшее значение ключа. После того как этот элемент найден, он меняется местами с первой записью в таблице. В результате 1 Этот метод называется также сортировкой посредством простого выбора.— Прим. ред. 630
такой перестановки запись с наименьшим значением ключа по- мещается в первую позицию в таблице. Затем осуществляется поиск второго наименьшего ключа путем дальнейшей проверки ключей записей, начиная со второго элемента. Элемент, имеющий второе наименьшее значение ключа, меняется местами с элемен- том, расположенным во второй позиции в таблице. Это процесс поиска записи со следующим наименьшим ключом и помещением его в соответствующей позиции (внутри желаемого упорядочения) продолжается до тех пор, пока все записи не будут отсортированы в восходящем порядке. Данный процесс формализуется следу- ющим алгоритмом. Алгоритм SELECTION. Задана таблица элементов Rlf R-г,..., Rn. Данный алгоритм реорганизует таблицу в восходящем по- рядке, т. е. для ее ключей будет иметь место соотношение Ki < Ка Кга. Процесс сортировки основан на описанном выше методе. 1. [Цикл по индексу прохода.] Повторять шаги 2—4 при i = 1, 2, ..., п — 1. 2. [Инициализация минимального индекса поиска.] Уста- новить г ч— i. 3. [Выполнение прохода и нахождение наименьшего ключа. ] Повторять при j == i + J, I -Ь 2, п: если К,- < Кг, то устано- вить г ч- j. 4. [Перестановка записей. ] Если г j, то R, <—> Rr. 5. [Конец. ] Выход. В данном алгоритме поиск записи со следующим наименьшим ключом называется проходом. Для того чтобы выполнить сорти- ровку, требуется п — 1 таких проходов, так как на каждом про- ходе заносится только одна запись в соответствующую позицию в таблице. Пример сортировки посредством выбора приведен на рис. 6-1.2. Данные, помещенные в кружки, означают записи с наименьшим ключом, выбранным на конкретном проходе. Эле- менты, расположенные выше двойной черты для данного про- хода, — это элементы, которые уже расставлены по порядку. Обратимся теперь к характеристикам данного алгоритма. В течение первого прохода, на котором отыскивается запись с наименьшим ключом, сравниваются п — 1 записей. В общем случае для i-ro прохода сортировки требуется п — i сравнений. Следовательно, обшее число сравнений задается суммой S (n — i) = 4~п(п — 1). Таким образом, число сравнений пропорционально п2, что часто записывается в виде О(п2) (т. е. порядка п2). Количество перестановок записей зависит от того, как вначале рассорти- 531
Неотсортированные Номер прохода (,) Отсортированные j К, 1 2 3 4 5 6 7 8 1 4» Ц 11 Н И 11 П И И И 2 23 @ Щ 23 23 23 23 23 23 23 3 74 74 74<| 36 36 36 36 36 36 36 4 42 42 @ 42 42 42 42 42 42 5 65 65 65 6 58 58 58 65 65«j Jg 58 58 58 58 58 65 65 65 7 94 94 94 94 94 94 94ч 24 74 74 8 36 36 74 74 74 94ч §7 87 9 99 99 99 99 99 99 99 99 99ч 94 10 87 87 87 87 87 87 87 99 Рис. 6-1.2. Трасса алгоритма сортировки посредством выбора рована таблица. Поскольку в течение одного прохода требуется не более одной перестановки, максимальное количество пере- становок при такой сортировке равно п — 1. 6-1.3. Сортировка методом пузырька Другим хорошо известным способом сортировки яв- ляется сортировка методом пузырька. Ее отличие от сортировки посредством выбора заключается в том, что вместо поиска наи- меньшей записи с последующей ее перестановкой две записи ме- няются местами сразу, как только обнаружено, что между ними нарушается порядок. При использовании этого способа требуется самое большое п — 1 проходов. В течение первого прохода сравниваются ключи Ki и К2, и, если порядок между ними нарушен, записи Rx и R2 меняются местами; этот процесс повторяется для записей Rz и Rs, R8 и R4 и т. д. Данный метод заставляет двигаться, или «всплывать», записи с малыми ключами. После первого прохода запись с наибольшим ключом будет находиться иа n-й позиции. На каждом последующем проходе записи со следующим наиболь- шим ключом будут располагаться в позициях п —1, п —2, ..., 2 соответственно, в результате чего будет сформирована отсортиро- ванная таблица. После каждого прохода через таблицу может быть сделана проверка, были ли совершены перестановки в течение данного прохода. Если перестановок не было, то это означает, что таблица уже отсортирована и дальнейших проходов не требуется. Перей- дем теперь к формулировке данного процесса сортировки. 532
Алгоритм BUBLE. Задана таблица записей R2, ..., Rn. Данный алгоритм реорганизует таблицу в восходящем порядке, т. е. для ее ключей будет иметь место соотношение Ki К2 Кп. Процесс сортировки основан на описанном выше методе. 1. [Цикл по индексу прохода. ] Повторять шаги 2 и 3 при i = 1,2, ..., n —1. 2. [Инициализация флага перестановки. ] Установить FLAG +- <-0. 3. [Произвести проход.] Повторять при j = 1, 2, ...» п —i: если К] + 1 < Kj, то установить FLAG ч- 1, Rj-<—^Rj + х. Если FLAG = 0, то завершить выполнение алгоритма. 4. [Конец. ] Выход. Работа данного алгоритма очевидна. Перед каждым проходом флаг FLAG устанавливается в нуль. Этот флаг анализируется в конце каждого шага. Если он не изменился, то сортировка вы- полнена полностью. Характеристики пузырьковой сортировки в худшем случае составляют ~п (п — 1) сравнений и—- п (п — 1) перестановок. Среднее число проходов примерно равно п X 1.25р'п (см. ра- боту [25]). При п = 10 среднее число проходов равно 6, как показано на рис. 6-1.3. Среднее число сравнений и перестановок имеет порядок О (п2). В процессе сортировки^методом пузырька можно внести не- сколько усовершенствований. Некоторые из них рассмотрены в упражнениях. Эти усовершенствования, тем не менее, суще- ственно не улучшают характеристик метода. В заключение отме- Неотсорти- Отсорти- рованные Номер проходи (I) рованные j Kj 1 2 3 9 5 6 1 92 23 23 ^11 И 11 11 2 23 92 ^1Г 23 23 23 23 3 79 .11^ 92 92 92 ^36 36 4 IV 65 ^58 58 ^,36^ 92 5 65 58^ 65 36 58 5Л 58 б 5V 79 ^36 65 65 65 65 7 99 ,36 ' 79 7£ 79 79 79 8 36^ 99 87 87 87 87 9 99 .87** 99 99 99 99 99 Ю Рис. 6-1.3. трасса V/ алгоритма 99 99 сортировки 99 методом 99 пузырька 99 99 533
Тим, что сортировка методом пузырька приемлема для таблиц, содержащих небольшое число записей (менее 15), однако этот метод нежелательно использовать для таблиц большого размера. 6-1.4. Обменная сортировка с разделением Теперь мы рассмотрим метод сортировки, весьма эф- фективный для больших таблиц. Целью каждого шага в данном методе является помещение очередной рассматриваемой записи на ее конечную позицию внутри таблицы. Если поступать таким способом, то все записи, предшествующие данной, будут иметь меньший ключ, в то время как все последующие — больший. При использовании такого метода таблица всегда делится на две под- таблицы. Аналогичный процесс может затем быть применен к каж- дой из этих подтаблиц и повторяться до тех пор, пока все записи не будут установлены на их конечные позиции. В качестве примера рассмотрим следующий набор ключей: 42 23 74 11 65 58 94 36 99 87. Используются два индекса I и j с начальными значениями 1 и 10 соответственно. Сравниваются ключи Ki и Kj, и, если перестановка не требуется, то j уменьшается на 1 и процесс повторяется. В том случае, когда К-, Kj, записи R, и Rj меняются местами. Затем этот процесс повторяется с I, увеличенным на 1, н фиксирован- ным j до тех пор, пока не возникнет другая перестановка; в этот момент j снова будет уменьшено на 1, a i останется фиксирован- ным, и. т. д. Ниже приведена последовательность перестановок при по- мещении ключа 42 на его конечную позицию; кружками в строках обведены ключи, значения которых сравнивались: @ 23 74 11 65 @ 23 74 11 65 @ 23 74 II 65 * 36 @ 74 11 65 36 23 @ 11 65 36 23 @ 11 65 36 23 @ И 65 36 . 23 @ 11 @ 36 23 @ @ 65 36 23 11 42 65 58 94 36 99 @ 58 94 36 @ 87 58 94 @ 99 87 58 94 @ 99 87 58 94 @ 99 87 58 @ 74 99 87 @ 94 74 99 87 58 94 74 99 87 58 94 74 99 87 58 94 74 99 87 В результате исходный набор ключей оказался разбит на две подтаблицы, а именно: на наборы {36, 23, 11} и {65, 58, 94 , 74, 534
99, 87}. Этот же процесс может быть применен к каждому из получаемых наборов до тех пор, пока таблица не окажется пол- ностью отсортированной. Этот метод обменной сортировки с раз- делением называется также быстрой сортировкой. Каждый раз таблица разбивается на две подтаблицы, одна из которых обрабатывается, в то время как границы второй запоми- наются, с тем чтобы она была обработана позже. В этих целях может быть использован стек. В стек, например, могут быть помещены границы большей подтаблицы, которые сохраняются в нем до тех пор, пока обрабатывается другая подтаблица. Элемент стека, описывающий необрабатываемую подтаблицу, должен со- держать маркеры ее левой и правой границ. Эти идеи объединены в следующем алгоритме. Алгоритм QUICK—SORT. Задана таблица записей Rx, R2, Rn- Данный алгоритм сортирует таблицу в возрастающем порядке. Он основан на описанном выше методе обменной сорти- ровки с разделением. Предполагается наличие двух фиктивных записей Ro и Rn + п причем Ко Ki Kn +1 для всех 1 i п Стек, используемый для хранения нижней и верхней границ каждой необрабатываемой подтаблицы, представляется соответ- ственно векторами LOWER и UPPER; ТОР означает верхний элемент стека. Переменные LB и UB означают нижнюю и верхнюю границы обрабатываемой в данный момент подтаблицы. В про- цессе обработки подтаблицы используются индексы i и j. Пере- менная TEMP содержит ключ, запись с которым должна быть помещена в ее конечную позицию в сортируемой подтаблице. 1. (Инициализация. ] Установить ТОРч-1, LOWER [ТОР ]-<- ч-J и UPPER [ТОР] ч-п. 2. (Выполнение сортировки.] Повторять до тех пор, пока ТОР 0: (обработка новой подтаблицы) установить LB ч- ч-LOWER [TOP], UB ч— UPPER (ТОР], ТОР ч-ТОР — 1. Повторять шаги 3—6 [до тех пор, пока UB > LB. Выход. 3. [Инициализация прохода.] Установить [ч-LB, j ч-UB и TEMP ч— ki- 4. [Сканирование ключей справа налево. ] Повторять до тех пор, пока TEMP < Kj : установить j j — 1. Если j =& i. то установить Ki ч-ТЕМР и перейти к шагу 6. Установить Kj ч- КГ и i ч-i Ч- 1. 5. (Сканирование ключей слева направо. ] Повторять до тех пор, пока Kj < TEMP: установить i ч-i + 1. Если j >1, то установить Kj ч- Ki, j ч- j — 1 и перейти к шагу 4. Установить Kjj ч-ТЕМР и i ч-j. г 6. [Занесение в стек дескриптора необрабатываемой подта- блицы.] Установить ТОР ч-ТОР + 1. Если I —LB < UB — 1, то установить LOWER [ТОР] ч-j + 1, UPPER (ТОР] ч-UB и UB ч— i — 1; в противном случае установить LOWER (ТОР 1 ч- ч-LB, UPPER [ТОР] ч-i — 1 и LB ч-i Ц- 1. 535
Работа данного алгоритма начинается с занесения границ исходной таблицы в стек. В шаге 2 из стека извлекается информа- ция о границах каждой необработанной подтаблицы и затем начи- нается ее разделение. В шагах 3—6 осуществляется разделение конкретной подтаблицы. При разбиении подтаблицы на два под- множества дескриптор большего подмножества заносится в стек. Прослеживание работы этого алгоритма для приведенного выше набора ключей оставляем в качестве упражнения. Среднее число сравнений для данного алгоритма составляет О (nlog2n). Наихудшей ситуацией при использовании алгоритма QUICK____SORT является случай, когда таблица уже отсортиро- вана. При этом число сравнений равно О (п2). В этом случае алго- ритм QUICK-SORT не лучше сортировки посредством выбора. 6-1.5. Методы сортировки на деревьях В данном пункте мы рассмотрим два метода сортировки, основанных на древовидном представлении заданной таблицы. Первый метод, довольно простой, представляет собой сортировку с помощью бинарных деревьев. Второй метод, при котором также используются бинарные деревья, намного сложнее. Поскольку мы ранее уже ввели все концепции, необходимые для понимания сортировки с использованием бинарных деревьев, то здесь мы обрисуем первый метод только в общих чертах. Алго- ритм состоит из двух фаз, а именно, фазы построения и фазы обхода. Фаза построения обеспечивает последовательную вставку новых записей в древовидную структуру способом, аналогичным приведенному в п. 5-2.2, где речь идет о построении таблицы символов. Далее может быть произведен упорядоченный обход дерева, полученного на первой фазе (см. п. 5-1.2), в результате чего будет сформирована отсортированная таблица. Среднее число сравнений для данного метода равно О (nlog2n). Однако в наи- худшей ситуации требуется число сравнений О (п2), что соответ- ствует случаю, когда сортируемое дерево весьма «несбалансиро- вано». Более детальная информация о несбалансированных де- ревьях будет приведена в п. 6-2.3. Второй метод сортировки будет объяснен в терминах турнира игры в гольф. Предположим, что в турнире принимают участие восемь игроков и они должны встречаться в соответствии с графи- ком, данным на рис. 6-1.4. На этой диаграмме также представлены результаты встреч, в соответствии с которыми Поль побеждает Джона, Боб побеждает Рика и т. д., и, наконец, Кларенс побе- ждает Поля. В результате Кларенс объявляется победителем тур- нира. Сейчас мы хотим найти второго лучшего игрока. Им может быть либо Поль, либо Билл, либо Харвей. Второй лучший игрок может быть определен, если Билл сыграет с Харвеем, а победи- тель встречи "сыграет с Полем. Важно отметить, что при этом 536
Кларенс Кларенс может отсутствовать и нет необходимости полностью повторять турнир. Алгоритм, который мы сейчас сформулируем, является комби- нацией алгоритмов, предложенных Флойдом [9] и Уильямсом [261. На рис. 6-1.5 представлен пример таблицы в виде бинарного дерева, называемой пирамидой. В общем случае пирамида, пред- ставляющая таблицу из п записей, удовлетворяет условию Kj Ki при 2 j п и i = | j/2 | Бинарное дерево размещается последовательно таким образом, что индексы левого и правого «сыновей» записи (если они суще- ствуют) будут иметь значения 21 и 214-1 соответственно. И, на- оборот, индекс «родителя» записи (если он существует) будет иметь значение | j/2 |. Из рис. 6-1.5 ясно, что древовидная струк- тура, изображенная на нем, удовлетворяет определению пира- миды. Если имеется пирамидальное представление таблицы, то запись с наибольшим ключом находится в корне дерева (называемом также вершиной пирамиды). Теперь мы сформулируем алгоритм, входом которого является неотсортированная последовательно размещенная таблица, а на выходе создается пирамида. Исходным моментом ва работе алгоритма является построение начальной пирамиды (пи- /х рамиду образует дерево из одной записи) / с последующей вставкой в имеющуюся пирамиду очередной записи, в результате /\ /~\ чего формируется новая пирамида. Встав- / \ У V ки выполняются неоднократно до тех пор, к?^ пока в пирамиду не войдут все записи A Г исходной таблицы. J К Алгоритм CREATE_HEAP. Задана таблица R, содержащая записи Rx, К2, ..., Ки- ДаННЫИ аЛГОрИТМ строит пирамиду представление набора ключей 537
описанным выше способом. Индексная переменная q служит для управления числом выполненных вставок. Переменная j является индексом родителя записи Rj. NEW—рабочая область для записи, a KEY содержит ключ записи, которая в данный момент должна быть вставлена в имеющуюся пирамиду. 1. [Построение пирамиды.] Повторять шаги 2—6 при q = 2, 3, ..., п. 2. [Инициализация.] Установить i ч-q, NEW Rq и KEY ч- *-Kq. 3. [Вставка новой записи в имеющуюся пирамиду. ] Повторять шаги 4 и 5 до тех пор, пока i > 1. 4. [Выявление родителя новой записи. ] Установить i ч— 5. [Перестановка записей?] Если KEY > Kj, то установить Ri ч-Rj и i ч- j; в противном случае перейти к шагу 6. 6. [Копирование новой записи в соответствующее ей место.] Установить Rj ч—NEW. 7. [Конец. ] Выход. Первый шаг алгоритма —это повторяемый оператор, который управляет построением требуемой пирамиды путем последова- тельных вставок записей. В шаге 2 выбирается запись, которая должна быть вставлена в имеющуюся пирамиду, и производится копирование этой записи в область NEW. В шагах 4 н 5 новая запись добавляется (в качестве листа) в имеющуюся пирамиду (т. е. в бинарное дерево) и продвигается иа дереве по пути между новым листом и вершиной пирамиды. Этот процесс повторяется до тех лор, пока новая запись не достигнет позиции в дереве, удовлетворяющей определению пирамиды- Копирование новой записи в соответствующее ей место в дереве производится в шаге 6. На рис. 6-1.6 представлена последовательность построения пира- миды, изображенной на рис. 6-1.5, со следующим исходным на- бором ключей: 42, 23, 74, И, 65, 58, 94, 36, 99, 87. Каждое дерево на диаграмме представляет состояние после того, как процесс вставки и реконструкции полностью выполнен. Записью с наибольшим ключом сейчас является Rx; она может быть выписана непосредственно. Это осуществляется путем пере- становки Rx и Rn и последующей реконструкции новой пирамиды, содержащей только п — 1 записей. Это реализуется способом, аналогичным используемому в алгоритме CREATE______HEAP Ре- зультатом такой реконструкции является помещение записи со вторым наибольшим ключом в Rx. Эту запись теперь можно поменять местами с записью Ro _ х. Затем строится новая пирамида из п —2 записей. Сортировка исходной таблицы может быть проведена путем повторения такого процесса перестановки и ре- конструкции. Алгоритм сортировки формулируется следующим образом. 538
Рис. 6-1.6- Трасса алгоритма CREATE___HEAP Алгоритм HEAP—SORT. Заданы таблица R, содержащая п записей Rx, R2, Rn, и известен алгоритм CREATE_______HEAP, который был ранее описан. Алгоритм HEAP „.SORT осуществляет сортировку таблицы в возрастающем порядке. Переменная q является индексом обхода дерева. Индексные переменные i и j используются в том случае, когда j является индексом левого сына записи с ключом К3-; SAVE является рабочей областью хранения записи, a KEY является переменной, содержащей ключ записи на каждом проходе. 1. [Построение пирамиды.] Вызвать CREATE__HEAP (R). 2. [Выполнение сортировки. ] Повторять шаги 2—8 при q = = n, п — 1, ..., 2 3. [Извлечение записи. ] Установить Rx Rq. 4. [Инициализация.] Установить 1ч-1, SAVE-*-Rlt KEY ч— ч- Kj и j ч- 2. 5. [Реконструкция пирамиды. ] Повторять шаги 6 и 7 до тех пор, пока j q — 1. 6. [Получение индекса сына с наибольшим значением ключа. ] Если j -|- 1 < q и Kj + j > Kj, установить j -<-j + 1- 7. [Требуется перестановка записей?] Если Kj > KEY, то установить R{ ч- Rj, i ч- j, j <- 2 * i; в противном случае перейти к шагу 8. 8. [Копирование записи в соответствующее ей место. ] Уста- новить Rj ч- SAVE. 9. [Конец. ] Выход. 539
Рис. 6-1.7. Трасса алгоритма HEAP__SORT Работа данного алгоритма начинается с построения пирамиды для исходной таблицы. Шаг 2 осуществляет управление п — 1 проходами, требуемыми для сортировки таблицы. Остальные шаги весьма сходны с шагами, выполняемыми в алгоритме CREATE___HEAP для построения новой пирамида после включе- ния новой записи. На рис. 6-1.7 представлена трасса алгоритма сортировки для пирамиды, изображенной на рис. 6-1.5, где каж- дое дерево представляет состояние процесса сортировки в конце очередного прохода. Анализ наихудшего случая для данного алгоритма показал (см. работу [251), что число сравнений равно О (nlog2n). Кроме того, для данного алгоритма не требуется до- полнительных рабочих областей памяти, за исключением области для одной записи. Отметим, что данный алгоритм при некоторых вариантах наборов ключей может превосходить по своей эффек- тивности алгоритм QUICK____SORT, для которого число сравне- ний в худшем случае равно О (п2). 6-1.6. Сортировка слиянием Операция сортировки тесно связана с процессом сли- яния. На заре обработки данных слияние осуществлялось на перфокартах с помощью устройств, называемых подборочными 540
машинами. На вход такого устройства подавались две отдельные колоды перфокарт, каждая из которых была отсортирована. Устройство выполняло слияние этих колод, в результате которого создавалась одна отсортированная колода. В данном параграфе мы сформулируем алгоритм сортировки, основанный на последо- вательных слияниях. Сначала рассмотрим слияние двух упорядоченных таблиц, которые должны быть объединены таким образом, чтобы получи- лась одна отсортированная таблица. Этот процесс может быть легко выполнен путем последовательного выбора записи с наи- меньшим значением ключа, имеющейся в любой из таблиц, и по- мещения этой записи в новую таблицу, в результате чего будет формироваться упорядоченный список. Например, для таблиц: таблица 1 Ц 23 42 таблица 2 9 25 этот процесс отобразится следующим образом таблица 1 [ 1 таблица 2 25 новая таблица 9 таблица I 23 таблица 2 25 новая таблица 9 таблица 1 42 таблица 2 25 новая таблица 9 таблица 1 42 таблица 2 новая таблица 9 таблица 1 таблица 2 новая таблица 9 23 42 42 И И 23 11 23 25 11 23 25 42 Данный процесс описывается следующим алгоритмом. Алгоритм SIMPLE - MERGE. Заданы две упорядоченные таб- лицы ах, а2, ..., ап и Ьь Ь2, .... bm. Данный алгоритм сливает эти таблицы и строит упорядоченную таблицу clt с2, ...» cn + т. Пере- менные i, j и к используются в качестве индексов в этих трех таблицах. 1. (Инициализация. J Установить i ч-j ч-k ч- 1. 2. [Сравнение соответствующих записей и извлечение записи с наименьшим значением. ] Повторять до тех пор, пока i п и j т: если а( bif то установить ск ч-а„ i ч-j -|- 1 и к ч- ч-к + 1; в противном ’’случае установить’ск ч-bj, j ч-j 4- 1 и к ч-к 4 1. 3. [Перенос оставшихся необработанных записей в выходную область.} Если I > п, то повторять при г = j, j 4 1, .... m: установить ck ч-br и к ч-к -j- 1; в противном случае повторять при г ~ i, I 4- 1, ..., п: установить ск ч-аг и к ч-к 4- 1. Предыдущий алгоритм может быть обобщен на случай слияния к отсортированных таблиц в одну отсортированную таблицу* 541
Такая операция слияния называется многопутевым слиянием, или k-путевым слиянием. Многопутевое слияние может быть осуществлено путем много- кратного выполнения попарных слияний. Например, если имеется 16 таблиц, которые должны быть слиты, мы можем сначала слить их попарно, используя алгоритм SIMPLE—MERGE. В результате первого тага образуется восемь таблиц, которые снова попарно сливаются и дают четыре таблицы. Этот процесс повторяется до тех пор, пока не будет получена единственная таблица. В данном примере, для того чтобы получить одну таблицу, требуется четыре отдельных шага. В общем случае, для того чтобы слить 2к отдель- ных таблиц в одну таблицу, требуется осуществить к отдельных шагов. Эта стратегия может быть легко применена для сорти- ровки. Если задана таблица, содержащая п записей, ее можно рассматривать как набор из п таблиц, каждая из которых содер- жит одну запись. Очевидно, что таблица из одной записи является отсортированной. Следующий ниже алгоритм выполняет один проход сортировки. Алгоритм MERGE—PASS. Задана таблица R из п записей, которая рассматривается как расчлененная на упорядоченные подтаблицы. Каждая из этих подтаблиц содержит L записей (или менее). Данный алгоритм попарно сливает эти подтаблицы. За- писи таблицы R обозначаются как Rx, R2, ..., Rn. В процессе сли- яния требуется вспомогательная таблица С с записями Q, С2, ..., См. Переменные р и q служат для указания того, какие именно пары подтаблиц к настоящему моменту подвергаются слиянию. Поскольку п необязательно равно целой степени двух, размеры сливаемых подтаблиц не всегда одинаковы (т. е. не равны L). Размеры таблиц задаются переменными пх и п2. Переменные I и j индексируют соответствующие записи сливаемых подтаблиц. Ин- дексная переменная к указывает запись в выходной области, а г является рабочей индексной переменной. 1. [Инициализация первого прохода. ] Установить р ч- 1, пх ч- ч- п2 <rL л q < -p|L. 2. [Выполнение одного прохода. J Повторять шаги 3—7 до тех пор, пока q п. 3. [Инициализация простого слияния.] Установить i ч-р, j <-q и к <-р. 4. [Сравнение соответствующих записей и выбор записи с наи- меньшим значением. 1 Повторять до тех пор, пока 1 -f- i — р --=т пт и 1 4- j — q с п2: если Ki < Kj, то установить Ck 4-Rif i ч-i + 1 и k ч-k 4- h в противном случае установить Ck ч-Rj, j j 4- 1 и к ч- k 4- 1. 5. [Копирование оставшихся необработанных записей из под- таблицы в выходную область. ] Если 1 i — р > лх» то повто- рять при г = j, j 4- К •••» q 4- n2 — 1: установить Ck ч- Rr и k ч-k 4- 1:в противном случае повторять при г = i, i + 1, ...» р 4- nx — 1: установить Q -<-Rr и к ч-k 4- 1. 542
6. [Обновление и проверка индекса подтаблицы. ] Установить р ч—q + п2. Если р > п, то завершить выполнение алгоритма. 7. [Обновление q и контроль границы второй подтаблицы. ] Установить q ч-р + L. Если q + L >п + 1> то установить п2 ч-п — q + 1. 8. [Копирование непарной подтаблицы. ] Повторять при г = р, р + 1, п: установить Сг 4-Rr. Выход. Шаги алгоритма 4—-6 представляют собой перезапись алгори- тма SIMPLE—MERGE, когда две подвергаемые слиянию упо- рядоченных подтаблицы содержатся в таблицу R. Результиру- ющая упорядоченная таблица заносится в таблицу С. Поскольку п может быть любым целым положительным числом, то может воз- никнуть ситуация, когда для отдельной упорядоченной подта- блицы не имеется другой подтаблицы, с которой она может быть слита. В этом случае непарная подтаблица просто копируется в выходную область, как показано в шаге 8. Пример выполнения алгоритма MERGE__PASS дан на рис. 6-1.8. Алгоритм MERGE_PASS может теперь многократно вызы- ваться для сортировки заданной таблицы. Если имеется такое к, что п = 2к, то потребуется к проходов. При произвольном п потребуется Slogan | проходов. Для двухпутевой сортировки слиянием основным является следующий алгоритм. Алгоритм TWO—WAY.—MERGE______.SORT. Задана таблица R, содержащая п записей Rn R2, ..., Rn. Данный алгоритм осуще- ствляет сортировку исходной таблицы в возрастающем порядке путем последовательного вызова алгоритма MERGE PASS. Для работы алгоритма необходима вспомогательная таблица С, име- ющая тот же размер, что и таблица R. Переменная L специфи- Рис. 6-1.8. Трасса одного прохода алгоритма MARGE___PASS 543
цирует число элементов в каждой из подтаблиц, сливаемых на очередном проходе. 1. [Выполнение сортировки.] Повторять при L = 1, 2, 4, 2r]og£ni—1- если log2n четный, то вызвать MERGE._PASS (R, n, C, L); в противном случае вызвать MERGE PASS (С, п, R, L). Рис. 6-1.10. Альтернативный подход к двухпутевой сортировке слиянием 544
2. [Перекопированиет если требуется. 1 Если [”log2n*~| не- четный, то повторять при i = 1, 2, п: установить R, -*-Сг Выход. В шаге 1 путем анализа значения логарифма величины о опре- деляется, какая из областей (R или С) является выходной областью в очередном проходе. Аналогичная методика используется для определения того, требуется ли конечная операция перекопирова- ния после того, как таблица отсортирована. Трасса данного алгоритма для определенной таблицы представлена на рис. 6-1.9. Описанный метод сортировки весьма эффективен. Поскольку при сортировке требуется выполнить | log2n ] проходов, то необходимое суммарное число сравнений равно О (nlog2n). Отме- тим, что эта величина соответствует как наихудшему, так и сред- нему случаям. Одним из очевидных недостатков данного метода является требование большой вспомогательной области памяти. Другим способом выполнения двухпутевой сортировки сли- янием является учет степени упорядоченности в исходной таблице. Сортировка, основанная на таком подходе, иллюстрируется на рис. 6-1.10. Формулировка соответствующего алгоритма при- лагается в качестве упражнения в конце этого параграфа. 6-1.7. Поразрядная сортировка Поразрядная сортировка — это метод, который возник задолго до распространения цифровых вычислительных машии. Он использовался и еще используется для сортировки перфокарт на механических устройствах. Такое устройство сортировки обычно обрабатывает стандартную перфокарту с 80 колонками, каждая из которых может содержать символ из некоторого алфа- вита. В процессе сортировки перфокарт на устройстве такого типа за один раз анализируется только одна колонка карты. Для селекции одной из 80 колонок используется металлический указа- тель устройства сортировки. Для числовых данных устройство сортировки укладывает все перфокарты, содержащие заданную цифру, в соответствующий карман * 1 . Имеется десять карманов, соответствующих десяти десятичным цифрам. В формулируемой таким образом колоде перфокарт внизу находится стопка карт из кармана 0, а вверху — из кармана 9. В общем случае сортируются числа, содержащие более одной цифры. При этом может быть произведена сортировка в возрастающем порядке путем выполне- ния нескольких индивидуальных цифровых сортировок по по- рядку. Иначе говоря, осуществляется последовательно сорти- ровка по каждой колонке, начиная с колонки самого младшего (самого правого) разряда и продвигаясь через другие колонки справа налево. В качестве примера рассмотрим следующую по- следовательность чисел (по одному числу на каждой перфокарте): 42, 23, 74, II, 65, 57, 94, 36, 99, 87, 70, 81, 61. 1 Другое название этого метода — «карманная сортировка». — Прим. ред. 18 Трамбле Ж-, Соренсон П. 545
После первого просмотра по младшей цифре каждого числа мы получим: 61 81 94 87 99 70 11 42 23 74 65 36 57 Карман: 0123456789 Теперь, объединяя содержимое разных карманов таким обра- зом, чтобы перфокарты, входящие в карман 0, были снизу, а вхо- дящие в карман 9 — сверху, получим: 70, 11. 81. 61, 42. 23. 74 , 94. 65. 36. 57. 87, 99. На втором просмотре мы сортируем числа по старшей цифре. Это приводит к следующему: 65 74 87 99 11 23 36 42 57 61 70 81 94 Карман: 0(23456789 Объединяя теперь карты всех десяти карманов в том же по- рядке, что и при первом просмотре, мы заканчиваем сортировку. Этот тип сортировки называется поразрядной сортировкой. Описанный метод механической сортировки может быть при- менен и в ЭВМ. Одной из возникающих при этом проблем является организация «карманов» в памяти машины. Методы последова- тельного распределения памяти для организации карманов не- практичны, поскольку мы заранее не зиаем, как много записей будет содержать тот или иной карман в некотором шаге сорти- ровки. Так как мы не можем предсказать число записей в кармане, то следует использовать связанное распределение. Каждый кар- ман может быть представлен как- связанная очередь типа FIFO (First-in, first-out — первым пришел, первым обслу- живаешься). В конце каждого просмотра эти очереди могут быть легко объединены в нужном порядке. Если наибольшее число цифр в ключе равно гп, то для того, чтобы отсортировать числа, потребуется ш последовательных просмотров, начиная с первой и кончая старшей цифрой. В приводимом ниже алгоритме мы пред- полагаем, что ключ К содержит m цифр в форме bmbm _ г ... bv Также предполагается, что имеется механизм селекции для вы- бора каждой цифры ключа. Будем считать, что исходная таблица устроена в виде простого связанного списка. Алгоритм RADIX_SORT. Задана таблица из п записей, организованная в виде связанного списка. Каждый узел списка содержит поле ключа К и поле указателя LINK. Данный алго- ритм осуществляет поразрядную сортировку описанным выше способом. Адрес первой записи в связанной таблице задан с по- мощью указательной переменной FIRST, векторы Т и В исполь- зуются для хранения адресов конечной и начальной записи в каж- дой очереди (кармане). В частности, обозначения Т [i 1 и В [i 1 указывают соответственно на верхнюю и нижнюю записи в i-м 546
кармане. Переменная j является индексом просмотра. Перемен- ная i используется в качестве индекса кармана, а г является рабо- чей индексной переменной. Указательная переменная R означает адрес текущей записи, проанализированной в таблице и напра- вленной в подходящий карман. PREV является указательной переменной, которая используется в процессе объединения со- держимого карманов в конце каждого просмотра. 1. [Выполнение сортировки.] Повторять шаги 2—4 при j — = 1, 2, ..., m. 2. [Инициализация просмотра.] Повторять при i = 0, 1, ...,9: установить Т [i] ч-В [i ] ч- NULL.’ Установить R ч-FIRST. 3. [Распределение всех записей в подходящие карманы. ] Повторять до тех пор, пока R =# NULL: Установить к ч-bj (получение j-й цифры ключа К (R))- Установить NEXT ч- 4-LINK(R). Если Т [k] = NULL, то ? установить Т [к] ч- ч-В [к] ч-R; в противном случае установить LINK (Т [к]) ч- ч- R и Т [к ] ч- R. Установить LINK (R) ч- NULL и R ч- НЕХТ. 4. [Объединение содержимого карманов. ] Установить г ч- 0. Повторять до тех пор, пока В [г] = NULL: установить г ч-г + 1. Установить FIRST ч-В [г]. Повторять при i = г + 1, г + 2, ..., 9: Установить PREV ч-Т [i—1]. Если Т [i] NULL, то тИ—711 flO) » NULL ♦ в/0/ tW——|—* | TW—'l | // [*—е (1) аа—.и т(7А—.И т01—4,1 тИ J |* в (J) —i'"'"'!99 । '* । т1ч}——в (4) Tffl—65 |*—в/Я Т|Я—1| | 57 |. в(Я J/; I*—в т/Й | |» I ч [ 6/ |*—в/6/ а71—Xх! 87 Н~* 1 !—»И rw—»| |^|*—j—* 1 h—в w тЙЦ__» null 4—b(uj —»| | #7 |*—[— | g/ |»—в(в1 r,9>—{<И 991*—в у№1 | 99 |«—|— | 94 |* а1 а Рис. 6-1.11. Последовательность выполнения ° первый проход; б — второй проход 18* алгоритма поразрядной сортировки: 547
установить LINK (PREV) -<-B [rl; в противном случае устано- вить Т [i J ч-PREV. Данный алгоритм весьма прост Шаг 1 управляет числом про- смотров, требуемых для выполнения сортировки. В шаге 2 осу- ществляется инициализация массивов, представляющих карманы, таким образом, что все карманы становятся пустыми в начале очередного просмотра. Кроме того, переменная R устанавливается так, чтобы указывать на первую запись в таблице на данном шаге. В шаге 3 алгоритма осуществляется обработка каждой записи таблицы и передача такой записи в соответствующий кар- ман. В шаге 4 содержимое карманов объединяется в новую свя- занную таблицу, которая используется в качестве исходной для следующего просмотра. Переменная FIRST устанавливается та- ким образом, чтобы указывать на низшую запись в первом же не- пустом кармане (начиная с кармана 0 до кармана 9). Рис. 6-1.11 иллюстрирует работу алгоритма при сортировке ранее приведенной таблицы. Данный алгоритм имеет хорошие характеристики, когда ключи относительно коротки. Для ключа из m цифр требуется m * п обращений к ключу. 6-1,8. Сортировка с вычислением адреса В качестве последнего метода рассмотрим применение функции хеширования для сортировки. Вспомним приведенное в п. 4-3.2 построение таблицы символов, где все имена были отоб- ражены путем хеширования («хешированы») в числа. Набор имен, которые хешируются в одно и то же число, был назван классом эквивалентности. Классы эквивалентности представлялись с по- мощью связанных списков. Эта же идея может быть использована и для сортировки таблицы. Было бы идеально, если бы для набора ключей имело место равномерное распределение, т. е. вероятность того, что некоторый конкретный ключ отобразится в любой из ш классов эквивалентности, равнялась бы 1/т. Если данный ключ хешируется в определенное число (т. е. связанный список), в кото- рое уже был ранее хеширован некоторый предшествующий ключ (т. е имеет место коллизия), то новый ключ вставляется в соот- ветствующий данному числу связанный список, причем так, чтобы сохранить порядок ключей. На рис. 6-1.12 представлен результат хеширования и вставки набора ключей 42, 23, 74, 11, 65, 57, 94, 36, 99. 87, 70, 81, 61 с использованием функции хеширования Н (х) = х mod 10 4- 1, где х -—значение ключа, a m = 10. На' рис. 6-1.12 элемент EQUIV ИI задает адрес первой записи в i-м связанном списке. Полученные на этапах хеширования и вставки m связанных спи- 548
сков теперь могут быть объедине- ны (см. п. 6-1.6) в один связанный список, задающий требуемую от- сортированную таблицу. Кнут показал, что среднее число сравне- ний для этого метода равно О (п). Это первый среди рассмотренных нами метод сортировки, обеспе- чивающий число сравнений по- рядка п. Напомним, тем не менее, что этот результат справедлив только тогда, когда вероятность хеширования любого ключа в лю- бое число от 1 до га равна 1/пъ Наихудшим случаем является си- туация, когда все ключи отобра- жаются в одно число. При этом характеристика метода ухуд- шается до О (п2). Функция хеши- рования может также приме- няться и в поиске. Эта идея будет развита в п. 6-2.4. Итак, если число записей в таблице мало, то может быть ис- пользована сортировка посредством выбора или сортировка мето- дом пузырька. Если же п велико, а ключи короткие, то успешно может быть реализована поразрядная сортировка. При больших п и длинных ключах могут использоваться быстрая сортировка, пирамидальная сортировка или сортировка слиянием. Если та- блица первоначально почти отсортирована, то быструю сорти- ровку применять не следует. Когда ключи после хеширования равномерно распределяются на интервале [1, mJ, то весьма хоро- шие результаты дает метод сортировки вычислением адреса. Упражнения к п. 6-1 1. Измените алгоритм BUBBLE с учетом того факта, что все записи, начиная с некоторой и кончая последней, уже расположены в правильном по- рядке; следовательно, эти записи не должны снова анализироваться. 2. Модифицируйте алгоритм BUBBLE так, чтобы соседние проходы выпол- нялись в противоположных направлениях, т. е. в течение первого прохода за- пись с наибольшим значением ключа должна быть помещена в конец таблицы, а в течение второго прохода запись с наименьшим ключом должна стать первой записью в таблице и т. д. 3. Постройте алгоритм сортировки способом выбора, когда таблица пред- ставлена в виде связанного списка. 4. Проследите выполнение алгоритма QUICK— SORT для таблицы, пред- ставленной в п. 6-1.4. б. Сформулируйте алгоритм сортировки на бинарных деревьях. 6. Проследите выполнение алгоритма HEAP—SORT для таблицы, задан- ной в П. 6-1.5. 549
7. Постройте алгоритм двухпутевого слияния, учитывающий степень упо- рядочения, которое уже существует в исходной таблице, как это предлагалось в конце п. 6-1.6. 8. Составьте алгоритм даухпутевой сортировки слиянием, используя методы связанного распределения. 9. Измените алгоритм RADIX—SORT так, чтобы очереди были цикли- ческими. 10. Сформулируйте алгоритм сортировки с вычислением адреса, основан- ный на рассуждениях, приведенных в тексте. 6-2. ПОИСК В этом параграфе мы сформулируем ряд постепенно усложняющихся алгоритмов поиска данных. Линейный и бинар- ный методы поиска являются относительно простыми, но для некоторых задач они имеют серьезные недостатки. Для многих процессов эффективным является поиск на сбалансированном дереве. Обсуждается также ряд поисковых методов, включающих использование функций хеширования. 6-2.1. Последовательный поиск Простейшим методом поиска определенной записи, на- ходящейся в неупорядоченной таблице, является последователь- ный просмотр каждой табличной записи, который продолжается до тех пор, пока не будет найдена желаемая запись. Алгоритм та- кой процедуры поиска следующий. Алгоритм LINEAR_____SEARCH. Дана неупорядоченная таб- лица записей Rlt R2, Rn с ключами соответственно К2, ..., Кп. Алгоритм ищет запись с ключом х. Предполагается суще- ствование ограничительной записи Rn + х. 1. [Инициализация.] Установить Кп + 1-^-х. 2. [Поиск в таблице.] Повторять при i ~ 1, 2, ..., п + 1: (Сравнение с каждым ключом). Если К,- = х, то, если i = п + 1, напечатать ’НЕУДАЧНО’; в противном случае напечатать ’УСПЕШНО’. Выход. В первом шаге алгоритма ключу ограничительной записи при- сваивается значение х. Во втором шаге осуществляется последо- вательный поиск среди n + 1 записей. Если индекс найденной записи указывает на Rn + поиск не удался; в противном случае поиск закончился успешно, a i является индексом требуемой записи. Эффективность метода поиска может быть оценена путем под- счета числа сравнений ключей, потребовавшихся для нахождения определенной записи. Существует два важных случая, а именно, средний и наихудший. Для предыдущего алгоритма в худшем случае проводится n J- 1 сравнений ключей, в то время как в среднем требуется (п + 1)/2 сравнений. Для этого метода как среднее, так и наихудшее время поиска пропорциональны п, т. е. равны О (и). Эти оценки основаны на предположении, что вероят- 550 .
ность обращения к заданной записи такая же, как и к любой дру- гой записи. Пусть Pt есть вероятность обращения к записи Rt- при 1^ i <: п. Средняя длина поиска ALOS 1 для таблицы, содержа- щей п записей, задается выражением ALOS = 1 X ₽! + 2 < Р2 + ... 4- п X Рп, где Рх I- Р2 -г - + Pn = 1. Теперь предположим, что вероятности обращений к отдельным записям не равны, т. е. Р,- =£ 1/п для 1 i и. Это приводит к естественному вопросу, можем ли мы организовать таблицу так, чтобы уменьшить величину ALOS. Ответ на него утвердительный, и требуемую реорганизацию можно провести на основании ана- лиза предыдущего выражения для величины ALOS. Последняя будет минимальна, если записи расположить так, чтобы (1) Например, полагая п — 5 и Р{ = 1/5 для 1 i 5, получим ALOS = 1 X 1/5 + 2 X 1/5 + 3 X 1/5 + 4 X 1,5 + + 5 X 1/5 = 3. Теперь предположим, что Рх == 0,4; Р2 — 0,3; Р3 ~ 0,2; Р4 = = 0,07 и Р6 = 0,03. В этом случае средняя длина поиска составит ALOS = 1 X 0,4 + 2 0,3 + 3 :< 0.2 + 4 X 0,07 + 5 X 0,03 = = 2,03. Мы получили величину, существенно меньшую 3. Реорганизация начальной таблицы в соответствии с условиями (1) называется предупорядочением. Если возможны частые удаления записей из таблицы, ее жела- тельно представить в виде связанного списка. Просмотр такой таблицы занимает почти столько же времени, что и просмотр ее последовательного эквивалента. Отметим, что включение новых записей в последовательно просматриваемую таблицу может быть выполнено весьма эффективно в предположении, что таблица не упорядочена. Если требуется дальнейшее уменьшение времени поиска, мы должны сначала упорядочить элементы таблицы. Этот подход обсуждается в следующем пункте. 6-2.2. Бинарный поиск Другим относительно простым методом доступа к та- блице является метод бинарного поиска. Записи в таблицу за- носятся в лексикографическом или численно возрастающем по- 1 Средняя длина поиска (Average Length of Search) измеряется здесь средним числом проб, или сравнений, необходимых для нахождения записи в таблице или отыскания свободного места в таблице для новой записи. — Прим. ред. 551
рядке. Для достижения упорядоченности может быть использован какой-либо из подходящих методов, обсуждавшихся в предыдущем параграфе. В рассматриваемом методе поиск отдельной записи с определенным значением ключа напоминает поиск фамилии в телефонном справочнике. Сначала приближенно определяется запись в середину таблицы и анализируется значение ее ключа. Если оно слишком велико, то анализируется значение ключа, соответствующего записи в середине первой половины таблицы, и указанная процедура повторяется в этой половине до тех пор, пока не будет найдена требуемая запись. Если значение ключа слишком мало, испытывается ключ, соответствующий записи в се- редине второй половины таблицы, и процедура повторяется в этой половине. Этот процесс продолжается до тех пор, пока не будет найден требуемый ключ или не станет пустым интервал, в котором осуществляется поиск. Бинарный поиск осуществляет следующий алгоритм. Алгоритм BINARY_____SEARCH. Дана таблица записей Rlt R2, ..., Rn, которые расположены в порядке возрастания их клю- чей; алгоритм ищет запись с заданным ключом х. Переменные В и Е указывают соответственно на нижнюю и верхнюю границы интервала поиска. I. [Инициализация.! Установить В 1 и Е п. 2. [Выполнение поиска. ] Повторять шаги 3—4 до тех пор, пока В Е. 3. [Получение индекса середины интервала поиска. 1 Уста- новить i |__(В -J- E)/2_J. 4. [Сравнение.] Если х < К-, то установить Е i —1; в противном случае, если х > К,-, то установить В i + 1; в противном случае напечатать ’ЭЛЕМЕНТ НАЙДЕН’ и завер- шить выполнение алгоритма. 5. [Неудачный поиск. ] Напечатать ’ЭЛЕМЕНТ НЕ НАЙДЕН’ и завершить выполнение алгоритма. Ниже отображена последовательность выполнения данного алгоритма для следующей выборки данных: 75, 151, 203, 275, 318, 489, 524, 591, 647, 727 при х = 275 (табл. 6-2.1, а) и х = 727 (табл. 6-2.1, б). Для того чтобы иайти нужную запись в таблице, в среднем требуется |__log2n_| •— 1 проб. В худшем случае понадобится l_log2n_J + 1 проб. Это значительно лучше, чем при последова- тельном поиске. У бинарного поиска есть ряд недостатков. Чтобы вставить новую запись в существующую таблицу, для сохранения упорядо- ченности потребуется физически переместить много записей. По- добная ситуация возникает и при удалении записей. В связи с этим для данного метода отношение времени вставки или удаления запи- сей к времени, поиска довольно велико. Бинарный поиск удобен 552
Таблица 6-2.1 Трассировка бинарного поиска Поиск записи а) с ключом 275 С) с ключом 727 Итера- ция В Е i Итера- ция В Е i 1 1 10 5 1 1 10 5 2 1 4 2 2 6 10 8 3 3 4 3 3 9 10 9 4 4 4 4 4 . ю 10 10 при небольшом числе вставок и (или) удалений записей из та- блицы. Одним из путей улучшения этого метода поиска является использование для представления таблиц связанных бинарных деревьев, что облегчает задачу вставки и удаления записей. Это средство обсуждается в следующем пункте. 6-2.3. Деревья поиска Приведенный в предыдущем пункте метод бинарного поиска можно легко объяснить в терминах бинарных деревьев. На рис. 6-2.1 изображено бинарное дерево, соответствующее бинарному поиску по алгоритму BI NARY_SEARCH при п = 10. В п. 5-2.2 связанные бинарные деревья использовались для по- строения таблиц символов. Такая же структура может предста- влять и таблицу записей. Алгоритм поиска для этого представле- ния очень похож на алгоритм TABLE, приведенный в п. 5-2.2, и поэтому здесь опускается. Средняя длина поиска для такого представления записей также равна О (log2n). Однако в худшем из возможных случаев порядок длины поиска составляет величину О (п), что не лучше, чем в худшем варианте последовательного поиска. На рис. 6-2.2 показаны некоторые варианты деревьев, использование которых может привести к плохим характеристикам поиска. В двух первых случаях содержатся деревья, имеющие только ненулевые левые или правые связки. Остальные два случая пред- ставляют зигзагообразные структуры. Время поиска для этих структур соответствует п сравнениям. Мы уже рассматривали вставку листьев в бинарное дерево. Теперь будет исследована операция удаления из дерева произ- вольной вершины. Заметим, что из дерева можно исключить лю- бую вершину, даже корень. Таким образом, возникает несколько 553
5 Рис. 6-2.1. Бинарное де- рево, соответствующее бинар- ному поиску при п = 10 Рис. 6-2.2. Бинарные деревья, дающие наихудшие значения времени поиска ситуаций. Во-первых, если для удаления намечена вершина с пу- стым левым или правым поддеревом, то операция будет тривиаль- ной. Этот вариант изображен на рис. 6-2.3, а, здесь удаляется вер- шина 6. Однако если у удаляемой вершины левое и правое под- деревья не пусты, удаляется один из ее потомков (не ближайший), который потом используется для замещения первоначально на- меченной для удаления вершины. Отметим, что у этой вершины- потомка всегда пусто левое поддерево. На рис. 6-2.3, б приведен пример второго случая, когда удаляется вершина 4. Допустим, что структура каждой вершины содержит четыре ноля, а именно LPTR, RPTR, К (ключ) и DATA. Дерево также содержит головную вершину, адрес которой дается указателем HEAD. Алгоритм TREE_DELETE. Дано упорядоченное связанное бинарное дерево, имеющее только что описанную структуру вер- шин, и ключ записи х, указывающий, какая запись намечена для удаления. Алгоритм удаляет запись с ключом х, сохраняя при этом структуру бинарного дерева; PARENT обозначает адрес родителя вершины, намеченной для удаления. Переменная Р содержит адрес записи, ключ которой равен х; PRED и SUC являются указателями, используемыми для нахождения потомка До ' После Рис. 6-2.3. Удаление вершины из бинарного дерева 554
вершины Р (не ближайшего). Переменная Q содержит адрес вер- шины, к которой с целью завершения удаления должна быть присоединена левая или правая ветвь родителя удаляемой вер- шины. Переменная D характеризует направление от родительской вершины к вершине, предназначенной для удаления. 1. [Инициализация.] Если LPTR (HEAD) A HEAD, то уста- новить Р ч-LPTR (HEAD), PARENT -«-HEAD, D ч-’L’; в про- тивном случае печатать ’ДЕРЕВО ПУСТО’ и завершить выпол- нение алгоритма. 2. [Сравнение.] Если Р - NULL, печатать ’ВЕРШИНА НЕ НАЙДЕНА’ и завершить выполнение алгоритма. Если х < К (Р), то установить PARENT ч-Р, Р ч-LPTR (Р), D ч-’L’ и перейти к шагу 2. Если х > К (Р), то установить PARENT ч-Р, Р -е- RPTR (Р), D ч-’R’ и перейти к шагу 2. 3. [LPTR равно NULL?] Если LPTR (Р) = NULL, то уста- новить Q ч- RPTR (Р) и перейти к шагу 7. 4. [RPTR равно NULL?] Если RPTR (Р) = NULL, то уста- новить Q ч- LPTR (Р) и перейти к шагу 7. 5. [Проверка правого сына. ] Установить PRED ч- RPTR (Р). Если LPTR (PRED) = NULL, то установить LPTR (PRED) ч- *-LPTR (Р), Q -«—PRED и перейти к шагу 7. 6. [Нахождение потомка. ] Установить SUC ч- LPTR (PRED). Если LPTR (SUC) Ф NULL, то установить PRED ч-SUC и пе- рейти к шагу 6; в противном случае установить LPTR (PRED) ч- Ч- RPTR (SUC), LPTR (SUC) ч- LPTR (P), RPTR (SUC) ч- ч-RPTR (P), Q ^SUC. 7. [Изменение связей родителя. J Если D — *L’, то установить LPTR (PARENT) Q; в противном случае RPTR (PARENT) ч- <-Q. 8. [Освобождение записи с ключом х. 1 Возвратить вершину Р в список свободной памяти и завершить выполнение алгоритма. В шаге 1 алгоритма проверяется, не является ли дерево пу- стым. Инициализация выполняется в том случае, если дерево не пусто. В шаге 2 выполняется поиск записи, которая должна быть удалена. Если не удается найти нужную запись, алгоритм закан- чивается. Переменная D используется для запоминания информа- ции о том, является ли удаляемая запись корнем левого или пра- вого поддерева для PARENT. В шагах 3 и 4 проверяется простей- ший случай удаления. Более сложный случай реализуется ша- гами 5 и 6. Для отражения удаления в шаге 7 изменяется соответ- ствующая связь родителя записи, соответствующей ключу х. В последнем шаге удаленная вершина возвращается в список сво- бодной памяти. Теперь вернемся к задаче поддержания структуры дерева таким образом, чтобы за время, не большее О (log2n), могла быть выполнена каждая из следующих операций: 1) вставить новый 555
е(Т DlRECTlOb Рис. 6-2.8. Пример для случая 1: а — до балансирования: б — после балансирования ника выражения означают максимальную длин}' пути в соот- ветствующих деревьях после вставки вершины. Например, так как вершина X является критической (рис. 6-2.7, а), вершина Y должна быть сбалансирована до вставки новой вершины. Этот случай включает ситуацию, когда вершина Y становится .переве- шивающей в том же направлении, в котором перевешивала вер- шина X. На рис. 6-2.8 приведен конкретный пример второй воз- можной разновидности случая 1. Векторы PATH и DIRECTION определяются в приводимом ниже алгоритме. Отметим, что основ- ные шаги состоят в изменении трех указателей. Случай 2, приведенный на рис. 6-2.9, очень похож на первый, но вершина Y становится перевешивающей в направлении, проти- воположном тому, в котором перевешивала вершина X. Ясно, что вершина Z должна быть сбалансирована до включения новой вер- шины. Отметим, что ситуации, соответствующие левым деревьям на рис. 6-2.9, «гиб, очень похожи. Специфический пример случая 2 приведен на рис. 6-2.10, б. Векторы PATH и DIRECTION отно- сятся к приведенному ниже алгоритму. Сформулируем теперь алгоритм вставки элемента в сбаланси- рованное дерево. В структуру вершины входят левый указатель LPTR, правый указатель RPTR, поле ключа К, показатель сба- лансированности BI и информационное поле DATA. Именем всей структуры вершины является NODE. Предполагается, что заго- ловок списка, представляющего дерево, совпадает с левым указа- телем, содержащим адрес корня дерева. Алгоритм BALA NCED_ INSERT. Дано связанное представ- ление сбалансированного бинарного дерева с головой списка HEAD, структура которой описана выше, и заданы переменные NAME и INFO, содержащие значения ключа и информационного поля включаемого элемента. Алгоритм вставляет в дерево новый элемент, причем таким образом, что сохраняется свойство сбалан- сированности дерева; NEW является адресом вновь создаваемой вершины. Массив PATH используется для запоминания адресов 558
55
вершин от заголовка списка до места вставки в дерево новой вер- шины Соответствующий им вектор DIRECTION необходим для запоминания направлений ветвлении на этом пути. Величины L и R используются для обозначения соответственно левого или пра- вого ветвлений. Переменная MARK обозначает индекс элемента в массиве PATH, содержащего адрес критической вершины X. Переменная F указывает на родителя критической вершины перед операцией балансирования дерева. Функции указательных пере- менных X, Y и Z были описаны ранее. LEVEL является индексной переменной; Т — рабочий указатель, используемый для обхода дерева от корня до вновь вставляемой вершины. 1. [Это первая вставка?] Если LPTR (HEAD) = HEAD, то установить NEW <= NODE, LPTR (NEW) ч— RPTR (NEW) <- NULL, BI (NEW) 'В'. К (NEW) NAME, DATA (NEW) ч- ч- INFO, LPTR (HEAD) <— NEW и завершить выполнение алго- ритма. 2. [Инициализация. ] Установить LEVEL ч- О, PATH [LEVEL! HEAD, Т ч- LPTR (HEAD). 3. [Сравнение и при необходимости вставка.] Если NAME < < К (Т), то, если LPTR (Т) ф NULL, то установить LEVEL ч— ч- LEVEL + 1, PATH [LEVEL] ч- Т, DIRECTION [LEVEL] ч- <— *L', T <—LPTR (T) и перейти к шагу 3; в противном случае установить NEW <= NODE, LPTR (NEW) ч- RPTR (NEW) <- NULL, К (NEU7) <- NAME, DATA (NEW) <- INFO, LPTR (T) ч- NEW, BI (NEW) «- 'B'. LEVEL ч- LEVEL + 1, PATH [LEVEL] T, DIRECTION [LEVEL] ч-'L' и перейти к шагу 5. Если NAME > К (Т), то, если RPTR (Т) NULL, то установить LEVEL ч- LEVEL + 1, PATH [LEVEL ] ч- T, DIRECTION [LEVEL ] ч— 'R', T ч— RPTR (T) и перейти к шагу 3; в противном случае установить NEW <= NODE, LPTR (NEW) ч- ч- RPTR(NEW) <- NULL, К (NEW) <- NAME, DATA (NEU7)-*- INFO, RPTR (T) 4- NEW, BI (NEU7) ч- 'B', LEVEL ч- LEVEL + 1, PATH [LEVEL] ч- T, DIRECTION [LEVEL]<- ч— 'R' и перейти к шагу 5. 4. [Совпадение. ] Печатать 'ВЕРШИНА ВСТАВЛЕНА' и за- вершить выполнение алгоритма. 5. [Поиск несбалансированной вершины. I Повторять при i = LEVEL, LEVEL — 1, ..., I: установить P ч- PATH [il, если BI (P) =д 'В', то установить MARK <- i и перейти к шагу 6. Установить MARK, ч- 0. 6. [Модификация показателей сбалансированности.] Повто- рять при i = MARK + 1, MARK + 2, LEVEL: еслиИАМЕ< < К (PATH [i ]), то установить ВI (PATH [i ]) ч— 'L'; в против- ном случае установить BI(PATH [i]).«_'R'. 7. [Критическая вершина? ] Если MAR К = 0, то, завершить выполнение алгоритма. Установить D <— DIRECTION [MARK], X ч- PATH [MARK] и Y ч- PATH [MARK +11- (Вершина была сбалансированной, а теперь стала перевешивающей.) 560
а) Если BI (X) = 'В', то установить BI (X) D и завершить выполнение алгоритма. (Вершина была перевешивающей, а теперь стала сбалансированной.) б) Если BI (X) =# D, то, установить BI (X) 'В' и завершить выполнение алгоритма. (Вершина была перевешивающей, а теперь стала критической.) в) Если BI (Y) =/= D, то перейти к шагу 9. 8. (Повторное балансирование дерева: случай 1. ] Если D = = 'L', то установить LPTR (X) RPTR (Y) и RPTR (Y) <- X; в противном случае установить RPTR (X) LPTR (Y) и LPTR (Y) «-X. Установить BI (X) <- BI (Y) <- 'В', F ч- <- PATH (MARR — 1 ]- Если X = LPTR (F), то установить LPTR (F) ч- Y; в противном случае установить RPTR (F) Y. Выход. 9. [Повторное балансирование дерева: случай 2.1 а) (Изменение структурных связей.) t Если D = 'L', то установить Z ч— RPTR (Y), RPTR (Y) <- ч- LPTR (Z), LPTR (Z) Y, LPTR (X) ч- RPTR (Z), RPTR (Z)^- ч- X; в противном случае установить Z ч— LPTR (Y), LPTR (Y) <- <- RPTR (Z), RPTR (Z) +- Y, RPTR (X) LPTR (Z) и LPTR (Z) <- X. Установить F +- PATH [MARK — 1.1- Если X = LPTR (F), то установить LPTR (F) *— Z; в против- ном случае установить RPTR (F) ч— Z. б) (Изменение показателей сбалансированности.) Если BI (Z) ~ D, то установить BI (Y) ч— 'В', BI (Z) 'В'; если D ~ ’L’, то установить BI (X) ч- 'R'; в противном случае установить BI (X) 'L' и завершить выполнение алгоритма; в противном случае установить BI (X) <— 'В', Bl (Z) ч— 'В', BI (Y) <-D и завершить выполнение алгоритма. Данный алгоритм, хотя и длинный, но простой. Некоторые по- следовательности действий, особенно в шаге 3, повторяются. Они могут быть объединены в небольшие модули, вызываемые как под- программы. Шаги 1—4 почти копируют алгоритм TABLE из п. 5-2.2. В шаге 3 к существующему дереву добавляется новая вер- шина, если ее там еще нет, а в векторы PATH и DIRECTION соот- ветственно записываются адреса вершин на пути от заголовка списка до вставляемого листа и направление пути для каждой вершины. В шаге 5 алгоритм ищет несбалансированную вершину, ближайшую к вновь вставленной. В шаге 6 корректируются пока- затели сбалансированности для вершин, лежащих между найден- ной на предыдущем шаге несбалансированной вершиной и новой вершиной. В шаге 7 определяется, существует ли критическая вер- шина. Если существует, выполнение алгоритма продолжается в шаге 8 (случай 1) или шаге 9 (случай 2). Если критическая вер- шина не найдена, корректируется полученный в шаге 5 показатель сбалансированности несбалансированной вершины. Последние два шага алгоритма соответствуют обсуждаемым ранее случаям 1 и 2, и для каждого случая заново выполняется балансирование 561
Таблица 6-2.2 Боровидная структура для списка слов дерева. Читателю предлагается проследить выполнение алго- ритма на примерах, приведенных на рис. 6-2.8 и 6-2.10. Рассмотрим эффективность данного алгоритма. Можно пока- зать, что в сбалансированном дереве с п вершинами максимальная длина пути m равна 1,5 log2 (п + 1) (см. работу [25]). Включение элемента, сопровождающееся балансированием любого вида, имеет длину поиска ие более О (log2 и). До сих пор мы рассматривали только бинарные деревья. Дан- ный пункт заканчивается кратким введением в m-арные деревья. Подобные древовидные структуры часто используются в задачах организации и поиска информации. Описываемый далее метод поиска аналогичен рассмотренному ранее методу цифровой сорти- ровки. Полное m-арное дерево, в котором каждая вершина состоит из m компонентов, называется структурой бора. * (боровидная структура). Обычно ее компонентами являются цифры илн буквы. Табл. 6-2.2 представляет собой пример боровидной структуры, используемой для поиска набора записей, состоящих из 29 анг- лийских слов. Эта структура состоит из 12 вершин, каждая из ко- торых представляет собой вектор из 27 элементов. Каждый эле- мент содержит либо прочерк, либо требуемое слово или номер вершины. Символ пробела ()£ ) используется при сканировании 562 1 В оригинале слово trie, которое мы переводим как «бор» (см. работу [15]) — Прим. пер. Гис. 6-2.11. Представление бора из табл. 6-2.2 в виде леса 563
слова для обозначения его конца. Вершина I является корнем дерева. Для примера проследим поиск слова END. Буква Е говорит о том, что от вершины 1 следует перейти к вершине 5. Затем для выбора соответствующего элемента в вершине 5 используется вто- рая буква (N). Соответствующий метке N вход указывает на вер- шину 11. В этой вершине для окончательного нахождения требуе- мого слова используется буква D. Для этого метода поиска легко сформулировать алгоритм, что предлагается сделать в качестве упражнения к данному параграфу. В приведенном в табл. 6-2.2 боре очень расточительно исполь- зуется память. Требуемую память можно сократить за счет уве- личения времени выполнения алгоритма, представляя каждую вершину в виде связанного списка. Такое представление (лес) показано на рис. 6-2.11 для бора из табл. 6-2.2. Лучшей, с точки зрения затрат времени на выполнение, яв- ляется ситуация, при которой лишь несколько уровней бора ис- пользуются для поиска первых букв ключа, а для поиска остав- шихся букв используется какая-нибудь другая структура типа линейного списка или бинарного дерева. 6-2.4. Методы хеширования таблиц У лучших из предложенных до сих пор методов поиска длина поиска была пропорциональна log2 п. В этом пункте будет исследована группа методов, у которых длина поиска может быть независима от числа записей в таблице. Для достижения этой цели при поиске должен применяться совершенно новый подход. Он состоит в том, чтобы положение конкретной записи в таблице опре- делять по значению ключа этой записи. Основные положения этого подхода были кратко изложены в п. 4-3.2 и в дальнейшем исполь- зованы в п. 5-5.4, где отношение между значением ключа и поло- жением соответствующей ему записи в таблице определялось с по- мощью функции хеширования. К сожалению, несколько разных ключей могут отображаться в один и тот же адрес, или местополо- жение в таблице, поэтому требуются методы устранения коллизий. Продолжим теперь обсуждение функций хеширования. Эти функции можно разделить на две группы, а именно, не зависящие и зависящие от распределения значений ключей. Не зависящие от распределения функции хеширования при вычислении положения записи не используют информацию о распределении значений клю- чей. Напротив, зависящие от распределения функции хеширования получаются в результате анализа подмножества ключей, соответ- ствующих известным записям. Во второй части этого пункта описываются некоторые методы устранения коллизий, применяемые при работе с функциями хе- ширования. 564
Опишем сначала терминологию, которая используется в этом пункте. Таблица называется таблицей с прямым доступом, если для определения местоположения каждой записи используется ее ключ. Функция хеширования определяется как отображение Н: К -» А, где К — пространство, или множество ключей, которые могут идентифицировать записи в таблице с прямым доступом, а А — адресное пространство {с + 1, с 4- 2, .... с 4- т|. Для того чтобы упростить обсуждение и некоторые формулы, пред- положим, что адресное пространство имеет вид {1, 2, ..., ш}. Отметим, что если адреса в этом пространстве определяются функ- цией Н (х), то для перехода к приведенному выше адресному про- странству может быть применена функция Н (х) 4~ с. Мерой ис- пользования памяти в таблицах с прямым доступом служит коэф- фициент заполнения, определяемый как отношение числа записей к числу мест в таблице. В таком случае коэффициент заполнения <% = п/m *. Прежде чем описывать отдельные функции хеширова- ния, исследуем более подробно пространство ключей К. Каждый элемент пространства К является цифровым, алфавит- ным или алфавитно-цифровым идентификатором. Очевидно, что личные номера студентов, такие как 692784, 712116 и 730786, яв- ляются цифровыми ключами. Алфавитными ключами могут быть имена, например Дой, Джонс и Смит. Иногда номерные знаки авто- мобилей могут содержать три буквы, за которыми следуют три цифры, например, SAM 097, VIC 222 или RED 023. Их можно ис- пользовать как алфавитно-цифровые ключи в таблице, содержа- щей записи об автомобилях. Описываемые далее функции хеширо- вания для получения адресов выполняют над ключами арифмети- ческие или логические операции. Если доступно внутреннее пред- ставление алфавитных нли алфавитно-цифровых ключей, эти функ- ции можно также применять и к ним. (Алфавитные и другие спе- циальные символы во внутреннем представлении в компьютере ко- дируются цифрами). Альтернативным вариантом является коди- рование букв А, В, ..., Z десятичными числами 11, 12, ..., 36. Например, ключи SMITH и SAM 097 будут иметь коды 2923193018 и 291123097 соответственно. Такое кодирование сохраняет уни- кальность алфавитных ключей. Всегда имеется возможность пре- образовать ключи в целые числа, поэтому предполагается, что пространство ключей состоит из целых величин. Во многих случаях цифровое представление ключей получается слишком длинным для его размещения в одном машинном слове; и тогда, если только не используются операции, над числами с много- кратной точностью, ключи должны быть сжаты. В методах сжатия ключей используются некоторые функции хеширования, описы- ваемые ниже. Часто используется сначала одна функция хеширо- вания для отображения ключей в некоторое промежуточное иро- Предполагается, что число записей равно п. — Прим. ред. 565
странство значений, а затем другая функция — для отображения значений из этого пространства в требуемое адресное пространство. Вероятно, наиболее широко распространенная функция хеши- рования основывается на методе деления и определяется в виде Н (х) — х mod m 1, где m — делитель. Эта функция хеширования — одна из первых и наиболее широко используемых. При отображении ключей в адреса методом деления до некото- рой степени сохраняется существующая во множестве ключей равномерность распределения. Ключи с близкими значениями ото- бражаются при этом в уникальные адреса. Например, при делителе равном 101, такая функция отобразила бы ключи 2000, 2001, ... ..., 2017 в адреса 82, 83, ..., 99. К сожалению, если два скопления ключей или более отображаются в одни и те же адреса, то сохране- ние равномерности будет недостатком. Например, если имеются также ключи 3310, 3311, 3313, 3314, ..., 3323, 3324, то при дели- теле 101 они будут отображены в адреса 79, 80, 82, 83, ..., 92, 93, и с группой ключей, значения которых начинаются с 2000, про- изойдет много коллизий. Причина этого состоит в том, что ключи из этих двух групп совпадают по модулю 101. Вообще, если по модулю d совпадает много ключей, a m и d не являются взаимно простыми числами, то использование зна- чения m в качестве делителя может привести к низкой эффек- тивности хеширования, основанного на методе деления. Это по- казано в предыдущем примере, в котором m = d = 101. Возьмем другой пример: если все ключи в совокупности записей совпадают по модулю 5 и делителем является число 65, то значения ключей отображаются лишь в 13 различных позициях. Если m является большим простым числом, то обычно ключи ие совпадают по мо- дулю т, и поэтому в качестве делителя следует выбирать простое число. Исследования, однако, показывают, что удовлетворитель- ные результаты получаются и при нечетном делителе, не имеющем миожителей менее 20. В особенности следует избегать четных де- лителей, так как при этом четные и нечетные ключи отображаются соответственно в нечетные и четные адреса (в предположении, что адресное пространство имеет вид {1, 2, ..., т}). При этом возни- кали бы трудности в организации таблиц, содержащих в основном четные или в основном нечетные ключи. При хешировании по методу середины квадрата ключ умно- жается сам на себя, а адрес получается отсечением битов или цифр от обоих концов произведения, которое выполняется до тех пор, пока число оставшихся битов или цифр не станет равным требуемой длине адреса. Во всех получаемых произведениях при этом должны использоваться одни и те же позиции. В качестве примера рассмо- трим шестизначный ключ 113586 При возведении его в квадрат получается 12901779396. Если требуется четырехзначный адрес, могут быть выбраны позиции 5—8, дающие адрес 1779. Метод сере- 566
дины квадрата подвергался критике, но его применение к некото- рым наборам ключей дает хорошие результаты. В методе свертывания ключ разбивается на части, каждая из которых имеет длину, равную длине требуемого адреса (кроме, возможно, последней части). Чтобы сформировать адрес, части затем складываются, при этом игнорируется перенос в старшем разряде. Если ключи представлены в двоичном виде, го вместо сложения может быть использована операция исключающего ИЛИ. Существуют различные вариации этого метода, которые лучше всего проиллюстрировать на конкретном примере ключа 187249653. В методе свертывания со сдвигом складываются числа 187, 249 и 653, при этом получается адрес 89. В методе граничного свертыва- ния инверсируются цифры в крайних частях ключа, и таким обра- зом в нашем примере складываются числа 781, 249 и 356, что дает адрес 386. Свертывание является функцией хеширования, удобной для сжатия многословных ключей и последующего перехода к дру- гим функциям хеширования. Преобразование системы счисления является методом хеширо- вания, в котором делается попытка получить случайное распреде- ление ключей по.адресам в адресном пространстве. Ключ, пред- ставленный в системе счисления q (q обычно равно 2 или 10), рас- сматривается как число в системе счисления р, где р больше q, причем р и q — взаимно простые. Это число из системы счисления с основанием р переводится в систему счисления с основанием q, и адрес формируется путем выбора правых цифр, или битов, но- вого числа или применением метода деления. Например, ключ 5304761О, рассматриваемый как 530476ц, переводится в десятичную систему счисления с помощью следующих вычислений: 530476п = 5 X II5 4- 3 > 114 + 4 X 112 4- 7 z. 11 4- 6 = 8497451О. Отсечение трех левых цифр в полученном числе дает адрес 745 в адресном пространстве {0, 1, ..., 999}. Функция хеширования, основанная на алгебраическом кодиро- вании, разделяет скопления ключей. В этом методе используется алгебраическая теория кодирования. Состоящий из г битов ключ (kx, k2, ..., kr)2 рассматривается как многочлен К (х) = у к,х'-1. i=l Если требуется сформировать адрес в интервале от 0 до m = = 2* — 1, то многочлен К (х) делится на другой многочлен: t Р (х) = х* 4 - PjX'-1. i=l Получающийся при делении по модулю 2 остаток t К (х) mod (Р (х)) == X- hjX*-1 i=i дает адрес (hjhg ... ht)8. 567
Кнут 115 I установил, что при г = 15 и t = 10 многочлен-дели- тель Р (х) = х10 + х8 4- х5 Ч~ х4 4- х2 4- х 4- 1 приводит к функции хеширования Н такой, что если представлен- ные в двоичном виде ключи уг и уа различаются по крайней мере в шести битах, то значения Н (ух) и Н (уа) не равны. Первона- чально метод алгебраического кодирования предназначался глав- ным образом для аппаратной, а не программной реализации хеш- функции. Киотт [14] и Кнут [15] утверждают, что весьма удобной яв- ляется мультипликативная функция хеширования. Для неотри- цательного целого ключа х и константы с такой, что 0 < с < 1, эта функция определяется в виде Н (х) = [_ш (ex mod 1)_| 4- 1. Здесь выражение ex mod 1 обозначает дробную часть величины сх, а скобки |__| — наибольшее целое, не превышающее значения заключенной между ними величины- Такая мультипликативная функция дает хорошие результаты при правильном выборе кон- станты с, что трудно сделать. Все рассмотренные до сих пор функции хеширования не зави- сят от распределения значений ключей. Обратимся теперь к завися- щим от такого распределения функциям хеширования. Традиционные функции хеширования выбирают так, чтобы осу- ществить равномерное распределение значений ключей в адресном пространстве, но эти функции ие учитывают действительного рас- пределения значений ключей в их собственном пространстве. Такая независимость функций от распределения часто требуется в тех случаях, когда множество ключей подвергается частым вставкам и удалениям. Зависящие от распределения значений ключей функ- ции совершенно отличны от функций хеширования, которые ши- роко использовались в прошлом. Киотт [13, 14] и Дьючер [5, 6] в своих работах обсуждали такие зависящие от распределения функции и определяли их следующим образом. В пространстве ключей К дано его подмножество S; требуется найти функцию хеширования Н, равномерно отображающую эле- менты S в адресное пространство. Это означает, что ключи должны равномерно отображаться в адреса 1, 2, ..., ш. Для получения требуемой функции может быть использована дискретная кумуля- тивная функция распределения Fz(x) = Р (Z х) случайной величины Z, значения которой принадлежат S. Предположим, что S содержит п ключей. Тогда, если в S не содержится одинаковых элементов, случайная переменная Fz (Z) такова, что p(fz(z)<4)=a 568
для 0 < к sgn. Отсюда следует, что Fz (Z) имеет равномерное ди- скретное распределение на множестве значений и тогда mFz(Z) имеет равномерное дискретное распределение на Поэтому распределение величииы| niFz (Z)-[, где скобки} (обо- значают наименьшее целое число, большее или равное заключен- ной в них величине, приблизительно равномерно на множестве {1, 2, ..., ш} (особенно при m «с п). Таким образом, для заданного ключа х зависящая от распреде- ления функция хеширования Н, определяется выражением н (х) = mFz (х)“|. Однако в большинстве случаев функция распределения Fz неизвестна и должна быть аппроксимирована. Все зависящие от распределения функции хеширования различаются лишь исполь- зуемым для оценки Fz подходом. Любая из этих функций может быть определена только после анализа одного или нескольких под- множеств ключей, соответствующих известным записям. Вследст- вие частых включений в таблицу и исключений из нее такое под- множество может коренным образом измениться. Это приводит к необходимости периодического переопределения функции хеши- рования и реорганизации таблицы с прямым доступом. Хеш-преобразование, известное под названием поразрядного анализа, в определенной степени является зависящим от распреде- ления. Адреса формируются путем выбора н сдвига цифр или би- тов исходного ключа. Например, ключ 1234567 может быть преоб- разован в адрес 6543 выбором цифр из разрядов 3—6 и изменением их порядка на обратный. Для заданного множества ключей должны быть использованы одни и те же разряды ключей и один и тот же способ их реорганизации. Для того чтобы определить, какие раз- ряды ключа следует использовать для формирования адреса, про- водится анализ какой-нибудь выборки из множества ключей. Рас- смотрим для примера поразрядный анализ, приведенный в табл. 6-2.3. Чтобы определить, какие разряды ключа следует ис- пользовать для формирования элементов адресного пространства {О, 1, ..., 9999}, анализу были подвергнуты 5000 десятиразрядных ключей. Наиболее равномерное распределение цифр получилось для разрядов 1, 5, 6, 8, поэтому их и выбирают для формирования адреса. Другой зависящей от распределения функцией хеширования, которую можно использовать для аппроксимации Fz, является кусочно-линейная функция. Пространство ключей, состоящее из целых величин в замкнутом интервале la, d ], делится иа j равных 569
Та б л'и ц а 6-2.3 Поразрядный анализ множества десятиразрядных ключей Цифра разряд ключа 1 2 3 4 5 6 7 8 9 10 0 531 594 1565 5000 499 590 2540 562 1133 721 1 582 568 874 0 536 467 1581 612 759 905 2 571 620 657 0 531 563 557 542 606 1553 3 546 565 555 0 511 512 332 522 482 277 4 518 529 284 0 495 461 0 546 521 0 5 503 503 276 0 500 463 0 472 469 673 6 488 456 263 0 469 510 0 426 296 629 7 449 411 212 0 500 459 0 425 365 0 8 422 431 159 0 470 457 0 455 310 501 9 390 323 155 0 489 518 0 438 59 741 подынтервалов длины L, таких что L ~ (d —- a)/j. Используя сле- дующую формулу, можно определить для данного ключа х, в ка- ком из интервалов он расположен: j = 1 + L(x — a)/L_|, где скобки[_и__|обозначают наибольшее целое число, не превышаю- щее значения заключенной в них величины. Используя это равен- ство для подмножества S, содержащего п значений ключей, можно определить некоторые числа N, и Gs для каждого из следующих интервалов: _((a,a + L), i = 1; 1 ~ l[a + (i — 1) L, а + iL), 2 < i < j. N| определяется как число ключей из S, содержащихся в ин- тервале I5, a Gj — это число ключей, значения которых меньше а Ч~ iL. Таким образом, N. и Gf являются соответственно частотой и кумулятивной частотой значений ключей для интервала Ij. Используя Nj и Gj, мы можем определить линейную аппроксима- цию кумулятивной функции распределения Fz для х в интервале I. Pj (х) = (Gj + ((х — a)/L — i) Nj)/n. Таким образом, требуемой функцией хеширования для ключа х в интервале Ij является функция Hi (х) = ГтР, (х)П, 1 i j- Вычисление кусочно-линейной функции для непрямой адресации обеспечивается следующим алгоритмом. Алгоритм PIECE___WISE. Даны величины j, a, d, гп и п, смысл которых определен ранее, и множество ключей |xj, х2, ..., хп}. Для оценки кусочио-линейиой функции требуется вычислить длину интервала L, частоты Nf и кумулятивные частоты Gj, 1 i j. 1. {Обнуление массива NJ. Повторять при 1=1,2, ..., j: установить N( ч— О, 570
2. [Определение длины интервала и частот значений ключей в интервале. 1 Установить L (d — a)/j. Повторять при к — ==1,2, п: установить i 1 -|- |_(хк — а)/Е__|иЦ«- N5 Ч- 1. 3. [Вычисление кумулятивных частот. ] Установить Gt Nt. Повторять при i = 2, 3, j: установить Gf <- GM -|- Nj. Выход. Алгоритм PIECE _WISE очень прост и для определения эле- ментов N и G требует только одного просмотра значений ключей в множестве ключей. Адрес ключа х из интервала la, d ] вычис- ляется по параметрам, определяемым алгоритмом с помощью следующих операций: Установить i <- 1 +|__(х — 1 и Н (х) f~m (Gj + ((х — — a)/L — i) Nj)/n~|. Здесь Н (х) представляет значение функции хеширования. В качестве примера использования кусочно-линейного метода рассмотрим случай, когда а = 0, d = 200, j =10, п = НО, L = 20, причем векторы N и G даны в табл. 6-2.4. Для значения ключа х = 105 при m = 100 получится i = [_(105 — 0)/20_] = 6 и Н (х) = ГЮ0 (53 -I- ((105 — 0)/20 — 6) 13)/110~] = 40. Другой метод, предназначенный для определения частотного распределения множества ключей, также базируется на кусочно- линейной оценке функции хеширования. Начиная с произвольного числа равных по длине интервалов, разделяющих пространство ключей, моделируется хранение подмножества ключей S. Те интер- валы, для которых средняя длина поиска ALOS больше некоторого предопределенного значения, разделяются таким образом, чтобы для новых интервалов можно было получить улучшенную оцеик}? частотного распределения значений ключей. Для того чтобы подын- тервалы также могли быть расщеплены, процесс деления интерва- лов выполняется итеративно. Полученная с помощью такого ме- тода функция хеширования называется кусочно-линейной функцией с расщеплением интервалов. Она требует довольно сложной струк- туры данных, которая может быть представлена с помощью мас- сива. Пример такого представления приведен иа рнс. 6-2.12. Как показано на рис. 6-2.12, диапазон значений ключей (a, d) первоначально был разбит на j = 10 интервалов длины L = = (d— а)/10. Для каждого из этих интервалов указана частота Если i-й интервал не был расщеплен, G{ имеет неотрицательное значение, равное кумулятивной частоте значений ключей для этого интервала. Если же Gj отрицательна, то абсолютное значение Gj является индексом первой из двух последовательных пар элемен- тов N 1 ц. । и G । ц. ], N j Gj । ч-i и G j Gj i -f-1- Эти элементы соответствуют полуинтервалам интервала i, причем длина полуинтервала равна (d — а)/20; N1Gil и Njc^-i-i являются частотами для первого и 571
Длина интервала ' L= (d-a )//<; Длина полуинтервала L/2^(A-&)IZ0 Длина четвергпьинтербала Lrt= (d-a)/4c Драна одной восьмой интервала L/8=(d-&)/80 JO Л 12 13 74 15 16 17 .18 19 20 21 22 24 0 ID 11 2 -11 -13 19 20 20 —15 33 —17 7Z7 19 17 -19 30 90 —21 -23 29 4Z 44 24 29 Рис. 6-2.12. Представление структуры данных для кусочно-линейной функ- ции хеширования с разделением интер- валов Т а б л и ц а 6-2.4 Значения векторов N и G 5 8 15 3 9 13 20 16 12 9 5 13 28 31 40 53 73 89 101 110 z 2 N G второго полуинтервалов интервала i. Пусть к представляет вели- чину | Gj | или | Gj 1 4-1; если значение Gk неотрицательно, то оно является кумулятивной частотой для соответствующего полу- интервала. В противном случае | Gk | представляет собой индекс первой из двух последовательных пар элементов массива, которые содержат информацию для двух четверть интервалов соответствую- щего полуинтервала. Этот процесс можно продолжать до получе- ния восьмой части интервала и т. д. Рассмотрим интервал 8 с величинами N8 — 10 и G8 — —15. Этот интервал затем разделяется на два подынтервала, и | G81 = = 15 указывает иа величины Nlg, GIS, Nie и Gle, содержащие ин- формацию для полуинтервалов интервала 8. Тот факт, что Nie = 1 и G16 = 30, означает нерасщепленность второго полуинтервала. В первом полуинтервале N15 = 9 и G15 — —19, что указывает иа его последующее расщепление, при этом NM, G19, N20 и G20 соот- ветствуют его четвертьинтервалам. Первый четвертьинтервал тоже расщеплен, что видно из значения G19 — —23, а второй, не содер- жащий ключей четвертьинтервал, не разделен. Величины N23, G2S, N24 и G24 представляют собой частоты и кумулятивные частоты для восьмой части интервала первого четвертьинтервала. В этой точке процесс расщепления интервалов прекращается, хотя можно разделить и восьмую часть интервала. Следующий алгоритм вычисляет адрес, используя описанный тип структуры данных для кусочно-линейиой функции с расщеп- лением интервала. Предполагается, что интервалы не должны быть расщеплены более чем до (1/2)р~~1 от их первоначального размера. 572
Таким образом, при р = 1 алгоритм просто вычисляет кусочно- линейиую функцию хеширования. Алгоритм ISAC. Даны величины L, a, m, п и р, определенные выше, и массивы N н G, содержимое которых в качестве примера представлено на рис. 6-2-12. Для ключа х требуется вычислить адрес Н в пространстве {1, 2, ..., tn}. 1 [Вычисление начального номера интервала. ] Установить +L(x-a)/L_|. 2. [Цикл. ] Повторять шаги 3 и 4 при к — 1, 2, ..., р. 3. [Разделен ли интервал или полуинтервал?] Если Gj 5= О, перейти к шагу 5. 4. [Вычисление номера интервала и индекса массива. ] Устано- вить г ч- 1 + |__(х — a)/(L/2ft)_J и i ч-Gf — (г mod 2) + 1. 5. [Вычисление адреса. ] Установить Н ч-f” m (Gj + ((х — — a)/(L/2k-1) — г) NJ/n-1 и завершить выполнение алгоритма. Заметим, что г, вычисляемое в шаге 4, таково, что 1 «с г 2к., где j равно первоначальному числу подынтервалов. Величина г используется для вычисления адреса в шаге 5, а также для моди- фикации величины —Gj в шаге 4. При нечетном г величина —Gj ие изменяется, но если г четное, то —G, увеличивается на единицу. Это вычисление дает индекс i элементов массива, соответствующий требуемому полуинтервалу. Так как интервалы не расщепляются более чем до (1/2)р-1 от их первоначального размера, шаг 4 никогда не выполняется, если к = р. Алгоритм для построения массивов N и G имеет большое время выполнения. При его реали- зации требуется 2р — 1 раз осуществить просмотр множества ключей. Киотт [14], Лам и др. [19], Лондон [17], Кнут [15], Бух- хольц [4], Дьючер [5, 6] проводили исследования описанных здесь функций хеширования. Хотя некоторые из этих методов часто дают равномерное распределение ключей по адресам, в при- менении к конкретным множествам ключей все же необходимо подбирать функцию хеширования. Для сравнения различных функ- ций хеширования необходима некоторая мера их эффективнос- ти. Наиболее широко успользуемой мерой эффективности является средняя длина поиска ALOS. Для множества записей, расположенных в файле с прямым доступом, такой мерой является требуемое для поиска записи среднее число обращений к запомина- ющему устройству. Обычно лучшая для конкретного множества ключей функция хеширования минимизирует величину ALOS. Отметим, что для минимизации ALOS кроме функции хеширования следует учитывать и другие факторы, которые обсуждаются далее в данном пункте. Функция хеширования часто отображает несколько ключей в один и тот же адрес. В этом случае записи переполнения должны запоминаться в других участках памяти, которые определяются с помощью методов устранения коллизий. Существуют две основ- ные группы таких методов, а именно, открытая адресация и метод 573
цепочек. Ниже представлены алгоритмы из обеих групп. Упоми- наются также некоторые модификации основных методов. При открытой адресации, если ключ х отображается в таблич- ную позицию d, а она уже занята, в таблице просматриваются другие позиции, и эта операция продолжается до тех пор, пока не будет найдено свободное место для размещения записи, приведшей к коллизии. Может случиться, что свободное место содержит ра- нее удаленную запись. Если из таблицы удаляется запись с клю- чом Ki, то ключу К{ присваивается специальная неотрицательная величина MAR К, которая не равна значению ни одного из ключей. Последовательность, в которой просматриваются свободные места, может быть определена различными путями. Простейшим способом обработки коллизий является использование следующей последо- вательност и.- d, d 1, ..., m — 1, m, 1, 2, d — 1. Если для новой записи существует по крайней мере одно сво- бодное место, оно будет найдено; в противном случае после про- смотра позиций в таблице поиск прекратится. При поиске записи просматривается та же последовательность позиций, продолжаю- щаяся до тех пор, пока не будет обнаружена нужная запись или не встретится пустая неиспользованная позиция. В последнем случае в таблице отсутствует требуемая запись, и поиск неудачен. Такой способ устранения коллизий называется линейным опробо- ванием. Следующий алгоритм включает в таблицу запись, используя способ линейного опробования с последовательностью d, d + 1, .. ..., m — 1, m, 1, 2, ..., d — 1. Содержащая m позиций таблица представляется в виде описания таблицы, приведенного в начале этой главы. Предполагается, что если элемент Rf ни разу не содер- жал записи, то поле ключа К, имеет отрицательное значение. Алгоритм OPENLP. Дана запись REC, идентифицируемая ключом х; требуется вставить эту запись в таблицу, представлен- ную структурой R. Для вычисления начального адреса исполь- зуется функция хеширования Н. 1. [Вычисление адреса.] Установить i ч-d «-Н'(х). 2. [Поиск свободной позиции. ] Если К[ < 0 или Ki — МАРК, то установить К, «- х и завершить выполнение алгоритма. 3. [Увеличение и проверка индекса.] Установить i ч— i + 1. Если i > m, то установить i ч— 1. Если i = d, то печатать ’ПЕРЕ- ПОЛНЕНИЕ’ и завершить выполнение алгоритма: в противном случае перейти к шагу 2. Алгоритм OPENLP выполняется очень просто. В шаге 1 вычи- сляется начальный адрес. ,В шаге 2 рассматривается очередная позиция таблицы, и, если ранее она была пустой или содержала признак удаленного ключа, в ней запоминается запись, а алгоритм заканчивается успешно. В противном случае для увеличения ин- декса i или, если необходимо, установки его в состояние 1 выпол- 574
няется шаг 3. Если индекс i становится равным своему начальному значению d, то это означает, что для записи пет свободного места, и алгоритм заканчивается неудачей. Сходный алгоритм используется и для поиска записи. Его можно получить из алгоритма OPFNLP, заменив шаг 2 на следу- ющий: 2. [Поиск записи с ключом х. ] Если х = Кь то установить REC <- R, и завершить выполнение алгоритма; в противном слу- чае, если Kj < 0, то печатать ’ЗАПИСЬ ОТСУТСТВУЕТ’ и завер- шить выполнение алгоритма. Для примера предположим следующее: имя NODE отображается в позицию 1; имя STORAGE отображается в позицию 2; имена AN и ADD отображаются в позицию 3; имена FUNCTION, В, BRAND и PARAMETER отображаются в позицию 9. Предположим также, что вставки записей выполняются в сле- дующем порядке: NODE, STORAGE, AN, ADD, FUNCTION, В, BRAND и PARAMETER На рис. 6-2.13 представлена результирующая структура табли- цей записей при m = 11. Первые три записи размещаются в таб- лице с первой же попытки, но запись ADD должна быть помещена в позицию 4 вместо уже занятой 3-й позиции; FUNCTION с первой попытки помещается в позицию 9, но для В и BRAND требуются соответственно две и три попытки. Наконец, размещение записи PARAMETER заканчивается в позиции 5 после восьми попыток, так как позиции 9, 10, 11, 1, 2, 3 и 4 к этому времени уже заняты. Поиск заканчивается успешно, если ключ х найден, и неудачно, если встречается пустая позиция. Поскольку при этом шаги 1 и 3 такие же, как и при включении записей, то те же самые соображе- ния справедливы и для поиска. Каждый раз при выполнении шага 2 алгоритма OPENLP, как для вставки записи, так и для поиска требуется одно сравнение. Если все записи запоминаются или ищутся в таблице, содержащей и записей, средняя длина поиска ALOS равна среднему числу вы- полнений шага 2. Для анализа методов устранения коллизий Кнут предложил вероятностную модель и получил формулы для средней длины ус- пешного поиска ALOS в случае открытой адресации. При этом предполагалось, что каждый ключ отображается в каждый из m адресов таблицы с вероятностью 17 m. Таким образом, существует mn различных вариантов отображения значений ключей в адресное пространство. Средняя длина поиска ALOS зависит от коэффициента заполнения таблицы. Для коэффициента заполнения ос = т/п, 575
Рис. 6-2.13- Устранение коллизий с помощью открытой адресации Таблица 6-2.5 Средняя длина поиска для линейного опробования Коэффициент заполнения а Число проб при успехе при неудаче 0.10 1.056 1.118 0.20 1.125 1.281 0.30 1.214 1.520 0.40 1.333 1.889 0.50 1.500 2.500 0.60 1.750 3.625 0.70 2.167 6.060 0.80 3.000 13.000 0.90 5.500 50.500 0.95 10.500 200.500 где щ н п определены ранее, Кнут выводит следующие фор- мулы: Ч- Г1 + “гЧ—) ПРИ Удачном поиске; ALOS^ 1 / Г \ -Q- ( 1 4 Ti-\й“) ПРИ неудачном поиске. 2 \ (1 — СС) / В табл. 6-2.5 приведены соответствующие этим формулам значе- ния средней длины поиска при различных коэффициентах заполне- ния х. Значение ALOS возрастает с ростом коэффициента заполне- ния, так как чем больше записей находится в таблице, тем более вероятны коллизии. Заметим, что в сравнении с ранее рассмотрен- ными методами поиска при а < 0.80 результаты вполне удовле- творительные. Число проб пропорционально коэффициенту за- грузки. Результат этот, однако, основывается на предположении о равномерном отображении множества ключей в адресное про- странство. Устранение коллизий методом линейного опробования имеет много недостатков. В частности, при этом трудно выполнять удале- ние записей. Был использован подход, состоящий в организации специального элемента таблицы с признаком MARK, который означает удаление соответствующей записи. Такая стратегия по- зволяет правильно выполнять поиск в таблице. Предположим, например, что запись с ключом FUNCTION на рис. 6-2.13 отме- чена как удаленная с помощью присвоения признака /MARK ключу К9- Если теперь требуется найти запись с ключом BRAND, то эту задачу может выполнить наш предыдущий алгоритм. У чи- тателя может возникнуть вопрос, зачем понадобилось использо- вать специальный признак MARK для обозначения удаленных 1 В таблице в качестве ALOS использовано среднее число проб. — Прим. ред. 576
записей? Почему бы в этом случае просто не присвоить ключу за- писи, которая должна быть удалена, отрицательное значение? Причина состоит в том, что если^бы это^было сделано в предыдущем примере, то алгоритм нашел бы в позиции 9 пустое место и сделал ошибочное заключение, что величины BRAND в таблице нет. Такое решение проблемы удаления является вполне удовлетво- рительным при небольшом числе удалений из таблицы. Однако, если удалений много, таблица в конечном счете будет содержать только записи, отмеченные как удаленные. В результате этого попытка вставки записи приведет к ситуации переполнения. Таким образом, в изменяющихся по составу таблицах записи, отмечен- ные как удаляемые, должны быть фактически исключены из таб- лицы. Можно разработать алгоритм, который будет выполнять удаления, перемещая, если надо, оставшиеся записи. Такой алго- ритм устраняет необходимость в.записях с признаком MARK. Другими словами, в этом случае позиция для записи может быть либо занятой, либо пустой. Одни из возможных подходов состоит в том, что сначала уда- ленная запись отмечается как пустая. Затем по порядку осущест- вляется поиск следующей пустой позиции. Если найдена некоторая запись (пусть это будет запись у), для которой значение функции хеширования ие лежит в интервале между позицией, только что отмеченной как удаленная, и очередной пустой позицией, то запись у может быть перемещена, с тем чтобы занять место удаленной записи. Затем ранее занятая записью у позиция отмечается как пустая, и весь процесс повторяется, начиная с новой позиции, занимаемой записью у. Точное формулирование данного алго- ритма оставляется в качестве упражнения к этому параграфу. Другой недостаток метода линейного опробования обусловлен эффектом окучивания, который значительно усиливается при почти заполненной таблице. Это явление можно объяснить, рассматривая последовательность изменений таблицы на рис. 6-2.13, которые отражают ее состояние после каждой вставки. Такая последова- тельность приведена на рис. 6-2.14. Очевидно, что для первой вставки записи вероятность того, что новый элемент попадет в определенную позицию, равна 1/11. Однако при второй вставке вероятность того, что будет занята позиция 2, в 2 раза больше, чем вероятность попадания в остальные позиции, а именно, запись будет помещена в позицию 2, если ключ отображается либо в пер- вую позицию, либо во вторую. Продолжая действовать тем же образом, получим, что при пятой вставке вероятность размещения новой записи в позиции 5 в 5 раз выше вероятности попадания в остальные незанятые места таблицы. Следовательно, имеет место тенденция возникновения все более длинных последовательностей занятых подряд позиций. Такое явление называется первичным скУчиванием. Проблема первичных скучиваний может быть частично решена, если использовать другой метод опробования, а именно, метод 19 Трамбле Ж., Соренсон П. 577
BBANoInOOS Рис. 6-2.14. Изменение состояние таблицы в процессе новых вставок при использова- нии метода линейного опробования случайного опробования \ При использовании этого метода гене- рируется случайная последовательность позиций в отличие от упо- рядоченной последовательности, справедливой для метода линей- ного опробования. Генерируемая случайная последовательность должна содержать все целые числа от 1 до m в точности по одному разу. Таблица считается заполненной, если встречается первое дублирование случайного числа. Примером генератора случайных чисел, создающего такую циклическую пермутацию чисел, является оператор У (У + с) mod m; здесь у в скобках является предыдущим числом последователь- ности, а с и ш — взаимно простые числа, т. е. их наибольший общий делитель равен 1. Например, предположим, что m = 11 и с = 7; тогда это выражение, начиная со значения 3, генерирует следующую последовательность чисел: 10, 6, 2, 9, 5, 1, 8, 4, 0, 7 и 3. Прибавление 1 к каждому элементу преобразует полученную по- следовательность к требуемому интервалу П, 111. Теперь можно сформулировать следующий алгоритм. 1 Алгоритм OPENRP. Дана запись REC, идентифицируемая ключом х; требуется вставить ее в таблицу, представленную структурой R. Для вычисления начального адреса используется функция хеширования Н. Величина MARK служит для той же цели, что и в алгоритме OPENRP. 1 Метод случайного опробования называют также методом рандомизации. — Прим. ред. 578
1. [Инициализация.] Установить d <— Н (х). 2. [Первая проба. ] Если Kd < О или Kd == MARK, то уста- новить Ка -е-х и завершить выполнение алгоритма. 3. [Продолжение поиска]. Установить у <- d — 1. 4. [Сканирование следующей записи. ] Установить у <- (у -А с) mod гл и j у + 1; Если j = d, то печатать 'ПЕРЕПОЛНЕНИЕ' и завершить выполнение алгоритма. 5. [Данная позиция таблицы занята? ] Если Kj < 0 или Kj = == MAR К, то установить Kj х и завершить выполнение алго- ритма; в противном случае перейти к шагу 4. Для метода случайного опробования проблема удаления за- писей становится более сложной, чем в случае линейного опробо- вания. Поэтому в случае часто меняющейся таблицы для устране- ния коллизий следует использовать другие методы. Хотя метод случайного опробования частично решает проблему первичного скучивания, группирование записей все же может происходить. Такая ситуация возникает в том случае, когда функ- ция хеширования дает одно и то же значение адреса двум разным ключам. При этом описанный выше метод случайного опробования для обоих ключей генерирует одинаковую последовательность, или путь. Такое явление называется вторичным скучиванием. Один из способов частичного исключения данной ситуации состоит в использовании для выбора параметра (например, параметра с в алгоритме OPENRP), который будет взят для случайного опро- бования, второй функции хеширования, ие зависящей от первой. Предположим, например, что первой функцией хеширования яв- ляется Н15 такая, что Нх (хг) = Hj (х2) = i, где хх Ф х2. Теперь, если мы имеем вторую функцию хеширования Н2 такую, что Н2 (xj) Н2 (х2) при хх ==£ х2, то в качестве значения параметра с в алгоритме OPENRP можно использовать значение Н2 (хх) или Н2 (х2). Если Нх и Н2 являются независимыми, две случайные по- следовательности адресов, сгенерированные с помощью такого ме- тода, будут различными. Следовательно, мы избавляемся от вто- ричного скучивания. Такая вариация метода открытой адресации называется двойным хешированием. Средняя длина поиска для метода двойного хеширования при независимых функциях Н, и Н2 дается следующими формулами: ALOS ---In (1 — а)Ппри£уДачном^ поиске; 1 t — а при неудачном поиске. В табл. 6-2.6 приведены значения средней длины поиска для - Метода двойного хеширования. Его характеристики, несомненно, лучше, чем те, что были получены для линейного опробования. Рассмотренные методы открытой адресации нецелесообразно Использовать для меняющихся таблиц из-за постоянно существую- 19* 579
Таблица 6-2.6 Средняя длина поиска для метода случайного опробования с двойным хешированием Коэффициент заполнения а Число проб при удаче при неудаче 0.10 1.054 1.111 0.20 1-116 1.250 0.30 1.189 1.249 0.40 1.277 1.667 0.50 1.386 2.000 0.60 1.527 2.500 0.70 - 1.720 3.333 0.80 2.012 5.000 0.90 2.558 10.000 0.95 3.153 20.000 Таблица 6-2.7 Средняя длина поиска для метода раздельного сцепления Число проб Коэффициент заполнения а при удаче при неудаче 0.10 1.050 1.005 0.20 1.100 1.019 0.30 1.150 1.041 0.40 1.200 1.070 0.50 1.250 1.107 0.60 1.300 1.149 0.70 1.350 1.197 0-80 1.400 1.249 0.90 1.450 1.307 0.95 1.475 1.337 щей возможности коллизий и трудности физического удаления за- писей из таблицы. Чтобы обойти эти трудности, обратимся теперь к методам связанного распределения. В п, 4-3.2 был рассмотрен метод формирования связанного словаря, или таблицы, при котором функция хеширования исполь- зовалась для отображения имен в классы эквивалентности. Два имени отображаются в один и тот же класс в том случае, если функ- ция хеширования отображает их в одно и то же число. Каждый класс эквивалентности хранится в виде отдельного связанного списка, или цепочки. Такой метод устранения коллизий называется раздельным сцеплением 1. На рис. 6-2.15 показан использованный ранее в данном пункте набор ключей при m = 11 и п = 8, пред- ставленный в виде раздельных цепочек. Предполагается, что ключи помещаются в таблицу в следующем порядке: NODE, STORAGE, AN, ADD, FUNCTION, В, BRAND и PARAMETER Отметим, что каждая вставка осуществляется в начало соот- ветствующего списка. Средняя длина поиска при раздельном сцеп- лении определяется выражением ALOS 1 4- при удачном поиске; cc-]-e_<z при неудачном поиске. Значения средней длины поиска для метода раздельного сцеп- ления приведены в табл. 6-2.7. Отметим, что коэффициент запол- нения желательно иметь по возможности небольшим. Этого можно Этот метод называется также методом раздельных цепочек. Прим. ред. 580
EOUIV Hi pj->| NODE 1/1 EQU IV 12) P-H STORAOE " V] eouiv (J) [~^~*|add ~[*tHan И ЕОШУ^Р] EQU1v(5j0 Eou.v^0 equiv/7|0 EQU1V($0 EOUIV^)p^-4 PARAMETER 1| BRAND | Ц-ч|~В [Д-Д FUNCTION j/| EDUIVp₽/0 6QUn?(/?)[7J Рис. 6-2.15. Устранение коллизий с помощью раздельных цепочек добиться, увеличивая значение т. Последнее, однако, приведет к тому, что многие списки останутся пустыми, и для их заголовков будет напрасно расходоваться память. Хотя при использовании метода устранения коллизий раздельным сцеплением для органи- зации связей требуется дополнительная память, эффективность и гибкость этого метода при работе с меняющимися таблицами де- лают его более предпочтительным, чем метод открытой адресации. Более того, приведенные формулы для средней длины поиска остаются справедливыми и при а > 1! Для того чтобы по возможности увеличить m и в то же время исключить появление слишком большого количества пустых заго- ловков списков, применяют различные методы организации свя- зей. Узлы связанных списков могут частично совпадать с заголов- ками списков. Такая организация позволяет при m записях резер- вировать память только для m связок, в то время как для запо- минания и записей с помощью раздельного сцепления требуется хранить m -f- п связок. Такое представление для набора ключей из нашего примера приведено на рис. 6-2.16. Для этого представле- ния средняя длина поиска не- сколько выше, чем при раздельном сцеплении, однако экономия па- мяти, достигаемая с помощью та- кого переменного представления, Делает метод привлекательным. Резюмируя, отметим, что метод линейного опробования приемлем 581
при условии, что число записей в таблице невелико. Метод бинарного поиска идеально подходит для статических таблиц. Если требуется обеспечить эффективность вычислений для широкого спектра операций при умеренно больших в, следует применять поиск на сбалансированном дереве. При использовании функций хеширования хорошие результаты достигаются в том случае, когда множество ключей может быть равномерно распределено в адрес- ном пространстве. Упражнения к п. 6-2 1. Сформулируйте алгоритмы вставки и удаления для упорядо- ченной таблицы. 2. Изобразите бинарное дерево, построенное в результате последователь- ности вставок для следующего набора ключей: 8, 17, 10, 15, 5, 2, 16, 19, 13, 1, 4, 11. 3. Используя дерево, полученное в упр. 2, и алгоритм TREE—DELETE, покажите в каждом шаге дерево, полученное для последовательности удалений, соответствующей следующим ключам: 15, 2, 16, 13, 19. 4. Используя алгоритм BALANCE—INSERT, изобразите для каждого шага дерево, создаваемое в результате последовательности вставок следующих ключей: 6, 7, 8, 12, 15, 17, 9, 10. 5. Сформулируйте алгоритм, который удаляет вершину из сбалансирован- ного дерева, сохраняя при этом его сбалансированность. (Подсказка: если вер- шина не является листом, найдите ее предшественника и определите, можно ли его исключить.) 6. Составьте основанный на представлении бора, приведенном в табл. 6-2.2, алгоритм для выполнения поиска по бору. 7. Что произойдет в алгоритме OPENLP, если шаг 3 заменить оператором i (j с) mod m-|- 1? Упростит ли это изменение проблему скучивания? 8. Проследите выполнение алгоритма OPENRP для той же последователь- ности имен, которая была использована в тексте. 9. Разработайте алгоритм удаления записи из таблицы, если для устране- ния коллизий используется метод линейного опробования. 10. Составьте алгоритм поиска, основанный иа двойном хешировании. 11. Рассмотрите проблемы, которые возникают при формировании алго- ритма удаления, если использован метод двойного хеширования. 12. Сформулируйте алгоритм для раздельного сцепления, в котором каж- дый класс эквивалентности должен быть представлен не односвязаниой цепоч- кой, а в виде древовидной структуры. 13. Сформулируйте алгоритм вставки, основанный на результатах обсуж- дений, проводимых в конце данного параграфа (см. рис. 6-2.16). Список литературы 1. Адельсон-Вельский Г. М., Ландис Е. М. Один алгоритм орга- низации информации. ДАН СССР Серия математическая, т. 146, 1962, № 2, с. 263—266. 2. Берзтисс А. Т. Структуры данных. Пер. с англ. М.: Статистика. 1974, 403 с. 3. Brooks F. Р., Iverson К. Е. Automatic Data Processing: System/360 Edition. New York: Wiley, 1969. 4. Buchhoiz. W. File Organization and Addressing. —IBM System Journal, vol. 2, June, 1963, pp. 86—-110. 682
5. Deutscher R. F., Tremblay J. P., Sorenson P. G. A Comparative Study of Distribution — Dependent and Distribution-Independent Hashing Functions.— Proceedings of the ACM Pacific 75, April 17 and 18, 1975, San Francisco, on. 172-178. F 6. Deutscher R. F., Sorenson P. G., Tremblay J. P. Distribution-Depen- dent Hashing Functions and Their Characteristics. —Proseedings of the Inter- national Conference on the Management of Data, A. C. MjSlGMOD, May 14—15, 1975, San Jose, pp. 224—236. 7. Elson M. Data Structures. Palo Alto; Science Research Associates, 1975. 8. Flores I. Computer Sorting. Englewood Cliffs; Prentice—Hall, 1969. 9. Floyd R. W. Algorithm 245, Treesort 3. — Communications of the ACM, vol. 7, No. 12, December 1964, p. 701. 10. Harrison M. C. Data Structures and Programming. Glenview Scott, Fo- resman, 1973. 11. HoareC. A. R. Algorithms 63 and 64. — Communications of the ACM, vol- 4, No. 7, July, 1961, p. 321. 12. Isaac E. J., Singleton R. C. Sorting by Address Calculation — Journal of the ACM, vol. 3, July, 1965, pp. 169—174. 13. Knott G. D. Expandable Open—Addressing Hash-Table Storage and Retrieval. Proceedings of the S1GF1DET Workshop on Data Description, Access, and Control — ACM, 1971, pp. 187—206. 14. Knott G. D. Hashing Functions. -The Computer Journal, v. 18, N. 3, Aug. 1975, pp. 265—278. 15. Кнут Д. E. Искусство программирования для ЭВМ. Т. 3: Сортировка и поиск. Пер. с англ. М.: Мир, 1978. 844 с. 16. Kronmal R. A., Tarter М. Е. Cumulative Polygon Address Calculation Sorting. — Proceedings of the 20th National Conference of the ACAI, 1965, pp. 376—385. 17. London K. R. Techniques for Direct Access. Philadel—phia; Auerbach Publishers, 1973. 18. Lum V. Y. General Performance Analysis of Key—to—Address Trans- formation Methods Using an Abstract File Concept. — Communications of the ACM, vol. 16, No 10, 1973, pp. 603—612. r. - 19. Lum V. Y., Yuen P. S. T., Dodd M. Key-to-Address Transform Techni- ques: A Fundamental Performance Study on Large Existing Formatted Files. — Communications of the АСЛ1, vol. 14, No. 4, 1971, pp. 228—239. 20. Maurer W. D. Programming San Francisco; Holden-Day, 1968. 21. Maurer W. D., Lewis T. G. Hash Table Methods.—ACM Computing Surveys, vol. 7, No. 1, March, 1975, pp. 5—19. 22. Morris R. Scatter Storage Techniques. —Communications of the ACM, vol II, No. 1, 1968, pp. 38—44. 23. Peterson W. W. Addressing for Random-Access Storage. — /ВЛ1 Jour- nal of Research and Development, vol. 1, April, 1957, pp. 130—146. 24. Price С. E. Table Lookup Techniques. — ACM Computing Surveys, vol. 3, No. 2, 1971, pp. 49—65. 25. Stone H. S. Introduction to Computer Organization and Data Structures. New York: McGraw-Hill, 1972. 26. Williams J. W. J. Algorithm 232, Heapsort. — Communications of the ACM, vol. 7, No. 6, June, 1964, pp. 347—348.
Глава?. ФАЙЛОВЫЕ СТРУКТУРЫ ДАННЫХ До сих пор в книге в основном обсуждались представления струк- тур данных и операции над ними. Описанные представления рассматривались только по отношению к данным, располагаемым в основной памяти. Но имеется по крайней мере две причины, по которым не вся обрабатываемая ЭВМ инфор- мация может размещаться в быстрой памяти. Во-первых, существуют настолько большие программы и данные для программ, что они не могут разместиться в ос- новной памяти, обычно являющейся дефицитным ресурсом в вычислительной системе. Во-вторых, часто бывает желательно или даже необходимо сохранять информацию от одного выполнения программы до другого (например, в системах оплаты счетов). Поэтому данные большого объема и архивные данные обычно хранятся на внешней памяти в виде некоторых специальных объектов, содержа- щих информацию и называемых файлами. В данной главе мы сконцентрируем внимание на структурах файлов (т. е. иа представлениях файлов в памяти) и операциях над файлами. Мы начнем с опи- сания устройств внешней памяти — среды, в которой обычно размещаются файлы. Затем введем определения и основные понятия, используемые позже при обсуж- дении ряда файловых организаций — последовательной, индексно-последова- тельной и прямой. Как альтернативный способ обработки больших объемов данных рассмотрена виртуальная память. В частности, описывается файловая организация VSAM, используемая в системах виртуальной памяти. Далее рас- сматриваются способы доступа к файлам по нескольким ключам. В конце главы приводятся концепции и основные функции, относящиеся к системам управле- ния базами данных трех типов. 7-1. УСТРОЙСТВА ВНЕШНЕЙ ПАМЯТИ Размещение информации в основной (вн}пгренней) па- мяти ЭВМ обсуждалось в п. 1-3. Поиск любого элемента данных в основной памяти осуществляется очень быстро, типичное время доступа менее чем 1 мкс = 10~6 с. Основная память обеспечивает текущие потребности центрального процессора при выполнении программ, включая программы пользователя, ассемблеры, компи- ляторы и супервизорные программы операционной системы. Емкость основной памяти ограничивается главным образом двумя факторами — стоимостью этой памяти и техническими про- блемами при разработке основной памяти большого объема. Фак- тически во всех вычислительных системах требования к емкости памяти для программ и данных, обрабатываемых программой, пре- вышают объем основной памяти. Следовательно, необходимо рас- ширять емкость памяти вычислительной машины, используя внеш- ние по отношению к основной памяти устройства. 584 а
Устройство внешней памяти нестрого можно определить как устройство, не являющееся основной' памятью, на котором можно разместить информацию или данные и откуда они могут быть из- влечены в последующие моменты времени. Операции размещения и извлечения обычно называются записью и чтением соответст- венно. Устройства внешней памяти имеют большой объем и более дешевы в пересчете на хранимый ими бит информации, нежели основная память. Однако время, требуемое для доступа к информа- ции, у этих устройств гораздо больше. Основными направлениями применения устройств внешней памяти являются: 1) временное хранение и повторная загрузка программ в процессе их выполнения; 2) сохранение программ и подпрограмм для будущего использования; 3) накопление инфор- мации в файлах. В данной главе мы остановимся лишь на третьем направлении, хотя некоторые результаты можно использовать и для первых двух направлений. Устройство ввода и перфорации карт можно рассматривать как примитивное устройство внешней памяти. Однако в этой главе мы будем иметь дело с устройствами, обеспечивающими более быструю передачу данных и более удобную среду для хранения информа- ции, нежели колода перфокарт Мы обсудим наиболее распростра- ненные устройства внешней памяти в том порядке, в котором они начали разрабатываться и использоваться, — магнитная лента, барабан и дисковая память. Кратко будут изложены как физиче- ские характеристики, так и некоторые логические аспекты рассма- триваемых устройств. Также будут описаны некоторые новые тех- нологии, относящиеся к устройствам массовой памяти и устройст- вам промежуточной памяти. 7-1.1. Магнитные ленты Магнитные ленты — это первые широко используемые компактные устройства внешней памяти. Лента изготовляется из гибкого материала, покрытого легко намагничивающимся слоем феррита. Физически магнитная лента аналогична лентам, исполь- зуемым в обычных магнитофонах, хотя магнитная лента, применяе- мая в ЭВМ, существенно шире. Несколько тысяч футов магнитной ленты наматываются иа бобину. Информация кодируется на ленту последовательно, символ за символом. Вдоль ленты расположено несколько дорожек, по одной дорожке для каждой битовой по- зиции двоичного представления символа. Как правило, исполь- зуется еще одна дополнительная дорожка для контрольного бита. На одном дюйме 1 магнитной ленты можно записать несколько сотен символов; обычно плотность записи равна 800 или Ю00 байт/дюйм. На бобине с 3600 футами ленты с плотностью записи 1600 байт/дюйм умещается максимально 1600 байт/дюймX 1 дюйм равен 2,54 см. — Прим. пер. 585
У 12 дюйм/фут У 3600 фут — 69 120 000 байт. (Если учесть, что для хранения одного символа используется байт, то одной ленты потенциально достаточно для хранения текста примерно 25 книг подобных этой). В дальнейшем мы увидим, что фактически этот максимум невозможно получить. Информация читается или пишется на магнитную ленту с по- мощью лентопротяжного механизма. Лента протягивается под головками чтения/записи с типичной скоростью в 125 дюйм/с. Следовательно, скорость передачи данных при плотности записи 1600 байт/дюйм равна 200 000 байт/с. Когда лентопротяжное устройство не занято чтением или за- писью информации, лента неподвижна. Подача процессором ко- манд чтения или записи вызывает ускорение ленты до постоянной высокой скорости. После завершения команд чтения или записи лента замедляется до полной остановки. В течение фазы ускоре- ния или замедления под головками чтения/записи проходит неко- торый сегмент ленты, который не может служить для чтения или записи информации. Этот сегмент ленты находится между последо- вательными записями (группами данных) и называется межзонным промежутком (IRG) (см. рис. 7-1.1, а). Долина межзонного проме- жутка меняется от 1/2 до 3/4 дюйма в зависимости от типа устрой- ства. Чем больше промежуток между записями, тем меньше инфор- мационная емкость ленты. Например, предположим, что записи по 800 байт каждая пишутся поочередно на ленту с плотностью 1600 байт/дюйм. Тогда при длине межзонного промежутка в 1/2 дюйма только половина ленты используется для хранения данных. Для более полного использования ленты ее записи часто груп- пируются в блоки. Если записи сблокированы, одна команда за- писи может передавать несколько последовательных записей на ленту при отсутствии межзонных промежутков, как показано на рис. 7-1.1, б. В этом случае промежутки, называемые межблоч- ными промежутками (IBG), появляются только между последова- тельными блоками. С увеличением количества записей в блоке (часто называемого коэффициентом блокирования} возрастает степень использования ленточной памяти. Если коэффициент бло- Рис. 7-1.1. Расположение записей на магнитной ленте 586
кирования в предыдущем примере равен 10, то между промежут- ками может дополнительно разместиться 800 < 10 —8000 байт, т. е. полезно используемый объем ленты возрастает с 1/2 до 10/11, Среднее время, необходимое для записи или чтения одной за- писи, обратно пропорционально коэффициенту блокирования, поскольку требуется перематывать меньше промежутков, не содер- жащих информацию, и больше записей может быть прочитано или записано по одной команде. Создается иллюзия, что увеличивая как можно больше коэффициент блокирования, можно повысить степень использования ленточной памяти и минимизировать время чтения и записи. Однако при записи или чтении блока имеет место передача данных из области или в область основной памяти, назы- ваемую буфером. (Подробнее о буферах будет сказано в п. 7-4.1.) Поскольку основная память, как правило, пользуется большим спросом, то размер буфера не может быть слишком большим. Очевидно, следует стремиться к компромиссу между эффектив- ностью использования магнитной ленты и временем чтения/записи, с одной стороны и объемом основной памяти, имеющейся в наличии для буферизации, с другой. Устройства внешней памяти на магнитной ленте имеют огра- ничение, заключающееся в необходимости обработки записей в по- рядке, в котором они поступали на ленту. Следовательно, доступ к записи требует сканирования всех предшествующих записей. Такая форма доступа, называемая последовательным доступом, бу- дет детально обсуждена в п. 7-4. Гибкость и эксплуатационные ка- чества устройств внешней памяти на магнитной ленте увеличи- ваются посредством таких операций, как перемотка на начало или перемотка на заданное число записей. Магнитная лента является, по-видимому, самым дешевым типом внешней памяти большого объема; на сегодняшний день цена бо- бины магнитной ленты приблизительно равна 25 долл. Кроме того, бобину магнитной ленты можно легко устанавливать на лентопро- тяжный механизм и демонтировать, и поэтому она удобна для хранения данных в офлайновом режиме. 7-1.2. Магнитные барабаны Магнитный барабан представляет собой металлический Цилиндр диаметром от 10 до 36 дюймов с внешней поверхностью, покрытой магнитным записывающим материалом, как это изобра- жено на рис. 7-1.2. Цилиндрическая поверхность барабана де- лится на ряд параллельных полос, называемых дорожками. До- рожки далее делятся либо на секторы., либо на блоки в зависимости °т типа барабана. Сектор илн блок является наименьшей адресуе- мой единицей барабана. Отдельный сектор или блок прямо адре- суем в том смысле, что для доступа к блоку или сектору п нет необ- ходимости получать доступ к блокам илн секторам с 1-го до п—1-й, хак это требуется прн использовании устройств последовательной 587
Памяти типа магнитной ленты. По этим причинам барабан отно- сится к так называемым устройствам прямого доступа к памяти. Секторы являются дугами дорожек фиксированной длины, причем целое число секторов составляет полную дорожку. Сек- торы имеют один и тот же размер для всех дорожек барабана. Длина сектора фиксируется либо в аппаратуре барабана, либо при гене- рации вычислительной системы. Барабан с адресуемыми блоками отличается от барабана с адре- суемыми секторами тем, что количество записей блока определяет программист. (Заметим, что понятие блока в данном случае ана- логично понятию блока, введенному при обсуждении магнитных лент). Следовательно, число блоков на дорожках барабана может быть переменным, и даже может меняться размер блоков, помещен- ных на одну и ту же дорожку. Однако, как в барабанах с адресуе- мыми секторами, так и в барабанах с адресуемыми блоками, за один раз в буфер пишется (или из него читается) полный сектор или блок соответственно. Поскольку все секторы имеют идентичный размер и целое число их размещается на дорожке, то требуемый сектор в общем случае находится быстрее, нежели блок. Однако для некоторых примене- ний длина сектора не делится нацело на длину записи, и поэтому часть длины сектора может оказаться неиспользованной. К тому же запись может не умещаться в секторе, и в этом случае потре- буется несколько команд ввода-вывода для записи или чтения од- ной записи. В данной главе, если не указано обратное, мы будем иметь в виду барабаны с адресуемыми блоками. Устройство с адресуе- мыми секторами можно рассматривать как специальный тип уст- ройства с адресуемыми блоками, на дорожках которого имеется целое число одинаковых по размерам блоков. l&toteu чтения''записи Перемещаемые головки чтения/ записи ДорС»М1 Сектор Рис. 7-1.2. Магнитные барабаны: а — с фиксированными головками чтения/записи. б — с перемещаемыми головками чгеимя/записи 588
Данные передаются от барабана или к барабану по мере его вращения с большой скоростью мимо головок чтения/записи. В магнитных барабанах используются две схемы расположения головок чтения/записи. Наиболее распространена схема с фикси- рованными головками чтения/записи, по одной головке на каждую дорожку, как показано на рис. 7-1.2, а. В другой конструкции, показанной на рис. 7-1.2, б, используются перемещаемые головки. Здесь группа головок чтения/записи смонтирована на поперечине, при этом головки могут передвигаться вдоль всего барабана. На- пример, барабан может иметь 100 дорожек и группу из пяти голо- вок чтения/записи. Подобная система позволяет группе головок перемещаться в одну из двадцати позиций, в каждой из которых имеется доступ к пяти смежным дорожкам. Основным компонентом времени доступа к записи для барабана с фиксированными головками является задержка на вращение, или латентный, период L, представляющий собой время ожидания мо- мента, когда барабан повернется в позицию, позволяющую начать требуемую передачу данных. Рис. 7-1.3 иллюстрирует условия, при которых имеют место максимальная, средняя и минимальная задержки. Для барабанов с перемещаемыми головками требуется дополнительное время для установки головок чтения/записи над желаемой дорожкой. Этот добавочный компонент времени доступа часто называют временем поиска S. Таким образом, время доступа A (i), ассоциированное с отдельной операцией ввода-вывода i, вы- ражается как сумма времени задержки и поиска для операции i: A (i) = L (i) + S (i). Для вычисления полного времени завершения операции чтения или записи необходимо учитывать еще один компонент, называемый временем передачи Т. Время передачи представляет собой время на запись или чтение одной записи или серин записей (в зависи- мости от команды ввода-вывода) в предположении, что головки по- зиционированы к месту размещения первой записи, которая должна быть прочитана или записана. Аналогично задержке на вращение время передачи непосредственно зависит от скорости Рис. 7-1.3. Условия:- п — максимальной; б — средней; в — минимальной задержки на вращение; 1 — головка чтения/записи; 2 — начало требуемой записи 589
вращения барабана. Таким образом, полное время для завершения операции т (i) = L (i) Ч~ S (i) Ч~ Т (i). Для барабана с фиксированными головками время S (i) равно нулю- Заметим, что т (i) меняется в зависимости от текущей позиции считывающих головок (для барабана с перемещаемыми головками) и от того, где размещена первая требуемая запись на окружности, ассоциированной с данной дорожкой. Следовательно, т (i) непре- рывно меняется во время работы вычислительной системы, при этом целесообразнее рассматривать статистическое среднее время доступа, являющееся суммой среднего времени поиска и средней задержки на вращение: A (i) = L (i) + S (i). Таким образом, среднее время на выполнение операции ввода-вы- вода определяется выражением 7 (i) = S (i) + L (i) 4- T (i) = A (i) + T (i). Барабаны с фиксированными головками обеспечивают очень быст- рый доступ и высокую скорость передачи данных. Однако они до- роги и поэтому чаще всего используются лишь для временного хранения программ в момент их выполнения. Барабаны с переме- щающимися головками обычно имеют большую емкость памяти и более подходят для хранения больших объемов данных. В табл. 7-1.1 приведены характеристики ряда магнитных бара- банов. Для каждого устройства даются общие емкость памяти, Таблица 7-1.1 Магнитные барабаны Система IBM 360 UN I VAC 1108 UNI VAC 1108 ICL 1900 Модель 2301 FH 432 FASTRAND 1964 Число дорожек 200 128 6 144 512 Емкость сектора, сим- волов Число: Пере- менная 6 158 4 секторов на дорожке Пере- менное 2 048 64 1 024 символов на дорожке 20 483 12 288 10 752 4 048 символов на барабане 4 096 600 I 572 864 132 120 756 2 072 574 Средняя задержка на вращение, мс 8.6 4.25 35 20.5 Среднее время поиска, мс 0 0 58 0 Скорость передачи, еимвол/мс 1 200 1 440 153.8 100 590
емкость дорожки, время доступа и скорость передачи данных. Отметим, что в барабанах фирмы IBM допускается переменный фор- мат хранимых данных. Как упоминалось ранее, адресуемые элементы (секторы или блоки) на барабане могут быть быстро найдены для передачи дан- ных, при этом в отличие от магнитных лент здесь нет необходимо- сти в сканировании ненужных данных. Однако, в противополож- ность магнитным лентам барабан нельзя демонтировать с привода или вала, и, следовательно, максимальная емкость памяти такого устройства ограничена емкостью единичного барабана. 7-1.3. Магнитные диски Магнитные диски в качестве устройств внешней памяти прямого доступа в настоящее время используются более широко, чем магнитные барабаны, главным образом из-за меньшей стоимо- сти. Дисковые устройства обеспечивают относительно малое время доступа и высокую скорость передачи данных. Используются два типа дисковых устройств — сменные диски и несъемные диски. В обоих типах устройств дисковый модуль, или пакет, состоит из нескольких плоских круглых металлических пластин, надетых одна за другой на ось, как это показано на рис. 7-1.4. Верхняя и нижняя поверхности каждой пластины по- крыты ферромагнитными частицами, создающими среду для хра- нения информации. Внешние поверхности верхней и нижней пла- стин обычно не используют для хранения данных, поскольку они легко могут быть повреждены. Поверхность каждой пластины делится на концентрические полосы, называемые дорожками (рис. 7-1.5). Так же как и в маг- нитных барабанах, каждая дорожка делится далее на секторы (или блоки), являющиеся адресуемыми элементами памяти. Отметим, Рис. 7-1.4. Механизмы доступа для дискового устройства 2311 (с разрешения фирмы IBM); * — блок доступа; 2 — дорожки; 3 — дорожка; 4 — Цилиндр; 5 — диски; 6 — десять головок чтения/ записи; 7 — пять рычагов для подвода головок Рис. 7-1.5. Дисковая по- верхность диска с адресуе- мыми секторами 591
что хотя дорожки диска имеют разную длину, каждая из них до- пускает размещение одного и того же объема информации. Следо- вательно, плотность записи на внутренних дорожках выше плот- ности записи на внешних дорожках диска. Информация поступает к диску и передается от него посредст- вом головок чтения/записи. Каждая головка чтения/записи пла- вает над поверхностью диска илн под ней, при этом диск непре- рывно вращается с высокой частотой. В устройствах с несъемными дисками дисковый пакет всегда остается на дисководе. В этом случае, как правило, каждая до- рожка каждой поверхности диска, предназначенной для хранения информации, имеет свою головку чтения/записи. Этим самым обес- печивается быстрый доступ к данным, поскольку время поиска фактически равно нулю, и задержка на вращение является глав- ным компонентом времени доступа. Устройства со сменными дисками имеют перемещающиеся го- ловки чтения/записи. Головки прикрепляются к специальным ры- чагам для их подвода, образуя конструкцию, похожую на гребенку, как это изображено на рис. 7-1.4. Когда требуется получить доступ к данным на определенной дорожке, вся конструкция переме- щается так, чтобы головки чтения/записи разместились над этой дорожкой. Передача данных в каждый момент времени может осу- ществляться только через одну головку чтения/записи, хотя в по- зиции, пригодной для чтения/записи, одновременно располагаются несколько головок. Гребенку с головками можно вывести из про- странства между поверхностями дисковых пластин, что позволяет снять пакет и установить на его место другой. Область диска, откуда можно считать и куда можно записать информацию без перемещения головок чтения/записи, называется цилиндром, или областью поиска (см. рис. 7-1.4). Следовательно, цилиндр является множеством всех смежных по вертикали доро- жек, располагаемых на всех пластинах дискового пакета. Такое определение цилиндра применимо как по отношению к устройст- вам с фиксированными так и перемещаемыми головками. Устройство памяти на диске можно рассматривать как совокуп- ность последовательно пронумерованных цилиндров. Поиском называется'перемещение головок чтения/записи для установки их на цилиндре, содержащем требуемую дорожку. Время поиска определяет наибольшую задержку при доступе к информации, размещаемой на диске, как и при доступе к данным на барабане с перемещаемыми головками. Следовательно, всегда желательно минимизировать время поиска. Для нахождения полного времени на доступ к данным на диске необходимо добавить еще задержку на вращение (латентный период) необходимую для поворота нужного сектора или блока в положение, пригодное для передачи данных. Таким образом, формула для"среднего времени на завершение операции ввода-вывода, данная ранее для магнитных барабанов, применима также и для дисков. 592
Характеристики некоторых дисковых устройств с фиксирован- ными и перемещаемыми головками приведены в табл. 7-1.2 и 7-1.3 соответственно. Устройства фирмы IBM блокоориентированные и поэтому допускают переменный формат данных в отличие от дн- Таблица 7-1-2 Диски с фиксированными головками Система Burroughs В2500/3500 DEC PDP-11 IBM 360 Модель В9370-2 RS 03 2305 Число поверхностей на пакете 2 1 12 Число дорожек на поверх- 100 64 32 нести Размер сектора, символов 100 64 Переменный Число: секторов на дорожке 100 64 Переменное символов иа дорожке 10 000 4 096 14 136 символов иа пакете 2 000 000 262 144 5 428 224 Средняя задержка на враще- 17 8.5 2.5 ние, мс Скорость передачи данных 298.75 250 3 000 символ/мс Т а б л и ц а 7-1-3 Диски с перемещаемыми головками Система CDC HP 3100/3300 2100 IBM 370 Модель 853 2315 2311 2314 3330 Число: поверхностей на па- кете дорожек на поверх- ности символов в секторе секторов на дорожке символов иа дорож- ке символов на пакете Средняя задержка на вращение, мс Среднее время поиска, мс Скорость передачи Данных, символ/мс 10 100 256 16 4 096 4 096 000 12.5 85 288.3 4 200 256 24 6 144 4 915 200 12.5 30 312.5 10 203 Пере- менное То же 3 650 7 250 000 12.5 75 156 20 200 Пере- менное То же 7 294 29 176 000 12.5 60 312 19 404 Пере- менное То же 13 030 100 018 290 8.3 30 806 593
сковых устройств с адресацией секторов. Следует также отметить, что многие дисковые устройства имеют по нескольку дисководов и, следовательно, по нескольку дисковых пакетов или модулей од- новременно на каждом таком устройстве. Приводимые в таблицах данные относятся к одному дисковому пакету. Для сравнения времени, необходимого на передачу данных в устройствах с несъемными и сменными дисками одного класса, рассмотрим операции чтения 4096 байтов информации с дорожки диска с фиксированными головками В9370-2 фирмы Burroughs и с дорожки диска с перемещаемыми головками CDC 853. Используя формулу, приведенную в п. 7-1.1, получаем ^вэ87о-2 (чтение 4096 байт) — 17мс-ф- 4096 символ 298.75 символ/мс — 17 мс -f-13.7 мс = 30.7 мс; Ч'бз (чтение 4096 байт) = 85 мс -ф-12.5 мс -ф- . 4096 символ с । 1 л о ,,, -7 + • осе о-Т—7---- = 97.5 мс 4-14.2 мс = 111.7 мс. 1 288.3 символ/мс Заметим, что полное время на передачу данных при использо- вании дисков с фиксированными головками приблизительно в 3 раза меньше, чем среднее время поиска для сменных дисков. Это ясно иллюстрирует важность минимизации времени поиска для дисков с перемещаемыми головками. В настоящее время дисковая память является наиболее гибким запоминающим устройством. Она может обеспечить как большой объем, так и малое время доступа и, следовательно, удовлетворяет требованиям большинства вычислительных систем. 7-1.4. Массовая память В начале 60-х годов несколько фирм (NCR, IBM, ICL и RCA), производящих ЭВМ, начали продажу устройств прямо- го доступа, так называемых запоминающих устройств на^магнит- ных картах с объемом памяти порядка полумиллиарда символов. Запоминающее устройство на магнитных картах состоит из групп (колод, магазинов, ячеек “или массивов) магнитных карт, на кото- рых хранятся данные. Основной операцией для этих запоминаю- щих устройств является выбор нужной карты и транспортировка ее к вращающемуся барабану. Карта оборачивается вокруг барабана, и данные затем передаются от карты или поступают к ней с по- мощью головок чтения/записи. Вследствие обилия чисто механических операций типичное время доступа для этих устройств превышает полсекунды. Глав- ным преимуществом магнитных карт является большая емкость памяти. Но так как емкость дисковых запоминающих устройств постоянно увеличивается, а их стоимость в пересчете на бит умень- шается, то магнитные карты постепенно перестают использовать. 594
Хотя запоминающие устройства на магнитных картах посте- пенно выходят из употребления, принципы, лежащие в основе их работы, были использованы в новом устройстве массовой памяти MSF385I фирмы IBM. Это устройство обеспечивает третий уровень в иерархии запоминающих устройств и служит для хранения ар- хивных данных вычислительной системы. В настоящее время ста- новятся реальностью базы данных с объемом в несколько миллиар- дов символов, доступные в онлайновом режиме. Применение диско- вой памяти для размещения таких объемов информации слишком расточительно и непрактично. Более целесообразно размещать ко- пии «часто используемых» программ и данных на диске, а пол- ностью все программы хранить на дешевых, хотя и медленных, устройствах массовой памяти. Если программа или данные тре- буются для использования в онлайновом режиме, то их копируют на устройство памяти прямого доступа второго уровня и получен- ную копию используют для активной работы и передачи данных. После завершения необходимых действий копия с диска возвра- щается на устройство массовой памяти, заменяя собой старую ко- ПИЮ- Система фирмы IBM, в которой используется устройство MSF3851, называется системой массовой памяти 3850 (MSS3850). В качестве памяти второго уровня в этой системе служат дисковые устройства типа IBM 3330 (см. табл. 7-1.3). В устройстве MSF 3851 в качестве среды для запоминания данных применяются ленточные кассеты. Ленточная кассета представляет собой катушку магнит- ной ленты 4 дюйма длиной и 2 дюйма в диаметре и имеет емкость, равную половине емкости дискового пакета IBM 3330. Доступ к кассете становится возможным после того, как она будет извле- чена из библиотеки кассет, а лента обернута вокруг специального ролика и тем самым позиционирована под набором головок чтения/ записи. Данные передаются цилиндр за цилиндром от диска IBM 3330 к ленточной кассете и наоборот. Устройство MSF 3851 может содержать до 4720 ленточных кас- сет с общей емкостью до 236 млрд, символов. Не составляет боль- шого труда представить себе, что в будущем устройства массовой памяти, подобные MSF 3851, существенно уменьшат, если не устра- нят совсем, необходимость размещения больших объемов данных В офлайновой памяти на ленточных бобинах или сменных диско- вых пакетах. 7-1.5. Промежуточная память Новым направлением в области внешних запоминающих Устройств является электронный диск. Название «электронный» объясняется тем, что быстрый доступ к данным на электронном диске осуществляется без механических перемещений. Электрон- ный диск разработан в качестве промежуточной памяти для запол- нения разрыва между основной памятью и памятью прямого до- 595
Ступа. Это стало возможным за счет меньшей стоимости по сравне- нию со стоимостью основной памяти и меньшего времени доступа по сравнению с временем доступа в существующих устройствах внешней памяти. В настоящее время наиболее перспективными для использо- вания в качестве электронных дисков потенциально являются приборы с зарядовой связью. Они базируются на полупроводниковой технологии, но должны стоить в пересчете на бит в 3 раза меньше, чем основная память. Среднее время доступа для этих устройств составляет около 60 мкс. Другими «кандидатами» в электронные диски могут быть ЗУ на магнитных доменах и память со считыванием с помощью электрон- ного луча. Упражнения к п. 7-1 1. Сколько записей по 80 байт каждая может разместиться на маг- нитной ленте длиной 3600 фут при условии, что: а) в блоке содержится по одной записи; плотность записи 1600 байт/дюйм, межблочные промежутки имеют длину ®/4 дюйма каждый; б) пять записей в блоке; плотность записи 1600 байт, межблочные проме- жутки имеют длину 3/4 дюйма; в) двадцать пять записей в блоке; плотность записи 3200 байт/дюйм, меж- блочный промежуток имеет длину */2 дюйма; г) одна запись на блок, плотность записи 800 байт/дюйм, межблочный про- межуток равен х/2 дюйма. 2. В вычислительных системах используется несколько стандартных зна- чений для плотности записи (см. упр. I). Очевидно, что эти значения плотности записи не являются эффективными поскольку они не зависят от размеров бло- ков и межблочных промежутков. Выведите формулу для эффективной плотности записи (выраженной в количестве эффективных байтов иа 1 дюйм), зависящий от трех факторов: стандартной плотности, размера блока и длины межблочного промежутка. 3. Вычислите эффективную плотность для случаев а—г в упр. I. 4. Предположим, что средние скорости движения ленты равны 125 дюйм/с прн чтении информации и 30 дюйм/с прн прохождении через межблочные про- межутки. Вычислите время, требуемое для чтения ленточных данных, специфи- рованных в упр. 1. 5. Устройства прямого доступа иногда называют устройствами с произ- вольным доступом. Чем можно объяснить такое название? 6. Сколько дорожек барабана типа UN I VAC FASTRAND II потребуется для размещения файла из 10 000 записей, каждая длиной 80 байт? Предпола- гается, что записи ие должны выходить за границы секторов. Сколько времени потребуется для чтения этого файла? В оценку времени включите начальное среднее время доступа. 7. Выполните упр. 6, предполагая, что используется барабаи IBM 2301, причем коэффициент блокирования равен 20, в каждом блоке размещается 14 байт систехмпой информации и записи не должны выходить за пределы дорожек. 8. Вычислите ожидаемое время для нахождения и чтения отдельной 64- символьной записи при использовании следующих дисков: a) DEC RS03, б) IBM 3330 ив) HP 2315. Предположим, что мы обновляем записи вместо их чтения. В большинстве систем сразу после операции записи выполняется операция чтения. Эта добавочная операция, на выполнение которой требуется время, равное периоду вращения диска, необходима для проверки правильности записи. ' Т. е. фактическими значениями. — Прим. пер. 596
Й. Допустим, что для подготовки к чтению Двух файлов, содержащих 64- байтовые записи, один из которых размещен на устройстве PS03 фирмы DEC, а второй—на .устройстве 2314 фирмы IBM, требуется одинаковое среднее время. По сколько записей должно быть в каждом файле, чтобы полное время чтения первого файла было равно полному времени чтения второго (или приблизительно равно)? Относительно расположения записей на устройстве 2314 сделайте те же предположения, что и для устройства 2301 в упр. 7. 7-2. ОПРЕДЕЛЕНИЯ И ОСНОВНЫЕ ПОНЯТИЯ В данном параграфе вводится целый ряд определений и терминов, используемых в оставшейся части главы. Поскольку некоторые из определяемых понятий непосредственно связаны с характеристиками внешних устройств, мы решили поместить этот параграф после описания запоминающих устройств. Некоторые из определяемых терминов были введены ранее. Мы обсудим их еще раз, с тем чтобы развить выразительный и последовательный взгляд на иерархию информационных структур, связанных с обработкой файлов. Запись (иногда называемая группой или сегментом) является коллекцией элементов информации, касающейся отдельного объекта. Например, запись может состоять из информации о пасса- жире самолета или о предмете, проданном универсальным мага- зином. Элемент (иногда называемый полем) записи — это мини- мальная значащая единица информации об объекте. Различными элементами записи с информацией о пассажире могут быть имя пассажира, адрес, номер места и ограничения на меню. Обычно элемент записи является целым, действительным или литерно- строчным элементом данных. Однако элементы записи могут быть агрегатами элементов, такими как массив элементов или набор неоднородных элементов. В ПЛ/1 понятие записи в его самой ши- рокой интерпретации можно без натяжки считать эквивалентным понятию структуры. Например, возможная структура записи, содержащей информацию о пассажире, объявляется следующим образом: DECLARE 1 PASSENGER, 2 NAME, 3 INITIALS CHAR (2), 3 SURNAME CHAR (20), 2 ADDRESS CHAR (30), 2 SEAT—-NO CHAR (2), 2 MENU CHAR (35); Коллекция записей, описывающая некоторый набор объектов, связанных между собой, и созданная для решения определенной задачи, называется файлом. Например, набор записей с информа- цией о пассажирах, имеющих билеты на один рейс, составляет файл. Элемент данных, единственным образом идентифицирующий за- пись в файле, называется ключом. В файле пассажиров индивиду- 597
альная запись с информацией о пассажире может однозначно опре- деляться именем пассажира при условии отсутствия у пассажиров одного рейса одинаковых имен. При желании в качестве ключа можно использовать элемент, содержащий номер места, поскольку для одного рейса номера мест имеют различные значения. Как правило, записи в файле упорядочиваются в соответствии со значениями ключей. Следовательно, если в качестве ключа вы- брано имя пассажира, то запись с информацией об Адамсе будет предшествовать записи о Брауне, располагающейся, в свою очередь, перед записью о Кэмпе при лексико-графическом упорядочении по именам. Некоторые файлы упорядочиваются по элементу записи, называемому элементом следования, который может иметь повторя- ющиеся значения. Например, в файле месячных продаж некоторой фирмы для одного покупателя допустимо появление нескольких записей с информацией о покупках. Файл можно упорядочить по номеру счета покупателя, и, вероятно, для некоторых номеров в файле будет встречаться по нескольку записей. До сих пор мы рассматривали иерархию информационных структур, в которой композиция элементов данных образует запись, а из записей, в свою очередь, формируются файлы. Файлы могут группироваться, образуя набор файлов. Если набор фай- лов используется в прикладных программах для определенного предприятия или прикладной области и если между записями этих файлов существуют некоторые ассоциативные связи или зависимости, то такая коллекция файлов рассматривается как база данных. На рис. 7-2.1 изображена иерархия информацион- ных структур в виде, типичном для сферы обработки файлов. Элементы, записи, файлы и базы данных являются логиче- скими понятиями, так как они были введены без каких-либо указаний на их физическую реализацию на устройствах внешней памяти. С этими логическими представлениями ассоциируется ряд понятий, относящихся к структурам файлов. В предыдущем пункте было описано физическое размещение записей на разных 598
устройствах внешней памяти. В частности, было указано, что логические записи в. представлении программиста могут груп- пироваться, образуя единый физический объект, называемый блоком или физической записью. Подобное блокирование записей не влияет на их логическую обработку. Однако это обеспечивает более эффективную обработку, так как значительно уменьшает число команд ввода-вывода, выдаваемых операционной системой. Для термина файл в среде специалистов по обработке данных допускается как физическая, так и логическая интерпретация. Физически файл рассматривается как совокупность физических записей, почти всегда размещаемых в смежных областях внешней памяти. Для обеспечения соответствующего доступа к физическим записям файла имеющиеся в операционной системе программы управления памятью ведут целый ряд таблиц. Эти таблицы, на- ряду с другой управляющей информацией, помещаемой в физи- ческой записи (такой, как длина записи), невидимы для пользо- вателя, работающего с файлом на логическом уровне. В системах IBM физический файл принято называть набором данных. Физическое строение набора данных описывается посред- ством DD-предложений языка управления заданиями JCL. Ряд таких предложений будет приведен в разделах, посвященных фай- ловой организации. Физическая запись состоит из полей байтов и слов с информа- цией в двоичном представлении. Термин физическое поле ассо- циируется с представленными в двоичном коде информационными единицами, соответствующими элементу или полю логической записи. Для простоты и определенности мы будем использовать термины «поле», описывая поле физической записи, и «элемент» при ссылке на поле логической записи. Мы обнаружим в дальней- шем, что физическая запись имеет несколько полей, не имеющих соответствующих им эквивалентов в логическом описании записи, в частности используемые системой поля, содержащие указатели. После введения большей части терминов, необходимых для нашего обсуждения файлов, исследуем некоторые факторы, опре- деляющие файловую организацию. Наибольшее влияние на орга- низацию файла оказывает тип операций, необходимых для работы с файлом. Тип операций определяется, в свою очередь, областью применения. Обычно требуются операции, рассмотренные ранее в книге, а именно, добавление, удаление и обновление. Отдельная операция, затрагивающая запись илн несколько записей, назы- вается транзакцией. Например, сообщение «Удалить Питера Харди из списка пассажиров рейса 279» является транзакцией. При обработке файлов транзакции часто представляются в форме записей транзакций. Записи транзакций содержат ключи записей, подлежащих обработке, а также тип операции и любую доба- вочную информацию, необходимую для выполнения этой опера- ции. В следующем параграфе мы увидим, что выбор файловой структуры определяется не только операциями или типами тран- 599
закций. Он также зависит и от длины транзакций, частоты их поступления и времени ответа, требуемого для завершения тран- закции или нескольких транзакций. Организация файла зависит также от запоминающей среды, на которой он размещается. Например, позже мы обнаружим, что файловая организация на последовательных устройствах, таких, как магнитная лента, значительно отличается от файловой организации, допускаемой устройствами прямого доступа, по- добными магнитным дискам. Другими важными аспектами, обсуждаемыми далее в этой главе, являются создание и реорганизация файлов. 7-3. ОРГАНИЗАЦИЯ ЗАПИСЕЙ В предыдущем параграфе мы описали запись как нечто, состоящее из элементов или полей. В этом параграфе мы сосредо- точим внимание на важном моменте организации записей, а именно на структуре элементов записей. Прежде чем обсуждать этот вопрос, введем понятие диапазона элемента. Диапазон элемента представляет собой набор значе- ний, которые может принимать элемент. Пусть, например, мы исследуем прикладную задачу, связанную с размещением ауди- торий по зданиям университета. Очевидно, одним из элементов записи, описывающей отдельную аудиторию, должен быть указа- тель корпуса, в котором находится аудитория. Типичный диапа- зон значений для такого элемента могут представлять корпуса: административный, сельского хозяйства, гуманитарных наук, химии, экономики, образования, техники, общего назначения, юридический, медицины и физики. Если известны все значения элемента, как в последнем примере, то говорят, что элемент имеет предкоординированный диапазон. Некоторые элементы записей, содержащие текстовую информацию, не являются предкоордини- рованными. Например, в записи для библиографической системы может содержаться элемент данных для аннотации книги. Оче- видно, не все аннотации известны до формирования файла с биб- лиографической информацией. Теперь мы изложим целый спектр методов для представления обоих типов элементов данных и начнем с рассмотрения способов, справедливых для предкоордннированных элементов. Логическое кодирование элементов. Для каждого допустимого значения предкоординированного элемента резервируется един- ственное значение в векторе или строке логических значений этого элемента. В элементе, описывающем местоположение ауди- тории, можно отвести по одному биту для каждого здания универ- ситетского городка. Будем считать, что значение первого бита представляет административный корпус, значение второго бита— корпус сельского хозяйства, и т. д., в конце концов, значение последнего, одиннадцатого, бита представляет здание физики. 500
Например, элемент данных со значением ’01000000000’ В в би- тово-строчном представлении языка ПЛ/1 обозначает корпус сельского хозяйства, если логическим значением 1 обозначить принадлежность аудитории соответствующему корпусу, а значе- нием 0 — ее непринадлежность. Большим преимуществом строк логических значений является то, что с их помощью можно представлять многозначные элементы (т. е. элементы, которые могут принимать более одного значе- ния из допустимого диапазона). Например, *10001000011’ В ука- зывает на корпуса административный, экономики, медицины и физики. Конечно, такие многозначные элементы не имеет смысла использовать в записях, описывающих свойства ауди- тории. Однако, если подобный элемент появляется в записях файла, указывающих на наличие определенных средств обслу- живания, таких как кондиционирование воздуха, кафетериев, библиотек и других, то многозначные элементы становятся не- обходимыми. Отметим, что элементы, закодированные с помощью логических значений, имеют фиксированную длину, но могут представлять переменное число значений из диапазона элемента. Это весьма важное качество, поскольку элементы переменной длины приводят к записям переменной длины, обработка которых не так эффективна, как обработка записей фиксированной длины. Дальнейшее обсуждение этой темы будет продолжено в конце параграфа. Двоичное кодирование элементов. Метод, основанный на двоич- ном кодировании, позволяет использовать память более эффек- тивно, чем только что рассмотренный метод логического кодиро- вания. Предположим, что элемент принимает одно из п значе- ний. Воспользовавшись двоичной схемой, можно закодировать значения элемента так, что каждый код будет иметь длину [Iog2 п 1. Пример кодирования такого типа был дан в табл. 1-1.1 и обсуж- дался в п. 1-1. В сущности схема двоичного кодирования заклю- чается в приписывании единственного двоичного числа каждому значению из диапазона .значений элемента. Табл. 7-3.1 иллюстри- рует применение схемы двоичного кодирования для приведенной Т а б л и ц а 7-3.1 Пример двоичного кодирования для корпусов университетского городка Корпус Код Корпус Код Административный ’0000’В Техники ’ОПО’В Сельского хозяйства •ооогв Общего назначения он ГВ Гуманитарных наук ’ООЮ’В Юридических наук ’1000’Б Химии ’001ГБ Медицины ’100ГБ Экономики ’ОЮО’В Физики ’1010’Б Образования ’010ГБ 601
Рис. 7-3.1. Получение кода Хаффмана выше задачи с корпусами университетского городка. Для задания двоичных кодов в этой таблице используется аппарат представ- ления битовых строк в ПЛ/1. Следует сразу отметить, что при использовании схемы двоич- ного кодирования элемент данных в нашем примере может быть представлен четырьмя битами, в то время как при логическом кодировании необходимо 11 бит. Однако в двоичной схеме нельзя представить многозначные элементы, если только онн явно не включены в схему (например, ’111Г В может обозначать и адми- нистративный корпус, и корпус гуманитарных наук одновре- менно). Схема кодирования по Хаффману.^ В схеме кодирования по Хаффману предполагаются известными вероятности появления значений элемента из допустимого диапазона. При известных вероятностях для каждого значения из предкоординированного диапазона можно сконструировать двоичный код с минимальной средней длиной. Коды Хаффмана для обозначения местоположе- ния аудитории даны в табл. 7-3.2, где указаны и соответству- ющие вероятности. Такое априорное знание вероятностей прин- ципиально необходимо при получении кодов Хаффмана. В схеме кодирования по Хаффману короткий по длине код приписывается значению с большей вероятностью появления, а более длинный код — значению с меньшей вероятностью появ- ления. Ожидаемая, или средняя, длина кода, указывающего ме- стоположение аудитории, может быть найдена с помощью следующей формулы: п Средняя длина = £ /£- X pt, 602
Таблица 7-3.2 Вероятность нахождения аудитории в корпусах и соответствующие коды Хаффмана Корпус вероятность Код Гуманитарных наук 1/4 10 Техники 3/16 00 Образования 1/8 по Медицины 1/8 011 Экономики 1/16 1110 Общего назначения 1/16 1I11I Химии I/I6 11110 Юридических наук I/I6 0101 Сельского хозяйства 1/32 01001 Адми нистративный 1/64 010001 физики 1/64 010000 где li и pi являются соответственно длиной и вероятностью, связанными с кодом i. Следовательно, средняя длина кода, иден- тифицирующего корпус, будет равна 2 X 1/4 Ч- 2 X 3/16 + 3 X1/8 + 3 X 1/8 + 4 X 1/16 + + 5 X 1/16 + 5 X 1/16 + 4 X 1/16 + 5 X 1/32 + 6 >: 1/64 + + 6 X 1/64 = 33/32 бит 3.09 бит. Напомним формулу (1-1-1) из п. 1-1 для вычисления среднего количества информации, переданной от источника: н =—f' P/iog2ta). 1=1 Применяя эту формулу для заданных в табл. 7-3.2 вероятностей элементов, получим Н = — 1/4 ?< Iog3 (1/4) — 3/16 X Ioge (3/16) — — ... — 1/64 X logs (1/64) 3.04 бит. Таким образом, средняя длина элемента, определяющего рас- положение аудитории, приблизительно равняется среднему ко- личеству информации, передаваемому для идентификации кор- пуса. Так как Н представляет собой минимальное среднее коли- чество информации, необходимой для передачи закодированного значения, то становится ясно, что схема кодирования по Хафф- ману, данная в табл. 7-3.2, очень эффективна с точки зрения ми- нимизации затрат памяти. В подтверждение этому вспомним, что для того же элемента при логическом кодировании и двоичном кодировании с кодами фиксированной длины требуется соответ- ственно 11 и 4 бит. 603
С помощью рис. 7-3.1 вкратце объясним, как генерируется код Хаффмана. Первоначально предполагается, что для каждого значения элемента кодовое слово является пустой строкой. Для построения кодов Хаффмана значения элементов упорядочи- ваются согласно их вероятностям, начиная со значения элемента, имеющего наибольшую вероятность, и кончая значением элемента с наименьшей вероятностью. Затем значению элемента с наимень- шей вероятностью ставится в соответствие код, получаемый сцеп- лением нуля и кодового слова для этого значения. Код для значе- ния элемента со следующей по величине вероятностью получается сцеплением единицы и его кодового слова. (Заметим, что следу- ющая по величине вероятность может равняться наименьшей). Далее эти значения элемента группируются вместе, формируя новое значение элемента с вероятностью, равной сумме вероят- ностей двух комбинированных значений. После этого в случае необходимости таблица переупорядочивается, и для двух значе- ний элемента с наименьшей и следующей по величине вероят- ностями перед их кодовыми словами ставятся 0 и 1 соответственно. Эти значения элементов затем комбинируются, и процесс продол- жается. Вся процедура заканчивается, когда размер таблицы умень- шается до двух, и последнее множество значений элемента полу- чает в качестве кодов соответствующие двоичные значения, сцепленные с кодовыми словами для этих значений элементов. На рис. 7-3.1 последовательность, в которой получается кодовое слово 010000, представляющее корпус физики, иллюстрируется с помощью жирных стрелок. Разработка итеративных и рекурсив- ных алгоритмов для получения кодов Хаффмана оставлена в ка- честве упражнения в конце этого параграфа. Коды Хаффмана имеют два важных свойства. Во-первых, можно доказать [1 ], что коды Хаффмана являются двоичными кодами с минимальной средней длиной. Следовательно, закоди- рованные по Хаффману элементы записей имеют в среднем мини- мальную длину. Также необходимо отметить, что коды Хафф- мана обладают префиксным свойством. Это значит, что ни один код в полученном множестве кодов С не имеет префикса другого кода. Таким образом, для любого у £ С не существует х С такого, что х О z — У для некоторой непустой двоичной строки. Поэтому при обычном линейном сканировании строки, содержа- щей последовательность кодов Хаффмана, можно выделить каж- дое следующее закодированное значение в силу уникальности пре- фикса. Например, предположим, что сканируется запись с двоич- ной информацией 110100011... для выделения следующего значе- ния элемента, определяющего корпус. Этим значением элемента будет корпус образования, так как он представляется кодом *110* В, а *100011...’ будет остатком записи. Нетрудно заметить, что префиксное свойство чрезвычайно важно при декодировании элементов записей переменной длины. ' 604
Самым большим недостатком кодов Хаффмана является то, что онн имеют переменную длину, и при неизвестной длине кода для выделения очередного значения элемента требуются допол- нительные затраты времени. Элементы фиксированной длины. Если элементы записей имеют фиксированную длину и их область значений слишком обширна для эффективного битового кодирования, то для пред- ставлений элемента выбирают некоторую примитивную структуру данных (т. е. целые, вещественные, строки и т. д.). Например, неразумно использовать битовое кодирование для элемента дан- ных, представляющего чистую выручку от продажи за месяц. Вместо этого мы можем описать запись, содержащую требуемый элемент данных, с помощью средств используемого языка про- граммирования. В ПЛ/1 такое описание можно сделать следу- ющим образом: DECLARE 1 MONTHLY—REPORT, 2 MONTH CHAR (9), 2 NET—SALES FIXED (8, 2), Элемент данных, представляющий чистый доход, имеет диапазон значений от —999999.99 до 999999.99. Едва ли будет целесооб- разным решение программиста воспользоваться битовым кодиро- ванием для элемента данных со столь большим множеством зна- чений, в то время, как компилятор обеспечивает эффективное кодирование такого элемента посредством десятичных перемен- ных с фиксированной точкой. Заметим, что элемент записи MONTH можно существенно уменьшить в длине, если восполь- зоваться двоичным кодом фиксированной длины ’0000’ В для января, ’0001* В для февраля, *1011’ В для декабря и описать соответствующий элемент записи в виде битовой строки ВГТ (4). Другими примерами элементов данных, имеющих диапазон значе- ний, слишком большой для битового кодирования, являются фамилии и номера в каталоге. Поскольку оба эти элемента данных мы можем рассматривать как элементы фиксированной длины, то формально диапазоны их значений подходят под определение предкоординированного Диапазона. Понятно, что элементы данных фиксированной длины принимают конечное множество значений и, следовательно, мо- гут быть априори перечислены. Однако мы зарезервируем термин «предкоординированный» для таких множеств значений, которые заранее заданы явным образом; в большинстве же систем, свя- занных с фамилиями и номерами в каталоге, такое априорное знание отсутствует. До сих пор, за исключением кодов Хаффмана, мы обсуждали структуры записей, состоящих из элементов данных фиксиро- ванной длины. Рассмотрим организацию 'записей, содержащих элементы, которые часто приводят к записям переменной длины. 605
Повторяющиеся элементы данных. Имеется много приложе- ний, в которых величина, связанная с элементом данных, может быть списком каких-то значений. Например, «полученные ученые степени» и «языки программирования, используемые на вычисли- тельной установке» являются элементами, в качестве значения которых может быть некоторый набор свойств или объектов. В этом случае значением элемента может быть список 1 «В. Sc., М. Sc., Ph. D.» или список «КОБОЛ. ФОРТРАН, ПЛ/1, БЕЙСИК» соответственно. Наиболее популярный метод организации повторяющихся по- лей заключается в создании элемента, в котором можно накапли- вать ряд значений, но не более некоторого максимального числа. Если ограничить этот максимум числом три, то элементы, взятые нами в качестве примеров, позволят хранить такую информацию, как «три последние полученные степени» и «три наиболее часто используемых языка программирования». Некоторые вычислительные системы обеспечивают организа- цию повторяющихся элементе® как элементов переменной длины при условии, что они появляются последними в объявлении за- писи. Такой тип записей переменной длины допускается в ПЛ/1. Теговая организация записей. Элементом с теговой организа- цией называется такой элемент, который содержит не только информацию, относящуюся к конкретному приложению, но н информацию, описывающую структуру самого элемента. Напри- мер, в кадровой системе, записи которой хранят информацию о служащих некоторой компании, элементы записи могут содер- жать список предыдущих адресов жительства, образование, опыт предыдущей работы, перечень поощрений и т. д. Вместо создания повторяющихся элементов для каждого из этих информационных полей более удобно помещать теги вместе с каждым элементом В общем случае тег используют одним из двух следующих спо- собов. При первом подразумевается заранее определенным фик- сированный порядок элементов записи, а тег применяется для разделения элементов, как показано на рис. 7-3.2. Разделение 1 Соответственно «бакалавр наук», «магистр наук», «доктор философии».— Прим. пер. Рис. 7-3.2. Иллюстрация записи с предварительно установленным порядком элементов, отделяемых друг от друга с помощью тегов 606
Опыт предыдущей работы шпсутстдует 3 предыдущих адреса___________|______________________ * •#Ш Birchmount Park, Vancouver, Б С Apt 201,1992 Columbus Cres, Halifax, N S P 0 302. Noose law, Sask 2 научные степени ____________j M.Sc Computer Science, Univ of 8 I ,19 В 8 Sc Mathematics, Saint Mary's, 1969 рис. 7-3.3. Иллюстрация включения в гег описания элементов записи элементов обеспечивается либо с помощью граничного маркера, либо с помощью дескриптора длины, как это описано в п. 2-4. Вторым подходом к организации записей, при котором исполь- зуются теговые элементы, является включение в тег описания илн имени элемента (рис. 7-3.3). При использовании этой техники нет никакой необходимости размещать значения элементов за- писи в каком-то заранее определенном порядке. Само собой разу- меется, что добавочная гибкость, даваемая тегами, динамически описывающими синтаксис записи, приводит к увеличению длины записи. Текстовые элементы. Имеется еще один тип элементов запи- сей, отличный от рассмотренных ранее. Это отличие в основном заключается в длине элемента. Под текстовым элементом пони- мается элемент, содержащий большое количество текстовой инфор- мации, такой как текст рукописи, аннотации книги, доклада или объявления в газете. В типичном случае записи для таких эле- ментов являются одноэлементными. Нередко вследствие большой длины записей текстового типа их разделяют на несколько физи- ческих записей, или блоков. Для уменьшения памяти, необходи- мой для хранения текстовой информации, часто применяют не- которые методы сжатия информации. Один из таких методов осно- ван на использовании конкорданции всего текста на уровне слов. В методе конкорданции каждое различное слово текста хранится вместе с перечнем позиций, в которых оно встречается в тексте. Например, табл. 7-3.3 иллюстрирует конкорданцию английского предложения: In a concordance each unique word in the text is stored along with the various positions at which that word appears in the text. Обычно список слов для конкорданции организуется в алфа- витном порядке. Для восстановления «нормальной» формы текста необходимо сцепить слова в порядке, продиктованном возраста- ющими значениями позиций в конкорданции. Экономия памяти достигается за счет того, что многократно появляющиеся в тексте слова, такие как «the» и «in», помещаются в конкорданцию только один раз. 607
Таблица 7-3.3 Конкорданция английского предложения Слово Позиция Слово Позиция а 2 stored 11 along 12 text 9, 24 appears 21 that 19 at 17 the 8, 14, 23 concordance 3 unique S , each 4 various 15 in 1, 7, 22 which ’ 18 J is 10 with ‘ 13 positions 16 word 6, 20 Схема кодирования по Хаффману, рассмотренная ранее, также может быть использована для уменьшения длины текстового элемента данных. Помимо операции восстановления первоначальной формы тек- ста, для информации в сжатом виде может потребоваться обработка и другого типа, например редактирование текста, записанного в форме конкорданции. Следует при этом учесть, что в некоторых прикладных задачах добавочное время обработки является ре- шающим фактором. Указатели. Далее в этой главе будет показано, что очень важ- ным типом элементов записей является элемент, содержащий информацию, которая отсылает к другой записи в файле и в от- дельных случаях к записи другого файла. Информация, служа- щая в качестве указателя, может иметь форму ключа другой записи, относительной позиции записи в файле или быть абсо- лютным физическим адресом в терминах адресов цилиндра, до- рожки, сегмента (блока). Элементы, используемые как указатели, мы обсудим более подробно при изучении файлов прямого доступа в п. 7-6. Там же будет проиллюстрировано применение указателей на ряде примеров. Прежде чем закончить этот параграф, полезно сделать заклю- чительные комментарии относительно записей переменной длины. Если структура записей в файле существенно неоднородная, то использование записей переменной длины приводит к экономии памяти. Для элементов данных переменной длины, таких, как коды Хаффмана, повторяющиеся элементы и элементы с теговой организацией, требуется дополнительное время на кодирование и декодирование записей. Обычно разработка программ кодиро- вания и декодирования остается за прикладным программистом, поскольку во многих ситуациях формат записей определяется областью применения. Следовательно, прежде чем выбирать переменный формат записей, необходимо хорошо представлять 608
себе возникающие при этом проблемы. В следующем параграфе описывается детальная структура файлов, используемых с запи- сями переменной длины. После изучения формата записей пере- менной длины, можно лучше оценить сложности, возникающие в процессе обработки таких записей. Резюмируя, можно сказать, что элементы с небольшим пред- координированным диапазоном могут быть эффективно пред- ставлены с помощью одной из двоичных схем кодирования. Для элементов с большим множеством допустимых значений лучше воспользоваться средствами описания данных, имеющимися в ис- пользуемом языке программирования. Многозначные н повторя- ющиеся элементы приводят к записям переменной длины, если их нельзя закодировать битовой строкой. Текстовую информацию, которая подвергается только незначительной текстовой обработке, имеет смысл предварительно сжать. Рассмотрев организацию за- писей, можно перейти к вопросу применения различных типов элементов, описанных в данном параграфе, для формирования записей, определяющих структуру файла. Упражнения к п. 7-3 1. Дайте практический пример многозначного элемента записи, для которого удобно использовать логическое кодирование. 2. Большая межнациональная компания выпускает свою продукцию в не- скольких городах. Элемент в записи, описывающей выпускаемый продукт, со- держит одно из следующих названий городов: Нью-Йорк, Токио, Чикаго, Лон- дон, Саи-Франциско, Париж, Монреаль, Детройт, Дюссельдорф, Мехико, Сент- Луис. Предложите двоичный код фиксированной длины для элемента, указываю- щего место выпуска продукции. Сколько еще городов можно добавить в список, не увеличивая длину кода? 3. Разработайте итеративный алгоритм получения кодов Хаффмана при заданных вероятностях р1г ..., рп для п значений элемента. 4. Разработайте рекурсивный алгоритм получения кодов Хаффмана при заданных вероятностях р1г .... рп .для п значений элемента. 5. Известно, что значения элемента, указанные в упр. 2, имеют приблизи- тельно следующие вероятности появления в записи, описывающей выпускаемую продукцию: 1/4, 1/6, 1/8, 1/8, 1/12, 1/16, 1/24, 1/48, 1/48. Получите коды Хафф- мана для этих значений. Какова средняя длина элемента при использовании этого кода? 6. Вычислите среднее количество передаваемой информации (т. е. энтропию источника сообщения) для элемента записи из упр. 2, приняв значения вероят- ностей, заданные в упр. 5. Насколько эффективны коды, полученные в упр. 2 н упр. 5? 7. Как бы вы организовали запись для файла служащих, в которой должны содержаться следующие элементы: а) имя, б) адрес, в) время службы в ком- пании, г) характер выполняемой работы (предполагается, что используется фиксированное число типов работы), полученные степени, е) предыдущие занимаемые должности Опишите на языке ПЛ/1 структуру, представляющую запись. 8. Постройте пословную конкорданцию для первых трех предложений этой главы, исключая ее название. 9. Какой тип структуры хранении вы выберете для представления следую- щих элементов записи: а) расстояние в милях, пройденное коммивояжером за месяц; б) днн недели; в) излюбленные кулинарные рецепты тетушки Матильды; г) погодные условия дня, описываемые одной из следующих категорий: ясно, 20 Трамбле Ж-, Соренсон П. 600
облачно, пасмурно, дождь, метель, шторм илн торнадо; д) то же множество зна* ченнй, что и в предыдущем пункте; г), но допускается более чем одна категория при описаннн погодных условий дня; е) прежние места жительства. 7-4. ПОСЛЕДОВАТЕЛЬНЫЕ ФАЙЛЫ1 В п. 7-2 было отмечено, что большая часть операцион- ных систем обеспечивает набор основных файловых организаций, популярных среди пользователей систем. Тремя наиболее распро- страненными типами организаций являются последовательная, индексно-последовательная и прямая (см. пп. 7-6 и 7-8 соответ- ственно). Обсуждение каждого из этих типов организации начи- нается с описания его файловой структуры. Затем рассматри- ваются допускаемые файловой организацией типы обработки, поясняемые с помощью алгоритмов. В конце каждого параграфа описываются средства обработки для данного типа файловой орга- низации в ПЛ/1. Практические примеры, иллюстрирующие каж- дый тип файловой организации, даются в отдельных параграфах. 7-4.1. Структура последовательных файлов В последовательном файле записи в запоминающем устройстве размещаются одна за другой. Конечно, последова- тельный тип представления информации в памяти не нов для нас. Ранее мы обсуждали последовательное представление литер в строке, элементов массивов и некоторых линейных и нелиней- ных списков. Поскольку последовательное размещение концеп- туально просто и в то же время достаточно гибко, чтобы решить многие проблемы, связанные с хранением и обработкой больших объемов данных, последовательный файл был самой популярной базовой файловой структурой в индустрии обработки данных. Все типы устройств внешней памяти обеспечивают использо- вание последовательной файловой организации. Отдельные ус- тройства по своей природе могут быть предназначены для хра- нения только последовательных файлов. Например, как опи- сано в п. 7-1, информация размещается на магнитной ленте в виде непрерывной цепочки записей по всей длине ленты. Для доступа к отдельной записи требуется доступ ко всем предыдущим запи- сям файла. Строго последовательными по своей природе являются также устройства ввода с перфоленты, карточного ввода и построч- ной печати. Магнитные диски и барабаны обеспечивают как пря- мой, так и последовательный доступ к записям и, следовательно, допускают использование последовательных файлов наряду с дру- гими типами файлов. Физическое размещение последовательного файла на барабане или диске осуществляется занесением последовательности запи- сей в смежные части дорожки. Конечно, если файл больше, чем имеющееся на дорожке пространство, то записи размещаются 610
на смежных дорожках. Такое понятие физической смежности можно расширить на цилиндры и даже на комплекс из несколь- ких внешних устройств, связанных с одним устройством управ- ления. Операции, которые можно выполнять над последовательными файлами, несколько различаются в зависимости от используе- мого устройства внешней памяти. Например, файл на магнитной лейте может быть либо входным, либо выходным файлом, ио не тем и другим одновременно. Последовательный файл на диске может быть использован или только для ввода, или только для вывода, или для обновления. Под обновлением подразумевается возможность перезаписи последней прочитанной записи в тот оке самый- файл, если в этом возникает необходимость. Некоторые операционные системы имеют средства управления файлами, позволяющие расширять файл за счет записей, помещаемых после текущей последней записи. В некоторых системах при последова- тельном сканировании файла в прямом и обратном направлениях допускается пропускать некоторое количество записей без их чтения или записи. Последняя возможность лежит за пределами сферы основных средств обработки последовательных файлов и поэтому не рассматривается нами в деталях. Перед обсуждением типа обработки, который обычно приме- няется для последовательных файлов, необходимо изучить пере- дачу информации из файла в программу пользователя и наоборот. В предыдущих параграфах этой главы мы ввели понятие логиче- ской записи и обсудили организацию такой записи. В п. 7-1.1 были показаны преимущества группирования нескольких логи- ческих записей в одну физическую запись, или блок. В таком случае основная память и внешние запоминающие устройства обмениваются полными блоками, а ие индивидуальными запи- сями. Заметим, однако, что выполнение инструкции чтения или записи языка программирования высокого уровня, такого как ПЛ/1, затрагивает только одну логическую запись в соответствии со спецификациями в списке параметров инструкции. Например, рассмотрим инструкцию языка ПЛ/1: READ FILE (MASTER) INTO (EMPLOYEE), где EMPLOYEE описана как структура ПЛ/1 следующим об- разом: DECLARE 1 EMPLOYEE,"П 2 NAME, 3 SURNAME' CHART(20), 3 INITIALS CHAR (6), 2 SOC—INS # FIXED (9), 2 WAGE—PER __HR FIXED (5, 2), 2 CLASSFCTN CHAR (3); 20* 611
Рис. 7-4.1. Иллюстрация чтения в основную память блока записей В каждый момент времени, когда выполняется READ-инструк- ция, следующая запись из последовательного файла MASTER пересылается в область программы и назначается структуре EMPLOYEE. Однако в каждый момент времени, когда операция чтения или записи выполняется -для конкретного запоминающего устройства, фактически передается блок логических записей. Явное несоответствие между программными чтением и записью и командами чтения и записи, выполняемыми для конкретного устройства, разрешается использованием буфера между внешней памятью и областью данных программ. Буфер — это секция основной памяти, размер которой равен максимальному размеру блока логических записей, используемых программой. Программы управления данными используют буфер для «блокирования» и «разблокирования» записей. Для иллюстрации того, как посредством буфера обеспечивается блокирование и разблокирование записей, рассмотрим использо- вание последовательного файла MASTER как входного. При первом выполнении READ-предложения блок записей пересы- лается из внешней памяти в буфер. Затем первая запись в блоке пересылается в область данных программы, как это изображено на рис. 7-4.1. Прн каждом последующем выполнении READ- предложения следующая по порядку запись в буфере пересы- лается в область данных. Только после того, как в ответ на READ-предложен'ие все записи буфера перемещены в область данных, следующее выполнение READ-предложения вызывает пересылку блока от запоминающего устройства в буфер. Новые записи в буфере пересылаются в область данных в соответствии с описанной выше процедурой, и весь процесс повторяется для каждого вводимого блока. Аналогичным образом WRITE-предложение вызывает пере- сылку данных программы в буфер. Когда буфер (соответству- ющий блоку логических записей) заполняется, блок записывается 612
на внешнюю память непосредственно после предыдущего блока записей. Только что описанная техника буферизации обычно называется одиночной буферизацией. При мультибуферизации используется очередь буферов, как правило, контролируемых операционной системой. Необходимость использования более чем одного бу- фера возникает из-за задержки (порядка нескольких миллисе- кунд), необходимой для чтения или записи следующего блока. Эта задержка в выполнении программы при использовании одного буфера возникает после выполнения каждых п READ- или WRlTE-предложений, если коэффициент блокирования равен п. Одиако, при выполнении программ в системах, от которых тре- буется малое время ответа и где необходимо совмещать работу процессора с выполнением команд ввода-вывода, разумно устра- нить эту задержку за счет введения нескольких буферов. На рис. 7-4.2 изображена циклическая очередь из трех буфе- ров. При выполнении первого READ-предложения три буфера А, В и С заполняются тремя последовательными блоками, по одному блоку на буфер. После обработки всех записей в буфере А выпол- нение следующей READ-инструкции приводит к пересылке первой записи из буфера В в область программы. Одновременно опера- ционной системой выдается команда чтения, инициализирующая передачу в буфер А блока из последовательного файла на внеш- нем запоминающем устройстве. Последующие выполнения READ- инструкций над файлом MASTER вызывают последовательную пересылку записей из буфера В и затем из буфера С. Во время обработки записей в буфере С буфер А заполнен новым блоком с новыми записями, и происходит заполнение буфера В. Если процесс заполнения одного буфера новыми записями в целом сба- лансирован с процессом чтения записей из остающихся буферов, то обычно имеет место только одна задержка на чтение или запись во время первоначального заполнения буферов. Рис. 7-4.2. Система с тремя'буферами 613
RL Данные запаси 1 I - р, | Данные KL записи! в BL RL Данные записи 3 г В В. °) I) Рис. 7-4.3. Формат записей переменной длины: а — несблокированные записи; б — сблокированные записи В некоторых системах, таких как Система IBM 370, блоки ло- гических записей, составляющие последовательный файл, могут быть либо фиксированной длины (случай, рассматриваемый нами до сих пор), либо переменной длины. Блок переменной длины содержит записи переменной длины. Следовательно, неизвестно, сколько записей разместятся в блоке, и поэтому задается макси- мальная длина блока. Эта величина используется при оценке размера буфера, необходимого для размещения блока, в который система управления данными группирует столько записей, сколько это возможно. Рис. 7-4.3 показывает формат записей для сблоки- рованных и несблокированных записей переменной длины. За- метим, что BL (длина блока) и RL (длина записи) должны хра- ниться одновременно с каждым блоком и записью соответственно. Эти параметры необходимы при разблокировании записей во время выполнения инструкции READ. Максимальная длина блока зависит от запоминающего устрой- ства, -используемого для размещения файла. Для магнитных лент эта длина определяется максимальным пространством основ- ной памяти, которое можно отвести под буфер. Для дисковой па- мяти размеры блоков обычно ограничиваются емкостью дорожки. При использовании устройств с адресуемыми секторами блок соот- ветствует некоторому максимальному количеству секторов. 7-4.2. Обработка последовательных файлов Обсудив физическую организацию последовательного файла и особенности пересылки записей между областью про- граммы и файлом, изучим типы обработки, для которых более всего подходят последовательные файлы. Сериальной обработкой называется выборка записей одна за другой в порядке их физиче- ского размещения в файле. Простота сериальной обработки файла очевидна. Под последовательной обработкой понимается выборка записей в порядке возрастания ключа, или индексного элемента записи. Например, если файл MASTER с информацией о служа- щих упорядочен по их именам (скажем, запись с именем ADAMS первая, BAKER—вторая, ..., ZURCHER — последняя), то 614
последовательная обработка По именам эквивалентна сериальной обработке файла. При создании последовательных файлов их упо- рядочивают в основном по ключам, или индексным элементам, таким, как имя служащего, регистрационный номер студента или номер в магазинном каталоге. Ключом, или индексным элемен- том, должен быть элемент, по которому чаще всего осуществляется поиск при обработке файла. Для демонстрации важности выбора ключа допустим, что файл служащих MASTER упорядочен по номеру в системе со- циального страхования. Предположим, что требуется найти записи нескольких служащих, зная только их имена. Нахождение записи первого служащего, скажем, с именем ADAMS, сводится к простой сериальной обработке файла до тех пор, пока не появится запись с элементом имени ADAMS (при этом возможность суще- ствования одинаковых имен игнорируется). Рассмотрим поиск следующей записи, скажем, с именем BAKER. Так как позиция записи BAKER не имеет никакой связи с позицией записи ADAMS, то нет иной альтернативы, кроме повторения сериальной обработки с начала файла MASTER. В весьма редких случаях требуется только сериальная обра- ботка безотносительно к ключу, или индексному элементу, по которому упорядочен файл. Например, если мы должны увели- чить ставки на 2 долл./ч всем служащим и сделать соответству- ющие изменения в элементах, содержащих размер заработной платы, то не имеет никакого значения, упорядочен ли файл по имени или по номеру социального страхования. При последовательной обработке записи транзакций обычно группируются вместе (т. е. пакетируются) и сортируются согласно значению того же индексного элемента, что и записи файла. Каждая последующая запись файла читается, сравнивается с по- ступающей записью транзакции и затем обрабатывается спосо- бом, обычно зависящим от того, меньше, равно или больше зна- чение индексного элемента записи, чем значение индексного эле- мента в транзакции. Последовательная и сериальная обработки наиболее эффек- тивны при необходимости обработки большей части записей файла. Поскольку каждая запись в файле сканируется, то при обработке имеет смысл группировать достаточно большое количество тран- закций. При добавлении записей в файл необходимо создать но- вый файл, если записи не пишутся в его конец. В большинстве систем отсутствуют средства, обеспечивающие непосредственное расширение последовательного файла. Для удаления записей из последовательного файла можно их метить как «удаленные» во время обновления файла. Однако эта процедура приводит к по- явлению «фиктивных» записей в файлах, и тогда неэффективно используется память и увеличивается время обработки. Обычно записи, которые должны быть удалены, физически исключаются путем создания нового файла. Хотя создание нового файла время 615
от времени становится необходимым, делать это нужно как можно реже. Для облегчения описания обработки файлов необходимо рас- ширить алгоритмическую систему обозначений так, чтобы можно было бы выразить имеющиеся в большинстве языков программи- рования средства манипулирования файлами: файлу дается иден- тифицирующее его имя, которое нельзя индексировать или квали- фицировать указателем. Обозначим этот идентификатор через синтаксический элемент (имя файла). Первым действием перед обработкой файла является резервирование памяти под буфер. Операции, которые можно совершать иад файлом, обычно при- нято описывать заранее. Следует знать, будет ли использоваться файл для ввода, вывода или для того и другого сразу (для обнов- ления). Такое предварительное ограничение служит мерой пре- досторожности, особенно, если нежелательно случайно записать что-то в файл, используемый для ввода. Соответствующие функции могут быть выражены в нашей алгоритмической нотации следу- ющим образом: 1. Открыть входной файл (имя файла) 2. Открыть выходной файл (имя файла) 3. Открыть обновляемый файл (имя файла) Если файл описан как входной, то разрешается только читать записи; в выходной файл записи можно только писать; для обнов- ляемого файла записи можно как читать, так и переписывать, а также можно писать в конец файла. Заметим, что в последова- тельном файле, размещаемом на ленте, записи нельзя перезапи- сывать. После завершения обработки файла можно освободить память под буфера, написав Закрыть файл (имя файла) В системах с разделением времени и мультипрограммных систе- мах предложения открытия и закрытия файла служат также для предохранения от одновременного доступа к нему нескольких пользователей. Программа не может открыть файл, уже открытый другой программой, для записи до тех пор, пока эта другая про- грамма не закроет файл. Некоторые файловые системы позволяют нескольким пользователям одновременно' читать информацию из файла. Для последовательного файла специфицированы две основные операции: читать и писать. Объектом в предложении чтения или источником в предложении записи должен быть идентификатор переменной или структуры, соответствующий записи в файле. Обозначим этот идентификатор через (имя записи). Предложение чтения имеет вид Читать из файла (имя файла) в (имя записи) 616
и предложение записи Писать (имя записи) в файл (имя файла) Третья операция, применяемая для последовательных файлов, размещаемых иа устройствах прямого доступа, записывается так: -Перезаписать (имя записи) в файле (имя файла) Эта операция используется только при условии, что файл открыт для обновления. При ее выполнении запись помещается иа место самой последней прочитанной записи. Приведем алгоритм, иллюстрирующий основные операции, применяемые при последовательной обработке файлов. Предпо- ложим, что требуется обработать последовательный файл с име- нем MASTER. Записи этого файла будем читать в структуру MRECORD, состоящую из MKEY и MDATA. Записи отсорти- рованы в порядке возрастания значения MKEY. Предполагается отсутствие повторяющихся ключей. Пусть записи транзакций помещены в последовательный файл с именем TRANSACTION, чьи записи можно читать в струк- туру TRECORD, состоящую из элементов TKEY, TDATA и CODE. Записи транзакций отсортированы в порядке возраста- ния значения TKEY, и в этом случае также предполагается от- сутствие повторяющихся значений ключа. Элемент CODE прини- мает одно из трех возможных значений: 1, 2 или 3, имеющих сле- дующую интерпретацию: 1 — обновить запись в файле MASTER с ключом TKEY, используя данные TDATA. 2 — добавить запись в файл MASTER с ключом TKEY и данными TDATA. 3 . удалить из файла MASTEP запись, имеющую ключ TKEY. Алгоритм 1 SEQUENTIAL-PROCESSING. Заданы файлы MASTER и TRANSACTION. Необходимо сформировать новый файл NEW_MASTER, выполняя операции, специфированные в записях транзакций. Структура NRECORD имеет формат, идентичный структуре MRECORD, и состоит из NKEY и NDATA. 1. [Открытие файлов]. Открыть входной файл MASTER, входной файл TRANSACTION и выходной файл NEW___MASTER. 2. [Чтение следующей транзакции. ] Читать из файла TRANSACTION в TRECORD. Если конец файла TRANSACTION, то перейти к шагу 7. 3. [Чтение записи из обновляемого файла. ] Читать из файла MASTER в MRECORD. Если конец файла MASTER, то повто- рять до конца файла TRANSACTION: если CODE—2 (добавление записи), то установить NDATA <- «-TDATA и NKEY<-TKEY и'писать NRECORD в файл NEW—MASTER; иначе, печатать TRECORD и сообщение об ошибке. Читать из файла TRANSACTION в TRECORD. . Перейти к шагу 8. 617
4. [Пересылка старой записи.] Если MKEY < TKEY, то писать MRECORD в файл NEW___MASTER и перейти к шагу 3, 5. [Обновлениеили удаление записи.] Если MKEY — TKEY, то если CODE «= 1 (обновление записи), то установить MDATA <— TDATA и писать MRECORD в файл NEW.. MASTEP; иначе (удаление или ошибка), если CODE 3, то писать MRECORD в файл NEW—MASTER и печатать TRECORD и сообщение об ошибке. Перейти к шагу 2. 6. [Добавление новой записи. ] Если CODE = 2 (добавление записи), то установить NDATA *— TDATA, NKEY TKEY и писать NRECORD в файл NEW___MASTER; иначе, печатать TRECORD и сообщение об ошибке. Читать из файла TRANSACTION в TRECORD. Если не конец файла TRANSACTION, то перейти к шагу 4. 7. [Пересылка оставшихся записей из файла MASTER. ] Повторять до конца файла MASTER: писать MRECORD в файл NEW___MASTER и читать из файла MASTER в MRECORD. 8. [Закрытие файлов. ] Закрыть файл MASTER, файл TRANSACTION и файл NEW—MASTER. На примере алгоритма SEQUENTIAL—PROCESSING можно продемонстрировать сложности, возникающие при обновлении файла MASTER, согласно транзакциям, содержащимся в файле TRANSACTION. Так как любой нз файлов может окончиться ранее другого, то в шагах 3 и 7 предусмотрены действия, необ- ходимые для завершения сканирования файлов TRANSACTION и MASTER соответственно. Диагностируются ошибки двух ти- пов. В шаге 5, если значения ключей TKEY и MKEY равны, но не специфировано обновление или добавление, то считается, что либо используется непредусмотренный код операции, либо делается ошибочная попытка добавить запись с уже имеющимся ключом. Если TKEY когда-либо оказывается меньше, чем MKEY (шаги 3 и 6), но CODE не равен 2, то предписание обновить или удалить несуществующую запись игнорируется. Требование упорядочения записей в последовательном файле по ключам необязательно, если файл сканируется для выполнения одной и той же операции над каждой записью. Отметим основные моменты, связанные с последовательной обработкой последовательных файлов. 1. Последовательная обработка наиболее предпочтительна тогда, когда можно сгруппировать большое число транзакций для одного прогона файла. 2. Необходимо создать новый файл при добавлении даже одной записи и при большом количестве удалений. 3. Нельзя ожидать быстрого ответа при обработке транзак- ции или пакета транзакций,
4. Требование упорядочения записей в последовательном файле по какому-либо ключу необязательно, если файл скани- руется для выполнения одной и той же операции над каждой записью (т. е. при сериальной обработке). Из следующих параграфов станет ясно, что существуют не- которые другие файловые организации, которые значительно лучше, чем последовательные файлы, удовлетворяют таким тре- бованиям, как малое время ответа и возможность обработки инди- видуальных транзакций. Если последовательный файл разме- щается на устройстве прямого доступа (таком, как диск или ба- рабан), можно значительно уменьшить время получения ответа. Этого можно добиться путем введения процедуры внешнего двоич- ного поиска, позволяющей быстро обнаружить нужную дорожку, и тем самым избежать последовательного просмотра записей. В некоторых системах, однако, пользователь не всегда распола- гает информацией, касающейся адресов дорожек. 7-4.3. Последовательные файлы в ПЛ/1 В оставшейся части этого параграфа дано описание последовательных файлов в языке ПЛ/1. Это делается главным образом с целью иллюстрации средств, необходимых для ра- боты с подобными файлами в конкретном языке программирова- ния. Кроме того, этот материал служит основой для обсуждения применений файлов в пп. 7-5, 7-7 и 7-9. Традиционно при обра- ботке файлов чаще всего используется КОБОЛ. Л!ы будем ис- пользовать ПЛ/1, чтобы сохранить для всех программ книги один и тот же язык программирования. К тому же, почти все средства работы с файлами в ПЛ/1 имеют свой точный эквивалент в КОБОЛе. В ПЛ/1 имеются последовательные файлы двух типов: потоко- ориентированные файлы (имеющие атрибут STREAM) и записео- риентированные файлы (с атрибутами CONSECUTIVE и RECORD). Мы предполагаем, что читатель знаком с основными моментами потокоориентированной передачи данных в ПЛ/1, т. е. с вводом-выводом, управляемым редактированием, списком и данными, и использующим стандартные системные файлы SYSIN и SYSPRINT. Мы обсудим и некоторые менее известные, но не менее важные свойства потокоориентированного ввода- вывода. Знание читателем записеориентированного ввода-вывода не предполагается, поэтому этот тип передачи данных рассма- тривается более подробно и сравнивается с потокоориентирован- ным вводом-выводом. Потокоориентированный файл, как это уже следует из назва- ния, можно трактовать как непрерывный поток информации, «втекающий» в область программ при выполнении GET-предло- жений и «вытекающий» из области программы при выполнении PUT-предложений. Этот поток информации размещается в файле в литерной форме (например, в коде EBCDIC). Только из сообра- 619
Жёний удобства й эффективности информация в файле сгрупп1и рована в физические записи или блоки, однако, как это вскоре будет показано, содержимое блоков не имеет отношения к списку аргументов GET- и PUT-предложений. Точно так же, как любой массив или структура в ПЛ/1, файл является объектом, содержащим данные. Следовательно, все файлы должны быть описаны, исключение составляют два стан- дартных потокоориентированных файла — SYSIN и SYSPRINT. Рассмотрим следующее описание файла: DECLARE TRIANGL FILE STREAM INPUT ENVIRONMENT (F (20)); здесь TRIANGL описан как потокоориентированный входной файл (т. е. для этого файла можно употреблять только GET- предложеиия). Атрибут ENVIRONMENT, или сокращенно ENV, содержит список параметров, необходимых программам управле- ния данными операционной системы. В данном случае F (20) указывает, что файл состоит из блоков фиксированной длины и размер блока равен 20 символам, или байтам. Далее в этой главе будут использованы и другие параметры атрибута ENV. В ПЛ/1 открытие и закрытие файлов необходимы для полу- чения и возврата буферов памяти операционной системе. Этой цели служат OPEN- и CLOSE-предложения. Например, OPEN FILE (TRIANGL); CLOSE FILE (TRIANGL); В качестве примера простой программы, в которой используется файл TRIANGL, не являющийся стандартным, рассмотрим про- грамму на рис. 7-4.4. Программа читает из файла три значения $TREAM_EG: PROCEDURE OPTIONS (MAIN)i DECLARE TRIANGL FILE STREAM INPUT ENV(F(20))e (А, В, C) FLOAT BINARY! ON ENDFILE (TRIANGL) STOP; OPEN FILE (TRIANGL); DO WHILE (’1’B); 2* DO UNTIL END OF FILE ’ GET FILE (TRIANGL) LIST (А, В, С)? IF (Д4-В) <C | (A + С) < В I (В + C) < A THEN PUT SKIP FILE (SYSPRINT) LIST (А, В, C, 'DOES NOT FORM A TRIANGLE’); ELSE PUT SKIP FILE (SYSPRINT) LIST (A, 8, C, ’FORMS A TRIANGLE’); end; END STREAM_EG; Рис. 7-4.4. Пример программы обработки потокоориентированного файла 620
a Содержимое буфера. i Содержимое буфера Z Рис. 7-4.5. Представление: а — блока в файле TRIANGLE; б — содержимого двух буферов после чтения п них двух блоков файла. Вся информация хранится в литерном представлении для А, В н С и проверяет, могут ли отрезки соответствующей длины служить сторонами некоторого треугольника. Размещение информации в файле TRIANGL для этого примера показано на рис. 7-4.5, а. При первоначальном выполнении пред- ложения GET две первые физические записи пересылаются в бу- феры, зарезервированные при выполнении OPEN-предложения. Для потокоорнентированного файла автоматически отводится два буфера — необходимость этого станет понятной в ходе даль- нейшего обсуждения. Символьная информация в первом буфере (см. рис. 7-4.5, б) интерпретируется ответственной за потокоориен- тированный ввод-вывод подсистемой ПЛ/1. Информационные объекты, соответствующие элементам А, В и С, изолированы друг от друга (т. е. промежуточные пробелы трактуются как раз- делители). Эти объекты (5.0, 3.0 и 4.0) преобразуются из литер- ных форматов в представления с плавающей точкой. Представле- ния с плавающей точкой назначаются ячейкам памяти для хра- нения величин А, В и С, которые располагаются в области про- граммы. Таким образом, для потокоориентированного ввода- вывода буфер используется не только для сглаживания передачи данных при вводе-выводе, но и как промежуточная область, из которой осуществляется преобразование литерного представления. После первого выполнения GET-предложения системный ука- затель устанавливается на точке а буфера 1. По мере интерпрета- ции информации в буфере этот указатель перемещается вдоль записи, указывая на литеру, которая должна интерпретироваться следующей. Второе выполнение GET-предложения приводит к ин- терпретации следующего набора из трех элементов данных в ин- формационном потоке. Эти три элемента пересекают границу блоков, т. е. размещаются в разных буферах. Однако при нали- чии системы из Двух буферов нет никаких проблем ни в интерпре- 621
тации, ни в преобразовании данных. Системный указатель просто перемещается из конца одного буфера в начало другого. Таким образом, из графической иллюстрации становится понятным, почему не требуется связывать количество данных, помещаемых в блок, со списком аргументов GET-предложения и почему для потокоориентированного ввода-вывода необходима система из двух буферов. Далее мы увидим, что в случае записеориентиро- ванного ввода-вывода дело обстоит иначе. Предложение PUT предназначено для выполнения действий, обратных действиям для предложения GET. При выполнении PUT-предложения величины в списке аргументов преобразуются к их литерному представлению при пересылке из области про- граммы в буфер. После заполнения буфера его содержимое пересылается в со- ответствующий файл, размещаемый на устройстве внешней па- мяти. Литеры конца блока (межблочные промежутки для магнит- ных лент) разделяют различные блоки. При закрытии файла за последним блоком помещается маркер конца файла (EOF), а также некоторая другая информация (часто называемая трейлерной информацией). Потокоориентированные файлы целесообразно использовать в основном как файлы, связанные с офлайновыми последователь- ными устройствами, такими как устройства ввода с перфокарт, построчно-печатающие устройства и магнитные ленты, поскольку такие устройства ориентированы на символьную информацию. Из этого не следует, что потокоориентированные файлы не могут быть размещены на устройствах прямого доступа, таких как диски или барабаны; как правило, они там и размещаются. Однако файлы на устройствах прямого доступа чаще всего соз- даются операционной системой как промежуточные, перед выда- чей на печатающее устройство или после ввода с перфокарт. Подобные файлы позволяют устранить проблему замедления выполнения программ, возникающую в случае ожидания каждой программой ввода отдельных карт или печати отдельных строк. Потокоориентированные файлы так часто используются для хранения информации, предназначенной для печати, что в ПЛ/1 имеется специальный тип потокоориеитированных файлов, назы- ваемый PRINT-файлы. PRINT-файлы позволяют пользователю помещать в первый байт каждой записи специальную ANS-л итеру управления печатающим устройством для указа- ния необходимости пропуска строк или перехода на новую страницу. В ПЛ/1 имеется много типов запнсеориентированных файлов (CONSECUTIVE, INDEXED, REGIONAL (1), REGIONAL (2), REGIONAL (3) и TRANSIENT). В данном пункте детально будут рассмотрены только файлы с атрибутом CONSECUTIVE. Перед тем как приступить к этому обсуждению, выделим некоторые об- щие свойства записеориентированной передачи данных, позволя- 622
ющие ясно отличать записеорнентированные файлы от потокоо- риеитированных. При записеорнентнрованной передаче данные в файле рассма- триваются как коллекция записей, хранимых в любой внутрен- ней форме (например, в форме чисел, объявленных с атрибутами FLOAT или FIXED, в форме битовых или литерных строк и т. д.), допускаемой программой на ПЛ/1. Таким образом, во время за- писеориентированного ввода-вывода преобразование данных не требуется. При вводе READ-предложение, например такое, как READ FILE (MASTER) INTO (EMPLOYEE); вызывает пересылку единственной записи в программную пере- менную EMPLOYEE с точным сохранением формы, в которой она хранится в файле. При выводе WR 1ТЕ-предложение WRITE FILE (MASTER) FROM (EMPLOYEE); или REWRITE-предложение (описываемое позже) вызывает пере- дачу единственной ‘ записи из программной переменной EMPLOYEE в файл MASTER точно в том виде, в каком она су- ществует во внутреннем представлении. Из предыдущих примеров предложений языка ПЛ/1 стано- вится очевидным, что программа обменивается с файлом запи- сями через единственную программную переменную. Программ- ная переменная может быть простой, индексированной или чаще всего структурной переменной. Хотя предложения ПЛ/1, исполь- зуемые в записеориентированной передаче, связаны с записями (определяемыми переменными программы), данные фактически пересылаются в файл и из него блоками записей. Блоки могут содержать записи фиксированной или переменной длины в форме, описанной в п. 7-1 (в частности, см. рис. 7-1.1). Следовательно, для записеориентнрованного ввода-вывода имеется определенная связь между логической записью и блоком, или физической за- писью. В блоке находится целое число логических записей, при этом записям не позволяется пересекать границы блоков. Си- стема IBM 370 допускает исключение из этого правила для слу- чая необычно больших записей. А именно, если запись настолько велика, что ее невозможно или неудобно размещать в одном блоке, то части записи могут размещаться в нескольких блоках. На практике необходимость в этом возникает редко. Для понимания того, как последовательная обработка осу- ществляется в ПЛ/1, изучим программу, представленную на рис. 7-4.6. Программа в основном следует логике алгоритма SEQ UE NT IA L._ PROCESSING, в котором файл MASTER обнов- ляется согласно коду в записи транзакции. Программа ориенти- рована иа обработку платежных ведомостей. Записи в файле MASTER и в карточном файле транзакций SYS1N предполагаются упорядоченными по номеру в системе социального страхо- вания. 623
В программе оба файла 'MASTER и NEWMTR объявлены с атрибутами RECORD и CONSECUTIVE. Атрибут CONSECUTIVE указывает, что записи в файле размещены в смежных областях внешней памяти.. Файлы, объявленные как RECORD SEQPROC? PROCEDURE OPTIONS(MAINI 5 /» A PROCEDURE WHICH READS IN TRANSACTION CARDS AND APPLIES THEM TO A MASTER EMPLOYEE FILE. THE TRANSACTION CARDS ARE OF THE FORM <TYPE> <SURNAME> <INITIALS> <SOCIAL INS #> <WAGE/HR> <CLASSIFICATN> WHERE CC 1 <TY₽E>? 1=DELETE, 2=ADD, 3=UPDATE CC 2-22 <SURNAME> CC 22-27 «INITIALS? CC 20-36 «SOCIAL INSURANCE NUMBER? CC 37-41 «WAGE PER HOUR> CC 42-44 «WORK CLASSIFICATION CODE> *, DECLARE 1 EMPLOYEE, 2 NAME, 3 SURNAME CHAR(20), 3 INITIALS CHAR(6), 2 SOC-TNS# FIXED19), 2 WAGE„PER_HR FIXED(5,2>, 2 CLASSIFICATION CHAR(3)? DECLARE 1 TRAN_REC, 2 NAME, 3 SURNAME CHAR(20), 3 INITIALS CHAR(6t, ' 2 SOC.lNS# FIXEDI9I, 2 WAGE_PER_HR FIXED(5,2), 2 CLASSIFICATION CHAR(3); ' DECLARE MASTER FILE RECORD ENV (CONSECUTIVE F(370,37>), NEWMTR FILE RECORD ENV (CONSECUTIVE F(370,371), CODE FIXED)1), L(3> LABEL, (MASTERDONE, SYSINDONEJ BIT(l) INITIAL)’0’Bl ? ON ENDFILE(SYSIN) CALL COMPLETE; ON ENDFILE(MASTER) CALL EXTEND? OPEN FILE (MASTER) INPUT? OPEN FILE (NEWMTR) OUTPUT; /* INITIAL READING OF MASTER ANO TRANSACTION RECORDS */ READ FILE(MASTER) INTO (EMPLOYEE); GET FILE(SYS IN) EDIK CODE,TRAN„RECI(CDL(1),F(1),A(201,A(6),F(9), F(5),A(3)); REPEAT: DO WHILEI’l’BJ; 7» REPEAT UNTIL ДМ END OF FILE CONDITION *f DO WHILE (EMPLOYEE.SOC_INS# < TRAN_REC.SOC.INS#>; WRITE FILE (NEWMTRI FROM (EMPLOYEE)? READ FILE(MASTER) INTO (EMPLOYEE); END; GO TO L(CODE); L(l): /* DELETE */ IF EMPLOYEE.SOC-INS# -.= TRAN_REC.SOC_INS# THEN DO? PUT SKIP LISTl’ERROR* RECORD TO BE DELETED DOES NOT EXIST’) WRITE FILE (NEWMTR) FROM (EMPLOYEE); END; READ FILE(MASTER) INTO (EMPLOYEE); J GO TO NEWCARD; L(2 ) ? 7* ADD *7 WRITE FILE (NEWMTR) FROM (TRAN_REC); Рис. 7-4.6.1.Обработка последовательного файда 624
63 TO NEUCARD; ИЗ); /ft UPDATE */ IF EMPLOYEE. SOC-1NS# TRAN_REC . SQC_IN$# then do; put skip listi’errcr* record to be updated does not exist*»? WRITE FILE (NEWMTR) FROM (EMPLOYEE); ENO; ELSE WRITE FILE (NEWMTR) FROM (TRAN.REC); READ FILE(MASTER) INTO (EMPLOYEE); NEWCARD: GET FILEISYSIN) EDIT(CEDE,TRAN.REC)(COL(1),F<11,A(20)»A<6), F(9),F(5>»A(3») 5 END REPEAT; COMPLETE: PROCEDURE; IF MASTERDONE THEN DO; CLOSE FILEI MASTER ). FILEINEWMTR) : STOP: END; SYS I NOONE - 4’B; DO WHILE!'l’B»5 /» UNTIL END OF MASTER FILE */ WRITE FILE (NEWMTR) FRCM (EMPLOYEE); READ FILE(MASTER) I NYC (EMPLOYEE); END; END COMPLETE; EXTEND: PROCEDURE; IF SYS I NOONE THEN DO; CLOSE FILE(MASTER», FILE (NEWMTR); STOP; END; MASTERDONE = ’1’B? DO WHILEPL’B); /* UNTIL END OF SYSIN FILE */ IF CODE = 26 EMPLOYEE.SOC_INSP < TRAN_REC.SOC„INS# THEN DO; WRITE FILE (NEWMTR) FROM (TRAN_REC); EMPLOYEE.SOC_INSfi = TRAN_REC.SOC_1NS# ! ENO ; ELSE PUT SKIP LIST(’ERROR - ILLEGAL ADDITION ATTEMPTED1); GET FILUSYSIN» EOIT(CODE,TRAN_REC) (С01Ш ,Ft I) ,A(2O> ,M6> ,F(9h F(5),A(3»>; END; END EXTEND; end seqproc; Рис. 7—4.6. Продолжение CONSECUTIVE, являются последовательными, поскольку для доступа к отдельной записи необходимо прочесть все предыдущие. Теперь укажем на некоторые важные технические моменты программы на ПЛ/1. Во-первых, для стандартного входного файла SYSIN, содержащего карты с транзакциями, не требуется явных OPEN- и CLOSE-предложений •— открытие и закрытие файла автоматически выполняется операционной системой. Во-вторых, параметры F (370, 37) атрибута ENV указывают, что для обоих файлов MASTER и NEWMTR блоки состоят из 10 записей, по 37 байт каждая. Отсюда вытекает, что блок и буфер имеют длину 370 байт. Остальная часть программы очевидна при условии отчетливого ’ понимания основных моментов алгоритма SEQUENTIAL__PROCESSING. Следует заметить, что в DECLARE-предложениях, описыва- ющих файлы, присутствует не вся информация, необходимая Системе для полной идентификации файлов MASTER и NEWMTR. №
Помимо размеров блока и длины логической записи, полное опи- сание должно включать такую информацию, как объем про- странства, требуемый файлу, диспозиция файла (например, соз- дается ли файл в данный момент или он уже создан), тип устрой- ства и номер тома, на котором располагается файл. Такая инфор- мация обычно содержится вне программы в предложениях языка системных команд определенной вычислительной установки, на которой выполняется программа. Например, в Системах IBM язык системных команд называется языком управления зада- ниями JCL, и файлы вне программы описываются DD-предло- жениями этого языка. Следующие DD-предложения вместе с ин- формацией в параметрах атрибутов ENV описывают файлы MASTER п NEWMTR: //GO.MASTER DD DISP = (OLD, KEEP), UNIT = SYSDA, // VOL = SER = USER01, ‘ Ц SPACE = (TRK, 5) //GO.NEWMTR, DD DISP = (NEW, KEEP), Ц UNIT - SYSDA, VOL = SER = USER01, Ц SPACE = (TRK, 5) В дальнейшем мы не будем снабжать все программы этой главы системной управляющей информацией (например, DD- предложеииями), так как эта информация может меняться при переходе от одной вычислительной установки к другой. При написании программы на ПЛ/1 с использованием файлов нужно обратиться за консультацией к руководству програм- миста из документации для конкретной вычислительной уста- новки. Последним предложением ПЛ/1, которое мы опишем, закан- чивая этот пункт, является предложение REWRITE. При жела- нии просто обновить файл без создания или удаления записей мы можем это сделать, используя REWRITE-предложение при условии, что файл размещается иа устройстве прямого доступа. На рис. 7-4,7 представлен фрагмент программы на языке ПЛ/1, иллюстрирующий, как можно модифицировать файл MASTER, ие создавая при этом нового файла и предполагая только допу- стимость транзакций обновления. Подразумевается, что объявле- ния для MASTER, EMPLOYEE и TRAN______REC идентичны пред- ставленным на рис. 7-4.6. Команда REWRITE предназначена для перезаписи последней прочитанной записи. Легко увидеть, как, используя такое средство, можно обновить файл MASTER без создания нового файла. Для углубления понимания последовательных файлов, рас- смотрим в следующем параграфе программу для небольшой пла- тежной системы. В этой программе используются многие из кон- цепций, обсуждавшихся в данном параграфе. 62G
A-Example io .«чоы update of a file without creating a new pile */ /* UPDATES ISA SET OF NEW EMPLOYEE RECORDS TO REPLACE CORRESPONDING */ /» RECORDS IN THE FILE ’MASTER’. IN BOTH FILES, RECORDS MUST BE «/ /* ORDERED BY SOCI AL- INSURANCE-NUMBER. */ UPDATE: PROCEDURE OPTIONS (MAIN); DECLARE MASTER FILE SEQUENTIAL RECORD ENV (CONSECUTIVE F(370,37)); DECLARE UPDATES FILE STREAM; DECLARE 1 EMPLOYEE, 2 NAME, 3 SURNAME CHAP(2C)-, 3 INITIALS CHAR<6), 2 SOC.INS# FIXED(9), 2 WAGE_PER_HR FIXED15,2), 2 CLASSIFICATION CHAR13K DECLARE 1 TRAN_REC, 2 ЛАМЕ, 3 SURNAME CHAR<201, 3 INITIALS CHAR(6J, 2 SOC_INS# FIXED(9), 2 WAGE_PER„HR F1XED(5,2», 2 CLASSIFICATION CHAR<3); DECLARE OLD_SOC_INS* FIXEDI91 INIT(000000000J: ON ENDFTLE (UPDATES! BEGIN: CLOSE FILE (MASTER); CLOSE FILE (UPDATES»; stop ; END; ON ENDFILE "(MASTER! BEGIN; put skip list cend of master file.’»; CLOSE FILE (MASTER); Close file (updatfsj- stop; END; OPEN FILE (UPDATES) INPUT; OPEN FILE (MASTER) SEQUENTIAL UPDATE: /* READ TO START UP «/ READ FILE (MASTER) INTO (EMPLOYEE): DO WHILE (‘1’B); /* UNTIL END OF UPDATES FILE «Г GET FILE (UPDATES) EDIT (TRAN-REC) <COL 11) , A(20), A(6).F19)« F ( 5,2 ), Д (3 )) S IF TRAN_REC.SOC_!NS# < CLO„SOC_tNS« IHEN RUT SKIP EDIT (’ERROR* OUT OF ORDER CARO, NUMBERS », , . TRAN_REC.SOC_lNS#,’. NAME; ’,TRAN_REC«SURNAME) <A,F19! ,A-.A); ELS^ DCS OLD_$OC_TNS# -= TRAN_p.EC,S0C_INSl9; DO WHILE (EMPLOYEE.SOC^INSff < TRAXl_REC. SDC_tNS!/l,S READ FILE (MASTER) INTO (EMPLOYEE); END; -s IF (EMPLOYEE.SOC_INS« = TRAN.REC.SOC_INS«) THEN REWRITE FILE (MASTER) FROM (TRAN_REC)5 ELSE PUT SKIP EDIT (’ERROR* NON-EXISTENT RECORD. NAME:’, TRAN_REC.SURNAME,’. NUMBER: ',TRAN_REC.SOC_INSf (3 A,F(9))5 ЕДО? END; END UPDATE; 7-4.7. Обновление последовательного файла 627
Упражнения к и. 7-4 1. Предположим, [вы создаете ’последовательный файл, содер- жащий записи, описывающие новые поступления в библиотеку. Информация, касающаяся новой книги, отыскивается по ключу, в качестве которого исполь- зуется фамилия автора. Насколько существенны преимущества применения файла, упорядоченного по фамилии автора, если: а) запросы на поиск не паке- тируются и б) имеет место пакетирование, запросов? 2. Предположим, мы должны создать последовательный файл, который содержит записи с детальным описанием различных курсов, предлагаемых сту- дентам института. Номера курсов могут быть использованы в качестве ключа для поиска в файле. Примерами номеров курсов являются СМРТ 181, MATH 222 и PHYS 111. Имеет ли какое-нибудь преимущество упорядочивание файла по номеру курса яри условии, что: а) запросы, на поиск ие пакетируются и б) поиско- вые запросы пакетируются? Считайте, что почти все запросы, касающиеся кур- сов, содержат допустимые ключи (т, е. ключи, для которых записи существуют). Не можете ли вы предложить лучший по сравнению с упорядочением по номерам курсов способ упорядочения записей для ситуаций, в которых поисковые запросы ие пакетируются? 3. Опишите основные различия между потокоориентироваиным и записе- ориеитированным вводом-выводом в ПЛ/1. В каких случаях нужно использо- вать каждый из них? 4. Допустим, что вы должны разработать н реализовать небольшую систему для получения платежных ведомостей. Система содержит следующую инфор- мацию: а) иомер служащего (5 цифр); б) зарплата служащего (5 цифр); в) номер в системе социального страхования (9 цифр); г) сумма освобождения от налогов (5 цифр); д) вклад в кассу взаимопомощи (3 цифры); е) плата за стоянку автомобиля (2 цифры); ж) членские взносы (3 цифры); з) имя (до 37 букв). Изменение информации о каждом служащем осуществляется посредством транзакций, которые поступают в систему во время получения платежных чеков (т. е. раз в месяц). Требуется обеспечить соответствующую реакпию на три типа транзакций: а) транзакция добавления содержит всю необходимую информацию о слу- жащем, иапример, ADD 67823 14400 708312694 01925 320 35 000 LISTOE, A.D.; б) транзакция удаления содержит только номер служащего, иапример, DEL 67823; в) транзакция обновления содержит номер служащего и явную идентифи- кацию изменяемого элемента записи, например, UPD 67823 PARKING = 48 ТАХ—ЕМР •= 02400. В конце месяца система должна печатать платежные чеки для каждого слу- жащего, включая тех, информация о которых была только что введена или уже исключена. Платежный чек состоит из двух частей — собственно чека и бюлле- теня чистого дохода (рис. 7-4.8). Вычисление подоходного налога основано на использовании градуирован- ной шкалы, которую можно аппроксимировать с помощью следующей формулы (N является доходом, с которого вычисляется налог, т. е. N — месячная зар- плата —• освобождение от налогов): Подоходный налог — (N/250 + 16) %. 628
J. В. REGIONAL BANK 3972-9th AVE -Лидия 1 /р 7Б MONTREAL. P. Q PAY TO THE ORDER OF______________A. D. Litto»________ $ 889.34 EIGHT HUNDRED EIGHTY NINE........ ...... .34 DOLLAR DATE ое]о1 |tb tot. EXEmp $2400 SALARY ?М400 SOCIAL INSURANCE NO. EMP. NO. 703-312^84 6Я13В REGULAR PAY INCOME TAX GROUP INSURANCE PENSION PARKING DUES 1200ДО 200.00- 26.66- 80.00- 4.00- .00 889.34 Рис. 7-4.8. Образец выходного документа платежной системы Следовательно, подоходный налог, уплаченный A.Listoe, ((1200 — 200)/250 + 16) % X (1200 — 200) = 200 долл. Пенсионные выплаты равны 8 % заработка после вычета подоходного, налога. Спроектируйте только что описанную платежную систему в деталях, а за- тем реализуйте ее. Фаза проектирования должна включать детальное обсужде- ние записей транзакций, платежной записи служащего и форматов выходных записей. Часть информации на платежных чеках должна печататься в стилизо- ванной форме, даже если для этого придется заранее подготовить специальные бланки в какой-нибудь производственной системе. 7-5. НЕБОЛЬШАЯ СИСТЕМА ОПЛАТЫ СЧЕТОВ Структура этого параграфа, равно как и других пара- графов настоящей главы, посвященных прикладным задачам, будет следующей: анализ проблемы, проектирование системы (в частности, определение необходимой для системы структуры файлов) и описание возможной реализации системы. Каждая из этих трех фаз разработки системы описывается в отдельном пункте. Было бы идеально, если разработка систем осуществлялась строго в соответствии с методом сверху-вниз, при котором первоначально проводится полный анализ системы, затем проектирование и, наконец, осуществляется реализация. При обсуждении приклад- ных задач в данной .главе может показаться, что эта идеальная схема легко осуществима. Однако на практике так почти никогда не бывает. В процессе разработки системы обычно совершаются итерации между фазами анализа и проектирования, а затем между фазами проектирования и реализации, пока не будет создан окон- чательный вариант системы. (В процессе разработки некоторых 629
систем имеют место итерации даже между фазами анализа и реа- лизации. Нет ничего хуже этого, и необходимо избегать подобного подхода!). Непосредственно данный параграф касается анализа, проекти- рования и реализации простой системы оплаты счетов. Будучи включенным в основном для иллюстрации применений последова- тельных файлов, пример интересен и сам по себе, поскольку каж- дому приходится иметь дело с подобной системой. 7-5.1. Анализ системы Некая компания (Company of Canada, Ltd) имеет сеть небольших универсальных магазинов. Компания решила предложить своим покупателям сервис счетов-поручительств с ис- пользованием кредитных карточек. Товарный чек, содержащий иомер кредитной карточки, наименование товара и его стоимость, заполняется кассиром во время покупки. Товарный чек одно- временно служит для^ восстановления кредита покупателя в слу- чае возврата товара. Кредитная карточка содержит шестизнач- ный номер счета покупателя, а штамп магазина указывает дату сделки. Практически ежедневно товарные чеки посылаются в цен- тральное бюро компании для обработки. Центральное бюро рассы- лает ежемесячные бюллетени всем покупателям, имеющим кре- дитные карточки. Все оплаты по счетам-поручительствам посту- пают в центральное бюро. Компания начисляет полуторапроцент- ное пени на сумму, неоплаченную покупателем в течение следу- ющего после покупки месяца. Компания нашла выгодным автоматизировать свою систему оплаты счетов. Компанию нельзя отнести к числу крупных. Пред- варительный анализ показал невыгодность приобретения соб- ственного компьютера. Поэтому были заключены соглашения об аренде у другой фирмы некоторого объема дисковой памяти и аренде раз в месяц необходимого количества машинного времени. В результате каждого ежемесячного расчета компании необ- ходимо получать следующую информацию. 1. Список всех покупателей, имеющих кредит. Список дол- жен быть упорядочен по номеру счета и содержать для каждого номера счета имя, адрес и текущий баланс. 2. Месячный отчет, который должен включать общий баланс неуплаченной компании суммы, сумму покупок за месяц, общее количество выплат, удержанный процент и новый общий баланс долга клиента. 3. Отчет по ежедневным продажам, содержащий для каждого дня месяца сумму по всем квитанциям на покупку, полученным центральным бюро. 4. Месячные бюллетени для всех покупателей, содержащие исходную величину баланса, дату, имя, список всех покупок, 630
даты и величины всех выплат по счету, удержанный процент (если он есть) и конечное значение задолженности. Здесь же должен находиться адрес покупателя, располагаемый таким обра- зом, чтобы его можно было использовать при посылке конверта с бюллетенем. Адрес должен иметь не более четырех строк по 30 символов каждая. Входная информация в систему оплаты счетов содержит: L Товарные чеки в каждом из которых имеется идентифика- ционный номер покупателя, дата покупки, описание и количе- ство купленного товара. 2. Заявление на кредитную карточку от новых покупателей, содержащие имя, адрес и идентификационный номер покупателя. 3. Квитанции об уплате, содержащие идентификационный номер покупателя, дату платежа и размер выплаченной суммы. 4. Данные по покупателям, прекращающим пользоваться услугами фирмы. Эти данные должны содержать идентификацион- ный номер покупателя и дату закрытия.счета. Имеется несколько обстоятельств, потенциально затрудня- ющих проектирование системы. Во-первых, все новые кредитные карточки должны выпускаться центральным бюро. Во-вторых, для удобства покупателям разрешается извещать о закрытии счета непосредственно центральное бюро или сообщать об этом местному отделению, которое в таком случае само посылает необ- ходимое извещение. 7-5.2. Проектирование системы Мы начнем проектирование системы с определения формата входных и выходных данных системы, а затем рассмотрим задачу, как лучше получить требуемую выходную информацию. Входная информация поступает из двух источников: с места продажи (карточки покупок и закрытия, счетов) и от покупателя (заявление на кредитную карточку, квитанции об уплате и изве- щение о закрытии счета). По мере поступления товарных чеков данные о покупках перфорируются для ввода в систему в следующем формате: Перфокарта с данными о покупке: Колонки карты 1—6: Идентификационный номер покупателя 7—12: Дата (по две колонки на месяц, день и год) 13—20: Стоимость покупки 21—40: Описание предмета покупки Карточный файл, полученный перфорацией данных о покупке, назовем PURCHASE. Вследствие того, что сообщения о закры- тии счетов, так же как и данные о покупках, прибывают из точек продажи, было решено идентифицировать закрытие счета разме- 631
щеиием сообщения 'DELETE THIS CUSTOMER' * в поле опи- сания предмета в записи файла PURCHASE. Следовательно, записи PURCHASE должны создаваться также и для извещений о закрытии счетов, посланных покупателем непосредственно в центральное бюро. Два отдельных карточных файла PAYMENT и NEWACCT создаются для ввода информации от покупателей по уплаченным суммам и заявлениям на кредитные карточки соответственно. Перфокарта с данными о платеже: Колонки карты 1— 6: Идентификационный номер покупа- теля 7—12: Дата (по две колонки на месяц, день и год) 13—20: Сумма платежа Перфокарта с данными о новом покупателе: Колонки карты 1—• 6: Идентификационный номер покупа- теля 7—2.6: Имя покупателя 27—46: Улица, где живет покупатель, или номер почтового ящика и т. д. 47—65: Город, провинция или штат покупа- теля 66-—72: Почтовый код покупателя 73—80: Первоначальный баланс покупателя (всегда^ равен нулю) Важной частью разработки любой подобной системы является разработка выходных форм. В данной задаче наибольшее внима- ние должно быть уделено разработке бюллетеня покупателя. Устраивающий компанию формат бюллетеня дан на рис. 7-5.1. Заметим, что жирным шрифтом выделена общая для всех покупа- телей часть бюллетеня. Этот общий текст должен быть заранее отпечатан на бланке в какой-нибудь типографии. Таким образом, компьютер обеспечивает только печать дат, покупок, баланса и т. д. для каждого покупателя. Подобная процедура сохраняет огромное количество времени и допускает получение многоцвет- ных бюллетеней. По требованию компании списки покупателей должны быть упорядочены по идентификационному номеру и вся существенная информация о покупателе должна появиться в компактной, но в то же время легко читаемой форме. Чтобы это обеспечить, относя- щаяся к покупателю информация печатается в одну строку сле- дующей формы: ID.NUM NAME ... STREET.OR.P.O.BOX ... CITY..., STATE ... POST.CODE BALANCE ’ИСКЛЮЧИТЬ ДАННОГО ПОКУПАТЕЛЯ’. — Прим. пер. 63g
*ГНЕ COMPANY OF CANADA. LTD. 4141 THE STREET THE CITY, PROVINCE ЛОА 1B1 CUSTOMER'S.NAME... .NUMBER STREET.OR.BOX.NUMBER CITY, PROVINCE.OR.STATE POSCOD Date TRANSACTION DEBIT CREDIT BALANCE BALANCE FORWARD SDD.DD sbb.bb MM/DD/YY PURCHASE (ITEM) SDDDD sbb.bb MM/OD/YY PAYMENT ON ACCOUNT scc.cc SBB.BB MM/OD/YY INTERESTON; $tl.|| SDD.DD Sbb.bb MM/DD/YY CURRENT AMOUNT OWING Sbb.bb Рис. 7-5.1. Обычный формат бюллетеня^покупателя Данные по месячному балансу и суммы платежей за каждый день месяца печатаются на одной выходной форме в легкочитае- мом формате. Конкретные примеры для всех трех форм будут представлены в следующем пункте. Теперь обратимся к разработке программы, которая будет обрабатывать данные, получаемые в ранее специфицированном формате, и генерировать желаемый результат, форматированный должным образом. JHa рис. 7-5.2 показана возможная структура системы. Конечно, существуют другие, лучшие'(решения, и не- которыеМз’иих’будут] рассмотрены позже в этом'пункте. Очевидным и в то же время очень важным аспектом разработки системы является необходимость сохранения от одного месячного расчета до другого системного'!файла, содержащего сведения о каждом покупателе (о текущем^ балансе, имени, номере счета и адресе). Будем называть этот файл CUSTOMER и создадим его как последовательный файл, упорядоченный по номеру счета. Концептуально простейшим путем включения в систему но- вых покупателей является слияние множества новых счетов с файлом CUSTOMER и создание нового последовательного файла, который назовем CUST1. В файле CUST1 новый счет будет иметь поле «текущий баланс», установленное в нуль. Заметим, что в си- стеме, которую мы разрабатываем, выбор нового номера счета делается вручную. Хотя это и приводит к некоторой добавочной «бухгалтерии», последовательный карточный файл, упорядочен- ный по номеру счета, может быть создан достаточно легко в те- чение месяца. Этот упорядоченный последовательный файл, на- званный NEWACCT, является файлом, сливаемым с файлом CUSTOMER для формирования CUST1. 633
Рис. 7-5.2. Структура системы оплаты счетов Для генерации бюллетеня состояния счетов покупателей необходимо объединить вместе данные о покупках и платежах за месяц с предшествующим месячным балансом. Объединение информации по покупкам и платежам с предыдущим месячным балансом очень неэффективно и дорого, если делать отдельный прогон для каждого покупателя. Однако сортировкой файлов PURCHASE и PAYMENT по номерам счетов и последующим слиянием информации в этих двух файлах с информацией в файле CUST1 можно получить бюллетени по всем покупателям за один прогон (т. е. только один раз просматривая все три файла). Каж- дый из файлов PURCHASE и PAYMENT может быть отсорти- рован с помощью механической сортировальной машиныJили с помощью стандартной системной программы сортировки, или, на худой конец, можно написать свою собственную процедуру сортировки и использовать ее в месте, указанном на рис. 7-5.2. Слияние трех файлов осуществляется очень просто и описывается следующими шестью шагами. 1. Читать запись из файла CUST1. 2. Читать и обрабатывать все записи из файлов PURCHASE и PAYMENT с теми же номерами счетов, что и запись из CUST1. 634
(Так как файлы упорядочены по номерам счетов, то информация о любой покупке или платеже покупателя с номером счета, ука- занным в записи файла CUST1, должна по необходимости по- явиться в одной из «следующих» записей при последовательной обработке файлов PURCHASE и PAYMENT.) 3. Вычислить новый баланс. 4. Подсчитать пени, если это необходимо. 5. Напечатать бюллетень покупателя и обновить запись файла CUST1. 6. Возвратиться для обработки следующей записи файла CUST1. Отчеты компании также имеет смысл получать во время обнов- ления файла покупателей. Номера счетов покупателей, имена, адреса и балансы могут печататься немедленно после вычисления баланса покупателя во время обработки этих трех файлов. Однако, поскольку бюллетени покупателей печатаются в то же самое время, необходимо позаботиться о размещении списков покупателей компании и их бюллетеней в различных файлах для печати. При дальнейшем обсуждении реализации системы мы увидим, как это может быть достигнуто. Отчет компании, содержащий суммарный месячный баланс, легко создать накоплением балансов в процессе обработки инди- видуальных записей покупателей. Окончательный отчет выво- дится немедленно после обновления всех записей покупателей, fex Отчет о ежедневных продажах генерируется с помощью век- тора из 31 элемента, в котором каждый элемент служит для на- копления суммы от продаж в данный день. Это значит, что п-й элемент предназначен для получения полной стоимости продан- ных в n-й день месяца товаров. Ежемесячные расчеты завершаются созданием нового файла CUSTOMER из временного файла CUST1. Фактически это послед- нее действие над файлами является не более чем просто копирова- нием запись за записью (т. е. сериально) информации из файла CUST1 в файл CUSTOMER. Ранее мы сознательно пренебрегли проблемой, связанной с действиями со счетами, которые должны быть закрыты. Одним из возможных действий является исключение закрытых счетов после того, как имела место их обработка. Номер счета устанав- ливается в 000000 во время процедуры слияния-обновления файла CUST1, и затем все записи со значением ключа 000000 удаляются при создании нового файла CUSTOMER. Недостатком подобной схемы является то, что запоздалые кредитные карточки по закры- тым счетам могут быть получены в следующем’месяце в то время, как номера счетов уже удалены из системы. Альтернативная схема рассматривается в конце следующего пункта. 7-5.3. Реализация Программа BILLS на языке ПЛ/1, представленная на рис. 7-5.3, отвечает схеме, разработанной в предыдущем 635
// EXEC PL1LFCLG,PARM»'ATR,XREF* Z/PLU.SYSIN DO « BILLS: PROCEDURE OPTIONS (MAIN!? /* THIS PROGRAM IS DESIGNED TO HANDLE THE BILLING REQUIREMENTS OF THE*/ ✓* COMPANY OF THE WORLD, LTD. AS FOLLOWS» */ /« THE PROGRAM IS COMPOSED OF FIVE LOGICALLY DISTINCT SEGMENTS. ТнЕ */ /* FIRST TWO SORT THE CARD FILES, PURCHASE AND PAYMENT. THE «/ /» THIRD OF THESE IS USED TO INSERT NEW CUSTOMERS INTO THE FILE OF */ /« THE CURRENT CUSTOMERS OF THE COMPANY, AND TO DELETE THOSE OLD »/ /<* CUSTOMERS WHICH ARE CLAGGED FOR DELETION. THE FOURTH PART OF THIS*/ /* PROGRAM IS USED TO UPDATE THE CURRENT BALANCES OF EACH CUSTOMER »/ /* AND TO PRINT HIS MONTHLY STATEMENT. THE PROGRAM ALSO PRINTS A *-/ /* LIST OF TOTALS WHICH SERVE AS A CHECK ON THE BOOKKEEPING OF THF */ /Р COMPANY ANO ON THIS PROGRAM. THE FINAL PART CREATES A NEU «/ /0 CUSTOMER FILE AFTER A MONTHLY RUN */ Z* SOME OF THE KEY VARIABLES AND FILE NAMES USED IN THIS PROGRAM ARE *Z CUSTOMER A DISK FILE OF SEQUENTIAL ORGANIZATION WHICH HOLOS */ THE OLD LIST OF CUSTOMERS AND THEIR OLD BALANCES */ NEWACCT A CARD FILE WHICH CONTAINS A LIST OF NEW CUSTOMERS TO BE INSERTED INTO THE CUSTOMER FILE. */ PURCHASE A CARD FILE USED TO CONTAIN A RECORD OF ALL PURCHASES MADE ON CREDIT IN THE LAST MONTH. */ PAYMENT A CARD FILE USED TO CONTAIN A RECORD OF ALL PAYMENTS RECEIVED IN THE LAST MONTH. */ CU5T1 A DISK FILE, ALSO OF SEQUENTIAL ORGANIZATION, ON */ WHICH IS PLACED THE NEW LIST OF CUSTOMERS. */ STATMENT A PRINT FILE ON WHICH ALL THE MONTHLY STATEMENTS ARE PRINTED. */ CUST A STRUCTURE OF THE SAME FORMAT AS THE RECORDS ON */ THE CUSTOMER FILES </ PURCH A STRUCTURE USED TO INTERNALLY STORE THE RECORDS FROM THE PURCHASE CARD FILE */ PAY A STRUCTURE USED TQ STORE INTERNALLY THE RECORDS FROM THE PAYMENT FILE. </ OLD BALANCE,PURCHASES,PAYMENTS,INTEREST,NEWJiALANCE,₽AY_DAYS */ VARIABLES USED IN THE CALCULATION OF TOTALS. */ DECLARE (CUSTOMER, CUSTII FILE RECORD SEQUENTIAL ENVICONSECUTIVE F< 750,751b (NEWACCT,PURCHASE,PAYMENT J FILE STREAM ENV(CONSECUT IVEb STATMENT FILE STREAM PRINT, 1 CUST, 2 ID FIXED DECIMAL (6,01, 2 NAME CHARACTER (201, 2 ADDRESS, 3 LINE1 CHARACTER (201, 3 LINE2 CHARACTER (191, 3 POSTAL.COOE CHARACTER (71, 2 BALANCE FIXED DECIMAL (8,2b 1 PURCH, 2 ID FIXED DECIMAL (6,01, 2 -PDATE, 3 MONTH CHARACTER <21, Рис. 7-5.3. Небольшая система оплаты счетов 636
3 DAY CHARACTER (2Ь 3 'YEAR CHARACTER (2), 2 AMOUNT FIXED DECIMAL (8,2b 2 DESCRIPTION CHARACTER (20), 1 PAY» 2 ID FIXED DECIMAL (6,0), 2 PDATE, 3 MONTH CHARACTER (2), 3 DAY CHARACTER (21, 3 YEAR CHARACTER (2), 2 AMOUNT FIXED DECIMAL (8,2), OLD.BALANCE FIXED DECIMAL (10,2) INITIAL (0), PURCHASES FIXED DECIMAL (10,2) INITIAL (0) , PAYMENTS FIXED DECIMAL (10,2) INITIAL (0 ), INTEREST FIXED DECIMAL (10,2) INITIAL (0), NEW.BALANCE FIXED DECIMAL (10,2) INITIAL (0), PAY.DAY131) FIXED DECIMAL (10,2) INITIAL ( (31) 0 (CUR-.BALANCE, INTEREST.AMT) FIXED DECIMAL (8,2), DELETE BIT(l) INITIAL ('O'B), (BOATE,TODAY) CHARACTER (8)5 /<- THIS STATEMENT DETERMINES THE DATE OF THIS RUN TO USE AS THE <7 /t> CURRENT DATE ON THE STATEMENTS THAT ARE PRINTED. TODAY = SUBSTR(DATE,3,2) |[ (( SUBSTR (DATE,5,2) (I II SU8STR (DATE,1,2)5 /* MAINLINE <7 CALL SORT.PURCHASES; CALL SDRT_PAYMENTS; CALL MERGE.ACCTS; CALL MERGE_UPDATE; CALL COPY; /о A PROGRAM SEGMENT WHICH CONTAINS THE THREE INTERNAL PROCEDURES *7 /* SORT.PURCHASES, S3RT.PAYMENTS AND MERGE.ACCTS WOULD NORMALLY FALL */ IN THIS SECTION. */ MERGE.UPDATE: PROCEDURE; /« THIS PROCEDURE READS A RECORD FROM THE CUST1 TEMPORARY FILE */ /* AND THEN PROCESSES RECORDS FROM THE PURCHASE AND PAYMENTS FILES */ /« WHICH HAVE THE SAME 10 NUMBER. NOTE THAT THIS REOUIRES THAT THE «/ /* PURCHASE AND PAYMENT FILES ARE ALSO SORTED IN ORDER OF ASCENDING <-/ /* ID NUMBERS. (THIS IS ALSO A REQUIREMENT FDR THE NEWACCT FILE IN «>/ /* THE MERGE.ACCTS PROCEDURE). */ OPEN FILE(PURCHASE) INPUT, FILE (PAYMENT) INPUT, FILE (CUSTL) UPDATE, FILE (STATMENT) OUTPUT 5 ON ENDFILE (CUSTL) GO TO T; ON ENOFILE (PURCHASE) PURCH.Ю = 0; Рис. 7-5.3. Продолжение 637
^ON ENDFILE (PAYMENT) PAY.ID = 0; GET FILE (PURCHASE) EDIT (PURCH) (COLUMN!1),Ftbt,3 A(2),F(B,2i, A(2O))5 GET FILE (PAYMENT) EDIT (PAY) (COLUMN(1),F<6),3 A(Z) ,F(8,2))5 DO WHILE (1'B); REAO FILE (CUST1) INTO (GUST)» 7* PRINT STATEMENT HEADINGS AND PREVIOUS BALANCE </ PUT FILE (STATHENT) EDIT ('THE COMPANY OF CANADA, LTD.', •4141 THE STREET', •THE CITY, PROVINCE’,»A0A 1B1*) (XI22),A,SKIP,X(28), A, SKIP , X(26),A,SK1P,X(32),A) (NAME,CUST.ID,ADDRESS) (SKIP(3),A(21),F(6),3 (SKIP, A)) ('DATE',* TRANSACTION*,’DEBIT*,•CREDIT’,’BALANCE* > ( SKI₽(3) ,X(2),A(8),A(31) ,A(11),A(H) ,A(7I) ('BALANCE FORWARD*,BALANCE) (SKI₽(2) ,X(10) ,A(49),P'$^$$,$$9V.99CR')5 IF BALANCE < 0 THEN PUT FILE (STATHENT) EDIT (BALANCE) (SKIP(0)7X( 4?) ,P •$$$$»$£’¥•"•>? ELSE IF BALANCE'» 0 THEN PUT FILE (STATHENT) EDIT (BALANCE) (SKI P (0), X( 35), P *$$£$. $$9V.99’); OLD_BALANCE = OLD_BALANCE ♦ BALANCE* CUR_BALANCE = BALANCE; /> PURCHASES •/ /= THIS SEGMENT IS SET UP TO HANDLE ALL PURCHASE RECORDS. ®7 DO WHILE (CUST.ID = PURCH.ID); /* THIS IS WHERE AND HOU THE DELETE FLAG IS SET. *7 IF DESCRIPTION = 'DELETE THIS CUSTOMER' THEN DELETE = »1’B5 ELSE DO; PURCHASES = PURCHASES * PURCH.AMOUNT; BDATE = PURCH.MONTH 11 •/' )| PURCH.DAY II PURCH.YEAR; BALANCE = BALANCE ♦ PURCH.AMOUNT; IF PURCH.AMOUNT >= 0 THEN I = 5; ELSE I =17; /♦ PUT OUT THE LINE INDICATING A PURCHASE. PUT FILE (STATMEMT) EDIT (BOATE,DESCRIPT ION, ABS(PURCH.AMOUNT),BALANCE) (SKIP,A(10),A(20),X(I),P'$t$$,f$9V.99*, C0LUMN(60),P*$6$S,$$9V.99CR'); GET FILE (PURCHASE) EDIT (PURCH) (COLUMN!1),F(6),3 A(2), F(8,2),A(20>); END; 7* OF PURCHASE SEGMENT </ Рис. 7-S.3. Продолжение 638
/с THIS SEGMENT IS SET UP TO HANDLE ALL PAYMENT RECORDS. DO WHILE (CUST.ID = PAY.ID); BALANCE = BALANCE - PAY.AMOUNT; CUR_BALANCE = CUR_BALANCE - PAY.AMOUNT; PAYMENTS = PAYMENTS ♦ PAY.AMOUNT; PAY-DAY(PAY.DAY J = PAY-DAY(PAY-DAY» ♦ PAY.A MOUNT; BDATE = PAY.MONTH II •/• II PAY-DAY || •/• J] PAY.YEAR; IF PAY.AMOUNT > О THEN 1 =17* ELSE 1=5; /* PUT OUT THE LINE INDICATING A PAYMENT. ' */ PUT FILE (STATMENT) EDIT (BOATE,•PAYMENT ON ACCOUNT*, A8S(PAY.AMOUNT ),BALANCE) (SKIP,A(10),A(20),X(I) ,P*«m,$49V.99‘, C0LUMN(60),P*,$®9V.99CR‘); GET FILE (PAYMENT) EDIT (PAY) (COLUMN(1I,F(6),3 A(2), FIB,2)1; END; /> OF PAYMENTS SEGMENT */ /ф ** ФФДО X>* <$ ОДОД $1М> йФФ ФЛО ЭФОЙ дофОДОДФв / /* INTEREST AND BALANCE OWING «/ /* THIS SEGMENT CALCJLATES THE AMOUNT OF INTEREST TO BE CHARGED. */ /« INTEREST IS CHARGED ON THAT PORTION OF LAST MONTH’S DEFICIT BALANCE ft> FDR WHICH PAYMENTS WERE NOT RECEIVED THIS MONTH. <*/ IF CUR_BALANCE > О THEN DO; INTEREST_AMT = 0.015 ° CUR_BALANCE»’ BALANCE = BALANCE ♦ INTEREST-AMT; INTEREST = INTEREST ♦ INTEREST—AMT; PUT FILE(STATMENT) EDIT (TODAY,•INTEREST ON CUR-BALANCE,•:•,INTEREST_AMT,BALANCE) (SKIP,A(10),A,P*Si®J,S«9V.99’,A(3)5P'SS2,$$9V.99*, C0LUMN(60)sP’«i«®;«S9V.99CR»>; END! PRINT THE NEW BALANCE OWING (WHICH MAY BE A CREDIT BALANCE) */ PUT FILE (STATHENT) EDIT (TODAY,’CURRENT AMOUNT OWING’,. BALANCE) (SKIP(2),A(10),AC49>,P‘SS$S,®S9V.99C‘”»: IF DELETE THEN do; DELETE - 'O’B; CUST.ID = 0; /*> IF THE CUSTOMER IS DELETED A MESSAGE IS PRINTED TO INDICATE THAT •/ /< THIS IS HIS FINAL STATEMENT. •/ PUT FILE (STATHENT) EDIT (’THIS IS YOUR FINAL STATEMENT’, ’. THANKYOU FOR YOUR BUSINESS.’) (SK1P(3»,A,AI5 end; NEW.BALANCE = NEw_BAlANCE ♦ BALANCE; PUT FILE (STATHENT) PAGE; REWRITE FILE (CUSTL) FROM (CUST); ’ PUT FILE (SYSPRINT) EDIT (CUST) (SKIP(1),F(6),X(1),3 A(20I, Рис. 7-5-3. Продолжение 639
ENO; /0 OF MAIN LOOP A(7),F(9,2))4 /О 4>ф$одзд ? ФФОФОФОЙФОФО Cl 4>й <**<>«>«<• е’»*йфй<и>йй«| $i St <H>Oo4hS:Wv / /V MONTHLY TOTALS й/ /< THIS SEGMENT IS USED TO PRINT OUT THE OVERALL TOTALS ' «/ Ti POT FILE (SYSPRINT) PAGE EOIT(’MONTHLY TOTALS’) (A) (’OLD BALANCE OWING’, DLD_6ALANCE, 'PURCHASES MADE', PURCHASES, ’PAYMENTS RECEIVED’,PAYMENTS, INTEREST CHARGED’,INTEREST, ’CURRENT BALANCE OWING’,NEU_BALANCE> (SKIPfll, 5 (SKlPf l),A(23),PfSS$£,Sg54$9V.990B'M5 PUT FILE (SYSPRINT) EDIT (’DAILY PAYMENTS RECEIVED THIS MONTH') fSK1PI5),AI? DO I = 1 TO 315 IF (PAY_DAY(I) -»= 0) , ' THEN PUT FILE (SYSPRINT) EDIT (I,PAY_DAY(1)I {SKIP(2),F<2hX(2bP' , , 9V.990B'); END; close file icustd, file (sta.tment}, file (payment), Fl LEI PURCHASE) 5 END MERGE_UPDATE; COPY: PROCEDURE; /* THIS PROCEDURE COPIES THE FINAL /<- TO SET UP FOR NEXT MONTH’S RUN. /v AN 10 OF 000000 ARE THOSE WHICH CUST1 RECORDS BACK INTO CUSTOMER, */ NOTE THAT THOSE GUSTOMERS WlT^ -*•/ HAVE BEEN ^LAGGED FOR DELETION, */ OPEN FILE (CUSTOMER) OUTPUT, FILE (CUST1J INPUT; ON ENDFILE (CUSTD STOP;- DO WHILE (’1*B>; READ FILE (CUST1) INTO (CUSTj; IF (CUST.ID OOOCOO) THEN WRITE FILE (CUSTOMER) FROM (CUST), ENDS END COPY; END BILLS; //GD.SYSPR1NT DO SYSOUT = ( J, , 73U) //GO.STATMENT OD SYSOUT=A //GO.CUSTX DD DSN=£CCUSTOMER,D1SP = (NEW,DELETE ),UNIT»SYSDA, // V0L=SER=USER02,S₽ACEM75,(25,5)) /У0О.CUSTOMER DO DSN=£&CUSTOM,O1S₽=(OID,KEEP),UNIT=S¥SOA, // V0L=SER=USER02,SPACE=(75,(25,51) //GO.NEHACCT W » (NEWACCT CARO FILE SHOULD FOLLOW IMMEDIATELY) //GO.PURCHASE OD * (PURCHASE CARD FILE SHOULD FOLLOW IMMEDIATELY) //GO.PAYMENT DO * (PAYMENT CARD FILE SHOULD FOLLOW HERE) Рис. 7-5.8. Продолжение 640
пункте. BILLS состоит из главного сегмента, содержащего опи- сания файлов, записей и временных переменных наряду с после- довательностью из пяти вызовов следующих основных модулей системы: SORT-PURCHASES, SORT—PAYMENTS, MER GE—ACCTS, MER GE —UPDATE, Каждый модуль соответствует одному из прямоугольных блоков обработки на рис. 7-5.2. Так как файлы CUSTOMER и CUST1 не являются ни карточными, ни ориентированными на печать, они описаны как записеориентированные последовательные файлы. Файл CUSTOMER сохраняется от прогона к прогону; CUST1 — временный файл; NEWACCT, PAYMENT и PURCHASE — по- токоориентированные входные файлы; STATEMENT - - выходной потокоориентированный файл (PRINT-файл), который содержит бюллетени покупателей. Структура CUST используется при обработке файлов CUSTOMER и CUST, PURCH — при обработке файла PURCHASE, a PAY — при обработке файла PAYMENT. Информация о суммах продаж по каждому дню месяца накапли- вается в векторе PAY_DAY. Назначение остальных переменных понятно из контекста программы и комментариев в ней. Процедуры SORT—PURCHASE и SORT—PAYMENTS не приводятся. Для упорядочения файлов PURCHASE и PAYMENTS можно воспользоваться каким-либо известным методом сортиров- ки, например описанной в гл. 6 сортировкой слиянием. Кроме того, существует несколько специальных внешних методов сортировки, таких как многофазная и осциллирующая сортировки [19,26]. Процедура MERGE—ACCTS опущена вследствие ее очевидной аналогии процедуре, используемой в программе, представленной на рис. 7-4.6. Процедура MERGE____APDATE включает последовательную обработку файлов PURCHASE, PAYMENT и CUST1. Эта об- работка осуществляется в соответствии с описанием процесса из шести шагов, которое было приведено в пункте разработки системы. Предложение REWRITE предназначено для изменения файла CUST1. Очевидно, если CUST1 располагается не на магнит- ном диске, а на магнитной ленте, то перезапись недопустима и должен быть создан новый файл. Заметим также, что при пе- чати требуемых выходных форм использовались форматы, в зна- чительной степени подобные форматам, определяемым фразой PICTURE в языке КОБОЛ (т. е. форматы типа P’$.$9V.99’). Подобные форматы допускают запись денежного знака $, смеж- ного с самой левой ненулевой цифрой. Они обеспечивают также возможность автоматической печати символов 'CR' (для кредита) или 'DB' (для дебета) следом за числом в зависимости от его знака. Читатель, не знакомый с данным средством языка КОБОЛ, дол- жен разобраться в этом типе форматирования по описанию языка ПЛ/1. 21 Трамбле Ж., Соренсон П 641
145203 JOHN fl. DRYDEN 504058 PAMELA 8. SCHULTZ О PETER t. CLARKE 529270 DAVID N. PARKER 932147 PATRICK I. WATSON 0 RAYMONO H. JAMES 538494 ALICE H. COCHRANE 542137 CINDY L. PARENT 556090 DIANNE P. HOLMES 563619 PAUL E. JACKSON . --- 570144 RICHARD 0 UlLLIAMSONBSl CACHE DRIVE 587542 ROBERT C. SMYTHE -------------' ' 59L146 LINDA T. GARDNER 6094B3 GEORGE H. ELSEV 615966 JUDY R. JONES 637263 SUSAN C. FROST 643120 JOHN A. THOMPSON 661301 ROY B. ANDERSON 61800T JAMES ₽. MACDONALD 636725 LARRY R. BROWN 693121 PATRICIA L. FOX 792145 DAVE 8ROAOFOOT BOX 400 1356 OSLER ST. 233? WEST LOTH AVE. 4534 HIGHLAND ST. 0123-1968 COMOX ST. 753 11TH S*. S. 2348 MAIN ST. 4112 HIGH AVE. 23 ASHTON CRES. 1356 AVENUE ЯОАО «Ха AVENUE S «*-14015 7?ТН AVE. 2415 COLONY ST. 4125 ARBUTUS ST. 4532 HIGHLAND BLVD. 4105 36TH ST. JASPER, AB SASKATOON, VANCOUVER, EOliONTON, VANCOUVER, CALGARY, i WINNIPEG, f CALGARY, I THUN0ER8AY, TORONTO, — KAMLOOPS, __ SASKATOON, SK CALGARY, AB EDMONTON, AB REGINA, SK KAMLOOPS, BC EDMONTON, '* SASKATOON, VANCOUVER, ___ NORTH YANCOUVER- RED DEER, AB KAMLOOPS. BC ав AB MAN AB • T, ONT. ONT. SK MONTHLY TOTALS OLO BALANCE OWING PURCHASES MADE PAYMENTS RECEIVED INTEREST CHARGEO CURRENT BALANCE OWING $5,590.50 $1,420.32 (°) TOE 1E0 S7N 0V2 V2K IH8 M20 0S9 USX 3W9 TOM 2*0 T3R 010 S4N 303 U30 1G5 S?N OS? BC VBO 2X3 Т5Й 1U9 е.со 1393.03 0.00 230.90 -3.36 300.00 39.22 145.46 183.19 290.00 DAILY PAYMENTS RECEIVED THIS MONTH $5.0000 $9.00 $100.00 $50.00 $25.00 10 $30.00 $100,00 12 $5.00 $54,32 15 $115.00 $200.00 $100.00 25 $100.00 20 £90.00 $400.00 $70.00 (61 The company op Canada, t 4141 THE STREET The city, province aoa 1B1 DIANNE P. HOLMES 556090 23 ASHTCN CRES. thunderbay, ont. M9J 1T4 date transaction BALANCE FORWARD 05/08/74 RECORD 05/08/14 RECORD PLAYER 05/08/74 PAYMENT ON ACCOUNT 05/22/74 PAYMENT ON ACCOUNT 05/20/75 INTEREST ON $150,24? C5/20/75 CURRENT AMOpfiT OKING DEBIT CREDIT BALANCE $300.24 $300.24 $5.00 $305.24 $253.94 $559.18 $50.00 $509.18 $100.00 $409.18 $2.25 $411.43 $411.43 Рис. 7-6.4. Образцы отчетов: a____список покупателей: б — месячный баланс; е — ежедневный отчет о продажах г — месячный бюллетень 642
Последняя процедура COPY состоит из простого цикла, ко- торый перемещает записи из CUST1 в файл CUSTOMER, пере- записывая при этом старые записи файла CUSTOMER. Записи, помеченные номером счета 000000, исключаются из нового файла CUSTOMER. Операторы JCL для файлов NEWACCTS, PURCHASE и PAYMENT помещены в конце программы. На рис. 7-5.4 при- ведено несколько примеров отчетов. Предполагается, что перед прогоном программы файл CUSTOMER содержит некоторую информацию. Завершая параграф, попытаемся внести несколько изменений в структуру только что реализованной системы. Вспомните, что разработка системы является итеративным процессом. Два глав- ных изменения согласовать с системой довольно просто. Вместо организации отдельной процедуры для слияния файлов CUSTOMER и NEWACCT можно включить это слияние в про- цесс слияния-обновления файлов PURCHASE и PAYMENT. Заметим, что при этом временный файл CUSTL все еще исполь- зуется. Мы могли бы поместить результирующие записи сразу же обратно в файл CUSTOMER, но это рискованно. Если вычис- лительная система собьется в середине процедуры слияния- обновления, то файл CUSTOMER будет содержать как старые, так и новые записи. Подобная ситуация приведет к невозможности рестарта системы оплаты счетов. Второе изменение решит проблему запоздавших счетов — проблему, которую необходимо учитывать в реальной ситуации. Вместо замены номера закрытого счета на 000000 можно скопи- ровать информацию о счете в дополнительный файл TERMINAL, сохраняемый из месяца в месяц. Таким образом, закрытые счета устраняются из файла CUSTOMER. Когда встречаются квитан- ции об уплате или покупках по счетам с номерами, не найденными в файле CUSTOMER, поиск ведется в файле TERMINAL. Най- денные данные о клиенте берутся из этого файла, и на их основе можно печатать бюллетени. Полное закрытие счетов теперь должно базироваться на транзакциях, применяемых к файлу TERMINAL. В данном параграфе мы дали несколько упрощенный взгляд на небольшую систему оплаты счетов. Тем не менее система поз- волила проиллюстрировать возможности последовательных фай- лов и создать некоторое представление о методах обработки дан- ных в больших системах оплаты счетов. Теперь мы сосредоточим наше внимание на обсуждении другого метода организации файлов. 7-6. ИНДЕКСНО-ПОСЛЕДОВАТЕЛЬНЫЕ ФАЙЛЫ При разработке системы оплаты счетов мы не допу- скали запросов об их состоянии, если не считать ежемесячных проверок. По мере расширения компанией объема операций мо- жет оказаться удобным или обязательным предоставление служа- 21* 643
щим магазинов средств контроля состояния счета клиента непо- средственно в момент продажи. Необходимость проверки счета вызывается хотя бы тем, что время от времени компании при- ходится иметь делос покупателями, кредит которых исчерпан или предъявляющими украденные кредитные карточки. Проверка в магазине состояния счета должна быть мгновенной, так как покупатель становится раздражительным, если его медленно об- служивают. Для запросов о состоянии дел покупателя можно использо- вать уже разработанную систему, лишь слегка ее модифицировав. Однако характеристики системы при этом окажутся ниже всякой критики. Это объясняется тем, что CUSTOMER (основной по- стоянно хранимый файл системы) является последовательным файлом, и для отыскания требуемой записи часто требуется про- смотр почти всего файла. Задержка, связанная с подобным про- смотром, может оказаться недопустимо большой. Например предположим, что в файле CUSTOMER размещено 5000 записей. Обрабатывая файл последовательно со средним временем (вклю- чая время на поиск,'вращение и передачу данных) 30 мс на за- пись, мы дойдем до последней записи через 150 000 мс или через 2.5 мин! Для получения разумного времени ответа на онлайновый запрос продавца необходимо наличие прямого доступа к требуе- мой записи. Как можно обеспечить прямое обращение к записи, мы изучим в данном параграфе. Помимо возможности прямого доступа в системе оплаты счетов, весьма удобно сохранить после- довательную упорядоченность файла. Упорядоченность записей в файле CUSTOMER необходима при ежемесячном получении отчетных форм. Месячные отчеты можно получить, используя только прямой доступ. Однако вследствие непоследовательной обработки файла почти случайные перемещения считывающих головок вызовут огромные затраты дополнительного времени. Таким образом, для подобной системы оплаты счетов с запро- сами в онлайновом режиме необходимо поддержание как прямой, так и последовательной формы доступа. В данном параграфе мы исследуем организацию файла, обеспечивающую оба типа до- ступа. 7-6.1. Структура индексно-последовательных файлов Важнейшим аспектом, влияющим на структуру файла, является тип физического носителя, на котором располагается файл. Прямой доступ к записи по ключу (или уникальному ин- дексу) может осуществляться, если устройство внешней памяти обеспечивает необходимый тип доступа. В частности, устройства типа карточного ввода и накопителя на магнитной ленте обеспе- чивают доступ к отдельным записям только после чтения всех записей, физически предшествующих искомой записи файла. 644
Следовательно, прямой доступ к записи невозможен для устройств этих типов. Устройствами, обеспечивающими как прямой так и последовательный доступ, являются магнитные барабаны, а также диски с фиксированными и перемещающимися головками. Основные структурные концепции индексно-последовательных файлов лучше всего можно показать, если в качестве носителя информации рассматривать диски с перемещающимися голов- ками. Кроме того, вследствие сравнительно низкой стоимости, хороших рабочих характеристик и большой емкости памяти именно сменные диски чаще всего выбирают для хранения индекс- но-последовательных файлов. Поэтому при последующем обсужде- нии предполагается, что файловые структуры размещаются на устройстве такого рода. Мы опишем два типа индексно-последовательных организа- ций — первый тип обязан своим происхождением фирме IBM, второй — CDC. Индексно-последовательный файл IBM состоит из трех отдельных областей: основной области, индексной области и области переполнения. Область, в которую записи помещаются при первоначальном создании файла, называется основной об- ластью. Файл создается последовательно, т. е. размещением записей в основной области в последовательности, определяемой лексикографическим упорядочением ключей записей. Процесс записи стартует со второй дорожки некоторого цилиндра, ска- жем n-го. Когда цилиндр заполняется, запись продолжается на второй дорожке следующего (п -J- 1-го) цилиндра, и в такой ма- нере процесс записи продолжается до тех пор, пока не завер- шится создание файла. Если вновь созданный файл обрабаты- вается последовательно согласно полю ключа, то записи читаются в том же порядке, в каком они были записаны в файл. Вторая большая область индексно-последовательного файла— индексная область (или индекс) создается автоматически програм- мами управления данными операционной системы. Для доступа к индекс но-последовательному файлу можно использовать не- сколько уровней индекса. Самый нижний уровень называется индексом дорожек и размещается всегда на первой дорожке (называемой 0-й) цилиндров индексно-последовательного файла. Индекс дорожек содержит две записи для каждой дорожки основ- ной области — нормальный элемент и элемент переполнения. Нормальный элемент состоит из адреса дорожки, с которой он связан, и значения самого большого из ключей записей, храня- щихся на дорожке. Если записи размещены только в основной области, то элемент переполнения остается равным нормальному, элементу. Смысл элемента переполнения описан позже в этом пункте при обсуждении области переполнения. Рис. 7-6.1 иллю- стрирует структуру индексно-последовательного файла с дан- ными о покупателях. Ключевым элементом в записях этого файла является шестизначный иомер счета; изображен только один Цилиндр с основной областью из m дорожек. 645
Дорожка О Нормальный Элемент элемент переполнения Дорожка 7 | 010213 [данные о покупателях | 011120 Данные опрхупатгёёх^. . .\o2QQ28 Данные о покупателях ] Дорожка 2 \ 023612 | 1 \о2^121 | 1 |* • '\o28761 [Донные о покупателях} Дорожкат |ggyg75 | 1 |ggP7£J | 1 |- • • [(№4415 Данные о покупателях} Рис. 7-е. 1. Индекс дорожек и основная область индексно-последовательного файла Аналогично тому, как индекс дорожек описывает размещение записей на дорожках цилиндра, индекс цилиндров указывает, как записи размещаются на нескольких цилиндрах. Индекс цилин- дров ссылается на индексы дорожек — один элемент индекса цилиндров приходится на один индекс дорожек. Существует и самый высокий уровень в иерархической струк- туре индексов, называемый главным индексом. Главный индекс используют для чрезвычайно больших файлов, когда для поиска в индексе цилиндров требуется слишком много времени. Главный индекс образует корневой индекс дерева индексов, используемого для ускорения доступа к индексно-последовательному файлу. Зависимости между индексами различных уровней иллюстрирует рис. 7-6.2. Для нахождения записи покупателя с номером счета 089631 необходимо первоначально просмотреть главный индекс, чтобы определить соответствующий индекс цилиндров (т. е. индекс цилиндров 1). Затем поиск проводится в индексе цилиндров для нахождения цилиндра, на котором размещается запись (ци- линдр 1). Поиск в индексе дорожек дает номер дорожки, на ко- тги\ .hiSS, Глабный индекс Индекс иилиндроЬ 094415 4 121362 187129 267211 Ц 289316 ц 300210 4 30У312 ц 342168 467213 Ц 521664 792141 Ц 399213 « \ Цилиндр! - —"цилиндрП дорошо [(ш^^^ Дорожка 1 1010213 [данные] [ 820028 [данные] 1901362 [донные | Дорожка т 1063213 [донные 111• 1094415[донные[ [з9894б! Данные | 1921311 [данные | [ 399213[Данные [ Рис. 7-6.2. Связь между различными уровнями индексов 646
дорожка [Дорожка I [Дорожка О20028\ . 020028, . 1 1 ! Дорожка] !'Дорожка \Дортка\ [Дорож- 010213 } Данные о покупателе j 011120 J Данные о покупателе 020028 Данные о покупателе 023612 । Данные о покупателе 029121 | Данные о покупателе 026929 Данные о покупателе 089213 Данные о покупателе 089725 Данные о покупателе 099915 Данные о покупателе I 028761 Данные о покупателе. Дорожка 2 а) О •-• 09991^^^09^1^^ 010213 {Данные о покупателе 1. 011120 j Данные о покупателе 020028 | Данные о покупателе 021008 j Данные о покупателе 1 . 023612 } Данные о покупателе I 024121 } Данные о покупателе I 089213 {данные о покупателе 089725 Данные о покупателе 099915 Данные о покупателе ! \ Дорожка 028761 ! Данные о покупателе'^ i I ? ! [Дорожка m-fl 026929 j Данные о покупателе 1 , б> Рис. 7-6.3. Следствия переполнения в индексно-последовательном файле: а — добавление записи с ключом 026924; б — добавление записи с ключом 021008 торой размещается запись (дорожка ш), после чего, наконец, остается найти на дорожке нужную запись. Процесс поиска записи более точно описывается в алгоритме IS SEQUENTIAE, данном в этом параграфе после обсуж- дения записей переполнения. Стоит заметить, что главный индекс необходим далеко не всегда и его следует использовать только для больших файлов. Будучи запрошенным, главный индекс должен находиться в основной памяти в течение всего времени обработки индексно-последовательного файла. Прн добавлении новых записей к последовательному файлу необходимо создавать новый файл. Можно использовать подоб- ный подход при добавлении записей к индекс но-последовательному файлу. Однако из-за возможности прямого обращениями запи- 647
сям индексно-последовательные файлы используются в очень динамичных и требующих минимального времени ответа систе- мах, т. е. в системах с большим количеством добавлений и исклю- чений, обусловленных или'запросами в онлайновом режиме, или наличием небольших пакетов запросов. Такие исключения и добавления записей должны выполняться немедленно, а не в конце месяца. Проблемы добавления записей решаются созданием области или областей переполнения и, как правило, на том же устрой- стве, на котором располагается файл. Возможны два типа обла- стей переполнения — область переполнения цилиндра и неза- висимая область переполнения. Область переполнения цилиндра представляет собой несколько специально отведенных дорожек цилиндра, содержащего дорожки основной области. Если при добавлении записи создается переполнение дорожек цилиндра, принадлежащих основной области, то переполняющие записи размещаются в области переполнения цилиндра. Изменение структуры файла под влиянием записей перепол- нения иллюстрирует'рис. 7-6.3. Отметим, что мы делаем нереаль- ное, но заметно упрощающее картину допущение, размещая на одной дорожке только по три записи. Первоначально к файлу, изображенному на рис. 7-6.1, добавляется запись о покупателе с номером счета 026924. Чтобы сохранить упорядоченность за- писей на дорожке 2 основной области, запись с номером счета 028761 должна переместиться в область переполнения цилиндра на ш + 1-ю дорожку. Как следствие, вызываются два других изменения файла. -Во-первых, нормальный элемент в индексе дорожек для дорожки 2 должен быть изменен с 028761 на 026924, поскольку последнее число теперь становится самым большим ключом на дорожке. Во-вторых, согласовывается элемент пере- полнения так, чтобы его первое поле содержало значение самого большого ключа среди записей переполнения для дорожки 2 (т. е. число 028761), а второе поле устанавливается равным адресу дорожки, содержащей запись переполнения для дорожки 2 с наи- меньшим значением ключа. Запись переполнения идентична записи в основной области, за исключением того, что в конец записи переполнения добав- ляется поле связи с адресами дорожки и записи на дорожке. Поле связи содержит указатель на запись переполнения со следующим по значению ключом в списке записей переполнения для данной дорожки. Следовательно, когда добавляется запись с ключом 021008 и запись с ключом 026924 становится записью перепол- нения, то в ее поле связи помещается указатель на запись с клю- чом 028761. Рис. 7-6.3 дает упрощенную картину. В действитель- ности дорожек переполнения может быть несколько, и записи пере- полнения для каждой основной дорожки образуют связанные списки. Голова связанного списка задается адресом дорожка/за- пись в индексе дорожек. Последняя запись в этом списке специ- 648
фицируется установкой номера соответствующей основной до- рожки в поле дорожка/запись записи переполнения (например, в поле связи записи переполнения с ключом 028761 помещается номер дорожки 2). По мере добавления в индексно-последовательный файл все большего числа записей область переполнения исчерпывается. Если это случается, то новые записи переполнения передаются в независимую область переполнения, при условии, что такая об- ласть описана при создании файла. Под независимую область переполнения отводится цилиндр или группа цилиндров, не за- нятых под основную область. Записи в независимой области пере- полнения связываются точно так же, как и в области перепол- нения цилиндра. Заметим, однако, что для дисков с перемеща- ющимися головками использование независимой области пере- полнения может привести к обескураживающим результатам, по- скольку на перемещение считывающих головок между независи- мой областью переполнения и основной областью тратится много времени. До сих пор мы рассматривали добавление записей. Теперь ' обратимся к задаче удаления записей из файла. В индексно-по- следовательной организации, используемой фирмой IBM, уда- ляемые записи физически не исключаются из файла, а только помечаются как удаленные путем установки '11111111'В в пер- вом байте записи. Если затем к файлу добавляется новая запись с тем же самым ключом, что и удаленная запись, то пространство, занятое удаленной записью, используется повторно. Записи, попавшие в область переполнения, никогда не воз- вращаются обратно в основную область, несмотря на удаление в ней записей. Записи переполнения попадают в основную область только в результате реорганизации файла. При реорганизации записи файла последовательно копируются во временный файл, а затем файл создается заново последовательным копированием записей обратно в исходный файл. Так как доступ к записям пере- полнения может привести к большим накладным расходам, то необходимо тщательно следить за состоянием индексно-последова- тельного файла. Надежным и простым правилом при использова- нии дисков с перемещаемыми головками является проведение реорганизации файла, как только записи начинают попадать в независимую область переполнения. Временно отложим обсуждение индексно-последовательной организации фирмы IBM для краткого рассмотрения иной файло- вой’ структуры индексно-последовательного файла. Мы возвра- тимся к структуре файла IBM при обсуждении методов обработки индексно-последовательных файлов в следующем пункте. Операционная система SCOPE для ЭВМ CDC 6600 и семей- ства CYBER обеспечивает индексно-последовательную органи- зацию, значительно отличающуюся по структуре от используе- мой в Системах IBM. Индексно-последовательный файл в системе 649
нв Ключ Блек SIB2 Ключ Блок 0963/4 I РВ9~ >2317/ { SIBf Ключ Блок 361284 ] РВ19~ "''Область %4'. / '.сбибоднойЩ/ //',паь'яти г^'2 ________DB1________ Данные о покупателе с ponepoti 010123 лвз Ключ Блок DBm Данные о покупателе с номером 0/1120 Данные о покупателе I С номером020028 | Данные о покупателе I с номером 023012 | <Область свободной-4 ''/.’'уХ^НЯти 02002e(j023612ff Донные о покупателе I с номером 998946 Рис. 7-6.4. Индексные блоки и блоки данных в индексно-последовательном файле си- стемы SCOPE SCOPE состоит из блоков данных и индексных блоков. Блоки обоих типов существуют как логические записи. Размещение этих записей в основной памяти и передача из иее выполняются си- стемой SCOPE. Пользователь не имеет возможности контроли- ровать физическое размещение блоков на устройстве внешней памяти. Полный системный контроль физической памяти является необходимым, так как система SCOPE обеспечивает возможность разделения дисковых файлов в системах коллективного пользова- ния. Пользователю оставлено право управлять размерами индекс- ных блоков и блоков данных. Блок данных состоит из записей данных, ключей с указателями на записи данных внутри блока и свободного пространства, для размещения записей переполнения. Несколько блоков дан- ных изображено на рис. 7-6.4. Заметим, что пользователь может задавать размер свободного пространства в долях размера пол- ного блока (т. е. 0.5 означает, что половина блока данных отве- дена под свободное пространство). Индексные блоки образуют иерархическую древовидную струк- туру из ключей и указателей, во многом сходную с организацией индексной области для Системы IBM. Индексный блок содержит 650
пары из ключей и адресов и имеет .свободное пространство для добавления таких пар. Пара ключ/адрес состоит из наименьшего значения ключа в отдельном блоке данных или в индексном блоке «низшего уровня» и адреса блока, в котором располагается ключ. Рис. 7-6.4 демонстрирует связь между индексными блоками и блоками данных для файла с двухуровневой структурой индекс- ных блоков. Заметим, что пользователь может при создании файла выбирать размер свободного пространства индексных блоков и указывать число уровней индекса. На рис. 7-6.4 блок главного индекса МВ имеет ссылки на три подчиненных индексных блока SIB1, SIB2 и SIB3, которые, в свою очередь, указывают на блоки данных DB1, DB2, ...» DBm. Итак, мы снова описали отчасти нереальную ситуацию, по- мещая только по три записи в блок данных. Но, добавив две записи с ключами 010943 и 010000, мы сможем проиллюстриро- вать, как осуществляется управление записями переполнения в системе SCOPE. Рис. 7-6.5, а показывает локальный эффект в блоке от добавления записи с ключом 010943. На рис. 7-6.5. б dbi 3181 010000 | DB1 --------1------ 010903 ЮВгп+1 -——ч----------- 020028 J D82 ив 010000 SIB1 096319 S1B2 900100 sm (% Область %% Щсбободной'Л" /л памяти //0%, РВт+1 О) Рис. 7-6.5. Результаты добавления:, « — записи с клюнем 610943; 6 — записи с ключом 010000 651
изображены глобальные последствия в блоках DB1, SIB1 и МВ, вызванные добавлением записи с ключом 010000. Побочным про- дуктом последнего добавления является создание нового блока данных DBm + 1, содержащего половину записей, которые могли бы разместиться в DB1 при наличии там достаточного места. По мере создания новых блоков данных индексный блок запол- няется. Как и переполнение блоков данных, переполнение в ин- дексном блоке приводит к созданию нового блока, и половина индексных записей заполненного индексного блока перемещается в новый индексный блок. Расщепление переполненного блока позволяет исключить проблему непрерывного перемещения запи- сей переполнения из заполненного блока в отдельную область переполнения, как это приходится делать для основной области в Системе IBM. Естественно, что для расщепления требуется больше памяти, чем для обработки переполнений по одной записи, из-за необходимости резервирования дополнительного простран- ства. В системе SCOPE фирмы CDC удаленные записи обрабаты- ваются сборщиком мусора. Это значит, что пустоты, оставленные удаленными записями, зймещаются находящимися в блоке актив- ными записями с большими значениями ключа. Таким образом, как область с активными записями, так и область свободного пространства в блоке являются непрерывными. Перейдем к изучению типов обработки, выполняемой при ис- пользовании индексно-последовательных файлов. 7-6.2. Обработка индексно-последовательных файлов Теперь должно быть ясно, что организация индексно- последовательных файлов куда сложнее, чем последовательных. Как следствие этой сложности, в большинстве операционных систем имеются средства, или методы доступа, которые управляют изме- нениями в структуре файла, возникающими при включении и удалении записей. Ниже мы выделим аспекты обработки индек- сно-последовательных файлов, обычно выполняемые методами доступа, и аспекты, которыми занимается пользователь. г*” Основным преимуществом индексно-последовательных фап- лов является возможность как последовательной, так и прямой «обработки записей. В этом пункте мы опишем оба типа обработки и их осуществление в индексно-последователь ной организации файлов в Системе IBM. Разработка алгоритмов для последова- тельной и прямой обработки индекс но-последовательных файлов в системе SCOPE дана в качестве упражнения в конце этого пара- графа. Последовательная обработка индексно-последовательного файла логически идентична последовательной обработке последо- вательного файла, т. е. записи обрабатываются в порядке, опреде- 652
ляемом индексным элементом. Допускаются такие типы транзак- ций, которые вызывают чтение, изменение, добавление и удаление записей. На уровне пользователя для обработки последователь- ных файлов служат предложения READ, WRITE и REWRITE в том виде, как они описаны в п. 7-4. Хотя обычно типы транзак- ций и операции, выполняющие транзакции этих типов, одинаковы для последовательных и индексно-последовательных файлов, способ доступа к записям существенно различный вследствие раз- ницы в структурах файлов. Алгоритм IS___SEQUENTIAL в общих чертах обрисовывает последовательную обработку записей индексно-последовательного файла. В алгоритме мы намеренно не описали тип обрабатываемых транзакций. В часто встречающемся случае обеспечивается об- работка транзакций добавления, удаления и изменения записей. Обработка этих транзакций описана в деталях в алгоритме SEQUENTIAL-PROCESS в п. 7-4 и поэтому здесь не представ- лена. Вместо этого предложение «обработка транзакции» указы- вает ту часть алгоритма, в которой должна находиться обработка транзакции. Алгоритм IS____SEQUENTIAL показывает, наско- лько различен последовательный доступ к записи в последова- тельном и в индексно-последовательном файлах. Алгоритм IS___SEQUENTIAL. Для заданного индексно-по- следовательного файла MASTER с записями, каждая из которых состоит из ключа и нескольких информационных элементов, ал- горитм обеспечивает последовательный доступ к записям. В про- цессе обработки файла MASTER записи читаются в переменную MTR—REC, содержащую элегченты KEY и INFO. Для обозна- чения записи из основной области служит переменная PRIME____ REC, состоящая из подэлементов KEY и INFO. Индекс дорожек TRK—INDEX рассматривается как вектор длиной т, в котором каждый компонент содержит четыре элемента: NORMAL-KEY, NORMAL— ADDR, OVFLOW—KEY и OVFLOW-ADDR. Пере- менная OVFLOW—REC состоит из элементов KEY, INFO и LINK и обозначает запись переполнения. Для данной записи пере- полнения LINK содержит адрес дорожка/запись следующей за- писи с большим значением ключа. Переменные CYL и TRK указы- вают цилиндр и дорожку, к которым осуществляется доступ. ADDR является специальной переменной типа указатель, содер- жащей адрес дорожка/запись следующей обрабатываемой записи. Неявно подразумевается, что ADDR инициализируется так, чтобы указывать на первую запись файла в момент его от- крытия. 1. [Инициализация.] Открыть входной файл MASTER. 2. [Обработка записей, начиная с первого цилиндра (пред- ставленного к) до последнего цилиндра (представленного п), как это определено индексом. ] Повторять шаги от 3 до 5 при CYL = к, к + 1> п. 653
3. [Обработка записей дорожек основной области и связанных с ними записей переполнения для данного цилиндра. ] Повторять шаги от 4 до 5 при TRK = 1, 2, ..., гл. 4. [Чтение и обработка записей на основной дорожке. ] Уста- новить ADDR, на первую запись на дорожке TRK- Повторять до тех пор, пока KEY из PRIME—REC (ADDR) Ф NORMAL—. KEY из TRK—INDEX [TRK]: читать PRIME—REC, указан- ную ADDR из файла MASTER в MTR—REC; установить ADDR на начало следующей записи дорожки; обработать транзакции, предназначенные для MTR—REC. Читать PRIME___REC, указанную ADDR из файла MASTER в MTR—REC. (Обработка последней записи.) 5. [Чтение и обработка связанного списка записей перепол- нения.] Если NORMAL_____KEY из TRK______INDEX [TRK1 Ф Ф OVFLOW—KEY из TRK—INDEX [TPKL то установить ADDR ч-OVFLOW—ADDR из TRK—INDEX (TRK], инициа- лизировать ADDR; повторять до тех пор, пока ADDR Ф TRK: читать OVFLOW—REC, указанную ADDR из файла MASTER в MTR—REC; установить ADDR ч- LINK из OVFLOW______REC; обрабо- тать транзакции, предназначенные для MTR—REC. 6. [Конец.] Выход. Алгоритм IS___SEQUENTIAL начинается с выделения бу- ферной области для обработки файла MASTER. Записи для от- дельной основной дорожки обрабатываются в шаге 4. Обратите внимание на использование переменной типа указателя ADDR при ссылке на отдельную запись основной области [т. е. PRIME— REC (ADDR)]. В шаге 5 записи переполнения читаются следуя цепочке указателей в записях из области переполнения цилиндра и независимой области переполнения, если она используется. Процесс продолжается для всех дорожек всех цилиндров, содер- жащих индексно-последовательный файл. Алгоритм предполагает, что для индексно-последовательного файла отводится целое число цилиндров. Это предположение справедливо для Системы IBM. Алгоритм IS___SEQUENTIAL иллюстрирует сложность про- цесса, происходящего при простом чтении следующей по порядку записи. Однако для пользователя последовательная обработка индексно-последовательного файла ничем не отличается от после- довательной обработки последовательного файла, упорядоченного по выбранному ключу. Пользователю достаточно двух шагов: 1. Открыть входной файл MASTER. 2. Повторять до тех пор, пока не кончится файл MASTER: читать следующую запись; обработать транзакции, предназначен- ные для MTR—REC. Следовательно, средства доступа системы автоматически обес- печивают приращение CYL и TRK и модификацию переменной ADDR в алгоритме IS____SEQUENTIAL. 654
Прямая обработка индексно-последовательного файла сущест- венно отличается от только что обрисованной последовательной обработки. Алгоритм 15— DIRECT показывает, как выполняется прямая обработка для заданной транзакции, предписывающей чтение, изменение или удаление существующей записи. Алгоритм предполагает, что каждый раз на входе имеется только одна тран- закция. Это допущение соответствует реальности, поскольку на практике прямая обработка используется, если для многочислен- ных независимых запросов необходимо малое время ответа и отсутствует достаточное время на группирование запросов с це- лью достижения системой большой пропускной способности. За- пись транзакции имеет форму (запись транзакции} : : = (тип транзакции) (ключ) (инфо), где (тип транзакции) и соответствующее поле (инфо) даны в табл. 7-6.1. Т а б л и ц а 7-6.1 Типы транзакций (тип транзакции) (ключ) (инфо) READ UPDATE DELETE ADD только ключ записи (без (инфо)) ключ и новые элементы данных только ключ записи (без (инфо)) ключ и информационные элементы новой записи Алгоритм 1S__DIRECT. Для заданного индексно-последова- тельного файла MASTER с записями, каждая из которых состоит из ключа и набора информационных элементов, алгоритм реали- зует прямой доступ к записям и выполняет над этими записями действия, заданные транзакциями. Индекс цилиндра CYL_INDEX рассматривается как вектор длиной р, в котором каждый элемент содержит два элемента CYLNO (означающий номер цилиндра) и KEY. Записи транзакций читаются в переменную TRAN—R ЕС, состоящую из элементов TRAN, KEY и INFO. Остальные пере- менные играют ту же роль, что и в алгоритме IS._SEQUENTIAL. 1. [Инициализация. ] Открыть файл MASTER для обновления и прочитать TRAN____REC. 2. [Поиск в индексе цилиндров.] Повторять прн i = 1, 2, ..., р — I: если KEY из TPAN—REC KEY из CYL_INDEX li], то установить CYL CYLNO из CYL_INDEX [i ] и перейти к шагу 3. Установить CYL CYLNO из CYL_________INDEX [р ]. 3. [Поиск в индексе дорожек цилиндра.) Повторять при j = I, 2, ..., ш: если KEY из TRAN—REC =& OVFLOW—KEY из TRK— INDEX [j], to 655
если KEY из TRAN—REC ==s NORMAL—KEY из TRK _ INDEX [j ], то установить ADDR NORMAL—ADDR из TRK—INDEX [j], и перейти к шагу 5; установить ADDR«-OVFLOW_ADDR из TRK-INDEX [j], и перейти к шагу 6. 4. [Сравнение ключей. ] Если TRANS — 'ADD’, то установить KEY из CYL_____ INDEX [p]-<-KEY из TRAN—REC, вызвать 1S_OF— INSERT, и закончить выполнение алгоритма; иначе, печатать 'НЕЗАКОННАЯ ТРАНЗАКЦИЯ’ и закон- чить выполнение алгоритма. 5. [Определение места записи в основной области. ] IADDR первоначально указывает на первую запись на дорожке основной области). Если TRANS = 'ADD', то вызвать IS__PRIME- INSERT и закончить выполнение алгоритма. Повторять до тех пор, пока KEY из PRIME—RЕС (ADDR) < KEY из TRAN_ REC: установить ADDR на начало следующей записи основной дорожки. Если KEY из PRIME—RЕС (ADDR) = KEY из TRAN—REC, то перейти к шагу 7. Печатать 'В ФАЙЛЕ ОТСУТСТВУЕТ ЗАПИСЬ С КЛЮЧОМ ТРАНЗАКЦИИ' и закончить выполнение алгоритма. 6. [Определение места записи в области переполнения. ] (ADDR первоначально указывает на первую запись в списке за- писей переполнения). Если TRANS — 'ADD', то вызвать IS _OF__INSERT и закончить выполнение алгоритма. Повторять до тех пор, пока KEY из OVFLOW_REC"(ADDR) <KEY из TRAN—REC: 4 • установить ADDR LINK из OVFLOW—REC (ADDR). Если KEY из OVFLOW—REC (ADDR)=/=KEY из TRAN—REC, то печатать 'В ФАЙЛЕ ОТСУТСТВУЕТ ЗАПИСЬ С КЛЮЧОМ ТРАНЗАКЦИИ', и закончить выполнение алгоритма. 7. [Обработка транзакции. ] Читать запись на дорожке, ука- занную ADDR из файла MASTER в MTR—REC. Если TRANS из TRAN___REC = 'DELETE', то удалить за- пись на дорожке, указанную ADDR из файла MASTER и закон- чить выполнение алгоритма. Если TRANS из TRAN—REC = 'ALTER', то переписать запись, указанную ADDR в файле MASTER с KEY и INFO из TRAN—RЕС и закончить выполнение алгоритма; Печатать ^'НЕПРАВИЛЬНАЯ СПЕЦИФИКАЦИЯ ТРАН- ЗАКЦИИ' и закончить выполнение алгоритма. Прежде чем описывать алгоритм IS__ DIRECT, следует ука- зать, что главный индекс в алгоритме не используется. Если же он необходим, то перед выполнением шага 2 требуется добавочный шаг для нахождения соответствующего индекса цилиндров. 656
Алгоритм начинается с открытия файла MASTER и чтения записи транзакции. Положение записи файла MASTER, соответ- ствующей ключу в транзакции, определяется по таблицам индекса цилиндров и индекса дорожек в шагах 2 и 3. Шаг 4 необходим для редкой, но возможной ситуации добавления записи с ключом, большим ключей всех других записей файла. В этом случае ин- декс цилиндров необходимо изменить, а запись добавить в область переполнения последнего цилиндра. Шаги 5 и 6 служат для ло- кализации требуемых записей для транзакций чтения, удаления и изменения в основной области и области переполнения соответ- ственно. В шаге 7 запрошенная транзакция выполняется. В алгоритме IS____DIRECT используются две процедуры, описы- ваемые алгоритмами IS___PRIME—INSERT н IS_____OF—INSERT. Алгоритм IS—.PRIME_____INSERT описывает нахождение места записи на основной дорожке, на которой необходимо поместить запись из транзакции. Если фиктивная или удаленная запись имеет тот же самый ключ, что и ключ транзакции, то новая запись раз- мещается на ее место. Под фиктивной записью мы понимаем запись, помещаемую в файл обычно при его создании и не содер- жащую никакой значащей информции, кроме своего ключа. Такая запись создается в предвидении того, что позже на этапе использования файла она будет заполнена значащей инфор- мацией. Если фиктивные или удаленные записи, соответствующие ключу транзакции, отсутствуют, то записи с ключом, большим ключа транзакции, сдвигаются дальше по основной дорожке, освобо- ждая место для размещения записи. Результатом такого переме- щения записей является пересылка в область переполнения по- следней записи на дорожке. Эта новая запись переполнения со- держит наименьший ключ среди записей переполнения для рас- сматриваемой основной дорожки. Следовательно, адресное поле соответствующего элемента переполнения индекса дорожки дол- жно быть изменено, чтобы указывать на новую запись перепол- нения. Формальное, шаг за шагом описание алгоритма IS-PRIME—INSERT оставлено в качестве упражнения. Однако второй алгоритм для включения записи — алгоритм IS____OF___ INSERT описан ниже. Алгоритм IS___OF._INSERT. В ADDR задан адрес доро- жка/запись первой записи переполнения некоторой основной дорожки j. Новая запись, содержащаяся в записи транзакции, включается в соответствующем месте упорядоченного списка записей переполнения для данной дорожки. Адрес дорожка/за- пись, по которому размещена новая запись, назначается перемен- ной NEWLOC. PREVADDR служит в качестве временного адреса при включении новой записи в связанный список записей перепол- нения. OVFLOW—AVAIL указывает на место для следующей записи в списке свободных мест для размещения записей пере- полнения. 657
I. [Размещение новой записи в следующем свободном месте для записи переполнения. 1 Установить NEWLOC OVFLOW— AVAIL и модифицировать указатель OVFLOW— AVAIL. Писать KEY и INFO из TRAN—REC в файл MASTER по адресу NEWLOC. 2. [Инициализация поиска места новой записи в списке пере- полнения.] Если KEY из OVFLOW______REC (ADDR) > KEY из TRAN— REC (добавление'' в начало списка), то установить OVFLOW—ADDR из TRK—INDEX [j]^-NEWLOC, устано- вить LINK из OVFLOW—REC (NEWLOC) ADDR и закон- чить выполнение алгоритма. 3. [Включение в середину списка переполнения.] Установить PREVADDRADDR и ADDR LINK из OVFLOW—REC (ADDR). Повторять до тех пор, пока KEY из OVFLOW—RЕС (ADDR) Ф OVFLOW_____KEY из TRK—INDEX [j] (выполнять, пока не кончится список): если KEY из _ OVFLOW—RЕС (ADDR) KEY из TRAN___REC, то установить LINK из OVFLOW—REC (PREVADDR) ч- NEWLOC, установить LINK из OVFLOW___________REC (NEWLOC) ADDR и закончить выполнение алгоритма; установить PREVADR ч— ADDR, и ADDR LINK из OVFLOW—REC (ADDR). 4. [Включение в конец списка переполнения]. Установить LINK из OVFLOW—RЕС (ADDR) NEWLOC, установить LINK из OVFLOW_____REC (NEWLOC) j (Запись в поле связи номера основной дорожки.) Установить OVFLOW______KEY из TPK—INDEX [j] л- KEY из TRANS—REC. В алгоритме предполагается, что специальный указатель OVFLOW____AVAIL используется при управлении имеющейся в наличии памяти в области переполнения. Логика алгоритма IS—OF INSERT почти не отличается от логики большинства алгоритмов, представленных в и. 4-4.1, и, следовательно, должна быть понятна без детального пояснения. Из предыдущего должно быть ясно, что процедуры управления файлами, которые пользователь должен написать, в случае отсут- ствия системных методов доступа для индексно-последовательных файлов будут очень сложными. Однако, если поиск и управле- ние записями выполняются системными средствами как это обычно и делается), мы можем описать команды иа уровне пользователя для прямой обработки индексно-последовательного файла сле- дующими шестью шагами. 1. Открыть обновляемый файл MASTER и прочитать TRAN—R ЕС. 658
2. Если TRANS = 'ADD', то писать INFO из TRAN___REC p ключом из TRAN—REC в файл MASTER и закончить выпол- нение алгоритма. 3. Читать из файла MASTER в MTR____REC с ключом из TRAN—R ЕС. 4. Если TRANS — 'ALTER', то переписать INFO из TRAN—REC с ключом из TRAN—REC в файл MASTER. 5. Если TRANS = 'DELETE', то удалить из файла запись с ключом KEY из TRAN—REC и закончить выполнение алгоритма. 6. Если TRANS 'READ', то печатать 'НЕДОПУСТИМАЯ ТРАНЗАКЦИЯ'. Выход. Заметим, что перед тем как удалять или переписать запись, мы ее читаем. Это сделано намеренно для обеспечения совпадения с методом доступа, используемым в Системах IBM. Предваритель- ное чтение необходимо для нахождения записи. Если запрашива- емая запись отсутствует, система вырабатывает соответствующий код состояния. Пользователь может перехватить этот код и таким образом избежать выполнения команд удаления или перезаписи, которые могут вызвать ошибки системы. При обсуждении в сле- дующем пункте средств обработки индексно-последовательных файлов в ПЛ/1 мы продемонстрируем, как можно использовать коды состояния. Завершая этот пункт, подведем итоги и перечислим главные свойства, характерные для индексно-последовательных файлов. 1. Индексно-последовательные- файлы обеспечивают сравни- тельно быстрый доступ к записям при использовании как после- довательной так и прямой обработки. 2. Для достаточно статичных файлов можно не заводить неза- висимой области переполнения, а область переполнения цилиндра сделать минимальной, достигая, таким образом, высокой степени использования дисковой памяти. 3. Для сильно изменчивых файлов время доступа к записи становится чрезмерно большим при заполнении области пере- полнения. 4. Индексно-последовательные средства доступа обычно обес- печиваются во многих системах, тем самым освобождая програм- миста от большого числа обременительных деталей при ведении индексов и связей в областях переполнения. На уровне програм- миста последовательная обработка индексно-последовательного файла выглядит идентично с последовательной обработкой после- довательных файлов. 7-6.3. Индексно-последовательная организация файлов в ПЛ/1 Индексно-последовательный файл идентифицируется в программе на языке ПЛ/1 предложением описания файла сле- дующего вида: DECLARE MASTER FILE RECORD KEYED ENV (INDEXED); 659
Два других набора атрибутов появляются либо в предложении описания файла, либо в OPEN-предложении для файла. Этими наборами атрибутов являются атрибуты, задающие тип доступа (DIRECT или SEQUENTIAL), и атрибуты, определяющие режим обработки (INPUT, OUTPUT или UPDATE). Например, при создании индексно-последовательного файла, скажем, MASTER, мы используем либо предложение OPEN FILE (MASTER) SEQUENTIAL OUTPUT; либо добавляем атрибуты SEQUENTIAL и OUTPUT при описа- нии файла. Вспомним, что все индексно-последовательные файлы должны создаваться последовательно, после чего можно получить к ним последовательный или прямой доступ. Для операций над индексно-последовательными файлами в язы- ке ПЛ/1 имеется набор различных предложений ввода-вывода. Табл. 7-6.2 дает список этих предложений для файла CUSTOMER Таблица 7-6.2 Предложения ввода-вывода для индексно-последовательных файлов в языке ПЛ.'1 Режим обработки Тип доступа Цель применения Возможные утверждения ввода -вывода OUTPUT SEQUENTIAL Создание ново- го индексно-гю- следовател ьного файла WRITE FILE (CUSTO- MER) FROM (CUST); /*ПЛИ*/ WRITE FILE (CUSTO- MER) FROM (CUST) KEY- FROM (ACCT NO); INPUT SEQUENTIAL Последовател ь - ная обработка всех записей READ FILE (CUSTOMER) INTO (CUST); /»ИЛИ*/ READ FILE (CUSTOMER) INTO (CUST) KEYTO (ACCT_ NO); INPUT DIRECT Выборочная обработка запи- сей READ FILE (CUSTOMER) INTO (CUST) KEY (ACCT—NO); UPDATE SEQUENTIAL Изменение всех записей REWRITE FILE (CUSTO- MER); /*или*/ REWRITE FILE (CUSTO- MER) FROM (CUST); UPDATE DIRECT Изменение от- дельных записей REWRITE FILE (CUSTO- MER) FROM (CUST) KEY (ACCT- NO); UPDATE DIRECT Добавление к файлу новых записей WRITE FILE (CUSTO- MER) FROM (CUST) KEY- FROM (ACCT—NO); UPDATE DIRECT Удаление из файла отдельных записей DELETE FILE (CUSTO- MER) KEY (ACCT. NO); 660
с ключом, содержащимся в АССТ—NO. Запись CUST содержит информацию, идентифицируемую ключом АССТ—NO. Использование большинства приведенных в таблице предло- жений иллюстрируется в программе на ПЛ/1, данной на рис. 7-6.6. Программа состоит из трех главных сегментов. Первый сегмент создает файл CUSTOMER последовательно. Второй сег- мент модифицирует записи файла CUSTOMER, используя пря- мой доступ, аналогично тому, как это описано в последнем пункте в алгоритме IS_DIRECT. Тип транзакции определяется одним символом: 'А' означает добавить, 'С' — изменить, 'D' — удалить и 'R' — прочитать. Еще раз заметим, что перед перезаписью или удалением запись должна быть сначала прочитана. В третьем сегменте записи файла читаются последовательно и их содержимое печатается. Заметим, что при обработке файла, открытогос атрибутом SEQUENTIAL, используются предложения READ с группой KEYTO. Такая форма предложения READ необходима, если ключ не встроен в саму запись. Группа KEYTO (АССТ__NO) обеспечивает чтение невстроенного в запись ключа в переменную АССТ__NO, что позволяет печатать номер счета наряду с другой, относящейся к клиенту, информацией. Если же опустить KEYTO, то мы не сможем восстановить помер счета, поскольку он отсутствует в записи с информацией о клиенте. Различия в структуре между записями с встроенными и не- встроенными ключами иллюстрируют рис. 7-6.7 и 7-6.8. Для указания системе управления файлами положения и длины встро- енного в запись ключа используются параметры RKP (относи- тельная позиция ключа) и KEYLEN (длина ключа). Предполо- жим, требуется ввести ключ АССТ___NO в структуру CUST. Тогда структуру CUST можно описать следующим образом: DECLARE 1 CUST, 2 DELETE—FLAG BIT (8), 2 ACCT—NO CHARACTER (6), 2 INFO, 3 NAME CHARACTER (20), 3 BALANCE FIXED DECIMAL (7,2); В качестве параметров блока DCB в этом случае должны быть установлены RKP = 1 и KEYLEN = 6. Параметр RKP задает число байтов от первого байта записи до байта, с которого начи- нается встроенный в запись ключ. Нулевое значение RKP имеет иной смысл и указывает на то, что ключ не встроен в запись. Минимальная величина RKP для записей переменной длины равна 4 и подразумевает невстроенный ключ. Излишек в четыре байта предназначен для указателей длин блока и записи, которые должны сопровождать запись переменной длины. DD -предложения для файла CUSTOMER на рис. 7-6.6 ука- зывают системе структуру индексно-последовательного файла. 661
ft EXEC PL1LFCLG 7/PHL.SYSlN DO • INDX„EG: PROCEDURE OPTIONS(MAIN); 7* INITIALIZATION */ DECLARE CUSTOMER FILE RECORD KEYED ENVIINDEXED F(77O,771), TRANFILE FILE STREAM INPUT, 1 CUST, 2 DELETE„FLAG BIT(8), 2 INFO, 3 NAME CHARACTER(201, 3 ADDRESS, 4 LINE1 CHARACTER(20) , 4 LINE2 CHARACTER(19) , 4 POSTAL„COOE CHARACTER!7), 3 BALANCE FIXED DECIMAL!7,2), ACCT„ND CHAR (6), 7* USED AS A KEY ITEM FOR RECORDS */ CODE CHARACTER!1), LAB(0:4) LABEL? OELETE_FLAG = (8)’0»B; /* CREATING FILE ♦/ ON ENDFILE(SYSIN) GO TO UPOTE; OPEN FILE(CUSTOMER) SEQUENTIAL OUTPUT; DO WHILE !’L*B); 7* UNTIL ENO OF FILE SYSIN *7 GET EDIT (ACCT„NO,CUST.INFO) (COL(1),ACfc),2 A(20),A(19),A(7),F|7,2)); WRITE FILE!CUSTOMER) FROM(CUST) KEVFROM(ACCT_NO); ENO! UPDTEs CLOSE FILE(CUSTOMER); /* UPDATING FILE ♦/ OPEN FILE(CUSTOMER) DIRECT UPDATE; OPEN FILE!TRANFILE); ON ENDFILE(TRANFILE1 GO TO OTPTI ON KEY(CUSTOMER) BEGIN; IF ONCODE = 51 THEN PUT SKIP LIST!ACCT„NO,’ NOT FOUND*); IF ONCODE = 52 THEN PUT SKIP LIST!’DUPLICATE ACCOUNT*,ACCT_NO)i ENO; DO WHILE(*1*B); GET FILEITRANFILE> ED1T{CODE)(COL(1),A(1}); I = INDEX!’ACOR’,CODE ); GO TO LAB(I); LAB(O); /* ILLEGAL TRANSACTION CODE *7 PUT SKIP LIST!’ILLEGAL TRANSACTION CODE ’.CODE); GO TO NEXT„TRAN: LAB(l): /* A - ADDITION TRANSACTION *7 GET FILE(TRANFILE ) EDIT!ACCT„NO,CUST.INFO) Рис. 7-6.6. Пример программы на языке ПЛ/1, в которой используется индексно-после- довательный файл 662
t а(6)«Д(2оьа(2оьа(19),д(тья т.гт WRITE FILE(CUSTOMER) FROMICUST) KEYFROMIACCT_NO); f GO TO NEXT,. TRAN: LABI 2)s /* C - CHANGE OR ALTER TRANSACTION */ GET FILE!TrANF ILE ) EOIT( ACCT_NOК A( 6 ) ) *, REAO FILE!CUSTOMER) INTOICUST) KEY(ACCT_NO)t GET FILE{TRANFILE I EDIT!NAME.ADDRESS)!2 Al20),Al191,A{7)); REWRITE FILE I CUSTOMER) FROM!CUST> KEYtACCT_NO); GO TO NEXT TRAN; LABI 31s /* D - DELETE TRANSACTION */ GET FILE!TRANFILE) EOtT{ACCT_NC)!AI6)1; READ FILE!CUSTOMER) INTOICUST) KEY! ACCT, NO) : DELETE FILE{CUSTOMER) KEY!ACCT_NO); GO TO NEXT_TRAN: LABI 4): /* R - READ TRANSACTION */ GET FILE!TRANFILE) EDIT!ACCT_NO)IA(6)); READ FILEICUSTOMER) INTOICUST) KEY!ACCT^NO)5 NtXT_TRANi END; /* DO WHILE PROCESSING TRANFILE */ OTPT: CLOSE FILEiCUSTOMER Is OUTPUT FILE OPEN FILEICUSTOMER) INPUT SEQUENTIAL; ON ENDFILEICUSTOMER) STOP; DO WHILEI'l'B); /♦ UNTIL ENO OF CUSTOMER FILE */ READ FILEICUSTOMER) INTO(CUST) KEYTOI AC CT., NO I; PUT SKIP EDIT!ACCT_NO.CUST.INFO)I 5 IA.XI1)), FI7.21): END; END INDX.EC; //GO.CUSTOMER CD DSNAME =BI LLINGI INDEX),D1SP®I NEW,KEEP),UNIT«SYSDA, // V0L = SER=USER02.DCB = t DSORG=IS,RKP=0,KEYLEN=fc,OPTCD-LI,SPACE=ICYL,1) // DD DSNAME = BILLINGlPRIME I,0ISP=(NEW,KE EP),UNIT=SYSDA, // VOLsSER=USER02 ,OCB=IDSORG=l5,RKP=O,KEYLEN=6,OPTCD-L ) ,SPACE= I CYL. 3 I // DO DSNAME=BILLINGIOVFLOW),D!SP=fNEW,KEEP).UNIT-SYSOA, // VOL=SER=USERD2,OCB=(DSORG=IS,RKP=0,KEYLEN=6,OPTCD=LI.SPACE=ICYL . 1) //G9.SYSIN DO * 134679MR. JOHN Q. BROWN 445 5TH AVE. N. SASKATOON, SASK. S7K 2H6 123.45 654821M«S. ALICE W. SMITH 2B5 VANCOUVER AVE. WINNIPEG, MAN. R4T 5S5 512.34 753981M9RE 0 THE SAME 2 LITTLE DATUM RO. ANYTOWN, ANYWHERE A1A 1A1 9bZ6J>4MR GIGO DATA 12345 DATA AVE DATA, DATA D5A 4TI 123.56 //GO.TRANFILE DD * A321548MR. JOHN W. SMITH 321 MONTREAL AVE. CALGARY, ALTA. R4T 2D932L.54 Rb46 79 j9o7c> 54 C&54821MRS. ALICE w. SMITH 286 VANCOUVER AVE. WINNIPEG, MAN. R4T 5S5 Rhc. 7-6.6. Продолжение 663
kwiS"”] 1л t - |A/pw^| | \Мация | Г °) p Информация -j p Информация —। p Информация —। Млюч! j |/frwwj IXffWVl j р/7/оу! ~ ~ f Тот же самый ключ 5) F44 — Тот же самый ключ —* В) р Информация—\ р Информация —j р Информация —। |л>7лзу[ l/frwvl i ptwwi j j/fg/wl j Тот же самый ключ -- г; l/tooy j j ЛДЛ7У| I рис. 7-6.7- Структура записей фиксированной длины в индексно-последовательном файле: и — иееблокированные ваписи — неустроенные ключи; б — несблокированные за- писи — встроенные ключи; в — сблокированные записи — невстроеняые ключи; г — сблокированные записи — встроенные ключи р Информация -j 1клю«Л б£ I /?11 1Нлюч1 j 111 ' ' '} , ’----------------1-Тот же самый ключ а) рИнформация -| ^-Информация q ^-Информация I---1—|—I--------1----1——I—I------[---1—~Т—i-------1---1— |/t7z/wl& |/?М \Ключ\ Ifflj l/frwyl Iffl-l 1K/7/WI — — —- Тот же самый ключ -----—-----------Т б) ।——[------Информация |/toovl 6Z. IRL Ift/w1/! ' j —Тот же самый ключ б) Информация----т Информация Информация I 1 1 П 1 I "Г МЛюч! IRL \Ключ\ I RL I L Тот же самый ключ Рис. 7-6.8. Структура записей переменной длины в индексно-последовательном файле: а — несблокированные записи переменной длины. RKP >4: б — сблокированные записи переменной длины, RftP > 4; о — несблокированные записи переменной длины, RKP — 4; г — сблокированные записи переменной длины, RKP — 4 664
Параметры INDEX, PRIME и OVFLOW для DSNAME описы- вают три различные области файла: в DD-предложении с пара- метром INDEX описывается пространство файла, необходимое для индекса цилиндров, в DD-предложении с параметром PRIME — пространство, необходимое для основной области, и в пред- ложении с параметром OVFLOW пространство, необходимое для независимой области переполнения. Для области перепол- нения цилиндра место отведено не было. Если же мы хотим соз- дать файл без индекса цилиндров и без независимой области пере- полнения, но с областью переполнения цилиндра, состоящей из трех дорожек на цилиндр, то можно использовать следующее D D-предложение: //GO.CUSTOMER DD DSNAME = BILLING (PRIME), // DISP = (NEW, KEEP). // UNIT = SYSDA, VOL = SER - USER02, Ц DCB = (DSORG = IS, // RKP = 0, KEYLEN = 6, CYLOFL = 3), // SPACE = (CYL, 3) При обработке файлов в ПЛ/1 очень важна форма ON KEY предложения ON. Ее применение проиллюстрировано на рис. 7-6.6. В случае возникновения исключительного состояния при выполнении предложения ввода-вывода, это состояние реги- стрируется операционной системой и вырабатывается код состоя- ния. Программа на ПЛ/1 может «перехватить» это исключительное состояние, используя форму ON KEY предложения ON. Код состояния в ПЛ/1 назначается специальной псевдоперемеиной ONCODE, применяемой для предупреждения об ошибках ввода- вывода с тем, чтобы можно было предпринять альтернативное действие. Список возможных значений ONCODE, которые можно проверять в этом случае, следующий: 50 KEY сигнализировано, используется для тестирования условий ON KEY 51 Запись с ключом не найдена 52 Попытка добавить дублированный ключ 53 Ошибка в последовательности ключей при создании ин- дексно-последовательного файла 54 Ошибка преобразования ключа, литерная строка не может использоваться в качестве ключа 55 Ошибка спецификации ключа 56 Диапазон значений ключа выходит за пределы набора данных (код используется в региональных файлах) 57 В файле отсутствует место для добавления записи с клю- чом (или место отсутствует в пределах, заданных подпа- раметром LMTCT). В качестве заключительного комментария, относящегося к про- грамме, представленной на рис. 7-6.6, необходимо указать, что • 665
программист песет ответственность за резервирование однобайто- вого поля для признака удаления, если во время обработки файла в режиме UPDATE DIRECT требуется удалять записи. Предложение DELETE помечает запись как_пустую установкой значения (8) 1'В' в первом байте записи. Имеется много других интересных возможностей обработки ин- дексно-последовательных файлов. Например, параметр GENKEY (общий ключ) атрибута ENVIRONMENT позволяет осуществлять доступ к записям согласно их классу ключей, определяемому общим ключом. Например, ключи записей '692138', '693112', '694761' и '698882' являются членами класса ключей, идентифи- цируемого общим ключом '69'. Для иллюстрации того, как можно использовать параметры GENKEY, рассмотрим следующие предложения ПЛ/1: DECLARE CUSTOMER FILE RECORD SEQUENTIAL KEYED UPDATE ENV (INDEXED GENKEY); READ FILE (CUSTOMER) INTO (CUST) KEY (’69’); LOOP: READ FILE (CUSTOMER) INTO (CUST); GO TO LOOP; Первое READ локализует и читает первую запись с ключом, начинающимся кодом'69'. Второе READ читает остающиеся за- писи с ключами, принадлежащими классу с общим ключом '69'. Для получения более глубокого представления о создании и использовании индексно-последовательных файлов в ПЛ/1 сле- дует изучить соответствующие разделы литературы 114 J. А теперь обратимся к реальной задаче, которая поможет про- иллюстрировать концепции, представленные в этом параграфе. Упражнения к п. 7-6 1. Сформулируйте алгоритм IS—SEQ—SCOPE, который бы отра- зил последовательную обработку записей в индексно-последовательном файле системы CDC SCOPE. 2. Сформулируйте алгоритм IS—DIR—SCOPE, описывающий прямую обработку записей индексно-последовательного файла системы CDC SCOPE. 3. Сконструируйте алгоритм IS—PRIME—INSERT, который должен раз- мещать записи, добавляемые к индексно-последовательному файлу (версии IBM) в соответствующем месте основной области. 4. Допустим, что мы пользуемся только последовательным доступом к опре- деленному файлу. При каких условиях, если они вообще существуют, предпоч- тительнее использовать файл, организованный индексно-последовательно, не- жели применять последовательную организацию файла? 5. Важной задачей, решаемой во многих системах, связанных с бизнесом, является анализ продаж. Анализ продаж обеспечивает администрацию обзором данных о продажах по покупателям, статьям и продавцам. Имеется два основных метода ведения данных о продажах — детальный метод и интегральный метод. 666
При использовании детального подхода подробная информация о сделках на- капливается до конца какого-то определенного периода (скажем, месяца), по истечении которого она сортируется и затем анализируется для получения ин- формации о покупателях, проданных товарах и продавцах. Очевидно, что для хранения такой накапливаемой информации можно использовать последова- тельный файл. Интегральный метод базируется иа данных о продажах, накапливаемых ежедневно из информации, содержащейся в заказах иа покупку, накладных и в инвентарной отчетности. В ответ иа отдельные запросы должна возвращаться информация о покупателях, продавцах и статьях продаж па текущий день. Для гарантии быстрого ответа на такие запросы следует выбрать индексно-последо- вательную организацию. Напишите программу на ПЛ/1, которая использует в качестве входной информацию, получаемую из накладных покупателей: номер продавца, номер счета покупателя, количество проданного (или возвращенного) товара для каж- дого из типов товаров А, В, С и D и общее количество проданного (или возвра- щенного) товара. Для сводных отчетов необходимо создать и вести два файла — файл покупателей и файл продавцов. В файле покупателей, где ключом является иомер счета покупателя, содержатся аккумулированные данные о покупках, сделанных покупателем к текущему дню. Файл продавцов сохраняет информацию об общем количестве продаж для каждого типа товара А, В, С и D и общее число продаж на текущий день для каждого продавца. Ключом в этом файле является помер продавца. Для более тщательного тестирования ваших программ пере- мешайте запросы, касающиеся статуса счета определенного покупателя или качества работы заданного продавца, и информацию по накладным. Запросы относительно покупателя имеют в качестве ключа номера счета и возвращают суммарную стоимость покупок на текущий день. В запросах по продавцам ключом служат имена продавцов, поэтому требуется таблица, связывающая имя про- давца и его номер. По этим запросам получают сведения об объемах продаж па текущий день для каждого типа товаров А, В, С и D плюс общая сумма продаж, 7-7. СИСТЕМА КОНТРОЛЯ УСПЕВАЕМОСТИ СТУДЕНТОВ В этом параграфе мы опишем систему для поиска, хранения и обработки студенческих оценок по группам. По срав- нению с другими системами обработки данных система контроля успеваемости студентов (CRR — Class-Records Retrieval Sy- stem) сравнительно невелика. Тем не менее она достаточно иллю- стративна, понятна и имеет дело с областью, знакомой каждому читателю. Как обычно, разработка системы будет представлена в трех фазах: системного анализа, системного проектирования и реализации системы. 7-7.1. Системный анализ Группа преподавателей факультета вычислительных наук решила упростить регистрацию студенческих оценок. В ка- честве инструмента, способного эффективно и точно вести груп- повые записи, предложена небольшая компьютерная информа- ционно-поисковая система. По мнению преподавателей, система должна обладать следующими возможностями. 1. Выдавать полный список имен, номеров и оценок для всех студентов определенной группы. 2. Вычислять взвешенные средние для каждого студента, сред- нее по курсу для любого задания и результирующие баллы. 667
3. Вводить записи для новых студентов или удалять записи исключенных студентов. 4. Вводить или изменять оценки. Желательно, чтобы система могла функционировать в онлай- новом режиме, обеспечивая немедленный доступ к информации и исключая необходимость таких офлайновых форм ввода, как иабивка перфокарт или подготовка перфоленты. Общение с системой должно осуществляться посредством лег- козаучиваемых предложений, подобных предложениям обычного английского языка. Необходимо, чтобы допускались такие запросы пользователя, как LIST OF STUDENTS WITH MARKS ON ASSIGNMENT 3 > 80 (СПИСОК СТУДЕНТОВ С ОЦЕНКАМИ ПО ЗАДАНИЮ 3>80) или LIST OF STUDENTS, IDS, MARKS, GRADES (СПИСОК СТУДЕНТОВ, ИДЕНТИФИКАЦИОННЫХ НОМЕРОВ, ОЦЕНОК, БАЛЛОВ) Термин GRADE (БАЛЛ) означает здесь взвешенную ито- говую семестровую оценку или взвешенную итоговую оценку на текущий день, если еще не все семестровые оценки занесены в систему. Выходная информация должна быть представлена в легко читаемом формате, причем при печати она должна быть упорядо- чена по студенческим именам. Такое упорядочение наиболее под- ходит при переписи оценок для официальных отчетов универ- ситета, составляющихся в соответствии с алфавитным порядком фамилий студентов. 7-7.2. Проектирование системы В соответствии с нисходящим методом проектирование системы мы начнем с определения языка запросов и закончим проектированием структур файлов и процедур их обработки. Проектирование языка запросов является одним из наиболее важных шагов при создании систем, особенно систем, предназна- ченных для использования на установках, работающих в онлай- новом режиме. Пользователь должен иметь возможность выражать свои запросы в форме, отражающей особенности практического использования системы. Вероятно, преподаватели будут обра- щаться к системе далеко не каждый день. Следовательно, язык не должен иметь жесткую структуру с использованием аббреви- атур для команд и операндов команд. В системах, подобных си- стеме резервирования мест на авиалиниях, допустим и даже же- лателен строгий язык в форме аббревиатур, так как операторы 668
часами используют свои терминалы в режиме, требующем быстрого обслуживания клиентов. Следующее ниже описание в форме, подобной БНФ, опреде- ляет язык запросов, ориентированный на естественный язык. Конечно, этот язык не отражает всю сложность запросов, выра- жаемых посредством полного английского языка. Грамматические выражения в форме означают, что синтаксический элемент, представленный А, может появляться 0 или 1, или 2, или п раз для п, равного любому неотрицательному целому числу А Выражения вида [А] означают, что синтаксический элемент, пред- ставленный А, либо появляется однажды, либо вовсе не появля- ется. (запрос) (предложение) 'подлежащее 1) 'подлежащее 2) (дополнение f) (дополнение 2) (заголовок дополнения 1) (заголовок дополнения 2) (цель 1) (цель 2) (предлог) (реляционное предложение) (отношение) (связка) (список чисел) (число) : ; = (предложение) | (функция ведения) ] (функция взвешивания оценок ) : : — (подлежащее 1) (дополнение 1) (подлежащее 2) (дополнение 2) : : = LIST OF2 | NUMBER OF3 : : = AVERAGE4 | MIN [IMUM] I MAX [IMUM] :: = (заголовок дополнения D {(цель 1)) | {(цель 2>| (дополнение 2) : : = (заголовок дополнения 2) [(реляционное предложение)] {(цель I)} | {(цель 2'} : : = STWENTISp [(список имен'] | ID |SJ« I (заголовок дополнения 2) - - _ grade [S]7 | MARK ]S]8 [ON ASSIGNMENT9 (число)] : : = (предлог) (заголовок дополнения 1) : : = (предлог) (заголовок дополнения 2; : : = WITH10 | FOR11 | , : : = (отношение) (число) (связка) (отношение) (числоЪ :: = < | = 1 ) I | )= | = :: = AND12 ] OR13 : : = (число) | (число) j (список чисел) : = (цифра) | (цифра) (число) 1 Синтаксические элементы отделяются друг от Друга запятыми. — Прим, ред. 3 список 3 число 4 СРЕДНИЙ 6 СТУДЕНТ [ОВ] с ИДЕНТИФИКАЦИОННЫЙ НОМЕР[ОВ] 7 БАЛЛ [ОВ ] 8 ОЦЕНКА (ОЦЕНОК) 9 ПО ЗАДАНИЮ 10 с 11 для 12 Логическое ’И’ 18 Логическое ’ИЛИ’ 669
(цифра) (список имен) (имя) (буква) (функция ведения) : : = 1 | 2 I 3 | 4 | 5 | 6 | 7 | в | 9 х : : == (имя) | (имя), 'список имен) : : = (буква) | (буква) (имя) : : = А I В | С ... | X | Y | Z | & | . 1 . : : = DELETE1 (список имен) { INSERT2 3 * [MANY8] (заголовок обновления) : : = UPDATE ASSIGNMENT* число [ALL5] (заготовок обновления) (функция взвешивания оценок) : : — WEIGHTING 6 (список чисел) Примеры запросов, использующих этот язык: LIST OF STUDENTS WITH MARKS ON ASSIGNMENT 3 > 80 LIST OF IDS, GRADES (СПИСОК ИДЕНТИФИКАЦИОННЫХ НОМЕРОВ, БАЛЛОВ) LIST OF STUDENTS. IDS. MARKS, GRADES AVERAGE MARKS, GRADES (СРЕДНИЕ ОЦЕНКИ, БАЛЛЫ) MINIMUM GRADE (МИНИМАЛЬНЫЙ БАЛЛ) MAXIMUM MARK ON ASSIGNMENT 1 (МАКСИМАЛЬНАЯ ОЦЕНКА ПО ЗАДАНИЮ 1) DELETE HARPER.B (УДАЛИТЬ HARPER.B) INSERT MANY (ВКЛЮЧИТЬ МНОГИХ) UPDATE ASSIGNMENT 2 (ОБНОВИТЬ ЗАДАНИЕ 2) WE1GHTIHG 33, 34, 33 (ВЗВЕШИВАНИЕ 33, 34 , 33) Смысл первых семи запросов очевиден. Две системные функции ведения INSERT и UPDATE необходимо пояснить. Система должна быть сконструирована так, чтобы подсказать пользователю требуемый ввод, когда делается любой из этих двух запросов. Например, при занесении в систему данных о новом студенте необходимо обеспечить ввод такой информации, как его (ее) имя, идентификационный номер студента, название колледжа и год обучения. Надежным способом, гарантирующим ввод требуемой информации, является руководство действиями пользователя с помощью команд-подсказок, помеченных звездочками, на- пример' 7 * ENTER THE STUDENT’S NAME- HARPER.В * ENTER THE STUDENT’S IDENTIFICATION NUMBER... 724115 * Удалить a ВВЕСТИ 3 МНОГИХ * ОБНОВИТЬ ЗАДАНИЕ 5 BCE 5 ВЗВЕШИВАНИЕ ’♦ ВВЕДИТЕ ИМЯ СТУДЕНТА... HARPER. В * ВВЕДИТЕ ИДЕНТИФИКАЦИОННЫЙ НОМЕР СТУДЕНТА 724115 670
*' ENTER THE STUDENT’S COLLEGE... COMMERCE * ENTER THE STUDENT’S YEAR... 3 * HARPER.В HAS BEEN SUCCESSFULLY INSERTED.1 Команда UPDATE также выполняется в режиме подсказки. Команда WEIGHTING устанавливает относительный вес семест- ровых заданий и экзаменов. Сумма чисел в списке, следующем за ключевым словом WEIGHTING должна быть равна 100. Поэтому число 34 на месте второго элемента списка указывает, что задание 2 должно войти в итоговый балл с весом 34/100. После определения языка запросов следующим шагом разра- ботки является выбор стратегий сканирования и разбора (т. е. синтаксического анализа) для распознавания предложений языка. Сканер, ответственный за распознавание слов языка запросов, выделяет ключевые слова, такие как LIST, NUMBER, OF, MIN, MINIMUM, GRADE, GRADES, AND, OR, DELETE;;INSERT и т. д., а также такие синтаксические единицы, как (имя) и (число). (Более подробно функции сканера описаны в п. 2-5.2.) Имеется много различных методов разбора, применяемых для распознавания предложений языка запросов. В гл. 2 и 5 мы уже представили два основных метода грамматического разбора — восходящий и нисходящий. Обсуждение других методов не явля- ется нашей целью, они хорошо описаны в работе [11]. В п. 5-2.3 мы обсуждали один из простейших методов — рекурсивный спуск. А так как язык запросов описывается грамматикой с не- большим числом правил, то неэффективностью метода рекурсив- ного спуска можно пренебречь и воспользоваться им для грам- матического разбора. Рис. 7-7.1 иллюстрирует место фазы ана- лиза языка запросов в полной структуре системы. Заметим, что лексические ошибки (например, ввод символа не принадле- жащего к алфавиту языка) и синтаксические ошибки (напри- мер, ’NUMB STUDENTS’ вместо ’NUMBER OF STUDENTS’) выявляются, и пользователю посылаются диагностические сооб- щения. Если запрос принадлежит к ранее определенному нами языку запросов, то система должна его выполнить. Для системы CRR таким действием является либо правильный ответ, что определя- ется получением запрашиваемой информации, либо правильное выполнение требуемых манипуляций базой данных (т. е. набором записей о студентах). Это логическое расщепление фазы обработки запроса отражено на рис. 7-1.1. Блок генератора ответов на за- ВВЕДИТЕ НАЗВАНИЕ КОЛЛЕДЖА... КОММЕРЦИЯ * ВВЕДИТЕ ГОД ОБУЧЕНИЯ ... 3 * ИНФОРМАЦИЯ О СТУДЕНТЕ HARPER. В УСПЕШНО ВВЕДЕНА. 671
Рис. 7-7.1. Схема, иллюстрирующая функционирование системы контроля успеваемости студентов просы обслуживает запросы на поиск информации, задаваемые командами LIST OF, NUMBER OF, AVERAGE, MIN и MAX. Большинство этих поисковых запросов требуют последовательной обработки файла студенческих записей. Например, запрос NUMBER OF STUDENTS WITH GRADE > 80 (ЧИСЛО СТУДЕНТОВ С БАЛЛОМ >80) предполагает анализ записи о каждом студенте для выяснения, набрал ли этот студент больше 80 баллов. Для каждого такого результата значение счетчика увеличивается на единицу, и по- сле просмотра всех записей его содержимое печатается. Анало- гичная форма обработки требуется для обслуживания большин- ства других поисковых запросов, за исключением команд, обра- щающихся к записям о конкретных студентах по именам. На- пример, запрос LIST OF STUDENTS COOPER.R, RIDGWAY.W, NYLANDER.L WITH MARKS ON ASSIGNMENT 1 > 90 (СПИСОК СТУДЕНТОВ COOPER.R RIDGWAY.W, NYLANDER.L С ОЦЕНКАМИ ПО ЗАДАНИЮ 1 > 90) требует анализа только трех указанных записей о студентах. Следовательно, возможность получать доступ непосредственно к записям по именам студентов является определенным преимуще- 672
ством. Заметим, что мы могли бы разработать язык запросов так, чтобы ссылаться на запись о студентах по идентификацион- ным номерам студентов, а не по их«именам. Однако такое решение непривлекательно с точки зрения пользователя. Дело в том, что как преподаватели, так и студенты при общении используют имена, а не числа. Если бы мы разрабатывали регистрационную систему, охватывающую весь университет, то идентификация студентов посредством номеров имела бы много явных преиму- ществ. Такими преимуществами явились бы, например, аноним- ность записей и уникальность идентификационных номеров, что не всегда имеет место, когда идентификаторами служат имена. И, конечно, идентификационные номера студентов первоначально были придуманы для регистрационных систем. Прямой доступ к записям требуется и в тех случаях, когда выполняются функции ведения системы. Мы удаляем и обновляем записи о студентах по именам. При добавлении новой записи на- ряду с именем студента одновременно требуется и другая инфор- мация о студенте. Следовательно, обработка файла, необходимая для нашей задачи, предполагает последовательный просмотр записей в файле, печать информации, содержащейся в записях в алфавитном порядке студенческих имен, и прямой доступ к за- данным записям по индексу, являющемуся именем студента. Ясно, что индексно-последовательная организация файлов удов- летворяет этим требованиям при обработке, и необходимо исполь- зовать индексно-последовательный файл, содержащий групповые записи с ключами, представляющими собой имена студентов. На этот файл на рис. 7-7.1 мы ссылаемся как на «групповой файл». Для сохранения всей информации, необходимой пользовате- лям системы, запись в групповом файле должна содержать имя студента, I. D. (идентификационный иомер), название колледжа, год обучения, оценки для разумного числа заданий (скажем, 12) и итоговый балл студента. В идеале мы должны создать свои файлы для каждой группы студентов. Однако для индексно-по- следовательных файлов Системы IBM память выделяется целыми цилиндрами и файл не может занимать меньше одного цилиндра. Предположим, что мы будем применять систему CRR иа установке IBM. с дисковыми пакетами IBM 3330, тогда это минимальное пространство слишком велико для файла с записями только об одной группе. В самом деле, имеется всего 13030 байт на дорожке, 18 доро- жек на цилиндре. Поэтому на цилиндре для основной области и области переполнения цилиндра пакета IBM 3330 размещается 234 540 байт. Даже если мы предположим, что половина этой памяти будет занята под цепочки переполнения, описатели длины записи и другую информацию, описывающую структуру записи, остается 117 270 байт для размещения информационных записей. На хранение информации об одном студенте требуется прибли- зительно 40 байт. Таким образом, один цилиндр может содержать 22 Трамбле Ж-, Соренсон П. 673
2931 запись (мы использовали весьма осторожную процедуру оце- нивания). Будет слишком расточительно расходовать целый цилиндр дискового пакета д.дя каждой группы студентов. Лучшим решением будет размещение записей по всем группам на общем цилиндре и использование параметра GENKEY для по- иска записей отдельной группы. Принятие этой стратегии означает, что ключ записи, относящейся к студенту по имени D. Smith из группы СМРТ 316, следует задавать в виде ’316 SMITH. D’, а ключ записи о студенте D. Jones из группы СМРТ 441 — в виде 441’ JONES. D’. Для получения доступа ко всем записям из группы СМРТ 136 необходимо первоначально использовать об- щий ключ ’316’, как это было описано в конце п. 7-6.3. Исходный список студентов группы обычно составляется перед началом курсовых лекции. Индексно-последовательный группо- вой файл создается последовательным вводом в файл записей о студентах, упорядоченных в порядке возрастания значения ключа номер группы/имя студента. Для обновления, удаления я изменения записей этого исходного файла не требуется большой -области переполнения. Выход, генерируемый фазой синтеза запросов, имеет три фор- мы. Во-первых, имеется информация, извлекаемая по командам языка запросов, таким как LIST и MAXIMUM. Такого рода вы- ходная информация должна иметь легко читаемую форму. Не- сколько примеров выходных форм представлены в следующем пункте. Во-вторых, во время обработки запросов могут возникать семантические ошибки. Примерами семантических ошибок явля- ются попытки удалить запись, которой нет в файле, или добавить уже существующую запись. В-третьих, чрезвычайно полезную форму выходной информации представляют собой ответы системы пользователю, содержащие подтверждение завершения команд ведения файла, например командьт удаления. На этом мы завершили описание фазы проектирования и теперь •обратимся к обсуждению вопросов конкретной реализации системы. 7-7.3. Реализация Реализация системы CRR включает напнсание про- грамм для четырех основных модулей, представленных на рис. 7-7.1, а именно сканера, синтаксического анализатора, гене- ратора ответов иа запросы и модуля ведения файлов. Сканер реализован в процедуре, названной SCAN. Синтаксический ана- лизатор состоит из 19 процедур, многие из которых рекурсивны, так как мы используем метод рекурсивного спуска. Анализатор первоначально вызывается обращением к процедуре QUERY, которая прямо или косвенно вызывает остальные 18 процедур. Как генератор ответов на запросы, так и монитор ведения файлов располагаются в процедуре, названной RESPONSE. Эти три процедуры (SCAN, QUERY и RESPONSE) перво- начально вызываются в главной программе системы CRR, как €74
это показано на рис. 7-7.2. В этот же рисунок включены описания и' инициализация многих переменных программы. Специально- мы должны отметить объявления имен различных процедур как точек входа и объявление структуры А—STUDENT, описыва- ющей записи, помещаемые в индексно-последовательный файл CLASS. Процедура MSG ответственна за печать большинства сооб- щений пользователю. Для процедуры SCAN дан только заго- ловок. Обсуждение важнейших особенностей работы сканера было дано в п. 2-5.2. Главная программа начинает выполняться с передачи сооб- щения ’HELLO’ и запрашивания номера группы. После инициа- лизации некоторых системных переменных в цикле последова- тельно вызываются процедуры SCAN, QUERY и RESPONSE до тех пор, пока пользователь не окончит сеанс. Процедура QUERY и рекурсивная процедура для распоз- навания синтаксической единицы (заголовок дополнения Г> изображены иа рис. 7-7.3. Обе процедуры иллюстрируют прямую связь, существующую между данным синтаксическим описанием и последовательностью вызовов, используемых для контроля соответствия этому описанию. В процедуре OBJECT—НЕAD1 значения глобальных битово-строчных флажков STUDENT и 1D устанавливаются в зависимости от формы запроса. Эти битово- строчные переменные, так же как ряд других битово-строчных флажков, объявлены на рис. 7-7.2, а используются в програм- мной секции, генерирующей ответы системы. Именно эти перемен- ные определяют, какие системные функции необходимо выпол- нить и какая информация должна быть найдена. В связи с тем, что главной целью этого параграфа является применение индексно-последовательных файлов, мы более под- робно обсудим операции, совершаемые над групповым файлом. Операции над файлом выполняются в фазе синтеза запросов, как это реализовано в процедуре RESPONSE. Схематическое пред- ставление процедуры RESPONSE приведено на рис. 7-7.4. Модуль для выполнения запросов, требующих нахождения ми- нимальной или максимальной оценки для заданного списка сту- дентов, представлен как пример из блока генерации ответов про- цедуры RESPONSE. Этот сегмент программы иллюстрирует, как осуществляется доступ к индексно-последовательному файлу CLASS посредством ключа номер группы/имя студента. Отметим, что мы использовали условие ON KEY для детектирования зап- росов о студентах, отсутствующих в файле для заданной группы. Модуль для нахождения минимальной и максимальной оценок но отдельным заданиям ие приведен. Однако должно быть ясно,, что такой модуль включает последовательный доступ ко всем' записям группы. Другие модули, которые обеспечивают выпол- нение команд, ориентированных на поиск, таких как LIST, NUMBER и average, также требуют последовательного и прямого чтения записей студентов. Прямой доступ требуется при 22* 675
CRR! PROCEDURE OPTIONS (MAIN is - /* THIS PROGRAM IS DESIGNED TC PERFORM A RETRIEVAL FUNCTION ON A DATA SET THAT CONTAINS RECORDS WITH INFORMATION PERTAINING To A CLASS. FOR THIS REASON IT IS KNOWN AS THE CLASS RECORDS RETRIEVAL SYSTEM (CRR). A QUERY LANGUAGE IS USED TO EXTRACT THE DESIRED INFORMATION FROM THE FILE. THE PROGRAM IS COMPOSED OF TWO LOGICALLY DISTINCT PHASES. THE FIRST SCANS THE INPUT QUERY AND THEN TESTS IT FOR SEMANTIC VALIDITY. THE SECOND PORTION OF THE PROGRAM PERFORMS THE ACTUAL RETRIEVAL OF IN- FORMATION. */ DECLARE SCAN MSG QUERY RESPONSE ENTRY RETURNS (CHARACTER <301 VARYING). ENTRY (CHARACTER l*1h ENTRY. entry; /* THE FOLLOWING LIST OF PROCEDURES ARE USED IN THE PARSING OF THE INPUT QUERY. A RECURSIVE DESCENT PARSE HAS BEEN IMPLEMENTED. */ DECLARE PROPOSITION MAINTENANCE WEIGHTING SUBJECT! ’ SUBJECT2 OBJECT! 0BJECT2 OBJECT_HEAD1 OBJECT-HEAD 2 OBJECTIVE! OBJECTIVE2 REL_STAT NAME NAME_LI57 RELATION NUMBER NUMBER-LIST UPDATE_HEAD ENTRY RETURNS (BIT(ll), ENTRY RETURNS (BTT(l)), ENTRY RETURNS (BI7(1>), ENTRY RETURNS (BIT(D), ENTRY RETURNS (BIT<1> > > ENTRY RETURNS (BIT(D), ENTRY RETURNS IBIT(l)), ENTRY RETURNS (BIT(l)I, ENTRY RETURNS (BIT(!)I, ENTRY RETURNS (BIT(1) ), ENTRY RETURNS (BIT(l)», ENTRY RETURNS (BIT(l)I, ENTRY RETURNS (BIT(!>), ENTRY RETURNS (BIT(l))r ENTRY RETURNS (BIT(L>), ENTRY RETURNS (BIT(lt), ENTRY RETURNS (BIT(1)), ENTRY RETURNS (BIT(D), /# FOLLOWING IS A LIST OF BIT STRING FLAGS WHICH ARE SET DURING THE PARSING PROCEDURE ANO THEN REFERENCED DURING THE RETRIEVAL OF INFOR- MATION. DECLARATIONS OF TEMPORARY VARIABLES ARE INCLUDED AS WELL. */ (NUM,LIST,MIN,MAX,AVER AGE,DUMMY,STUDENT,GRADE,ID, MARK,DELETE,INSERT.UPDTE,WEIGHT,ASNMT) BIT 111, ERROR BIT ll> INITIAL (’D’B), NEXTSYM CHARACTER (30) VARYING, RELATE (5) CHARACTER (2) VARYING, ST-NAMES 115) CHARACTER (20) VARYING, ASSIGN)16) CHARACTER (7) VARYING INITIAL ( (16) ’-!’), LOOP BIT (1), COLUMN_SCAN FIXED, WORK CHARACTER (3), W(!2) FIXED BINARY INITIAL < (12) -1 >» NO-ST FIXED BINARY INITIAL (0), (STN, ASM) FIXED! /♦ THIS CLASS FILE IS THE FILE ASSOCIATED WITH THE DATA SET WHICH CONTAINS THE CLASS RECORDS. THE STRUCTURE! ’A_STUDENT’ IS USED TO MA- NIPULATE THE RECORDS INTERNALLY. THE STRUCTURE SERVES TO INDICATE рис. 7-7.2. Главный сегмент программы системы контроля успеваемости студентов 676
THF FOPMAT AND CDNTENS OF EACH RECORD DECIARE CLASS FILE RECORD KEYED ENVIRONMENT (INDEXED GENKEY). I A_STUDENT, 2 FILLER CHARACTER (II, 2 S_ID CHARACTER (6), 2 S_CLASS CHARACTER(4), 2 S_NAME CHARACTER (20), 2 S„COLLFGE CHARACTER (14), 2 S„YEAR CHARACTER (1), 2 S_NARKS (12) FIXED DECIMAL (3), 2 S^AVERAGE FIXED DECIMAL (5,2), jftSG; PROCEDURE (MESSAGE); /* THE MSG PROCEDURE IS USED BY ALL PORTIONS OF THE PROGRAM TO PRINT4- ANY KIND OF MESSAGE TO THE USER. ... йЛ DECLARE MESSAGE CHARACTER (*); PUT EDIT (MESSAGE) (SKIP (2), A); .END msg; у««««4«««»«««««««Ф««« SCAN 5CA№ PROCEDURE RETURNS (CHARACTER (30) VARYING); /* THE ’SCAN’ PROCEDURE SCANS THE INPUT MESSAGE RETURNING THE NEXT LEXICAL UNIT TO BE USED BY THE PARSER. ... ». END SCAN; /««««««ИЛЛ# QUERY PARSING PROCEDURES £ ««««««ftftft/ QUERY SYNTHESIS PROCEDURES *****«*&/ /ааяоаа» FALL HERE ««**«»«/ j««««« MAIN LINE ф**#йй«**й«*лй**лй«««/ /* THIS SECTION OF THE PROGRAM INTRODUCES THE USER TO THE SYSTEM AND THEN INITIALIZES A NUMBER OF SYSTEM VARIABLES AND CALLS THE QUERY PROCESSING ROUTINES. */ ON ENDFtLE(SYSIN) GO TO DONE; loop» n* e; DO WHILE(LOOP); PUT EDIT(’HELLO PLEASE ENTER YOUR CLASS NUMBER (E.G. ’, ’3138,441A,102,ETC) ’ ) ( (2) ( COLUMN(L),A),A,COLUMN(1),A(0)); GET EDIT (STRING) (COLUMN)1) , A( 72))J I = 1; NEXTSYM = SCAN; IF (LENGTH!NEXTSYM) = 3) I (LENGTH!NEXTSYM) = 4) THEN do; YOUR_CLASS - NEXTSYM; CALL MSG <’THANKYOU’): LOOP - ’O’B; ENO; ELSE CALL MSG (’CLASS NUMBER IS TOO LONG OR TOO SHORT’); END; 7* OF DO WHILE (LOOP); »/ LOOP = ’1’B? ON ENDFILE (SYSIN) LOOP = *0’8; DO WHILE (LOOP); **ис. 7-7.2. Продолжение 677
FILLER = LOW 11); NUM,LI ST,MIN,MAX,AVERAGE5STUDENT,IDEGRADE,MARK,DELETE» INSERT,UPDTE,WEIGHT.ASNWT = ’0’0; DO J = 1 TO 16; IF J <= 5 THEN RELATE (J) = ”5 end; COLUMN_SCAN = 81; NEXTSYM = SCAN; IF -.ERROR THEN CALL QUERY; IF -.ERROR THEN DO; CALL response; CLOSE FILE(CLASS); END; ERROR = ’O’B; PUT EDIT I’ ’> (COLUMN(l),AtOHS ENO; DOME; PUT SKIP LIST (’THIS RUN OF CRR IS NOW ENDED.*); END CRR; Рис. 7-7.2. Продолжение запросе с определенным списком студентов, а последовательный - для запроса, вовлекающего все записи студентов. Чтобы показать ие только операцию чтения, но и другие опе.- рации с файлом, мы должны описать функции ведения файла в системе. Программные модули для функций DELETE, UPDATE и INSERT также показаны на рис. 7-7.4. Предложение ON KEY опять используется для детектирования ошибочных спецификаций записей о студентах. Функция DELETE реали- зована с помощью инструкции DELETE языка ПЛ/1. Эта инструкция устанавливает первый байт записи равным ’11111111’В,, тем самым указывая, что запись не может быть прочи- тана. Сегмент программы для функции INSERT разработан таким образом, чтобы подсказывать пользователю нужные действия и тем самым помогать обеспечивать наличие всех элементов ин- формации во вводимых записях. Для размещения новой записи в файле используется инструкция WRITE с ключом номер груп- пы/имя студента. Функция UPDATE может использоваться для изменения оце- нок определенного задания для всей группы или оценки отдель- ного студента за определенное задание. Сегмент программы для реализации первой возможности представлен на рис. 7-7.4. Понятно, что для обновления индексно-последовательно го файла применяется инструкция REWRITE. Величина ASSIGN (I) идентифицирует задание, которое должно быть обновлено. Мас- сив W содержит относительные веса заданий. Когда пользователь выполняет обновление, система опять под- сказывает ему нужные действия, для чего появляется сообщение, содержащее имя следующего студента, информация о котором должна ^быть обновлена. При изменении оценки отдельного 678
QUERY; PROCEDURE; /« QUERY IS THE PROCEDURE CALLED INITIALLY IN THE RECURSIVE DESCENT PARSER. ALL OTHER PROCEDURES ARE CALLED EITHER DIRECTLY OR INDIRECTLY FROM THIS PROCEDURE о/ /» BNF NOTATION WILL BE USED WITH THE FOLLOWING ADDITIONS AND CHANGES: " WILL BE USED ( WILL BE USED J WILL BE USED IN PLACE OF BRACES IN PLACE OF THE LEFT SQUARE BRACKET IN PLACE OF THE RIGHT SQUARE BRACKET /* «QUERY» s:= «PROPOSITION» I «MAINTENANCE FUNCTION> | «MARKS WEIGHTING FUNCTION» */ IF -PROPOSITION E -.ERROR THEN IF -MAINTENANCE S -ERROR THEN IF -WEIGHTING 8 -ERROR THEN CALL MSG (’FIRST WORD OF COMMAND IS INVALID’I? COLUMNS CAN = 815 END QUERY? A TYPICAL PROCEDURE IN THE RECURSIVE DESCENT PARSER IS SHOWN FOR THE PRODUCTIONS INVOLVING THE NON-TERMTNAL OBJECT_HEADI. */ OBJECT „HEAD!: PROCEDURE RECURSIVE RETURNS /* «OBJECT НЕАО1» STUOENT(SJ I STUDENTfSJ «NAME LIST» I lO(Si I «OBJECT НЕАО2» •/ IF NEXTSYM = ’STUDENT’ I NEXTSYM = ’STUDENTS* THEN oo; NEXTSYM = SCAN; STUDENT = *1’B; IF INEXTSYM-=’WITH’ 8 NEXtSYM-=’FOR• 8 NEXTSYM-1’»* 8 NEXTSYM—=’P*J THEN DO; IF -NAHE_LIST THEN RETURN(*O’B); END} RETURN (’1'BI5 END; IF NEXTSYM = ’IO* ! NEXTSYM ~ ’IOS* THEN DO; NEXTSYM = SCAN; ID - ’L’B; RETURN I’l’Bh END; IF OBJECT_HEAD2 THEN RETURN t’l’BJ? RETURNl’O'BI; END OBJECT_HFAOL; Рис. 7-7,3. Примеры процедур для синтаксического анализа запросов 679
RESPONSE: PROCEDURE? /* THE 'RESPONSE* PROCEDURE IS USED TO EVALUATE THE QUERY’S ANO TO PERFORM THE NECESSARY RETRIEVAL OF INFORMATION. IT ALSO PERFORMS ALL UPDATE FUNCTIONS WHICH ARE REQUIRED BY THE QUERY’S. */ /«№««««»««««« MAINTENANCE FUNCTIONS ««scsaosot»/ IF STN >0 /♦ INDEX INTO ARRAY $T_NAMES </ THEN DO? OPEN FILE(CLASS> DIRECT UPDATE? /* THE DELETION OF A RECORD. NOTE THAT THERE IS A CHECK TO SEE IF THE .RECORD TO BE DELETED ACTUALLY EXISTS ON FILE. IF DELETE THEN DO? ON KEY (CLASSI BEGIN; CALL MSG (’THE STUDENT YOU ARE TRYING TO DELETE — • II ST_NAMES(J) II ’ — IS NOT IN THE FILE’J; GO TO RET; ENO? DO J *= 1 TO STN? IF St_NAMES(JI -= ’tCWEIGHTSCC’ THEN DO? DELETE FILE (CLASSI KEY <YOUR_CLASS II ST_NAMES(JH; NO_ST = NO_ST - I? CALL MSG (ST_NAMES(J> II ‘HAS BEEN SUCCESSFULLY ♦ It ’DELETED ♦♦ I; END; ELSE CALL M^G (’EEWEIGHTSCC IS A PROTECTED NAME AND • li ’MAY NOT BE DELETED FRGpl THE FILE.’I? ERROR = »0»B; END? RETURNS END; /# INSERT A RECORD. NOTE THRT THERE IS A CHECK TO MAKE SURE THAT THE RECORD TO BE INSERTED IS NOT ALREADY ON FILE. ♦/ IF INSERT THEN DO; ON KEY (CLASSI BEGIN; CALL MSG (’THE STUDENT YOU ARE TRYING TO INSERT — • II S_NAflE 11 < — IS ALREADY IN THE FILE*IT GO TO RET; END; ST_NAMES(15> = NEXTSYH; pz: PUT EDIT!’ENTER THE STUDENT»»S NAME’,» ») (COLUMN(1))A«COLUMN(II,A(D|>; STRING = S_NAME; GET EDIT(S_NAME 1 (SKIP{l],A(20)1? COLUMN_SCAN = 1? NEXT$YM,S_NAME = SCAN? IF S_NAME = ’ENC» THEN RETURN, IF -NAME THEN DO; Рис. 7-7.4. Процедура для поддержания ответов на запросы 680
ERROR = ’0’6; GO TO P2; END; /• PROMPTING CYCLES TO ENSURE FNTRY OF STUDENT IDENTIFICATION NUMBER, YEAR, AND COLLEGE SHOULD APPEAR AT THIS POINT IN THE PROGRAM. . . . »/ S,.AVERAGE = O; S—CLASS = YOUR-CLASS; WRITE FILE (CLASS) FROM (A—STUDENT) KEYFROM( S..CLASS II S-NAME); NO_ST = NO_ST * 1; PUT EDIT(S_NAME || ’ HAS BEEN SUCCESSFULLY INSERTED ’} (COLUMN(1),A); IF ST_NAMES(15) = ’MANY’ THEN GO TO P2; return; ✓ * THIS SEGMENT HANDLES THE UPDATING OF MARKS ON A SPECIFIED ASSIGNMENT FOR ALL STUDENTS IN THE CLASS. */ IF UPDTE THEN do; /* WORK IS USED AS A TEMPORARY CHARACTER STRING VARIABLE. */ IF NEXTSYM = ’ALL’ THEN DO; CLOSE FILE!CLASS); OPEN FILE (CLASS) SEQUENTIAL UPDATE; ON ENDFILE (CLASS J begin; STN = C; /* STUDENT INDEX SET TO ZERO ♦/ S-NAME = ’I’; /* STUDENT NAME SET TO ’I’ ♦/ END; READ FILE(CLASS) INTO (A-STLIOENT) KEYfVOUR-CLASS)? IP S_NAME = ’KCWEIGHTSCC• THEN READ FILE(CLASS) into(a_student); DO WHILE!STN=1); P6: PUT EDIT!’ENTER THE MARK FOR ’,S„NAME,’ ’) 1 COLUMN(1),A,A,COLUMN(11,A(0)); GET EDIT(WORK) (SKIP(1),A(3)I; STRING = work; SUB STR(STRING, 76,5) COLUMN-SCAN = 1; NEXTSYM = SCAN; ERROR = *1’8; IF --NUMBER THEN DO; ERROR = ’O’B; CALL MSG(’THE VALUE ENTERED AS A MARK IS ’ If ’NON-NUMERIC’); GO TO P6; ENO; ERROR = ’O’B; DO WHILE(SUBSTR(WORK,3,1) = ’ WORK = ’ ’ |J SUBSTR(WORK,1,2); ' END; S-AVERAGE =0; ₽ис, 7=7.4, Продолжение 681
S_MARKS(ASSIGNIH) = WORK! DO К = 1 TO IZ! ( S_AVERAGE = S_AVERAGE •» ROUND(W<K) * 0.010000 * S_MARK$fK), 2>5 eno; REWRITE FILE(CLASS) FROM(A_STUDEMT}; PUT EDIT(»THE MARK FOR •fS_NAME,' HAS BEEN •UPDATED ’) (COLUMN(1),4 (All: R6: READ FILE(CLASS) INTO (A_STUDENT>; IF S_NAME = •EGUEIGHTSEC* THEN GO TO R6; IF S_CLAS5 YOUR-CLASS THEN STN = 0! return; /* THE NEXT SECTION SHOULD HANDLE THE UPDATE OF THE MARKS FOR A SPECIFIC STUDENT AND A SPECIFIC ASSIGNMENT. . . . /* FOR ALL INFORMATION RETRIEVAL FUNCTIONS TO BE PERFORMED ON A SPECI- FIED LIST OF STUDENTS» A CHECK IS WADE TO ENSURE THAT EACH STUDENT ’• ’-if щт Is ON THE FILE. */ ON KEY 1 CLASS* BEGIN; CALL MSGf’THIS STUDENT -- * U ST_NAMESJI II ’ — IS • II 'мат in the file»); GO TO RET; END; y*»>.,**+** QUERY RESPONSE FUNCTIONS ********* / z* FINDING ThE MINIMUM OR MAXIMUM MARK FOR SPECIFIED STUDENTS. *' IF MIN I MAX THEN IF -.MARK | ASNMT THEN DO; CALL MSG<'WHEN REFERRING TO SPECIFIC STUDENTS. THE • II ’MINlIMUMl OR MAX(IMUMI FUNCTIONS MUST ’ II •REFER TO ALL MARKS OF THOSE STUDENTS’!; RETURN ; ENO; ELSE DO; do J = 1 TO stn: READ FILE (CLASS! INTO iA_STUDENT> KEV(YOUR_CLASS II ST-NAMES(J)!Г IF MIN THEN WORK = 101; ELSE WORK = -1; DO К = I TO 125 IF MIN THEN IF !(S_MARKS(K) < WORK) C IS_MARKS(K) -.= -1)1 THEN WORK = S_MARKS(K>; ELSE; ELSE IF(IS.MARKS(К I > WORK) & (S_MARKS(K| -.= -1)) THEN WORK - S_MARKS(K); END; IF WORK - 101 ) WORK = -1 THEN WORK = 0; PUT EDIT (S_NAME,WORK) (COLUMN(11,A(21),A); END; RETURN; ENO; . THE REMAINING INFORMATION PROCESSING FUNCTIONS SUCH AS AVERAGE CAL- CULATIONS, LISTING STUDENTS AND GRADES, ETC. APPEAR IN THIS SECTION’ OF The PROGRAM. ... * RET: return; END RESPONSE; Рис. 7-7.4. Продолжение
студента за определенное задание подсказки системы выглядят следующим образом; UPDATE ASSIGNMENT’ 4 * ENTER THE STUDENT’S NAME THOMSON.C * ENTER THE STUDENT’S MARK 91 * MARK FOR THOMSON.C WAS UPDATED SUCCESSFULLY L Для ясности мы пометили строки, генерированные системой, префиксом * *. В интерактивных системах, если не необходимо, то, по крайней мере, желательно это делать для лучшей читаемости выходного текста, сопровождающего сеанс работы с терми- налом. Представленный «скелет» системы не обеспечен средствами автоматического управления активностью файла. Это очень важный аспект в любой системе, особенно в системах, использу- ющих индексно-последовательные файлы. Когда основная область индексно-последовательного файла заполнена или близка к за- полнению, то каждое включение может привести к генерации записи переполнения. С другой стороны, если к файлу применя- ется большое число транзакций типа удаления, значительная часть файла будет состоять из «пустых» удаленных записей. И в этом случае производительность системы будет уменьшаться. Число включений и удалений должно записываться, и после достижения определенного уровня файл должен быть реорганизован повтор- ным его созданием. Имеется много других улучшений качества системы, которые можно применить к системе CRR, тем не менее представленная простая система адекватным образом иллюстри- рует главные концепции, относящиеся к индексно-последователь- ным файлам. В двух последних параграфах мы изучали файловую органи- зацию, обеспечивающую прямой и последовательный доступ к за- писям. Реализация обоих режимов доступа влечет за собой боль- шие накладные расходы, особенно в связи с перемещением записей в основной области, а также с созданием и модификацией в ин- дексах и областях переполнения. В следующем параграфе мы изучим файловую организацию, которая не гарантирует последо- вательного доступа к записям, но зато обеспечивает более бы- стрый прямой доступ и не требует так много накладных расходов при управлении файлами. 1 ОБНОВИТЬ ЗАДАНИЕ 4 * ВВЕДИТЕ ИМЯ СТУДЕНТА THOMSON С * ВВЕДИТЕ ОЦЕНКУ СТУДЕНТА 91 * ОБНОВЛЕНИЕ ОЦЕНКИ СТУДЕНТА THOMSON. С ВЫПОЛНЕНО УСПЕШНО 683
7-8. ФАЙЛЫ ПРЯМОГО ДОСТУПА Для иллюстрации характера обработки файлов прямого доступа вернемся на короткое время к рассмотренной нами в ка- честве примера небольшой системе оплаты счетов. Мы привели этот пример для пояснения особенностей использования последо- вательных файлов (в п. 7-5) и затем повторно рассмотрели его во введении в индексно-последовательные файлы (в п. 7-6). В п. 7-6 было указано, что для обеспечения возможности использования любой формы онлайновой обработки, касающейся состояния счета,, необходимо иметь прямой доступ к индивидуальным записям покупателя. Еще одним пожеланием было последовательное упо- рядочение записей по номерам счетов, что необходимо для полу- чения ежемесячных счетов, базирующихся на пакетах квитанций,, доставляемых из мест продажи. Предположим, что в ближайшем будущем наша небольшая компания захочет иметь систему оплаты счетов, позволяющую обойтись без заполнения товарных чеков в местах продажи с по- следующей их пересылкой в центральное бюро для обработки компьютером. Простым, хотя и более дорогостоящим подходом является немедленное занесение в счет клиента сведений о по- купке или возврате с помощью терминала, управляемого клерком прямо на месте продажи. Онлайновые терминалы могут быть удалены от вычислительной машины, но непосредственно связаны с ней телефонными линиями. Если покупки и возвраты учиты- ваются сразу в точке продажи, то отпадает необходимость паке- тировать квитанции, а затем их последовательно обрабатывать для корректирования счетов с помощью процедуры слияния, описан- ной в п. 7-5. С устранением необходимости последовательной обработки мы можем ограничиться разработкой системы, исполь- зующей исключительно прямой доступ. В этом параграфе мы рассмотрим ряд файловых структур, обеспечивающих эффектив- ный прямой доступ. Эта эффективность достигается устранением требования такой организации файла, которая допускает как последовательный, так и прямой доступ к записям. Этот параграф опять делится на три части, в которых описы- ваются структуры файлов, прямая обработка файлов и файлы пря- мого доступа в ПЛ/1. В следующем параграфе мы займемся при- менением обработки файлов прямого доступа в онлайновой бан- ковской системе. 7-8.1. Структура файлов прямого доступа В файлах прямого доступа, (иногда говорят произволь- ного доступа), илн прямых файлах выполняется преобразование или отображение ключа в адрес памяти, по которому запись будет размещаться в файле. Один из алгоритмов генерации такого пре- образования называется алгоритмом хеширования. В п. 6-2.4 мы- 684
изучали алгоритмы хеширования в том виде, в каком оии при- меняются при размещении записей в перемешанных таблицах. Там было отмечено, что алгоритмы хеширования состоят из двух компонентов — функций хеширования, или хеш-функции, опре- деляющей преобразование пространства ключей в адресное про- странство, и средств разрешения коллизий, устраняющих кон- фликты, возникающие, когда более чем один ключ записи пре- образуется в один и тот же адрес таблицы. Алгоритмы хеширования для файлов прямого доступа очень сходны с теми, которые применяются для таблиц. Вследствие этого, прежде чем читать данный параграф, необходимо добиться полного понимания материала, изложенного в п. 6-2.4. Основные концептуальные различия обязаны своим происхождением физи- ческим характеристикам внешней памяти, отличающимся от характеристик непосредственно адресуемой памяти, предполага- емых для таблиц в гл. 6. В частности, время доступа к записи в таблице в основной памяти имеет порядок нескольких микро- секунд, в то время как доступ к записи во внешней памяти соста- вляет миллисекунды. К тому же записи в файле размещаются в пакетах, каждый из которых имеет b мест для записей, в таблице же — только одно место. Количество записей в пакете называется емкостью пакета. Можно представлять себе пакет как сектор в устройствах с секторной адресацией или блок в устройствах с адресацией блоков. Для выделения отдельной записи необходимо найти пакет, в котором размещена запись, содержимое пакета перенести в буфер в основной памяти, а затем извлечь требуемую запись из буфера. Определим адресное пространство А размером m в виде А = = {С + 1, С + 2, ..., С -F т}, где А — целая константа. Следо- вательно, в А может поместиться mb записей, и коэффициент за- полнения для файла прямого доступа будет равен n/(mb) при пред- положении, что в адресное пространство преобразуется множество из п значений ключа. Множество ключей S = |ХЬ Х2, ..., Хы} является подмноже- ством множества К возможных значений ключа, называемого пространством ключей. Если число элементов в К равно числу мест для записей в А и пространство значений ключей последова- тельно, то можно определить преобразование, приписывающее каждому пакету из А ровно b ключей из К. Этот тип взаимно одно- значного преобразования называется прямой адресацией и по форме аналогичен адресации, применяемой в массивах. Однако в большинстве случаев S является всего лишь неболь- шим подмножеством К, и прямая адресация приводит к чрезвы- чайно низкому использованию памяти прямого доступа. По этой причине применяется косвенная адресация. А именно, S отобра- жается в А таким образом, что имеется отличная от нуля вероят- ность назначения одного и того же пакета такому числу записей, которое приведет к переполнению пакета. В этом случае для раз- 685
мещения любой записи переполнения следует использовать сред- ства управления переполнением пакета. Позже в этом пункте мы обнаружим, что методы для управления переполнением пакета очень схожи с методами разрешения конфликтов в таблице. Получив некоторое представление о концепциях, относящихся к алгоритмам хеширования, теперь более детально изучим воз- можные способы организации файлов прямого доступа. Мы начнем с рассмотрения хеш-функций, которые можно использовать для преобразования адреса. Затем более глубоко изучим методы упра- вления переполнением. В п. 6-2.4 функции хеширования были разделены на два боль- ших класса — независящие и зависящие от распределения зна- чений ключей. Три наиболее значительных исследования хеш- функцин, не зависящих от распределения, выполнили Буххольц [3'1, Лам и др. [23] и Лам [22]. Результаты этих трех исследова- ний показали, что метод деления, использующий простой дели- тель или делитель, взаимно простой по отношению к размеру адресного пространства, обеспечивает в среднем наилучшие ха- рактеристики. Это вовсе- не означает, что для некоторых множеств ключей с определенными коэффициентами загрузки и емкостями пакетов какой-то из методов, обсуждавшихся в п. 6-2.4, не может превосходить метода деления. Дьючер и др. [9] получил результаты, указывающие, что определенные хеш-функцин, зависящие от распределения (в ча- стности, кусочно-линейные функции с расщеплением на интервалы и многочастотные функции), могут превосходить метод деления. Однако вычисление хеш-значения для этих типов функций требует существенно больше времени, чем при использовании метода деле- ния. Кроме того, для хранения зависящей от распределения ин- формации требуется значительное количество памяти. Вследствие этих двух моментов и того факта, что требуется знание распределе- ния ключей в пространстве ключей, функции, зависящие от рас- пределения, не всегда удобны для применения. Но когда файл с прямой организацией и, следовательно, его множество ключей достаточно статичны по своей природе, то можно применять ме- тоды, зависящие от распределения. Большие затраты времени, необходимые для вычисления хеш-значения, при этом все же существенно меньше времени доступа к записи во внешней памяти. Вторым аспектом алгоритмов хеширования является метод разрешения коллизий. В файле прямого доступа наименьшей адресуемой единицей является пакет, причем пакет может со- держать много записей, которым был назначен один и тот же адрес. Следовательно, для файла прямого доступа с заданной емкостью пакета можно ожидать возникновения определенного числа коллизий. Однако, когда число коллизий превосходит ем- кость пакета, то необходимо найти метод для управления этими переполняющими значениями. Вместо термина «разрешение кол- лизий», применяемого в методах хеширования для таблиц, при 686
работе с внешней памятью используют термин средства управле- ния переполнением. ’> В я. 6-2.4 были даны два класса средств разрешения колли- зий — открытая адресация и метод цепочек. Та же общая клас- сификация может быть применена и для средств управления пере- полнением. Если мы используем пакет с емкостью, большей еди- ницы, то фактически мы реализуем ограниченную схему линейного опробования из метода открытой адресации. Когда запись до- бавляется в незаполненный пакет, то новая запись помещается в следующее свободное место. Понятно, что следующее свободное место расположено в пакете и уже зарезервировано для записей, назначаемых по адресу этого пакета. При дальнейшем обсуждении мы будем ссылаться на пакет, в который запись направляется вычислением адреса, как на основной пакет для этой записи. Если запись отсутствует в основ- ном пакете, то она размещается в пакетах переполнения илн ее совсем нет в файле. Поскольку по одному запросу в основную область памяти переносится все содержимое пакета, то очень выгодно размещать требуемую запись где-то в основном пакете. Если запись отсут- ствует в основном пакете, то необходим еще один запрос для пере- мещения в основную память записи переполнения в соответствии с методом управления переполнением. При использовании линейного опробования для управления переполнениями требуется последовательный поиск записей в оставшихся пакетах файла. Поиск заканчивается успешно, если запись локализуется. С другой стороны, поиск оканчивается неудачно, если при поиске встречается пустая (фиктивная) запись или если поиск возвращается к первоначально просмотренному пакету. Разновидности открытой адресации, использующие случайное опробование, не всегда являются эффективными методами для обработки записей переполнения в файлах прямого доступа. В обоих методах просматриваемые последовательности пакетов переполнения не обладают свойством физической смежности. Это означает, что два пакета, просматриваемые последовательно друг за другом;, необязательно физически смежны Физическая смеж- ность может иметь очень существенное значение, так как для записей, не являющихся физически смежными, имеется большая вероятность перемещения головок в устройствах памяти с пере- мещающимися головками. Требуемое для этого добавочное время поиска иногда оказывается недопустимым. Записи переполнения могут быть связаны в цепочки, размеща- емые в отдельной области переполнения. Указатели на цепочки располагаются в основной области. Записи переполнения жела- тельно размещать в пакетах переполнения в той же области по- иска (например, на том же цилиндре), что и основная область для записи, попавшей в переполнение. Особенно хорошей стратегией 68Т
является резервирование нескольких последних пакетов области поиска только для записей переполнения основных пакетов в этой же области поиска. Такая же стратегия применяется при исполь- зовании области переполнения цилиндра в индексно-последова- тельных файлах. Однако пользоваться такой стратегией может оказаться затруднительным из-за нарушения пакетами пере- полнения необходимой для прямой адресации линейной схемы адресации (так как группы основных пакетов и пакетов перепол- нения будут чередоваться в пространстве файла). Поэтому может потребоваться независимая область переполнения, полностью отделенная от основной области. Последняя стратегия переполнения, которую мы рассмотрим, также основана на применении цепочек. Кнут ссылается на эту стратегию, как на метод срастающихся цепочек. В этом методе записи переполнения помещаются в свободные пакеты в основной области прямого файла, и места размещения переполнений опре- деляются указателями из одного пакета на другой. Следова- тельно, если хеширование ключа привело к некоторому пакету, то начинается поиск в цепочке пакетов, пока не найдется требуемая запись илн незанятое место памяти. Преобразование ключа может приводить к пакетам, которые не являются первыми в цепочке, поэтому цепочки могут срастаться. В цепочке пакетов могут быть найдены ключи, первоначально хешированные к различным ад- ресам. Сравнительный анализ методов управления переполнениями дан Кнутом в работе [19 J, где показано, что метод цепочек пере- полнения, использующий раздельные списки, требует наимень- шего среднего времени поиска среди обсужденных трех методов управления переполнением. Это явное преимущество достигается за счет двух факторов- Во-первых, отдельная область переполне- ния не считается частью общего пространства файла. Следова- тельно, если п записей добавляются к файлу, все и записей по- мещаются в основной области с использованием открытой адреса- ции или срастающихся цепочек. Однако, если имеется m записей переполнения, то при использовании раздельных цепочек в основ- ной области оказывается только п — щ записей. Поэтому при связывании переполнений в раздельные цепочки эффективный коэффициент заполнения файла оказывается меньше. Второй фактор способствует лучшим характеристикам любого метода, использующего связывание в цепочки, по сравнению с методом прямой адресации. При использовании списков ин- формация, касающаяся размещения следующей записи пере- полнения, помещается в саму запись. Вследствие этого отпадает необходимость анализировать промежуточные записи, которые могут и не быть записями переполнения. Стоит, однако, отметить, что указатели переполнений увеличивают длину записей. Это обстоятельство может быть существенным, особенно если файл состоит нз множества небольших записей. 688
Имя Адрес Внешней пакета (цилиндр, допожка, сектор)' Ashcroft 1,07,04 Barnsley J,07,0$ Bernard 1,0В,15 BuKe 1,06,09 Edder 1,07,00 Groff 1,06,10 Katz 1,06,11 Murray 1,08,13 Paulsen 1,06,00 Smith 1,06,03 Thomas 1,06,04 Tollarr [ 1,06,11 Yu 1,07,01 Рис. 7-8.1. Таблица перекрестных ссы- лок И наконец, необходимо иметь в виду, что доступ к независимой области переполнения чаще всего приводит к запросу на переме- щение головок. Поэтому при ис- пользовании устройств внешней памяти с перемещаемыми го- ловками связывание в цепочки в независимой области перепол- нения необязательно является наиболее эффективным приемом. Возможным компромиссом яв- ляется применение срастающих- ся цепочек. Правда, этот метод все же недостаточно тщательно исследован. До снх пор при обсуждении структур прямых файлов мы кон- центрировали внимание на методах хеширования (илн преобра- зования адреса). Имеются и другие, хотя и менее популярные, пути организации файлов прямого доступа, которые можно успешно применять в некоторых задачах. Если число записей в файле относительно невелико, а размер записей достаточно велик (т, е. в пакете размещается всего не- сколько записей), то имеет смысл рассмотреть схему прямой адресации. Ряд методов для выполнения прямого преобразования основывается на использовании перекрестных ссылок или индекси- рования. Таблица, перекрестных ссылок — это просто таблица клю- чей и адресов, в которой каждому ключу назначен уникальный адрес внешней памяти. Рис. 7-8.1 иллюстрирует таблицу пере- крестных ссылок имен и адресов внешней памяти для устройства с секторной адресацией. Таблица перекрестных ссылок в сущности представляет собой не что иное, как ассоциативный список в тер- минах п. 4-4. Для локализации записи по заданному ключу необ- ходимо просто прочитать внешний адрес, ассоциированный с клю- чом, а затем выдать команду ввода-вывода, которая прямо извле- кает требуемую запись. К сожалению, во многих языках програм- мирования ассоциативные списки не предусмотрены, и сам про- граммист должен вести таблицы перекрестных ссылок. Таблица может быть организована как неупорядоченный список, и поэтому в нее легко добавлять записи. В этом случае для поиска адреса необходим линейный просмотр, а удаления записей создают «дырки» в таблице. Можно вести таблицы как списки, упорядочен- ные по множеству значений ключа. При такой организации таблиц для ускорения поиска адреса удобно применять двоичный поиск. Однако добавления и удаления записей при этом вызывают опре- деленные проблемы из-за необходимости сохранения упорядочен- ности в таблице. 68&
Рис. 7-8.2. Бинарно-древовидная индексная схема для таблицы перекрестных ссылок Для обеспечения прямой адресации в файлах прямого доступа возможно применение индексных методов, основанных на бинар- ных деревьях, m-арных деревьях или древовидных структурах. При работе с методами, использующими древовидные структуры, добавления и удаления записей могут выполняться более эффек- тивно. Индексная схема в виде бинарного дерева для таблицы перекрестных ссылок приведена на рис. 7-8.2. Во всех рассуждениях в этом пункте мы предполагали, что при отсутствии требуемой записи в основном пакете вырабатывается серия команд ввода-вывода. Каждая команда вводит в основную память пакет переполнения, который сканируется для поиска записи. Только что описанная стратегия верна для устройств внешней памяти с секторной адресацией. Однако некоторые устройства с прямой адресацией записей, такие как дисковое устройство IBM 3330, способны обнаруживать отдельные записи на заданной дорожке по ключу с помощью аппаратных средств. При обсуждении в п. 7-8.3 файлов прямого доступа в ПЛ/1 мы обнаружим, что эта особенность аппаратуры может помочь избе- жать сканирования большей части пакетов переполнения [в основ- ной памяти, которое необходимо при использовании устройств с секторной адресацией. Теперь обратим наше внимание на тип обработки, который свя- зан с файлами прямого доступа. 7-8.2. Обработка файлов прямого доступа Обработка файлов прямого доступа зависит от метода преобразования множества ключей записей в адреса внешней памяти. В предыдущем пункте мы сделали обзор нескольких методов преобразования ключа. Для обсуждения обработки пря- 690
мых файлов применительно ко всем ।---------->----- । —] методам потребовалось бы слишком р [______________| '' | * много времени. Поэтому мы выбрали " в качестве репрезентативного примера tKEY_________link прямую обработку файла, доступ к ко- торому выполняется с помощью алго- ритма хеширования, использующего Рис- 7’8-3- Узел переполнения для управления переполнением раз- дельные списки. Алгоритмы, реализующие обработку для такого типа организации, и представлены в этом пункте. Прямые файлы в основном обрабатываются в режиме прямого доступа. Это означает, что ключ преобразуется в адрес, и в зави- симости от природы файловой транзакции запись создается, уда- ляется, обновляется, извлекается по этому адресу или по -не- которому следующему адресу, если имеет место коллизия. Есте- ственно, следующий адрес определяется с помощью предусмотрен- ных средств управления переполнением. Когда средства управления переполнением базируются на раз- дельных списках переполнения, то указатель на связанный список записей переполнения включаются в каждый пакет. Размещение записи переполнения в отдельном списке показано на рис. 7-8.3. Каждый элемент в области переполнения состоит из двух основ- ных частей: OR, содержащей запись переполнения 1, и указателя LINK с адресом следующего элемента в списке. KEY является ключом записи, находящейся в поле OR. Указатель на цепочку записей переполнения включается в каж- дый пакет, и для i-ro пакета этот указатель обозначается PTRj. Если записи переполнения для пакета отсутствуют, то PTRj имеет значение NULL; в противном случае его значением является адрес первой записи в цепочке переполнения для этого пакета. Следующий ниже алгоритм включает записи в файл прямого доступа, организованный согласно данной ранее спецификации. Алгоритм DIRECT— IN SE RT. Задана запись R с ключом х, требуется включить R в файл прямого доступа с п основными пакетами Вь ..., Вп. Каждый пакет В„ содержит m мест для запи- сей bilt bi2, ..., Ьря. Если запись занимает место Ьц, то ее ключ обозначается ки-. Если запись отсутствует, то в поле ключа зано- сится отрицательное число. Имеется много других соглашений, которые могут быть приняты для представления пустого места записи. В следующем пункте мы увидим, что в Системах IBM в первый байт помещается значение (8)'1'В. В случае возникнове- ния состояния переполнения запись R размещается в списке переполнения соответствующего основного пакета. Для вычисле- ния адреса используется хеш-функцня Н. 1. (Вычисление хеш-функции. I Установить i Н (х). J OR — начальные буквы слов overflow record. — Прим. пер. 691
2. [Сканирование указанного пакета. ] Если PTR,- =/= NULL то перейти к шагу 3. Повторять при j — 1, 2, m: если kjj <0, то установить b,j <— К и закончить выполнение алгоритма. 3. [Размещение записи R в области переполнения в голове списка переполнения. I Получить место для записи переполнения и присвоить ему адрес Р. Установить OR (Р) ч-Р, LINK (Р) •*- ч- PTRj, PTRj ч— Р и закончить выполнение алгоритма. В шаге 2 запись помещается в пакет, адрес которого получен с помощью хеш-функции, если только в пакете имеется свободное место. В противном случае берется свободный узел переполнения, его адрес назначается переменной Р, запись R помещается в место, указываемое Р, и указатели LINK (Р) и PTRj изменяются так, что новый элемент становится первым в цепочке переполнения для пакета i. Необходимо заметить, что в алгоритме отсутствует контроль возможности равенства ключа добавляемой записи ключу записи, уже находящейся в файле. Этот контроль необходимо делать для исключения наложения данных или их несостоятельности. Рас- ширение алгоритма DIRECT . INSERT для обеспечения такого, контроля оставлено в качестве упражнения. Алгоритм извлечения записи из файла прямого доступа, использующий цепочки раздельных списков, следует ниже. Алгоритм DIRECT_____RETRIEVE. Задан ключ х, требуется извлечь запись, идентифицированную этим ключом из файла пря- мого доступа с основными пакетами Bi, ..., Вп и отдельными областями переполнения. 1. [Вычисление хеш-функции. I Установить i ч—Н (х). 2. [Поиск в указанном пакете. I Повторять при i = 1, 2, m: если х = kjj, то установить R ч— и закончить выполнение алгоритма; в противном случае, если кц < 0, то запись не найдена, закончить выполнение алгоритма. Установить Р ч- PTRj. 3. [Поиск в цепочке переполнения. ] Если Р — NULL, то за- пись не найдена, закончить выполнение алгоритма. Если KEY (Р) — х, (если ключ записи, указанной в Р, есть х), то установить R ч- OR (Р) и закончить выполнение алгоритма; в противном случае установить Р ч- LINK (Р) и повторить этот шаг. В шаге 2 требуемая запись помещается в R, если она найдена в пакете В;. Если пакет не заполнен и запись не найдена, то поиск заканчивается неудачно. В противном случае в шаге 3 анализи- руется последовательно каждый элемент цепочки переполнения, пока запись не будет найдена или не встретится конец связанного списка. 692
Если эти алгоритмы применяются для дисковой памяти, то элементы переполнения должны быть размещены в общей области поиска, а в идеале — в той же самой области, что и связанные с ними основные пакеты. Это делается для того, чтобы избежать дополнительных операций перемещения головок при сканирова- нии связанного списка записей переполнения. Кнут [19] аналитически вывел формулу для средней длины поиска, предполагая наличие цепочек переполнения с емкостями пакетов единица или больше. Прежде чем применять метод цепочек переполнения, читателю полезно изучить эти результаты. В предыдущем пункте мы сделали обзор различных вариантов- открытой адресации как наиболее популярного метода управления переполнениями. Поскольку в п. 6-2.4 было дано несколько алго- ритмов для прямого доступа к таблицам, то здесь они больше не- повторяются. Предлагаем читателю еще раз просмотреть эти алгоритмы. Расширение возможностей алгоритмов с целью сделать их применимыми к файлам прямого доступа оставлено в качестве упражнения. До сих пор единственным типом обработки, который мы рас- сматривали применительно к файлам прямого доступа, являлась прямая обработка. В некоторых случаях может оказаться необхо- димым выполнение идентичных операций для всех или почти веек записей в файле. Например, в небольшой системе оплаты счетов,, на которую мы часто ссылаемся для иллюстрации материала по- организации файлов, может оказаться желательным печатать месячные счета, несмотря на наличие прямого доступа к индиви- дуальным счетам покупателей в онлайновом режиме. Для реше- ния такой задачи достаточно воспользоваться физически последо- вательным, или сериальным методом доступа. Сериальный доступ к файлу прямого доступа не всегда тожде- ствен последовательному. Это означает, что из хх х2 для ключей хх и х2 необязательно следует Н (хх) Н (х2) для некоторой функции хеширования Н. Фактически мы отбрасываем возмож- ность последовательного доступа в угоду более быстрому прямому доступу, когда переходим от индексно-последовательных к прямым: файлам. Для большинства файлов с прямой организацией сериальная обработка не ставит никаких проблем. Доступ начинается от физи- ческого начала и оканчивается в физическом конце файла. Однако, если файл использует отдельную область переполнения, то может оказаться затруднительным получение доступа к этой области в сериальном режиме, независимо от основной области. Трудности возникают из-за того, что область переполнения может состоять из несблокированных записей или сблокированных иначе, чем в основной области. Логически состоятельным, но потенциально весьма медленным методом сериального доступа к отдельной области переполнения является чтение записей в первом пакете основной области, затем всех записей переполнения для этого 693
пакета и затем возврат к чтению следующего основного пакета и его записей переполнения нт. д., пока все записи в файле не будут прочитаны. Последним аспектом обработки файлов прямого доступа, кото- рый следует рассмотреть, является ведение файла. Многие си- стемы, предоставляющие возможность работы с файлами прямого доступа, просто помечают удаленные записи и возвращают про- странство, занятое этими записями, когда новая запись должна быть добавлена в файл в помеченном месте. Место, занятое уда- .ленной записью, может требовать излишнего анализа при поиске записи в основном пакете. К тому же удаленные записи снижают эффективность при проверке или движении по цепочке в файле во время поиска записи переполнения. Иногда есть возможность логического исключения удаленных записей, особенно если при- меняются цепочки, что, однако, делается весьма редко. Вместо этого управление активностью файла отдается под ответственность программиста, с тем чтобы он проводил реорганизацию при силь- ной деградации характеристик функционирования. Реорганиза- цию можно выполнить сериальным чтением файла и последующим созданием нового файла прямого доступа, в который включаются только активные записи старого файла. Мы завершим этот пункт представлением наиболее важных свойств, относящихся к файлам прямого доступа. 1. Для прямых файлов характерно малое время прямого до- ступа к записям, особенно для файлов с низким коэффициентом заполнения и небольшим числом записей переполнения. 2. Вследствие того, что во избежание появления чрезмерного числа записей переполнения определенная часть файла остается незанятой, использование памяти в файлах прямого доступа менее эффективно по сравнению с другими изученными нами файловыми организациями. 3. Качество функционирования при использовании файлов пря- мого доступа в большой степени зависит от применяемого алго- ритма преобразования ключа в адрес, который, в свою очередь, зависит от области применения и обычно реализуется посредством программ пользователя. 4. Записи могут обрабатываться сериально, ио не последова- тельно, если только не ведется отдельный упорядоченный список ключей. 7-8.3. Файлы прямого доступа в ПЛ/1 В языке ПЛ/1 прямые файлы называются региональ- ными файлами. Имеется три типа региональных файлов: REGIONAL (1) REGIONAL (2) и REGIONAL (3). В этом пункте изучаются все три типа файлов. •Для полной оценки разницы между этими тремя видами прямой организации файлов нам необходимо в деталях обсудить формат дорожек для устройств памяти с адресуемыми записями, таких, «694
— Индексный маркер (начало дорожки) ___Собственный адрес ; | 11cve/77W/rj| Данные |[cw/zzwa-^Данные || • • • ^Счетчик|^Данные [• Описатель дорожки Запись с данными Запись с данными (RO) (R/) (R") а) Индексный маркер (начало дорожки) Собственный адрес СчетчикjjДанные^jСчетчикj Ключ Данные Описатель дорожки (R0) Запись с данными (R7) Счетчик^ Ключ Ц Донные Запись с данными- (Rn) 6У Рис. 7-8.4. Форматы дорожки: а — счетчик-данные: б — счетчик-ключ-данные как, например, дисковые устройства IBM 2311, 2314, 3330 и 3340- Подобное обсуждение вызвано явной ориентацией инструкций языка ПЛ/1 для региональных файлов на устройства прямого доступа с адресацией записей. Однако следует отметить, что эти инструкции в принципе применимы и для других типов устройств прямого доступа. Имеется два формата дорожек для устройств прямого доступа IBM. Эти форматы называются счетчик-данные и счетчик-ключ- данные. Размещение информации для этих форматов иллюстри- рует рис. 7-8.4. В обоих форматах область счетчика имеет длину 10 байтов и описывает положение записи в терминах цилиндра, дорожки и номера записи, длину ключа (равную 0, если ключ не исполь- зуется) и длину поля данных записи. Если ключ используется, то область ключа содержит ключ, имеющий длину от 1 до 255 бай- тов. Область данных содержит информацию, поставляемую поль- зователем для записи. Максимальная длина этого поля опре- деляется емкостью дорожки устройства. Область данных удобно представлять как блок (пакет или регион), который может состоять из нескольких логических записей. Однако, если в область данных помещается более чем одна логическая запись, ответственность за блокирование и разблокирование записей ложится на пользова- теля, но этот аспект в настоящем пункте обсуждаться не будет. Заметим, что запись является специальной записью, поддержива- емой системой. Она содержит информацию, относящуюся к ста- тусу дорожки (например, является ли дорожка дефектной, и, если Да, то запись содержит адрес альтернативной дорожки). §95-
Все региональные файлы делятся иа относительные регионы, каждый из которых идентифицируется номером региона. Регионы пронумерованы последовательно от нуля до 16777215 (224 — 1). Доступ к записи осуществляется заданием номера соответству- ющего региона в предложении ввода-вывода одним из двух спо- собов: в виде относительной записи или относительной дорожки. На относительную запись ссылаются по номеру относительно первой записи в файле. Рис. 7-8.5, а иллюстрирует деление ди- сковой поверхности на относительные записи. В схеме относитель- ных записей, которая используется в файлах REGIONAL (1) и REGIONAL (2), допускается только одна запись на регион. В спецификации относительной дорожки адресация отдельной дорожки осуществляется относительно первой дорожки. Рис. 7-8.5, б иллюстрирует этот тип организации. Регионом яв- ляется дорожка, в которую может помещаться более чем одна запись. В файлах R EGIONAL (3) обеспечивается именно этот тип -организации. Прежде чем рассматривать каждый из трех типов региональных •файлов, мы должны дать представление об исходном и записыва- емом ключах и добиться ясного понимания различий между ними. Исходным ключом называется литерная строка, задаваемая в пред- ложении записеориентированного ввода-вывода и следующая за группами KEY или KEYFROM. Например, в READ FILE (INVEN) INTO (STORE-ITEM) KEY (CATLG#) исходным ключом служит CATLG^jz, который может иметь, на- пример, значение ’А2Е0110124Г. В файлах REGIONAL (1) исходный ключ состоит только из номера региона, и этого номера достаточно для однозначной идентификации отдельной записи. Поэтому исходный ключ для «организации REGIONAL (1) состоит из строки длиной восемь Фис. 7-8.5. Спецификации регионов: «а — относительная запись: б — относительная дорожка <696
литер, способной представлять номер региона в диапазоне от О* до 16777215. Так как в организации REGIONAL (1) отсутствуют записываемые ключи, то используется формат счетчик-даиные.. Исходный ключ для файлов REGIONAL (2) или REGIONAL (3) имеет две логические части — ключ сравнения и номер региона. Номер региона находится в правых восьми позициях исходного ключа, а ключ сравнения обычно образуется из оставшейся части исходного ключа. Следовательно, исходный ключ со зна- чением ’А.2Е0110124Г обозначает номер региона ’01101241’ и ключ сравнения ’А2Е’. Часть исходного ключа, помещаемая в область ключа записи,, называется записываемым ключом. Записываемый ключ может иметь длину от одной до 255 литер. Длина ключа специфицируется посредством параметра KEYLENGTH атрибута ENVIRONMENT или как параметр блока DCB в DD-предложении языка управле- ния заданиями, имеющий форму KEYLENGTH—и. Записываемый ключ принимает значение ключа сравнения. Поэтому, чтобы объявить файл INVEN как REGIONAL (2) с за- писываемым ключом, равным только что рассмотренному нами ключу сравнения, полученному из исходного ключа CATLG^j:, мы пишем DECLARE INVEN FILE RECORD KEYED ENV (REGIONAL (2) KEYLENGTH = 3); Следовательно, первые три литеры исходного ключа образуют записываемый ключ. Если мы зададим KEYLENGTH равным единице или четырем, то ключ сравнения и записываемый ключ для ’А2Е01101241’ будут соответственно ’А’ или ’А2Е0’. Таким образом, часть исходного ключа может не использоваться, и ключ сравнения и записываемый ключ могут содержать часть или даже весь номер региона. Различие между ключом сравнения и записываемым ключом является, строго говоря, только функциональным. При выводе ключ сравнения помещается в область ключа записи как записы- ваемый ключ, а при вводе ключ сравнения должен сравниваться с записанным ключом. Формат предложений ввода-вывода для прямых файлов в ПЛ/1 идентичен формату предложений ввода-вывода для индексно- последовательных файлов. Пример предложения READ для файла INVEN был дан в этом пункте выше. Предложение WRITE для прямой записи в файл INVEN выглядит следующим образом: WRITE FILE (INVEN) FROM (STORE_JTEM) KEYFROM (CATLG#); Допустимы также предложения DELETE и REWRITE. Все эти предложения будут проиллюстрированы ниже в этом пункте. Существуют и другие общие особенности у прямых и индексно- последовательных файлов в ПЛ/L Прямые файлы также могут открыться для режимов INPUT, OUTPUT или UPDATE. Атри- буты DIRECT и SEQUENTIAL можно употреблять для специфи- 697
кации режима доступа к записям в файле. Атрибут DIRECT обозначает, что при доступе к записи используется ключ, SEQUENTIAL определяет доступ к записям согласно их физи- ческой последовательности в файле. Теперь подробнее опишем три упомянутые выше региональные организации файлов. Файлы типа REGIONAL (1). Файлы REGIONAL (1) предна- значены для применений, допускающих прямое 'преобразование адреса (т. е. каждый ключ в файле соответствует уникальному адресу внешней памяти). Исходным ключом для файлов REGIONAL (1) является номер региона. И в этом случае значение исходного ключа служит тем относительным адресом записи, который преобразуется в уникальный адрес внешней памяти. Обычной ситуацией при прямом преобразовании адреса является наличие незаполненных мест для записей. Места, не заполненные информацией пользователя, идентифицируются как фиктивные записи константой (8)1’В’ в первом банте записи (точно так же, как и в индексно-последовательных файлах). Фиктивные записи могут быть заменены любой действительной информацией. Файлы REGIONAL (1) и фактически любой региональный файл могут создаваться J3 последовательном или прямом режиме ввода. Однако, когда ’используется атрибут SEQUENTIAL OUTPUT при создании регионального файла, записи должны по- ставляться в порядке возрастания номера региона. Ошибка в по- следовательности или дублирующий ключ будут вызывать возник- новение состояния KEY. Любой регион, пропущенный во время создания файла, заполняется фиктивной записью. Если файл создается в режиме прямого доступа, то в момент открытия файла .все пространство в файле заполняется фиктивными записями. Доступ к файлам REGIONAL (1) может осуществляться в се- риальном режиме (одновременно являющемся последовательным по номерам региона) н в режиме прямого доступа Стандартные операции над файлами, такие как чтение, добавление, удале- ние н изменение, выполняются с помощью предложений READ, WRITE, DELETE и REWRITE. Примеры употребления этих •операций даны в программе на рис. 7-8.6. Программа воспринимает записи транзакций следующей формы: (тип транзакции} (номер автомашины) [подробная информация об автомашине] Для элемента (тип транзакции) допускаются такие значения, как ’REQUEST’, ’ADD’, ’UPDATE’ и ’DELETE’. Значением эле- мента (номер автомашины) является номер автомашины, состо- ящий из шести или менее цифр и используемый в качестве номера региона. Подробная информация, касающаяся автомашины, со- держится в структуре INFO. Эта информация не является обяза- тельной и отсутствует в записях транзакций, запрашивающих или удаляющих информацию об автомашинах. «98
// ЕКЁС PL1LFCLG,PARM=*ATR,XREF’ //PL1L.SYSIN DD * REGL^EG: PROCEDURE OPT IONS < MA IN J ? - DECLARE CARS RECORO KEYED ENV(F<U5I REGIONAL! 111, 1 INFO, 2 DEL_CODE BIT!6), 2 NAME CHAR(30), 2 ADDR CHAR 1801, i , 2 AGE FIXEDI21, 2 OFF# FIXEDI2), TRANSACTION CHARI 71, LICENSE# CHAR(6), 1 FIXED, TRANI 0:41 LABEL, TYPE_STR CHAR 1281 INITIAL I’REQUESTADD UPDATE DELETE ’I; ON KEVICARS) BEGIN; PUT SKIP LIST!'RECORD REQUESTED IS NOT PRESENT OR ATTEMPT ING’I I ' TO ADD A DUPLICATE RECORD — ONKEY IS: ’, ONKEY); STOP; End; OPEN FILE(CARS) DIRECT UPDATE; ON ENDFILE ISVSIN) STOP; /♦ SVSIN CONTAINS TRANSACTION RECORDS */ DO WHILE(’l'B); GET EDIT!TRANSACT ION,LICENSE#)!COL(1),A(T),A!6)): I = (INDEX!TYPE_STR,TRANSACT ION) * 6) /7; IF I -,= 2 С I -,= 0 /* NOT AN 'ADO' TRANSACTION */ THEN READ FILE I CARS) INTO!INFO) KEY!LICENSE#); 30 TO TRANI II; /* I DETERMINES TRANSACTION TYPE */ TRANI 0): /* ILLEGAL TRANSACTION */ PUT SKIP LIST!’ILLEGAL TRANSACTION’); GO TO CONTINUE; TRAN!1)s /* ’REQUEST’ TRANSACTION*/ PUT SKIP EDIT!’THE INFORMATION CONCERNING LICENSE NUMBER LICENSE#, ’IS AS FOLLOWS:'NAME: ',NAME,'ADDRESS: ’,AOOR,. ’AGE: AGE,'NUMBER OF PREVIOUS OFFENSES: ’,OFF«) (3 A,SKIP,2 A,SKIP,2 A,SK1P,A,F(2),SKIP,A,F! 21); GO TO CONTINUE; TRAN(2): /* ’ADD’ TRANSACTION */ GET EDI T ( NAME, ADDR ,AGE, OFF# ) (COL (1) , A!20) , COL! 11, A! 80 ) , 2 F(2))£. WRITE FILE!CARS) FROM!INFO) KEYFROM!LICENSE#); GO TO CONTINUE; TRAN!3): /* ’UPDATE' TRANSACTION »/ GET EDIT ( NAME, ADDR, AGE,OFF#HCOL ( 11, A! 20) ,COL! l»,A(80>,2 F < 2) I S. REWRITE FILE(CARS) FRCM(INFO) KEY!LICENSE#)5 GO TO CONTINUE; TRANI 4): /* ’DELETE' TRANSACTION */ DELETE FILE(CARS) KEYILICENSE#); CONTINUE: END; /* OF DO WHILE */ END REG1_EG; //GO.CARS OD DSN=LICENSE,DISP=(OLD,KEEP),UNIT=SYSDA //GO.SYSIN DD * (DATA GO HERE) Рис. 7-8.6. Пример программы обработки файла тира REGIONAL (1) . 699)
Регион О__________Регион 1_________Регион 2________ Запись А1ЕООООООО(\ {запись B2L00000002\ Запись Z6K00000003 Рис« 7*8.7- Включение записи в файл типа REGIONAL (2) Файлы типа REGIONAL (2). Каждая запись в наборах дан- ных REGIONAL (2) однозначно идентифицируется записанным ключом. Однако позиция записи в файле относительно других записей определяется номером региона, получаемым из исходного ключа в предложении WRITE, создающем запись. Когда запись добавляется к файлу, она помещается вместе с записываемым ключом в первое же свободное место после начала дорожки, со- держащей заданный регион. При чтении, перезаписи нли удалении записи поиск ведется от начала дорожки, содержащей заданный регион, и продолжается до обнаружения записи с соответству- ющим записанным ключом. Этот поиск продолжается за границей дорожки и может вестись до конца файла. Допускается лимитиро- вание области поиска некоторым числом дорожек, задаваемым параметром LIMCT блока DCB (например, LIMCT = п, где п — некоторое неотрицательное число) в предложении DD языка управления заданиями. Рассмотрим теперь на примере, как записи помещаются в файл .REGIONAL (2). Допустим, мы используем в качестве исходного ключа переменную CATLG4+, описанную ранее в этом пункте. Если мы используем первые три литеры 11-символьного номера в каталоге как записываемый ключ, то ’А1Е00000000’ специфи- цирует запись с номером региона, равным нулю, н записываемым ключом *А1Е’. Предположим, что имеется четыре региона (т. е. записи) на дорожку, как это показано на рис. 7-8.7. Пусть записи с исходными ключами ’А1ЕОООООООО’ и ’B2L00000002’ уже соз- даны. Тогда в результате выполнения предложения WRITE FILE (INVEN) FROM (SALES_ITEM) KEYFROM (’Z6K00000003’); ловая запись добавляется в файл в регионе 1. Она попадает в этот регион потому, что регион 1 является первым свободным местом от начала дорожки, содержащей заданный регион. Когда файлы REGIONAL (2) создаются последовательно, записи должны представляться в возрастающем порядке номеров регионов. Любой регнои, номер которого пропущен, заполняется фиктивной записью. Если в последовательности номеров имеется ошибка или делается попытка занести несколько записей с одним и тем же номером региона, то возникает состояние KEY. Если файл REGIONAL (2) создается в прямом режиме, то все пространства файла заполняются фиктивными записями. Записи могут вводиться в файл в любом порядке, и состояние KEY не 700
возникает ни при дублировании ключа, ни при дублировании ре- гиона, ни при дублировании ключа и региона одновременно. Как следствие, в файле могут оказаться дублированные записи. Чтобы избежать такого дублирования, разумно перед выполне- нием предложения WRITE использовать предложение READ с исходным ключом, равным ключу добавляемой записи. Если при выполнении предложения READ создается KEY-состояние, новую запись можно размещать в файле, не опасаясь дублирования. Когда доступ к файлу REGIONAL (2) осуществляется после- довательно, для извлечения записанного ключа можно применять пункт KEYTO. Например, выполнение предложения READ FILE (INVEN) INTO(SALES_ITEM) KEYTO (CATLG#); приведет к чтению следующей записи в соответствии с возраста- ющим номером региона. Записываемый ключ сцепляется с номером региона и затем присваивается переменной CATLG^. При выборке, удалении или обновлении записей с дублирован- ными записанными ключами действие осуществляется над за- писью, ближайшей к началу файла. На рис. 7-8.8 приведен пример программы, обеспечивающей генерацию и обновление накладных в системе приема и выполне- ния заказов. Номером региона служит номер накладной, а запи- сываемый ключ получается присоединением к номеру накладной одного нз следующих префиксов: ’О’, означающего «на заказ»; ’S’, означающего «отгружено»; ’R’, означающего «получено и оплачено» и ’D’ — «возвращены отдельные дефектные предметы». В большинстве случаев имеется только одна накладная на каж- дый номер региона (т. е. используется прямая адресация). В не- которых ситуациях, однако, товары, относящиеся к одному заказу, могут быть получены, и часть из них позже возвращены как де- фектные. В этом случае для заказа создаются две записи с теми же номерами региона. Чтобы удовлетворить этому специальному тре- бованию, предусматривающему возможность дублирования номе- ров региона, не используются номера заказов, оканчивающиеся нулем. Программа на рнс. 7-8.8 иллюстрирует, как файлы типа REGIONAL (2) можно использовать в режиме прямой адре- сации. К файлам этого типа применима и косвенная адресация. Например, в прикладной задаче, связанной с выпуском узлов агрегатов несколькими производственными отделами, различные производители имеют различную схему нумерации узлов. При- мерами номеров узлов являются ТС103, 176—232, 6225АХ. Пред- положим, что в системе желательно использовать файлы прямого доступа. В таком случае для получения номеров региона можно хешировать номера узлов. Применяя при этом в качестве записы- ваемого ключа фактические номера узлов, мы сможем с помощью этого ключа единственным образом идентифицировать запись. Следовательно, регион используется как основная область, номер 701
Il EXEC PL1LFCLG,PARM=’SM=(2,8C,11’ //PL1L.SYSIN DD * REG2—EG: PROCEDURE OPTIONS (MAIN!; i DECLARE INVOICE RECORD KEYED ENV (REGIONAL!2) F(92)), 1 RECORD, - 2 DELETE—CODE BIT(8), 2 DATE FIXED (6), 2 ITEM-COUNT FIXED (1), 2 TOTAL-PRICE FIXED <10, 2», 2 ITEM (5), ! 3 PART# CHAR (6), 3 HANUF# CHAR (3), 3 QUANTITY FIXED (2), 3 UNIT_PRKE FIXED (B, 21, (TYPE_CDDE, NEW—TYPE_COOE) CHAR (1), INVOICE# CHAR (8), TRANSACTION CHAR (71, TYPE-STR CHAR (281 INITIAL I'REQUESTADO UPDATE DELETE *1, I FIXED, LABEL (0:4i LABEL; ON KEY (INVOICE) BEGIN; PUT SKIP LIST (’ERROR -- KEY CONDITION RAl SEO’,’ONKEY=’,ONKEY); END; OPEN FILE (INVOICE) DIRECT UPDATE; ON ENOFILE (SYSIN) STOP; LOOP: DO WHILE 1’1’B); GET FILE (SYSIN) EDIT (TRANSACTION, TYPE-CODE, INVOICE#) (COL(I), A(7), All), A(8)); I = (INDEX (TYPE-STR, TRANSACTION) ♦ 6) / 7S IF I 2 С I •»= 0 /♦ NOT AN ’ADD’ •/ THEN READ FILE (INVOICE) INTO (RECORD) KEY (TYPE-CODE II INVOICE#) GO TO LABEL!I); LABEL(0): /* ILLEGAL TRANSACTION ♦/ PUT SKIP LIST (’ILLEGAL TRANSACTION :’, TRANSACTION); GO TO CONT IN; LABEL 11): /• ’REQUEST’ TRANSACTION »f PUT SKIP EDIT (’INVOICE #’,INVOICE#,’DATE ’,OATE, •PART’MANUFACTURER’,’QUANTITY’,’UNIT PRICE’) (2 A,X(1O),2 A.COLl1),A,COL110),A,COL(30),A,COL(50),A) ; PUT SKIP EDIT ( (ITEM(I) DD 1=1 TO ITEM-COUNT) ) ((ITEM-COUNT) (COLIl),A,COL(15),A,COL(33),F(2),COL(50),F(11,2)))5 PUT SKIP EDIT (’TOTAL PRICE’, TOTAL-PRICE) (COL(30),A,€OL(48),FI13,2)); GO TO CONTIn; LABEL(2): /♦ ’ADD' TRANSACTION ♦/ GET EDIT (DATE, ITEM-COUNT, TOTAL—PRICE) (COL(IJ, F(6), F(l), F(10,2)); GET EDIT ( (ITEM (!) DO 1=1 TO ITEM—COUNT) ) ( (ITEM-COUNT) (COL(1),A<6),A(3),F(2),F(8,2)) ); WRITE FILE (INVOICE) FROM (RECORD) KEYFROM (TYPE-CODE II INVOICE#)» GO TO CONT in; Рис. 7-8.8. Пример программы обработки файла типа REGIONAL (3) 702
LABEL О»: GET *=' GET GET 1 "UPDATE’ TRANSACTION •/ EDIT (NEW_TY₽ E_CODE » (COKO, AIL»»; EDIT (DATE, ITEM_COUNT, TOTAL_PRICEI \ (COLILI, FI6), FILI» FlL0,2»»; EDIT ( (ITEM II» 00 1 = 1 TO ITEr4_C0UNT» » ( (ITEH_COUNT1 (COLl1),A(6»,A(3»,F(2»,F(6i2lI )( THEN*RtWRITt FILEW(INVOICE»6FROM (RECORD» KEY (TYPE-CODE II INVOICE*! EL5E °9i_____________ГОСГПОП1 WRITE FILE (INVOICE» FROM (RECORD» KEYEROM (NEW_TYPE_CODE II INVOICE»» GO TO LABEL!*J: /• TO DELETE OLD RECORD ' END; GO TO CONTIN; LABEL!*): /» "DELETE" TRANSACTION */ DELETE FILE (INVOICE) KEY (TYPE-CODE II INVOICE»); CONTIN: ENO LOOP; //GO INVOICE*00 DSN=INVEN,DISP=(OLO,KEE₽),UNIT«SYSOAt j, ’ OCB=(OSORG-DA,KEYL£N=9) //GO.SYS IN DD * (DATA GO HERE) /* Рис. 7-8.8. Продолжение региона указывает относительную позицию записи, а записыва- емый ключ служит для выделения желаемой записи внутри этой области. Файлы типа REGIONAL (3). Файлы, имеющие организа- цию REGIONAL (3), отличаются от файлов типа REGIONAL (1) и REGIONAL (2) в следующих аспектах. Каждый регион в фай- лах REGIONAL (3) является дорожкой на устройстве прямого доступа (см. рис. 7-8.5, б), и номер региона не должен превосходить 32767. Вероятно, наиболее значительным отличием файлов REGIONAL (3) является то, что регион может содержать одну и более записей и допускаются записи как фиксированной, так и переменной длины. Если файл создается последовательно, записи должны посту- пать в порядке возрастания номеров регионов. Последовательные записи, однако, могут иметь идентичные номера регионов. Когда дорожка заполняется записями, соответствующий номер региона автоматически увеличивается. Последующая попытка добавить запись, имеющую номер предыдущего региона, приводит к состо- янию KEY, как и любая ошибка в последовательности номеров. Если создается файл с записями фиксированной длины в ре- жиме прямого доступа, все файловое пространство заполняется фиктивными записями в момент открытия файла. Для файлов с записями переменной длины в каждом регионе формируется только одна фиктивная запись, которая может занимать всю до- рожку. Так же, как и в случае записей REGIONAL (2), повторя- ющиеся ключи в одном н том же регионе ие вызывают никакого исключительного состояния. Когда запись добавляется в регион, °на помещается на место первой встреченной фиктивной записи. 703
II EXEC PL1LFCLG,P'ARM-'SIZE-999499,5M-(2,80,I»• //PL1L.SYSIN DD • REG3_EG: PROCEDURE OPTIONS (MAIN»; x DECLARE ABSTRACTS FILE RECORD KEYED ENV (REGI0NAL(31 ¥(161811, /* RECORD IS COMPOSED OF A TEN CHARACTER CASE NUMBER FOLLOWED BY A VARIABLE LENGTH CASE ABSTRACT ♦/ RECORD CHAR <16101 VARYING. CARO CHAR (801, EOF BIT (1) INIT (’O’B!, REQUEST CHAR (81, REQUEST_STR CHAR (321 INIT (•RETRIEVEAOO DELETE REPLACE •», (I, TYPEI FIXED, NAMES CHAR 1171, HASH ENTRY (CHAR (*!1 RETURNS (CHAR (8JI, GETCAROS ENTRY, PROC (0:41 LABEL; ON ENOFILE (SYSIN! EOF - ’I’B; ON KEY (ABSTRACTS! BEGIN; PUT SKIP E?IT (’ERROR IN REQUEST: ’• REQUEST, ’NAMES: ’, NAMES» . <2 A,X(101,2 Al; IF ONCODE - 51 THEN DO; PUT SKIP LIST (’RECORD NOT FOUND’»; CALL GETCARDS 1 END; GO TO continue; end; O’EN FILE (ABSTRACTS! DIRECT UPDATES GET FILE (SVSIN! EDIT (CARD! (A (80)); . j DO.LOOP: DD WHILE T-.E0F1; J REQUEST - SUBSTR (CARO, 2, 6>( * NAMES • SUBSTR (CARD, 10, 171; TYPE » (INDEX CREQUEST-STR, REQUEST) F П / 8; GO TO PROC (TYPE!; j PROC(O): /♦ BAD REQUEST •/ ’ PUT SKIP LIST (’UNKNOWN REQUEST REQUEST); CALL GETCAROS; GO TO continue; PROCdl: /* ’RETRIEVE’ REQUEST */ READ FILE (ABSTRACTS! INTO (RECORD! KEY (NAMES II HASH (NAMES!»: PUT SKIPI2» EDIT (’CASE’, NAMES, ’PROCEEDINGS SUBSTR (RECORD,1,101 (A, X(41, A, C0L<35),2 А»; j PUT SKIPI21 LIST (’ABSTRACT :•»; DO I « 11 BY 80 WHILE (I <• LENGTH (RECORD!); PUT SKIP LIST (SUBSTR (RECORD, I, 801»; end; put skip; CALL GETCARDS; GO TO CONTINUE; PR0C(21: /• ’ADD» REQUEST */ CALL GETCARDS; WRITE FILE (ABSTRACTS» FROM (RECORD! KEYFROM (NAMES II HASH (NAMES!!; Рис. 7-8.9. Пример программы обработки файла типа REGIONAL (3) 704
GO TO CONTINUE J pRDCO):, /» ’DELETE' REQUEST */ DELETE FILE (ABSTRACTS» KEV (NAMES >1 HASH (NAMES))» , CALL GETCARDS; •- GO TO CONTINUE; PRDC(4): /* •UPDATE' REQUEST </ CALL GETCARDS; REWRITE FILE (ABSTRACTS) FROM (RECORD! KEY (NAMES II HASH (NAMES)I; CONTINUE: END DO_LOO₽; GETCARDS: PROCEDURE; /* READ PROCEEDINGS NO. ANO ABSTRACT (IF NEEDED) AND GET NEXT REQUEST »/ GET FILE (SYSIN) EDIT (CARD) (A (8O»l; IF SUBSTR (CARO* 1» 1) = **• THEN RETURN; RECORD = SUBSTR (CARD, 1, 10); GET FILE (SYSIN) EDIT (CARO) (A (S0> >5 DO WHILE (-«EOF £ SUBSTR (CARD, 1, 1) -= •*•>? RECORD = RECORD || CARO; GET FILE (SYSIN) EDIT (CARD) (A (80)); END; END GETCARDS; HASH: PROCEDURE (KEY) RETURNS (CHAR (8)»? DECLARE KEY CHAR (»», (I, HASHED) FIXED; I « INDEX (KEY, •-*); HASHED = MOO (UNSPEC (SUBSTR (KEY, 1, 3)) * UNSPEC (SUBSTR (KEY, I, 3))- 997)? RETURN (HASHED); END HASH: ENO REG3.EG? //GO.ABSTRACT DO DSnAHE-CASES,01SP*(0LD,KEEP),UNIT=SYSDAj // 0CB=(DS0RG=0A,KEYLEN=17) //GO.SYS IN DD # (DATA GO HERE) /<’ Рис. 7-8.9. Продолжение При использовании записей переменной длины запись добавляется в первое имеющееся свободное место на дорожке. Прямой доступ к записи осуществляется сканированием кон- кретной дорожки (определяемой номером региона в исходном ключе), пока не будет совпадения ключа сравнения с записанным ключом. Следует отметить, что пространство, занятое записью фиксированной длины, может использоваться повторно. Однако место, которое занимала удаленная запись переменной длины, становится недоступным для повторного размещения записей. Кроме того, нельзя заменить запись переменной длины другой, имеющей большую длину. Попытку выполнить такое действие можно контролировать использованием условия ON RECORD. На рис. 7-8.9 показан пример программы, иллюстрирующий обработку файла типа REGIONAL (3). Файл содержит ряд выпи- 23 Трамбле Ж., Соренсон П. 705
сок^из судебных дел гражданского суда. Первые десять символов каждой записи являются номерами судебных протоколов. Эти номера используются для получения полных копий протоколов в том виде, в каком они составлены судебным протоколистом. Остальная часть записи содержит переменное число восьмидесятн- символьных строк текста, содержащего выписки из судебных дел. Исходный ключ состоит из двадцатипятилитерного ключа срав- нения, содержащего имена участвовавших в процессе сторон (на- пример, HARTENGER — WILSON), и восьмиразрядного номера региона, первые пять цифр которого нули, а три последние опре- деляются хеш-функцней. Хеш-функция основывается на методе деления и использует три первых символа двух имен участву- ющих сторон, представленных в коде EBCDIC [например, для HAR и WIL мы имеем (C8ClD9)je и (E6C9D3)ie или 13 156 625 и 15 124 947 соответственно ]. Полученные числа складываются друг с другом, и результат, взятый по модулю 997 (наибольшее простое число, меньшее 1000), берется в качестве номера региона (в этом примере номером региона будет число 670). Запись транзакции состоит частично из команд ^RETRIEVE, #ADD, ^DELETE йли #REPLACE *, которые вместе с име- нами участвующих в процессе сторон формируют первую строку транзакции. Для команд * ADD и * REPLACE требуются доба- вочные информационные элементы (т. е. номер судебного прото- кола и выписки из дел). Теперь становится очевидным, что в отношении разрешения коллизий региональная организация файлов отличается от ранее описанных в этой главе типов организаций. В региональных файлах коллизии в явном виде отсутствуют. При этом, конечно, подразумевается, что устройства внешней памяти способны лока- лизовать отдельную запись по ее ключу. Дисковое устройство IBM 3330 (с адресацией записей) имеет такие аппаратные средства, и при его использовании нет необходимости переносить запись в основную память для ее идентификации. В файлах типов REGIONAL (2) и REGIONAL (3) в режиме прямого доступа добавление записей выполняется без контроля на присутствие в файле записей с тем же номером региона, т. е. контроль коллизий отсутствует. Вместо этого запись помещается в первое же свобод- ное место дорожки, ассоциированной с номером региона записи. Конечно, если дорожка заполнена, то осуществляется поиск сво- бодного места на последующих дорожках. Таким образом, для файлов REGIONAL (2) и REGIONAL (3) дорожку можно пред- ставлять как пакет, в котором располагаются записи. Если жела- емая запись не локализуется на основной дорожке (определяемой номером региона), то дальнейшие действия выполняются сред- ствами управления переполнением. * * ИЗВЛЕЧЬ , * ДОБАВИТЬ , * УДАЛИТЬ , *ЗАМЕНИТЬ 706
В противоположность этому, устройства с секторной адреса- цией требуют больше действий для локализации записей пере- полнения. Во-первых, в основную память переносится основной пакет (т. е. сектор), и его содержимое сканируется для определе- ния присутствия в пакете нужной записи. Если ее нет, то в основ- ную память переносится, а затем анализируется пакет перепол- нения. И прежде чем запись будет найдена, может потребоваться целый ряд таких действий. Очевидно, что необходимость переноса содержимого пакета в основную память с последующим сканиро- ванием его запись за записью, делает прямой доступ для устройств с секторной адресацией медленнее, чем для устройств с адреса- цией записей. В следующем параграфе мы обсудим онлайновую банковскую систему в качестве примера применения файлов прямого доступа. Поскольку система реализована на языке ПЛ/1, то к ней имеет отношение большая часть материала этого пункта. Упражнения к п. 7-8 1, Каким образом емкость пакета влияет на характеристики функ- ционирования информационных систем, в которых используются файлы прямого доступа? 2. Перечислите преимущества и недостатки использования цепочек пере- полнений с раздельными списками по сравнению с методом открытой адресации. 3. Постройте алгоритмы для включения, удаления и извлечения записей из файлов прямого доступа, если переполнения связываются в срастающиеся цепочки. 4. Сформулируйте алгоритм DIRECT—SEQ для чтения записей файла пря- мого доступа, в котором обработка переполнений основывается на применения раздельных цепочек переполнения. 5. Перечислите преимущества и недостатки файлов прямого доступа на устройствах с адресацией записей (таких, как дисковые устройства IBM 3330) по сравнению с размещением файлов на устройствах с секторной адресацией. 6. Как вы думаете, почему записи переменной длины не допускаются в фай- лах REGIONAL (1) и REGIONAL (2)? 7. Хорошо всем знакомым типом информационной системы, работающей в онлайновом режиме, является система резервирования мест на авиалиниях. Система должна обеспечивать мгновенное резервирование (и подтверждение при- ема заказа) места для пассажира на определенный рейс. Попытайтесь разрабо- тать и реализовать эту часть системы. Предполагается, что при резервировании приходится иметь дело с двумя типами элементов информации. Первый элемент имеет форму запроса, сделанного на билет определенного типа (общий или первый класс) иа заданный рейс (например, TW937) в определенный день. Система должна сразу же определить, может ли запрос быть выполнен. Если да. то поступает вторая порция информации — имя пассажира и номер телефона, используемая для завершения операции резервирования. Напишите программу на ПЛ/1, в которой для резервирования мест приме- няется файл прямого ‘доступа [типа REGIONAL (3)]. Наименование рейса, имеющее форму (сокращенное наименование авиалинии) {трехзначный номер), используется как часть ключа для прямого файла. Примерами наименований рейса являются АМ666, АС 125, CPQ98, ЕА930, LU002, NW794 и TW365. Каж- дой авиалинии должен соответствовать свой номер региона в исходном ключе. Номера и даты рейсов используются для идентификации рейсов внутри задан- ного региона файла. Следовательно, информация о рейсе АМ666 иа 5 января 1975 г. преобразуется в исходный ключ 666005197500000001, если авиакомпании Ате- 23* 707
rican Airlines соответствует номер региона 00000001. После 666 является номе- ром полета, 005 обозначает пятый день года, а 1975 — год. В первых трех записях файла типа REGIONAL (3) должно храниться число мест каждого класса, которые еще не зарезервированы, и общее число мест на рейс. Остальные записи представляют собой значения исходных ключей для файла типа REGIONAL (1), каждая из записей которого содержит имя пасса- жира и его номер телефона. Например, когда в систему поступают данные о пас- сажире, заказавшем место общего класса, этому пассажиру присваивается номер, служащий исходным ключом для файла типа REGIONAL (1). Этот номер поме- щается в файл типа REGIONAL (3), и число во второй записи этого файла умень- шается на единицу. При завершении процедуры резервирования имя и номер телефона пассажира записываются в соответствующее место в файл типа REGIONAL (1). Реализация процедуры аннулирования заказа очевидна. По- старайтесь снабдить систему процедурами контроля ошибок и тщательно про- тестируйте ее. 8. Каковы преимущества и недостатки применения двух файлов вместо од- ного (например, только одного файла типа REGIONAL (3)) в упр. 7? 7-9. ОНЛАЙНОВАЯ БАНКОВСКАЯ СИСТЕМА Чтобы продемонстрировать применение файлов прямого доступа, мы выбрали банковскую систему для ведения счетов вкладчиков, которая должна функционировать в онлайновом режиме. Реализация системы на ПЛ/1 иллюстрирует использова- ние двух типов файлов прямого доступа: REGIONAL (1) и REGIONAL (2). При разработке системы основное внимание уделяется методологическим аспектам, и многие из тонких реше- ний, применяемых в современных банковских системах, не полу- чили здесь отражения. Тем не менее изучение разработки системы поучительно и интересно. 7-9.1. Анализ системы Система должна позволять клиентам банка выполнять денежные операции через посредничество кассира, который вво- дит транзакции 1 с помощью клавиатуры терминала, и обеспечи- вать немедленное изменение текущего счета клиента. Клиент может узнать текущее состояние своего счета с помощью банков- ской книжки. Если клиент предъявляет банковскую книжку кассиру, последний может ввести ее в специальный регистриру- ющий паз терминала, и текущая транзакция плюс все ранее не- зарегистрированные транзакции печатаются в банковской книжке. На рынке имеется целый ряд специальных банковских термина- лов. Типичная клавиатура, ориентированная на конкретные транз- акции, изображена на рис. 7-9.1. Важно разработать систему та- ким образом, чтобы можно было выполнять денежные операции без банковской книжки клиента, так как клиент имеет преслову- 1 В данном параграфе авторы употребляют термин транзакция «tran- saction» как для обозначения банковских операций клиента, так и для опера- ций над файлом счетов. — Прим. пер. 708
Окошко дня сообщений Рис. 7-9.1. Клавиатура для системы, ориентированная на банковские операции тую привычку забывать ее из-за рассеянности или лени. Процент, начисляется ежемесячно по низшему балансу последнего месяца. Это вычисление выполняется автоматически и отмечается в бан- ковской книжке, как только книжка предъявляется. Если бан- ковская книжка не была предъявлена в течение какого-то месяца, то процент накапливается, пока банковская книжка не будет предъявлена, и в ней не будут сделаны все необходимые записи. Мы ограничимся в системе только одним типом счетов клиента— вкладами для хранения сбережений, с которыми можно выпол- нять следующие операции: 1. Открытие нового счета. 2. Внесение денег на существующий счет. 3. Снятие денег с существующего счета (чековый сервис не допускается). 4. Проверка состояния счета (с записью в банковской книжке). 5. Закрытие существующего счета. Система должна быть разработана так, чтобы вести счета из ряда отделений, расположенных в той же самой географической области. Таким образом, клиент не обязан ходить в одно и то же отделение для выполнения всех операций. Последним требованием к системе является обеспечение ее надежности. При отказе вычислительной машины, используемой для обновления счетов клиентов, система должна обеспечивать подстраховочный режим выполнения операций. В идеале одно- 709
временно должна функционировать вторая ЭВМ и выполнять те же операции, что и первая. Если первая вычислительная ма- шина отказывает, вторая может продолжить ведение счетов без прерывания банковского обслуживания. Менее дорогой формой подстраховки системы является присоединение мини-ЭВМ для записи на магнитную ленту всех транзакций в период неработо- способности основной системы. Когда система восстанавливается, транзакции посылаются в пакетном режиме в основной вычисли- тель. Последней альтернативой может служить переход на ручной способ записи событий в период отказа основной системы. При восстановлении работоспособности системы задерживаемые транз- акции вводятся кассиром в момент, когда у него появится возмож- ность это сделать. Несмотря на все сказанное, в следующем пункте мы рассмотрим разработку основной системы, игнорируя проблему подстраховки ее работы. 7-9.2. Проектирование системы Мы начнем €. изучения тех аспектов проектирования системы, которые наиболее существенны для пользователя, а именно средств ввода транзакций. В полностью автоматизиро- ванной банковской системе кассиры в идеале вводят транзакции клиента посредством клавиатуры, похожей на ту, что изображена на рис. 7-9.1. Однако мы предположим, что в нашей системе ис- пользуется стандартное устройство ввода (например, дисплей). Мы делаем это допущение в основном для лучшей иллюстрации взаимосвязи между набранным на терминале запросом и резуль- тирующей транзакцией для файла в программе на ПЛ/1. Заметим, что специальные банковские терминалы являются роскошью, которую не каждый банк может себе позволить. Должно быть очевидным, что если в банковской системе, рабо- тающей в онлайновом режиме, используется язык запросов, то он должен иметь сжатую форму. Поскольку кассиры постоянно взаимодействуют с системой, сокращенная структура команд повышает эффективность и уменьшает ошибки при наборе на кла- виатуре, с большей вероятностью появляющиеся в длинных тек- стовых командах. Также полезно по возможности подсказывать кассиру требуемые от него действия при запросе им помощи. Неопытному кассиру эта возможность будет помогать, искушен- ный может отказаться от средств подсказывания. Пятью транзакциями клиентов, с которыми обычно имеют дело при банковском обслуживании, являются: открытие счета, снятие н вклад на счет, запрос о состоянии счета и закрытие счета. Так как процедуры открытия и закрытия счета сравнительно редкие, но в то же время и очень важные события, операциям такого типа часто дается специальный статус. В некоторых банковских системах выполнение этих операций разрешается только упра- вляющим, которые выполняют их, набирая на клавиатуре спе- 710
Рис. 7-9.2. Схема, иллюстрирующая обработку транзакций клиента в банковской системе циальные коды, известные только им одним. Другие банковские системы вообще не выполняют вклады в онлайновом режиме по соображениям безопасности. Типы обработки, необходимые в онлайновой банковской си- стеме, совершенно элементарны. Входная информация, предста- вляющая собой транзакцию клиента, вначале анализируется. После определенияДхарактера транзакции активизируется не- которая системная процедура. Для транзакций каждого типа имеется по одной системной процедуре. Рис. 7-9.2 в общих чертах дает представление о функционировании системы. Специальный модуль для сканирования и идентификации базовых лексических единиц входных сообщений вызывается несколько раз на каждый входной запрос процедурой анализа входной информации. В си- стемах с функциональной клавиатурой, подобной той, которая изображена на рис. 7-9.1, необходимость в упомянутом модуле отсутствует. Вернемся теперь к исследованию требуемых системой структур файлов и файловых транзакций. Почти во всех современных бан- 711
ковских системах доступ к информации, касающейся банковского счета, осуществляется по номеру счета. Это справедливо как для ручных, так и для автоматизированных систем. Номер счета яв- ляется хорошим средством для однозначной и отчасти анонимной идентификации счета. Уникальность номера счета делает его идеальным основным ключом для доступа к информации о счете в системе с компьюте- ром. Если предположить, что мы разрабатываем онлайновую систему для сравнительно небольшого банка, то можно принять следующую схему нумерации счетов. Используется пятизначный номер счета, первые две цифры которого указывают на отделение банка. Если отделение слишком велико, то одному и тому же отде- лению назначается несколько двухзначных чисел. Последние три цифры идентифицируют клиента. Такая схема нумерации счетов очень хорошо подходит для файла прямого доступа, при этом первые две цифры используются для локализации относительного региона, в котором размещается запись клиента, и последние три цифры служат для выделения желаемой записи. Файловая организация REGIONAL (2) в ПЛ/1 хорошо моделирует эту форму доступа, поэтому мы ее и будем использовать. В дальнейшем мы ссылаемся на этот файл как на файл счетов. Чтобы клиент мог вносить денежный вклад и снимать деньги со счета без предъявления банковской книжки, онлайновая си- стема нуждается в файле для хранения транзакций, еще не напе- чатанных в банковской книжке. Для заданного счета система должна располагать возможностью проверки наличия незареги- стрированных записей и в случае их существования обеспечивать прямой доступ к этим записям. Хотя существует несколько путей решения проблемы незаре- гистрированных входных транзакций, мы проанализируем только один, использующий файл прямого доступа в. режиме прямой адресации. Например, мы можем создать файл, содержащий 100 записей, пронумерованный от 0 до 99. Записи первоначально организуются как связанный список, в котором запись п указы- вает на запись п + 1 для 1 с п < 98, а поле связи записи 99 содержит число —1 (обозначающее конец списка). Запись с но- мером 0 является специальной записью, содержащей два указа- теля: один указывает на текущую голову списка (вначале он уста- навливается равным 1), а другой показывает на текущий хвост списка (первоначально устанавливается равным 99). Запись 0 фактически представляет собой специальную голову списка. Между файлом счетов и файлом незарегистрированных транз- акций имеется связь. Каждая запись в файле счетов содержит указатель, либо ссылающийся на связанный список в файле не- зарегистрированных транзакций, либо содержащий число —1 (начальное значение), служащее для индикации отсутствия не- зарегистрированных транзакций. На рис. 7-9.3 отражена связь, 712
Файл счетов Файл незаписанных транзакций Рис. 7-9.3. Связь между файлом счетов и файлом незарегистрированных транзакций которая может существовать в случае файла счетов, содержащего только три записи. На этом рисунке штриховые стрелки обозна- чают связи в списке свободных мест для записей, а сплошные стрелки — связи для незарегистрированных транзакций.- В файле незарегистрированных транзакций сформирован ряд независимых списков, по одному списку для каждого номера счета, имеющего незарегистрированные транзакции. Когда для одного из этих счетов предъявляется банковская книжка, то она обно- вляется в соответствии с каждым из узлов списка незарегистри- рованных транзакций данного номера счета. Все узлы обработан- ного списка становятся затем доступными для других незареги- стрированных транзакций, появляющихся позже. Следовательно, список, имеющий головой запись 0, используется для управления свободными записями в файле незарегистрированных транзакций. Совершенно ясно, что организация REGIONAL (1) с ее возмож- ностями прямой адресации вполне подходит для файла незареги- стрированных транзакций. При разработке банковской системы имеется много других аспектов, требующих рассмотрения. Например, желательно иметь возможность замораживать счет до решения вопроса по имуществу умершего клиента. В автоматизированной системе должны быть предусмотрены средства защиты информации. Для сохранения всех транзакций за предыдущие шесть месяцев (нли что-то около 713
этого) следует вести архивные файлы, которые нужны как для процедур проверки счетов, так и для получения дубликата при потере или краже банковской книжки? Необходимы также сред- ства корректировки ошибочных транзакций, которые уже были использованы для изменения счета. Этот аспект очень важен при создании систем, ориентированных на пользователя. Если ошибки нельзя исправить легко и быстро, пользователи вскоре разочаруются и будут вести свои дела где-нибудь в другом месте. И, наконец, система, которую мы изучаем, обрабатывает только вклады для хранения. Чеки, чековые вклады, займы и кредитные карточки являются другими обычными услугами, предоставля- емыми банковскими системами. 7-9.3. Реализация Реализация онлайновой банковской системы точно так же, как реализация других систем, описанных в этой главе, вклю- чает программирование ряда подмодулей, вызываемых из глав- ного модуля программы. Эти подмодули, поименованные как AVALUATE, SCAN, ACTIVATE и PASSWRITE, соответствуют обрабатывающим блокам иа рис. 7-9.2. На рис. 7-9.4 приведены секция инициализации, процедура SCAN, скелеты процедур AVALUATE и ACTIVATE, процедура PASSWRITE, а также главная секция программы. Файлы АССТ и TRANS соответствуют описанным в предыдущем пункте файлам счетов и незарегистрированных транзакций. Структуры ACCOUNT и TRANSACTION описывают записи в файлах АССТ и TRANS соответственно. Процедура SCAN вызывается из про- цедуры EVALUATE всякий раз, когда необходимо выделить из входной транзакции пользователя новую лексическую единицу. Процедура EVALUATE не приводится главным образом из-за ее большой длины и также потому, что она не имеет прямого отно- шения к нашему обсуждению структур файлов. Иллюстрацией взаимодействия данной процедуры с пользова- телем может служить следующий сеанс * *: # ENTER TRANSACTION W7812B 2150 * INVALID ACCOUNT NUMBER — REENTER TRANSACTION W78127 21.500 * INVALID AMOUNT — REENTER TRANSACTION V78127 21.50 i * ВВЕДИТЕ ТРАНЗАКЦИЮ W7812B 2150 * НЕВЕРНО ЗАДАН НОМЕР СЧЕТА — ПОВТОРИТЕ ТРАНЗАКЦИЮ W78127 21.500 #НЕПРАВИЛЬНАЯ СУММА — ПОВТОРИТЕ ТРАНЗАКЦИЮ ¥78127 21.50 714
ft EXEC PLILFCGf //RLlUSYStN DD * /♦ ON-LINE BANKING SYSTEM BANKS: PROCEDURE OPTIONS (MAIN)? #/ /♦ THIS PROGRAM IS DESIGNED TC SIMULATE AN ON-LINE BANKING SYSTEM. ♦/ /* */ /* THIS IS A RATHER SIMPLIFIED SYSTEM AS ONLY SAVINGS ACCOUNTS ARE */ /* DEALT WITH. THE FOLLOWING TRANSACTIONS MAY BE PERFORMED ON THESE */ /* ACCOUNTS: MONEY MAY BE WITHDRAWN OR DEPOSITED, NEW ACCOUNTS MAY */ /* BE OPENED AND OLD ONES MAY BE CLOSED. ALSO, A SIMPLE PASSBOOK-UP-*/ /* DATE FUNCTION MAY BE PERFORMED. INTEREST IS CALCULATED AT THE */ /* GENEROUS RATE OF LZ PER MONTH, AND IS AUTOMATICALLY GIVEN TO */ /* THE CUSTOMER WHEN THE PASSBOOK IS PRESENTED. */ /* */ /• SCME OF THE KEY VARIABLES USED IN THIS PROGRAM ARE: »/ /* INSTRUCTION: A CHARACTER VARIABLE USED TO HOLO THE INCOMING */ /* INSTRUCTION (OR PORTION THEREOF). */ /* PART: A CHARACTER VARIABLE USED TO HOLD THE PORTIONS OF THE */ /* INSTRUCTION AS THEY ARE RETURNED BY THE SCANNER. */ /* SCAN: A PROCEDURE USED TO SCAN THE INPUT INSTRUCTIONS AND */ /* SEPARATE ITS INDIVIDUAL COMPONENTS. */ /» SOURCE_KEY: A SOURCE KEV COMPOSED OF THE ACCOUNT NUMBER AND */ /* THE FIRST TWO DIGITS OF IT — IT IS USED TO /* REFERENCE THE RECORDS STORED ON THE ACCOUNT FILE*/ /* WHICH HAVE RECORDED KEYS. »/ /* jACCTJMO: A VARIABLE USED TO TEMPORARILY HOLD THE ACCOUNT */ /• NUMBER AS IT I S’PICKED OFF BY THE SCANNER. «7 /• T„T¥PE: A VARIABLE USED TO TEMPORARILY STORE THE TRANSACTION*/ /* TYPE AS IT IS PICKED OFF BY THE SCANNER. ♦/ /♦ SAVE: A VARIABLE USED TO HOLD THE AMOUNT OF THE TRANSACTION »/ /* AS PICKED OFF BY THE SCANNER. ♦/ /* PASSBK: A PRINT FILE USED TO PRINT OUT RECORDS OF TRANS- */ /* ACTIONS ONTO PASSBOOKS. •/ /* TRANS: A DISK FILE HAVING DIRECT ACCESS CAPABILITIES, USED */ TO HOLO RECORCS OF ALL TRANSACTIONS MADE WHEN NO PASS*/ BOOK WAS-PRESENTED. */ ACCT: A FILE HAVING DIRECT ACCESS CAPABILITIES WHICH HOLDS */ ALL THE CUSTOMER ACCOUNTS INCLUDING THE CURRENT */ BALANCE FOR EACH CUSTOMER’S ACCOUNT. */ ACCOUNT: A STRUCTURE USED TO INTERNALLY HOLD RECORDS FROM */ THE ACCT FILE. IT GIVES THE READER A LOOK AT THE */ INFORMATION STORED ON THAT FILE. */ TRANS„POINT: A FIELD OF ACCOUNT WHICH POINTS INTO THE TRANS */ FILE, TRANS_POINT POINTS TO A LIST OF ♦/ UNRECORDED TRANSACTIONS INVOLVING THAT ACCOUNT.*/ /* TRANSACTION: A STRUCTURE USED TO INTERNALLY HOLD RECORDS */ /* FROM THE TRANS FILE. IT ALSO IS A DESCRIPTION */ /* OF CONTENTS OF EACH RECORD ON THE FILE */ /* LINK: A FIELD OF TRANSACTION. LINK IS USED TO FORM A «7 /* SINGLY-LINKED LIST OF THE UNRECORDED TRANSACTIONS FOR */ /♦ EACH CUSTOMER. THE FIRST RECORD OF THIS FILE CONTAINS A /* POINTER TO THE NEXT AVAILABLE NODE OF THIS LIST (THAT »/ /* IS, THE NEXT FREE RECORD IN THE FILE) AND A POINTER TO*/ /* THE LAST NODE IN THE LIST. THESE POINTERS ARE UPDATED*/ /* WHEN RECORDS ARE ADDED TO THE FILE OR WHEN LISTS ARE */ /* DELETED FROM THE FILE. THE DELETED SUBLISTS ARE */ /• "TACKED" ON TO THE END OF THE LIST OF AVAILABLE NODES.*/ /« THUS, WE HAVE AN EXTERNAL LINEAR LINKED LIST. ♦/ Рис. 7-9.4. Главная программа и подпрограммы для онлайновой банковской системы .715
DECLARE INSTRUCTION CHARACTERI79) VARYING, PARI CHARACTER!10) VARYING, SCAN ENTRY RETURNS(CHARACTER!10) VARYING», !1,J,L) FIXED BINARY, SOURCE_KEV CHARACTER (13), ACCT_NO CHARACTERISE (FIRST,LAST,TEMP) FIXED 0EC(2,D), T_TYPE CHARI 1 ), SAVE FIXED DEC (9,2 ), PASS8K FILE STREAM PRINT, TRANS FILE RECORD KEYED ENVIRONMENT! F ( 20) REGIONAL I I)), ACCT FILE RECORD KEYED ENVIRONMENT(F(90) REGIONAL(2)), 1 ACCOUNT, 2 FILLER CHAR(1 ), 2 TYPE CHAR!1 ) , 2 NUMBER CHARI 5), 2 NAME CHARI 20 ), 2 ADDRESS!3) CHAR 115 I, 2 BALANCE FIXED DECIMALI9,2). 2 LAST_DATE CHAR(6), 2 INT_BAL FIXED 0ECIMAL(9,2), 2 TRANS-POINT FIXED DECIMAL!2,0), TRANSACTION, 2 FILLER CHARACTER II), 2 TYPE CHARACTER!1), 2 NUMBER FIXED DECIMAL <9,21, 2 DAY CHARACTER < 6), 2 AMOUNT FIXED DECINAL I 9,2)» 2 LINK FIXED DECIMAL (2,0), PASSBOOK BITdlT SCANS PROCEDURE RETURNS!CHARACTER(10) VARYING): /* THE SCAN PROCEDURE IS USED TO PICK OFF THE INDIVIDUAL COMPONENTS */ /* OF THE INPUT INSTRUCTION. NOTE THAT IT 1$ ASSUMED THAT THE ♦/ /* COMPONENTS ARE DELIMITED FROM ONE ANOTHER BY AT LEAST ONE BLANK. »/ DECLARE ELEMENT CHARACTER!10) VARYING? INSTRUCTION = SUBSTRIINSTRUCTION,VERIFY(INSTRUCTION,’ ’ )11 /* TRIMS LEADING BLANKS. * */ ELEMENT = IF SUBSTRIINSTRUCTION,!,1) = THEN RETURN!ELEMENT ); element = substriinstruction,!,iindex!Instruction,’ ’)-im /* ELEMENT IS ASSIGNED THE VALUE OF THE FIRST WORD OF */ /* INSTRUCTION. */ INSTRUCTION a SUBSTRIINSTRUCTION,INDEX!INSTRUCTION,’ ’ll? /* FIRST WORD IS DELETED FROM INSTRUCTION. */ RETURN!ELEMENT); END SCAN? •‘evaluate-- procedure; /* EVALUATE IS A PROCEDURE USED TO EVALUATE THE INPUT TRANSACTION. ♦/ /* THIS PROCEDURE CALLS ’SCAN’ TO ACCESS THE INDIVIDUAL PARTS OF THE */ /* INSTRUCTION AND THEN CHECKS THESE COMPONENTS FOR VALIDITY. IT WILL /* REQUEST CORRECT INFORMATION IF ANY OF THAT WHICH IS SUPPLIED IS */ /* FOUND TO BE INVALID. */ Рис. 7-9.4, Продолжение 716
END EVALUATE! PASSWRITEt PROCEDURE? /* THIS SHORT PROCEDURE IS USED TO OUTPUT THE LINES ONTO THE PASSBOOK*/ /* FILE* */ PUT FILE(PASSBK) EDIT ( SUBSTR!D0V»5,2 )»SUBSTR( 0AY,3, 2) , SUBSTR(DAY,1,2)) (COLUMN(1),3 (X(1),A(2))); IF TRANSACT ION.TYPE = ’W* /* WITHDRAWL */ THEN PUT FlLE(PASSBK) EDIТ!AMOUNT, ” ) (F(11,2),X{20 ),A); ELSE DO? PUT FILE(PASSBK) EDIT(AMOUNT) IX112),F(10,2))5 IF TRANSACTION.TYPE = ’I’ /♦ INTEREST ♦/ THEN PUT FILE(PASSBK) EDIT!’INTEREST’) (X(1),A); ELSE PUT FILE(PASSBK) EDIT!”) ( X ( 9) , A ( О I ) ; eno; PUT FILE(PASSBK) EDITfTRANSACTION.NUMBERI (F(11,21)5 RETURN; END PASSWRITE; ACTIVATE; PROCEDURE; /♦ THE ACTIVATE PROCEDURE PROCESSES THE /♦ THE PRINTING OF THE TRANSACTION ONTO /* THE TRANS FILE. INSTRUCTIONS AND ALSO DOES */ EITHER THE PASSBOOK FILE, OR */ END activate; /*•«**«**«»«*» MAINLINE *•♦*******♦♦*/ /♦ THE MAINLINE SECTION OF THE PROGRAM. •/ OPEN FILE(TRANS) DIRECT UPDATE, FILE(ACCT) DIRECT UPDATE, FILE(PASSBK) OUTPUT; /» THE FIRST RECORD OF THE TRANSACTION FILE IS READ IN TO DETERMINE THE /♦ AVAILABLE NODES OF THE TRANSACTION FILE. •/ READ FILE(TRANS) INTO!TRANSACTION) KEY(’O'); last=link; FIRST = TRANSACTION.NUMBER; ON ENDFILEfSYSIN) GO TO DONE; PUT FILE(PASSBK) EDITf'CATE’,’WITHDRAWAL’,’DEPOSIT»,’COMMENTS’, ’BALANCE") (COLUMN!1),X(3) , A ( 7),A(11),X(2)♦ A(9),A(9),X(3>»A(7)); DO WHILE(’l’B); PUT FILE(PASSBK) EDIT!’ ’) (COLUMN!I).A(0)): PASSBOOK » *1*8; CALL EVALUATE; CALL activate; eno; DONE: TRANSACT I ON.NUMBER = FIRST; LINK = LAST; WRITE FILE(TRANS) FROM(TP ANSACT ION) KEYFROM! ’0’), CLOSE FILE(TRANS), FILE(ACCT), FILE(PASSBK): END BANKS; //GO.TRANS OD UN IT=SYSOA,VOL=SERXUSER02,SPACE=(20, 100 I, // DS№TRNOATA,DISP=( OLD, KEEP) //GO.ACCT DD UNIT=SYSDA,VOL = SER=USER03,SPACE=(95,4 It), 11 DS№ACTDATA,DC8=(К EYLEN=5,LMTCT=10),ОIS₽=(OLD,KEEP) //GO.SYSIN DO-* (INCOMING TRANSACTIONS) /* Рис. 7-9.4. Продолжение
activate: procedure» DECLARE TRANSOIOS*» LABEL 5 у*************** OPEN ♦»**♦#♦#♦♦*•••*/ /* WHEN AN ACCOUNT IS TO BE OPENED, A CHECK IS MADE TO SEE IF AN */ /* ACCOUNT BY THAT NUMBER EXISTS. IF SO, THE OPEN FUNCTION IS NOT */ /* PERFORMED. */ IF T_TYPE = ’O’ THEN DO; ON KEY(ACCT) GO TO INITIALIZE; READ FILE!ACCT) INTO (ACCOUNT) KEY!SOURCE-KEY)J PUT EDIT!’THE ACCOUNT NUMBERED »,ACCT_NO,’ HAS ALREADY *, ’BEEN OPENED.») (COLUMN(l),* A); RETURN; INITIALIZES ACCOUNT.FILLER = LOW(l); ACCOUNT.TYPE = 'S’; ACCOUNT.NUMBER = ACCT_NO; BALANCE = AMOUNT; LAST_DATE = DATE; IF SUBSTRfDATE,5,2) • ’01* THEN INT„BAL = AMOUNT; ELSE INT_BAL -= 0; TRANS_POINT = -1; /♦ WHEN OPENING AN ACCOUNT ADDITIONAL INFORMATION ABOUT THE CUSTOMER ♦/ /•IS NEEDED. THE PROGRAM RECUESTS THIS INFORMATION ONE LINE AT A •/ /* TIME. */ P2 S PUT EOITl’ENTER THE CUSTOMER”S NAME’,”) (COLUMN!1),A); GET EDIT(NAME) (COLUMN!1),A(20)); IF NAME = ’’ THEN GO TO P2; P3: PUT EDIT!’ENTER THE FIRST LINE OF THE ADDRESS’,»’) (COLUMN!1),A); GET EDITIAOORESS!1)) I COLUMN!!),A!15))J IF ADORESS(l) = ” THEN GO TO P3; P*J PUT EDITf’ENTER THE SECOND LINE OF THE ADDRESS’,’*) (COLUMN!1),A); GET EDIT(ADDRESS!2)) (COLUMN!1),A(15))i IF ADDRESS!2) « *’ THEN GO TO P4; PUT EDIT!’ENTER THE THIRD LINE OF THE ADDRESS (IF THERE ’ II ’IS ONE), OR A NULL LINE’,”) (COLUMN!1),A)? GET ED IT(ADDRESSO) ) (COLUMN!1),A(15 ))i WRITE FILE!ACCT) FROM!ACCOUNT) KEYFROM!SOURCE-KEY); PUT FILE(PASSBK) EDIT(SUBSTR(DATE,5,2),SUBSTRfDATE,3,2), SUBSTR(0AT E,1,2 bAMOUNT,’NEW’, AMOUNT) (C0LUMN(D,3 (X(1),A(2I), X(12),F(10,2),X(4),A(6), F(10,2))J RETURN; ENO; SAVE = AMOUNT? /• FOR ALL OTHER CASES A CHECK IS MADE ON THE VALIDITY OF THE ACCOUNT*/ /♦ NUMBER BEING PROCESSED. THAT IS, A CHECK IS MADE T0 SEE WHETHER .♦/ /* OR NOT AN ACCOUNT BY THAT NUMBER EXISTS. »/ ON KEY(ACCT) begin; PUT EDIT!’THE FOLLOWING ACCOUNT NUMBER IS INVALID:’, SUBSTR(SOURCE^KEY,1,5),*’) (COLUMN!1),A)j GO TO RET1; Рис. 7-9.5. Процедура ACTIVATE для обработки транзакций покупателя 718
END; READ FIlE(ACCT) INTCtACCOUNT I KEY!SOURCE_KEY); /♦ THIS SECTION UPDATES THE PASSBOOK IF THERE ARE TRANSACTIONS FOR */ /♦ WHICH THE CUSTOMER DID NOT HAVE HIS PASSBOOK PRESENT. ♦/ IF PASSBOOK E TP.ANS_POINT is -1 THEN DO; IF FIRST = -1 THEN FIRST = TRANS-POINT; ELSE do; READ FILE(TRANS) INTO(TRANSACTION) KEY(LAST); LINK = TRANS_POINT; WRITE FILE(TRANS) FROM(TRANSACT ION) KEYFROMtLAST); END; LAST = TRANS_POINT; READ FILE(TRANS) INTO!TRANSACTION) KEY(TRANS-POINT); CALL PASSWRITE; DO WHILE{LI NK-.= -l); LAST = LINK: READ FILE(TRANS) INTOITRANSACTION) KEY(ltNK): CALL PASSWRITE; END; TRANS_POINT = -I» END; /* THIS SEGMENT CALCULATES THE INTEREST OWING THE CUSTOMER, IF THE */ /♦ DATE OF HIS LAST VISIT TO THE BANK WAS NOT IN THE SANE MONTH AS */ /* THE CURRENT VISIT. INTEREST ENTRIES ARE CREATED FOR EACH MONTH */ LAST_OATE = DATE! GO TO TRANS#!INDEX!’PWOC‘,T_TYPE)); /♦**********«,# PASSBOOK *****♦**♦»♦**/ /* IF THE ONLY PURPOSE OF THIS VI'SIT TO THE BANK WAS TO UPDATE A PASS*/ 7* BOOK, THEN THE PROCESSING IS HALTED. */ TRANS#! 1) : REWRITE FILE(ACCT) FROM!ACCOUNT) KEYJSOURCE KEY); RETURN; /♦*♦*****,,**« WITHDRAWLS ************/ /♦ THIS SEGMENT HANDLES ’WITHORAWL* TYPE TRANSACTIONS. */ TRANS*!2): BALANCE = BALANCE - SAVE; IF INT_8AL > BALANCE THEN INT—BAL = BALANCE; IF PASSBOOK THEN PUT FILEIPASSBK) EDIT((SUBSTR(LAST—DATE,J,2) DO J = 5 BY -2 TO 1 ),SAVE,BALANCE) (COLUMN(li,3 (X(1),A!2)),F!11,2), X(2O),F|11,2) ); ELSE DO; IF FIRST = -L THEN DO; PUT EDU!’*** WARNING *“ THE TRANSACTION FILE IS FULL’, ’THE FOLLOWING TRANSACTION CANNOT BE STORED:’) (COLUMN!1),A,COLUMN!1),X!17),A) Рис. 7-9,5. Продолжение 719
(«WITHDRAWS *,SAVE,’; NEW BALANCE: BALANCE) (COLUMN(1),A-P110»21,b,F{10,21*; RETURN; END! IF TRANS-POINT -,= -1 THEN DOs TEMP = TRANS_POINT; REAP FJLEITRANS) INTO(TRANSACTION) KEY(TEMP); 00 WHllE(LINK -.= -1) ; TEMP = LINK; READ FILEITRANS) INTO(TRANSACT ION) KEY(LINK); END ; LINK - first; WRITE FILEITRANS) FROMCTRANSACTIONl KEYFROMI TEMP) ; END; ELSE TRANS_POINT - FIRST; READ FILE(TRANS) INTO(TRANSACTION) KEY(FIRST); TRANSACTION.FILLER = LCW(l); TRANSACTION.TYPE = *W*S TRANSACTION .NUMBER ж BALANCE; AMOUNT = SAVE’, DAY = DATE: TEMP = FIRST: FIRST = LINK: LINK = -1; WRITE FILE(TRANS) FROM(TRANSACT ION) KEYFROM(TEfiP) ; End; REWRITE FILE(ACCT) FROM(ACCOUNT) KEY{SOURCE„KEY); return; /Ф »»»•»*»»»«»» DEPOSITS '/* THIS SEGMENT HANDLES •DEPOSIT' TYPE TRANSACTIONS. * / TRANS#!3 I: BALANCE = BALANCE * SAVE; IF INT_BAL> BALANCE THEN INT_BAL = BALANCE; IF PASSBOOK THEN PUT FILE(PASSBK) EDIT<(SUBSTRCLAST_DATE, J , 2) DO J = 5 BY -2 TO I),SAVE ,BALANCE) (COLUMN!1),3 <X(1),A(2)),X<11),F(11«2), XISl -.FILL,2) I ; ELSE do; IF FIRST = -I • THEN DO ; PUT EDIT!•*»« WARNING “»* THE TRANSACTION FILE IS FULL •THE FOLLOWING TRANSACTION CANNOT BE STORED:* CCOLUMNdj ,A,COLUMN(1) ,X{1T),A> (’DEPOSIT: ’«SAVE,1; NEW BALANCE: BALANCE! (COLUMN(1J,A,F(10,2),A»F(10,2)); RETURN; end: if tpans_point = -i THEN TRANS_POINT = FIRST: ELSE oo; TEMP - TRANS_POINT; READ FILEITRANS) INTOtTRANSACTION) KE*iT£MP); DO WHILEtLINK -•= -1); TEMP = LINK; READ FlLE(tRANS) INTO (TRANSACTION! KE.Yd.INK5*, end; LINK = FIRST; WRITE FJLE(TRANS) FROM(TRANSACTION) KEYFROM(TENP); END; READ FILEITRANS) INTOtTRANSACTION) KEYIFIRST); TRANSACTION.FILLER * LOWtl); TRANSACTION .TYPE - *0'; TRANSACTION.NUMBER BALANCE; AMOUNT = SAVE; DAY = DATE; TEMP - FIRST; FIRST = LIN|<« LINK = -15 ’ WRITE FILEITRANS» FROM TRANSACTION) KEYFROH(TEMP)J ENO; REWRITE FILE(ACCT) FRDtll ACCOUNT I KEY<SOURCE_KEY); RETURN; /♦******•*♦* ♦ «♦•CLOSE***************/ /* THIS SEGMENT IS USED TO CLOSE OFF ANY FILES WHEN SUCH AN ACTION «/ /* HAS BEEN REQUESTED BY THE TELLER. ♦/ TRANS#14)S IF -PASSBOOK THEN DO; PUT EDIT('PASSBOOK MUST BE PRESENTED WHEN CLOSING AN* It ’ ACCOUNT-,«•1 (COLUMN!1)3A): RETURN; END; AMOUNT - BALANCE; BALANCE - 0; IF AMOUNT > 0 THEN PUT EDIT('THE FOLLOWING AMOUNT IS TO BE RETURNED TO1 If ‘ THE CUSTOMER: °) (COLUMN!!),A); ELSE IF AMOUNT < 0 THEN PUT EOTd’THE CUSTOMER OWES THE BANK: •) 4 COLUMN(li ,A); ELSE PUT EDIT(’THE CUST0NER"S ACCOUNT IS' II • "EMPTY".',") ( COLUMN! 1), A); IF AMOUNT -= 0 THEN PUT ED IT( AMOUNT, •• ) (F( ll,2bCOLUNN< 1) ,A(0) ) : PUT FILEIPASSBK) EDI T t t SUBST Rt DAT E , J ,2 ) DO J = 5 BY -2 ГО I),AMOUNT,'CLOSED',BALANCE) < COLUMN ( 1) ,3 a<li,M2H,f<ll,2h X(13),A(B),F(10,2)); DELETE FILE(ACCT) KEY!SOURCE_KEY); RET1: RETURN: ENO ACTIVATE? Рис. 7-9.5. Продолжение Рис. 7-9.5. Продолжение 720 721
* INVALID TRANSACTION TYPE — REENTER TRANSACTION W78127 21.50» Этот сеанс иллюстрирует и тип синтаксических ошибок, кото- рые должны выявляться функцией EVALUATE. Перед каждым системным сообщением помещается для отделения его от вводимой пользователем информации. Обработка транзакций клиента осуществляется процедурой ACTIVATE, приведенной на рнс. 7-9.5. Обработка транзакций пяти типов обеспечивается пятью различными сегментами внутри этой процедуры. Первый сегмент обрабатывает транзакции откры- тия счетов, отличающиеся от других тем, что для них нет контроля действительного существования номера счета, не требуется запись в банковскую книжку клиента, а также необходимо значительное количество информации о клиенте. Для каждой транзакции открытия счета проводится контроль на уникальность назначен- ного номера. Приведем теперь пример сеанса открытия счета 2: CENTER TRANSACTION 071444.00 * ENTER CUSTOMER 'S NAME GAIL W. WALKER -. * ENTER THE FIRST LINE OF THE ADDRESS 1002 LANDSDOWNE CRESCENT * ENTER THE SECOND LINE OF THE ADDRESS WINNIPEG, MANITOBA * ENTER THE THIRD LINE OF THEADDRESS (IF THERE IS ONE), OR A NULL LINE S7K3J5 CANADA Транзакции остальных типов обслуживаются после выполне- ния записей в банковской книжке н вычисления процентов за пре- дыдущие месяцы. Вычисление платежей по процентам не рассма- тривается, опять-таки для краткости. Четыре модуля, соответ- ствующих транзакциям PASSBOOK, WITHDRAWAL, DEPOSIT и CLOSE, иллюстрируют использование каждой из команд ввода- вывода в ПЛ/1 для файлов прямого доступа. При обработке транз- акций может возникать ряд семантических ошибок и особых со- стояний, таких как попытка взять со счета сумму, большую, чем »* * НЕПРАВИЛЬНЫЙ ТИП ТРАНЗАКЦИИ — ПОВТОРИТЕ ТРАНЗАКЦИЮ W78127 21.50 2 * ВВЕДИТЕ ТРАНЗАКЦИЮ 0 71444. 00 * ВВЕДИТЕ ИМЯ КЛИЕНТА CAIL W. WALKER * ВВЕДИТЕ ПЕРВУЮ СТРОКУ АДРЕСА 1002 LANDSDOWNE CRESCENT * ВВЕДИТЕ ВТОРУЮ СТРОКУ АДРЕСА WINNIPEG, MANITOBA * ВВЕДИТЕ ТРЕТЬЮ СТРОКУ АДРЕСА (ЕСЛИ ОНА ЕСТЬ) ИЛИ ПУСТУЮ СТРОКУ S7K3J5 CANADA 722
имеется на текущем счету, заполнение файла транзакций или закрытие счета без банковской книжки. Этн состояния детекти- руются в процедуре ACTIVATE. Процедура PASSWRITE, вызы- ваемая из ACTIVATE, ответственна за печать результата об- работки транзакций клиента в банковской книжке. Этот параграф завершает наше обсуждение файлов прямого доступа. Банковская система помогает отчетливо продемонстри- ровать наиболее важное свойство файлов прямого доступа — способность быстрого доступа к записи без необходимости после- довательного поиска в файле. В следующем параграфе мы взглянем на альтернативные фай- ловые организации для трех описанных нами и наиболее часто применяемых структур файлов. 7-10. ДРУГИЕ Л1ЕТОДЫ ОРГАНИЗАЦИИ ФАЙЛОВ Важной концепцией, определяющей организацию дан- ных (и программ) во многих вычислительных системах, является виртуальная память. Мы начнем этот параграф с изучения схем, реализующих виртуальную память, и обсуждения воздействия принципов виртуальной организации на сегодняшние и будущие файловые системы. Наше обсуждение виртуальных систем никоим образом не претендует на полноту, и интересующийся читатель, знакомый с операционными системами, может обратиться к лите- ратуре, на которую мы будем ссылаться. Вторая часть параграфа вводит тип файловой организации VSAM, предназначенный для систем виртуальной памяти. Этот тип организации файлов имеет возможности, которые охватывают возможности всех трех файловых организаций, описанных в пре- дыдущих параграфах. 7-10.1. Виртуальная память Во введении к этой главе были приведены две при- чины, обусловливающие хранение информации в файлах на уст- ройствах внешней памяти. Первой причиной было желание иметь недорогую память для хранения больших объемов данных, на- столько больших, что для их одновременного размещения не- хватает основной памяти. Второй причиной был ^архивный харак- тер некоторых данных. В ряде приложений желательно илн даже необходимо сохранять информацию от одного выполнения про- граммы до другого (например, в системах оплаты счетов). В та- ких случаях информация должна размещаться в легко доступной среде (например, магнитные диски или ленты), а не в основной памяти, являющейся критическим системным ресурсом, разделя- емым между многими программами. Если основная память станет очень дешевой, скажем 0,00001 цента за байт, и если миллиарды и даже триллионы байтов будут 723
прямо адресуемы, то файлы в том виде, как они описывались до снх пор, не потребуются. Структуры данных, представленные в гл. 1-5, будут вполне достаточны для решения большинства проблем в программировании. Хотя до сих пор технология основной памяти не достигла только что очерченного уровня, тем не менее были предприняты усилия для расширения основной памяти в логическом смысле. Один из типов систем, обеспечивающих подобное логическое рас- ширение, получил название системы виртуальной памяти. Си- стема виртуальной памяти выполняет динамическое отображение из пространства виртуальных адресов (адресное пространство, во много раз превосходящее основное адресное пространство) в основное адресное пространство. В системах виртуальной памяти все активные в текущий момент программы и данные занимают пространство (т. е. назначенные виртуальные адреса) в виртуаль- ной памяти. Программы и данные не могут разместиться (факти- чески обычно и не помещаются) в основной памяти и вместо этого располагаются на быстрых устройствах вторичной памяти типа барабанов или дисков, с фиксированными головками. При вы- полнении программы и обращении к данным все виртуальные адреса автоматически преобразуются операционной системой в ре- альные адреса основной памяти. Если программа вызывается опять для выполнения и в текущий момент не находится в основной памяти, то этот факт будет зафиксирован во время преобразования виртуальных адресов в адреса основной памяти. Автоматически (т. е. без участия и даже знания пользователя) программа или ее сегмент, запланированный для выполнения, переносится в основ- ную память и выполнение программы может продолжаться. При такой форме автоматического преобразования адресов мы полу- чаем возможность расширить нашу основную память до эффек- тивного размера, эквивалентного виртуальному адресному про- странству, состоящему из-многих миллионов байтов. Прежде чем обсуждать вопрос, как системы виртуальной па- мяти помогают решить некоторые проблемы, связанные с обработ- кой файлов, кратко проанализируем три типа систем виртуальной памяти: системы со страничной памятью, системы с сегментацией и сегментно-страничные системы. Страничные системы. Страничная организация памяти яв- ляется методом управления памятью, в котором виртуальное адресное пространство разбито на блоки фиксированной длины, называемые страницами. Пространство основной памяти раз- делено на физические секции равного размера, названные стра- ничными кадрами. Страничный кадр и страница имеют один и тот же размер. Преобразование виртуального адреса в основной (или реальный) адрес включает отображение страницы в страничный кадр. Виртуальный адрес в страничной системе состоит из двух компонентов р и d, где р обозначает страницу, a d — смещение 724
Название битов защиты Е-выполнять R - читать , W-писать J mnujj Рис. 7-10.1. Пример, иллюстрирующий преобразование адресов виртуальной памяти в страничной системе внутри страницы р. Преобразование этого двухкомпонентного адреса в адрес основной памяти, как правило, требует наличия таблицы страниц и алгоритма замены страниц. Таблица страниц связана с заданием каждого пользователя (т. е. множеством программ и данных пользователя). Каждая запись в таблице страниц содержит; 1. Бит присутствия (флажок, сигнализирующий о нахождении или отсутствии страницы в основной памяти). 2. Адрес страницы (в основной или во вторичной памяти). 3. Биты защиты, необходимые для контроля типа доступа, разрешенного для страницы. Для рассмотрения примера, иллюстрирующего страничную память, обратимся к рис. 7-10.1. В этом примере мы допустим, что размер страницы равен 1000 байтам (в большинстве машин со страничной организацией размер страниц колеблется от 1000 до 4000 байтов), виртуальное адресное пространство имеет размер 2 000 000 байтов, а основная память — 50 000 байтов. Адрес, например 0630, может быть представлен двухуровневым адресом 0,630, где первое число есть номер страницы, а второе число — смещение от начала страницы. Для удобства мы поместили про- граммы и данные для иллюстративного задания в первых шести страницах виртуальной памяти. Когда в процессе выполнения программы встречается некоторый адрес, скажем 4444 (или 4,444), то происходит обращение к та- блице страниц данного задания. Запись в этой таблице, соответ- ствующая номеру страницы р, содержит указатель на желаемую страницу, которая размещается либо в основной памяти, либо 725
во внешней. В нашем примере страница 4 располагается в основной памяти и может быть найдена в страничном кадре 3. Смещение на d позиций в странице даст желаемое место в памяти. Следова- тельно, инструкция LOAD 2110 находится в позиции 444 от начала страницы 4 (т. е. страничного кадра 3). Выполнение машинной инструкции «LOAD 2110» требует, чтобы данные, находящиеся по виртуальному адресу 2110, были загружены в регистр центрального процессора. При инициализа- ции преобразования виртуального адреса в реальный адрес па- мяти в таблице страниц будет обнаружено, что страница 2 в теку- щий момент в основной памяти отсутствует. Для продолжения выполнения задания необходимо, чтобы страница 2 была считана из внешней памяти и помещена в некоторый страничный кадр. Для определения того, какая из страниц, находящихся в теку- щий момент времени в основной памяти, должна быть заменена на страницу 2, необходим алгоритм страничного обмена (или стра- тегия замены страниц). Для систем со страничной организацией памяти было проанализировано и использовано множество стра- тегий замены страниц, описанных в ряде книг, посвященных новейшим операциоиным^системам [25, 34, 36]. Более детальное обсуждение этой темы выходит за пределы нашей книги. В течение интервала времени, пока выбранная для удаления страница перемещается во вторичную память, а затем требуемая страница вызывается из вторичной памяти на место удаленной, может начаться выполнение задания другого пользователя. В ка- кой-то более поздний момент времени, после того, как требуемая страница была перенесена в основную память, задание продолжит выполнение с инструкции «LOAD 2110». С точки зрения управления данными применение страничных систем дает некоторые важные преимущества. Во-первых, данные переносятся в основную память без какой бы то ни было явной спецификации типа инструкции чтения внутри программы поль- зователя. Понятие файла и системного справочника, содержащего имена файлов, может быть отброшено. Во-вторых, поскольку целая страница информации переносится в память в один момент вре- мени, последующий доступ к данным на одной странице может вы- полняться без ввода или вывода отдельных записей. Это возможно, конечно, только в том случае, если страница не выталкивается из памяти прежде, чем такой доступ осуществится. Такое свойство особенно выгодно, когда обрабатываемые данные в высокой сте- пени локализованы, что имеет место в случае последовательной обработки. Перечисленными выше достоинствами обладают все виртуальные системы памяти, обсуждаемые в данном параграфе. В страничной системе все страницы имеют одинаковый размер, и, следовательно, проблемы управления памятью не существует. Любая страница может быть заменена любой другой страницей. Следует также заметить, что в большинстве страничных систем преобразование виртуального адреса в реальный выполняется 726
специальным аппаратным блоком. Поэтому доступ к элементам данных, находящимся на странице, отсутствующей в основной памяти, все еще может быть достаточно эффективным. Основные недостатки страничной организации обусловли- ваются внутренней фрагментацией. Понятие фрагментации уже обсуждалось в п. 5-6. Внутренняя фрагментация возникает вслед- ствие размещения программ и данных в блоках фиксированной длины (т. е. страницах), некоторые из которых не заполняются целиком в связи с тем, что программам и данным двух различных заданий нельзя размещаться на одной и той же странице. Изучим теперь альтернативную схему управления памятью, которая устраняет проблему внутренней фрагментации. Системы с сегментной организацией. Программы и связанные с ними данные, как правило, состоят из ряда логических единиц, таких как процедуры, программные блоки и области данных. В системах с сегментной организацией адресная структура про- граммы базируется на логическом делении программы. Виртуаль- ное адресное пространство каждой программы в системах с сег- ментацией делится на блоки переменной длины, называемые сегментами, каждый из которых содержит одну из логических областей программы. Для доступа к слову внутри адресного про- странства пользователя мы опять, как и при страничной органи- зации, используем адрес, состоящий из двух частей. В адресе s, d размещение сегмента задается s, а смещение внутри сегмен- та — d. Преобразование адреса виртуальной памяти в реальный яв- ляется логически идентичным процессом для систем со страничной и с сегментной организациями. В противоположность таблице страниц, таблица сегментов содержит фактический адрес (т. е. адрес основной памяти или адрес вторичной памяти) начала от- дельного сегмента пользователя. Задание каждого пользователя имеет свою собственную таблицу сегментов, подобную той, которая представлена на рис. 7-10.2. Для доступа к отдельному элементу данных, такому как "<DATA1>, .52", указанному в инструкции "LOAD (DATA1/52", необходимо, во-первых, найти элемент та- блицы сегментов, связанный с сегментом "<DATA1'>". ‘(Описание того, как это выполняется автоматически, выходит за рамки нашей книги). Если сегмент "(DATA1)" находится в основной памяти, что определяется по значению бита присутствия, то доступ может выполняться непосредственно. Однако при отсутствии сегмента в основной памяти, для его выборки используется адрес внешней памяти. Размещение сегмента в основной памяти вызывает про- блемы, очень похожие на те, что возникают при размещении страниц в системах со страничной организацией. Если в основной памяти отсутствует непрерывный неиспользуемый участок памяти, Достаточный для размещения сегмента, то какой-то из сегментов в основной памяти должен быть удален, а освобожденное место должен занять требуемый сегмент. Выбор сегмента, подлежащего 727
Рис. 7-10.2. Преобразование адреса в системе с сегментной организацией виртуальной памяти удалению, осуществляется в соответствии с принятой в системе стратегией замены сегментов. Наряду с множеством схем замены страниц имеется и целый ряд возможных схем замены сегментов. Однако необходимо кон- статировать, что стратегия замены сегментов сталкивается с про- блемой управления памятью, отсутствующей в страничных си- стемах. Как следствие переменной длины сегментов, возникает внешняя фрагментация: сегмент А может заменять другой сегмент, скажем В, занимающий несколько большее пространство, чем пространство, необходимое для размещения А, и в результате создается небольшой неиспользуемый фрагмент основной памяти. После нескольких тысяч замен подобного типа память оказывается настолько раздробленной на маленькие фрагменты, что с ней ста- новится невозможно работать. Поэтому стратегия замены сег- ментов должна быть ориентирована на уменьшение внешней фрагментации. В качестве стратегии замены сегментов были опро- бованы некоторые методы управления памятью, в частности ч методы первого подходящего, наилучшего подходящего и метод $ «близнецов» (см. п. 5-6). Безотносительно к тому, какое правило замены применяется, внешняя фрагментация растет с ростом‘числа выполняемых’'про- грамм. Появляющиеся фрагменты свободной памяти при первой .. возможности должны быть собраны и сгруппированы в одну ие- < 728
прерывную область. Для процедуры сжатия могут использоваться методы сборки мусора, обсуждавшиеся в п. 5-6. Многие из достоинств сегментной организации присущи любым системам виртуальной памяти. В частности, те два достоинства, которые были нами ранее отмечены для страничных систем, можно с тем же основанием отнести и к преимуществам систем с сегмент- ной организацией. Так как при использовании сегментации вир- туальная память делится на логические элементы, то понятие файла все еще сохраняет некоторый смысл. С каждой отдельной областью данных можно связать имя, точно так же как в некоторых системах можно связать имя с файлом. Программист может найти этот аспект особенно привлекательным. Вдобавок, сегментная организация имеет и другие достоинства (например, легкость связывания процедур, легкость совместного использования и за- щиты программ и данных), ио мы не будем обсуждать эти аспекты. Хотя с помощью сегментации проблема внутренней фрагмента- ции успешно преодолевается, возникает новая и потенциально более опасная проблема внешней фрагментации. В попытках сгладить эффекты внешней фрагментации и в то же время сохра- нить достоинства сегментной организации было разработано много систем виртуальной памяти, являющихся "гибридами" рассмотрен- ных подходов. Сегментно-страничные системы. В сегментно-страничных си- стемах виртуальный адрес разбивается на три компонента — s, р и d, где s — имя сегмента (или адрес в таблице сегментов), р — индекс в таблице страниц сегмента s и d — смещение внутри стра- ницы, специфицированной индексом р. Пространство виртуальной памяти теперь делится на ряд сегментов переменной длины, со- стоящих из небольших страниц фиксированной длины. На рис. 7-10.3 изображены примеры таблиц, необходимых для преобразования виртуального адреса в реальный адрес в сег- ментно-страничной системе. Рассмотрим некоторый адрес; пусть это будет '\DATA1,', 3,98". преобразования его в реальный адрес сначала находим элемент в таблице сегментов, соответ- ствующий сегменту (DATA1). Затем для этого сегмента отыски- ваем таблицу страниц. Элемент в таблице страниц определяет страничный кадр, если страница находится в основной памяти. И, наконец, в нашем примере смещение, равное 98, используется для локализации требуемого слова. Если в процессе преобразования сегмент (DATA1) не помечен как присутствующий в основной памяти, то организуется выборка всех страниц этого сегмента в основную память. В случае необ- ходимости после выборки сегмента его страницы могут быть замещены по запросам. Это означает, что если к отдельным стра- ницам сегмента ссылаются редко или вовсе не ссылаются, то они могут быть вытолкнуты из основной памяти и заменены более активными страницами другого сегмента. Как следствие, в про- цессе преобразования адресов часто возникает необходимость 729
Рис. 7-10.3. Преобразование адреса в системе с сегментно-страничной организацией виртуальной памяти вызова в основную память страниц текущего активного элемента, так как они были вытолкнуты в предыдущие моменты времени из-за их неактивности. Сегментно-страничные системы возникли в результате попытки сохранить большинство преимуществ как страничных, так и сег- ментных систем. Внешняя фрагментация в них отсутствует, по- скольку основной единицей информации, поддерживаемой систе- мой, является страница. Обеспечивается разделение данных, связывание и защита программ вследствие реализации концепций сегментной организации. Внутренняя фрагментация, однако, при- сутствует, и она может быть снижена только уменьшением размера страницы. Двумя основными недостатками этой системы являются повы- шенная стоимость аппаратуры и дополнительные затраты времени процессора из-за необходимости преобразования трехуровневого адреса. Кроме того, требуется~больше основной памяти для хра- нения дополнительных таблиц преобразования адресов (т. е. та- блиц сегментов и страниц). Прежде чем описывать воздействие виртуальной памяти на структуры файлов и системы файлов, стоит отметить, что средства виртуальной адресации обеспечиваются в ряде имеющихся на рынке систем. Страничная память была впервые использована на вычислительной машине Атлас [10 ], а затем в системе XDS940. Сегментация применена в семействе вычислительных машин В5000 фирмы Burroughs [24]. Системы IBM 370 и Honeywell 6180 [2] используют странично-сегментную организацию виртуальной па- мяти. 730
Ясно, что системы виртуальной памяти стали уже реальностью и будут применяться в будущем. Их воздействие на управление данными нельзя игнорировать. В настоящее время имеется два научных подхода, развивающих связь между системами управле- ния файлами и системами виртуальной памяти. Один из них на- правлен на интеграцию файловых систем и средств, которыми обладают системы с сегментной и страничной организациями. Примером реализации этого подхода может служить опера- ционная система MULTICS [25]. Такая интеграция достигается сравнительно легко при признании следующих сходных свойств у файлов и сегментов. 1. Понятия файла и сегмента являются сходными в смысле логической организации информации. 2. Как файлы, так и сегменты требуют двумерной формы адре- сации: имени файла и адреса записи, имени сегмента и адреса слова соответственно. 3. Как файлы, так и сегменты могут иметь переменную длину — . они растут и сокращаются динамически. 4. Буфера в основной памяти функционально подобны стра^ ничным кадрам. Основным недостатком этого подхода является то, что запрос одной записи из сегмента данных (т. е. файла) приводит к выборке целого сегмента, что может вызвать чрезмерные накладные рас- ходы во время обработки всего нескольких записей из каждого файла при большом числе файлов. К тому же даже если выбран целый сегмент, то все равно нет гарантии, что позже отдельные страницы в сегменте не потребуется выбрать повторно из-за спе- цифического характера последовательности обращений к записи в конкретной задаче. Второй подход предполагает, что сходства между файлами и сегментами надо игнорировать и использовать для иих различ- ные механизмы реализации. Операционная система OS/VS осно- вана именно на этом. Анализ файлового механизма, обеспечива- емого данной системой, будет представлен в следующем пункте., Одним из преимуществ этого подхода является то, что файл ока- зывается объектом, отделенным от средств прямого управления памятью системы. Как следствие, файл может без труда копиро- ваться на легко переносимое устройство (т. е. на диск с перемеща- емыми головками или ленту) и использоваться в другой системе. Несомненным недостатком такого подхода является необходимость для пользователя знать специальный объект, содержащий данные, т. е. файл, к которому требуется ссылаться явным образом и обра- щение с которым отличается от обращения с другими объектами в программе, содержащими данные. Если файловая система объединена с системой виртуальной памяти, то исчезают обе причины для существования файла, которые были указаны в начале данного пункта. При разделении 731
этих двух структур файловая система становится необходимой только для целей архивного хранения и мобильности инфор- мации. 7-10.2. Файлы в методе доступа VSAM В 1972 г. фирма IBM объявила о создании нового метода доступа, названного VSAM (Virtual Storage Acces Method — метод доступа для виртуальной памяти), для вычислительных машин семейства IBM 370 с виртуальной организацией. Обсуждение файловой организации в VSAM представляется весьма важным, так как этот метод доступа обеспечивает большие возможности. Метод доступа VSAM был разработан с целью замены всех методов доступа (последовательного, индексно-последовательного и пря- мого), которые прежде поддерживались в Системах IBM. В ча- стности, мы вскоре увидим, что средства доступа по ключу в VSAM весьма существенно отличаются от стратегии, принятой в индекс- но-последовательном методе доступа ISAM Системы IBM, и имеют явное сходство со средствами индексно-последовательной органи- зации Системы SCOPE фирмы CDC (см. п. 7-6.1). Поэтому мы со- ветуем читателю еще раз внимательно проработать указанный пункт книги, описывающий индексно-последовательную органи- зацию в системе CDC, прежде чем продолжить ознакомление с дан- ным пунктом. Этот пункт мы начнем с описания возможных структур файлов в VSAM. Потом перейдем к связанным с методом доступа VSAM типам обработки. В конце мы кратко обсудим проблемы доступа для VSAM файлов в ПЛ/1. Для файлов в VSAM существует два типа файловых структур: структура с упорядочением по ключам и структура с упорядочением по входу. В файлах, упорядоченных по ключам, записи загру- жаются в упорядоченной последовательности, определяемой со- поставляющей последовательностью содержимого поля ключей в каждой записи. Прямой доступ к записям может осуществляться посредством уникального значения в поле ключа. В файлах с упо- рядочением по входу записи загружаются в соответствии с поряд- ком, в котором они поступают в файл. Позиция записи не зависит от ее содержимого. Прямой доступ к записи реализуется с помощью относительного адреса байта (RBA) от начала файла. Обсудим сначала концепции, относящиеся к файлам с упорядочением по ключам, и позже определим, какое они имеют отношение к файлам с упорядочением по входу. В VSAM файлы с упорядочением по ключам состоят из непре- рывных и имеющих фиксированную длину областей внешней па- мяти прямого доступа. Эти области называются контрольными интервалами и являются теми информационными единицами, которыми под управлением VSAM обмениваются между собой внешняя и виртуальная памяти. Размер контрольного интервала может меняться от одного файла к другому, однако для данного 732
Записи с Ионными. R2 |/?з|/?4| •' Свободное Управляющая пространство — информация Описатель контрольного интервала— Рис. 7-10.4. Относительное положение данных, свободного пространства и управляю- щей информации в контрольном интервале файла размер каждого интервала имеет фиксированное значение. Размер интервала определяется пользователем или VSAM, кото- рый пытается выбрать оптимальное значение для заданного типа устройств прямого доступа. Для записей в контрольном интервале допускается переменная или фиксированная длина, причем VSAM трактует их идентичным- образом. Управляющая информация, описывающая записи с дан- ными, помещается в конце контрольного интервала. Хранимая, запись является комбинацией данных записи и ее управляющей информации, даже если между ними отсутствует физическая смежность. Хранимые записи не могут пересекать границы кон- трольного интервала, и в момент определения файла необходимо установить размер контрольного интервала достаточным для раз- мещения наибольшей хранимой записи. Части контрольного интервала могут оставаться неиспользо- ванными, как показано на рис. 7-10.4. Позже в этом пункте мы опишем, как при добавлении или удалении записей область сво- бодного пространства поддерживается непрерывной. В общем слу- чае свободное пространство первоначально может распределяться по файлу с упорядочением по ключам двумя способами: резерви- рованием пространства на концах всех использованных контроль- ных интервалов и резервированием отдельных пустых контроль- ных интервалов. Запись с данными адресуется ее смещением (в байтах) от на- чала файла (т. е. относительным адресом записи). VSAM рас- сматривает контрольные интервалы как смежные и трактует файл, так же как при его размещении в виртуальной памяти, начиная с адреса 0. Множество контрольных интервалов может логически груп- пироваться, образуя контрольную область. Связь между ними показана на рис. 7-10.5. Соотнося рассматриваемые концепции с ме- тодом доступа ISAM, удобнее всего представлять себе контрольный интервал""как " логическую' дорожку? "а “контрольную область как Логический цйлиндр~Для_ каждой,_кодтрольной области соз- дается ~набор.индексов?ц_^ з_ат_ели относительных адресов .контрольных интерваловсоста- вляющих кон^олш!ую"'оола<^ обласпГ^формиррот^ ителя последовательности. 733
""Иекс/яс Г Kfzzjj тожество Определитель последова- тельности Записи Файла Контрольные интервалы последней контрольной. ' области I Контрольные интервалы Контрольные интервалы первой контрольной "------------' области второй контрольной области Рис. 7-10.5. Структура VSAM файла Набор индексов для вс5бх контрольных областей в файле назы- вается определителем последовательности. Индексы могут образовывать многоуровневую структуру. Каждый индекс размещается в некоторой записи, и множество всех таких записей с индексами называется индексным множеством. На самом высоком уровне индексов разрешается только один индекс. Элемент в записи индексного множества состоит из наи- большего значения ключа, содержащегося в индексной записи следующего более низкого уровня, и указателя на эту запись в индексе нижнего уровня. Каждый элемент в определителе после- довательности состоит из наибольшего ключа в контрольном ин- тервале и указателя на этот контрольный интервал. Для увеличения числа элементов в индексной записи VSAM использует технический прием, названный ключа, заклю- чающийся в отбрасывании тех символов в начале и конце ключа, без~ которых можно еще различать смежные ключи. Так как раз- мер ключей в элементах индекса уменьшается сжатием, то тем самым можно добиться либо меньшего индекса, либо индекса с большим числом элементов. Для файлов в VSAM с упорядочением по ключам допускается обработка трех типов: последовательная последовательная с про- пуском и прямая. При последовательной обработке файлов с упо- рядочением по ключам доступ к записям определяется последова- тельностью ключей в файле. Когда файл открывается для после- довательной обработки, программы метода доступа получают зна- чение RBA первой записи в файле, и последовательная обработка начинается с этого адреса. С другой стороны, для первого выпол- няемого предложения ввода-вывода можно указывать или спе- циальный или общий ключ. В таком случае последовательная 734
обработка может начаться с записи со специальным ключом или с первой из записей, имеющих указанный общий ключ. Последовательная обработка с пропуском обеспечивает доступ к упорядоченному подмножеству записей в файле. Пусть задан упорядоченный набор ключей, образующих подмножество ключей записей в файле. При обработке следующей записи на основе упо- рядоченного набора ключей горизонтальные связи в определителе последовательности используются для локализации соответству- ющего элемента в записях определителя последовательности. Адресная информация в этом элементе используется для выделе- ния контрольного интервала с требуемой записью данных, кон- трольный интервал переносится в виртуальную память, и запро- шенная запись обрабатывается. В качестве примера, иллюстрирующего последовательную об- работку с пропуском, обратимся к файлу, изображенному на рис. 7-10.5. Предположим, что записи, доступом к которым мы интересуемся, идентифицируются упорядоченным множеством ключей {АЗ, Е7, N6, Q3, Y5}. Обработка начинается с прямого доступа к записи, идентифицируемой ключом АЗ (например, по- иском в индексном множестве и определителе последовательности для выделения второго контрольного интервала в первой кон- трольной области). Следующей необходимо получить запись с клю- чом Е7. Вместо прямого доступа к записи с помощью прослежива- ния вертикальных указателей из наивысшего уровня индекса вниз к определителю последовательности, мы можем переместиться ко второму элементу определителя последовательности и, следова- тельно, фактически найти запись с ключом Е7 без спуска через индексы нескольких уровней. Несомненно, требование упорядо- ченного набора ключей для поиска является совершенно необхо- димым для успеха этого метода доступа. Прямой доступ к записи в файле метода VSAM с упорядоче- нием по ключам выполняется спуском вниз по дереву индексных записей к определителю последовательности. Затем выбйраётся соответствующий контрольный интервал, п доступ к желаемой записи осуществляется в виртуальной памяти. VSAM обеспечивает очень общую и совершенную форму доступа к" записям. Можно использовать ключ для спецификации выборки: отдельной записи (в этом случае ключ является точным клю- чом); записи со следующим, ббльшим по величине, ключом (т. е. ключ является аппроксимирующим ключом); первой записи, соответствующей общему ключу (т. е. ключ является общим ключом). Наиболее интересные свойства файлов в VSAM связаны с упра- влением данными при выполнении операций удаления, добавления и обновления. Когда записи исключаются из файла, упорядочен- ного по ключам, участок пространства, занимаемый записью, освобождается и добавляется к части свободного пространства 735
контрольного интервала. Это освобождение свободного простран- ства выполняется перемещением записей с данными в контрольном интервале так, чтобы гарантировать непрерывность участка запи- сей с данными и участка свободного пространства. Рис. 7-10.6 иллюстрирует эффект удаления записи с ключом А8 из третьего контрольного интервала файла, изображенного на рис. 7-10.5. Заметим, что если из этого интервала исключается запись ВЗ, то третий элемент в первой записи определителя последователь- ности и первый элемент в записи индексного множества должны быть изменены. Это делается для указания того, что В1 теперь становится наибольшим индексом в этом конкретном контрольном интервале и контрольной области соответственно. Когда к файлу с упорядочением по ключам добавляется запись, VSAM может переместить некоторые из существующих записей внутри контрольного интервала для обеспечения физического упорядочения записей согласно последовательности ключей. На- пример, предположим, что запись с ключом А9 добавляется в тре- тий контрольный интервал на рис. 7-10.5. Результат такого вклю- чения показан на рис. 7-10.7. Записи В1 и ВЗ перемещены и за- нимают некоторую область свободного пространства. Возникает очевидный вопрос: как быть, если нет достаточного места в контрольном интервале для включения новой записи? VSAM решает эту проблему расщеплением контрольного интер- вала, что почти идентично расщеплению блока данных в индексно- последовательных файлах системы SCOPE. При расщеплении контрольного интервала хранимые в нем записи перемещаются в пустой контрольный интервал той же самой контрольной обла- сти, а затем новая запись включается в соответствии с ее местом в последовательности ключей. Конкретный способ расщепления интервала определяется типом выполняемой обработки. При последовательном включении новая запись, если это возможно, помещается в исходный контрольный интервал, а все последу- ющие записи — в новый контрольный интервал. Такое расщепле- ние контрольного интервала, показанного на рис. 7-10.7, при включении небольшой записи с ключом В2 иллюстрируется на рис. 7-10.8, а. Если же новая запись слишком велика для того, чтобы поместиться в исходном контрольном интервале, то она вместе со всеми оставшимися записями переносится в новый кон- трольный интервал. Рис. 7-10.8, б изображает такое включение большой записи с ключом В2. Когда VSAM обнаруживает, что должны быть включены последовательно две или больше записей, то применяется метод группового последовательного включения, Рис. 7-10.6. Третий контрольный ин- Рис. 7-10.7. Третий контрольный интер- тервал после исключения записи А8 вал после добавления записи А9 . 736
который с помощью буферизации позволяет уменьшить число опе- раций ввода-вывода, необходимых для включения записей. Если расщепление возникает при прямом включении, то перемещается примерно половина записей кон- трольного интервала. Отсутствие в контрольной об- ласти свободного контрольного интервала в момент выполнения операции расщепления приводит к расщеплению контрольной об- ласти. При такой операции новая контрольная область создается либо Руч i-’yrri-iiiii I G) Еопись I Запись \ЗаписьЛ'у/-'/,,Х\\\ I А8 | А9 Г б/ I I | л„г | б) Рис. 7-10.8. Расщепление контроль- ного интервала, ^вызванное вклю- чением: а— небольшой записи В2; б»—боль- шой записи В2 повторным использованием занятого прежде пространства, либо расширением файла, если первоначально отведенное файлу пространство уже заполнено. При этом приблизительно половина контрольных интервалов перемещается из заполненной контрольной области в равную по размеру новую контрольную область. Новая запись размещается в одной из контрольных областей согласно значению ее ключа. Расщепление контрольной области должно выполняться очень редко, так как оно приводит к большой и дорогостоящей реоргани- зации файла. Процесс обновления записи новой записью, отличающейся по длине, вызывает тот же самый тип реорганизации файла, что и при удалении или включении. Если новая запись короче старой записи, лишнее пространство возвращается в область свободного пространства, аналогично тому, как это делается при удалении записи. Если новая запись длиннее старой, требуемое добавочное пространство занимается точно таким же образом, как и в случае заимствования пространства при включении записи. Следова- тельно, расщепление контрольного интервала и контрольной обла- сти может происходить и при удлинении обновляемой записи. Теперь должно стать очевидным, что средства обработки файлов, необходимые для ведения VSAM файлов с упорядочением по клю- чам, должны быть чрезвычайно изощренными. Теперь изучим файлы с упорядочением по входу. Наиболее примечательная разница между файлами с упорядочением по клю- чам и с упорядочением по входу состоит в том, что для последнего типа файлов метод доступа не создает индекс. Логда запись до- бавляется последовательно в файл с упорядочением по входу, VSAM возвращает ее RBA (относительный адрес байта). Исполь- зуя эти RBA, можно создать свой собственный индекс или индекс- ное множество и обеспечить быстрый прямой доступ. Если мы хотим создать файл и обращаться к нему в режиме прямого доступа, то следует предварительно заполнить файл пустыми записями. Для размещения в файле записи с данными 24 Трамбле Ж-, Соренсон П. 737
необходимо преобразовать ключевой элемент данных записи в зна- чение RBA и выбрать запись по ее RBA. Если место для записи, найденное по RBA, содержит пустую запись, туда помещается но- вая запись. В случае, когда это место уже заполнено записью с данными, должна вызываться написанная самим пользователем процедура разрешения коллизий, определяющая альтернативный RBA записи. Из сказанного выше можно заключить, что для файлов с упо- рядочением по входу, VSAM предоставляет пользователю очень немного средств обработки данных. Развитие файловых структур для повышения уровня средств доступа к файлу при этом пол- ностью оставлено пользователю. В табл. 7-10.1 дана сводка важ-. нейших различий между файлами с упорядочением по ключам и с упорядочением по входу. Таблица 7-10.1 Сравнение файлов в VSAM с упорядочением по ключам и с упорядочением по входу Файлы с упорядочением по ключам Файлы с упорядочением по входу Записи упорядочены по ключу Доступ по ключу, возможен доступ также по RBA Значения RBA для записей могут изменяться при добавлении и удале- нии записей 'Можно относительно легко ввести запись и изменить ее длину путем перераспределения свободного про- странства Внутри контрольного интервала пространство, получившееся вслед- ствие удаления или сокращения за- писи, автоматически возвращается свободному участку интервала Записи упорядочены в порядке их поступления в файл Доступ по RBA RBA записи ие изменяется Для выделения места новой записи может использоваться только про- странство в конце файла Записи нельзя удалять физически, одиако пространство можно повторно использовать для записей такой же длины До сих пор метод доступа VSAM еще не получил широкого признания среди специалистов по обработке данных. Причиной этого часто является первоначальная неотработанность опера- ционной системы OS/VS, предназначенной поддерживать VSAM. Так как теперь эти предварительные проблемы преодолены, ие остается никаких сомнений в том, что VSAM будет интенсивно использоваться. Сейчас многие компиляторы не обеспечивают специальных возможностей доступа к файлам в VSAM. Однако для доступа к файлам в VSAM можно использовать языковые средства метода ISAM.^ у 738
Упражнения к п. 7-10 1. Сформулируйте алгоритм, описывающий шаги, необходимый для локализации отдельного слова виртуальной памяти по заданному трехуров- невому адресу s, р, d, где s — имя сегмента, р — индекс для таблицы страниц сегмента s, ad — смещение внутри страницы р. Считайте, что таблицы сегментов н таблицы страниц имеют форму, проиллюстрированную иа рис. 7-10.3. 2. Предположим, вы переносите файловую информационную систему с ма- шины, имеющей средства управления данными, поддерживающие последователь- ные, индексно-последовательные и прямые файлы, на систему виртуальной па- мяти с сегментно-страничной организацией. Пусть файловая организация ана- логична файловой организации, имеющейся в системе MULTICS, где файловая система объединена с сегментио-страничной системой. Обрисуйте в общих чертах потенциальные достоинства и недостатки такого преобразования, предполагая, •что в исходной информационной системе использовались последовательные, индексно-последовательные и прямые файлы. 3. В системах виртуальной памяти, обсуждавшихся в этом параграфе, при перемещении страницы или сегмента в основную память требуется заменить страницу или сегмент, имеющиеся в текущий момент в основной памяти. Будет ли правильным выбирать страницу или сегмент для замены с помощью процедуры, обеспечивающей случайный выбор? Не_можете ли вы предложить лучшую стра- тегию замены? 4. Разработайте алгоритмы для доступа к файлам в VSAM в последователь- ном режиме с пропуском н в режиме прямого доступа. Используйте обозначе- ния, согласующиеся с принятыми в этом параграфе. 5. Перечислите преимущества VSAM-файлов с упорядочением по ключам по сравнению с индексно-последовательными файлами (для версии IBM). Можете ли вы указать какие-нибудь недостатки файлов в VSAM с упорядочением по ллючам? 7-11. ДОСТУП ПО МНОГИМ КЛЮЧАМ Описание структуры файлов дают нам возможность доступа к записи по единственному ключу. Этот ключ часто назы- вают основным ключом. Однако для многих практических задач бывает желательным и даже необходимым иметь доступ к записи с использованием любого из целого ряда ключей. Одной из таких задач является информационная система больницы, предназна- ченная для оказания помощи в распределении лекарств пациен- там. В данном параграфе мы приведем несколько конкретных при- меров из этой системы. Пользователи системы получают ответы на запросы следующего типа: Список всех пациентов в палатах для выздоравливающих. Каким пациентам доктора Новака назначено лекарство XEN-02? Сколько сейчас пациентов в палате педиатрии? Есль ли среди пациентов Джон Браун? На рис. 7-11.1 изображены записи файла пациентов, упорядо- ченного по госпитализационному иомеру. Этот номер однозначно идентифицирует каждую запись файла и, следовательно, может служить основным ключом. Другие элементы записи пациента называются вторичными индексными элементами (также часто называемыми вторичными ключами, что отчасти бессмысленно, (гак как эти элементы совсем не обязательно однозначно иденти- фицируют запись). Вторичный индесный элемент важен при обра- 24* 739
Основной ключ Элементы вторичного индекса Иырпрмащ10н1-ъ:е Госпитали- зационныи номер Имя пациента номер больничного места Доктор пациента Назначенное лемрство не относящиеся к элементам вто- ричного индекса 0913628 Ьраун R67 Новак XEN-02 "П 0931767 Каупек АО? 7етл HYPOCH 1 101376? Ролли АОЧ блэк SULPH-3 102937? Кристи 821 Новак CRYOL ZZ1 3056718 Джонс R69 Джеймс RE5IN-A 1 ЗО8У258 Уотсон 823 Тетл CRYOL 3931768 Эндрюс А09 Новак NEOBEN 1 9111239 Маккорд 829 Джеймс HYPOCH УЧ50902 Тэт 133 Кинг SULPH-3 1 6331313 Уайт А08 блэк LAX j 7619009 Дрч R68 Джеймс NEOBEN ] 7729310 бент АОЗ Новак SULPH-3 1 Смысл номера больничного места А-общая палата, В-палата педиатрии, I-палата усиленного леченая, fl- палата для беременных, R-палата для выздоравливающих Рис. 7-11.1. Записи в информационной больничной системе ботке запросов, основанных на значении элемента, и используется аналогично основному ключу или вместе с ним для прямого до- ступа к записи. Например, ответом на запрос «Список всех пациен- тов в палате для выздоравливающих» может являться список всех фамилий пациентов с номерами больничных мест, начинающимися с буквы R, вместе с другой информацией, представляющей ка- кой-то интерес ’. Неиндексные элементы на рис. 7-11.1 могут содержать такую информацию, как адрес пациента и список различных лечебных процедур и услуг, использованных пациентом во время лечения (например, установка для лечения кобальтом и услуги, оказанные при родах). Некоторые из элементов, не являющиеся индексными, могут также рассматриваться как вторичные индексные элементы* если потребуется отвечать на запросы, относящиеся к этим эле- ментам. Например, запрос «Список всех пациентов, для которых необходима психотерапия», наиболее эффективно обслуживается при выборе одного из методов доступа со вторичными ключами, описанных ниже в данном параграфе для элемента записи пациента* соответствующего используемым лечебным процедурам. В этом параграфе мы проанализируем ряд структур файлов помогающих при выборке информации для запросов по вторичным' индексным элементам. В частности, будет рассмотрена организа- ция файлов в виде мультисписков (многократно прошитых списков), а также в виде инвертированных списков. Введением ограничения 1 R — начальная буква в слове recovery (выздоровление). — Прим, nejfc 740
для структуры с управля- емой длиной списка все за- писи в более коротком из списков, соответствующих термам конъюнктивного за- проса, все еще должны ска- нироваться. Например, если мы допустим, что доктор Новак имеет шесть пациен- тов, и имя доктора рассмат- ривается как вторичный ин- дексный элемент, то запрос «Список всех пациентов док- тора Новака, находящихся в общей палате» должен вы- зывать поиск записей в обоих списках для общей палаты в соответствии с рис. 7-11.5. Тем не менее расщепление длинного спис- ка на несколько меньших подсписков имеет большие преимущества. Если инди- видуальные списки распо- лагаются на отдельных мо- дулях внешней памяти (т. е. Основной КЛЮЧ Элемент вторичного индекса Оосаитоли- зационный номер Номер больничного места Поре связи. (основной ключ) 0913628 0931762 1013761 1020372 3056718 3089255 3931768 9111239 9950902 6331313 7619003 7729310 R67 А02 АОЧ 821 R69 823 А09 829 133 А08 R68 АОЗ 7619009 7720310 null 3089255 null 9111239 null null null 3931768 3056718 1013761 Палата Больничное место Поле связи Длина Общая А02 0931762 3 Общая АОЗ 6331313 2 Педиатрии 821 1029372 3 Интенсивного лечения 133 9650902 1 Для беременных Oil «нН О R67 0913628 3 Рис. 7-11.Б. Мультисписок с управляемой) длиной списка отдельных цилиндрах или устройствах), то иногда можно совмещать доступ к спискам,, так чтобы обработка одного списка и чтение другого выполнялись параллельно. Теперь должно стать очевидным, что мультисписок с управляе- мой длиной списка, равной единице, является просто структурой с инвертированными списками, а мультисписок с управляемой длиной списка, равной бесконечности, есть не что иное, как муль- тисписковая структура, рассмотренная нами ранее. Будучи гиб- ридной структурой, мультисписок с управляемой длиной списка несет в себе недостатки обеих «родительских» структур. Мы уже обсудили, каким образом проблемы, вызываемые запросами с конъ- юнктивными термами, несколько упрощаются при управлении длиной списка в мультисписковой структуре. Недостаток, свя- занный с избыточным включением индексных элементов в инвер- тированный список и в записи основного файла, проникает и в стру- ктуру с управляемой длиной списка. Чем больше создается под- списков, тем больше элементов появляется во вторичном индексе. При этом каждый элемент в индексе содержит поле имени, которое дублируется и в записи основного файла. Ниже в этом параграфе мы еще раз вернемся к некоторым проблемам, относящимся к веде- нию мультисписковых структур с управляемой длиной списка. 747
7-11.4. Структуры с разделением на секции До этого момента при рассмотрении методов доступа со вторичными ключами мы почти полностью игнорировали физиче- ское размещение записей. Альтернативой является использова- ние преимуществ, даваемых размещением записей по секциям (т. е. блокам или секторам) применяемого устройства памяти пря- мого доступа. Необходимо по возможности пытаться загрузить записи с общим атрибутом (например, записи пациентов одного и того же врача) в одну и ту же секцию. При такой форме группи- рования ко всем или, по крайней мере, ко многим из записей с об- щим атрибутом можно получить доступ с помощью одной опе- рации чтения. Большинство файлов, однако, организуется на базе основного ключа, и размещение записей по отношению ко вторичному ключу =(или индексу) часто оказывается произвольным. Но даже в таких •ситуациях имеет смысл организовать вторичные индексы, или ин- вертированные структуры, на базе разделения файла на секции. Для иллюстрации того, что понимается под разделением на •секции, рассмотрим мультисписковую структуру, изображенную на рис. 7-11.6. Элемент во вторичном индексе создается каждый раз, когда некоторое значение вторичного ключа появляется один или несколько раз в выделенной секции. Следовательно, значению Новак соответствует три элемента в индексе, поскольку имя Новак появляется во всех трех секциях, а значению Кинг — один элемент, так как это имя встречается только в одной секции (сек- ция 3). Относительная позиция в секции первой записи в цепочке за- носится в индекс, чтобы упростить прямой доступ к записи после 'Считывания секции в основную память. Поле длины представляет Вторичный индекс, _ описыдсющий относительную позицию записи гРис. >7-11.6. Мультисписковая структура с разделением на секции 7ДЙ
Вторичный инбексный Разделение на секции Имя Секция # элемент 1 2 3 Новак 1 2 3 Доктор Лекарство Новак Тетя Блэк Джеймс Кит 1 1 1 1 1 0 1 0 1 g i 1 0 О 1 Тетп 1 2 XEN-02 HYPOCH SULPH-3 CRYOL NEOBEN LAX RESIN-A 1 0 0 1. 1 0 1 о 1: 110 0 1 1 0 0 1 0 10 Блэк 1 3 Джеймс 2 3 Кинг 3 c) t) Рис. 7-11.7. Таблицы вторичного индекса для: а — секционной сериальной структуры; б —• секционной структуры с использованием инвертированных списков/ дополнительную информацию при выполнении ответов на конъ- юнктивные запросы. Следует заметить, что при обработке запросов, содержащих конъюнкцию термов, имеет смысл выполнить некоторую предва- рительную обработку на уровне секций. Например, рассмотрим запрос: «Сколько пациентов доктора Джеймса получают лекар- ство HYPOCH?» Предположим, что название лекарства трак- туется как элемент вторичного индекса, к которому можно полу- чить доступ посредством мультисписковой структуры, разделен- ной на секции. Тогда название HYPOCH появляется в записях секций 1 и 2. Поскольку имя Джеймс имеется в секциях 2 и 3, то операция пересечения на уровне секций, указывает на то, что все пациенты доктора Джеймса, принимающие HYPOCH, могут быть найдены в секции 2. Мультисписковая структура с разделением на секции в основ- ном полезна при большом числе записей, находящихся в секции. Добавочное пространство, требуемое полем связи, оправдано, если оно значительно уменьшает время поиска списка записей в секции. Если же в секции имеется относительно мало записей,, как в примере на рис. 7-11.6, то лучше опустить поле связи и анализировать записи секции сериально. Индекс для такого типа структуры, относящийся к секциям файла пациентов на рис. 7-11.6, иллюстрируется рис. 7-11.7, а. Заметим, что в индекс не включены ни относительная позиция записи, ни поле длины. Левковиц [201 749
•ссылается на этот тип структуры как на секционированную сериаль- ную структуру. Мартин [26 ] описывает похожий тип структуры, который он называет секционированный инвертированный список. Индексом для такой структуры является двоичная матрица, каждый элемент которой указывает на присутствие (значение 1) или отсутствие (значение 0) соответствующего значения вторич- ного индекса в заданной секции. Рис. 7-11.7, б иллюстрирует та- кую структуру для элементов, содержащих имя доктора и ле- карство. Этот последний тип структуры может использоваться -очень эффективно при обработке запросов, включающих логиче- ское И (конъюнкцию) и логическое ИЛИ (дизъюнкцию) термов за- проса. Например, для нахождения секций, содержащих информа- цию, удовлетворяющую запросу: «Список всех, кто либо является пациентами доктора Блэка, либо принимает лекарство LAX», необходимо сделать логическую операцию 101 V 001 = 101. Сле- довательно, нам достаточно выполнить сериальный поиск в сек- циях 1 и 3, чтобы найти всю информацию, удовлетворяющую ус- ловиям запроса. Главным преимуществом разделения на секции является воз- можность одновременного инициирования нескольких операций чтения и совмещения этих операций с обработкой запроса и гене- рацией ответа. Время поиска может быть несколько уменьшено предварительным выполнением логических операций на уровне секций. Если записи с общими значениями элементов не сгруппи- рованы в нескольких секциях (т. е. если значения вторичных ин- дексных элементов рассеяны по ряду секций с одной или двумя записями в секции, содержащими общее значение элемента), то применение структур с инвертированными списками предпочти- тельнее. Чем большее рассеяние имеют общие значения вторичных ключей по секциям файла, тем больше требуется элементов во вторичном индексе и тем медленнее выполняется доступ к записям, имеющим общее значение вторичного ключа. Мы рассмотрели ряд файловых структур, используемых для доступа со вторичными ключами, привели примеры таких струк- тур и обсудили преимущества и недостатки каждой из них в тер- минах процесса поиска (т. е. как легко каждая из них обеспечи- вает обслуживание запросов). Теперь изучим, насколько легко или, наоборот, затруднительно поддерживать каждую из этих структур. Мы будем рассматривать такие операции, как добавление, удале- ние и обновление записи файла. 7-11.5. Ведение мультисписковой структуры Добавление записи в файл может вызывать его сущест- венную реорганизацию вследствие создания записей переполнения и вынужденных изменений в индексах для основного ключа. Эти вопросы обсуждались в предыдущих параграфах настоящей главы. При мультиключевом доступе добавление записи также может из- 750
менить таблицы вторичных индексов, связанных с каждым вто- ричным ключом файла. В мультисписковых структурах добавле- ния могут выполняться относительно легко. Если списки не упо- рядочены, добавления проще всего реализуются размещением новых записей в логической голове списка. Это сводится только к изменению адресного поля элемента в индексе для каждого зна- чения вторичного ключа, представленного в новой записи. Рис. 7-11.8 иллюстрирует действия такого изменения на примере элемента с именем доктора. Если список должен быть упорядо- чен в соответствии с некоторым критерием, то новая запись вклю- чается в середину списка. И, наконец, если список упорядочен по значению счетчика активности (т. е.счетчика, фиксирующего число обращений к данной записи), запись следует помещать в ло- гический конец списка. В этом случае для ускорения процедуры включения полезно хранить позицию последней записи списка в соответствующем элементе индекса. Заметим, что поле длины в элементе индекса необходимо увеличивать на единицу при каждом добавлении записи в файл. Удаление записи из мультисписковой структуры можно трак- товать как процесс, обратный включению записи. Сначала лока- лизуется элемент в индексе, соответствующий каждому значению вторичного ключа удаляемой записи. Затем, используя информа- цию о голове списка, предоставляемую элементом индекса, и про- ходя по списку для заданного значения вторичного ключа отыски- вается требуемая запись и удаляется из списка изменением поля связи. Для удаления можно использовать процесс, аналогичный описанному в алгоритме DELETE из п. 4-2.1. Этот процесс оказы- вается длинным и сложным, если в удаляемой записи имеется ряд элементов, присутствующих в мультисписковых структурах. Время, необходимое для выполнения удалений, можно умень- шить, если вместо логического устранения записи из мульти- списка просто пометить ее как удаленную. Недостатком такого подхода является то, что удаленную запись мы должны рассма- тривать как активную при работе со списком в мультисписковой структуре. Для файлов, подвергаемых частым изменениям, на- кладные расходы, вызываемые удаленными записями, которые остались в мультисписковых цепочках, оказываются очень боль- 751
шими. Характеристики функционирования системы можно улуч- шить периодическим удалением неактивных записей с использо- ванием низкоприоритетной фоновой обработки. Если система требует как ведения файла, так и поиска в нем в онлайновом режиме, то в мультисписковых структурах необхо- димо применять двусторонние указатели. Удаление узла из дву- направленного списка было описано в алгоритме DOUBDEL в п. 4-2.3. С помощью этого алгоритма запись может быть удалена без прохождения по списковой структуре, начиная с головы списка. Следовательно, удаления выполняются более быстро, если допу- стить небольшой перерасход памяти на поле связи для каждого вторичного ключа в записи. В некоторых случаях необходимо удалять только вторичный индексный элемент из записи, не удаляя запись целиком. Для этого достаточно остановиться на одном из следующих решений. Во-первых, можно выделить специальное значение как признак того, что это значение элемента не несет реального смысла (на- пример, использовать «нулевое» значение), или, во-вторых, со- провождать каждый элемент битом, указывающим, удален эле- мент из списка или нет. Этот метод предложил Левковиц [201. В-третьих, можно логически удалить элемент из связанного списка, используя только что описанные приемы для удаления записи, а затем переписать запись с фактическим удалением элемента. Теперь обратим внимание на проблемы, относящиеся к обнов- лениям записей в мультисписковой структуре. При перезаписи всякий раз меняется некоторый элемент. Если элемент, принад- лежащий мультисписку, меняет свое значение, то запись должна удаляться из одного списка и добавляться в другой. Обновление влечет за собой вызов процедур удаления и добавления для спис- ковых структур. Такие процедуры ранее уже обсуждались. Об- новления, вовлекающие добавление элемента или удаление его из записи, выполняются аналогичным образом. В зависимости от того, как записи организованы в файле, добавление элемента мо- жет приводить к перемещению записи. Последний важный момент, относящийся к ведению мульти- списковых структур, касается восстановления системы в случае отказа аппаратуры (например, отказа устройства внешней памяти во время записи) или программного сбоя (например, при появле- нии программной ошибки, приводящей к стиранию записи)'. Если неисправность возникает в то время, когда модифицируется указатель, результатом может оказаться его ошибочное зна- чение. Обеспечить восстановление системы относительно легко, если мультисписковая структура имеет двойные связи (обратного указателя достаточно и для восстановления неправильно записан- ного прямого указателя). При использовании односвязанных списков восстановление затруднительно, если вообще воз- можно. 752
7-11.6. Ведение инвертированных списков Добавление записи, содержащей одни или более ин- вертированных элементов, может вызвать существенные проб- лемы. Если инвертированный список ведется как таблица с по- следовательным размещением или как последовательный файл, то добавление новой записи, содержащей инвертированный эле- мент, приводит к перемещению элементов в таблице или записей последовательного файла с целью освобождения места для новой записи. В качестве примера рассмотрим влияние добавления записи пациента с госпитализационным номером 6293109, полем имени Этвуд и номером больничного места А07 на инвертированные списки, показанные на рис. 7-11.4. Если инвертированный список размещается в файле прямого доступа или в последовательном файле, то новый инвертированный элемент может создать запись переполнения. Если инвертированная структура реализована в виде дерева или связанного списка, то добавление вызывает не- много проблем, а именно создание узла в списковой структуре и изменение нескольких полей связи. Добавление нового значения элемента, например добавление психиатрической (Р) палаты к списку палат на рис. 7-11.4, приводит к формированию совер- шенно нового подсписка внутри текущей структуры инвертиро- вагного списка. В большинстве случаев, однако, добавления вы- полняются быстрее для инвертированных списков, чем для муль- тнсписковой структуры, что объясняется отсутствием необходи- мости чтения записей основного файла. Удаление записей, содержащих инвертированные элементы, требует исключения из инвертированного списка указателей на каждый такой элемент. Например, удаление записи пациента с номером 3931768 приводит к исключению элемента, соответ- ствующего имени Эндрюс в списке имен пациентов. Кроме того, необходимо также удалить элемент, содержащийся в подсписке для общей палаты списка палат пациентов, изображенного на рис. 7-11.4. Трудности устранения указателя зависят от соответ- ствующей списку структуры хранения (или файла). Прн исполь- зовании последовательной структуры хранения более предпочти- тельно связать с каждым элементом инвертированного списка флажок удаления вместо того, чтобы физически удалить указа- тель. Время от времени такие инвертированные списки можно реорганизовать и устранять удаленные элементы физически. Уда- ление индивидуальных инвертированных элементов не вызывает дополнительных проблем к тем, которые уже были обсуждены н касались устранения целых записей. Аналогичным образом об- новление записей не приводит к новым концептуальным трудно- стям, н его удобнее рассматривать как процесс удаления элемента из одного инвертированного списка и включения нового элемента в другой. 25 Трамбле Ж.. Соренсон П. 753
7-11.7. Ведение ограниченного мультисписка и секционированных структур Добавление, удаление или обновление записей с эле- ментами, предназначенными для использования в качестве вто- ричных ключей, увеличивает или уменьшает число записей в кон- кретном списке внутри мультисписковой структуры. Следова- тельно, ведение структуры, подобной мультисписку с управляемой длиной списка, может вызывать некоторую дополнительную обра- ботку, не требуемую для мультисписка с неограниченной длиной списка. Например, добавление записи пациента с госпитализацион- ным номером 6912488 н номером больничного места АО! приводит к существенной реорганизации мультисписковой структуры с уп- равляемой длиной мультисписка, изображенной на рис. 7-11.5. Запись с номером больничного места А04 должна перемещаться в голову второго подсписка для обшей палаты, а новая запись с но- мером А01 оказывается в голове первого подсписка. Заметим, что добавление еще одного пациента в общую палату вызовет созда- ние нового подсписка -и. нового элемента вторичного индекса. Удаление элемента требует меньше действий, поскольку нет необходимости создавать новый список. Тем не меиее индексные элементы могут быть изменены или удалены совсем, если длина списка уменьшится до нуля. Для ускорения удалений в онлайно- вом режиме можно воспользоваться битом удаления. Большие реорганизации списков с максимально допустимыми длинами мо- гут выполняться в офлайновом режиме. Обновление вторичных ключей приводит к действиям по удалению и добавлению записей. Ведение структур с разделением на секции реализуется проще, чем ведение структур с управляемой длиной списка. По возмож- ности записи должны размещаться так, чтобы способствовать скоплению общих элементов вторичного индекса в секциях. До- биться этого очень трудно, если вообще возможно, из-за порядка следования записей, определенного основным ключом. Для сек- ционированных мультисписковых структур элементы в индексе могут изменяться при удалении и добавлении записей или инди- видуальных элементов вторичного индекса. Заметим, что такие изменения минимальны при использовании секционированной сериальной структуры или структуры с секционированными ин- вертированными списками. 7-11.8. Обзор методов доступа со вторичными ключами В Табл. 7-11.1 собраны сводные данные о достоинствах и недостатках методов доступа со вторичными ключами, обсуждав- шихся в этом параграфе. При отсутствии возможности некоторого распараллеливания операций ввода-вывода для осуществления доступа со вторичными ключами обычно выбирается мульти- 754 i
Таблица 7-11.1 Сравнение структур для методов доступа со вторичными ключами Структура Преи му щества Недостатки Мультисписок Легкость программирова- ния. Простота обновления, особенно при использовании двунаправленных связей. Эффективное использова- ние памяти, особенно если элементы вторичного индек- са не хранятся в записях основного файла. Упорядо- чение списков в соответ ствии с частотой обращения может повысить скорость доступа. Наиболее удобен для за- просов, содержащих един- ственный вторичный ключ, так как должны анализиро- ваться все записи в списке Медленный доступ, так как требуется проанализи- ровать много записей основ- ного файла. Затруднена об- работка запросов с конъ- юнктивными термами, по- скольку они требуют пере- сылки в память всех запи- сей самого короткого списка Инвертирован; ный список Обеспечивает самый бы- стрый доступ среди всех других методов и особенно удобен для запросов с конъюнктивными термами. Сравнительно быстрое об- новление, особенно если списки размещаются в ос- новной памяти. Легко осуществляется сбор файловой статистики об использовании вторич- ных ключей Удаления и добавления мо- гут создавать проблемы при последовательном размеще- нии списка. Неэффективное использование памяти, если элементы вторичного индекса находятся в инвертирован- ном списке и записях основ- ного файла. Отсутствуют заметные преимущества по сравнению с мультисписковой структу- рой при обработке запросов с одним термом, если нет па- раллельного доступа к запи- сям Мультисписок с управляемой длиной списка Возможен параллельный доступ. Списки делятся на под- списки, и если можно опре- делить, к какому подсписку принадлежит конъюнктив- ный терм, то запрос обслу- живается быстрее, чем для мультисписка. Может относительно лег- ко изменяться, принимая черты либо мультисписка, либо инвертированного спи- ска в зависимости от требо- ваний системы Для определенных конъ- юнктивных запросов может оказаться необходимым все подсписки списка анализиро- вать сериально. Обновление более сложно, чем для мультисписка. Присущи многие нз недо- статков мультисписка нли инвертированного списка в зависимости от того, уве- личивается ли длина под- списка или сокращается 25* 755
Продолжение табл. 7-II.1 Структура Преи мущества Недостатки Секционирован- ные мультисписки Предназначены для ис- пользования преимуществ параллельного доступа. Эффективность обслужи- вания запроса может быть повышена выполнением конъюнктивных операций на уровне секций. Относительная легкость обновления по сравнению с мультисписком с управ- ляемой длиной списка Рассеяние термов вторич- ного индекса приводит к низкой производительно- сти. Присущи многие из недо- статков мультисписка Секционирован- ный. сериальный и секционированный инвертированный списки Очень простое ведение ин- декса Обеспечивает лучшие характеристики, чем прн использовании секциониро- ванных мультисписков, если на секцию приходится только несколько записей. Битовая схема кодирова- ния обеспечивает быстрые логические операции над элементами вторичного ин- декса иа уровне секций Эффект рассеяния ухуд- шает характеристики При возрастании числа записей в секции время доступа мо- жет быть большим списковая структура или структура с инвертированными спис- ками. Мультисписковые структуры идеальны для систем с огра- ниченной основной памятью и (или) не требующих малого времени ответа. Простота программирования систем, применяющих муль- тисписковые структуры, является еще одним преимуществом. Для большинства больших информационных систем, однако, инвертированные списки более предпочтительны, если система требует малого времени ответа. В этом параграфе мы поставили знак равенства между муль- тиключевым доступом и доступом со вторичными ключами. До некоторой степени это сопоставление отражает нынешнее состоя- ние технологии обработки данных. Без сомнения, в будущем средства мультиключевого доступа будут в той же степени частью процедур управления данными операционной системы, как в на- стоящее время методы доступа по основному ключу, такие как ISAM и VSAM. К тому же разработка ассоциативной основной па- мяти и ассоциативной вторичной памяти определенно должна сыг- рать главную роль в будущих системах мультиключевого доступа. 756
Упражнения к п. 7-11 1. Разработайте алгоритм для ответа на запрос в форме: «Список всех элементов со свойствами к и #», где к и у представляют собой значения двух различных элементов в записи основного файла. Если отдельная запись в файле имеет эти свойства, то возвращается значение третьего элемента, скажем х. При* мером такого запроса является: «Список имен всех пациентов в общей палате, принимающих CRYOL»- В этом случае «общая палата» и «CRYOL» соответ* ствуют х и у, а «имена всех пациентов» — г. Алгоритм должен быть разработан в предположении, что каждый нз элементов для х и у обеспечивает вторичный доступ к мультисписковым структурам. Должны быть сформулированы все допущения, касающиеся форм таблиц, содержащих вторичные индексы. 2. Выполните упр. 1, предполагая, что для доступа со вторичными клю- чами х и у используются инвертированные списки. 3. Выполните упр. 1, предполагая использование для доступа со вторич- ными ключами х и у мультисписков с управляемой длиной списка. 4. Выполните упр. 1, предполагая использование для доступа с ключами х и у секционированной сериальной структуры. 5. Если конъюнктивный запрос вовлекает две мультисписковые структуры, то мы должны анализировать записи самого короткого списка. Предположим, что для одного терма конъюнктивного запроса обеспечивается доступ со вторич- ным ключом через инвертированный список, а для второго терма — через муль- тисписок. Какой критерий мы должны использовать в этой ситуации для селек- ции записей? 6. Свернутой структурой называется мультисписковая структура, в кото- рой все значения для элементов вторичного индекса устранены из основного файла, а на их месте присутствуют только поля связи. Рис. 7-II.9 иллюстри- рует такую структуру на примере информационной системы больницы. Проана- лизируйте преимущества и недостатки свернутой структуры. 7. Разработайте общий алгоритм для обработки конъюнктивного запроса, содержащего п термов (например, «Сколько объектов имеют свойства лу и х2 и xs ... и хп»), в предположении, что все объекты обеспечивают доступ с помощью вторичных ключей с использованием структуры с инвертированными списками. 8. На протяжении этого параграфа мы допускали для каждой записи па- циента только одно лекарство. Это, безусловно, нскусствениое ограничение, так как пациенты часто принимают более одного лекарства. Обсудите проблемы, связанные с трактовкой элементов с повторяющимися полями в качестве вторич- ных индексных элементов. В частности, укажите преимущества и недостатки использования для такого элемента а) мультисписка и б) инвертированного списка. Основной ключ Поле связи вторичных индексных элементов Госпитали- Поле связи Поле связи зационныи номер для доктора для лекарства 0931762 2066766 1010123 0955128 1967892 null 1010123 7936129 1967892 1967892 3077990 null 2066766 null 8813762 - Индекс доктора Доктор Указатель Длина Браун 0955128 3 Дуглас 1010123 2 Гастингс 0931762 2 Индекс лекарства Лекарство Указатель Длина COBALT 2066766 2 CRYOL 0955128 SETRl 0931762 3 Рис. 7-11.9. Свернутая структура 757
7-12. ВВЕДЕНИЕ В СИСТЕМЫ БАЗ ДАННЫХ В п. 7-2 было введено понятие базы данных. База дан- ных была определена как совокупность файлов, используемых прикладными программами некоторого отдельного, предприятия таким образом, что между файлами проявляются определенные ассоциации или взаимосвязи на уровне записей. В этом параграфе мы начнем с тщательной разработки некоторых основных концеп- ций, относящихся к системам баз данных, а затем обсудим три подхода, используемых при построении систем с большими ба- зами данных. Этими тремя подходами являются иерархический, сетевой и реляционный. 7-12.1. Общие концепции в системах баз данных Только что данное определение для базы данных является достаточным со структурной точки зрения, но оно опус- кает важное свойство, идеальным образом характеризующее си- стемы, разработанные с использованием концепций базы данных. Эго важное свойство, называемое независимостью данных, может быть описано как условие, при котором данные и прикладные программы независимы в том смысле, что каждое из. них может быть изменено без изменения другого. Следовательно, например, прикладные программы могут оставаться неизменными при мо- дификации данных и способа их организации. Чтобы добиться ясного понимания независимости данных, мы опишем, как такой термин можно применить по отношению к руч- ной файловой системе. Предположим, мы просим секретаря выб- рать информацию в файле с именем ALBERT. Любые изменения в положении файла (т. е. перенос файла из ящика А в ящик В), во внутренней нумерации файла, числа подфайлов, созданных из основного файла ALBERT, не должны серьезно сказываться на доступе к интересующей нас информации. Следовательно, за- прос на информацию в некоторой степени не зависит от способов хранения и организации информации. Однако если независимость данных доведена до крайности (т. е. мы устранили имя ALBERT как индекс в нашей файловой системе и разнесли информацию в файле по ряду других файлов), то доступ к информации становится затруднительным и требует много времени. Мы увидим, что в си- стемах баз данных степень независимости данных частично за- висит от выбранного подхода к разработке системы. Помимо некоторой степени независимости данных интегриро- ванные системы баз данных должны обеспечивать централизован- ные операции по управлению. Операции такого типа необходимы при решении следующих задач: 1) уменьшение степени избыточности в хранимых данных; 2) поддержание целостности данных н устранение проблем противоречивости данных (возникающих при изменении одного 758
Рис. 7-12.1. Архитектура базы данных. Жирные стрелки относятся к отображениям, выполняемым программными средствами проявления факта без изменения других проявлений того же факта); 3) упрощение совместного доступа к данным между пользова- телями; '4 ) обеспечение более единообразного и эффективного контроля за надежностью и секретностью данных. Пути достижения этих целей более подробно описал Мартин [261. Рис. 7-12.1 отображает общую организацию системы баз дан- ных. Этот рисунок и большая часть терминологии, применяемой в данном параграфе, следуют Дейту [81. На рисунке показаны различные уровни в системе баз данных. Верхний уровень является уровнем пользователя, который взаимодействует с системой в ра- бочей среде посредством языка, зависящего от области применения. В одной системе при использовании одной и той же базы данных могут сосуществовать несколько классов пользователей, приме- няющих различные языки. Предложения на языке пользователя транслируются в выражения подъязыка данных. Выражения подъ- языка затем анализируются, после чего проводится требуемая выборка из базы данных или модификация базы данных. Примеры подъязыка данных будут приведены ниже в этом параграфе. Для достижения независимости данных создается модель данных, управляемая человеком (или группой людей), называемым администратором базы данных. Модель данных обеспечивает ло- гическое представление хранимой информации. Логическое пред- 759
ставлени не должно зависеть от физического представления дан- ных. Например, доступ к отдельной записи может проявляться на логическом уровне как поиск ассоциативного типа, в то время как информация хранится в файле прямого доступа, выборка из которого фактически осуществляется с помощью алгоритма пре- образования адреса И, разумеется, переход к другому типу физи- ческого представления, использующему индексно-последователь- ный файл, не должен приводить к изменению логического пред- ставления. В системе баз данных могут быть подмодели данных (т. е. под- модели модели данных), предназначенные для отдельных • групп пользователей системы баз данных. Эти подмодели дают ограни- ченные представления общей модели данных; необходимость та- ких суженных представлений оправдывается соображениями за- щиты информации. Мы уже отмечали важность отображения модели данных на структуру хранения базы данных. Обычно такое отображение использует методы доступа средств управления данными опера- ционной системы. Программное обеспечение, необходимое для та- кого отображения, наряду с программным обеспечением, реали- зующим другие отображения, представленные на рис. 7-12.1, и составляет систему управления базами данных (DBMS). Должно быть очевидным, что роль администратора базы дан- ных очень важна. Детальный перечень его обязанностей, приве- денный в работе 18 J, включает решения относительно информа- ционного содержания базы данных и структуры памяти, обеспе- чение связи с пользователями, определение прав доступа, выра- SUPPLIER# SNAME DELIVRY- TIME CITY PART# PNAME PSIZE QTY 5/ Black 1 New York p; Гайка 3/4 3 S7 Black 2 New York P2 болт 3/4-2 7 S2 Lee 1 Toronto P5 Пружина 2 4 S2 Lee 1 Toronto P6 Звездочка 4 2 $3 Waters 1 Chicago p; Гайка 3/4 3 S3 Waters 1 Chicago P2 болт 3/4-2 4 S3 Waters 2 Chicago । P3 болт 3/4-7 2 S3 Waters 7 Chicago pf Звездочка ч 2 S4 Пуск 3 St Louis P4 Винт //«-; б S5 Jones 1 Montreal P4 Винт //4-7 2 S3 Jones 1 Montreal P5 Пружина 2 5 S5 Jones 1 Montreal P6 Звездочка 4 4 S6 Whyte 2 Los Angeles Р/ Гайка J/4 3 S6 Whyte 2 Los Angeles P2 болт 3/4-2 1 S6 Whyte 2 Los Angeles P3 болт 3/4-7 2 Рис. 7-12.2. Данные для задачи о поставщиках и изделиях 760
ботку стратегии копирования и восстановления информации, уп- равление характеристиками функционирования системы и пове- дением пользователя. В приводимом ниже описании каждого из трех подходов к уп- равлению базами данных мы начнем с представления модели дан- ных; затем опишем, как с помощью подъязыка данных, совмести- мого с моделью данных, осуществляется доступ к информации, и, наконец, обсудим некоторые из преимуществ и недостатков каж- дого подхода. При обсуждении мы будем использовать пример из области инвентарного контроля изделий. Этот пример стал популярен благодаря Кодду [51. Информационными элементами, относящимися к примеру, являются: 1. Имя поставщика. 2. Номер поставщика. 3. Адрес поставщика. 4. Время поставки. 5. Наименование изделия. 6. Номер изделия. 7. Спецификация изделия. 8. Число изделий поставщика. Экземпляр базы данных, с которой мы будем работать, показан на рис. 7-12.2. 7-12.2. Иерархический подход Иерархической системой баз данных называется си- стема, в которой пользователь представляет базу данных в виде деревьев сегментов. Эти сегменты могут содержать несколько под- уровней сегментов, оканчивающихся в информационной иерар- хии на уровне элементов. Термин сегмент введен фирмой IBM и обозначает основной квант данных, которым может манипулиро- вать программное обеспечение управления базой данных. Не- строго говоря, сегмент может быть записью или агрегатом эле- ментов. В индустрии обработки данных по традиции применяют иерар- хическую структуру данных, что хорошо видно на примере за- писи в КОБОЛе и структуры в ПЛ/1. Поэтому большинство сов- ременных систем управления данными являются иерархическими по своей природе. Примером такого подхода является система IMS (Informational Management System — система управления информацией) фирмы IBM. Для задачи о поставщиках и изделиях можно структурировать информацию, используя иерархическую модель данных, как это показано на рис. 7-12.3 и 7-12.4. На рис. 7-12.3, а приведен при- мер модели данных, в которой сегмент поставщика состоит из ряда объектов, включая сегмент изделия, а на рис. 7-12.3, б изображен вариант реализации модели данных, Рис. 7-12.4 иллюстрирует 701
SUPPLIER, 1 si Black 2 New York 2 SUPPLIER#, Pl \Гайка 3 '4 1 3 1 2 SNAME, P2 \болт У»-2: 4 2 DELVRY, 2 CITY, 2 PARUN], 3 PART#, 3 PNAME, 3 SIZE, 3 QTY. w (a) (6) Рис. 7-12.3. Модель данных с SUPPI IER в качестве родительского сегмента 1 PART, 2 PART#, 2 PNAME, 2 SIZE, 2 SUPPLIER^], 3 SUPPLIER#, 3 ShlAME, 3 DELVRY, 3 CITY, 3 QTY. ’ GO* | P2 bo/7w| 3/4-2 SI Black 2 New York 1 S3 Waters 1 Chicago 4 S6 Whyte 2 Los Angeles 1 (6) Рис. 7-12.4. Модель данных c PART в качестве родительского сегмента другую модель той же самой базы данных. В этом случае PART (ИЗДЕЛИЕ) является родительским сегментом, a SUPPLIER (ПОСТАВЩИК) является подчиненным сегментом. Теперь мы обсудим подъязык данных для доступа к информа- ции в иерархической базе данных. Язык функционально похож на DL/1 — подъязык данных, используемый в системе IMS. В табл. 7-12.1 дана сводка важнейших операций этого подъязыка. Проанализируем, как мы можем ответить на два запроса, ис- пользуя модель данных, изображенную на рис. 7-12.4. Вопрос 1. Найдите номера поставщиков, поставляющих из- делие РЗ. PART — NOT — FOUND = ’1’B; / * TRUE*/ GET UNIQUE PART (PART# = ’P3’) SUPPLIER; DO WHILE (PART—NOT—FOUND); print SUPPLIER#; GET NEXT WITHIN PARENT SUPPLIER; IF конец текущего исходного сегмента, THEN PART — NOT — FOUND = ’0’ B; / * FALSE * / END; 762
Таблица 7-12.1 Операции языка DL/1 Операция Семантика GET UNIQUE GET NEXT GET NEXT WITHIN PARENT GET HOLD UNIQUE, GET HOLD NEXT, GET HOLD NEXT WITHIN PARENT INSERT DELETE REPLACE Прямая выборка Последовательная выборка Последовательная выборка под те- кущим исходным сегментом Операции, эквивалентные приведен- ным выше, но после их выполнения могут следовать операции DELETE и REPLACE Добавление нового сегмента Удаление существующего сегмента Замена существующего сегмента Вопрос 2. Найдите номера изделий, поставляемых постав- щиком S2. ALL—PARTS—NOT—FOUND = ’1’B; / * TRUE*/ DO WHILE (ALL—PARTS—NOT—FOUND); GET NEXT PART; IF конец файла, THEN ALL—PARTS-NOT—FOUND = ’О’В; /* FALSE*/ ELSE DO; GET NEXT SUPPLIER (SUPPLIER# = 'S2'); IF номер поставщика равен S2, THEN print PART #; END; END; Оба эти запроса сформулированы на языке высокого уровня («гибридном» ПЛ/1) с включением операций подъязыка данных. Практика включения подъязыка данных в некоторой базовый язык не является редким исключением; например, как ПЛ/1, так и КОБОЛ служат базовыми языками для DL/1 Очевидно, что модель данных, представленная на рис. 7-12.4, гораздо лучше подходит для выборки информации, необходимой для ответа на вопрос 1, чегИна вопрос 2. При ответе на вопрос 1 необходимо только локализовать родительский сегмент с номером изделия РЗ и затем последовательно просмотреть подчиненный сегмент SUPPLIER (ПОСТАВЩИК) и напечатать значения поля SUPPLIER^ (НОМЕР- ПОСТАВЩИКА). Для ответа на вопрос 2, однако, необходимо получить доступ ко всем сегментам PART (ИЗДЕЛИЕ), и просмотреть каждый для поиска сегмента постав- щика с номером поставщика S2, что является задачей, требующей чрезвычайно много времени. С другой стороны, модель, представленная на рис. 7-12.3, облегчает получение ответа на вопрос 2, но не на вопрос 1. Это иллюстрирует основную проблему иерархического подхода. Хотя 763
эти два запроса вполне симметричны, процедуры подъязыка дай- ных, необходимые для ответа на такие запросы, при заданной мо- дели данных совсем не похожи друг на друга. Одна из процедур неоправданно сложна. Пользователь может уменьшить эту слож- ность, запрашивая у администратора данных расширения одной из моделей данных для включения обеих моделей. Однако такое расширение модели данных может привести к дублированному хранению многих элементов данных. Иерархический подход имеет и другие недостатки, в частности при ведении базы данных. Например, для модели данных, приве- денной на рис. 7-12.3, перед тем, как вводить в систему нового поставщика, мы должны предварительно ввести фиктивную деталь. Это не всегда удобно. Аналогичным образом удаление всех реали- заций изделий какого-то определенного поставщика в некоторых иерархических системах может привести к необходимости уда- ления самого поставщика. Если мы изменим спецификацию из- делия для отдельного изделия, скажем Р2 в модели данных на рис. 7-12.3, то мы должны изменить спецификацию для каждой реализации этого изделия^ Заканчивая наше введение в иерархические системы баз дан- ных, мы можем сказать, что основное преимущество этого подхода заключается в' отражении иерархического структурирования дан- ных, существующего в некоторых задачах «реального мира». Однако вследствие строгой иерархической структуры, навязанной таким подходом, пользователь может тратить время на преодоле- ние трудностей, внесенных исключительно моделью и не имеющих отношения к рассматриваемой проблеме. 7-12.3. Сетевой подход Сетевой системой баз данных называется система, пред- ставляемая пользователем в виде ряда реализаций индивидуаль- ных записей, в которых данный узел (т. е. элемент или агрегат элементов) может иметь любое число непосредственно старших или подчиненных узлов. Под старшим узлом мы понимаем узел, содержащий указатель на некоторый заданный узел. Сетевая струк- тура эквивалентна структуре графа, как она была описана в гл. 5, и отличается от иерархической структуры тем, что узел может иметь более одного старшего узла. В сетевой структуре имеется возможность представлять отно- шения «многие ко многим» (например, многие поставщики могут поставлять много изделий) вместо простых отношений «один ко многим» (например, один поставщик поставляет много изделий, как показано на рис. 7-12.3, или одно изделие поставляется мно- гими поставщиками, что изображено на рис. 7-12.4). Этот более общий тип отношения можно реализовать, используя внутри за- писей элементы связей. Рис. 7-12.5, а иллюстрирует общую схему отношений, которая может существовать в иерархической модели 764
Рис. 7-f2.fi. Сетевой подход: а — общая модель данных; б — реализация модели данных для задачи о поставщиках и изделиях, а рис. 7-12.5, б изображает некоторые фрагменты реализации модели с данными, взятыми из рис. 7-12.2. Связи верхнего уровня в записях постав- щик — изделие указывают изделия и соответствующие им налич- ные количества для данного поставщика. Связи нижнего уровня в записях поставщик — изделие указывают список поставщиков и соответствующие им наличные количества для данного изделия. При необходимости представить полную базу данных, показанную на рис. 7-12.2, вместо штриховых линий в цепочках следует рас- положить соответствующие записи. Прежде чем переходить к анализу преимуществ и недостатков такого подхода, познакомимся с подъязыком данных, который мо- жет служить для поиска и ведения сетевой базы данных. Мы вве- дем подъязык данных, базирующийся на операциях языка мани- пулирования данными COBOL DML. Описание этого языка содер- жится в сообщении рабочей группы DBLTG ассоциации CODASYL [71. Сводка важнейших операций этого языка дана в табл. 7-12.2. Два важных термина, появляющихся в табл. 7-12.2, следует объяснить подробно. Текущей реализацией обрабагпыеаемой единицы называется запись, к которой было самое последнее обращение при выполнении программы, использующей подъязык данных. Под набором в модели данных понимается некий объект, ха- рактеризующийся определенным типом записи в качестве своего владельца и записями других типов в качестве своих членов. На- пример, мы можем определить S—SP как набор, в котором тип записи SUPPLIER (ПОСТАВЩИК) является его владельцем, а тип записи SP (изделие поставщика) — членом набора. Набор является объектом, несущим информацию, определяющую связи между двумя типами записей. Модель данных на рис. 7-12,5, а 765
Таблица 7-12.2 Команды подъязыка данных для сетевой системы баз данных Операция Семантика FIND Локализует и устанавливает существующую реализа- цию записи как текущую активную обрабатываемую единицу GET Выбирает текущую реализацию обрабатываемой еди- ницы STORE Создает новую реализацию записи и устанавливает ее как текущую реализацию обрабатываемой единицы MODIFY Обновляет текущую реализацию обрабатываемой еди- ницы INSERT Включает текущую реализацию обрабатываемой еди- ницы в один или несколько реализаций наборов DELETE Удаляет текущую реализацию обрабатываемой единицы REMOVE Удаляет текущую реализацию обрабатываемой единицы из одного или нескольких реализаций наборов незавершена, так как в ней отсутствуют определения наборов, описывающих показанные на рис. 7-12.5, б связи между типами записей. Поэтому для завершения модели данных мы должны включить в нее определения наборов данных, аналогичные тем, которые приведены на рис. 7-12.6. Полная модель данных, полу- чаемая комбинацией моделей, представленных на рнс. 7-12.5, а и 7-12.6, иногда называется схемой (термин, используемый DBLTG). В общем случае схема может быть определена как любая таблица, содержащая все типы элементов данных и все типы записей, хра- нимых в базе данных [26]. Следует подчеркнуть, что данное определение схемы позволяет почувствовать только дух поня- тия, формально определенного DBLTG, и для точного описания читатель должен обратиться к ссылкам в библиографии. Теперь SET S-SP; MODE IS CHAINED; ORDER IS SORTED; OWNER IS SUPPLIER; MEMBER IS SP; ASCENDING KEY IS. PART?? IN SP - WITH DUPLICATES NOT ALLOWED; SET P-SP; MODE IS CHAINED; ORDER IS SORTED; OWNER IS PART; MEMBER IS SP; ASCENDING KEY IS SUPPLIER# IN SP WITH DUPLICATES NOT ALLOWED; Рис. 7-12.6. Определения наборов для s—SP и P— SP 766
вернемся к ответу на два вопроса, которыми мы занимались в пре- дыдущем пункте, применительно к только что описанным модели (или схеме) и подъязыку данных. В о п р о с 1. Найдите номера поставщиков, поставляющих изделие РЗ. МОТЕ 'РЗ' ТО PART# IN PART. FIND PART RECORD. FIND FIRST SP RECORD OF P-SP SET. IF ERROR-STATUS — 0307 (конец реализации набора) GO TO ENDING. AGAIN. GET SP. (Добавление номера, содержащегося в элементе SUPPLIER # записи SP, в список номеров поставщиков) FIND NEXT SP RECORD OF P-SP SET. IF ERROR-STATUS = 0307 (конец реализации набора) GO TO ENDING GO TO AGAIN. ENDING. Вопрос 2. Найдите номера изделий, поставляемых постав- щиком S2. MOVE 'S2' ТО SUPPLIER # IN SUPPLIER. FIND SUPPLIER RECORD. FIND FIRST SP RECORD OF S-SP SET. i IF ERROR-STATUS — 0307 (конец реализации набора) GO TO ENDING. AGAIN. GET SP. Добавление номера, содержащегося в элементе PART # записи SP, в список номеров поставщиков) FIND NEXT SP RECORD OF S-SP SET. IF ERROR-STATUS = 0307 (конец реализации набора) GO TO ENDING. GO TO AGAIN. ENDING. ... Ответы на вопросы формулируются на подъязыке данных, ко- торый включен в базовый язык, подобный КОБОЛу. Например, инструкция MOVE выполняет в КОБОЛе операцию присваива- ния. Должно быть очевидно, что эти программы полностью сим- метричны и, таким образом, отражают симметрию двух вопросов. .Следовательно, отсутствуют основные недостатки иерархического подхода. Эта симметрия может быть реализована без дублирова- ния больших объемов данных (дублируются только элементы РАДТф|: и SUPPLIERS). Заметим, что мы можем также добавить нового поставщика, скажем S7, без генерации фиктивной инфор- мации об изделии. Первоначально для нового поставщика связи отсутствуют; его цепочка состоит из единственного указателя из записи SUPPLIER на саму себя. Мы можем удалить все изделия некоторого поставщика без удаления самого поставщика, и изме- нение спецификации отдельного изделия повлечет только одно изменение, а именно в элементе SIZE (СПЕЦИФИКАЦИЯ) за- писи PART (ИЗДЕЛИЕ), 767
Возможно, основным недостатком сетевого подхода является необходимость описания связей, существующих между записями посредством использования цепочек. Как мы уже ранее видели, указатели отражают главным образом структуру хранения, и, следовательно, можно показать, что сетевая модель до некоторой степени является моделью структуры хранения. Таким образом, имеется опасность, что пользователь окажется в плену определен- ной структуры хранения [81, а этоТпротиворечит принципу не- зависимости данных. 7-12.4. Реляционный подход Реляционная система баз данных может быть описана как система, в которой пользователь представляет базу данных в виде ряда связанных между собой «плоских» файлов, или таб- лиц. Рис. 7-12.2, например, изображает базу данных в виде одной большой таблицы, в которой комбинация элементов SUPPLIER# (НОМЕР ПОСТАВЩИКА) и PART# (НОМЕР ИЗДЕЛИЯ) однозначно идентифицирует строку таблицы. Реляционный подход имеет в своей основе математическую теорию отношений. Термин отношение может быть определен следующим образом. Даны множества Dx, D2. ..., Dr. (необязатель- но взаимноразличные), R является отношением на этих множест- вах, если имеется множество упорядоченных кортежей из п эле- ментов (dlt d.2, ..., dn), таких, что dr принадлежит Dlf d2 принад- лежит ..., и 4ч принадлежит DM где множества Dt, D2, ... ...,Dn — домены отношения R. При этом R называют1 отноше- нием степени п. Таблица, представляющая отношение в базе данных, обладает некоторыми важными свойствами: 1. Никакие две ее строки (т. е. кортежи из п элементов) не могут быть идентичными. 2. Порядок строк несуществен. 3. Порядок столбцов несуществен. Обычно основной ключ отношения выделяется подчеркиванием и помещается в первом столбце таблицы. Однако в некоторых слу- чаях (как на рис. 7-12.2) основной ключ может состоять из более чем одного домена и, следовательно, не может появляться в первом столбце. Последним важным свойством отношения является то, что каж- дый элемент данных в своем домене должен быть атомом (т. е. по- добно целому числу или литерной строке не должен делиться на меньшие части). Если отношение обладает этим и тремя ранее перечисленными свойствами, то отношение называется нормализо- ванным. Говорят, что нормализованное отношение находится в первой нормальной форме (1NF). Это понятие введено Коддом [61- Или n-арным отношением. — Прим. пер.
Рис. 7-12.7. Функциональные зависимости для задачи и поставщиках и изделиях При использовании отношений в первой нормальной форме существуют две проблемы. Одна из них вызвана явной избыточ- ностью данных (например, сколько раз в отношении встречается 'S3'?). Как следствие, изменение отдельного неключевого элемента, например CITY (ГОРОД), требует значительных усилий по поиску в большой базе данных для гарантии того, что изменены все реа- лизации CITY конкретного поставщика. Вторая, и потенциально более 'серьезная, проблема относится к некоторым неполным функ- циональным зависимостям, допустимым для отношений 1NF. Мы говорим, что домен У функционально зависит от домена X, где X и Y являются доменами отношения R, если, и только если, в каждый момент времени каждому значению из X соответствует только одно значение из Y. Например, SNAME (ИМЯ ПОСТАВ- ЩИКА) функционально зависит от SUPPLIER# (НОМЕР ПО- СТАВЩИКА)- Домен Y является полностью функционально за- висимым от домеиа X, если он функционально зависит от X и не зависит функционально от любого подмножества X (где X может являться композицией доменов). Например, QTY (КОЛИ- ЧЕСТВО) полностью функционально зависит от комбинирован- ного домена PART#-SUPPLIER# (ИЗДЕЛИЕ-ПОСТАВЩИК); однако CITY не является полностью функционально зависимым от PART#-SUPPLIER#, поскольку он функционально зависим от SUPPLIER#, но не от PART# (НОМЕР ИЗДЕЛИЯ). Эти функциональные зависимости иллюстрируются на рис. 7-12.7. Поскольку не все домены в отношении, показанном на рис. 7-12.2, полностью функционально зависимы от комбиниро- ванного ключевого домеиа PART#-SUPPLIER#, то при ведении нашей базы данных возникают некоторые проблемы. Во-первых, мы не можем добавить в базу данных информацию о том факте, что определенный поставщик находится в определенном городе с определенным временем поставки до тех пор, пока поставщик не поставит хотя бы одно изделие. Добавление нельзя сделать из-за того, что не существует соответствующего основного ключа. Аналогичным образом, если мы удалим все реализации изделий некоторого поставщика, то необходимо удалить и информацию, связанную с поставщиком, поскольку для поставщика с этим но- мером не останется основного ключа. z Для смягчения этих аномалий Кодд [61 предложил формули- / ровку отношений во второй нормальной форме (2NF). Нормализо- 769
Отношенье пня постиБщина SUPPLIER# SNAME CITY . SI Black New York sz l.ec Toronto S3 waters Chicago S3 Dyck St Louis S3 Jones Montreal S6 Whyte Los Angeles PART# PHAME PSI ZE Р/ PZ P3 P4 PS Pt7 Гайт болт 6<3/!tn Винт Вручена JSesdowq 7/4-2 i'4-t //4-7 2 4 Отношение SP SUPPLIER# PART# QTY DELVRYJIME SI Si S2 S2 S3 S3 S3 S3 S't ss S3 S3 SO- SO se p; P2 PS Рб pf P2 P3 PC P4 P4 РГ Pf Pl P2 P3 I I <. 0 >! 3 1 2 2 2 1 1 3 1 7. 2 7 а) Рис. 7-12.8. Представление базы данных для задачи о поставщиках и изделиях во вто- рой нормальной форме: а — собственно база данных, б — функциональные зависимости s базе данных ванное отношение находится во второй нормальной форме, если, и только если неключевые домены в R являются полностью функ- ционально зависимыми от основного ключа R. Преобразование нашей исходной базы данных, изображенной на рис. 7-12.2, в три отношения 2NF показано на рис. 7-12.8, а, а функциональные зависимости, существующие для этих трех отношений, представ- лены на рис. 7-12.8, б. Очевидная трудность, возникающая при использовании второй нормальной формы, связана с тем, что полная функциональная зависимость должна существовать только между неключевыми доменами и доменом, служащим основным ключом. Что можно в тачом случае сказать о функциональных зависимостях, сущест- вующих между неключевыми доменами? Возникнут ли проблемы, если такая функциональная зависимость имеет место? Изменим на рис. 7-12.8, а для изделия РЗ, поставляемого S3, значение элемента DELVRY____TIME (ВРЕМЯ ПОСТАВКИ) иа 1 вместо 2; тогда можно доказать, что неключевой элемент DELVRY__TIME функционально зависит только от элемента SUPPLIER:^, яв- 770
ляЮщегося основным ключом и что отношение для поставщика может быть расширено включением элемента DELVRY___TIME, как показано на рис. 7-12.9, а. Разумеется, элемент DELVRY_ TIME должен быть устранен из отношения SP (ПОСТАВЩИК— ИЗДЕЛИЕ). Заметим только, что теперь DELVRY____TIME за- висит от CITY, и мы имеем функциональную зависимость между двумя неключевыми элементами. Может оказаться желательным выразить эту новую зависимость без элемента SUPPLIER Это означает, что если новый поставщик собирается поставлять изделия из города VANCOUVER, и DELVRY____TIME для этого города равно 3, то мы должны иметь возможность задать эту за- висимость в системе прежде, чем узнаем имя нового поставщика или его номер. Рис. 7-12.9, б показывает результат дробления от- ношения, приведенного на рис. 7-12.9, а. Кодд указал на проблемы, связанные с идентификацией не- ключевых зависимостей, и предложил третью нормальную форму для отношений [61. Говорят, что нормализованное отношение находится в третьей нормальной форме (3NF), если, и только если, неключевые домены отношения R 1) взаимно независимы и 2) функционально зависят от основного ключа отношения R. Не- ключевые элементы взаимно независимы, если между ними не суще- ствует функциональной зависимости. Следовательно, отноше- ния на рис. 7-12.8, а и 7-12.9, б находятся в третьей нормальной форме, в то время как отношение на рис. 7-12.9, а — во второй нормальной форме. Вследствие способа определения нами нормаль- ной формы можно сделать заключение, что любое отношение, ко- торое находится в третьей нормальной форме, находится также во второй и первой нормальной форме. А любое отношение, за- данное во второй нормальной форме, находится и в первой нормаль- SUPPLIER# SNAME CITY DELVRY. TIME sr 52 S3 54 S3 sc Black Lee Waters DycK Jones Whyte New York Toronto Chicago St Louis Montreal Los Angeles 2 1 1 3 1 2 SUPPLIER# SNAME CITY SI SZ S3 Si S5 S6 Black Lee Waters Dyck Jones Whyte New York Toronto Chicago St Louis Montreal Los Angeles CITY DEl-VRY- TIME New York Toronto Chicago St Louis Montreal Los Angeles Vancouver / Z '7 2 < J рис. 7 12.9. Генерация новых отношений для поставщика в случае функциональной зависимости DELVRY_____TIMF от CITY 771
ной форме. Таким образом, имеет место иерархия нормальных форм. ' Рассмотрим теперь подъязык данных для реляционного под- хода. В работе [6 ] предложены два типа языков: алгебра отноше- ний и исчисление отношений. Мы коснемся только исчисления от- ношений. Исчисление отношений является непроцедурным языком, базирующимся на утверждениях исчисления предикатов. (Исчис- ление предикатов обсуждается в работе 135}). Эти утверждения включают кванторы существования и общности. Для того чтобы дать представление об этом языке, мы приведем несколько примеров для отношений на рис. 7-12.8, а, заданных в третьей нормальной форме. Во-первых, мы ответим на два вопроса, поставленных ра- нее в этом параграфе. Вопрос 1. Найти номера поставщиков изделия РЗ. {SP.SUPPLIER # : SP.PART # « ’РЗ’} Таким образом, здесь сформулировано, что мы хотим получить для отношения SP те значения SUPPLIER:}!, для которых соот- ветствующие значения PART# равны РЗ. Выражение слева от двоеточия указывает иа то, что требуется получить (двоеточие чи- тается: «такое, что»). Выражение справа от двоеточия является предикатом. Если условие, заданное предикатом, удовлетворено, то возвращается соответствующая информация, определяемая ле- вой частью утверждения. Вопрос 2. Найти номера изделий, поставляемых поставщиком S3. {SP.PART # : SP.SUPPLIER # - ’S2’} Это предложение симметрично тому, которое было дано для ответа на вопрос 1. Некоторые более сложные запросы также легко фор- мулируются в языке исчисления отношений. Например: Вопрос 3. Для каждого поставщика иайти номера изделий и города, из которых могут быть получены изделия. {(SP.PART #, SUPPLIER.CITY) : SP.SUPPLIER # = SUPPLIER.SUPPLIER #} Вопрос 4. Найти иомера поставщиков из Торонто или Мон- реаля, поставляющих изделия со спецификацией 3/4. {SP.SUPPLIER # : HPART HSUPPLIER (PART.SIZE = ’3/4’ Д PART.PART # = SP.PART Д SP.SUPPLIER # = SUPPLIER.SUPPLIER # A (SUPPLIER.CITY -= ’TORONTO’ V SUPPL1ER.CITY -= ’MONTREAL’))} 772
Вопрос 4 показывает, что запросы могут быть очень сложными. Попробуйте-ка сформулировать такой же запрос на подъязыках для иерархической или сетевой баз данных! Так как язык, основанный на исчислении отношений, является непроцедурным (т. е. не требует описания действий шаг за шагом), мы не описывали операций, с помощью которых осуществ- ляется получение информации из заданных отношений. Необхо- димый для этого набор алгебраических операций (объединение, проекция и деление) является частью средств, имеющихся в язы- ках, основанных на алгебре отношений. Детальное обсуждение этих операций выходит за рамки книги, подробные сведения чи- татель может найти у Дейта [8]. 7-12.5. Заключение Один из главных выводов, который можно сделать в этом параграфе, заключается в том, что система баз данных со- стоит из данных об объектах и описаний связей между этими объ- ектами. В иерархическом подходе эти связи явным образом огра- ничиваются относительными положениями данных об объектах, или сегментов при определении записи. Например, изделие может быть старшим или подчиненным объектом по отношению -к по- ставщику, в зависимости от того, как определена модель данных. При сетевом подходе связи проявляются явно посредством ссы- лок. Объект может быть старшим, подчиненным или не связанным с другим объектом в зависимости от присутствия или отсутствия указателя. В реляционном подходе связи также представляются явным об- разом; однако и связи н сами данные об объектах присутствуют в отношениях (т. е. н те, и другие' рассматриваются как объекты того же типа). Следовательно, можно обеспечить единообразный взгляд иа базу данных (т. е. на объекты и иа отношения между ними) без рассмотрения деталей храпения или доступа, таких как исходный узел или указатель. Какой подход является более предпочтительным, для нас в дан- ный момент ие имеет особого значения — все подходы использо- вались и будут использоваться в дальнейшем. А что действительно представляется важным, так это то, что широкий набор структур данных и ассоциированные с ними структуры хранения, которые мы обсуждали в этой книге, имеют самое непосредственное отно- шение к области баз данных. Даже из этого краткого введения в си- стемы баз данных становится очевидным, насколько важны для их понимания такие структуры данных, как деревья, графы и ассоциативные структуры (наряду со множеством различных струк- тур файлов). Книга дает хорошую основу для дальнейшего изу- чения этой сравнительно новой и развивающейся области. 773
Список литературы 1. Abramson N. Information Theory and Coding. New York; McGraw-Hill, 1963. 2. Bensoussan A. Overview of the Locking Strategy in the File System. — Multics Systems Programmers Manual. Section B. G.' 1900, November, 1969, pp. 1—10. 3. Buchholz Werner. File Organization and Addressing. — IBM Systems Journal, vol. 2, June, 1963, pp. 86—110. 4. CODASYL Data Description Language Committee. — Journal of Development, June, 1973. 5. Codd E. F. A Relational Model of Data for Large Data Banks. — Com munications of the ACM,-vol. 13, No. 6, 1970, pp. 377—387. 6. Codd E. F. Further Normalization of the Data Base Relational Model. — Data Base Systems, Courant Computer Science Symposia Series, vol. 6, Engle- wood Cliffs; Prentice—Hall, 1972. 7. Data Base Task Group of CODASYL Programming Language Committee Report. April, 1971 (Availabl from ACM.) 8. Date C. J. An Introduction to Database Systems. Reading; Addison Wes- ley 1975. 9. Deutscher R. F., Sorenson P. G., Tremblay J. P. Distribution Depen- dent Hashing Functions andThier Characteristics. Proceedings of the International Conference of the Management of Data. AC Mf SIG MOD. May 14—15, 1975, San-Jose, pp. 224—236. - 10. Fotheringham J. Dynamic Storage Allocation in the Atlas Computer, Including an Automatic Use of Backing Store, — Communications of the ACM, vol. 4, October, 1961, pp. 435—436- 11. Грис Д. Конструирование компиляторов для цифровых вычислительных машин. М.: Мир, 1975. 544 с. 12. Hughes J. К. PL/1 Programming. New York; Wiley & Sons. 1973. 13. IBM System/360 Operating System PL/1 (F) Language Reference Manual. IBM Form No. GC28—8201. 14. IBM System/360 Operating System PL/ 1(F) Programmer’s Guide. IBM Form No. GC28-6594. 15. Information Management System/360 Version 2 General Information Manual. IBM Form No. GH20—0765. 16- Information Management System/360 Version 2 Utilities Reference Ma- nual. IBM Form No. SH20—0915. 17. Introducing the IBM 3600 Finance Communication System. IBM Form No. GA27—2764. 18. Kindred A. R. Data Systems and Management Englewood Cliffs; Pren- tice-Hall, 1973. 19. Кнут Д. Искусство программирования для ЭВМ. Т. 3: Алгоритмы сор- тировки и поиска. Пер с англ. М.: Мир, 1978. 844 с. 20. Lefkovltz D. File Structures for On-line Systems. New York; Spartan Books, 1969. 21. London K. R. Techniques for Direct Access, Philadelphia; Auerbach 1973. 22. Lum V. Y. General Performance Analysis of Key-to-Address Transfor- mation Methods Using an Abstract File Concept — Communications of the ACM, vol. 16, No. 10, 1973, pp. 603—612. 23. Lum V. Y., Yuen P. S. T., Dodd M. Key-to-Address Transform Techni- ques: A Fundamental Performance Study on Large Existing Formatted Files, — Communications of the a ACM, vol. 14. N. 4, 1971, pp. 228—239. 24. Mackenzie F. B. Automated Secondary Storage Management. —Data- mation vol. 11, 1965, pp. 24—28. 25. Мэдник С., Донован Дж. Операционные системы. Пер. с англ. М.: Мнр, 1978. 540 с. 26. Мартин Дж. Организация баз данных в вычислительных системах. — Пер. с англ. М.: Мир, 1978. 616 с. 774
27. Мидоу Ч. Анализ информационно-поисковых систем. Пер. с англ. М.: Мир, 1977. 400 с. 28 Mullin J. К- An Improved Indexed Sequential Access Method Using Hashed Overflow. — Communications of the ЛСЛ1, vol. 15, No? 5, May, 1972, pp. 301—307. 29. OS/VS Virtual Storage Access Method (VSAM) Planning Guide, IBM Form No. GC26—3799. 30 PDP-11 Peripherals Hanbdook, Digital Equipment Corporation, 1975. 31. Philippakis A. S., Kazmler L. J. Information Systems Through COBOL, New York; McGraw Hill Book Company, 1974. 32. Reference Manual for IBM 3330 Series Disk Storage. IBM Form No. GA26 - 1615. 33. SCOpE Indexed Sequential System. Control Data Corporation, CDC— 6O3O54OOA. 34. Shaw A. C. The Logical Design of Operating Systems, Englewood Cliffs, Prentice-Hall, 1974. 35. Tremblay J. P., Manohar R. P. Discrete Ma thematical Structures with Applications to Computer Science, New York; McGraw-Hill, 1975. 36. Цикритзис Д., Бернстайн ф. Операционные системы. М.: Мир. 1977. 336 с. 37. Weinberg G. М. PL/1 Programming: A Manual of Style, New York; McGraw- Hill, 1970.
ПРЕДМЕТНЫЙ УКАЗАТЕЛЬ Адресация абсолютная 80 — косвенная 81 — относительная 81 — прямая 686 — с помощью указателя 77 Адресное пространство 565, 686 Алгебраическое хеш-кодирование 567 АЛГОЛ W 79, 80 АЛГОЛ 68 68, 198 Алгоритм страничного обмена 726 — хеширования 684 Алгоритмы Маркова 86—95, 109 — — непомеченные 86—90 — — помеченные 91—95' —, система записи 21—25, 112—-122, 284—285, 346—347, 616—617 Алфавит 85, 96 Американский стандартный код для обмена информацией {ASCII) 65—66, 70—71, 72 Антецедент 87 Арифметика комплексных чисел 197— 200 —многократной точности 55, 331 - 341 — модулярная 49 — полиномиальная 284—289, 296— 300, 307—309, 319—323, 331—341 — чисел с плавающей точкой 54 — чисел с фиксированной точкой 53 Ассоциативное поле 346 Ассоциативный список 346—351 APL 38, 67 База данных 698 Базовые функции манипулирование строками 115—122 Байт 56 Барабан магнитный 587—591 Бинарный поиск, см. Двоичный поиск Бит 27 — знака 49 — переноса 52—53 — скрытый 61 Битовая строка 184 ---, применение при информаци- онном поиске 184—193 Блок 66, 586—587 — данных 650 Блокирования коэффициент 586 БНФ, см. Бэкуса—Наура форма Бор 563 Булева матрица 436 — сумма 440 Булево произведение 440 Буферизация 713 Бэкуса—Наура форма 97 Венский метод определения языка 97 Вершина графа 354 — ветвления 359 — изолированная 356 — концевая 369 — критическая 556 — левоперевешивающая 556 — начальная 355 — правоперевешивающая 556 — сбалансированная 556 Виртуальный метод доступа (VSAM) 732—738 Восьмеричная система счисления 40 Время доступа 589 — передачи 589 — поиска 589 Вспомогательный символ 89 Вторичное скучивание 579 Выключка 158 Выражение бесскобочное 236 — инфиксное 233, 235 — полностью скобочное 235 — постфиксное 188 — префиксное польское 235 — суффиксное польское ^35 Вычисление выражения 387 Вычислительная машина аналоговая 32 --- гибридная 33 ---цифровая 33 Вычисляемый адрес 77—78 778
Генерация кода из польской записи 246—250 — указателя 427—435 Голова списка 312, 317, 374. 389, 423, 449, 503 — строки 239 Главный индекс 646 Грамматика 96—109 — контекстно-свободная 108 — с фразовой структурой 108 Граничный маркер 144 Граф 353—356 — ациклический 369 — взвешенный 356 — неориентированный 354—355 — ориентированный (орграф) 354 — простой 355 — смешанный 355 Двоичная матрица, см. Булева ма- трица — система счисления 40 Двоично-кодированное десятичное чи- сло (BCD) 58, 59, 69, 70 Двоичный поиск 324, 551—552 Двойное хеширование 579 Двусвязный список 313—318 Действительное число 53 Дек 259 Дерево 282, 359 — бинарное 364 — ориентированное 359 — полное бинарное 364 — полное т-арное 364 — прошитое 374—375 — упорядоченное 361 — эквивалентное 381 Дескриптор строки 144 Десятичная система счисления 39 Динамическое распределение памяти 202 Динамическое управление памятью, .метод «близнецов» 510 ------- —, метод с граничными при- знаками 508 -------, метод «наиболее подходя- щего» 446 ---—, метод «наименее подходя- щего» 446 -------, метод «первого подходя- щего» 446—447, 506 Диаграммы Венна 362 Диск, см. Магнитный диск — электронный 595 — с фиксированными головками 593 Дискретный метод передачи инфор- мации 32 Дисплейный процессор 466—467 Дифференцирование выражения 484— 489 Длина строки 112 Домен 768—769 Дополнение до двух 49 — уменьшенное 51 --- основания 49 Дорожка 587, 591 Дробная часть числа 60, 63 Задержка на вращение 589 Заместитель 127 Замыкание множества 85 Запись 597 — алгоритмов, см. Алгоритмы, си- стема записи — логическая 599 - - нормальная 645 - — относительная 696 — переполнения 645 — переменной длины 608—609 — транзакции 599 — физическая 599 — чисел с плавающей точкой 54 Иерархическая циклическая струк- тура 471 — система баз данных 761—764 Измерение информации 27—28 Индекс дорожек 645 — цилиндров 646 Индексирование типа KWIC 178—184 Индексирующее слово 178 Индексное множество 734 Индексно-последовательный метод до- ступа (ISAM) 644—649 --файл 643—666 ---в системе SCOPE 649—652 ---в языке ПЛИ 669—666 ---, применение 667—683 Индукция 215 Интерактивная графическая сисгема 466 — машинная графика 466 Интерпретация команд редактора 166— 167 Информация 26 Исключение из очереди 256, 317 Источник 29, 458 Итеративный процесс 216—217 Канал передачи 29 Канонический разбор см. Разбор грам- матический Класс эквивалентности 325 Ключ 529, 597 — вторичный 739 — записываемый 697 — исходный 696 — основной 739 — сравнения 697 Ключевое слово 186
Код ASCH, см. ASCH — Бодо 5-битовый телеграфный 71 — BCD, см. BCD — EBCDIC, см. EBCDIC форматный 155 — Хаффмана 602—605 — Холлерита для перфокарт 70 — центрального процессора UNIVAC 69—70 Комплексное число 197 Компьютер 33 Конкатенация 85, 112, 113, 122, 130 — в СНОБОЛе 129 Конкорданция 607—608 Коисеквеит 87 Контекстно-свободная грамматика, см. Грамматика Контрольный интервал 732 Концевая вершина, см. Вершина графа Корень дерева 359 Коэффициент заполнения 565 к— путевое слияние, см. Многопуте- вое слияние Критический путь 461 Курсорная позиция 113 Кусочно-линейная функция 571—572 ----с растеплением интервала 572-573 Латентный период, см. Задержка на вращение Лексический анализатор, см. Сканер Лес 362 Линейное опробование 574—575 ЛИСП 74, 78 Лист дерева 359 Логическая величина 74—76 Логический оператор 75 Магнитная лента 585—587 Магнитный барабан 587—591 — диск 591—594 Мантисса 60 Маркер 89 Массив 194, 201 — треугольный 206 Матрица достижимости 439 — путевая 439 — разреженная 419—427 — расстояний 444 — смежности 432 Машинная графика 465 ----применение 466—484 Межблочный промежуток 586 Метаязык 97 - БНф 97 Метка перехода 128 Метод управления памятью с гра- ничными признаками 508—510 — критического пути (СРМ) 458 — деления 566 780 Методы доступа к внешней пахмятй со вторичными ключами 754—756 Многопутевое слияние 542 Модель итеративной процедуры 216 — рекурсивной процедуры 217 Моделирование системы с разделением времени 26’0—274 — система трафика 274—277 — рекурсии 224—230 Мультиграф 355 Мультисписок с разделением иа секции 748—750 — свернутый 757 — с управляемой длиной списка 746—747 Набор данных, см. Физический файл — символов APL 67—68 Наиболее позднее время выполнения 460 — раннее время выполнения 459 Независимость данных 758 Неопределенность 27 Непрерывный метод передачи инфор- мации 32 Нетерминальный символ 98 Нормализация 60 Нормальная Форма отношения первая (1NF) 768—769 -----вторая (2NF) 769—771 -----третья (3NF) 771 Нуль-строка, см. Пустая строка Область индекса 645 — основная 645 — переполнения независимая 649 и — переполнения цилиндра 648 1 — поиска 592 j Обработка файла последовательная 614 х ----- сериальная 614 £ Образец-переменная 130 3 Образец-структура 130 5 Обход дерева 369—376 ' Односвязный линейный список 280 ! Операции иад стеком 208—214 f -----, вытолкнуть (исключить) d 209---> ------ —, протолкнуть (включить) 208 1 ------- структурами данных 37—38 j -----файлами 616—617 1 Операционная система OS/VS 731 ? -----система MULTICS 731 j Операция альтернации 130 ’1 — ассоциативная 84 > — невычисляемого выражения 137 ' — немедленного присваивания 131 5 — условного присваивания 131 i Опробование случайное 578 | Орграф 282, 354 1 Ориентированное дерево 359 Ориентированный граф, см. Орграф
Основа 101—102 Основание системы счисления 39 Открытая адресация 573 Отношение 768 - нормализованное 768 — эквивалентности 325 Очередь 255 — дек 259 — приоритетная 255, 273—274 — с ограниченным входом 259 с ограниченным выходом 259 — циклическая 257—'258 Ошибка округления 55 Пакет 686 Память 34 — виртуальная 723—731 • — внешняя 34 — на магнитных сердечниках 34—36 — основная 34 — полупроводниковая 34, 36—37 Параметр фактический 78 — формальный 78 Первым пришел — первым обслужи- ваешься (FCFS) 255 Передатчик 29 Передача информации 29—31 — параметров 78—80 Переменная простая 38 Переполнение арифметическое 51 — очереди 256, 258 • — пакета 685—686 — стека 208—209 Пермутациоиное индексирование, см. KWIC PERT 458—465, 489—490 Петля графа 355 Пирамида 537 Подборочная машина 540—541 Подобие арифметических выражений 387 — деревьев 381 Подъязык данных DL/1 762—763 Позиционная система счисления 39 Поиск 550—582 — в хеш-таблице 325—328, 491 — 493, 564—582, 685—689 — двоичный, см. Двоичный поиск — линейный 324, 550—551 — по бинарному дереву 388—391, 553—555 — по бору 563—564 — по образцу 113, 116 — по сбалансированному дереву 556—563 Показатель сбалансированности 556 Поле 195 Польская запись 235—236 Поразрядный анализ 569 Порядок числа 54 Последним пришел — первым обслу- живаешься (L1FO), см. Стек Последовательность Фибоначчи 511 Прагматика 30—31 Предложение языка 96 Представление грамматики многосвя- зное 455—457 — графа 435—457 — действительных чисел 53—55, 59—64 — дерева последовательное 367, 379—381 — — связанное 367, 368—379 — массивов последовательное 201 — 206 ----связанное 419—427 — ориентированного дерева 362— 363 — разреженных матриц 422—427 — целых чисел 48—53, 56—59 — чисел с фиксированной точкой 63—64 Предупорядочение 551 Преобразование изображения, масшта- бирование 437 ---, перенос 473 ---, поворот 473 — инфиксной записи в польскую 239—246 - системы счисления 41—48 Префиксное свойство кодов Хафф- мана 604 Прибор с зарядовой связью 596 Приемник 29 Примитивные структуры данных 37 -------, действительные числа 53-55 -------, логические величины 74—76 -------, символы 65—68 -------, указатели 76—83 -------•, целые числа 48—53 — функции манипулирования стро- ками 112—115 Проблемная группа по языкам баз данных, см. DBLTG Продукция 87 — грамматическая 97 — марковская 87 — рекурсивная 102—ЮЗ — терминальная 87 Пространство ключей 686 Пустая строка 85. 112 Путь 357 — графа 357 — простой 358 — элементарный 358 Разбор грамматический 99—108 — — восходящий 101, 395, 671 —* — канонический 101 781
-----нисходящий 100—101, 395— 399, 671 Размещение массива по столбцам 202 -----по строкам 202 Ранг выражения 238—239 Распределение памяти динамическое 499—526 ----управляемое 212—213 — последовательное 201, 279, 414 — связанное 280 Расширенный двоично-кодированный десятичный код для обмена информа- цией (EBCDIC) 65—66, 70, 72 Ребро 354 — неориентированное 354 — ориентированное 354 — параллельное 355 Регион файла 696 Регистр 33 — базовый 80 — индексный 81 Редактирование текста 150—172 -----, выключка 158—162 _ -----, добавление 150—151 -----, замена 153—154 — —, оформление заголовков 157 -----, пропуск 157—158 -----, распечатка 151—153 -----, табулирование 155—157 -----, удаление 154—155 -----, форматизованная печать 155—166 Рекурсивная образец-структура 137 — процедура 21.6 — функция 216 Рекурсия 102, 214—230 Реляционная система баз данных 768—773 Решений таблица 400—419 -----полная (с расширенным вхо- дом) 403—404 * -----смешанная (со смешанным вхо- дом) 403- 404 -----сокращенная (с ограниченным входом) 403—404 Сбор мусора 149, 282, 516—526 Свернутая структура 757 Связка, см. Указатель Сегмент 697, 761 Сегментно-страничная система 729— 732 Сектор 587 Секционированная сериальная струк- тура 750 Семантика 30 Семейство ЭВМ В 5000 фирмы Burr- oughs 56, 62, 69, 82 -----фирмы IBM 56, 62, 69, 82 ----- 5600 фирмы CDC 56, 62, 69, 82 782 ----2100 фирмы Hewlett—Packard 56, 62, 69, 82 Сентенциальная форма 101 Сжатие ключа 734 Символ 65 Символическое дифференцирование 484—489 Символьная строка 68 Синтаксис 30 Синтаксический анализ, см. Разбор грамматический — анализатор 105 Синтаксическое дерево 99 Система баз данных 758—773 -------иерархическая 761—764 — -——реляционная, см. Реляци- онная система баз данных -------сетевая 764—768 — оплаты счетов 629—643 — с виртуальной памятью 723—724 — со страничной памятью 724—727 — счисления 39 ----восьмеричная, см. Восьмерич- ная система счисления ----двоичная, см. Двоичная си- стема счисления ----десятичная, см. Десятичная система счисления ------ позиционная, см. Позицион- ная система счисления ---шестнадцатеричная 40 — управления базой данных 760 ---информацией (IMS) 761 — учета успеваемости студентов 667—683 Системы сегментации 727—729 Сканер 78, 173 Скучивание вторичное 577 — первичное 579 Словарь 98 Случайное опробование, см. Опробо- вание случайное СНОБОЛ 95, 127—141 —, операция альтернации, см. Опе- рация альтернации —, — конкатенации 129 —, — невычисляемого выражения, см. Операция невычисляемого выра- жения —, — немедленного присваивания, см. Операция немедленного при- сваивания —, — условного присваивания, см. Операция условного присваивания —, функция BREAK 133*—134 — , — DUPL 135 — , — LEN 132—133 — , — POS 135 — , — SIZE 135 — , — SPAN 133
— TAB 134 —, — TABLE 348 Содержательное поле, см. Ассоциа- тивное поле Сопоставление с образцом, см. Поиск по образцу Сопоставляющая последовательность 115, 529 Сортировка 528—582 — быстрая 534—536 — вычисление адреса элемента 548—549 — к-путевая 542 - методом «пузырька» 532—534 — на деревьях 536—540 — обменная с разделением 534—536 — пирамидальная 536—540 — поразрядным группированием 545—548 — слиянием 540—545 — способом выбора 530—532 — топологическая 489—498 Список 444—454 — ассоциативный 345—351 — инвертированный секциониро- ванный 750 — линейный 195 — линейный связанный 195 — циклически связанный 311 Способы устранения переполнений 573—582 Ссылка, см. Указатель Стек 207 — аппаратный 252 —, применение 214—250 Стековые машины 251—254 Степень вершины полная 357 Страница 724 Страничный кадр 724 Стратегия замены сегментов 728 Строка 85 Строковое пространство t45 Структура данных 19 ----- бора 563 -----графа 435—436, 454—457 -----действительных чисел 59—64 ----- деревьев 367—383 -----комплексных чисел 198—199 -----линейного списка 280, 289 -----логических данных 75—76 ----массивов 201—206, 420—427 -----очередей 255—259, 273—274, 312, 317 -----символов 68—69 — — списков 448—450 -----стека 208, 212—214, 290—291 -----строк 142—149 -----указателей 80—81 — — целых чисел 56—59 — Многосвязная 419—435, 472. 476—484 — файловая 20 — хранения 19 Схема 766 — индексирования 178 ---типа KWIC 178—183 Таблица 528—529 — с прямым доступом 565 — страниц 725 — решений, см. Решений таблица Теория информации 27 Терминальный символ 98 Транзакция 599 Трансляция таблицы решений 400—419 Триггер 36 Узел списка 279, 448 -449 Указатель 76, 284 Упакованное десятичное число 59 Управление памятью методом «близ- нецов» 510 -------«наиболее подходящего» 504 -------«наименее подходящего» 505 -------«первого подходящего» 504 — переполнением 574—582 Уровень вершины 359 Устройства внешней памяти 34, 585— 596 — массовой памяти 594 — на магнитных картах 594—595 — промежуточной памяти 595—596 — прямого доступа 588 — с перемещающимися головками 589 — с фиксированными головками 589 Файл 597 — индексно-последовательный 643— 666 — логический 599 — последовательный 610—626 — прямого доступа (прямой) 584— 707 — региональный 694 — с инвертированным списком 750 — физический 599 — VSAM 732—738 формат «счетчик-данные» 695 — «счетчик-ключ-данные» 695 ФОРТРАН, см. Язык ФОРТРАН фрагментация внешняя 502, 728 — внутренняя 502, 729 Фраза 102 Функция адресации 196—197 — Аккермана 216 — факториал 215 — хеширования 197, 325, 493, 564— 574, 684—688 GCD 221—222 — MOD 222, 327 783
Ханойская башня 223—230 Характеристика числа 60 Хеш-функция, см- Функция хеши- рования Хранение информации 33—37 Цикл 358 — простой 358 — элементарный 358 Цилиндр 592 Четность ряда 144 Чип 36 Эквивалентность бинарных деревьев 381 Элемент вторичный индексный 739 Электронно-лучевая трубка (ЭЛТ) 466 Эффективность кода 69 Язык 96 — АЛГОЛ W 78, 80 — АЛГОЛ 68 68, 198 > ассемблера 246—247 — ЛИСП 74, 78 — манипулирования данными (DML) 765 — СНОБОЛ 68, 78, 86, 127—141 — управления заданиями (JCL) 626, 6G5. 699 — ФОРТРАН 37, 68—70, 74, 79, 86 — APL 38, 67 - СОМ1Т 86 — MAD-1 198 ASCII 65, 66, 69—70, 72 BCD 58, 70 CODASYL 765 DBLTG 765, 766 DBMS 760 DL/1 762 DML 765 EBCDIC 65, 66, 69—70, 72 ЕТЕХТЕ 150—172 FCFS 255 FIFO 255 GRAPHIC 2 470 IMS 761 JCL 626 KWIC 178—184 LIFO 207 MULTICS 731 SCOPE 649—652 SKETCHPAD 471 VSAM 732—738 ИБ № 2756 ж. Трамбле, П. Соренсон ВВЕДЕНИЕ В СТРУКТУРЫ ДАННЫХ Редакторы Е. В. Григории-Рябова, Т. В Абизова Художественный редактор С. С. Водчиц Технические редакторы Т. С. Старых и Е- П. Смирнова Корректоры И. М, Борейша и А. М. Усачева Оформление художника Г. Г. Кожанова Сдано в набор 17.06.81. Подписано в печать 18.01.82. Формат 6ОХ9О’/м- Бумага типографская № 2. Гарнитура литературная. Печать высокая. Усл. печ. л. 49.0. Уч.-изд. л. 54,6. Тираж 4000 экз. Заказ 603. Цена 3 р. 90 к. Ордена Трудового Красного Знамени издательство «ДАаши нестроение? 107076, г. Москва, Б-76, Стромынский пер., д. 4. Ленинградская типография № 6 ордена Трудового Красного Знамени Ленинградского объединения «Техническая книга» им. Евгении Соколовой Союзполиграфпрома при Государственном комитете СССР по делам из- дательств, полиграфии и книжной торговли. 193144, г. Ленинград, ул. Моисеенко, 10.