С. ОКУЛОВ
о с н н в ш
Москва Л а б о р а т о р и я Базовых|Эй6РЬМ" 2 002
УДК 519.85(023) Б Б К 22.18 О 52 О 52
Окулов С. М. Основы п р о г р а м м и р о в а н и я . — М.: ЮНИМЕДИАСТАИЛ, 2002. — 424 е.: ил. ISBN 5-94774-003-6
В учебнике рассмотрены основные управляющие конструкции системы программирования Турбо Паскаль, процедуры н функции, строковый, вещественный и файловый типы данных. Приводится материал для изучения массивов, методов сортировки и поиска, а также по динамическим структурам данных Рассмотрены следующие структуры данных: списки, стеки, очереди, двоичные деревья, АВЛ-деревья и Б деревья. В материалах для чтения обсуждаются практически все вопросы, входящие в школьный минимум знаний по информатике Книга является достаточно полным учебником по программированию, реализующим сложную задачу — формирование у читателя структурного стиля мышления Учебным материалом является система программирования Турбо Паскаль, а также большое число задач, включая задачи иа алгоритмы сортировки и поиска Достаточно подробно рассмотрена работа с динамическими структурами данных. Книга рассчитана на широкий круг читателей от школьника и студента до специалиста, решающего с помощью программирования прикладные задачи. УДК 519.85(023) ББК 22.18 Серия «Технический университет» Учебное издание Окулов Станислав Михайлович Основы программирования Художник Н Лозинская Компьютерная верстка Л Катуркиной Лицензия на издательскую деятельность №066140 от 12 октября 1998 г. Подписано в печать 30.01.02. Формат бОхЭО1/,,. Гарнитура Школьная. Бумага офсетная. Печать офсетная. Усл. печ.л. 26,5. Тираж 5000 экз. Заказ 452 ООО «Издательство Лаборатория Базовых Знаний», 2002 г. Адрес для переписки: 103473, Москва, а/я 9. Телефон (095)955-0398. E-mail: lbz@aha.ru Гигиеническое заключение 77.99.1.953.П.1815.4.99 от 12.04.1999 г. Отпечатано с готовых диапозитивов в полиграфической фирме «Полиграфист». 160001, г. Вологда, ул. Челюскинцев, 3.
ISBN 5-94774-003-6
© Окулов С. М., 2002 © ЮНИМЕДИАСТАЙЛ, 2002
Содержание
Содержание Предисловие Часть первая. О с н о в н ы е у п р а в л я ю щ и е конструкции Занятие Занятие Занятие работа с Занятие Занятие Занятие Занятие Занятие Занятие
№ 1. Первая программа № 2. Целый тип данных № 3. Команды редактора для работы с блоками, окнами № 4. Логический тип данных, операции сдвига. . . . № 5. Составной оператор и оператор If № 6. Оператор For № 7. Оператор While № 8. Оператор Repeat-Until № 9. Вложенные ц и к л ы
Часть вторая. П р о ц е д у р ы и ф у н к ц и и — э л е м е н т ы структуризации програм
3 5 10 10 20 28 37 46 56 66 73 84 99
Занятие № 10. Одномерные массивы. Работа с элементами Занятие № 11. Процедуры Занятие № 12. Функции Занятие № 13. Рекурсия Занятие № 14. Символьный и строковый типы данных . . . . Занятие № 15. Вещественный тип данных Занятие № 16. Текстовые файлы
99 115 133 149 167 186 202
Часть третья. М а с с и в — ф у н д а м е н т а л ь н а я структура д а н н ы х
220
Занятие №17. Методы работы с элементами одномерного массива Занятие № 18. Множественный тип данных Занятие № 19. Методы сортировки Занятие № 20. Методы быстрой сортировки Занятие № 21. Поиск данных Занятие № 22. Двумерные массивы. Работа с элементами Занятие № 23. Двумерные массивы. Вставка и удаление
220 234 243 252 266 288 302
Осповы программирования
Занятие № 24. Несколько задач на технику работы с двумерными массивами
311
Занятие № 25. Комбинированный тип данных (записи). . . . 323 Часть четвертая. Д и н а м и ч е с к и е с т р у к т у р ы данных
335
Занятие Занятие Занятие Занятие Занятие Занятие
335 350 360 373 384 399
№ № № № № №
26. 27. 28. 29 30. 31.
Заключение
Динамические структуры данных Стек Очередь Поиск в графе Двоичные деревья Сбалансированные деревья
421
Предисловие
Моему
отцу — Окулову Михаилу Митрофановичу, рано ушедшему из этой жизни, посвящаю.
Из истории возникновения учебника. В 1988 году перед автором возникла проблема: «чему учить в информатике и как учить». Исходным «багажом» при принятии решения было образование в классическом университете по специальности прикладная математика и более чем 15-летний опыт разработки программного обеспечения специализированных вычислительных комплексов в промышленности. Попытки обучения по существующим в то время учебникам, а их было не так много, как в настоящее время, не принесли ни удовлетворения, ни результатов. Информатика воспринималась как обычный предмет. Дидактические возможности компьютера «оставались за бортом». Может быть, причиной явилось отсутствие у автора соответствующего опыта и педагогического образования. Ограничимся констатацией факта, оставим критику этого результата, так же как и обсуждение достоинств и недостатков существующих учебников, доброжелателям. Тезисно обозначим исходные положения при принятии решения. Первой посылкой было убеждение в том, что программирование является «стержнем информатики». В нем синтезировано все, что десятилетиями нарабатывалось в Computer Science. Это и результаты работы специалистов, работающих на «стыке» математики и информатики, это и достижения в вычислительной технике, это и, наконец, огромный опыт формализации и решения сложнейших проблем в самом программировании, связанный с созданием больших программных комплексов. Второй исходной посылкой являлось убеждение в том, что занятия по информатике в корне должны отличаться от традиционных занятий по любому другому предмету. Во-первых, на з а н я т и я х по информатике должна поощряться ошибка, ибо только через ошибку можно прийти к результату, при изучении же любого другого предмета ошибка карается двойкой. Во-вторых, постоянная обратная связь с обучаемым через компьютер, объективная и лишенная эмоций, — это инструментарий индивидуального и развивающего обучения. В-третьих, стиль мышления у программистов свой, отличающийся от стиля м ы ш л е н и я к а к математика, так и любого другого специалиста. Он настроен, если так можно выразиться, на бо-
Осповы программирова
рьбу с хаосом. Любая сложная программа — это миллионы составляющих, движущихся и взаимодействующих. И в результате этого взаимодействия должен получаться определенный результат. Представим себе техническую систему такого уровня сложности ... Итак, за основу обучения следует взять программирование, с максимальным использованием компьютера на занятиях, и при этом должен формироваться определенный стиль мышления. В таком ключе и шла вся последующая работа. Большое влияние на нее оказала подготовка школьников к олимпиадам по информатике. Синтезировались и обобщались все педагогические находки, которые затем находили применение в преподавании информатики и подтверждали обозначенные выше положения. Основной методический принцип учебника — все познается через труд, через преодоление ошибок (собственных), через процесс решения задач. Этот принцип определяет структуру занятий. Вводная часть - обсуждение нового материала, эксперименты с заготовками решений задач, самостоятельное решение задач. Данный учебник нельзя считать продуктом творчества одной личности. Коллеги по работе в Вятском государственном педагогическом университете и физико-математическом лицее г. Кирова сделали много для того, чтобы работа была завершена. Влияние моих учеников на осмысление того, как учить и чему учить, просто огромно. Я благодарен им за совместную работу. В первой части учебника изучаются основные управляющие конструкции языка программирования Турбо Паскаль, но не только. Целевой установкой этих занятий было конструирование решения задач из минимального числа инструкций. В процессе занятий необходимо достичь такого уровня их понимания, чтобы работа программы школьником воспринималась в динамике. Ему необходимо «видеть» эту работу, динамику изменения значений переменных в процессе работы программы. С этой целью в начале применяется «ручная» прокрутка программы, а затем — средства отладчика системы программирования. В конечном итоге, использование этих инструкций учащимися должно осуществляться не по заученным схемам, а так же естественно, как применение русского языка. Учащемуся нужно как бы забыть об их существовании, писать их автоматически. Таким образом закладывается первый «кирпичик» в фундамент структурного стиля мышления.
Предисловие
7
Вторая часть учебника п о с в я щ е н а механизму использования процедур и ф у н к ц и й , создания «блоков» л о г и к и с одной точкой входа и одной точкой выхода. При этом взаимодействие по данн ы м д о л ж н о осуществляться по заданным п р а в и л а м — в соответствии с механизмом передачи параметров. Пересечение «блоков» к а к по у п р а в л е н и ю , т а к и по д а н н ы м д о л ж н о быть минимальным. Материал, т а к ж е к а к и в первой части учебника, разбит на з а н я т и я . Что под этим понимать? Это материал не д л я одного урока. Автор не дает р е к о м е н д а ц и й по этому поводу. Все зависит от у р о в н я подготовки ш к о л ь н и к о в . Приведу пример. Одну из п р о г р а м м четвертого з а н я т и я ему приходилось объяснять и 5 минут и целое з а н я т и е (да и того не хватало): Program Begin WriteLn WriteLn WriteLn ReadLn; End.
Му4_3; (1365 (1365 (1365
And 2730) ; HriteLn ^or 2730) ; UriteLn And $FF0) ; WriteLn
(1365 (1365 (1365
Or 2730) ; And $FF) ; And $FF00) ;
В целом, на н а ш взгляд, содержание учебника и стиль излож е н и я соответствуют среднему уровнмю подготовки ученика, хотя и д л я сильного у ч е н и к а в материале к а ж д о г о з а н я т и я есть достойные задачи и проблемы д л я исследования. Третья часть учебника посвящена, в основном, фундаментальному п о н я т и ю и н ф о р м а т и к и — массиву. П р и этом весь материал я в л я е т с я очередным витком в освоении и закреплении целевых установок первых двух частей. Разумеется, задачной основой этой части я в л я ю т с я в основном алгоритмы сортировк и и поиска, ибо д л я д о с т и ж е н и я н а ш е й цели — изучения массива к а к с т р у к т у р ы д а н н ы х , трудно н а й т и что-либо лучшее. З а н я т и е по комбинированному типу данных стоит несколько особняком. Оно не согласуется с целью этой части учебника, но является необходимым для работы с четвертой частью — динамическими структурами данных (последним «кирпичиком» фундамента нашего здания). В четвертой части учебника рассмотрены динамические структуры д а н н ы х : списки, стеки, очереди, двоичные деревья, ABJIдеревья и Б-деревья. Обычно этот материал не затрагивается при работе со ш к о л ь н и к а м и . Однако без него невозможен переход на следующий «виток» обучения программированию. В объектно-ориентированных, визуальных технологиях и т. д. он
Осповы программирования
является одним из стержневых, без которого понимание технологий остается «любительским». Это один аспект. Второй, и весьма немаловажный, заключается в том, что на этом материале «шлифуется» структурный стиль мышления. В задачах этой части учебника без автоматизма в структуризации решений (использования принципа «разделяй и властвуй») обойтись — очень трудно. Еще одна особенность — отладка программ является достаточно сложным мероприятием. Предвидеть и рассмотреть все варианты, случаи работы программы при различных исходных данных очень не просто. Оксфордский словарь английского языка трактует эвристику как искусство нахождения истины. Мы уточним — искусство в убеждении себя самого и окружающих в правильности работы программы. Владение этим искусством при решении задач четвертой части потребуется полное. Итак, подведем итоги. Главная цель учебника — развитие мышления ученика: от алгоритмического к структурному, а затем к эвристическому мышлению. Речь не идет о том, что вначале мы обучаем одному, затем другому и, наконец, третьему. Элементы обучения синтезированы в нечто единое, и те, и другие, и третьи аспекты проблемы развиваются одновременно. Обозначим на уровне общих положений к а ж д ы й стиль мышления. Умение написать алгоритм назовем алгоритмическим стилем мышления. Алгоритм — план будущей деятельности (модель деятельности), записанный в заранее выбранной формальной системе обозначений с ограниченными возможностями. Умение «расчленять» задачу, «разделять и властвовать» назовем структурным стилем мышления. Когда мы говорим о структуре, то обязаны сказать о том, из каких элементов она состоит, и как элементы связаны между собой. Характерные черты этого стиля: простота и ясность; использование только базовых (основополагающих) конструкций; отсутствие многоцелевых функциональных блоков и т. д. Умение находить истину, доказывать факт правильности решения задачи (работы программы) назовем эвристическим стилем мышления. Факторами успешной эвристической деятельности являются умение оценивать, рациональность действий, принцип экономии и т. д. Отметим, что компьютер, система программирования не являются целью обучения, они — инструмент реализации целей, хотя при этом, разумеется, познается, в определенном объеме, и сам инструмент.
Предисловие 9
В з а к л ю ч е н и и хотелось бы еще раз сказать слова благодарности (огромной!) всем моим коллегам и ученикам. Сергею Львовичу Островскому, главному редактору газеты «Информатика», хотелось бы в ы р а з и т ь особую признательность. Его умное и ироничное молчание значило д л я меня иногда больше, чем все ж а р к и е дискуссии по поводу ... Моей д р у ж н о й семье, все члены которой мирились с ежевечерней работой, ибо это единственное время, которое можно было выделить д л я н а п и с а н и я учебника, н и з к и й поклон. Видеть постоянно чем-то озабоченного м у ж а и отца, наверное, сомнительное удовольствие. Но они терпели и терпят, и я благодарен им, и не только за это. Все замеченные ошибки и опечатки автор с благодарностью примет (okulov@vspu.kirov.ru).
Часть первая
Основные у п р а в л я ю щ и е конструкции
З а н я т и е № 1. П е р в а я
программа
План занятия • з а г р у з к а Турбо П а с к а л я ; • структура диалога; • первая программа — н а х о ж д е н и е произведения двух цел ы х чисел; • к о м п и л я ц и я , сохранение и запуск п р о г р а м м ы ; • разбор п р о г р а м м ы . Опишем основные моменты проведения з а н я т и я . Структура диалога. После з а г р у з к и системы на экране поя в л я е т с я три окна: — окно 1 — главное меню — окно 2 — основное, или рабочее, окно — окно 3 — окно помощи, в нем указывается назначение основных функциональных клавиш Переход и з первого окна во второе и наоборот осуществляется п р и п о м о щ и н а ж а т и я к л а в и ш и F 1 0 . Набор программы Program Myl_l; Var a,Ь,rez: Integer; Begin WriteLn ('Введите два числа через пробел'); ReadLn(a,b) ; rez:=а*b; WriteLn (' Их произведение равно ' , rez) ; WriteLn('Нажмите <Enter>'); ReadLn; End.
Основные управляющие конструкции
11
Краткий разбор примера. Структура программы. Программа начинается с заголовка, имеющего следующий вид: Program <имя программы>; за ним идет раздел описаний, в котором должны быть описаны все идентификаторы (константы, переменные, типы, процедуры, функции, метки), которые будут использованы в программе. После раздела описаний идет раздел операторов, который начинается со служебного слова Begin и заканчивается служебным словом End. В этом разделе задаются действия над объектами программы, введенными в разделе описаний. Операторы в этом разделе отделяются друг от друга точкой с запятой. После последнего слова End ставится точка. И м я этой программы Му1_1 (заметим, что в имени программы не должно быть пробелов, оно должно начинаться с буквы или знака подчеркивания, состоять только из латинских букв, цифр и знаков подчеркивания, не допускается использование символов точки и запятой). Из разделов описаний имеется лишь один — раздел переменных. Он начинается со служебного слова Var, после которого идет последовательность объявления переменных, разделенных точкой с запятой. В каждом объявлении перечисляются через запятую имена переменных (идентификаторы) одного типа, после чего ставится двоеточие и указывается тип переменных. В нашем примере описаны три переменные: все они (a, b и rez) имеют целый тип (Integer), то есть значениями переменных этого типа являются целые числа. Понятие переменной — центральное в любом языке программирования. Для описания переменной (величины, которая может изменяться в процессе работы программы) следует указать имя переменной, ее тип и значение. Следует соблюдать следующий принцип: «Использовать переменную можно лишь тогда, когда ей присвоено некоторое значение». Это позволит Вам избежать многочисленных ошибок в работе программ. После описательной части идет раздел операторов, начинающийся со служебного слова Begin, после которого записываются операторы языка. Первый встречающийся оператор — это WnteLn('<meKcm>')\ — записать (вывести) на экран текст, заключенный между апострофами, Ln добавляется после Write для того, чтобы курсор автоматически переходил на следующую строку при выводе на экран текстов или результатов выполнения программы. Следующий оператор — это ReadLn(a.b); — читать данные с клавиатуры. В данном случае необходимо ввести два целых
Часть первая
12
числа через пробел, тогда переменной а присваивается значение, равное первому введенному числу, а переменной b присваивается значение, равное второму введенному числу. Например, вы ввели числа 12 и 45, тогда а=12, а Ь=45. После Write т а к ж е можно ставить L n . После этих двух операторов стоит оператор п р и с в а и в а н и я : rez:=a*b; (:= — это знак оператора п р и с в а и в а н и я в я з ы к е Паскаль). Значение в ы р а ж е н и я из правой части оператора п р и с в а и в а н и я заменяет текущее значение переменной и з левой части. Тип значение в ы р а ж е н и я обычно должен совпадать с типом переменной. При выполнении оператора переменная rez получит значение, равное произведению числа а на число b (см. рис.). Так к а к в результате у м н о ж е н и я двух ц е л ы х чисел получается целое число, то переменная rez описана типом Integer (значениям и которого могут быть л и ш ь целые числа).
readln(a,b)
rez =a*b
С л е д у ю щ и й оператор — это снова оператор WriteLn ( <текст>', rez) — он выведет на экран текст, з а к л ю ч е н н ы й между апострофами, а за ним значение переменной rez. Затем следующий оператор WriteLn выведет на экран сообщение: Наж м и т е < E n t e r > , а оператор ReadLn будет ожидать этого нажат и я в окне выполнения. В конце раздела операторов стоит служебное слово End с завершающей точкой. Запуск программы. Д л я того, чтобы запустить программу, выходим в главное меню (нажатием F10) — первое окно, выбираем режим Run и д в а ж д ы н а ж и м а е м <Enter>. На экране появляется сообщение:
Основные управляющие конструкции Введите
два
целых
13 числа
через
пробел
Курсор м и г а е т в с л е д у ю щ е й строке, вводим два ц е л ы х ч и с л а через пробел и н а ж и м а е м <Enter>, после этого п о я в л я е т с я сообщение: произведение равно... нажмите <Enter> Вместо т о ч е к будет н а п и с а н о з н а ч е н и е переменной гег, то есть ч и с л о , р а в н о е п р о и з в е д е н и ю первого введенного ч и с л а н а второе. Это сообщение будет на э к р а н е до тех пор, п о к а не нажата клавиша <Enter>. Сохранение программы.. Д л я того, чтобы с о х р а н и т ь прог р а м м у на в н е ш н е м носителе, необходимо: • в ы й т и в г л а в н о е м е н ю и в ы б р а т ь р е ж и м File; • н а ж а т ь <Enter>, из появившегося окна выбрать р е ж и м Save as... и н а ж а т ь к л а в и ш у <Enter>; • в п о я в и в ш е м с я окне набрать и м я файла и н а ж а т ь клавиш у <Enter>.. Н а п р и м е р , a:\priml_l.pas; здесь а:\ — это л о г и ч е с к о е и м я д и с к а , н а к о т о р о м будем с о х р а н я т ь ф а й л , priml_l — и м я ф а й л а (оно м о ж е т с о д е р ж а т ь не более 8 символов), pas — расширение, сообщающее о том, что ф а й л с о д е р ж и т п р о г р а м м у , н а п и с а н н у ю на я з ы к е Паскаль. П р и м е ч а н и е Для быстрого сохранения файла можно воспользоваться командами Save или Save all меню File. Выход из системы программирования Турбо-Паскаль. Д л я того, чтобы з а к о н ч и т ь работу, необходимо: • в ы й т и в главное м е н ю и в ы б р а т ь р е ж и м File; • н а ж а т ь <Enter> и из п о я в и в ш е г о с я о к н а выбрать р е ж и м Quit, после чего н а ж а т ь либо <Enter>, либо к о м б и н а ц и ю <Alt>-<X>. Экспериментальный раздел работы 1. Введите ч и с л а при работе п р о г р а м м ы , н а п р и м е р 4 5 6 7 и 789. Убедитесь, что у Вас получается неправдоподобный результат — отрицательное число ( - 1 1 1 7 ) . Н а й д и т е экспериментал ь н ы м путем тот и н т е р в а л значений переменных а и Ь, когда результат у м н о ж е н и я п р а в и л ь н ы й . 2. Вместо ввода числа введите какой-нибудь символ. Убедитесь, что выполнение программы не произошло, компьютер выдал сообщение об о ш и б к е Error 106: Invalid numeric format.
3. Добавьте л и ш н и й знак апострофа в операторе WriteLn. Убедитесь, что к о м п и л я ц и я программы не п р о и з о ш л а , а компьютер Вас порадовал ошибкой Error 8: String constant exceeds line. 4. И з м е н и т е в программе Myl_l оператор гег~а*Ъ на rez:=a-(a Div b)*b. В ы я с н и т е действие операции Div д л я п е р е м е н н ы х целого т и п а . И з м е н и т е текстовую часть следующего за оператором присвоения оператора WriteLn, отразив в ней результат Вашего исследования. 5. Добавьте к программе предыдущего примера переменную с именем ost, оператор присвоения ost:=a Mod b и оператор вывода WriteLnf'????????' ,ost);. Выясните, что за действие выполняется с помощью оператора M o d . Замените з н а к и вопроса В а ш и м и пояснениями его работы. 6. Наберите следующую программу. Program Myl_2; Var a: Integer; Begin WriteLn('Введите целое числос); ReadLn (a); WriteLn('???????? Abs (a)); WriteLn('Нажмите <Enter>'); ReadLn; End. Вашей задачей является замена знаков вопроса в операторе WriteLn на текст, поясняющий работу операции Abs. Выполните аналогичное исследование д л я операций Sqr(a), Ord(a), Succ(a), Pred(a). П р и м е ч а н и е Рекомендуется следующий порядок работы. Текст программы Му1_1 сохраняется под новым именем, например Pnml_2.pas, а затем изменяется и вновь сохраняется. Это позволит сократить время на набор программы. Задания для самостоятельной работы 1. Изменить программу д л я нахождения суммы двух чисел. 2. Изменить программу для нахождения суммы четырех чисел. 3. Найти значение выражения: (a+(d-12)*3)*(c-5*k), где значен и я переменных a,d,c и к вводятся с клавиатуры.
Основные управляющие конструкции
15
4. Н а р и с о в а т ь р и с у н о к по т и п у р и с у н к а , п р и в е д е н н о г о в т е к сте з а н я т и я , о т р а ж а ю щ е г о д е й с т в и я и з с л е д у ю щ е г о фрагмента программы: X = 13; =2 5 ; =х; =у; =t;
У t X Y
5. И з м е н и т ь р и с у н о к и з п р е д ы д у щ е г о п р и м е р а д л я о т р а ж е н и я д е й с т в и й ф р а г м е н т а п р о г р а м м ы п р и замене п о с л е д н и х трех операторов на: х:=х-у; у:=х+у; X:=у-х; 6. Н а п и с а т ь п р о г р а м м у в ы в о д а на э к р а н н е с к о л ь к и х ч и с е л в виде: 13 14 15 16 или 101 102 103 Материал для чтения 1. И з и с т о р и и . Турбо П а с к а л ь п о я в и л с я на р ы н к е программ н ы х п р о д у к т о в в 1 9 8 3 году и совершил р е в о л ю ц и ю в программ и р о в а н и и . До этих пор предпочтение отдавалось Б е й с и к у — простому, д е ш е в о м у и л е г к о у с в а и в а е м о м у . П а с к а л ь ж е был апп а р а т н о з а в и с и м ы м , д о р о г и м и с л о ж н ы м в о б р а щ е н и и . С появл е н и е м Турбо П а с к а л я п о л о ж е н и е м е н я е т с я . Турбо П а с к а л ь состоит и з я з ы к а п р о г р а м м и р о в а н и я и среды п р о г р а м м и р о в а н и я , к о т о р а я создает удобства в работе. Изучение П а с к а л я к а к я з ы к а программирования идет вместе с изучением всей системы Турбо Паскаль. Я з ы к программирования Паскаль был разработан Н . Виртом в 1 9 6 8 - 1 9 7 0 годах и получил широкое распространение благодаря наглядности программ и легкости при изучении. Он послужил основой д л я разработки других я з ы к о в программирования (например, Ада, Модула-2).
Часть первая
16
Первая версия Турбо Паскаля использовалась не очень долго _ появилась в 1983 году, а уже в 1984 году ее заменила вторая версия, которая получила широкое распространение. К осени 1985 года появляется третья версия, более удобная в работе (быстрее работают компилятор и редактор, возможен вызов MS-DOS из программы). Четвертая версия (1988 год), представила Турбо Паскаль в новом виде (появление новой среды, компилятор стал встроенным). Осенью этого же года разработана пятая версия, у которой еще больше развита среда и появился встроенный отладчик. А в 1989 году появилась версия 5.5, позволившая перейти к объектно-ориентированному программированию. Шестая версия уже обеспечивала многооконный и многофайловый режим работы, использование «мыши», применение объектно-ориентированного программирования, обладала встроенным ассемблером и имела другие возможности. В 1992 году фирма Borland International выпустила два пакета программирования на языке Паскаль — это Borland Pascal 7.0 и Turbo Pascal 7.0. Пакет Turbo Pascal 7.0 использует новейшие достижения в программировании. Язык этой версии обладает широкими возможностями, имеет большую библиотеку модулей. Среда программирования позволяет создавать тексты программ, компилировать их, находить и исправлять ошибки, компоновать программы из отдельных частей, использовать модули, отлаживать и выполнять отлаженную программу. 2. Некоторые команды редактора. Режим работы Edit (главное меню) — режим редактирования текста программы. Команды упрдавления движением курсора — перемещение курсора на символ вправо — перемещение курсора на символ влево — перемещение курсора на строку вверх — перемещение курсора на строку вниз — перемещение курсора в начало текущей строки — перемещение курсора в конец текущей строки — перемещение курсора на страницу вверх. — перемещение курсора на страницу вниз.
Основные управляющие конструкции
17
П р и м е ч а н и е С т р а н и ц а — это ч и с л о строк т е к с т а , с о с т а в л я ю щ и х один э к р а н .
I
Ctrl
1+
Ctrl
+
Ноте
End
— перемещение курсора в левый верхний угол
I
—перемещение >урсора в левый нижний угол
Команды вставки и удаления Insert
текста
— включение и выключение режима вставки,
П р и м е ч а н и е Е с л и р е ж и м в с т а в к и в к л ю ч е н , то на э к р а н е к у р с о р и м е е т вид м и г а ю щ е й ч е р т ы . В р е ж и м е в с т а в к и н а б и р а е м ы й с и м в о л в в о д и т с я в по з и ц и ю , в к о т о р о й стоит к у р с о р , а все с и м в о л ы ( н а ч и н а я с с и м в о л а с т о я щ е г о в п о з и ц и и к у р с о р а ранее), р а с п о л о ж е н н ы е правее, сдвига ю т с я в п р а в о . Е с л и р е ж и м в с т а в к и в ы к л ю ч е н , то н а б и р а е м ы й сим вол з а м е н и т тот с и м в о л , к о т о р ы й н а х о д и т с я в п о з и ц и и к у р с о р а , та к и м о б р а з о м м о ж н о с т а р ы й текст з а м е н и т ь на н о в ы й . Delete
Backspase
— удаление символа, стоящего в позиции курсора
I
— удаление символа, стоящего перед курсором.
П р и м е ч а н и е И н о г д а на этой к л а в и ш е н а п и с а н о BS, а иногда это л е в о с т о р о н н я я с т р е л к а , р а с п о л о ж е н н а я н а д к л а в и ш е й ввода ( E N T E R ) .
1Ч+ ГН
ЕП
+
п л
— вставка пустой строки над строкой, где находится курсор — удаление строки, где находится курсор.
3. Д а н н ы е — о б щ е е п о н я т и е всего того, с ч е м работает к о м п ь ю т е р . В а п п а р а т у р е в с е д а н н ы е прей*и»р2гатготсятрв»-я®-~ следовательности двоичных цифр (разря!чв). Л я з ы к а з с в н о в " к о г о у р о в н я , а к н и м о т н о с и т с я Т у р б о П а | ; к а Л Е , riW*rpAl«ilpiy*l® ся от д е т а л е й п р е д с т а в л е н и я д а н н ы х в П а м я т и к Л и д а т е р а .
18 Часть первая
Любой тип д а н н ы х определяет м н о ж е с т в о з н а ч е н и й , к о т о р ы е м о ж е т п р и н и м а т ь в е л и ч и н а этого т и п а , и те о п е р а ц и и , котор ы е м о ж н о п р и м е н я т ь к в е л и ч и н а м этого т и п а . В Турбо Паск а л е работают с п я т ь ю т и п а м и д а н н ы х : п р о с т ы м и , строковыми, составными, с с ы л о ч н ы м и и п р о ц е д у р н ы м и . К п р о с т ы м т и п а м д а н н ы х относятся ц е л ы е , в е щ е с т в е н н ы й , л о г и ч е с к и й , с и м в о л ь н ы й , п е р е ч и с л и м ы й и о г р а н и ч е н н ы й (два п о с л е д н и х о п р е д е л я ю т с я п о л ь з о в а т е л е м ) . Н а п р о с т ы х т и п а х д а н н ы х , кроме вещественного, определено отношение порядка. Ч т о это такое? Все множество з н а ч е н и й т и п а р а с с м а т р и в а е т с я к а к упорядоченное множество, и каждое значение связано с некоторым ц е л ы м ч и с л о м , которое есть его п о р я д к о в ы й номер. В любом порядковом типе д л я к а ж д о г о з н а ч е н и я , к р о м е первого, существует предшествующее з н а ч е н и е , и д л я к а ж д о г о з н а ч е н и я , за и с к л ю ч е н и е м последнего, существует п о с л е д у ю щ е е з н а ч е н и е . Определены следующие с т а н д а р т н ы е ф у н к ц и и д л я работы с порядковыми типами: • Ord — возвращает п о р я д к о в ы й номер з н а ч е н и я любого порядкового типа. • Pred — возвращает предшествующее значение д л я заданного значения порядкового типа (а если заданное значение первое?). • Succ — возвращает следующее значение д л я заданного значения порядкового типа (а если заданное значение последнее?). В Турбо П а с к а л е есть возможность конструировать (создавать) свои т и п ы данных из и м е ю щ и х с я типов, причем весьма с л о ж н ы е . И т а к , абстрагирование и конструирование суть концепции типа д а н н ы х . 4. К а ж д а я программа взаимодействует с о к р у ж а ю щ е й средой с помощью операторов ввода, вывода. Если т р а к т о в а т ь термин программа очень вольно, то можно считать, что любая программа что-то откуда-то берет, что-то делает с в в е д е н н ы м и данными (преобразует) и затем выводит куда-то полученные результаты. В Турбо Паскале связь программы с внешними устройствами осуществляется через имена файлов. Самый простой случай, когда эти имена с в я з а н ы с к л а в и а т у р о й (для ввода данных) и с экраном дисплея (для вывода). Д л я ввода с к л а в и а т у р ы используется оператор Read или Re adLn.
Основные управляющие конструкции
19
Вызов: Read(rj,r2,...,rn) Параметры: r j , r 2 , . . . , r n имеют тип Integer или Real или Char или String. Действие: если имеет тип • Integer или Real, то считывается одно число и значение его присваивается переменной При этом знаки пробела и перевода строки перед числом игнорируются; • Char, то считывается один символ и присваивается переменной • String, то считывается максимум q символов при длине q строковой переменной г^ Оператор ReadLn действует так же, к а к и Read. Отличие в том, что после ввода осуществляется переход на начало следующей строки. Вывод на экран осуществляется с помощью операторов Write или WriteLn. Вызов: Write(r1,r2,...,rn) Параметры: r j , r 2 , . . . , r n имеют тип Integer или Real или Boolean или Char или String. Действие: на экран выводится значение ^ в стандартном формате. Работа WriteLn отличается тем, что после вывода осуществляется переход на начало следующей строки.
20 Часть первая
З а н я т и е № 2. Ц е л ы й т и п д а н н ы х План занятия • обсуждение целого типа данных; • разбор программы выделения цифр из десятичного числа;^ • эксперименты с программой: перевод числа из десятичной системы счисления в двоичную систему счисления, выяснение сути арифметических операций с п е р е м е н н ы м и целого типа, преобразование переменных целого типа; • выполнение самостоятельной работы. Целый тип данных. Существует пять ц е л ы х типов: Shortlnt, Integer, Longlnt, Byte, Word. Они отличаются диапазоном значений, а значит, и размером памяти, отводимой для их представления. Тип
Диапазон значений
Объем памяти
Shortlnt
- 1 2 8 . 127
1 байт, со знаком
Integer
- 3 2 7 6 8 . . 32767
2 байта, со знаком
Longlnt
-2147483648..2147483647
4 байта, со знаком
! !i !;
Byte
0 . 255
1 байт, без знака
j
Word
0 ... 65535
2 байта, без знака
>
Операции с величинами целого типа: сложение (+), вычитание ( - ) , умножение (*), нахождение целой части деления (Dw), нахождение остатка от деления (Mod). Так к а к ц е л ы й тип данных относится к типам, на которых определено отношение порядка, то работают стандартные ф у н к ц и и Ord, Succ и Pred. П р и м е ч а н и е Переменной целого типа присваивать значение результата обычной операции деления «/» нельзя. Убедитесь в этом с помощью простой модификации программы первого занятия. Попробуйте най ти объяснение этому факту. Возникают, по крайней мере, два, достаточно с л о ж н ы х на этой стадии освоения я з ы к а , вопроса. Почему при представлении целых чисел со знаком диапазон отрицательных чисел на одно значение больше диапазона положительных чисел и к а к выполняются операции, например, при вычислении выражений, если все величины имеют разные целые т и п ы . Разбору программы операций Div и Mod.
предшествует обсуждение выполнения
Основные управляющие конструкции 21
В программе определяются цифры трехзначного числа. Можно ее и с п о л ь з о в а т ь и д л я о п р е д е л е н и я ц и ф р д в у з н а ч н о г о ч и с л а , просто ц и ф р а с о т е н в э т о м с л у ч а е р а в н а н у л ю , и это д е л а е т с я проще. Program Var a,
Му2_1; one, dec,
hun,
rez:Integer;
WriteLn('Введите число'); ReadLn (a); one:=a Mod 10; WriteLn('Цифра единиц числа ',one); dec: = (a Div 10) Hod 10; WriteLn('Цифра деечтков числа ',dec); hun:=a Div 1 00; WriteLn('Цифра сотен числа ',nun); Rez:=hun*100+dec*l0+one; WriteLn ('А это тоже число - ',rez); Write('Enter '); ReadLn; End. 137|10 "i30 13 [ i o _ 7 10 j 3
Н а п р и м е р , е с л и В ы в в е д е т е ч и с л о 1 3 7 , то знач е н и е п е р е м е н н о й one будет р а в н о 7, dec — 3 и hun — 1. В с п о м н и т е д е л е н и е ч и с е л с т о л б и к о м . П р и м е ч а н и е Не забудьте prim2_l.pas.
сохранить
программу
под
именем
Экспериментальный раздел работы 1. И з м е н и т е п р о г р а м м у Му2_1 д л я н а х о ж д е н и я ц и ф р д в у з н а ч н о г о ч и с л а . С о х р а н и т е ее под и м е н е м prim2_2.pas. 2. И з м е н и т е п р о г р а м м у Му2_1 д л я н а х о ж д е н и я ц и ф р ч е т ы р е х з н а ч н о г о ч и с л а . С о х р а н и т е ее под и м е н е м Prim2_2.pas. 3. Д е л е н и е н а 10 и н а х о ж д е н и е о с т а т к о в от д е л е н и я м ы рассмотрели в ы ш е . Р а с с м о т р и т е п р и м е р д е л е н и я с т о л б и к о м на 2. О с т а т к и от д е л е н и я и л и 0, и л и 1.
Часть первая
22 1IZ.L2 1 68. [2 О 2112 О 17 Ц 1 812. О 4 12. О 2Ц. О 1
Н а б е р и т е с л е д у ю щ у ю п р о г р а м м у , отладьте ее и попробуйте д а т ь о б ъ я с н е н и е полученному р е з у л ь т а т у . И з м е н и т е п р о г р а м м у т а к , чтобы она п р а в и л ь н о работала, н а п р и м е р , с ч и с л о м 115. Program Му2_2; Var r e z : I n t e g e r ; Begin WriteLn('137'); WriteLn('10001001'); Rez:=1 * 128+0*64 + 0*32+0*16+1*8 WriteLn(rez); ReadLn; End.
+ 0*4 + 0*2 + 1 *1;
4. Н а б е р и т е с л е д у ю щ у ю п р о г р а м м у : Program My2_3; Uses Crt; Var a:Integer; b:Word; rl:Integer; Begin ClrScr;(Очистка экрана, процедура а:=32000;b:=64 000; rl;=a+b; WriteLn(rl); r2:=a+b; WriteLn(r2); ReadLn; End.
r2:Longlnt; модуля
Crt}
После з а п у с к а В ы увидите, что з н а ч е н и е п е р е м е н н о й rl равно 3 0 4 6 4 , а з н а ч е н и е переменной г2 — 9 6 0 0 0 . Б е л и и з м е н и т ь тип переменной rl на W o r d , то р е з у л ь т а т не и з м е н и т с я . Используя информацию из таблицы, приведенной в начале занятия, и з м е н и т е п р о г р а м м у т а к , чтобы п р о д е л а т ь а н а л о г и ч н ы е эксперименты с другими целыми типами. Попробуйте найти логику получения результата компьютером. 5. Добавьте в п р о г р а м м у Му2_3 перед оператором ReadLn след у ю щ и е два оператора: Wri teLn (Longln t (1 00 *а)) ; Wri teLn (1 00*LongIn t (a)) ;
Основные управляющие конструкции
23
Функция Longlnt преобразует переменную типа Integer в тип Longlnt. В первом случае преобразование осуществлялось после умножения, а во втором — перед умножением. В первом случае получен результат, далекий от истины, — отрицательное число - 1 1 2 6 4 , во втором правильный — 3200000. Приведем основные правила, по которым в Турбо Паскале осуществляются операции с переменными целых типов. Перед выполнением операций (бинарных) над двумя операндами оба операнда преобразуются к общему для них типу. Им является тип с наименьшим диапазоном, включающим все возможные значения обоих типов. Например, общим типом для Integer и Byte будет Integer, для Integer и Word, — Longlnt. Результат будет общего типа. Выражение в правой части оператора присваивания вычисляется независимо от размера или типа переменной в левой части! Перед выполнением любой арифметической операции любой операнд длиной в 1 байт преобразуется в промежуточный операнд длиной в 2 байта, который является совместимым как с Integer, так и с Word. Задания для самостоятельной работы 1. Чему равны значения переменных а и b после выполнения последовательности действий: • а— 15 Div (16 Mod 7); b:= 34 Mod a* 5 — 29 Mod 5 + 2; • a:= 4 * 5 Dw 3 Mod 2; b~ 4 * 5 Div ( 3 Mod 2); 2. Дано двузначное число. Определить: • сумму и произведение цифр числа; • число, образованное перестановкой цифр исходного числа. 3. Дано трехзначное число. Определить: • сумму и произведение цифр числа; • число, образованное перестановкой цифр исходного числа; • число, полученное перестановкой цифр десятков и единиц; • число, полученное перестановкой цифр сотен и десятков; • четырехзначное число, полученное приписыванием цифры единиц в качестве цифры тысяч (например, из числа 137 необходимо получить число 7137). П р и м е ч а н и е Сколько различных чисел можно получить из трехзначного числа путем перестановки цифр?
Часть первая
24
4. Р е ш и т ь задачу 3 (кроме последнего п у н к т а ) д л я ч е т ы р е х з н а ч ных чисел. П р и м е ч а н и е Предложить максимальное количество разумных модификаций рассматриваемой задачи. 5. А р и ф м е т и ч е с к а я прогрессия — это последовательность чисел, в которой р а з н о с т ь м е ж д у п о с л е д у ю щ и м и п р е д ы д у щ и м элем е н т а м и остается неизменной. Последовательность 12, 15, 18, 21, 24, ... я в л я е т с я а р и ф м е т и ч е с к о й п р о г р е с с и е й , 12 — перв ы й член прогрессии ( а Д р а з н о с т ь п р о г р е с с и и р а в н а 3. Л ю бой ч л е н прогрессии в ы ч и с л я е т с я по ф о р м у л е a n = a j + d * ( n - l ) , где d — разность прогрессии, п — номер в з я т о г о ч л е н а . Д а н ы a j и d. Н а й т и п, п р и к о т о р о м з н а ч е н и е а п в ы х о д и т з а д и а п а зон т и п а I n t e g e r ( э к с п е р и м е н т а л ь н ы м п у т е м ) . 6. Сумма п е р в ы х п членов а р и ф м е т и ч е с к о й п р о г р е с с и и в ы ч и с л я е т с я по формуле S n =(aj-(-a n )*n/2. Д а н ы a j и d. Н а й т и п, п р и к о т о р о м з н а ч е н и е S n в ы х о д и т за д и а п а з о н т и п а I n t e g e r (эксп е р и м е н т а л ь н ы м путем). Материал для чтения 1. В ы р а ж е н и я . Они состоят и з о п е р а ц и й и о п е р а н д о в . Р а з л и чают б и н а р н ы е о п е р а ц и и — в ы п о л н я ю т с я н а д д в у м я о п е р а н д а м и , и у н а р н ы е (одноместные) — н а д о д н и м о п е р а н д о м . Б и н а р ные операции записываются в обычной математической форме, знак унарной операции предшествует операнду. Порядок вып о л н е н и я о п е р а ц и й в в ы р а ж е н и и о п р е д е л я е т с я п р а в и л а м и приоритета. П р и в е д е м и х . П р и м е ч а н и е Если некоторые из операций Вам не очень понятны, то не отчаивайтесь. Всему свое время. Операции Not, +, *, /, Div, Mod, And, Shi, Shr +, -, Or, Xor =,<>,<,>,<=, >= ,„
Приоритет 1-й (высший) 2-й 3-й 4-й (низший)
Тип операции унарный мультипликативный аддитивный операции отношения
|
С ф о р м у л и р у е м основные п р а в и л а п р и о р и т е т а : • операнд, р а с п о л о ж е н н ы й м е ж д у з н а к а м и д в у х о п е р а ц и й с р а з н ы м и п р и о р и т е т а м и , я в л я е т с я г р а н и ц е й о п е р а ц и и с более в ы с о к и м п р и о р и т е т о м ;
Основные управляющие конструкции
25
• о п е р а н д , р а с п о л о ж е н н ы й м е ж д у д в у м я о п е р а ц и я м и с равн ы м и приоритетами, является границей для операции, расположенной слева; • в ы р а ж е н и я в с к о б к а х в ы ч и с л я ю т с я до того, к а к они будут обрабатываться к а к один операнд. 2. С и с т е м ы с ч и с л е н и я — способ з а п и с и ч и с е л . С и с т е м ы счисл е н и я , в к о т о р ы х в к л а д к а ж д о й ц и ф р ы в в е л и ч и н у ч и с л а зависит от ее п о з и ц и и в п о с л е д о в а т е л ь н о с т и ц и ф р , и з о б р а ж а ю щ е й ч и с л о , н а з ы в а ю т с я п о з и ц и о н н ы м и . Ч и с л о в д е с я т и ч н о й системе счисления 3377=3*1000+3*100+7*10+7 =3*103+3*102+7*101+ + 7 * 1 0 ° — в к л а д к а к ц и ф р 3, т а к и ц и ф р 7 р а з л и ч е н . Это ж е число в пятеричной системе счисления записывается как 1 0 2 0 0 2 ( 3 3 7 7 = 1*5 5 + 0 * 5 4 + 2 * 5 3 + 0 * 5 2 + 0 * 5 4 2 * 5 ° ) . П о д с к а ж е м , ч т о 5 5 = 3 1 2 5 , а 5 3 = 1 2 5 . Это ж е ч и с л о в д в о и ч н о й системе счисл е н и я записывается как 110100110001 (3377 = 1 * 2 И +1*210 + + 0 * 2 9 + 1 * 2 8 + 0 * 2 7 +0*2® + 1 * 2 5 + 1 * 2 4 + 0 * 2 3 + 0 * 2 2 + 0 * 2 ' +1*2°). Приведем з н а ч е н и я степеней двойки: 2 И = 2 0 4 8 , 2 1 0 = Ю 2 4 , 29 = = 5 1 2 , 2 8 = 2 5 6 и т. д. А л г о р и т м перевода ц е л о г о ч и с л а и з десят и ч н о й с и с т е м ы с ч и с л е н и я в д в о и ч н у ю : д е л и м исходное число н а 2 н а ц е л о в д е с я т и ч н о й системе с ч и с л е н и я и с ч и т а е м н о в ы м з н а ч е н и е м ч и с л а ц е л у ю ч а с т ь частного, остаток от д е л е н и я , а это 0 и л и 1, з а п о м и н а е м ; п р о д о л ж а е м процесс д е л е н и я до тех пор, п о к а в р е з у л ь т а т е не будет п о л у ч е н 0. Пример: 3 3 7 7 : 2 = 1 6 8 8 (1) ^ 1 6 8 8 : 2 = 8 4 4 (0) 8 4 4 : 2 = 4 2 2 (0) 4 2 2 : 2 = 2 1 1 (0) 2 1 1 : 2 = 1 0 5 (1) 1 0 5 : 2 = 5 2 (1) 5 2 : 2 = 2 6 (0) 26:2=13(0) 1 3 : 2 = 6 (1) 6 : 2 = 3 (0) 3 : 2 = 1 (1) 1:2=0 (1) В с к о б к а х у к а з а н о з н а ч е н и е о с т а т к а от д е л е н и я . 337710=1101001100012.
Итак,
М е т о д и ч е с к и е р е к о м е н д а ц и и Ученикам следует предложить выполнить порядка 15-20 упражнений по переводу целых чисел из десятичной системы счисления в двоичную и обратно.
26 Часть первая
3. Представление ц е л ы х чисел в п а м я т и к о м п ь ю т е р а . Компьютер работает т о л ь к о с д а н н ы м и , п р е д с т а в л е н н ы м и в двоичной системе счисления. Не имеет з н а ч е н и я , к а к и е это данные: текст, звук, рисунок, целые числа, они д о л ж н ы б ы т ь переведены в двоичное представление (закодированы). Д л я х р а н е н и я одной двоичной ц и ф р ы , а это 0 и л и 1, используется один бит (разряд) п а м я т и к о м п ь ю т е р а . З а п о м н и т е , что 8 битов н а з ы в а ю т 1 байтом, 1024 байт — 1 к и л о б а й т о м (1Кбайт), 1024Кбайт — 1 мегабайтом (1Мбайт), 1024Мбайт — 1 гигабайтом (1Гбайт). В 2 битах м о ж н о х р а н и т ь 4 р а з л и ч н ы е последовательности из 0 и 1: 00, 01, 10, 11 (2 2 ). (В 3 битах — 8: 000, 001, 010, 011, 100, 101, 110, 111 (2 3 ). А в 8 битах и л и 1 байте?) Если перевести эти 8 последовательностей и з 0 и 1 из двоичной системы с ч и с л е н и я в десятичную, то п о л у ч и м д е с я т и ч н ы е ц и ф р ы от 0 до 7. Пусть у нас есть 4 бита, а это 16 р а з л и ч н ы х д в о и ч н ы х последовательностей, д л я х р а н е н и я ц е л ы х н е о т р и ц а т е л ь н ы х чисел. И т а к , м ы можем х р а н и т ь в этом случае ч и с л а и з и н т е р в а л а от 0 до 15. Проверьте. А что делать, если необходимо х р а н и т ь и отрицател ь н ы е числа? У нас п о - п р е ж н е м у 4 бита п а м я т и к о м п ь ю т е р а . Естественным шагом я в л я е т с я выделение одного бита д л я хран е н и я з н а к а числа. П о л у ч а е м , что, н а п р и м е р , - 5 х р а н и т с я к а к 1101, а 5 — 0101. И д и а п а з о н п р е д с т а в л е н и я ч и с е л в этом случае от - 7 до 7, т. е. 15 р а з л и ч н ы х з н а ч е н и й , а у нас есть возм о ж н о с т ь х р а н и т ь в 4 битах 16 р а з л и ч н ы х последовательностей. Последовательность 1000 не задействована и л и , что еще более н е п р и я т н о , она м о ж е т т р а к т о в а т ь с я к а к - 0 , а это у ж е и з р а з р я д а н е о б ъ я с н и м ы х фактов, 0 и - 0 . Д а в а й т е н а у ч и м с я вып о л н я т ь с л е д у ю щ и е действия: и н в е р с и ю и п р и б а в л е н и е 1 к двоичному числу. Рассмотрим на н а ш е м п р и м е р е . Число
Двоичное представление
Инверсия
Прибавление 1
1 2 3 4 5 6 7 8
0001 0010 0011 0100 0101 0110 0111 1000 0000
1110 1101 1100 1011 1010 1001 1000 0111 1111
1111 1110 1101 1100 1011 1010 1001 1000 10000 (пятый, старший разряд
0
«отбрасываем»)
Отрицательное число I -1 -2 -3 -4 -5 -6 -7 -8 0
Основные управляющие конструкции 27 В предпоследнем столбце м ы получаем двоичное представление отрицательных чисел. Все 16 двоичных последовательностей задействованы, диапазон представления от - 8 до 7 и представление 0 однозначно. Представление отрицательных чисел в том виде, к о т о р ы й приведен в таблице, называется дополнительным кодом. Естественно, что представление п о л о ж и т е л ь н ы х чисел в дополнительном коде совпадает с их обычным представлением. Сравните полученный результат с диапазоном значений величин целого типа из таблицы, приведенной в начале занятия. М е т о д и ч е с к и е р е к о м е н д а ц и и Вручную выполнить аналогичные действия при 5 и 6 разрядах, отводимых для представления целых чисел.
Часть первая
28
З а н я т и е № 3. К о м а н д ы р е д а к т о р а д л я р а б о т ы с блоками, работа с окнами План занятия • знакомство с командами редактора для работы с блоками; • знакомство с режимом Window системы программирования; • программа вычисления степеней двойки; • программа перевода чисел из десятичной системы счисления в двоичную систему счисления; • выполнение самостоятельной работы. Знакомство с командами редактора для работы, с блоками. Блок — любой фрагмент текста программы. Одновременно в тексте может быть только один блок. Отмеченный блок выделяется на экране яркостью. Для того, чтобы что-то сделать с блоком, его необходимо выделить, т. е. отметить начало и конец блока. После этого выполняются действия с блоком Приведем основные команды редактора (режим Edit главного меню) для работы с блоками: К
В
Отметить начало блока
Ctrl
К
К
Отметить конец блока
Ctrl
К
Т
Отметить одно слово как блок
Ctrl
К
с
Скопировать блок
Ctrl
К
У
Удалить блок
Ctrl
К
н
Убрать выделение блока
Ctrl
К
V
Переместить блок
Ctrl
К
я
Ввести с диска ранее записанный блок, начиная с позиции курсора
К
W
Записать отмеченный блок на диск
Примечание 1. Последовательность выполнения команд: • устанавливается курсор в требуемое место текста; • нажимается клавиша Ctrl; • не отпуская клавишу Ctrl, нажать клавишу с буквой К; • отпустив клавишу с буквой К, не отпуская клавишу Ctrl, нажать клавишу со второй буквой. 2. Знание команд работы с блоками значительно ускоряет набор Ваших программ. Используйте их при работе.
Основные управляющие конструкции 29
Структура задания 1. Набор любого содержательного текста. Например. «На счетах десятичное число кодируется положением костяшек на спицах, а в арифмометре (механическом вычислителе) число кодируется положением диска с 10 зубцами. С помощью т а к и х связанных между собой дисков можно построить суммирующее устройство. И д е я т а к о г о устройства п р и н а д л е ж и т Леонардо да Винчи ( 1 4 5 2 - 1 5 1 9 ) . А впервые т а к о е устройство сделал в 1642 году ф р а н ц у з с к и й философ и м а т е м а т и к Б л е з П а с к а л ь (1623-1662). В ы д а ю щ и й с я а м е р и к а н с к и й математик Д ж о н фон Нейман ( 1 9 0 5 - 1 9 5 7 ) при работе в составе группы исследователей над м а ш и н о й «ЭНИАК» сформулировал принципы, л е ж а щ и е в основе функционирования современных вычислительных машин. Суть этих принципов: в памяти ЭВМ хранятся не только данные, но и обрабатывающая их программа; представление в п а м я т и д а н н ы х и п р о г р а м м ы совпадает; программа должна выполняться последовательно команда за командой». 2. Выделение его фрагментов к а к блоков. 3. К о п и р о в а н и е фрагментов текста (выделяется блок, курсор у с т а н а в л и в а е т с я в требуемое место, в ы п о л н я е т с я команда копирования). 4. Перемещение фрагментов текста (выделяется блок, курсор устанавливается в требуемое место, выполняется команда перемещения). 5. Вставка фрагментов текста через диск (выделяется блок, задается команда записи на диск, при этом необходимо определить и м я блока, курсор устанавливается в требуемое место, выполняется команда чтения блока с диска). Работа с окнами. В Турбо Паскале есть возможность работы с несколькими окнами (режим Window главного меню). Окно — ограниченная область экрана. Его размеры и положение на экране можно изменять. Программа, а она хранится на диске к а к файл с определенным именем, размещается в определенном окне. А к т и в н ы м в т е к у щ и й момент является только одно окно, в нем находится курсор.
Часть первая
30 Команда
Tile Cascade Close All Size/Move
Функциональная клавиша
Назначение Последовательное размещение окон Каскадное размещение окон Закрытие всех окон
F5
Ctrl
Изменение размера, перемещение окна. При выполнении команды изменяется цвет рамки окна Для изменения размера окна используется комбинация клавиш S/i/ft+ft, У, <=, =>. Для перемещения — ft, У, <=, =г Для завершения работы с окном следует нажать клавишу Enter Цвет рамки окна изменится на первоначальный
J |j i ' | |1
II
Zoom
F5
Размеры активного окна устанавливаются 1 равными полному экрану
F6
Переход к следующему окну
F6 F3
Переход к предыдущему окну
'
II
Next Previ os Close
Закрытие активного окна
(
Просмотр списка открытых окон
ц
|
Us t
Shift
+
Alt
+
|
Структура задания 1. Открыть все программы предыдущих занятий. 2. Выполнить перечисленные выше команды работы с окнами. Экспериментальный раздел работы 1. Выполните обычные действия с программой МуЗ_1 компиляцию, запись на диск, запуск).
(набор,
Program МуЗ_1; Uses Crt; Var rO,rl,r2,r3,r4,r5,гб:Integer; Begin CIrScr; WriteLn ('Вычисляем степени числа 2'); г0:=1;rl:=2;г2:=rl*rl; r3:=r2*rl; г4:=г2*г2; r5:=rl *rl *rl *rl * rl ; r6:=r3*r3; WriteLn('Нулевая степень, ',г0); WriteLn('Первая степень, ',rl); WriteLn('Вторая степень, ' ,г2) ; WriteLn('Третья степень, ',гЗ); WriteLn('Четвертая степень, ',г4); WriteLn('Пятая степень, ',г5); WriteLn ('Шестая степень, ',г6); ReadLn; End.
Основные управляющие конструкции
31
П р и м е ч а н и е При наборе программы рекомендуется использовать изученные команды работы с блоками. Набирается r2:=rl*rl, а затем этот фрагмент выделяется как блок, копируется 4 раза и модифицируется. Аналогичные действия выполняются с операторами WriteLn. Д а в а й т е подсчитаем, за к а к о е количество о п е р а ц и й выполн я е т с я , н а п р и м е р , в ы ч и с л е н и е 5-й степени д в о й к и в н а ш е й программе — 4 о п е р а ц и и . А м о ж н о л и в ы ч и с л и т ь за меньшее количество о п е р а ц и й ? О к а з ы в а е т с я , «да», за 3 операции — г5~г2*г2*г1. В ы ч и с л е н и е г2 требует одну операцию умножен и я и две о п е р а ц и и д л я в ы ч и с л е н и я результата. М о д и ф и ц и р у й т е п р о г р а м м у т а к , чтобы она в ы ч и с л я л а степен и д в о й к и до 10-й в к л ю ч и т е л ь н о (за м и н и м а л ь н о е количество операций). 2. В ы п о л н и т е о б ы ч н ы е д е й с т в и я с п р о г р а м м о й МуЗ_2 (набор, к о м п и л я ц и ю , запись на диск, запуск). П р и м е ч а н и е При наборе программы рекомендуется использовать изученные команды работы с блоками и окнами. Скопируйте через диск текст программы МуЗ_1, запишите его как текст программы МуЗ_2, а затем измените его, максимально используя команды работы с блоками. Например, набирается s0~b Mod 2; b.=b Dw 2: и копируется столько раз, сколько необходимо. Program МуЗ_2; Uses Crt; Var rO,rl,r2,r3,г 4,г5,гб:Integer; s0,sl,s2,s3,s4,s5,s6:Integer; а,Ь,rez:Integer; one,dec,hun:Integer; Begin ClrScr;{*Очистка экрана*} {*Вычисление степеней двойки*} rO: =1;rl:=2;r2:=rl*rl;гЗ:=r2*rl; r4:=r2*r2; г5:=гЗ*г2; г6:=гЗ*гЗ; W r i t e L n ('Введите число меньше 128 и больше 64') ; ReadLn(а);Ь:=а; (*Перевод числа в двоичную систему счисления*} s0:=Ь Mod 2; Ь : = Ь D i v 2; sl:=b Mod 2; Ь:=Ь Div 2; s2:-b Mod 2; Ь : = Ь Div 2; s 3 r = b Mod 2; b:=b Div 2; s4: ~b Mod 2; b:~b Div 2; s5:=b Mod 2; b:=b Div 2; s6:~b Mod 2; b:=b Div 2; b: =a ;
Часть первая
32
( * Выделение десятичных цифр в записи числа*) one:=Ь Mod 10; Ь:=Ь Div 10; dec:=b Mod 10; b:=b Div 10; hun:=b Mod 10; WriteLn ('Мы правильно выделили десятичные цифры') ;1 WriteLn ('Вывод числа в десятичной системе счисления',hun*100+dec*10+one); WriteLn (hun,dec,one) ; WriteLn ('Это же число в двоичной системе счисления'); WriteLn (s6,s5,s4,s3,s2,sl,s0); WriteLn (' Переводим число из двоичной систем счисления в десятичную'); Rez:=s6*r6+s5*r5 + s4*r4 + s3*r3-ts2*r2+sl *rl + s0*rC; (*Перевод*) WriteLn('Все сделано правильно, числа совпадают'); Wnteln (а, ' ' , rez) ; Readln; End. Введите числа больше, чем 128. Оцените результат работы программы. Модифицируйте программу так, чтобы и в этом случае она была работоспособна. Задания для самостоятельной работы 1. Вычислять не очень большие степени двойки мы научились. Предположим, что у Вас есть некая последовательная (элементы следуют один за другим, и они одинаковые по своим свойствам) структура для работы с данными. Она имеет и м я , н а п р и м е р А, и элементы и з этой структуры выбираются по номеру (она последовательная). Так, запись А[10], говорит о том, что мы работаем с 10-м элементом иашей структуры. А задача заключается в том, чтобы, используя эту структуру данных, предложить идею (нет, нет, не программу!) вычисления больших степеней двойки за минимальное количество операций. 1
а 2
При наборе программ в Турбо Паскале (здесь и далее по тексту) переносить строковую констапту па следующую строку нельзя.
Основные управляющие конструкции
33
Структура — взаиморасположение и связь составных частей чего-либо (из словаря иностранных слов). Итак, когда мы говорим о структуре, то обязаны сказать о том, из каких элементов она состоит и как они (элементы) связаны между собой. Следует также понимать, что структура обладает новыми свойствами, качествами по отношению к свойствам элементов, ее составляющих. Если структуре дать какое-то имя, то мы получаем нечто новое. В информатике структура из однородных элементов, расположенных последовательно, называется массивом. 2. Модифицируйте программу МуЗ_1 так, чтобы она вычисляла степени тройки. 3. Модифицируйте программу МуЗ_2 так, чтобы она осуществляла перевод чисел из определенного интервала в троичную систему счисления и обратно. 4. На предыдущем занятии в материале для чтения мы рассмотрели представление отрицательных чисел в дополнительном коде. Пусть по-прежнему у нас только 4 разряда для представления чисел. Необходимо найти 7 - 3 или 7+(-3). Складываем в двоичной системе счисления 0111+1101=10100. Первое двоичное число это 7, второе - 3 в дополнительном коде. Результат пятиразрядный, у нас 4 разряда, отбрасываем старший разряд, получаем 0100, а это двоичная запись числа 4. Еще пример. Требуется найти 2 - 5 , или 2+(-5). В двоичной системе счисления 0010+1011=1101, а это запись - 3 в дополнительном коде. Пусть Вам дано 6 разрядов для представления целых чисел. Измените предыдущие операции так, чтобы они выполнялись и для этого случая. Разберите еще несколько аналогичных примеров. В компьютере нет необходимости реализовать операцию вычитания целых чисел! Достаточно уметь складывать числа, инвертировать двоичное представление числа и прибавлять единицу к младшему разряду. А сейчас представим себе, что любое сложение можно реализовать путем последовательного прибавления единицы к младшему разряду. Что остается? Инверсия и прибавление единицы к младшему разряду! 5. Подсчитайте сумму пятеричных чисел в интервале от 20 5 до 40 5 , включая границы интервала.
Часть первая
34
6. Восстановите цифры двоичного числа, на месте которых записан символ «*». 1**1+0011=1100. Придумайте и выполните еще несколько примеров такого типа. 7. Определите, является ли число 4301 5 четным? Найдите все четные числа в интервале от 4300 5 до 4340 5 . 8. В четверичной и восьмеричной системах счисления составить таблицы сложения и умножения. 9. Сравнить числа в различных системах счисления 312 4 и 72 g . Изменится ли результат, если вычесть из чисел соответственно 1 2 „ и 12 g ? 10. Существует ли система счисления с основанием х, в которой выполняются следующие равенства: З х + 4 х = 7 х , 3 х *4 х =13 х и 39 х +29 х =70 х ? Ответ обосновать. 11. Возможно ли в какой-нибудь системе счисления с основанием х выполнение следующего равенства 600 х =21 l x + 2 5 2 x i - 5 3 x ? Ответ обосновать. Материал для чтения Попытаемся ответить на вопрос, что такое программа? Существуют различные, очень умные, научные трактовки этого термина. Определим очень просто. Программа — это «откуда взять, что сделать, куда положить, а если это не так?» (А. Н. Венц «Профессия программист», 1999.) Правда, в книге таким образом определено понятие алгоритма, но, на наш взгляд, оно более соответствует тому, что понимается под словом программа. Оставим пока в стороне нюансы терминологии. Пусть оно будет нашим рабочим определением. Вспомним разобранные программы и ответим на вопросы. «Откуда взять» — мы пока берем только из файла, в который вводятся данные с клавиатуры компьютера, но есть и другие места, откуда можно взять исходные данные. «Что сделать» — например, в нашей первой программе один оператор присваивания. «Куда положить» — ответ на вопрос очевиден, пока этим местом является то, что связано с монитором нашего компьютера. И наконец, четвертый и, наверное, самый главный вопрос — «а если это не так». Мы видим, что даже в первой программе ответ не очевиден. Она работает не при всех исходных данных. Если Вы откомпилировали и один или два раза запустили Вашу программу и получили правильные результаты, то это еще не значит, что у Вас есть работающая программа. Она пока лишь первое приближение к программе. Итак, сформулируем один из основных принципов нашей с Вами работы: «Все подвергай сомнению, все
Основные управляющие конструкции
35
проверяй сам, ни одного факта на веру». Это относится не только к программам, но и к тому, о чем говорит учитель, к тому, что пишут в книгах по информатике! Сформулируем еще один из принципов. Работа по схеме воспроизведения, пусть даже творческого, того, что написано в учебнике, или того, что говорит учитель, не приводит к успеху в информатике. Львиная доля успешности освоения предмета приходится на самостоятельную, кропотливую работу. А. Н. Венц в своей книге приводит формулу великого программиста (ВП), выведенную экспериментальным путем: В П = 5 0 % К + 3 0 % Т + 1 0 % 0 + 5 % 3+ 5%ТЛ, где К — знать, как это делать, Т - трудолюбие, О — опыт, 3 — знание, ТЛ — талант. На то, что относится к вундеркиндам, только 5%, остальное — труд, ежедневный труд. А сейчас подвергните сомнению эту формулу. Автор, например, не очень понимает разницу между терминами: «знать, как ...» и просто «знание». Продолжим уточнение термина программа. Языков программирования существует великое множество, но, на каком бы из них Вы ни работали, при создании программы необходимо выполнить следующее: • ввести данные в программу (ввод); • определить представление этих данных в памяти компьютера (данные, точнее, структуры данных); • определить операции по обработке данных (операторы); • выполнить вывод результатов работы программы (вывод); Организация операций в программе может быть различна: • некоторые из них выполняются только при определенных условиях (условия); • часть из них выполняется несколько раз (циклы); • часть из них допускает разбивку на блоки и выполняется в различных частях программы (подпрограммы). Итак, эти семь элементов ввод, данные, операторы, вывод, условия, циклы, подпрограммы, являются основными при создании небольших программ на любом языке программирования. Однако следует заметить, что рассмотренное уточнение понимания того, что есть программа, относится примерно к 1975 году, да и то с некоторыми оговорками. Нет, например, того, что понимается под термином технологии программирования. Мы не говорим (пока) о том, как из этих элементов «склеивается», собирается программа (об этом позднее), а реальные программы, по оценкам экспертов, намного сложнее автомобилей. И не только об этом. Абсолютно не затронут вопрос о том, как
36
Часть первая
программа должна реагировать на события во внешней среде и многое другое. Таким образом, наша трактовка работы программы сводится к некоему последовательному процессу, а это не всегда соответствует действительности. Однако «нельзя объять необъятное», программирование — с л о ж н е й ш и й раздел информатики, сложнейшая отрасль производства, а учебник посвящен «основам программирования». В материалах для чтения мы сделаем обзор технологий программирования д л я того, чтобы составить общее представление о том, какой «виток» спирали развития технологий программирования изучается.
Основные управляющие конструкции
37
Занятие № 4. Л о г и ч е с к и й тип данных, операции сдвига План занятия • обсуждение логического типа • обсуждение операций сдвига; • эксперименты с программами выполнения операций сдвига С выполнение самостоятельной
данных; вывода таблиц истинности, и логических операций; работы.
Логический тип данных. Переменные логического типа описываются с помощью идентификатора Boolean. Диапазон значений — два: False (ложь) или True (истина), размер выделяемой памяти — 1 байт (False и True — стандартные константы). Тип янляется перечислимым, поэтому: False<True, Ord(False)=0, Ord(True)=l, Succ(False)=True, Pred(True)=False. Перечислим четыре логические операции, реализованные н Турбо Паскале: логическое сложение, или дизъюнкция, — Or; логическое умножение, или конъюнкция, — A n d ; отрицание — Not, исключающее «Или» (сложение по модулю два) — Хог. Результаты выполнения операций над переменными логического типа х и у приведены в таблице. Значение операции
Значение операнда
х
Not
]
x Andy
x Or у
xXory False
False
False
False
True
True
False
True
True
False
False
False
True
True
|j
True
True
False
True
True
False
j
False
1
У False
True
True
Выше приведены четыре таблицы истинности (сведены в одну таблицу), с помощью которых в математической логике обычно описываются значения логических функций. Таблица истинности представляет собой таблицу, устанавливающую соответствие между возможными значениями наборов переменных и значениями операции. Следует четко понимать, что результатом выполнения операций сравнения (отношения): «<» (меньше), «>» (больше), «<=» (меньше или равно), «>=» (больше или равно), «<>» (не равно), « = » (равно) является величина логического типа. Ее
38 Часть первая
з н а ч е н и е р а в н о True, е с л и о т н о ш е н и е в ы п о л н я е т с я д л я з н а ч е н и й в х о д я щ и х в него о п е р а н д о в , и False — в п р о т и в н о м с л у ч а е . В я з ы к е Т у р б о П а с к а л ь нет в о з м о ж н о с т и в в о д а л о г и ч е с к и х д а н н ы х с п о м о щ ь ю о п е р а т о р а Read. О д н а к о п р е д у с м о т р е н вывод з н а ч е н и й п е р е м е н н ы х л о г и ч е с к о г о т и п а с п о м о щ ь ю оператора Write. Операции сдвига. Р е ч ь идет о д в у х о п е р а ц и я х : Shi — с д в и г влево и Shr — с д в и г в п р а в о . Т и п о п е р а н д о в и р е з у л ь т а т а в опер а ц и я х с д в и г а Integer. И т а к , т. Shi п — з н а ч е н и е m с д в и г а е т с я в л е в о н а п р а з р я д о в ; а п р и т Shr п — з н а ч е н и е m с д в и г а е т с я в п р а в о н а п р а з р я д о в . П р и в ы п о л н е н и и о п е р а ц и й р а з р я д ы , выш е д ш и е з а п р е д е л ы о б л а с т и п а м я т и , в ы д е л я е м о й д л я т и п а данных, теряются, а с другой стороны добавляются нули. Например, если m р а в н о 32, то с д в и г влево н а один р а з р я д дает 64, а сдвиг в п р а в о — 16. О п е р а ц и и р а в н о с и л ь н ы у м н о ж е н и ю и делен и ю на д в а . Э к с п е р и м е н т а л ь н ы й раздел работы 1. В ы п о л н и т е о б ы ч н ы е д е й с т в и я с п р о г р а м м о й Му4_1 к о м п и л я ц и ю , з а п и с ь на д и с к , з а п у с к ) . Program Му4_1; Uses Crt; Var a ,b:Boolean; Begin ClrScr; a:=True;b:=True;WriteLn(a:6,b: a:=True;b:=False;WriteLn(a:6,b:6,a a:=False;b:=True;WriteLn(a:6,b:6,a a:=False;b:=False;WriteLn(a:6,b:6,a ReadLn; End.
6,a
(набор,
And b: 6) ; And b: 6) ; And b: 6) ; And b:6);
П р и м е ч а н и е При наборе программы не забывайте использовать команды работы с блоками. Набирается a:=True;b:=True;WriteLn(a.6,b:6,a And b:6); а затем этот фрагмент выделяется как блок, копируется 3 раза и модифицируется. И з м е н и т е программу д л я проверки о с т а л ь н ы х рассмотренных выше логических операций. 2. В ы п о л н и т е о б ы ч н ы е д е й с т в и я с п р о г р а м м о й Му4_2 (набор, к о м п и л я ц и ю , запись на диск, запуск).
Основные управляющие конструкции 39 Program Му4_2; Uses Crt; Var т,п:Integer; Begin ClrScr; WriteLn('Введите число и количество ReadLn (m,n); WriteLn(' При сдвиге на ' , п , ' разрядов числа ',т,' получаем число ',т Shi п), WriteLn ('Введите число и количество ReadLn (m,n); WriteLn (' При сдвиге на ' ,п,' разрядов числа ' ,т,' получаем число ' ,т Shr п) ; ReadLn; End.
сдвигов'); влево сдвигов'); вправо
Введите в том и другом случае ч и с л а 32 и 1, убедитесь, что п о л у ч а ю т с я ч и с л а 6 4 и 16. Сдвиги в п р а в о о т р и ц а т е л ь н ы х ч и с е л п р и в о д я т к и н т е р е с н ы м р е з у л ь т а т а м . Н а п р и м е р , если Вы введете - 1 и 1 д л я того и другого сдвигов, то п о л у ч и т е - 2 и 3 2 7 6 7 . Е с л и п е р в ы й р е з у л ь т а т вполне о б ъ я с н и м , то второй требует в с п о м н и т ь о п р е д с т а в л е н и и о т р и ц а т е л ь н ы х ц е л ы х ч и с е л в доп о л н и т е л ь н о м коде. П у с т ь у нас не ш е с т н а д ц а т ь р а з р я д о в д л я п р е д с т а в л е н и я ч и с е л (тип Integer), а 4. П р е д с т а в л е н и е - 1 в доп о л н и т е л ь н о м коде есть 1 1 1 1 2 . Сдвиг вправо на один р а з р я д п р и в о д и т к ч и с л у 0 1 1 1 2 , а это не что и н о е , к а к 7 1 0 . 3. В ы п о л н и т е набор п р о г р а м м ы Му4_3, н а б р а в т о л ь к о п е р в ы е т р и о п е р а т о р а WriteLn). О т к о м п и л и р у й т е ее, з а п и ш и т е на диск. Program Му4_3; Uses Crt; Begin ClrScr; WriteLn(1365 WriteLn(1365 WriteLn (1365 WriteLn (1365 WriteLn(1365 WriteLn(1365 ReadLn; End.
And 2730); Or 2730); Xor 2730); And $FF) ; And $FF0); And $FF00);
Часть первая М ы видим, что с величинами типа Integer м о ж н о выполнять логические операции, они выполняются поразрядно над двоичн ы м и представлениями чисел. Почему выбраны числа 1365 и 2730? Двоичное представление этих чисел имеет вид: 1365 10 =010101010101 2 , 2730i 0 =101010101010 2 (рассматриваются только 12 м л а д ш и х разрядов). Операция And, дает в результате число 0, а операции Or и Хог — 4095. Поэкспериментируйте с этой версией программы. Убедитесь, например, что -256 And 256=0, а -256 Or 256=-1 и -256 Хог 2 6 6 — 1 . Попытайтесь дать разумное объяснение этому результату. Добавьте к программе следующие три оператора WriteLn. В шестнадцатеричной системе счисления для обозначения цифр 10, 11, 12, 13, 14, 15 используются соответственно буквы латинского алфавита А, В, С, D, Е, F. Двоичное представление F — 11112. Знак $ означает, что величина (константа) записана в шестнадцатеричной системе счисления. Запустите программу. Убедитесь в том, что результат равен 85, 1360, 1280. Его правильность подтверждается выделением соответствующих разрядов из числа 010101010101 2 и переводом остатка в десятичную систему счисления. Исследуйте описанным способом представление в дополнительном коде отрицательных целых чисел. З а д а н и я для самостоятельной работы 1. В математической логике известна функция следования, или импликация, (х=>у), ее таблица истинности имеет вид X False False True True
-
Y False True False True
X^y True True False True
Проверьте, что х=>у эквивалентно Not(x) Or у. Составьте программу проверки эквивалентности этих двух логических функций. 2. В математической логике известна функция Ш е ф ф е р а (х I у), ее таблица истинности имеет вид X False False True True
"
У False True False True
xly True True True False
41
Основные управляющие конструкции
Проверьте, что х | у эквивалентно Not(x) Or Not(y). Составьте программу проверки эквивалентности этих днух логических ф у н к ц и й . 3. В математической логике известна функция Вебба, или стрелка Пирса, (xlly) ее таблица истинности имеет вид
[
XUy
X
У
False
False
True
False
True
False
True
False
False
True
True
False
Проверьте, что xlly эквивалентно Not(х) And Not(у). Составьте программу проверки эквивалентности этих двух логических ф у н к ц и й . 4. Дана логическая ф у н к ц и я , например, (x=>y)=>z. Построить таблицу истинности данной функции. Схема построения приведена в таблице. В первом столбике приведены возможные значения наборов переменных х, у и z (значение True обозначено как единица, значение False — как нуль). X=>y
II
I
(x=>y)=>z
000
1
00 1
1
0 1
0 1 0
1
0
0 1 1 1 0 0
1 0
1
101
0
1
110
1
0
111
1
1
Преобразуйте эту формулу в эквивалентную ей. Составьте программу проверки эквивалентности этих двух логических формул. 5. Постройте таблицы истинности для следующих функций: • • • • • •
(x|y)|z; (xlly)Uz; (х=>у) And z; Not (x Or Not(y) And z); x And Not(у Or Not(z)); Not(Not(x) Or у And z).
Часть первая
42
6. Дана логическая функция (xUy)U(zUv). Построить таблицу истинности. Возможных значений наборов переменных х, у, z и v в данной задаче шестнадцать. Обозначим значение True единицей, а значение False — нулем. х у ZV 00 0 0 00 0 1 00 1 0
zUv
1
0 0
(xUy)U(zUv)
1
1
0 i
0 0
00 1 1
1
j
0
|
0
010 0 01 0 1
о 0
|
1 0
;
о
01 1 0
0
01 1 1 10 0 0
о 0
! I
о 1
10 0 1 10 10
0 0
I |
10 11
0 0
0 0 о 1
0
0
1110
0
0
1 1 1 1
0
0
110 0 110 1
I
хНу 1
1
0
1 1 I
0 1
ll
1
!
1 '
1
;!
Материал для чтения 1. Слово «логика» употребляется в разных значениях, например логика событий, логика характера и т. д. В этом случае имеется в виду определенная последовательность и взаимозависимость событий или поступков. Слово «логика» употребляется и в связи с процессами мышления. Когда говорят о логичном мышлении, то рассматривают его последовательность, доказательность и т. д. Итак, логика — особая наука о мышлении. Основателем ее считается древнегреческий философ Аристотель (IV в. до н. э.). Позднее она стала называться формальной логикой, и ее цель на протяжении всей истории развития неизменна: исследование того, как из одних утверждений можно выводить другие, при этом считается, что правильность рассуждения определяется только его логической формой и не зависит от конкретного содержания входящих в него рассуждений. В XIX веке благодаря усилиям английского ученого Джорджа Буля возникла наука математическая логика. Джордж Буль перенес на логику законы и правила алгебраических действий, ввел логические операции, предложил способ записи вы-
Основные управляющие конструкции 42
о к а з ы в а н и й в с и м в о л и ч е с к о й форме. А л г е б р а л о г и к и — раздел м а т е м а т и ч е с к о й л о г и к и , и з у ч а ю щ е й строение (форму, структуру) с л о ж н ы х в ы с к а з ы в а н и й и способы у с т а н о в л е н и я и х истинности с п о м о щ ь ю а л г е б р а и ч е с к и х методов. В ы с к а з ы в а н и е — повествовательное п р е д л о ж е н и е , относительно которого м о ж н о с к а з а т ь , истинно оно или ложно. Все в ы с к а з ы в а н и я условно р а з д е л я ю т с я н а п р о с т ы е и с л о ж н ы е , и л и составные. Составные в ы с к а з ы в а н и я образуются и з п р о с т ы х . В ы с к а з ы в а н и я х и у — п р о с т ы е , в ы с к а з ы в а н и е х And у — составное, оно н а з ы в а е т с я к о н ъ ю н к ц и е й и и м е е т 4 л о г и ч е с к и е в о з м о ж н о с т и , рассмотренн ы е в ы ш е , д л я о п р е д е л е н и я в о з м о ж н о с т и его и с т и н н о с т и . Выс к а з ы в а н и е х O r у т о ж е составное и н а з ы в а е т с я д и з ъ ю н к ц и е й . 2. Алгебра л о г и к и и ее з а к о н ы . Операции алгебры: к о н ъ ю н к ц и я , д и з ъ ю н к ц и я и о т р и ц а н и е . Эти операции позволяют производить т о ж д е с т в е н н ы е преобразования логических в ы р а ж е н и й . Законы: • з а к о н о д и н а к о в о с т и : х Or х = х ; х And х = х ; • з а к о н к о м м у т а т и в н о с т и : х Or у = у Or х, xAnd у = у And х; • з а к о н а с с о ц и а т и в н о с т и : х Ог(у Or z)=(x Or у) Or z, x And(у And z)=(x And y) And z; • з а к о н ы д и с т р и б у т и в н о с т и : x And (y Or z ) = x And у Or x And z — п е р в ы й ; x Or у And z = ( x Or у ) A n d ( x Or z) — второй; • з а к о н двойного о т р и ц а н и я : Not(Not(x))=x; • з а к о н ы де М о р г а н а : Not(x) Or Not(y)=Not(x And у); • з а к о н ы п о г л о щ е н и я : х Or х And у = х ; х And (х Or у ) = х ; • з а к о н ы , о п р е д е л я ю щ и е д е й с т в и я с л о г и ч е с к и м и констант а м и False и True: х Or False=x; х And False=False; х Or True=True\ x And True=x~, Not(False)=True; Not (True )= =False; Not(x) Or x=True; Not(x) And x=False. Дополнительные законы (они в ы в о д я т с я из основных нов): • з а к о н ы с к л е и в а н и я : х And у Or Not(x) And у=у; (х And (Not(x) Or y)=y; • з а к о н Б л е й к а - П о р е ц к о г о : x Or Not(x) And y = x Or • з а к о н с в е р т к и логического в ы р а ж е н и я : х And у Or And z Or у And z = x And у Or Not(x) And z.
закоOr у) y; Not(x)
П р и м е ч а н и я 1. Приведем пример вывода для закона Блейка-Порецкого: X Or Not(x) And у= X And True Or Not(x) And y= x And (y Or Not(y)) Or Not(x.) And y= x And у Or x And Nof(y) Or Not(x) And y= x And у Or x Arcd Nof(y) Or Not(x) And у Or x And y= x Or y.
Часть первая
44
2. Тип упражнений для закрепления материала может быть следующим. Дается логическое выражение, например, Not(Not(x) Or Not(у))=? И варианты ответов: Not(x) Or у или х Or у и т. д., необходимо выбрать правильный ответ. 3. Л о г и ч е с к и е ф у н к ц и и м о ж н о п р е о б р а з о в а т ь в две р а з л и ч ные формы: • дизъюнктивную нормальную форму (ДНФ); • конъюнктивную нормальную форму (КНФ). В первом случае логическая ф у н к ц и я записывается в виде дизъюнкции конъюнкций, образованных из переменных и их о т р и ц а н и й . Во в т о р о м с л у ч а е н а о б о р о т . Примеры: Not(x) Or у And z, х And у And z Or Not(y) And Not (z) — Д Н Ф , a x And (y Or Not(z)) - н е т , ( N o t ( x ) Or y) And z, x And у And (z Or Not(v)) — К Н Ф , x And (y And z Or Not(v)) — н е т . В с я к а я с л о ж н а я логическая ф у н к ц и я может быть преобразована к а к к Д Н Ф , так и к К Н Ф . Алгоритм преобразования: • з а п и с а т ь ф у н к ц и ю с и с п о л ь з о в а н и е м т о л ь к о о п е р а ц и й Or, And, Not-, • с п о м о щ ь ю з а к о н о в де М о р г а н а о п е р а ц и ю о т р и ц а н и я довести до о т д е л ь н ы х п е р е м е н н ы х и у б р а т ь в ы р а ж е н и я т и п а Not(Not(x)) по з а к о н у д в о й н о г о о т р и ц а н и я ; • с п о м о щ ь ю п е р в о г о з а к о н а д и с т р и б у т и в н о с т и у б р а т ь все к о н ъ ю н к ц и и д и з ъ ю н к ц и й и провести поглощение. В р е з у л ь т а т е п о л у ч и м Д Н Ф п р е д с т а в л е н и я л о г и ч е с к о й функ ц и и . Д л я п о л у ч е н и я з а п и с и в виде К Н Ф следует и з м е н и т ь третий пункт алгоритма: • с п о м о щ ь ю второго з а к о н а д и с т р и б у т и в н о с т и у б р а т ь все д и з ъ ю н к ц и и к о н ъ ю н к ц и й и провести поглощение. Е с л и все к о н ъ ю н к ц и и в Д Н Ф с о д е р ж а т все л о г и ч е с к и е перем е н н ы е и л и и х о т р и ц а н и я , то Д Н Ф н а з ы в а е т с я с о в е р ш е н н о й . Аналогично определяют и совершенную К Н Ф . (x=>y)=>Not(z) 00 1 01 о 0 1 1
1 оо
Основные у п р а в л я ю щ и е конструкции
45
Р а с с м о т р и м построение с о в е р ш е н н ы х Д Н Ф и К Н Ф на незначительно и з м е н е н н о м 4-м п р и м е р е и з р а з д е л а с а м о с т о я т е л ь н о й работы з а н я т и я . Д а н а л о г и ч е с к а я ф у н к ц и я (x=>y)=>iVot(z). Построим т а б л и ц у и с т и н н о с т и д а н н о й ф у н к ц и и . СДНФ ((х=>у) =>Not (z) ) =Not (х) And Not (у) AndNot(z) Or Not (x) And у And Not (z) Or x And Not (у) And Not (z) Or x And Not (y) And (z) Or x And у And Not (z) . СКНФ( (x=>y)=>Not (z) ) =Not (Not (x) And Not (y) And z) And Not (Not (x) And у And z) And Not (x And у And z) = (x Or у Or Not (z)) And (x Or Not (y) Or Not (z)) And (Not (x) Or Not (y) Or Not ( z ) ) .
Часть первая
46
З а н я т и е № 5. С о с т а в н о й о п е р а т о р и о п е р а т о р План занятия • обсуждение операторов; О эксперименты с программами нахождения из двух и трех чисел; • выполнение самостоятельной работы.
If
наибольшего
Составной оператор. Он состоит и з р я д а о п е р а т о р о в , вып о л н я е м ы х в т о й п о с л е д о в а т е л ь н о с т и , в к о т о р о й они з а п и с а н ы в п р о г р а м м е . Его с х е м а : Begin оператор; оператор; оператор; End; П р и м е ч а н и е Разделитель «;» перед End можно не записывать. Оператор I f , или, условный ется с л е д у ю щ и м образом:
оператор.
Оператор записыва-
В ы п о л н е н и е условного о п е р а т о р а н а ч и н а е т с я с в ы ч и с л е н и я значения логического в ы р а ж е н и я , записанного в условии. Простые у с л о в и я з а п и с ы в а ю т с я в виде р а в е н с т в и л и н е р а в е н с т в . С л о ж н ы е у с л о в и я с о с т а в л я ю т и з п р о с т ы х с п о м о щ ь ю логичес к и х о п е р а ц и й . К а к известно, з н а ч е н и е м л о г и ч е с к о г о в ы р а ж е н и я я в л я е т с я и л и True, и л и False. В первом случае в ы п о л н я е т с я с о п е р а т о р 1>, во втором — с о п е р а т о р 2 > . В к а ч е с т в е <оператор 1> и л и < оператор 2 > м о ж е т в ы с т у п а т ь л ю б о й о п е р а т о р я з ы к а п р о г р а м м и р о в а н и я Турбо П а с к а л ь , в ч а с т н о с т и и составной оператор, и у с л о в н ы й оператор. В п о с л е д н е м с л у ч а е получ а е м а я к о н с т р у к ц и я н а з ы в а е т с я в л о ж е н н ы м и у с л о в н ы м и опер а т о р а м и . Д о п у с к а е т с я з а п и с ь неполного у с л о в н о г о о п е р а т о р а , без ветви Else. В этом случае п р и з н а ч е н и и False н и к а к и х действий не п р о и з в о д и т с я . В з а п и с и у с л о в н ы х операторов в о з н и к а ет н е о д н о з н а ч н о с т ь т и п а : If < у с л о в и е 1> Then < о п е р а т о р 1> If < у с л о в и е 2 > Then с о п е р а т о р 2 > Else с о п е р а т о р 3 > ; Н е я с н о , к к а к о м у оператору If относится ветвь Else. Она (неоднозначность) р а з р е ш а е т с я по с л е д у ю щ е м у п р а в и л у : «Else отн о с и т с я к б л и ж а й ш е м у оператору I f , у которого е щ е отсутствует д а н н а я в е т в ь » .
Основные управляющие конструкции
47
Экспериментальный р а з д е л р а б о т ы 1. Р а з б о р о п е р а т о р а м о ж н о в ы п о л н и т ь на с л е д у ю щ е м простом примере. Вывести на экран наибольшее из двух данных чисел. Program МуЬ_1; Var х,у: Integer; Begin WriteLn ('Введите 2 числа'); ReadLn(х,у); I f х>у Then WriteLn(<)Else WriteLn(у); ReadLn; End. П о с т а в ь т е «;» после оператора WriteLn(x). Убедитесь, что поя в и л а с ь о ш и б к а «Error 113: Error in statement». Конструкция (оператор) If — Then — Else неделима, поэтому р а з д е л и т е л ь «;» недопустим. В случае равенства чисел В а ш а п р о г р а м м а выводит значение п е р е м е н н о й у. И з м е н и т е п р о г р а м м у т а к , чтобы в этом случае она в ы в о д и л а на э к р а н сообщение « Ч и с л а р а в н ы » . П о п р о б у е м н а й т и н а и б о л ь ш е е и з трех ч и с е л — з н а ч е н и я пер е м е н н ы х х , у и z. П р е д п о л о ж и м , что нет равенств, т. е. все ч и с л а р а з л и ч н ы . В о з м о ж н ы шесть р а з л и ч н ы х случаев, они прив е д е н ы на р и с у н к е .
П р о г р а м м а о п р е д е л е н и я з н а ч е н и я н а и б о л ь ш е г о и з трех чисел имеет вид Program Му5_2; Var х,у, z : IntegersBegin WriteLn ('Введите три числа ReadLn (х,у, z) ;
через
пробел');
Часть первая
48 I f (х>у) And (x>z) Then WriteLn (у) Else I f (y>x) And (y>z) Then WriteLn Else WriteLn (z) ; {* I f x>y Then I f a>z Then WriteLn (a) Else WriteLn (z) Else I f y>z Then WriteLn (y) Else WriteLn(z);*} ReadLn; End.
(y)
В т о р а я версия р е ш е н и я з а к л ю ч е н а в ф и г у р н ы е с к о б к и (комм е н т а р и и ) . Уберите их, в к л ю ч и т е первую р е а л и з а ц и ю к а к комм е н т а р и й , убедитесь в п р а в и л ь н о с т и р е ш е н и я . И з м е н и т е прог р а м м у т а к , чтобы а н а л и з и р о в а л с я и с л у ч а й равенства ч и с е л . Обратите в н и м а н и е на то, что п р и н а п и с а н и и с л о ж н ы х у с л о в и й простые у с л о в и я з а к л ю ч а ю т с я в с к о б к и . Это с в я з а н о с тем, что о п е р а ц и и с р а в н е н и я имеют более н и з к и й п р и о р и т е т , чем логические о п е р а т о р ы . Задания для самостоятельной работы 1. Имеется условный оператор: If DolO Then wnteln('ypa.r) Else Writeln( плохо...')-, Можно ли з а м е н и т ь его с л е д у ю щ и м и операторами: I f D=10 Then Writeln I'ура Writeln ('плохо... ') ; I f Not (D—l 0) Then Writeln Writeln('плохо. •. ') ; I f Not (D=l 0) Then Writeln Wn teln ('ура ! ' ) ; I f Not (DO10) Then Writeln Writeln ('ура!').
1
')
Else
Сура
'')
('плохо. ('плохо...')
Else .. ')
Else Else
2. З а п и ш и т е у с л о в н ы й оператор, в котором з н а ч е н и е переменной в ы ч и с л я е т с я по формуле: а+Ъ, если а — нечетное и а*Ь, если а — четное. 3. В ы ч и с л и т ь з н а ч е н и е ф у н к ц и и : ж2 + 5, при х> 3 х - 8, при х < 3 4. Вывести на экран номер четверти, которой п р и н а д л е ж и т точк а с координатами (х,у), при условии, что х и у отличны от 0. 5. Дано двузначное число. Н а п и с а т ь п р о г р а м м у определения: • я в л я е т с я л и сумма его ц и ф р д в у з н а ч н ы м числом;
Основные управляющие конструкции
6. 7. 8.
9.
10. 11.
49
• больше ли числа X сумма его цифр, число X вводится с клавиатуры; • кратна ли шести сумма его цифр; • больше ли цифра десятков цифры единиц; • оканчивается ли число цифрой 5. Придумать не менее 8 аналогичных задач с трехзначными числами. Написать фрагмент программы, подсчитывающий сумму только положительных из трех данных чисел. Даны три числа. Написать фрагмент программы, подсчитывающий количество четных чисел. Дано трехзначное число. Написать программу определения является ли оно палиндромом («перевертышем»), т. е. числом, десятичная запись которого читается одинаково слева направо и справа налево. Если целое число М делится нацело на целое число N, то вывести на экран частное от деления, в противном случае — сообщение «М на N нацело не делится». Составьте программу, которая уменьшает первое число в пять раз, если оно больше второго по абсолютной величине. Вычислить значение функции: х - 12, при ж > 0 5,
при х = 0
хг,
при х < 0
12. Даны три целых числа, найти среднее из них. Средним назовем число, которое больше наименьшего из данных чисел, но меньше наибольшего. 13. Составьте программу нахождения произведения двух наибольших из трех введенных с клавиатуры чисел. 14. Найти количество п о л о ж и т е л ь н ы х (отрицательных) чисел среди четырех целых чисел А, В, С и D. 15. Дано двузначное (трехзначное) число. Написать программу определения: • входит ли в него цифра 5; • входят ли в него цифры 4 и 7; • входят ли в него цифры 3, 5, 7. 16. Даны целые числа х и у. Написать программу определения знака разности х - у . Разность не вычислять. Разрешается сравнивать числа х и у с нулем; а между собой можно сравнивать только модули чисел х и у.
50
Часть первая
17. И з в е с т н а т е к у щ а я дата. П о л ь з о в а т е л ь вводит день, м е с я ц и год своего р о ж д е н и я . Н а п и с а т ь п р о г р а м м у , о п р е д е л е н и я , исп о л н и л о с ь и л и нет п о л ь з о в а т е л ю п о л н ы х 16 лет. 18. Н а п и с а т ь п р о г р а м м у о п р е д е л е н и я ф а к т а п о п а д а н и я т о ч к и М(х,у) в з а ш т р и х о в а н н у ю область, и з о б р а ж е н н у ю на рисунке.
19. Н а п и с а т ь п р о г р а м м у о п р е д е л е н и я п р и н а д л е ж н о с т и т о ч к и М(х,у) з а ш т р и х о в а н н о й области, и з о б р а ж е н н о й на р и с у н к е
20. Составьте программу в ы ч и с л е н и я в ы р а ж е н и я • max(x+y+z, xyz)+3; • m i n ( x 2 + y 2 , y 2 + z 2 ) - 4, если x,y,z вводятся с к л а в и а т у р ы . 21. Составьте п р о г р а м м у , к о т о р а я из трех введенных с к л а в и а т у р ы чисел возводит в к в а д р а т п о л о ж и т е л ь н ы е , а отрицател ь н ы е оставляет без и з м е н е н и я .
Основные управляющие конструкции
51
22. Д а н ы два к о н в е р т а п р я м о у г о л ь н о й ф о р м ы с д л и н а м и сторон (а,Ь) и (c,d). Определить, можно л и один из конвертов в л о ж и т ь в другой? 23. Составьте п р о г р а м м у , к о т о р а я о п р е д е л я л а бы вид треугольн и к а по д л и н а м его сторон a, b и с (если д а н н ы е о т р е з к и поз в о л я ю т его п о с т р о и т ь ) . Н а п о м н и м , что у с л о в и е м того, что т р е у г о л ь н и к м о ж е т быть составлен, я в л я е т с я одновременное в ы п о л н е н и е с л е д у ю щ и х условий: a + b > c , Ь+с>а, а+с>Ь, а такж е то, что т р е у г о л ь н и к и могут быть р а з н ы м и : равносторонн и м и , р а в н о б е д р е н н ы м и , п р я м о у г о л ь н ы м и , равнобедренным и п р я м о у г о л ь н ы м и и т.д. К р о м е того, следует учесть, что в к а ч е с т в е д л и н сторон могут быть случайно введены к а к нулевые, так и отрицательные значения. П р и м е ч а н и е Эту, хорошо известную задачу, иногда называют тестом Г. Майерса на профессиональную пригодность (из книги «Искусство тестирования программ»). Если Вы сможете реализовать в своей программе порядка 10 различных ситуаций, то Вам следует выбрать программирование своей специальностью Материал для чтения 1. Д а в а й т е обсудим то, чем м ы п ы т а е м с я з а н и м а т ь с я , т. е. п р о г р а м м и р о в а н и е . Определение: « П р о г р а м м и р о в а н и е — теор е т и ч е с к а я и п р а к т и ч е с к а я д е я т е л ь н о с т ь по обеспечению программного управления обработкой данных, в к л ю ч а ю щ а я создание программ, а т а к ж е выбор структуры и кодирования данных». Попробуем определить н е с к о л ь к о иначе, более к о н с т р у к т и в н о , р а с с м а т р и в а я то, чем з а н и м а е т с я п р о г р а м м и с т . Есть задача и л и проблема. В первую очередь п р о г р а м м и с т д о л ж е н определить в о з м о ж н о с т ь ее р е ш е н и я , в ы б и р а я соответствующий метод. Затем разработать проект п р о г р а м м ы , с о с т о я щ и й из алгоритма на каком-либо из языков программирования, доказать правильность работы программы и предусмотреть возможность ее изменения, внесения изменений на этапе сопровождения. Т а к и м образом, в укрупненном виде м ы видим три этапа: до программирования, п р о г р а м м и р о в а н и е и после п р о г р а м м и р о в а н и я . Только часть работы с в я з а н а с выбором с т р у к т у р д а н н ы х и к о д и р о в а н и е м — использованием я з ы к о в программирования. Программирование есть, если т а к м о ж н о в ы р а з и т ь с я , и н ж е н е р н а я работа по констр у и р о в а н и ю некой целостной системы обработки д а н н ы х . Отл и ч и е п р о г р а м м ы , н а п р и м е р , от некоторой м е х а н и ч е с к о й сист е м ы в том, что число в з а и м о д е й с т в у ю щ и х частей в п р о г р а м м е н а с т о л ь к о в е л и к о , что не поддается н и к а к о м у р а з у м н о м у объ-
52
Часть первая
яснению, и проверить работу этой программной системы, перебирать все возможные способы взаимодействия ее частей немыслимо даже на сверхбыстродействующих компьютерах в разумные сроки. Где ж е выход? Видимо, только в технологиях, обеспечивающих на выходе качественный и н а д е ж н ы й продукт. «Технология — совокупность методов обработки, изготовления, изменения состояния, свойств, форм сырья, материала или полуфабриката в процессе производства или наука о способах воздействия на сырье, материалы или полуфабрикаты соответствующими орудиями производства» (словарь иностранных слов). Приведем более подходящее определение. «Технология — это способ реализации людьми конкретного сложного процесса путем разделения его на систему последовательных взаимосвязанных процедур и операций, которые выполняются более и л и менее однозначно и имеют целью достижение высокой эффективности. Под процедурой понимается набор действий (операций), посредством которых осуществляется тот или иной главный процесс (или его отдельный этап), выражающий суть конкретной технологии, а операция — это непосредственное практическое решение задачи в рамках данной процедуры, т. е. однородная логически неделимая часть конкретного процесса». В нашем случае технология должна поддерживать все три этапа работы программиста, о которых речь шла выше, ибо технология в данном случае есть искусство, мастерство изготовления, конструирования систем обработки данных. Обычно, когда говорят о технологиях программирования, то имеют в виду этап разработки программ, первый и третий этапы не рассматриваются. Действительно, можно считать этот этап решающим, качественный выход на этом этапе обеспечивает достаточную простоту третьего этапа. Его часто трактуют к а к этап сопровождения, но мы вкладываем более широкий смысл — эволюционное изменение системы обработки данных. 2. Поговорим о простых и сложных программах. Первые имеют ограниченную область применения, разработаны и сопровождаются одним человеком. Они могут быть профессионально изготовлены, но так или иначе они обозримы, и их сложность не превышает интеллектуальные возможности человека. Но не все программы простые. Программы управления железнодорожными или воздушными сообщениями, системы управления базами данных, обеспечивающих параллельный доступ многих пользователей и т. д. — это другой класс программ по уровню их сложности. Считается, что сложность программ от-
Основные управляющие конструкции
53
нюдь не случайное свойство, скорее необходимое. Где пролегает г р а н и ц а м е ж д у простым и с л о ж н ы м ? Обычно сложность программ определяется количеством операторов исходного текста п р о г р а м м ы . Это условная г р а д а ц и я , простая количественная мера, которую м о ж н о п р и н я т ь в качестве первого приближен и я . И з в е с т н ы разработки программ с менее 10000 операторами исходного текста, относящиеся по в ы п о л н я е м ы м ф у н к ц и я м к сложным и даже сверхсложным. Программа Простая
Количество операторов и с х о д н о г о текста (до) 1000
Средней сложности
10 000
Сложная
100 000
Сверхсложная
1 000 000
Гиперсложная
10 000 000 и более
I !
Ограничимся и н т у и т и в н ы м пониманием простого и сложного. Ф и л о с о ф с к и й а н а л и з этих понятий применительно к программам — предмет отдельного разговора. Отметим еще одну особенность больших программ. Они имеют тенденцию к эволюции в процессе их использования. Это часто называют сопровождением, но сопровождение есть устранение ошибок, а эволюц и я подразумевает внесение в программу изменений, обусловл е н н ы х и з м е н я ю щ и м и с я требованиями к ней. К а к «бороться» со сложностью? П р и н ц и п известен со времен древних греков — «разделяй и властвуй». Сложную задачу следует разделить на взаимосвязанные подзадачи. Последние в свою очередь опять разделяются на свои подзадачи и т. д., вплоть до самых н и з ш и х уровней нашего п о н и м а н и я задачи. Что м ы имеем? Во-первых, с л о ж н а я задача (проблема, система) описывается некой иерархической структурой, во-вторых, определяются принципы ее декомпозиции и, в-третьих, так как каждый уровень — это определенный уровень абстрагирования, создается инструментарий для описания абстракций. Иерархия — это р а н ж и р о в а н н а я , или упорядоченная, система абстракций, расположение частей и л и элементов целого в порядке от высшего к н и з ш е м у . В результате деятельности программиста создается и е р а р х и ч е с к а я структура из абстракций, а «абстракц и я — это такие существенные х а р а к т е р и с т и к и некоторого объекта, которые отличают его от всех других видов объектов и, т а к и м образом, четко определяют особенности данного объекта с точки зрения дальнейшего рассмотрения и анализа». Г. Бруч разделяет абстракцию сущности объекта — объект
54
Часть первая
представляет собой модель существенных сторон предметной области и абстракцию поведения — объект состоит из обобщенного множества операций, к а ж д а я из которых в ы п о л н я е т определенную ф у н к ц и ю . Другими словами, сущность объекта мы описываем на уровне д а н н ы х в программе, а поведение — действиями над этими д а н н ы м и . В ы с к а ж е м еще один тезис. На н а ш взгляд, программа — это не что иное, к а к модель реальной действительности, реальной задачи, причем д и н а м и ч е с к а я модель. 3. В этом и последующих материалах для ч т е н и я мы коснемся истории р а з в и т и я технологий программирования. Операциональное программирование. Этот этап развития технологий программирования характерен для ЭВМ первого п о к о л е н и я (с 1945 до 1959 года). Быстродействие ЭВМ этого поколения до 50 тысяч арифметических операций, объем оперативной п а м я т и в лучшем случае несколько килобайт ячеек. Ресурсы минимальны. Если сравнивать эти х а р а к т е р и с т и к и ЭВМ с современными компьютерами: быстродействие — м и л л и а р д ы операций в секунду, объемы п а м я т и — м е г а б а й т ы , то р а з л и ч и е п о р а з и т е л ь но. ЭВМ того времени п о н и м а л а только ц и ф р о в ы е к о м а н д ы , и п р о г р а м м а состояла из м н о ж е с т в а строк, с о с т о я щ и х из ц и ф р , и н т е р п р е т и р у е м ы х ц е н т р а л ь н ы м процессором. Н а п р и мер, 05 825 631 трактовалось к а к команда с л о ж е н и я двух чисел (код 05), з а п и с а н н ы х в я ч е й к и с н о м е р а м и 825 и 631. Мин и м а л ь н ы е ресурсы ЭВМ требовали с т р о ж а й ш е й э к о н о м и и оперативной п а м я т и и эффективных алгоритмов обработки. Программа по взаимосвязи составных частей напоминала «спагетти», примерно то, что изображено на рисунке.
Представим программу, состоящую из тысячи т а к и х строк, и отдадим должное программистам того времени. Производительность программистов очень н и з к а я , так к а к ему вручную необходимо было распределить все переменные своей программы в оперативной памяти (!). Следующий этап развития технологий программирования мало отличается от первого. Он связан с
Основные управляющие конструкции
55
ЭВМ второго п о к о л е н и я . П о я в и л и с ь я з ы к и п р о г р а м м и р о в а н и я типа ассемблера и автокода. Различие, на примере нашей команды, заключается в том, что команда сложения записывалась с использованием м н е м о н и к и — ADD (английское сложить) P R 1 , ZET, где ADD — код команды, P R 1 , ZET — имена ячеек. Перевод программы (трансляция), записанной т а к и м образом, в цифровое представление, а только такое понимает ЭВМ, осуществлялся с помощью с п е ц и а л ь н ы х п р о г р а м м , н а з ы в а е м ы х ассемблерами. Ч е м х а р а к т е р и з у е т с я этот этап р а з в и т и я технологий? Его м о ж но назвать операциональным программированием. П е р в ы й и т р е т и й этапы работы программиста не обсуждаются. П р о г р а м м а «собирается» из м е л к и х деталей, отдельных операций и имеет достаточно простую структуру, если исключить п р и н ц и п «спагетти» из у п р а в л е н и я в ы ч и с л и т е л ь н ы м процессом. Уровень абстрагирования — отдельное действие, принц и п ы д е к о м п о з и ц и и задачи отсутствуют, во в с я к о м случае, о них не говорят. Существует р а з р ы в м е ж д у т р е б о в а н и я м и практ и к и и в о з м о ж н о с т я м и п р о г р а м м и р о в а н и я . Круг р е ш а е м ы х с помощью ЭВМ задач достаточно ограничен — в основном расчетные з а д а ч и .
Часть первая
56
З а н я т и е № 6. О п е р а т о р For План занятия • обсуждение оператора; р разбор программ (с использованием ручной трассировки логики) определения того, что число является палиндромом, в записи четырехзначного числа ровно три одинаковые цифры; • выполнение самостоятельной работы. Оператор For. Оператор задает многократное выполнение некоторого оператора (может быть и составным) с одновременным изменением значения управляющей переменной. Вид оператора For: For управляющая переменная>.= А Го В Do <оператор>, For управляющая переменная> =А DownTo В Do <оператор>, где А — начальное значение управляющей переменной, В — конечное значение управляющей переменной.
Начальное (А) и конечное (В) значения управляющей переменной могут быть представлены константами, переменными или арифметическими выражениями. Они определяются один раз в начале выполнения оператора For и не изменяются во время выполнения этого оператора. Если окажется, что А>В при использовании слова Го, то оператор после слова Do («тело» цикла) не будет выполнен ни разу и выполнение цикла с параметром сразу же закончится (соответственно при DownTo, если А<В). Управляющая переменная, а также А и В должны быть одного типа, обязательно порядкового. Оператор после слова Do выполняется один раз для каждого значения управляющей переменной из диапазона, определяемого значениями А и В. Если в операторе For используется слово То, то значение управляющей переменной увеличивается на единицу при каждом повторении А, А+1,..., В—1, В, при DownTo — уменьшается на единицу. Рекомендации по использованию. Оператор For применяют тогда, когда известно число повторений одного и того же действия (оператора). Изменение значения управляющей переменной в теле цикла может привести к ошибкам, считается «дурным тоном» в программировании, поэтому договоримся о том, что это действие запрещено законом, т. е. учителем. Управляющая переменная после выполнения оператора For имеет неопределенное значение. Запретим использование ее значения для анализа чего-либо после выполнения оператора For, а также
Основные управляющие конструкции
57
«искусственные» выходы из For с помощью операторов GoTo, Exit и т. д. Оператор For должен иметь одну точку входа и одну точку выхода, он оператор! Экспериментальный раздел работы 1- Дано натуральное число п (п<9999). Определить, является ли оно палиндромом («перевертышем»), с у ч е т о м ч е т ы р е х цифр. Например, палиндромами являются числа: 2222, 6116, 0440. Начнем не с программы, а с ручной трассировки логики реш е н и я . Это в а ж н о , очень важно. Мы с Вами с помощью этого приема д о л ж н ы достичь того, чтобы при написании программы у Вас одновременно складывался «зрительный образ» ее работы, Вы видели ее работу, причем это должна быть не статическая «картинка», а динамическая. Трассировка обычно выполняется д л я к о н к р е т н ы х значений входных параметров задачи. Итак, у нас четырехзначное число, поэтому переменная оператора For изменяется от 1 до 4. В переменной с именем m хранится «остаток» числа, в первоначальный момент времени он равен введенному числу. В переменной с именем г формируем значение числа — «перевертыша». Основными операциями являются: r~10*r + т. Mod. 10 (добавление очередной цифры к числу «перевертышу») и т:=т Div 10 (изменение проверяемого числа). Процедура трассировки приведена в таблице. После ее выполнения написание программы — «техническая работа».
Program Му6_1; Var п,т, г, i:Integer; Begin WriteLn С Введите целое число, не большее ReadLn (п) ; m: =п ; г: = 0 ; For i:=l То 4 Do Begin {так как число четырехзначное) r : = r * 1 0 + m Mod 10; m:=m Div 10; End;
9999');
Часть первая
58 I f r=n Then ReadLn; End.
WriteLn
('ДА')
Else
WriteLn('НЕТ');
И з м е н и т е п р о г р а м м у т а к , ч т о б ы была в о з м о ж н о с т ь обрабатывать целые числа из диапазона Longlnt. 2. Д а н ы н а т у р а л ь н ы е ч и с л а n, к (п, к < 9 9 9 9 ) . И з ч и с е л от п до к в ы б р а т ь те, з а п и с ь к о т о р ы х с о д е р ж и т ровно три о д и н а к о в ы х ц и ф р ы . Например, числа 6766, 5444, 0006, 0060 содержат ровно т р и о д и н а к о в ы х ц и ф р ы .
Е с л и д а н н о е число с о д е р ж и т ровно три о д и н а к о в ы х ц и ф р ы , то т о л ь к о одна и з ц и ф р о т л и ч а е т с я от о с т а л ь н ы х , то есть возм о ж н ы ч е т ы р е с л у ч а я , п р и в е д е н н ы х в т а б л и ц е . П у с т ь в качестве п и к введены ч и с л а 3732 и 3 7 4 0 . В п е р е м е н н ы х a l , а2, аЗ, а4 х р а н и м з н а ч е н и я ц и ф р т е к у щ е г о ч и с л а i. i 3732 3733 3734 3735 3736 3737 3738 3739 3740
а1 3 3 3 3 3 3 3 3 3
a2 7 7 7 7 7 7 7 7 7
аЗ 3 3 3 3 3 3 3 3 4
a4 2 3 4 5 6 7 8 9 0
Результат сравнения ложь истина ложь ложь ложь ложь ложь ложь ложь
П р и м е ч а н и е Пусть Вас не смущает «элементарность» выполняемых действий. Вспомните одно из качеств «великого программиста». Program Муб 2; Var п, к,i,al,а2,аЗ,а4,т:Integer; Begin WriteLn (' Введите два числа, не больших 9999'); ReadLn (п, к); For I:=л То к Do Begin
Основные управляющие конструкции 58 m:=i; {выделение цифр: al - первая, а2 вторая, аЗ — третья, а4 — четвертая) а 4:=m Mod 10; т:=ю Div 10;аЗ:=т Mod 10; т:=т Div 10; а2:=т Mod 10; al:=m Div 10; I f ( (al=a2) And (al=a3) And (alOal)) Or {первое условие} ((al=a2) And (al=a4) And (al<>a3)) Or {второе условие} ((al=a3) And (al=a4) And (al<>a2)) Or {третье условие} ((a2=a3) And (a2=a4) And (a2<>al)) {четвертое условие} Then WriteLn (l: 5) ; End; ReadLn; End. И з м е н и т е п р о г р а м м у д л я обработки 4, 5 и л и 6 - з н а ч н ы х чисел. Е с л и В а ш е р е ш е н и е будет идейно к о п и р о в а т ь п р и в е д е н н о е , то это х о р о ш о , но не очень. Все ж е оно будет достаточно громозд к и м . П о с т а р а й т е с ь н а й т и другое р е ш е н и е , его не о б я з а т е л ь н о о ф о р м л я т ь в виде п р о г р а м м ы . Задания для самостоятельной работы П е р е д н а п и с а н и е м п р о г р а м м требуется в ы п о л н и т ь р у ч н у ю т р а с с и р о в к у о с н о в н ы х ф р а г м е н т о в р е ш е н и я , естественно, ц и к л ы т р а с с и р у ю т с я не п р и всех з н а ч е н и я х у п р а в л я ю щ е й переменной. 1. Н а й т и все двузначные числа, в которых есть цифра N и л и само ч и с л о д е л и т с я на N . 2. Составить п р о г р а м м у возведения натурального ч и с л а в квадрат, и с п о л ь з у я с л е д у ю щ у ю з а к о н о м е р н о с т ь : I 2 =1 22=1+3 З 2 =1 + 3 + 5 42=1+3+5+7 п2
=1+3+5+7+9+...+2п-1
3. Определить количество трехзначных натуральных чисел, сумма ц и ф р к о т о р ы х р а в н а з а д а н н о м у ч и с л у N .
59 Часть первая
4. Составить программу в ы ч и с л е н и я суммы кубов чисел от 25 до 55. 5. Среди д в у з н а ч н ы х чиселнайти те, сумма квадратов ц и ф р которых делится на 13. 6. Н а п и с а т ь программу п о и с к а д в у з н а ч н ы х чисел, т а к и х , что если к сумме ц и ф р этого числа прибавить квадрат этой сумм ы , то получится это число. 7. К в а д р а т трехзначного числа о к а н ч и в а е т с я т р е м я ц и ф р а м и , которые к а к раз и составляют это число. Написать программу поиска т а к и х чисел. 8. Написать программу поиска четырехзначного числа, которое при делении на 133 дает в остатке 125, а при делении на 134 дает в остатке 111. 9. Н а й т и сумму положительных нечетных чисел, меньших 100. 10. Найти сумму целых положительных чисел из промежутка от А до В, к р а т н ы х 4 (значения переменных А и В вводятся с клавиатуры). 11. Найти сумму целых п о л о ж и т е л ь н ы х чисел, больших 20, мен ь ш и х 100, к р а т н ы х 3 и з а к а н ч и в а ю щ и х с я на 2, 4 или 8. 12. В трехзначном числе зачеркнули первую цифру слева, когда полученное двузначное число у м н о ж и л и на 7, то п о л у ч и л и данное число. Н а й т и это число. 13. Сумма ц и ф р трехзначного числа кратна 7, само число т а к ж е делится на 7. Найти все такие числа. 14. Среди четырехзначных чисел выбрать те, у которых все четыре ц и ф р ы р а з л и ч н ы . 15. Среди двузначных чисел найти те, сумма ц и ф р которых равна п(0<п<18) и число делится без остатка на ч и с л а q. 16. Дано четырехзначное число п. Выбросить из записи числа п цифры 0 и 5, оставив п р е ж н и м порядок остальных цифр. Например, из числа 1509 должно получиться 19. 17. Натуральное число из п ц и ф р я в л я е т с я числом Армстронга, если сумма его цифр, возведенных в n-ю степень, равна самому числу (например, 1 5 3 = 1 3 + 5 3 + 3 3 ) . Получить все числа Армстронга, состоящие из трех и четырех ц и ф р . 18. Дана последовательность из 20 целых чисел. Определить количество чисел в наиболее длинной подпоследовательности из подряд и д у щ и х нулей. 19. Дано натуральное число. Найти все его делители и их сумму.
Основные управляющие конструкции
61
Материал для чтения Из истории программирования. О нисходящем проектировании программ, структурном и модульном программировании. Третье п о к о л е н и е ЭВМ (наиболее и з в е с т н а я — I B M / 360) с в я з а н о с п о я в л е н и е м и н т е г р а л ь н ы х схем. Существенной частью ЭВМ с т а н о в я т с я о п е р а ц и о н н ы е системы, на к о т о р ы е в о з л а г а ю т с я з а д а ч и у п р а в л е н и я работой к о м п ь ю т е р а . Операционные с и с т е м ы — я д р о системного программного обеспечения. Р а з в и в а ю т с я я з ы к и п р о г р а м м и р о в а н и я высокого у р о в н я . Е с л и первые в е р с и и F O R T R A N I, ALGOL-58 обеспечивали з а п и с ь мат е м а т и ч е с к и х ф о р м у л и не более, то в этом п о к о л е н и и я з ы к о в р е а л и з у ю т с я н о в ы е идеи: п о д п р о г р а м м ы и р а з д е л ь н а я к о м п и л я ц и я ( F O R T R A N II); б л о ч н а я с т р у к т у р а и т и п ы д а н н ы х (ALGOL-6O); о п и с а н и е д а н н ы х и работа с ф а й л а м и (COBOL); обработка с п и с к о в и у к а з а т е л и (Lisp). В с л е д у ю щ и х в е р с и я х я з ы к о в продолжается развитие: P L / 1 (FORTRAN+ALGOL+COBOL), ALGOL-68 ( п р е е м н и к ALGOL-60), Pascal (развитие ALGOL-60), Sim u l a ( к л а с с ы , а б с т р а к т н ы е данные). Следует особо с к а з а т ь о я з ы к е P L / 1 . В истории п р о г р а м м и р о в а н и я была, и, наверное, есть, и д е я с о з д а н и я универсального я з ы к а п р о г р а м м и р о в а н и я , позволяющего программировать задачи р а з л и ч н ы х классов, другими словами — всевозможные задачи. Обилие я з ы к о в программирования, а их зарегистрировано более трех тысяч, говорит о тщетности этих попыток. Возможности я з ы к о в программирован и я обеспечивают п о д д е р ж к у ( н а ч а л ь н ы е этапы) нисходящей технологии конструирования программ. Суть нисходящего конструирования п р о г р а м м в разбивке большой з а д а ч и на м е н ь ш и е подзадачи, к о т о р ы е могут р а с с м а т р и в а т ь с я отдельно. Основными п р а в и л а м и д л я успешного п р и м е н е н и я д а н н о й т е х н о л о г и и являются: • ф о р м а л и з о в а н н о е и строгое описание п р о г р а м м и с т о м входов ф у н к ц и й и выходов всех модулей п р о г р а м м ы и системы; • с о г л а с о в а н н а я р а з р а б о т к а с т р у к т у р д а н н ы х и алгоритмов; • о г р а н и ч е н и е на р а з м е р модулей. Н и с х о д я щ а я т е х н о л о г и я разработки п р о г р а м м не есть свод ж е с т к и х п р а в и л , скорее это основной п р и н ц и п , д о п у с к а ю щ и й в а р и а ц и и в соответствии с к о н к р е т н ы м и особенностями решаемой з а д а ч и . В свое в р е м я в обширной литературе по этому поводу г о в о р и л о с ь и о восходящей т е х н о л о г и и . В этом с л у ч а е р е ш е н и е ( п р о г р а м м а ) к а к бы « с к л а д ы в а л о с ь и з о т д е л ь н ы х к и р п и ч и к о в » , и з и з в е с т н ы х р е ш е н и й подзадач. Т а к и м образом, д а н н о й технологией оговаривается определенный п р и н ц и п
62
Часть первая
декомпозиции и и е р а р х и ч е с к а я структура п р о г р а м м ы . Важнейшей составляющей этой технологии я в л я е т с я структурное программирование ( я з ы к и п р о г р а м м и р о в а н и я П а с к а л ь , Модула-2). Профессор Э. Д е й к с т р а был первым инициатором структурного программирования. В 1965 году он в ы с к а з а л предположение о том, что оператор GO ТО мог бы быть и с к л ю ч е н из я з ы к о в программирования. Разумеется, структурное программирование представляет собой нечто большее, чем один л и ш ь отказ от оператора GO ТО. Структурное программирование — это некоторые п р и н ц и п ы н а п и с а н и я программ. Теоретическим и основаниями структурного п р о г р а м м и р о в а н и я я в л я ю т с я • формальные системы теории вычислимости (операторные схемы программы А. А. Ляпунова, системы Поста, алгор и т м ы Маркова, исчисление Черча); • анализ программ по нисходящей схеме, д е к о м п о з и ц и я , основанная н а разбивке задач по уровням 0, 1 к. В классической работе Бома и Д ж а к о п и н и показано, что так а я структура (иерархическая, разбитая на уровни) может быть реализована в я з ы к е , в к л ю ч а ю щ е м только ограниченное число у п р а в л я ю щ и х конструкций. Д л я реализации программ требуется три основных составляющих блока: • ф у н к ц и о н а л ь н ы й блок или к о н с т р у к ц и я следования; • к о н с т р у к ц и я обобщенного ц и к л а ; • к о н с т р у к ц и я п р и н я т и я двоичного и л и дихотомического решения. Графическое изображение этих блоков приведено на следующих трех рисунках.
Основные управляющие конструкции
63
Х а р а к т е р н ы е черты структурного стиля программирования: • простота и ясность (программа легко читается и анализируется, достаточное комментирование); • использование только базовых конструкций; • отсутствие сетевых структур в программе; • отсутствие многоцелевых функциональных блоков; • отсутствие неоправданно сложных арифметических и логических конструкций; • расположение в строке программы не более одного оператора я з ы к а программирования; • содержательность имен переменных. П р и м е р н е с т р у к т у р и р о в а н н о й л о г и к и приведен н а рисунке.
При этом процесс нисходящей разработки программы может продолжаться до тех пор, пока не будет достигнут уровень «атомарных» блоков, т. е. базовых конструкций (присвоения, if-then-else, do-while). Итак, если формулировать суть в сжатом виде, то в структурном программировании уточнен принцип декомпозиции задачи (в основном ее алгоритмического аспекта, управляющей
Часть первая
64
компоненты, т. е. действий, однако уровень и н т е г р а ц и и действий и д а н н ы х «на совести» разработчика) и сделана п о п ы т к а его строгой ф о р м а л и з а ц и и . К н и с х о д я щ е й технологии следует отнести и то, что называется модульным программированием. Достаточно независимые фрагменты задачи оформляются к а к модули. Создаются библиотеки модулей, определяется механизм в к л ю ч е н и я модулей в разрабатываемую программу. Модуль должен и м е т ь строго определенный интерфейс и скрытую часть, одну точку входа и одну точку выхода. Из ф о л ь к л о р а c o m p u t e r science — «модульность в программировании подобна честности в политике: к а ж д ы й утверждает, что она — одно из его достоинств, но к а ж е т с я , н и к т о не знает, что она собой представляет, к а к ее привить, обрести или добиться». Очередной этап развития принципов декомпозиции и абстрагирования. Схематично структура получаемой программы изображена на рисунке. модули
Следует отметить еще одно важное обстоятельство. В информатике существовало и существует к а к бы два программирован и я : теоретическое и практическое. Естественно, без теоретического программирования не было бы практического, но если строго следовать первому, то любую часть программы следует строить математическими методами, д о к а з ы в а я правильность ее работы. Вопросы взаимного в л и я н и я этих подходов — предмет отдельного исследования. В данном учебнике речь идет, в основном, о втором программировании. И если теоретическое программирование является уделом математиков — программистов и ему, в принципе, учат, в основном, на факультетах вычислительной математики и кибернетики классических университетов, то практическое программирование — это сочетание знаний, интеллекта (аналитических способностей ума) и здра-
Основные управляющие конструкции
65
вого смысла, другими словами это искусство разработки программ, причем этому искусству не учат практически нигде. Следует отметить, что на этом витке развития теоретическое программирование оказало очень большое влияние на становление и развитие технологий практического программирования. Проведем черту под этим этапом развития технологий программирования. Структурная технология предоставляет в распоряжение разработчиков строгие формализованные методы описания программ и принимаемых технических решений. Используется наглядная графическая техника (схемы, диаграммы). Однако труд этот не был автоматизирован, а вручную невозможно разработать и графически представить строгие формальные спецификации программы, проверить их полноту и непротиворечивость и тем более изменить. Программы имеют последовательную структуру (управление вычислительным процессом), идеи Э. Дейкстры реализованы в полной мере, что позволило сделать скачок в развитии технологий. Но, несмотря на возможность конструирования структур данных различной сложности, данные и действия над данными по-прежнему разделены. Разрыв между потребностями практики и возможностями разработки (по времени, надежности, возможности внесения изменений на стадии эксплуатации) сложных программ в пределах данной технологической схемы сохраняется. Образная формулировка сути этого разрыва заключается в том, что «мы не знаем, как этого достичь, у нас нет инструментария для обеспечения процесса правильной разработки программы, но так примерно должна работать эта программа».
З а н я т и е № 7. О п е р а т о р
While
План занятия • обсуждение оператора; • эксперименты с программами определения количества цифр в числе, преобразования натурального числа п в 1; • выполнение самостоятельной работы. Оператор While меет вид: |
While Логическое выражение» Do <оператор (составной оператор)?,
Оператор While содержит логическое выражение, значение которого (True или False) управляет повторным выполнением оператора (после служебного слова Do), им может быть и составной оператор. Значение выражения вычисляется перед выполнением оператора. Если результат равен True, то оператор выполняется, при значении False — нет. Таким образом, если в начале логическое выражение имеет значение False, оператор после Do вообще не выполняется. В операторе (составном операторе) обязательно изменение значений переменных, влияющих на значение логического выражения. При невыполнении этого условия получаем пример того, что называется «зацикливанием». Простейший пример — While True Do <что-то> — бесконечный цикл. Экспериментальный раздел работы 1. Дано натуральное число п. Подсчитать количество цифр данного числа. Количество цифр в числе п неизвестно, поэтому необходимо использовать оператор While. Использование For потребует или введения дополнительных переменных, или искусственного выхода из цикла, а мы договорились, что это плохо — каждый фрагмент нашей логики должен иметь одну точку входа и одну точку выхода. Подсчет количества цифр начнем с последней цифры числа. Увеличим счетчик цифр на единицу (к). Число ( т ) уменьшим в 10 раз, убирая тем самым из него последнюю цифру (подсчитанную). Далее с получившимся числом проделаем ту же последовательность действий и т. д., пока число не станет равным нулю. Для 653 этого и всех остальных примеров занятия, требуется выполнять ручную «трассировку». Пусть введено число 65387, присвоим это [ значение переменной с именем т , значение
|
Основные управляющие конструкции
67
с ч е т ч и к а ч и с л а ц и ф р (к) равно 0. В ы п о л н и м д е й с т в и я , описанн ы е в ы ш е , и х р е з у л ь т а т приведен в т а б л и ц е . И т а к , окончательное з н а ч е н и е п е р е м е н н о й к равно 5, в ч и с л е 5 ц и ф р . После этого работаем с п р о г р а м м о й Му7_1 по традиционной схеме: набор, к о м п и л я ц и я , с о х р а н е н и е , запуск и п р о в е р к а работы на к о н к ретных примерах. Proqra Му"_1 : Var г), г: Longh t; к: Integer;{счетчик числа цифр) Beg in Wr-teLn (' Введите целое число неравное С); ReadLn (п) ;гг : =ч ;к: =0; While тОС Do Eegm In с (к) ; n:=ir Div 10; En d ; WriteLn (' В числе ',л,' — ',к,' цифо ' ' ) ; ReadLn; End. М о д и ф и ц и р у я программу Му7_1, р е ш и т ь следующие задачи: • найти сумму цифр числа; • н а й т и п е р в у ю ц и ф р у ч и с л а , н а п р и м е р д л я ч и с л а 7265 это ц и ф р а 7; • п о м е н я т ь п о р я д о к ц и ф р ч и с л а на обратный. Н а п р и м е р , было 1 2 3 4 5 , стало 54321; • найти количество четных цифр числа; • н а й т и с а м у ю б о л ь ш у ю ц и ф р у числа; • н а й т и с у м м у ц и ф р ч и с л а , б о л ь ш и х 5; • ответить н а вопрос, с к о л ь к о раз д а н н а я ц и ф р а встречается в числе? П р и д у м а т ь еще 5 задач ( к а к м и н и м у м ) , р е ш а е м ы х по данной схеме. 2. В п р о г р а м м е Му7_2 р е а л и з о в а н а «гипотеза С и р а к у з » . Осущ е с т в л я е т с я последовательное преобразование натурального ч и с л а п в 1. З а п у с т и т е п р о г р а м м у , п р о в е р ь т е ее работу при нескольких значениях п. Результат есть. Мы делаем предп о л о ж е н и е , что при любом з н а ч е н и и п работа программы зав е р ш и т с я , т. е. будет получен р е з у л ь т а т . О д н а к о это неизвестно, доказательства ф а к т а завершения работы программы ( а л г о р и т м а ) п р и любом з н а ч е н и и п в н а с т о я щ е е в р е м я ник е м не п о л у ч е н о . П р о в е р к а ц и к л о в т и п а While требует особо т щ а т е л ь н о й работы, ибо ц и к л ы этого т и п а «потенциально б е с к о н е ч н ы во в р е м е н и » .
67 Часть первая
Program Му7_2; Var п:Integer; Begin WriteLn(Введите натуральное R e a d L n (п) ; Write (п) ; While п<>1 Do Begin I f n Mod 2=0 Then n:=n Div Else n:=(3*n+l) Div 2; Write ( ' — \n) ; End; ReadLn; End.
число:');
2
С помощью последовательного з а п у с к а п р о г р а м м ы оцените среднюю д л и н у цепочек чисел при и з м е н е н и и п от 2 до 20. К а к избавиться от этой ручной работы по запуску п р о г р а м м ы ? Наблюдая за п о л у ч е н н ы м и ц е п о ч к а м и ч и с е л н е т р у д н о з а м е т и т ь , что ф р а г м е н т ы этих цепочек часто п о в т о р я ю т с я . Н а п р и м е р , 8=>4=>2=>1, 5=>16=>8=>4=>2=>1. К а к использовать этот ф а к т п р и подсчете средней д л и н ы цепочек д л я чисел (л) из большого интервала, н а п р и м е р т и п а Integer? Потребуется л и в п р и в е д е н н о м фрагменте программы что-либо и з м е н я т ь в этом случае? Задания для самостоятельной работы. 1. Д а н а последовательность операторов: а :-1; Ь:=1; While a+b<8 Do Begin S:=a+b
a:=a+l;
b:=b+2
End;
Сколько раз в ы п о л н я е т с я проверка логического в ы р а ж е н и я в операторе W h i l e ? Определите з н а ч е н и я п е р е м е н н ы х а, Ь, и s после завершения этой последовательности операторов? 2. Определите значения переменных а и b после выполнения операторов: а: =1 ; Ь:=1; While а<=3 Do а:=а+1;
b:=b+l.
3. Определите значение переменной s после в ы п о л н е н и я следую щ и х операторов: • s:=0; • s:=0; dec(i)
i:=0; While i<5 Do Inc(i); i:=1; While 1>1 Do Begin End;
s:=s+100 s:=s+100
Div Div
i; i;
Основные управляющие конструкции 68
4 . Д а н ф р а г м е н т п р о г р а м м ы с о ш и б к а м и (их не б о л ь ш е 5) выч и с л е н и я ф а к т о р и а л а f ч и с л а п: А : -1; f:= 0; While к<п Do к:=к + 1 ;
f=f*k
Н а й д и т е эти о ш и б к и . 5. Н а й д и т е и и с п р а в ь т е о ш и б к и в с л е д у ю щ е м ф р а г м е н т е программы, определяющей для заданного натурального числа п число, записанное ц и ф р а м и числа п в обратном порядке. р • =л ; While р>=0 Do Begin а:=з+р Mod 10; р: =р Div 10 End; П р и м е ч а н и е Задания № 1 - 5 рекомендуется выполнять, используя режим ручной трассировки. 6. Н а й т и м и н и м а л ь н о е ч и с л о , б о л ь ш е е 3 0 0 , которое н а ц е л о дел и т с я н а 19. 7. П р и п и с а т ь п о 1 в н а ч а л о и в к о н е ц з а п и с и ч и с л а п. Н а п р и мер, было п = 3 4 5 6 , стало п = 1 3 4 5 6 1 . 8. П о м е н я т ь м е с т а м и первую и последнюю ц и ф р ы ч и с л а . Например, и з ч и с л а 8 5 4 7 д о л ж н о б ы т ь п о л у ч е н о ч и с л о 7 5 4 8 . 9. П р и п и с а т ь к и с х о д н о м у ч и с л у п т а к о е ж е ч и с л о . Н а п р и м е р , и з числа 1903 должно быть получено число 19031903. 10. О п р е д е л и т ь , я в л я е т с я л и з а д а н н о е ч и с л о с т е п е н ь ю 3. 11. С о с т а в и т ь п р о г р а м м у , п р о в е р я ю щ у ю , я в л я е т с я л и з а д а н н о е н а т у р а л ь н о е ч и с л о п а л и н д р о м о м , то есть т а к и м , д е с я т и ч н а я запись которого читается одинаково слева направо и справа налево. П р и м е ч а н и е Задача отличается от ранее рассмотренной тем, что количество цифр в числе неизвестно, а из этого следует, что тип используемого цикла должен быть другой. 12. В ы я с н и т ь , я в л я е т с я л и п о с л е д о в а т е л ь н о с т ь ц и ф р н а т у р а л ь н о г о ч и с л а п р и просмотре и х с п р а в а налево в о з р а с т а ю щ е й последовательностью. Н а п р и м е р , д л я ч и с л а 7 6 4 3 1 ответ полож и т е л ь н ы й , для чисел 6331, 9782 — отрицательный.
69 Часть первая
13. Вводится последовательность целых ненулевых чисел, признак окончания ввода — ввод 0. Количество чисел не меньше 2. Выяснить: • является ли последовательность возрастающей; • есть ли в ней хотя бы одна пара одинаковых «соседних» чисел; • является ли последовательность знакочередующейся (3, - 2 , 4, - 5 , 0 — «да»; 5, - 4 , - 7 , 8, 0 — «нет»), 14. Выяснить, сколько раз в натуральном числе встречается его максимальная цифра. Например, в числе 581088 — 3 раза, в числе 4537 — 1 раз. 15. Выяснить, является ли разность максимальной и минимальной цифр числа четной. Материал для чтения Объектно-ориентированное программирование. Компьютеры 4-го поколения конструируются на основе БИС — больших интегральных схем и СБИС — сверхбольших. Персональные компьютеры определяют лицо компьютеров этого поколения. Скорости обработки огромны, так же к а к и объемы оперативной памяти. Избыточность программного кода в несколько тысяч строк не играет принципиальной роли. Технологии программирования, сделав виток, возвращаются на новом уровне к «детской игре в кубики». Но если в период первого, второго поколений программа «собиралась» из отдельных операций и пирамида Хеопса не получалась (она разваливалась), то на этом этапе развития пирамида собирается из объектов — кубиков, интегрирующих в единое целое данные и допустимые действия над этими данными — объектно-ориен тированное программирование (языки программирования Турбо Паскаль, начиная с версии 5.5, Смоллток, С++). Объектно-ориентированные я з ы к и программирования характеризуются тремя основополагающими идеями: инкапсуляцией, наследованием, полиморфизмом. Инкапсуляция. Сочетание данных с допустимыми действиями над этими данными приводит к «рождению» нового элемента в конструировании программы — объекта. «Рожденный ползать — летать не может» — и наш объект действует только так, к а к это в нем заложено, и только над тем, что в нем описано. Обращение к данным объекта не через его действия недопустимо. Наследование. Программист для решения определенного класса задач строит иерархию объектов, в которой, и это самое главное, к а ж д ы й следующий
Основные управляющие конструкции
71
п р о и з в о д н ы й о б ъ е к т и м е е т доступ (наследует) к д а н н ы м и дейс т в и я м всех своих п р е д ш е с т в е н н и к о в ( « п р а р о д и т е л е й » ) . Характер связей между объектами вертикальный. Полиморфизм. В ы д е л е н и е н е к о т о р о г о д е й с т в и я , т. е. действие д о л ж н о и м е т ь и м я и с о з д а н и е средств и с п о л ь з о в а н и я д е й с т в и я о б ъ е к т а м и и е р а р х и и . П р и ч е м к а ж д ы й о б ъ е к т р е а л и з у е т это действие т а к , к а к оно д л я него п о д х о д и т . П р и м е р : есть м н о ж е с т в о геометрич е с к и х ф и г у р , о б р а з у ю щ и х и е р а р х и ю . Д е й с т в и е — перемещение по э к р а н у . М ы в и д и м «скачок» в т е х н о л о г и и п р о г р а м м и р о в а н и я , в п е р в ы е д е й с т в и я и д а н н ы е образуют нечто единое — новый уровень абстрагирования. Д л я х а р а к т е р и с т и к и объектно-ориентированной технологии п р о е к т и р о в а н и я п р о г р а м м обратимся к к л а с с и ч е с к о й работе Г. Б р у ч а . Этой т е х н о л о г и и п р и с у щ и определенные п р и н ц и п ы а б с т р а г и р о в а н и я и д е к о м п о з и ц и и . З а д а ч а описывается некой и е р а р х и ч е с к о й с т р у к т у р о й из классов. Основным инструментом построения т а к о й с т р у к т у р ы я в л я е т с я р е а л и з а ц и я концепц и и н а с л е д о в а н и я . Наследование означает такое соотношение м е ж д у к л а с с а м и , когда один к л а с с использует структурную и л и ф у н к ц и о н а л ь н у ю часть одного и л и нескольких других классов (соответственно простое и множественное наследование). И н ы м и с л о в а м и , наследование — это и е р а р х и я а б с т р а к ц и й , в которой п о д к л а с с ы наследуют строение от одного и л и нескольк и х суперклассов. Логическое завершение в объектно-ориентир о в а н н ы х системах п о л у ч и л а к о н ц е п ц и я т и п и з а ц и и , к о т о р а я строится н а п о н я т и и типов а б с т р а к т н ы х д а н н ы х . «Тип — это точное определение свойств строения и л и поведения, которое п р и с у щ е некоторой совокупности объектов». П р и этом возможно определение к а к с т а т и ч е с к и х , так и д и н а м и ч е с к и х связей. Если, н а п р и м е р , Турбо-Паскалю п р и с у щ а строгая т и п и з а ц и я , при которой осуществляется к о н т р о л ь н а соответствие т и п а м д а н н ы х и с в я з и с т а т и ч н ы во времени, и м е н а связываются с типами во в р е м я к о м п и л я ц и и , и связь не и з м е н я е т с я во время работы п р о г р а м м ы , то в о б ъ е к т н о - о р и е н т и р о в а н н ы х средах возм о ж н а д и н а м и ч е с к а я с в я з ь ( п о з д н я я связь). Это о з н а ч а е т с и т у а ц и ю , когда тип всех переменных и в ы р а ж е н и й определяется только во в р е м я и с п о л н е н и я программы, что позволяет реализовать идею п о л и м о р ф и з м а . Это свойство я в л я е т с я самым существенным в объектно-ориентированном программировании наряду со свойством р е а л и з а ц и и абстракций. Именно это свойство отличает объектно-ориентированное программирование от более т р а д и ц и о н н ы х методов программирования с использованием типов а б с т р а к т н ы х д а н н ы х .
72
Часть первая
Итак, подведем итоги. Развитие технологии привело к созданию методов объектно-ориентированного анализа. По большому счету он мало отличается от того, который назывался структурным анализом на предыдущем этапе развития технологий. Если раньше результатом работы было алгоритмически-ориентированное решение задачи, то на этом этапе — определение основных объектов и взаимодействий между ними. Первый этап работы программиста по-прежнему плохо формализован и не обеспечен необходимой поддержкой. На втором этапе за счет интеграции данных и действий над данными (инкапсуляции), строгого определения принципов декомпозиции (наследование и полиморфизм, модульность), ограничения доступа к данным объекта появилась возможность создания более надежных и качественных программ. Этапы тестирования и сопровождения упростились. Они в большей степени становятся инженерной работой. Структура конечного продукта (программы) последовательная, как и, в целом, процесс его разработки. Потребности практики по-прежнему не соответствуют имеющимся возможностям. Интенсивно развиваются сетевые технологии, системы обработки текстовой информации, мультимедийные программные системы и т. д. Ответные шаги процесса развития технологий программирования мы рассмотрим в следующих материалах для чтения.
Основные управляющие конструкции
73
З а н я т и е № 8. О п е р а т о р
Repeat-Until
План занятия • обсуждение оператора; • э к с п е р и м е н т ы с п р о г р а м м а м и определения простоты числ а , н а х о ж д е н и я наибольшего общего д е л и т е л я двух чисел с помощью алгоритма Евклида; • в ы п о л н е н и е самостоятельной работы. Оператор Repeat ( п о в т о р я т ь ) - [ / п Ш (до тех пор, пока) содерж и т логическое в ы р а ж е н и е (после Until), которое у п р а в л я е т повторением в ы п о л н е н и я последовательности операторов, зап и с а н н ы х м е ж д у Repeat и Until. Повторение п р о д о л ж а е т с я до тех пор, п о к а логическое в ы р а ж е н и е не примет значение True. Последовательность операторов тела ц и к л а в ы п о л н я е т с я не менее одного р а з а . Repeat < о п е р а т о р 1>; < о п е р а т о р 2>; < о п е р а т о р п>; Until логическое
выражение>;
П р и использовании оператора Repeat-Until ( ц и к л а с постусловием) необходимо у ч и т ы в а т ь следующее: • перед п е р в ы м выполнением оператора логическое выраж е н и е его о к о н ч а н и я (или п р о д о л ж е н и я ) д о л ж н о быть определено; О последовательность операторов д о л ж н а содержать х о т я бы один оператор, в л и я ю щ и й на значение логического выраж е н и я , иначе оператор Repeat-Until работает бесконечно долго; D логическое в ы р а ж е н и е в конечном итоге д о л ж н о п р и н я т ь значение True. П р и м е р простейшей, бесконечной по времени работы, констр у к ц и и : Repeat ... Until False. Экспериментальный раздел работы 1. Целое положительное число р называется простым, если оно имеет только два делителя, а именно 1 и р. По соглашению 1 не считают простым числом. Начало последовательности простых чисел имеет вид:
Часть первая
74 2,
5 , 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, Н а у ч и м с я у с т а н а в л и в а т ь ф а к т : я в л я е т с я л и ч и с л о п прос т ы м ? Н и ж е п р и в е д е н т е к с т р е ш е н и я . С ч и т а е м , что д е л и т е л и ч и с л а н а х о д я т с я в и н т е р в а л е от 2 до п Div 2, т о ч н е е в и н т е р в а л е от 2 до ц е л о й ч а с т и Sqrt(n). Program Му8_1; Var i,n:Longlnt; Begin WriteLn С Введите натуральное число'); ReadLn (п) ; 1: =1 ; Repeat Inc(i) ; Until (i> n Div 2) Or (n Mod l = 0) ; I f i> n Div 2 Then Writeln ('Число ' ,n,' простое') Else WriteLn('Число ' , i , ' первый делитель числа ',n); ReadLn; End. Естественно, что эту з а д а ч у м о ж н о р е ш и т ь и с и с п о л ь з о в а н и ем о п е р а т о р а While. С д е л а й т е эту н е б о л ь ш у ю работу. А з а т е м и з м е н и т е п р о г р а м м у т а к , ч т о б ы о с у щ е с т в л я л с я в ы в о д всех делителей числа п. П о д с к а з к а Логическое выражение оператора Repeat-Until упростится, останется только условие i> n Div 2, а в операторах тела цикла появится — If n Mod i =0 Then WriteLn( ,i). Основная теорема арифметики. Всякое натуральное число п р а з л а г а е т с я на п р о и з в е д е н и я п р о с т ы х ч и с е л . Это р а з л о ж е н и е о д н о з н а ч н о с точностью до п о р я д к а з а п и с и п р о с т ы х ч и с е л в произведении. А сейчас з а д а н и е . В ы б е р и т е и н т е р в а л ч и с е л , не очень больш о й , н а п р и м е р от 1 0 1 до 120, и, и с п о л ь з у я п р о г р а м м у , н а й д и т е разложения этих чисел на произведения простых чисел. В а ш и записи д о л ж н ы иметь вид: и^Р/'Рг^'-Рп4. 24=23*31.
где ( а >0), р
— простые ч и с л а ,
например,
Основные управляющие конструкции
75
2. Н а и б о л ь ш и й общий делитель (НОД) двух целых чисел а и b — это н а и б о л ь ш е е целое число, которое делит нацело оба числа. Д л я н а х о ж д е н и я НОД чисел а и b м о ж н о представить оба ч и с л а в в и д е : а = р 1 ° 1 р 2 а 2 . . . . р а ' и b = p / 1 p / 2 . . . . p q P q a затем найти НОД(а,Ь)=р1тт(а1Р1)р2П1"11а2()2'...рч1Ш11("(1Рч) ; некоторые степен и п р о с т ы х ч и с е л могут быть и нулевые. О т л о ж и м исследов а н и е этого метода н а х о ж д е н и я НОД, у нас п о к а не хватает з н а н и й . Обратимся к алгоритму греческого м а т е м а т и к а Евкл и д а , он о т к р ы л его в 3 3 0 - 3 2 0 гг. до н. э. А л г о р и т м основан на следующем свойстве ц е л ы х чисел. Пусть а и b одновременно не р а в н ы е н у л ю ц е л ы е н е о т р и ц а т е л ь н ы е ч и с л а и а>Ь, тогда если Ь=0, то НОД(а,Ъ) =а, а если Ь*0, то д л я чисел a, b и г, где г — о с т а т о к от д е л е н и я а на Ь, в ы п о л н я е т с я равенство НОД(а,Ь)=НОД(Ь,г). Действительно, г = а M o d b = a - ( a .Diu b)*b. Е с л и какое-то число делит нацело и а, и Ъ, то из приведенного равенства следует, что оно делит нацело и числа г и Ь. Н а п р и м е р , пусть а = 4 8 , а Ь=18, н а й д е м их н а и б о л ь ш и й общ и й д е л и т е л ь . Приведем р у ч н у ю трассировку логики, она, к а к обычно, сведена в таблицу. Г " a ! 1 48 | I 48 Mod 18=12 I I I II
12
12 mod 6=0
b 18 18 18 mod 12=6 6 6
Результаты a>b a<b a>b a=0
J
НОД(48,18)=НОД(12,18) НОД(12,18)=НОД(12,6) НОД(12,6)=НС)Д(0,6) || НС)Д(0,6)=6
Т а к и м образом, НОД(48,18)=6. П р о г р а м м н а я р е а л и з а ц и я алгоритма Е в к л и д а с использованием оператора R e p e a t - U n t i l имеет вид Program Му8_2; Var a, b: Longlnt; Begin WriteLn {'Введите два числа о 0'); ReadLn (а ,Ь) ; Repeat I f a>b Then a:=a Mod b Else b:=b Mod a; Until (a = 0) Or (b=0) ; WriteLn ('НОД=',a+b) ); ReadLn; End.
Часть первая
76
И з м е н и т е п р о г р а м м у т а к , ч т о б ы вместо о п е р а т о р а RepeatUntil и с п о л ь з о в а л с я о п е р а т о р While. К а к о е о г р а н и ч е н и е в этом случае м ы м о ж е м у б р а т ь ? Последовательность чисел ei = l + l = 2 , е 2 = 2 + 1 = 3, е3=2*3+1=7, е 4 = 2 * 3*7 + 1 = 4 3 , е5=2*3*7*43+1=1807, е 6 = 2 * 3 * 7 * 4 3 * 1 8 0 7 + 1 = 32 634 4 3 , Е7=2*3*7*43*1807*3263443+1=547*607*1033*31051, и т.
д.
называют числами Евклида. Первые 4 числа наталкивают на м ы с л ь о том, что Е в к л и д о в ы ч и с л а п р о с т ы е . О д н а к о у ж е е 5 явл я е т с я с о с т а в н ы м — 1 8 0 7 = 1 3 * 1 3 9 . И з в е с т н о , что Е в к л и д о в ы ч и с л а в з а и м н о простые — НОД(е 1 ,е ] )=1 п р и i*]. П р о в е р ь т е этот факт с помощью программы Му8_2 для первых 6 чисел Евклида. Д л я работы с о с т а л ь н ы м и ч и с л а м и Е в к л и д а т и п а Longlnt недостаточно, необходимо, по к р а й н е й мере, и з у ч и т ь основы «длинной» а р и ф м е т и к и . И с п о л ь з у я п р о г р а м м у М у 8 _ 1 , покаж и т е , что число е 6 простое. М е т о д и ч е с к и е р е к о м е н д а ц и и В режиме ручной трассировки необходимо просчитать для заданий 1, 2 по 7 - 1 0 примеров. Найти НОД(342,612). 3. В т е о р и и ч и с е л , р а з д е л е д и с к р е т н о й м а т е м а т и к и , д о к а з ы в а е т с я с л е д у ю щ а я теорема. Если а и b одновременно не р а в н ы нулю, то существуют ц е л ы е числа х и у, т а к и е , что НОД(а,Ь)= = а * х + Ь * у . Теорема не у т в е р ж д а е т , что х и у о п р е д е л е н ы однозначно, она л и ш ь говорит о том, ч т о НОД(а,Ь) м о ж е т б ы т ь в ы р а ж е н в таком виде. Например, 6 = Н О Д ( 1 2 , - 3 0 ) = 1 2 * 3 + +(-30)*1=12*(-2)+(-30)*(-1). С ф о р м и р у е м последовательность: • а 0 = а , а 0 = а * х 0 + Ь * у 0 , где х 0 = 1 , а у 0 = 0 ; • ajHo, a 1 = a * x 1 + b * y 1 , где x t = 0 . а у х = 1 ; D
a
2 = a o _ a i * < l i > г Д е 1 i = a o D i v a i> п о д с т а в л я е м з н а ч е н и я а 0 и i> получаем a 2 = a * x 0 + b * y 0 - ( a * x I + b * y 1 ) * q 1 = a * ( x 0 - x 1 * q 1 ) + +b*(y0-y1*q1)=a*x2+b*y2; • a 3 - a 1 - a 2 * q 2 , где q 2 = a j Div a 2 , п о д с т а в л я е м з н а ч е н и я a j и a 2 , получаем a 3 = a * x 1 + b * y 1 - ( a * x 2 + b * y 2 ) * q 2 = a * ( x 1 - x 2 * q 2 ) - t +b*(y1-y2*q2)=a*x3+b*y3; a
• ...
Основные управляющие конструкции D
• • • О D
77
a =a
i i - 2 ~ a i - i * V i > г Д е <li-i = a i-2 D i v а 1-1' a 1 = a * x 1 _ 2 + b + y 1 _ 2 -(a*x 1 _ 1 +b*y l _ 1 )*q 1 _ 1 =a*x 1 +b*y 1 ; ... 0 = a k _ r a k * q k , где q k = a k _ 1 Div a k , ... 0 = a * x k + 1 + b * y k + r Ч т о м ы и м е е м ? И т е р а ц и о н н ы й процесс, в к о т о р о м : Ч , - ! ^ 2 Div
о xi=x1-2_xi-i*<i1-i; D y^y.^yi-iH-r Рассмотрим трассировку логики на примере чисел а=48 и Ь=18. J _0 1 2 1
а, 48 18 12 6
"j
х, 1 0 1 -1
у, 0 1 -2 3
q,
2 2
Итак, а=НОД(48,18)=6 и 6=48*(-1)+18*3. М е т о д и ч е с к и е р е к о м е н д а ц и и Проделайте аналогичную трассировку для нескольких примеров типа а=1292, Ь=798. П р о г р а м м н о е р е ш е н и е д а н н о й з а д а ч и и м е е т вид: Program Му8_3; Var а ,Ь, а 0 ,al,х0,xl,у0,yl,д,t:Integer; (Нижних индексов у имен переменных на языке Турбо Паскаль нет.} Begin WriteLn ('Введите два числа, первое должно быть больше второго'); ReadLn(a,b) ; а 0:=а;а 1:=Ъ;хО:=1;xl:=0;у0:=0;у1:=1; Repeat q:=aO Div al; t:=aO;aO:=al;al:=t-al*q; t:-xO;xO:=xl;xl:=t-xl*q; t:=y0;y0:=yl;yl:=t-yl*q; Until al=0; WriteLn (aO,' = ',a,'*(' ,x0,') + ' , b * (' , y 0 , ' ) ' ) ; ReadLn; End.
77 Часть первая
Сравните результаты решения примеров с помощью ручной трассировки с результатами работы программы при этих же исходных данных. Задания для самостоятельной работы 1. Числа вида 2р—1, где р — простое число, называются числами М. Мерсенна (1588-1648 гг.). Являются ли числа Мерсенна при значениях р 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31 простыми? Написать программу. Почему мы ограничились только этими простыми числами. Ваша программа должна состоять из двух частей: в первой — вычисляется число Мерсенна для значения р (вводится с клавиатуры) , во второй — проверяется является ли оно простым. 2. Линейные уравнения от двух переменных (линейные диофантовые уравнения), т. е. уравнения вида: а*х+Ь*у=с, в котором а и b не равны нулю одновременно, имеют целые решения тогда и только тогда, когда d (d=HOfl(a,b)) делит нацело значение с. Причем, если х 0 , у 0 — частное решение, то все решения имеют вид: x=x 0 ~n*(b Div d), y=y 0 +n*(a Div d) для всех п. Пример: 12*х-30*у=84, НОД(12,-30)=6, 84 делится нацело на 6. Уравнение разрешимо. Одним из его решений является (х,у)=(2,-2). Все остальные р е ш е н и я имеют вид: х=2+5*п, у = - 2 + 2 * п . Даны целые числа а, Ь, с. Написать программу определения разрешимости соответствующего диофантового уравнения и, если оно разрешимо, поиска частного решения. 3. Пусть а и b — ненулевые целые числа. Целое число т > 0 называется наименьшим общим кратным (НОК) чисел а и Ь, если m делится и на а, и на b нацело, а т а к ж е для любого с, которое делится нацело и на а и на Ь, верно, что оно делится нацело и на т . Если а и b — ненулевые числа, то их наименьшее общее кратное существует и справедливо равенство HOK(a,b)=.Abs('a*bJ Div НОД(а,Ь). Написать программу нахождения НОК двух ненулевых чисел. 4. Числа Фибоначчи (f n ) определяются формулами: f 0 = f 1 = l ; f n = f n - i + f n - 2 П Р И 11=2, 3,..., т.е. это бесконечная последовательность чисел вида: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...Составить программу • определения номера последнего числа Фибоначчи, которое входит в диапазон типа Integer (Longlnt);
Основные управляющие конструкции
79
• в ы ч и с л е н и я s — с у м м ы п е р в ы х чисел Ф и б о н а ч ч и , т а к и х , что з н а ч е н и е s не п р е в ы ш а е т д и а п а з о н а т и п а Integer (Longlnt). 5. С о в е р ш е н н ы м ч и с л о м н а з ы в а е т с я ч и с л о , р а в н о е с у м м е всех с в о и х д е л и т е л е й , м е н ь ш и х , ч е м оно с а м о . Н а п р и м е р : 6 = 1 + 2 + 3 , 2 8 = 1 + 2 + 4 + 7 + 1 4 . Древним грекам были известны только четыре первых числа. Составить программу, проверяющ у ю , я в л я е т с я ли заданное натуральное число совершенным. П р и м е ч а н и я 1. Если Вы составите программу поиска 4 совершенных чисел, то материал следующего занятия Вы уже поняли. Подсказка: первые два числа Вам известны, а четвертое число не превышает значения 9999. 2. Поиск пятого числа и т. д. — отдельная проблема. Евклидом доказано, что каждое число вида 2 р1 *(2 р -1) является совершенным числом, если 2 р -1 — простое число. Л. Эйлер доказал, что все четные совершенные числа находятся по формуле Евклида, а относительно нечетных совершенных чисел ничего неизвестно до сих пор. 6. А в т о м о р ф н ы м называется такое число, которое равно последн и м ц и ф р а м своего квадрата. Например: 5 2 =25, 25 2 =625. Очевидно, что автоморфные числа д о л ж н ы о к а н ч и в а т ь с я либо на 1, либо н а 5, либо на 6. Составить программу н а х о ж д е н и я авт о м о р ф н ы х ч и с е л (с учетом приведенного ф а к т а ) , не превыш а ю щ и х значения 999. П р и м е ч а н и е Не забывайте о диапазонах переменных целого типа, 999 2 =998001. 7. Кубические автоморфные числа р а в н ы последним цифрам своих кубов. Например: 6 3 = 2 1 6 . Верно ли, что и такие числа долж н ы о к а н ч и в а т ь с я либо на 1, либо на 5, либо на 6? С учетом этого ф а к т а составить п р о г р а м м у н а х о ж д е н и я д в у з н а ч н ы х , т р е х з н а ч н ы х к у б и ч е с к и х а в т о м о р ф н ы х чисел. Материал для чтения 1. Одно из основных свойств ц е л ы х чисел — это свойство делимости, и л и евклидовости. М ы его неявно использовали на п р е д ы д у щ и х з а н я т и я х . В м а т е м а т и к е оно ф о р м у л и р у е т с я к а к теорема и, естественно, д о к а з ы в а е т с я . Теорема. Д л я любого а и любого ненулевого b существуют единственные (целые) частное q и остаток г, т а к и е , что a = b * q + r , 0<r<abs(b).
79 Часть первая
Идея доказательства. Рассмотрим множество целых чисел вида a - k * b , где к пробегает все множество целых чисел, положительных и неположительных: ..., a-3*b, а-2*Ь, а - b , а, а+Ь, a+2*b, а+3*Ь, ... В этой последовательности выбирается наименьшее неотрицательное число и обозначается через г, а через q обозначается соответствующее значение k(r=a-q*b). Существование такого г следует из конечности множества чисел последовательности, удовлетворяющих условию теоремы, и их упорядоченности. Далее, методом от противного, доказывается единственность значения г. 2. Простых чисел бесконечно много. Это доказано еще Евклидом. Его доказательство. Предположим, что простых чисел конечное множество. Пусть их к: 2, 3, 5, ....Р к . Рассмотрим число М=2*3*5*...*Р к +1. Ни одно из простых чисел не может делить М, ибо каждое из них делит М - 1 . Тогда должно быть другое простое число, делящее М, или само М — простое. А это противоречит предположению о конечности множества простых чисел, тому, что только 2, 3, 5, ..., Рк простые числа. 3. Продолжим наше знакомство с основными понятиями технологий программирования. Пусть Вам после первого чтения не все понятно, это нормальное явление. Только очень простые книги читаются «с листа». Познакомимся с понятием модели жизненного цикла программ. В настоящее время понятие жизненного цикла программы является одним из базовых понятий в технологии программирования. Жизненный цикл — это непрерывный процесс, который начинается с момента принятия решения о необходимости создания программы и заканчивается в момент ее полного изъятия из эксплуатации. Основным нормативным документом, регламентирующим жизненный цикл программы, является международный стандарт ISO/IEC 12207 (ISO — International Organization of Standardization — Международная организация по стандартизации, IEC — International Electrotechnical Commission — Международная комиссия по электротехнике). Он определяет структуру жизненного цикла, которая базируется на трех группах процессов: • основные процессы (приобретение, поставка, разработка, эксплуатация, сопровождение); • вспомогательные процессы, обеспечивающие выполнение основных процессов (документирование, управление конфигурацией, обеспечение качества, верификация, аттестация, оценка, аудит); • организационные процессы (управление проектами, создание инфраструктуры проекта, обучение).
Основные управляющие конструкции
81
К настоящему времени известны две основные модели жизненного цикла: • каскадная (до 1985 г.) — первые два рисунка;
Проектирование
Сопровождение
| Проектирование I—| Реализация
Сопровождение
• спиральная модель (с 1986 г. по 90-е гг.) — третий рисунок.
Реализация
К а с к а д н ы й п о д х о д х о р о ш о з а р е к о м е н д о в а л себя п р и построении программ, для которых в самом начале разработки м о ж н о д о с т а т о ч н о точно и п о л н о с ф о р м у л и р о в а т ь все т р е б о в а н и я . П р и т е р _ р а с ч е т н ы е з а д а ч и (первое, в т о р о е п о к о л е н и я ) . В с л у ч а е неточного и з л о ж е н и я требований и л и их и з м е н е н и я получается п р о г р а м м а , не о т в е ч а ю щ а я п о т р е б н о с т я м п о л ь з о в а т е л я , з а к а з ч и к а , поэтому реальный процесс разработки имеет вид, приведенный на втором рисунке. Д л я п р е о д о л е н и я н е д о с т а т к о в к а с к а д н о й с х е м ы б ы л а предложена, исторически сложилась (наверное, н а ч и н а я с визуальн ы х сред) с п и р а л ь н а я м о д е л ь ж и з н е н н о г о ц и к л а п р о г р а м м ы ( т р е т и й р и с у н о к ) . Ц е л е в а я у с т а н о в к а — у п о р н а а н а л и з и прое к т и р о в а н и е , п у т е м с о з д а н и е п р о т о т и п о в (моделей!!!). К а ж д ы й в и т о к с п и р а л и соответствует с о з д а н и ю в е р с и и п р о г р а м м ы . Н а ней у т о ч н я ю т с я ц е л и и т р е б о в а н и я , о п р е д е л я е т с я к а ч е с т в о , и п л а н и р у ю т с я р а б о т ы по с л е д у ю щ е м у в и т к у . Т а к и м о б р а з о м , угл у б л я ю т с я и п о с л е д о в а т е л ь н о к о н к р е т и з и р у ю т с я д е т а л и прог р а м м ы и в ы б и р а е т с я о к о н ч а т е л ь н а я версия, к о т о р а я доводится до р е а л и з а ц и и . С х о д я щ и й вид с п и р а л и п о д ч е р к и в а е т «размытость» п е р в о н а ч а л ь н ы х т р е б о в а н и й и и х п о с л е д у ю щ е е уточнение. 4. О ш и б к и в п р о г р а м м а х р а з д е л я ю т по т и п а м н а с и н т а к с и ч е ские, семантические и логические. С первыми м ы встречались на п р е д ы д у щ и х з а н я т и я х . Они д о с т а т о ч н о просто у с т р а н я ю т с я и г о в о р я т о несоответствии т е к с т а п р о г р а м м ы п р а в и л а м я з ы к а Турбо П а с к а л ь . В т о р ы е в о з н и к а ю т на с т а д и и в ы п о л н е н и я прог р а м м ы . Н а п р и м е р , д е л е н и е на н о л ь не у с т р а н я е т с я н а с т а д и и к о м п и л я ц и и , а п р и в о д и т к о ш и б к е н а с т а д и и в ы п о л н е н и я , ибо з а в и с и т от к о н к р е т н о г о з н а ч е н и я п е р е м е н н о й . Т р е т и й т и п о ш и бок с а м ы й с л о ж н ы й в о б н а р у ж е н и и . П р о г р а м м а работает т а к , к а к н а п и с а н а , но не т а к , к а к требуется. Т а к и е с л у ч а и я в л я ю т с я следствием м н о г и х п р и ч и н . Главное, что и х с л о ж н о обнаруж и т ь . В системе Турбо П а с к а л ь и м е е т с я достаточно м о щ н ы й о т л а д ч и к , п о з в о л я ю щ и й н а х о д и т ь о ш и б к и третьего т и п а в прог р а м м а х . Он работает с и с х о д н ы м т е к с т о м п р о г р а м м ы , п о з в о л я ет в ы п о л н я т ь п р о г р а м м у по ш а г а м и о т с л е ж и в а т ь и з м е н е н и е п е р е м е н н ы х п р о г р а м м ы в процессе р а б о т ы п о с л е д н е й . П е р е ч и с л и м основные к о м а н д ы о т л а д ч и к а и соответствующ и е и м к л а в и ш и , п о з в о л я ю щ и е в ы п о л н я т ь п р о г р а м м у по шагам и отслеживать значения переменных. Шагом при отладке я в л я е т с я строка п р о г р а м м ы . Т а к , если в строке исходного текста п р о г р а м м ы з а п и с а н о н е с к о л ь к о операторов, то о н и будут в ы п о л н е н ы за один т я г
Основные управляющие конструкции 82
Команда меню
Функциональная 1 клавиша
Run/Run Run/Go tu cursor
Ctrl+F9 F4
Run/Trace into
F7
Run/Step over
F8
Debug/watch Debug/Breakpoints 1
Debug/Evaluate
Debug/Add watch
Debug/Add
breakpoint
Ctrl+F4
Ctrl+F7
Clrl+F8
Назначение
|
Запуск программы ] Выполняет программу до j строки, в которой находится курсор ! Выполняет оператор f (операторы), записанный в > | текущей строке Если в этой i 1 строке записан вызов процедуры или функции, то осуществляется вход в эту : процедуру или функции, отслеживается их работа |, Выполняется оператор, записанный в текущей строке без вхождения в процедуры или функции Открывает окно для наблюдения за значениями переменных (окно Watches) 1 Открывает окно для работы с 1 точками останова , Открывается окно Evaluate and J Modify Набирается выражение j; (можно с использованием !; переменных прогоаммы) и вычисляется его значение Открывает окно Add watch для 1 набора отслеживаемых значений выражений или | переменных Эти действия J можно выполнить и другим j способом Сделать окно Watch активным и использовать 1 клавиши Insert и Delete для вставки и удаления выражений и переменных Текущая строка (в ней 1 1 находится курсор)
Занятие №
9. В л о ж е н н ы е
циклы
План занятия • обсуждение конструкции; • э к с п е р и м е н т ы с п р о г р а м м а м и (с и с п о л ь з о в а н и е м о т л а д ч и ка системы программирования) вычисления в ы р а ж е н и я l k + 2 k + . . . + n k , н а х о ж д е н и я ц и ф р о в о г о к о р н я ч и с л а ; старинной задачи о быках, коровах и телятах, нахождения н а т у р а л ь н ы х чисел, у д о в л е т в о р я ю щ и х определенному условию, и с в е д е н и я ч и с е л , к р а т н ы х 3, к ч и с л у 153; • выполнение самостоятельной работы. Д л я р е ш е н и я з а д а ч и д о с т а т о ч н о часто т р е б у е т с я и с п о л ь з о вать две и более ц и к л и ч е с к и е к о н с т р у к ц и й , одна и з к о т о р ы х помещается в тело ц и к л а другой. Т а к и е к о н с т р у к ц и и называют вложенными циклами. Внутренний и внешний ц и к л ы могут б ы т ь л ю б ы м и и з трех р а с с м о т р е н н ы х р а н е е в и д о в . П р а в и л а орг а н и з а ц и и в н е ш н е г о и в н у т р е н н е г о ц и к л о в т а к и е ж е , к а к и соответствующих простых операторов. Однако п р и и с п о л ь з о в а н и и в л о ж е н н ы х ц и к л о в необходимо соблюдать следующее условие: внутренний ц и к л должен полностью укладываться в циклическую часть внешнего ц и к л а . Экспериментальный раздел работы 1. Д а н ы н а т у р а л ь н ы е ч и с л а п и к . С о с т а в и т ь п р о г р а м м у в ы ч и с ления выражения 1к+2к+...+пк. Д л я вычисления указанной суммы целесообразно использовать оператор For с у п р а в л я ю щ е й п е р е м е н н о й 1, и з м е н я ю щ е й с я от 1 до п . В т е л е ц и к л а , в о - п е р в ы х , в ы ч и с л я л о с ь бы очередное з н а ч е н и е y = i k и , во-вторых, о с у щ е с т в л я л о с ь бы н а к о п л е н и е с у м м ы п р и б а в л е н и е м полученного с л а г а е м о г о к с у м м е всех п р е д ш е с т в у ю щ и х (s:=s+i/). Program Му9_1 ; Var п, к, у, i , s, j: Integer; Begin WriteLn('Введите исходные данные ReadLn (п, к) ; s: =0 ; For i:=1 То п Do Begin У--1; For j : =1 To к Do y:=y"i; s:=s+y; End; WriteLn('Ответ: ',s); End.
п
и к ') ;
Основные управляющие конструкции
85
А сейчас попробуем работать с отладчиком нашей системы программирования. Вспомните материал для чтения предыдущего занятия, пункт 4. Итак: • Выполните команду Debug/Watch. Появится окно Watches. • Используя клавишу Ins (инициируется открытие окна Add Watch), введите переменные программы (n, k, s, i, j, у). • Измените положение и размер окна Watches на экране. Оно должно находится в правом верхнем углу и быть таким, чтобы все введенные переменные были обозримы. С этой целью выполните команду Window/Size/Move (Ctrl+ +F5) и используйте к л а в и ш и Shift+(<=, 1)) для изменения размера окна и к л а в и ш и (=>, U) для его перемещения. • Выполните команду R u n / S t e p over (F8). Строка с оператором Begin выделяется другим цветом (курсор). При нажатии на F8 курсор перемещается на следующую строку — пошаговое выполнение программы. П Выполните программу в пошаговом режиме (последовательное нажатие на F8), отслеживая изменение значений переменных в окне Watches. При выполнении ReadLn (п,к) введите, например, значения 4 и 2 для того, чтобы количество повторений в операторах For было не очень большим. Продолжим изучение отладчика. • Установите курсор на строку программы с оператором s~s+y. Выполните команду Debug/Add Breakpoint (Ctrl+ +F8). Строка будет выделена другим цветом. Мы создали точку останова в программе. • Запустите программу (Ctrl+F9). Работа программы приостанавливается в том случае, когда достигнута точка останова. П Выполните один шаг в работе программы — нажмите клавишу F8. • Выполните команду Debug/Evaluate... В строке Expression окна Evaluate and Modify наберите имя переменной s. В строке Result окна мы видим значение переменной s, оно равно 1. • Продолжите работу с программой по описанной схеме. Следующее значение переменной s равно 5.
Часть первая
86
М е т о д и ч е с к и е р е к о м е н д а ц и и Максимально использовать отладчик системы программирования при выполнении заданий этого и следующих занятий. И так до тех пор, пока не появятся устойчивые навыки работы с ним. 2. Изменить программу так, чтобы вычислялась сумма 1 1 + 2 2 + + ...+П". Подсказка. Требуется изменить параметры у внутреннего оператора For (For j~l То i Do y~y*i;) и убрать переменную k. 3. Пусть значение к зафиксировано, например равно 5. Определить значение п, при котором диапазон целого типа данных Integer окажется недостаточным д л я хранения суммы степеней чисел. 4. Сложим все цифры какого-либо числа. Получим новое число, равное сумме всех цифр исходного числа. Продолжим этот процесс до тех пор, пока не получим однозначное число (цифру), называемое цифровым корнем данного числа. Например, цифровой корень числа 3 4 6 9 7 равен 2 (3+4 + 6 + 9 + 7 = 2 9 ; 2 + 9 = 1 1 ; 1+1=2). Составить программу нахождения цифрового корня натурального числа. Program Му9_2; Var г,к,s:Longlnt ; Begin WriteLn('Введите число');ReadLn s: =п ; While s>9 Do Begin к:=s; s:=0; Repeat s:=s+k Mod 10; k:=k Div 10; Until k=0; End; WriteLn('Цифровой корень числа End.
(п);
' ,n,'
равен
'
,s);
5. Старинная задача. Сколько можно купить быков, коров и телят, если плата за быка 10 рублей, за корову — 5 рублей, за теленка — полтинник (0.5 рубля), если на 100 рублей надо купить 100 голов скота. Решение. Обозначим через Ъ — количество быков; к — количество коров; t — количество телят. После этого можно записать два уравнения: 10b+5k+0.5t=100 и b + k + t = 1 0 0 . Преобразуем их: 20b+10k+t=200 n b + k + t = 1 0 0 .
Основные управляющие конструкции
87
Н а 100 рублей м о ж н о к у п и т ь : • не более 10 быков, т. е. 0<Ъ<10, • не более 2 0 коров, т. е. 0<к<20, • не более 2 0 0 т е л я т , т. е. 0<t<200. Т а к и м образом, п о л у ч а е м : Program Му9_3; Var Ь, к, t ; I n t e g e r ; Begin For b:=0 To 10 Do For к:=0 To 20 Do Foi t:=0 To 200 Do I f (20*o+10*k+t=20C) WriteLn ('быков ',o,' End.
Ana (v+K+t=100) Then коров ' ,k,' телят ' , t) ;
С к о л ь к о р а з будет п р о в е р я т ь с я условие в данной программе (оператор I f ) ? З н а ч е н и е переменной b и з м е н я е т с я 11 раз (от 0 до 10), д л я к а ж д о г о ее з н а ч е н и я п е р е м е н н а я к и з м е н я е т с я 21 раз, а д л я к а ж д о г о з н а ч е н и я переменной к п е р е м е н н а я t измен я е т с я 2 0 1 раз. Т а к и м образом, условие будет проверяться 1 1 * 2 1 * 2 0 1 = 4 6 4 3 1 раз. Но если известно к о л и ч е с т в о б ы к о в и коров, то к о л и ч е с т в о т е л я т м о ж н о в ы ч и с л и т ь по ф о р м у л е t = 1 0 0 — ( b + k ) и ц и к л по переменной t и с к л ю ч а е т с я . Program My9_3m; Var Ь, k, t: IntegersBegin For b:=0 To 10 Do For к:=0 To 20 Do Begin t: =100- (b+k) ; I f (20*b+10*k + t=200) Then коров ' , k , ' телят ' , t ) ; End; End.
WriteLn
( 'быков
' ,b, '
П р и этом р е ш е н и и условие проверяется 11*21=231 раз. Требуется еще у м е н ь ш и т ь количество проверок. 4. Н а п и с а т ь п р о г р а м м у , к о т о р а я находит все четырехзначные ч и с л а abed (а, Ь, с, d — ц и ф р ы числа, причем между ними нет совпадений, т. е. числа, например, типа 1221 нас не устраивают, т, е. любые две ц и ф р ы числа различны), д л я к о т о р ы х выполняется условие: ab-cd=a+b+c+d. Другими словами, разность чисел, составленных и з старших цифр числа и из младш и х , р а в н а сумме ц и ф р числа.
Часть первая
88
З а д а ч у м о ж н о р е ш а т ь р а з н ы м и с п о с о б а м и . Один и з н и х — перебор всех ч е т ы р е х з н а ч н ы х ч и с е л и п р о в е р к а в ы п о л н е н и я у с л о в и я . П о п р о б у е м с о к р а т и т ь перебор, д л я этого п р е о б р а з у е м условие. Из равенства 1 0 * a + b - ( 1 0 * c + d ) = a + b + c + d получаем 9 * ( a - c ) = 2 * ( c + d ) и л и ( a - c ) / ( c + d ) = 2 / 9 , а = с + 2 , d = 9 - c и 0<с<7. Program Му9_4; Var a,b,с,d:Integer; Begin For c:=0 To 7 Do Begin a:=c+2; d:=9-c; For b:=0 To 9 Do I f (b<>c) And (bOa) And (bod) Игi tehn (a ,b, c, d) ; End; End.
Then
5. Д а н о натуральное число, кратное 3. Н а й д е м сумму кубов ц и ф р д а н н о г о ч и с л а . П о л у ч и м новое ч и с л о . П р и м е н и м к н е м у так о е ж е п р е о б р а з о в а н и е . А ч т о в и т о г е ? О к а з ы в а е т с я , ч т о любая т а к а я последовательность чисел, н а ч и н а я с некоторого места, с т а н о в и т с я п о с т о я н н о й и ее э л е м е н т ы р а в н ы 1 5 3 . И з теории ч и с е л . Е с л и ч и с л о a j a 2 . . . a k д е л и т с я на 3, то и s = a 1 + a 2 + . . . + a k д е л и т с я на 3. Это н е о б х о д и м ы й и д о с т а т о ч н ы й п р и з н а к д е л и м о с т и ч и с л а на 3. И з него следует, ч т о и с у м м а кубов ц и ф р ч и с л а к р а т н а 3. Действительно: s 3 = a j 3 + a 2 3 + . . . + a k 3 + + 3 * Р , где Р — с у м м а п о п а р н ы х п р о и з в е д е н и й ц и ф р и и х к в а д ратов. Отсюда следует, ч т о a j 3 + a 2 3 + . . . + a k 3 = s 3 - 3 * P , т. е. с у м м а кубов ц и ф р р а с с м а т р и в а е м о г о ч и с л а к р а т н а 3. Осталось п о к а зать, ч т о п о с л е д о в а т е л ь н о с т ь я в л я е т с я с х о д я щ е й с я . Это доказ ы в а е т с я методом м а т е м а т и ч е с к о й и н д у к ц и и . В а м ж е п р е д л а г а ется составить т а б л и ц у : в п е р в о й строке п а р а м е т р к , з а д а ю щ и й к о л и ч е с т в о д е в я т о к в ч и с л е , во второй с т р о к е — с у м м а к у б о в ц и ф р т а к о г о ч и с л а . З н а ч е н и е к и з м е н я е т с я от 1 до 10. к
1
2
3
Сумма кубов цифр
729
1458
2187
4
5
6
7
8
9
?
•
"1
Проанализируйте таблицу. Вы увидите, что при к>5 количество ц и ф р в ч и с л е , составленном и з с у м м ы кубов ц и ф р , уменьш а е т с я . Т а к и м образом, если В ы проверите сходимость последовательности д л я всех н а т у р а л ь н ы х чисел, н а п р и м е р , м е н ь ш и х
Основные управляющие конструкции
89
и л и р а в н ы х 3 3 3 3 4 , то д л я б о л ь ш и х чисел последовательности с х о д я т с я в с и л у п р е д ы д у щ е г о у т в е р ж д е н и я . Вопрос о сходимости последовательности и м е н н о к з н а ч е н и ю 153 остается открытым. Program Му9_5; Var n,m,t,s,q:Longint; Begin WriteLn ('Введите число, оно умножается для полученного числа строится последовательность');ReadLn(п); л : =3 *п ; Wri te (m, ' ' ) , Repeat
на 3,
и
hhile m>C Do Begin q: =ni Mod 10; s:=s+q*q*q; m:=m Div 10; End; m: =s ; Wri te (m, ' ' ) ; Until m=t; ReadLn; End. Проверьте, что эта л о г и к а работает д л я всех чисел, например, м е н ь ш и х 3 3 3 3 3 . Д л я этого потребуется, естественно, дополнить программу. Задания для самостоятельной работы 1. Ч т о будет выведено на э к р а н е м о н и т о р а после в ы п о л н е н и я с л е д у ю щ е г о фрагмента п р о г р а м м ы : а:=1; Ь:=1; For i:=0 То n Do Begin For j : =1 To b Do Write ('*') ; WriteLn; c:=a+b; a:=b; b:=c; End; если n=6? Решение к а к о й задачи выражает этот фрагмент программы? 2. Ч т о будет выведено на э к р а н е монитора после в ы п о л н е н и я следующего фрагмента программы:
89 Часть первая
b: = 0 ; While а<>0 Do Begin b:=Ь*10+а Mod 10; а:=а Div 10; End; Write (b) ; если a = 1 3 3 0 5 ? Р е ш е н и е к а к о й задачи в ы р а ж а е т этот фрагмент программы? 3. И с х о д н о е д а н н о е — н а т у р а л ь н о е ч и с л о q, в ы р а ж а ю щ е е площ а д ь . Н а п и с а т ь п р о г р а м м у д л я н а х о ж д е н и я всех т а к и х прямоугольников, площадь которых равна q и стороны выражены натуральными числами. 4. С о с т а в и т ь п р о г р а м м у д л я г р а ф и ч е с к о г о и з о б р а ж е н и я делимости ч и с е л от 1 до п (п — и с х о д н о е данное). В к а ж д о й строке надо печатать число и столько плюсов, сколько делителей у этого ч и с л а . Н а п р и м е р , если исходное д а н н о е — ч и с л о 4, то на э к р а н е д о л ж н о б ы т ь н а п е ч а т а н о : 1+
2+ + 3+ + 4+++ 5. Д а н о натуральное число п. М о ж н о его представить в виде сумм ы трех к в а д р а т о в н а т у р а л ь н ы х ч и с е л ? Е с л и м о ж н о , то: • у к а з а т ь т р о й к у х , у, z т а к и х н а т у р а л ь н ы х ч и с е л , что x2+y2+z2=n; • у к а з а т ь все т р о й к и х, у, z т а к и х н а т у р а л ь н ы х чисел, что x2+y2+z2=n. 6. Н а й т и н а т у р а л ь н о е ч и с л о от 1 до 1 0 0 0 0 с м а к с и м а л ь н о й суммой делителей. 7. Д а н ы натуральные числа а, Ъ (а<Ъ). П о л у ч и т ь все простые числа р, у д о в л е т в о р я ю щ и е н е р а в е н с т в а м : а<р<Ь. 8. Д а н ы н а т у р а л ь н ы е ч и с л а n, т . П о л у ч и т ь все м е н ь ш и е п нат у р а л ь н ы е ч и с л а , к в а д р а т с у м м ы ц и ф р к о т о р ы х равен т . 9. Д а н ы н а т у р а л ь н ы е ч и с л а п и т . Н а й т и все п а р ы дружественн ы х чисел, л е ж а щ и х в диапазоне от п до т . Два числа называются дружественными, если каждое из н и х равно сумме всех делителей другого(само число в качестве делителя не рассматривается). 10. В д а н н о м н а т у р а л ь н о м числе переставить ц и ф р ы т а к и м образом, чтобы образовалось наименьшее число, записанное этими же цифрами.
Основные управляющие конструкции 90
11. Составить программу, печатающую для данного натурального ч и с л а k-ю ц и ф р у последовательности: • 1 2 3 4 5 6 7 8 9 1 0 . . . , в которой в ы п и с а н ы подряд все натурал ь н ы е числа; • 1 4 9 1 6 2 5 3 6 4 9 . . . , в которой в ы п и с а н ы подряд к в а д р а т ы всех н а т у р а л ь н ы х чисел; • 1 1 2 3 5 8 1 3 2 1 . . . , в которой в ы п и с а н ы подряд все числа Фибоначчи. 12. Составить п р о г р а м м у в о з в е д е н и я заданного числа в третью степень, и с п о л ь з у я следующую закономерность: 13=1
2 3 =3 + 5 3 J =7 + 9 + l l 4J=13+15+17+19 53=21+23+25+27+29 13. Составить п р о г р а м м у д л я н а х о ж д е н и я всех натуральных реш е н и й у р а в н е н и я n 2 + m 2 = k 2 в интервале [1, 10]. П р и м е ч а н и е Решения, которые получаются перестановкой п и ш, считать совпадающими. 14. Выписать фрагмент программы для решения указанной ниже задачи и обосновать, почему был выбран тот или иной вариант оператора ц и к л а : • в ы ч и с л и т ь ф а к т о р и а л некоторого числа р; • задано число р, определить, является л и оно факториалом некоторого числа, если «да», то найдите это число; • определить, является л и заданное число степенью числа 3; • в ы ч и с л и т ь y=l!+2!+3!+...+nl • в ы ч и с л и т ь х п , где п — целое положительное число; • в ы ч и с л и т ь з н а ч е н и я х 1 , х 2 , х 3 ,..., х п . 15. Дано трехзначное число. Найти все трехзначные числа, удовл е т в о р я ю щ и е к а ж д о м у из условий: • состоящих и з тех ж е цифр, что и исходное число; • равное среднему арифметическому всех трехзначных чисел ( в к л ю ч а я данное), и м е ю щ и х тот ж е цифровой состав. 16. Стороны прямоугольника заданы натуральными числами М и N. Составить программу, которая будет находить, на скол ь к о квадратов, стороны которых в ы р а ж е в ы натуральными числами, можно разрезать данный прямоугольник, если от него к а ж д ы й раз отрезается квадрат максимально большой площади.
91 Часть первая
17. Даны натуральные числа N и р. Получить все натуральные числа, меньшие N и взаимно простые с р. 18. Даны целые числа р и q. Получить все делители числа q, взаимно простые с р. 19. Сумма квадратов длин катетов а и b прямоугольного треугольника равна квадрату длины гипотенузы с: а 2 +Ь 2 =с 2 . Тройка натуральных чисел, удовлетворяющих этому равенству, называется Пифагоровыми числами Составить программу нахождения основных троек Пифагоровых чисел, используя следующие формулы a=u*v; b=(u*u-v*v) Div 2, c=(u*u+v*v) Div 2, где u и v — взаимно простые нечетные натуральные числа и u>v. 20. Найти наименьшее натуральное число N, представимое двумя различными способами в виде суммы кубов двух натуральных чисел х 3 и у 3 (х>у). 21. Даны натуральные числа т , п х , п 2 ,..., пга (т>2). Вычислить НОД(п 1 ,п 2 ,...,п т ), воспользовавшись соотношением НОД(п 1( п 2 ,... ,п т )=НОД(НОД(п 1 ,п 2 ,.. .,п т _ 1 ),п ш ) и алгоритмом Евклида. 22. Найти все простые несократимые дроби, заключенные между 0 и 1, знаменатели которых не превышают 7 (дробь задается двумя натуральными числами — числителем и знаменателем). Материал для чтения Продолжим обзор технологий программирования. Визуальные технологии. Визуальную технологию конструирования программ (например, систему программирования Delphi) можно отнести к технологиям пятого поколения. Она, во-первых, полностью поддерживает объектно-ориентированную технологию, во-вторых, идеи модульного программирования получают логическое завершение, в-третьих, и это принципиально новое в данной технологии — создан инструментарий (автоматизация) программирования реакции на события (любая программа в процессе своей работы с чем-то или кем-то взаимодействует). Структура программного кода вероятностная. Не все маршруты, трассы кодов жестко определены. Элементы программного кода взаимодействуют, начинают работать при возникновении определенных событий. А главное — процесс разработки носит не каскадный, последовательный характер, он развивается по спирали. Модель многокомпонентных объектов. Модель многокомпонентных объектов (Component Object Model — СОМ) лежит в
Основные управляющие конструкции
93
основе OLE (Object Linking and Embedding — связывание и встраивание объектов) и ActiveX (одна из технологий Microsoft). Традиционно программы разных типов предоставляли свои сервисы по-разному (вызовы локальных функций, передача сообщения средствами связи между процессами, системные вызовы и др.). СОМ определяет общий способ доступа к программным сервисам. Объекты СОМ предоставляют сервисы через методы, объединенные в интерфейсы. Вспомним идеи объектно-ориентированного программирования. Объединяются в единое целое данные и действия над данными. В визуальном программировании дается инструментарий для разработки взаимодействия программы и пользователя, программ между собой. Логика развития программирования (как науки, так и отрасли производства) подводит к объединению определенных схем взаимодействия объекта с другими объектами в нечто целое, называемое интерфейсом. Заметим, что в результате появляется новый инструментарий абстрагирования. Продолжим. Объект, использующий ресурсы других объектов, обычно называют клиентом. Описание поведения объекта включает описание операций, которые могут выполняться над ним, и операций, которые сам объект выполняет над другими объектами. При этом концентрируется внимание на внешних особенностях объекта. Полный набор операций, которые объект может осуществлять над другим объектом, называется протоколом. Протокол отражает все действия, которым объект может подвергаться сам и которыми может оказывать влияние на другие объекты, определяя тем самым полностью внешнее поведение абстракции со статической и динамической точек зрения. Метод — это функция или процедура, которая выполняет некоторое действие и может быть вызвана программным обеспечением, использующим данный объект (клиентом объекта). Методы, составляющие к а ж д ы й из интерфейсов, обычно определенным образом взаимосвязаны. Клиенты могут получить доступ к сервисам объекта СОМ только через вызовы методов интерфейсов объекта — у них нет непосредственного доступа к данным объекта. Методы интерфейса служат для предоставления определенного сервиса. Объект СОМ реализуется внутри сервера и обычно поддерживает несколько интерфейсов. Сервер может быть либо динамически подключаемой библиотекой (DDL), подгружаемой во время работы приложения, либо отдельным самостоятельным объектом. Соотношение СОМ и объектно-ориентированного программирования. Объекты — центральная идея СОМ. Объект состоит
94
Часть первая
из двух элементов: набора данных и групп методов. В отличие от СОМ большинство объектно-ориентированных технологий допускает не более одного интерфейса на объект. Распространенная концепция в объектно-ориентированном программировании — понятие класса. Объекты относятся к определенному классу, являются экземплярами класса (идея типизации в Турбо Паскале). СОМ объекты имеют классы. Класс в СОМ понимается к а к конкретная реализация набора интерфейсов. Может существовать несколько разных реализаций одного и того же набора интерфейсов, к а ж д а я из которых будет отдельным классом. Идеи объектно-ориентированного программирования н а ш л и свое отражение в СОМ технологии. Инкапсуляция. Данные объекта недоступны его клиентам непосредственно, они инкапсулируются, скрываются от прямого доступа извне. Клиент имеет доступ к данным объекта только через методы интерфейсов этого объекта. Полиморфизм. Возможность работы с объектами разных типов, к а ж д ы й из которых поддерживает данный набор интерфейсов, но реализует их по-разному. Наследование. Идея проста: имея некоторый объект, можно создавать новый, автоматически поддерживающий все или некоторые «способности» старого. Различают наследование реализации и наследование интерфейса. В первом случае объект наследует от своего родителя код. Когда клиент дочернего объекта вызывает один из унаследованных методов, на самом деле выполняется код метода родителя. Таким образом, это механизм повторного использования кода (языки С++, Smalltalk). Наследование интерфейса означает повторное использование спецификаций — определение методов, поддерживаемых объектом, облегчает решение задач полиморфизма. Определение нового интерфейса путем наследования от существующего гарантирует, что объект, поддерживающий новый интерфейс, можно рассматривать как объект, который поддерживает старый интерфейс. В чем отличие СОМ от объектно-ориентированных технологий? СОМ действительно объектно-ориентированная технология, однако способ определения и поведения объектов трактуется иначе. Идея, заложенная в СОМ технологии. Повторное использование программных компонент. Так, разработчику аппаратуры значительно легче за счет интенсивного повторного использования существующих компонентсоздавать продукт (изделие). Идею трудно назвать новой. Библиотеки (динамически подключаемые), объекты дают эту возможность. Библиотеки поставляются в двоичном виде, что позволяет скрыть секреты реализа-
Основные управляющие конструкции 94
ции. Основная сложность — расширение функциональных возможностей, библиотечный подход просто недостаточен. Повторное использование объектов возможно, однако рынка объектов нет, то есть возможно использование только в рамках рабочей группы программистов. Причины: стандартов для компоновки двоичных объектов в единое целое нет. Необходимо распространять объекты вместе с исходным текстом, что вряд ли возможно. Повторное использование объектов в различных системах программирования тоже не возможно. Рынок обязан поставлять объекты, которые могут использоваться разными языками и средами разработки. Но этого нет, объект на С++ затруднительно использовать в среде Delphi. Еще одна проблема связана с перекомпоновкой и перекомпиляцией всего приложения при изменении одного объекта. СОМ технология решает эти проблемы, при этом преимущества библиотек и объектов сохраняются и вносится в программирование преимущество технологии всеобщего повторного применения. В W W W уже существуют узлы, полные компонентов, основанных на СОМ. Преимущества СОМ: • Все преимущества объектно-ориентированного проектирования программ. Разработчик может организовать проект в виде СОМ объектов, а затем определить интерфейсы каждого объекта. • Общий подход к созданию всех типов программных сервисов. Находит нужное программное обеспечение в библиотеке, в другом процессе, в операционной системе. Сглаживает различия между системным и прикладным программным обеспечением. • Безразличен я з ы к программирования. СОМ определяет двоичный интерфейс, который должны поддерживать объекты. Объекты СОМ можно создавать на любом языке, способном поддерживать данный интерфейс. • Замена текущей версии программ на новую, с дополнительными возможностями, не повредив существующим клиентам старой версии. Способность СОМ-объекта поддерживать более одного интерфейса — ключ к решению этой проблемы. Технологии Active X и OLE. Первая реализация OLE была предназначена для обеспечения механизма создания и работы с составными документами. Элементы, созданные в различных приложениях, например Microsoft Excel и Microsoft Word, интегрируются в рамках единого документа. Составные документы создаются либо связыванием двух разных документов, либо
95 Часть первая
внедрением одного документа в другой. Частный случай. Дальнейшие версии — разные программные компоненты должны предоставлять друг другу сервисы. По-новому взглянуть на взаимодействие любых типов программ (библиотек, приложений, системного программного обеспечения и др.). Под термином OLE понималось все, что создавалось с использованием парадигмы СОМ. В 1996 году Microsoft ввела в оборот новый термин ActiveX. Сначала он относился к технологиям, связанным с Интернетом, и приложениям, выросшим из него, вроде W W W (World Wide Web). Но так к а к разработки Microsoft были основаны на СОМ, то ActiveX также была связана с OLE. Все вернулось на круги свои: OLE — означает в настоящее время только технологию создания составных документов связыванием и внедрением, а разнообразные технологии на основе СОМ, ранее объединенные под названием OLE, называют ActiveX. Технологии ActiveX и OLE — это не что иное, как программное обеспечение, предоставляющее клиентам сервисы через СОМ-интерфейсы, поддерживаемые СОМ объектами. Различные части ActiveX и OLE определяют стандартные интерфейсы для различных целей. Рынок стандартных компонентов. Такую цель и преследует финансируемая Microsoft программа разработки интерфейсов подобного рода — OLE Industry Solutions (Промышленные решения на основе OLE). В рамках этой программы группы финансовых компаний, организаций здравоохранения, поставщиков оборудования для торговых точек и др. определили стандартные интерфейсы компонентов, применяемых в соответствующих областях. Case технологии. (Computer Aided Software Engineering — автоматизация разработки ПО) При проектировании больших программ разработчик начинает с планирования своей работы, рисования некоторых диаграмм, написания каких-то предварительных спецификаций, разработки некоторого макета, позволяющего определить, как все составные части будут взаимодействовать между собой, решать поставленную проблему. В 60-х годах этот процесс был формализован с помощью блок-схем. Все, кто в то время занимался программированием, проходили через это. Считали, что эти понятия и инструментальные средства можно было использовать не только для разработки программ, но и для описания всех процессов на предприятии. Диаграммные методы играли ключевую роль в разработке программ и в 80-е годы. С их помощью пытались описать все стадии разработки программ. Появились даже специальные рабочие станции для автоматизированного процесса разработки
Осповпые управляющие конструкции
диаграмм. Идея полного описания и контроля разработки получила название программной инженерии (software engineering), дисциплины, позволяющей сложную программу строить в предсказуемом стиле и с качеством, которое можно измерить и гарантировать. Была сделана попытка автоматизации собственной работы («сапожник без сапог>>). Эта старая идея жива и в настоящее время, она имеет только другое название — CASE технологии. Первоначально под этим термином понимались средства автоматизации разработки программ. Сейчас он трактуется к а к программные средства, поддерживающие процессы создания и сопровождения программ, включая анализ и формулировку требований, проектирование, генерацию кода, тестирование, документирование, обеспечение качества и управления проектом разработки. Отношение к CASE технологиям не однозначное. С одной стороны, считают, что CASE технологии обладают высокими потенциальными возможностями в части увеличения производительности труда, улучшения качества программных продуктов, поддержки унифицированного и согласованного стиля работы. При этом существующие CASE средства, используя методы структурного и объектно-ориентированного анализа и проектирования программ, имеют различные инструментарии (например, диаграммы, тексты и т. д.) для описания внешних требований, связей между компонентами программы, динамики работы. Другая точка зрения заключается в том, что CASE технологии не более чем пакеты рисования и конструирования диаграмм. Истина, вероятно, находится посередине. Д. Васкевич приводит пример из прошлого, связанного с индустрией обработки текстов. В 80-е годы системы обработки текстов были дорогостоящими, большими и привязанными к конкретным ЭВМ. Эти системы были далеко за пределами возможностей обычных пользователей. Затем появилось следующее поколение для персональных компьютеров, но и они были слишком ограничены и тяжелы в применении. Большинство пользователей не применяли их. В настоящее время программы типа Microsoft Word выполняют больше, чем аналогичные разработки 80-х годов, и для миллионов людей использование текстовых процессоров стало таким же привычным, к а к использование ручки. История повторяется. Программ требуется все больше. Программистам необходимы инструментальные средства проектирования на базе компьютера. Подведем итог нашего сжатого обзора технологий программирования. Их развитие происходило и происходит по спира4-452
98
Часть первая
ли, не плоской, а пространственной. К а ж д ы й новый виток, «вбирая» все от предыдущего, решает основную проблему технологии (разработка надежного и эффективного программного проекта с м и н и м а л ь н ы м и затратами) новым, более совершенным инструментарием (принципы декомпозиции и абстрагирования, приемы анализа и синтеза и т. д.). Мера дезорганизации программных проектов (имеется в виду весь их « ж и з н е н н ы й цикл») к а к сложных систем уменьшается. Первый виток — операциональный. Я з ы к и программирования: FORTRAN I и II, ALGOL 58, 60, COBOL, LISP и др. Нарабатываемые идеи: подпрограмма (подпрограммы возникли до 1950 года, но рассматривались не к а к элемент абстрагирования, а к а к средство, упрощающее работу); типы данных и их описание, раздельная к о м п и л я ц и я , блочная структура, обработка списков, указатели и т. д. Второй виток — структурный (объединяем этим термином и нисходящее проектирование, и модульное). 1 9 6 6 - 1 9 8 5 годы. Я з ы к и программирования П Л / 1 , ALGOL 68, Pascal, Simula, С, Ada (наследник ALGOL 68, Pascal, Simula), Clos, С++ (возникший в результате с л и я н и я С и Simula) и т. д. В 70-е годы созданы тысячи языков и диалектов. Нарабатываемые идеи: подпрограммы как элемент абстрагирования (разработаны механизмы: передачи параметров; вложенности подпрограмм; локальных и глобальных переменных; теория типов; развитие модулей от группы логически связанных подпрограмм до раздельно компилируемых фрагментов со строго определенным интерфейсом). Третий виток — объектно-ориентированный, с 1986 года. Языки программирования: Smalltalk, Object Pascal, С++. Основным элементом конструирования программы является модуль, составленный из логически связанных объектов и т. д. Учебник, если так можно выразиться, посвящен второму витку развития этого сложного предмета — программирования.
Часть в т о р а я
Процедуры и функции — элементы структуризации программ
З а н я т и е № 10. О д н о м е р н ы е м а с с и в ы . Работа с элементами План занятия • раздел описания типов данных; • простой пример нахождения суммы чисел; • экспериментальная работа с программами поиска суммы элементов массива, кратных заданному числу; нахождения элементов с определенными свойствами в массиве целых чисел; формирование значений элементов массива с помощью генератора случайных чисел; вычисления факториала числа; • выполнение самостоятельной работы. Описание типов данных. При описании переменных через двоеточие указывается ее тип. До этого занятия использовались только два типа данных: Integer и Boolean. Типы данных Integer и Boolean относятся к простым типам (простые, потому что не могут состоять ни из каких других типов). Тип переменной определяет множество значений, которые она может принимать, и операции, которые могут быть над ней выполнены. Утверждение, после проделанной работы, понятное. С каждой встречающейся в программе переменной может быть связан только один тип данных. Естественно, перечнем Integer и Boolean не исчерпывается весь список типов Турбо Паскаля. Он не исчерпывается любым списком, ибо в языке есть возможность для конструирования своих типов данных. Однако, так или иначе, но любой, сверхсложный тип данных, который Вы создали в своей программе, сводится (или строится) к простым типам. Описание типов данных имеет следующий вид: Туре <имя_типа>=<тип_данных>;
100
Часть вторал
П о с л е этого в р а з д е л е о п и с а н и й п е р е м е н н ы х у Вас п о я в л я е т ся в о з м о ж н о с т ь с с ы л а т ь с я на в в е д е н н ы й т и п д а н н ы х , он ведь имеет имя! Var < и м я _ п е р е м е н н о й > : < и м я _ т и п а > ; П р и м е ч а н и е Не будем терять время на рассмотрение примеров. Схема использования описания типов прояснится из последующего материала занятий. Простой пример. Решение очевидно.
Необходимо найти сумму 5 целых чисел.
Program Му10_1; Var al,а2,аЗ,а4,а5,s: Integer; Begin WriteLn (' Введите пять целых ReadLn(al,а2,аЗ,а4,а5) ; s:= а1+а2+аЗ+аА+а5; WriteLn ('Ил сумма равна ' ,s) ReadLn; End.
чисел');
А если требуется н а й т и с у м м у 30 ц е л ы х ч и с е л ? Р е ш е н и е по а н а л о г и и требует в в е д е н и я 30 о д н о т и п н ы х п е р е м е н н ы х . Одномерный массив — это ф и к с и р о в а н н о е к о л и ч е с т в о элементов одного и того ж е т и п а , о б ъ е д и н е н н ы х о д н и м и м е н е м , где к а ж д ы й элемент имеет свой н о м е р . О б р а щ е н и е к э л е м е н т а м массива осуществляется с помощью у к а з а н и я имени массива и номеров э л е м е н т о в . Н а м д л я р а б о т ы требуется м а с с и в и з 30 цел ы х чисел. О п и ш е м в р а з д е л е т и п о в свой тип — о д н о м е р н ы й м а с с и в , сос т о я щ и й и з 30 ц е л ы х ч и с е л . Туре МуАггау = Array [1..30] Of Integer; Н а п о м н и м , что р а з д е л т и п о в н а ч и н а е т с я со с л у ж е б н о г о слова Туре, после этого идет и м я нового т и п а и его о п и с а н и е . М е ж д у и м е н е м т и п а и его о п и с а н и е м с т а в и т с я з н а к «равно» (в разделе п е р е м е н н ы х м е ж д у и м е н е м п е р е м е н н о й и ее о п и с а н и е м с т а в и т с я двоеточие). В н а ш е м случае МуАггау — и м я нового т и п а д а н н ы х ; Array — с л у ж е б н о е слово (в переводе с а н г л и й ского означает «массив», «набор»); [ 1 . . 3 0 ] — в к в а д р а т н ы х скобках у к а з ы в а е т с я номер первого элемента, затем, после двух т о ч е к , н о м е р последнего э л е м е н т а м а с с и в а ; Of — с л у ж е б н о е слово (в переводе с а н г л и й с к о г о «из» ); Integer — т и п элементов м а с с и в а . И р е ш е н и е , простое р е ш е н и е без 30 п е р е м е н н ы х .
Процедуры и функции-—элементы структуризации программ
101
Program Му10_1т; Const п=30; Type MyArray=Array[1..п] Of Integer; Var A: MyArray; s,i: Integer; Begin WriteLn ('Введите ',n, ' чисел'); For l :=1 To n Do ReadLn ( A f i J ) ; s: = 0 ; For i:=l To n Do s:=s+A[i] ; WriteLn(' Их сумма равна ' ,s); ReadLn; End. Экспериментальный раздел работы 1. Н а й т и с у м м у элементов массива, к р а т н ы х заданному числу. П р е д ы д у щ е е р е ш е н и е и з м е н и т с я н е з н а ч и т е л ь н ы м образом. Д о б а в л я е т с я описание еще одной переменной д л я х р а н е н и я з н а ч е н и я ч и с л а , на к р а т н о с т ь которому проверяются значен и я элементов массива. П о я в л я ю т с я операторы WriteLn('Введите число '); ReadLn (к) ; и и з м е н я е т с я оператор из тела ц и к л а I f Ah]
Моа к = 0 Then
s:=s+A[i];
П р и м е ч а н и е Не забудьте изменить значение константы п. Вводить при каждом запуске программы 30 чисел — утомительное занятие. И з м е н и т ь п р о г р а м м у т а к , чтобы определялось количество п о л о ж и т е л ь н ы х и о т р и ц а т е л ь н ы х элементов в данном массиве. Суть основного и з м е н е н и я п р о г р а м м ы з а к л ю ч а е т с я во введении двух п е р е м е н н ы х (счетчиков — pos, neg) д л я х р а н е н и я значен и й количества п о л о ж и т е л ь н ы х и о т р и ц а т е л ь н ы х элементов в массиве. Н а й д и т е местонахождение в программе следующих строк программного кода: pos, neg:Integer; pos:~0;neg:=0; I f A[ i ]>0 Then In с (pos) Else I f A [ l ]<0 Then In с (neg) ; WriteLn (pos: 4, neg: 4) ;
102
Часть вторал
Д а н н а я п р о г р а м м а не р е ш а е т з а д а ч у н а х о ж д е н и я и к о л и ч е ства н у л е в ы х э л е м е н т о в в м а с с и в е . П р о в е д и т е ее м о д и ф и к а ц и ю и д л я р е ш е н и я этой з а д а ч и . Изменить программу так, чтобы в массив В записывались н о м е р а ч е т н ы х э л е м е н т о в м а с с и в а Л . В в е д е н и е м а с с и в а В и работа с н и м т р е б у е т в в е д е н и я п е р е м е н н о й (j) д л я о б р а щ е н и я к э л е м е н т а м В . Суть р е ш е н и я з а к л ю ч а е т с я в п р о с м о т р е э л е м е н тов м а с с и в а А (это м ы у м е е м ) , в ы я в л е н и и ч е т н ы х э л е м е н т о в и з а п и с и и х н о м е р о в по т е к у щ е м у з н а ч е н и ю п е р е м е н н о й ; в массив В . П р и м е ч а н и е Мы записываем не значения элементов, а их номера! К сожалению, отличие между этими понятиями осознается учащимися при первом знакомстве не сразу, даже не за решение одной задачи. Program Му10_2; Const п=10; Type MyArray=Array[l..п] Of Integer; Var А,В: МуАггау; i,j : Integer; Begin WriteLn('Введите ',п, ' чисел'); For 1: =1 То п Do Rea dLn (А [ l ] ) ; 3--=0; For i:=l То л Do I f А[ i ] Mod 2 = 0 Then Begin Inc ( j ) ;B [ j ] : =1 ; End; For i:=l To j Do Wnte(B[i]:3); WriteLn; ReadLn; End. 2. Н а у ч и м с я определять, есть л и в массиве элемент с определенн ы м и свойствами, например, есть л и отрицательный элемент. А если т а к и х элементов несколько? Требуется л и определять номера этих элементов в массиве и л и выводить и х з н а ч е н и я ? Это все р а з н ы е задачи. Уточним постановку — номер последнего отрицательного элемента. Н а п р а ш и в а е т с я очевидное р е ш е н и е , его ф р а г м е н т : For i:=l То п Do I f A[i]<0 Then
к:=i;
Процедуры и функции-—элементы структуризации программ
103
I f к=0 Then <отрицательных элементов в массиве нет> Else <последний отрицательный элемент является к по счету в массиве А>; Это р е ш е н и е плохое. Действительно, если е д и н с т в е н н ы й отр и ц а т е л ь н ы й элемент в массиве записан на первом месте, то м ы в ы п о л н я е м л и ш н и е п-1 сравнение, п р о с м а т р и в а я массив до к о н ц а . Д о г о в о р и м с я о том, ч т о н а ш п р о г р а м м н ы й код д о л ж е н экономно, р а ц и о н а л ь н о использовать ресурсы компьютера, в частности и в р е м я его работы. Э ф ф е к т и в н ы й алгоритм значит в computer science больше, чем сверхбыстродействующий компьютер. Следующее и з м е н е н и е I f A[i]<0 Then Begin k:=i; Break; End; решает в о з н и к ш и й вопрос, но... Это изменение приводит к тому, что н а ш небольшой фрагмент л о г и к и имеет одну точку входа и две т о ч к и в ы х о д а . Т о ж е плохо, очень плохо. Обилие Break, GoTo п р е в р а т и т н а ш у программу в «спагетти», а у нас не 60-е годы, п е р в ы й в и т о к р а з в и т и я технологий программирования м ы о с т а в и л и и с т о р и и . И еще одно замечание. В элементарной задаче задействованы две переменные. А если задача не элемент а р н а я и м ы будем т а к и х « т р а н ж и р и т ь » ? Н а м не хватит, образно в ы р а ж а я с ь , н и к а к о г о а л ф а в и т а . П р и е м л е м ы й вариант р е ш е н и я имеет вид: 1: =л ; While (1>=1) And (A[i]>=0) DoDec(i); I f Kl Then <отрицательных элементов нет в массиве> Else <последний отрицательный элемент имеет номер i>; Чуть-чуть и з м е н и м задачу. Требуется найти значения и номера всех о т р и ц а т е л ь н ы х элементов. В этом случае использование оператора For неизбежно, ибо н а м необходимо просмотреть все элементы массива. И т а к , не н а ш е ж е л а н и е определяет тип используемого оператора ц и к л а , а р е ш а е м а я задача! ($R+) P r o g r a m Му10_3; C o n s t n—7; Туре МуАггау=Аггау[1..n] Of Integer; Var A:MyArray; i:Integer; Begin
104
Часть вторал
WriteLn('Ввод элементов массива. Не забудьте об отрицательных элементах. '); For i:=l То л Do Read (Al 1] ) ; WriteLn ('Вывод отрицательных элементов массива и их номеров (индексов)'); For 1:=1 То л do I f A[i]<0 Then WriteLn (Ali],' ' ,i,' '); Readln; End. Текст п р о г р а м м ы п о и с к а номеров всех о т р и ц а т е л ь н ы х элементов в массиве с о д е р ж и т строки: For i:=l То п do И з м е н и т е во второй строке п на п+1 и поставьте перед текстом программ ы д и р е к т и в у к о м п и л я т о р а {$R+}. П р и запуске п р о г р а м м ы поя в и т с я о ш и б к а периода в ы п о л н е н и я : Error 201: Range check er ror. Она я в л я е т с я сообщением о том, что значение п е р е м е н н о й i (индекс массива Л) в ы ш л о за д о п у с т и м ы й предел, т. е. за значение к о н с т а н т ы п. Д и р е к т и в ы к о м п и л я т о р а у п р а в л я ю т р е ж и м о м к о м п и л я ц и и . Директива н а ч и н а е т с я с символа $ после о т к р ы в а ю щ е й с к о б к и к о м м е н т а р и я , за к о т о р ы м следует и м я д и р е к т и в ы (состоящее из одной и л и н е с к о л ь к и х букв), определяющее ее н а з н а ч е н и е . Контроль в х о ж д е н и я в д и а п а з о н о с у щ е с т в л я е т с я с помощью д и р е к т и в ы {$R+} ({$R-}), п р и этом у с т а н а в л и в а е т с я и л и отмен я е т с я г е н е р а ц и я кода к о н т р о л я в х о ж д е н и я в д и а п а з о н . В реж и м е {$R-^} все индексы массивов и строк к о н т р о л и р у ю т с я на выход за г р а н и ц ы , а все п р и с в а и в а н и я з н а ч е н и й с к а л я р н ы х переменных и о г р а н и ч е н н ы х типов проверяются на в х о ж д е н и е в диапазон. П р и выходе за г р а н и ц ы выполнение п р о г р а м м ы прек р а щ а е т с я и выдается сообщение об ошибке. П р и м е ч а н и е При работе с массивами настоятельно рекомендуем использовать эту директиву при отладке программ. 3. Формирование з н а ч е н и й элементов массива путем ввода и х с к л а в и а т у р ы — достаточно утомительное з а н я т и е . Давайте попробуем использовать для этих целей генератор случайных чисел. Это сложное название м ы будем понимать очень просто. Есть что-то (черный я щ и к ) , и это что-то выдает числа, причем какое число выдается следующим за очередным, нам неизвестно. Этим ч е р н ы м я щ и к о м в Турбо П а с к а л е я в л я е т с я ф у н к ц и я Random. Она возвращает случайное число.
Процедуры и функции-—элементы структуризации программ
105
Функция Random. Описание: Random[(range:Word)]. Н а п о м н и м , что в квадратн ы х с к о б к а х у к а з ы в а е т с я н е о б я з а т е л ь н ы й п а р а м е т р конструкц и и Турбо П а с к а л я . Тип р е з у л ь т а т а : Real и л и Word в зависимости от н а л и ч и я параметра. К о м м е н т а р и й . Е с л и п а р а м е т р не задан, то результатом является ч и с л о т и п а Real в диапазоне 0 < х < 1 . П р и н а л и ч и и параметра в о з в р а щ а е т с я ч и с л о т и п а Word в диапазоне 0 < x < r a n g e . Обратите в н и м а н и е на то, что в е р х н я я г р а н и ц а д и а п а з о н а не достигается — строгое неравенство. П р и м е ч а н и е С типом Real у нас еще не было работы, она впереди. I $R~} Program Myl 4; Const п=10; Type MyArray=Array[1..п] Of Integer; Var A:MyArray; l: IntegersBegin (Randomize;} WriteLn('Формирование значений элементов массива А') ; For i: =1 То п Do A[i] := Random (21) (-10} ; WriteLn('Вывод'); For i : =1 To n Do Write (A[ l] : 5) ; ReadLn; End. Запустите несколько раз программу. На экране мы видим одну и ту ж е последовательность чисел в диапазоне от 0 до 20. Уберите ф и г у р н ы е скобки у - 1 0 и снова запустите несколько раз п р о г р а м м у . Последовательность чисел на экране одна и та ж е , но ч и с л а и з и н т е р в а л а от - 1 0 до 10. Объясните этот факт. Попробуйте п о л у ч а т ь ч и с л а из любого интервала, например от - 1 7 до 25. П р о д о л ж и м н а ш и эксперименты. Уберите фигурные скобки у процедуры Randomize. Повторите многократный запуск п р о г р а м м ы . Последовательности чисел на экране разные. В чем дело? Н а ш ч е р н ы й я щ и к , ф у н к ц и я Random, начинает генерировать в первом случае числа (каким-то неизвестным нам образом) от фиксированного начального числа. Во втором случае эти н а ч а л ь н ы е числа м е н я ю т с я от запуска к запуску (про-
Часть вторал
106
цедурой Randomize) и последовательности п о л у ч а ю т с я р а з н ы е . С л е д у ю щ и м и з м е н е н и е м я в л я е т с я в к л ю ч е н и е к о н т р о л я на выход за д о п у с т и м ы й д и а п а з о н {$R+}. После з а п у с к а м ы в и д и м у ж е з н а к о м у ю о ш и б к у : Error 201: Range check error. П р о ч и т а й те еще р а з в н и м а т е л ь н о описание ф у н к ц и и R a n d o m и попробуйте н а й т и о б ъ я с н е н и е п р и ч и н ы в о з н и к н о в е н и я о ш и б к и . Е с л и о б ъ я с н е н и е не найдено, то и з м е н и т е т е к с т п р о г р а м м ы . <$R+) Program Му10_4т; Const п=10; Var 1:Integer; Begin WriteLn ('Вывод 10 случайных чисел в диапазоне от -10 до 10') ; For i:=l То п Do WriteLn (Random (21) -10) ; ReadLn; End. О ш и б к и после з а п у с к а п р о г р а м м ы нет, но на э к р а н е не числа и з и н т е р в а л а от - 1 0 до 10, а к а к а я - т о с т р а н н а я последовательность чисел. Она м о ж е т быть и т а к о й : 65530, 1, 2,4, 65527, 2, 6 5 5 2 8 , 6 5 5 3 4 , 3, 2. И т а к , где ж е и с т и н а ? Н а п о м н и м , ч т о диапазон д л я ц е л ы х чисел типа Word от 0 до 6 5 5 3 5 и ч и с л о 16 6 5 5 3 5 1 0 ( 2 - 1 ) = 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 , а это - 1 в дополнительном коде. Ч и с л о 6 5 5 3 4 соответствует - 2 в д о п о л н и т е л ь н о м коде. В первой версии п р о г р а м м ы результат ф у н к ц и и Random имеет тип Word. Из этого ч и с л а в ы ч и т а е т с я целое число т и п а Integer. Т и п ы разные, в ы п о л н я е т с я преобразование типов по п р а в и л у , описанному в з а н я т и и 2, т. е. к типу Longlnt. А элемент массива имеет тип Integer — противоречие. И з м е н и т е тип элементов массива А на Longlnt и сравните р е з у л ь т а т ы работы программ. Во второй версии контроль на выход за д и а п а з о н не в ы п о л н я е т с я , может быть, потому что нет о п е р а ц и и присвоен и я (контролировать нечего и некого)? Верните тип Integer в описании элементов массива и измените одну строку программ ы на For i:=l
То n Do A[i]
:=In teger
(Random (21))
-10 ;
Запустите программу. Она прекрасно работает. Объясните, что происходит в этом случае. 4. Н а у ч и м с я в ы ч и с л я т ь факториал натурального числа N . Факториал ч и с л а — это п р о и з в е д е н и е ч и с е л 1*2*3*...*(N-1 )*N ( о б о з н а ч а е т с я к а к N!). С л о ж н о с т ь з а д а ч и в том, что у ж е 8!=40320, а 131=6227020800. Т и п ы д а н н ы х Integer, Longlnt
Процедуры и функции -— элементы структуризации программ
107
п р и м е н и м ы весьма в ограниченном диапазоне н а т у р а л ь н ы х ч и с е л . Д л я п р е д с т а в л е н и я ф а к т о р и а л а договоримся использовать массив. П р и м е р . А[1]
А[2]
А[3]
0 В массиве з а п и с а н о значение 11!=39916800. К а к и м образом? В А[0] ф и к с и р у е т с я число з а н я т ы х элементов массива, в А[1] — ц и ф р а е д и н и ц результата, в А[2] — ц и ф р а десятков р е з у л ь т а т а , в А[3] — ц и ф р а сотен результата и т. д. Почему т а к , а не наоборот? Т а к а я запись позволяет и с к л ю ч и т ь сдвиг элементов массива п р и переносе з н а ч е н и й в с т а р ш и й разряд. А сейчас наберите, к а к обычно, текст программы, выполните комп и л я ц и ю и, о т к р ы в окно W a t c h , выполните ее в пошаговом реж и м е , о т с л е ж и в а я изменение з н а ч е н и й переменных при не очень большом значении N . Добейтесь полного п о н и м а н и я лог и к и работы п р о г р а м м ы . Program Му10_5; Uses Crt; Const MaxN=300; Type MyArray=Array[0..MaxN] Of Integer; Var A:MyArray; Var i,j,r,w,N:Integer; Begin ClrScr; FillChar (A, SizeOf (A) ,0) ; WriteLn('Введите число, факториал которого необходимо подсчитать.'); ReadLn (N) ; А [ 0 ] :=1 ; А[1 ] : =1 ; j : =2 ; { *Начальные присвоения, начинаем вычислять 2'.*} While (j<=N) And (A[0]<MaxN) Do Begin (*Второе условие фиксирует факт выхода за пределы массива. *} r:=0;i:=l;{*г - перенос из разряда в разряд при выполнении умножения числа j на очередную цифру A[i] предыдущего результата.*) While (i <=А [0]) Or (roO) Do Beginf *Пока не «прошли» все цифры предыдущего результата или есть перенос цифры в старший разряд. *}
Часть вторал
108
w:=A[i]*j + r;{*Умножаем очередную цифру и прибавляем цифру переноса из предыдущего разряда. *1 А[1]:=ы Mod 10;{*Находим новую цифру с номером 1. *} r:= w Div 10;{*Вычисляем значение переноса. *) I f А[А[0]+1]<>0 Then Inc(A[0]);I*Иэменяем, если необходимо, количество задействованных элементов массива А, длину полученного результата. *) Inc(i) ; End; Inc(j); End; For l:=A[0] DownTo 1 Do Write(A[l]);(*Вывод результата. *} WriteLn; ReadLn; End. Н а й д и т е ч и с л о N , п р и к о т о р о м 3 0 0 элементного м а с с и в а А для хранения результата окажется недостаточным. И з м е н и т е п р о г р а м м у т а к , чтобы в ы в о д и л и с ь ф а к т о р и а л ы всех ч и с е л в и н т е р в а л е от 1 до N . И з м е н и т е п р о г р а м м у т а к , чтобы в ы ч и с л я л и с ь степени ч и с е л aN, например З100. Задания для самостоятельной работы 1. Д а н массив ц е л ы х ч и с е л . Н а й т и : • сумму элементов массива, б о л ь ш и х данного ч и с л а А (А вводить с к л а в и а т у р ы ) ; • сумму элементов массива, п р и н а д л е ж а щ и х п р о м е ж у т к у от А до Л (А и Л вводить с к л а в и а т у р ы ) ; • м а к с и м а л ь н ы й элемент массива и его номер, п р и условии, что все э л е м е н т ы р а з л и ч н ы е ; • номера всех элементов массива с м а к с и м а л ь н ы м значением. • м и н и м а л ь н ы й элемент массива; • сумму элементов массива с krro по кг-й, где k1 и k2 вводятся с клавиатуры; • количество н е ч е т н ы х элементов массива; • количество о т р и ц а т е л ь н ы х элементов массива; • сумму п е р в ы х п я т и элементов массива;
Процедуры и функции -— элементы структуризации программ
109
• все элементы, кратные 3 или 5. Сколько их; • сумму всех четных элементов массива, стоящих на четных местах, то есть имеющих четные номера; • сумму всех четных элементов массива (или сумму элементов, кратных заданному числу); • сумму положительных элементов массива; • сумму элементов, имеющих нечетное значение: • сумму элементов, имеющих нечетные индексы; • сумму положительных элементов, значения которых меньше 10; • удвоенную сумму положительных элементов; • сумму отрицательных элементов; • индексы тех элементов, значения которых больше заданного числа А; • количество элементов массива, значения которых больше заданного числа А и кратны 5; • индексы тех элементов, значения которых кратны 3 и 5; • индексы тех элементов, значения которых больше значения предыдущего элемента (начиная со второго); • количество тех элементов, значения которых положительны и не превосходят заданного числа А. 2. Определить: • сколько элементов массива превосходят по модулю заданное число А; • есть ли в данном массиве два соседних положительных элемента? Найти номера первой (последней) пары; • есть ли в данном массиве элемент, равный заданному числу? Если есть, то вывести номер одного из них; • есть ли в данном массиве положительные элементы, кратные k (ft вводить с клавиатуры); • номер первого отрицательного элемента, делящегося на 5 с остатком 2; • пару соседних элементов с суммой, равной заданному числу; П есть ли 2 пары соседних элементов с одинаковыми знаками; • номер последней пары соседних элементов с разными знаками. 3. Измените программу вычисления факториал числа N так, чтобы вычислялись: 1*3*5* ... * (2*N-1), 2*4*6*8* ... * (2*N). 4. Измените программу вычисления степени числа так, чтобы она могла вычислять степени отрицательных целых чисел. 5. Измените программу вычисления степени числа так, чтобы в степень возводились рациональные числа.
Часть вторал
110
Материал для чтения 1. Одномерный массив — это конечное упорядоченное множество элементов. За первым элементом идет второй, за вторым — третий и т. д. В силу этого элементы массива можно перенумеровать целыми числами — индексы элементов. Для доступа к элементу массива на логическом уровне достаточно знать имя массива и индекс элемента. Память компьютера представляет собой последовательность ячеек, имеющих адреса, с возможностью обращения к ячейкам по их номерам. Д л я массива обычно выделяется сплошной участок памяти компьютера. Как из логического адреса элемента массива вычисляется физический адрес? С этой целью массив имеет, если так можно выразиться, описатель (дескриптор). Приведем его структуру, например, для массива Var A: Array[1..10] Of Integer. j
Имя
1
А
j
Адрес начального элемента
j
Определяется системой, обозначим — Addr(A[1]} 1
!|
Индекс начального элемента
1
Индекс последнего элемента
|
Тип элемента
|
10 Integer
| i
Длина элемента
i
2 байта
j
Вычисление адреса элемента г выполняется по формуле: Addr(A[i])=Addr(A[ 1 ])+(i-l )*2. 2. Общие сведения no организации ЭВМ показана на рисунке.
ЭВМ. Структура простой
Процедуры и функции -— элементы структуризации программ
111
«Мозгом» вычислительной машины является центральный процессор. В его функцию входит выполнение программ, находящихся в основной памяти, путем выборки, проверки и последовательного выполнения составляющих их команд. Центральный процессор состоит из нескольких частей. Устройство управления осуществляет выборку команд из основной памяти и определение их типа. Арчфметическо-логическое устройство осуществляет выполнение таких операций, как сложение, сдвиг и т. д. Центральный процессор содержит высокоскоростную память для запоминания промежуточных результатов и управляющей информации. Эта память состоит из регистров, каждый из которых имеет определенное назначение. Одним из регистров является счетчик команд, указывающий адрес команды, которую необходимо выполнить следующей. Регистр команд содержит текущую выполняемую команду. Центральный процессор выполняет каждую команду в виде последовательности простых операций: • выбор очередной команды из основной (оперативной) памяти в регистр команд; • изменение в счетчике команд, чтобы он указывал на адрес команды, следующей за выбранной; • определение типа выбранной команды; • проверка, требуются ли для выполнения выбранной команды какие-либо данные, и если они нужны, то определение их места нахождения в памяти; • загрузка во внутренние регистры центрального процессора требуемых данных из памяти; • выполнение команды; • запоминание результатов выполнения команды в заданных ячейках памяти; • переход к первому действию для выполнения следующей команды. Такое описание функционирования центрального процессора по существу представляет программу, написанную на каком-либо языке программирования. Память — это часть ЭВМ, где хранятся программы и данные. Память состоит из ряда ячеек (наименьшая адресуемая единица памяти), каждая из которых может хранить данные. Каждой ячейке соответствует число, называемое ее адресом, при помощи которого программа может к ней обращаться. Если в памяти N ячеек, то они имеют адреса от 0 до N-1. Все ячейки памяти содержат одинаковое число битов. Если в ячейке k битов, то она может содержать любую из 2к их комбина-
112
Часть вторал
ций. Центральный процессор связан с основной памятью при помощи, к а к минимум, двух регистров — регистра адреса пам я т и и буферного регистра. Д л я того, чтобы считать из памяти содержимое ячейки, центральный процессор загружает адрес этой я ч е й к и в регистр адреса памяти и посылает памяти сигнал чтения. П а м я т ь начинает обработку и после некоторого времени помещает содержимое запрашиваемой ячейки в буферный регистр, где оно доступно центральному процессору д л я последующей обработки. Д л я того, чтобы записать данное в память, центральный процессор записывает адрес ячейки памяти в регистр адреса памяти и записываемое данное в буферный регистр, а затем сигнализирует памяти о начале операции записи. Выше по тексту сказано, что на рисунке приведена структура простой ЭВМ. В материале для чтения занятия 3 мы рассматривали понятие структуры. Структура ЭВМ определяется внутримашинным системным интерфейсом. Говоря простым языком, это то, к а к связаны составные части. Логически возможны два типа связей: к а ж д а я составная часть связана с другой отдельными «проводами»; все составные части связаны друг с другом через общую систему проводов (системная шина). Какое-то количество проводов, объединенных (по назначению) общей целью, называют шиной. Все остальные «конфигурации» являются комбинациями этих типов. Исходя, например, из описания принципов обмена данными между центральным процессором и памятью, системная шина должна состоять из шины данных, шины адреса, шины инструкции и, так к а к по проводам «бегают электрончики» — шины питания. При передаче адреса и данных логически возможны несколько вариантов: передавать их по различным проводам или передавать по одним и тем же проводам, но в разные моменты времени. Выбор того или иного принципа связей определяет структуру ЭВМ. Однако ограничимся этим. Детализация структуры ЭВМ не входит в круг наших задач, это предмет отдельных курсов, например «Архитектура ЭВМ», «Микроэлектроника». 3. Принципы Джона фон Неймана. Несмотря на большие разнообразия существующих в настоящее время ЭВМ, в основы их заложены некоторые общие принципы. Эти принципы были сформулированы в 40-х годах двадцатого столетия выдающимся американским математиком Джоном фон Нейманом и позволили создать устройство обработки информации с достаточно простыми и универсальными принципами работы. Это устройство называется ЭВМ. Первый принцип — принцип произволь-
Процедуры и функции -— элементы структуризации программ
ИЗ
ного доступа к основной (оперативной) памяти. Структурно основная память, как мы говорили, состоит из ячеек. Принцип гласит о том, что процессору в произвольный момент времени доступна любая ячейка, причем время доступа (чтения или записи) одинаково для всех ячеек. Чтобы обеспечить такой доступ, ячейки памяти обязаны иметь свои уникальные имена. Этими именами являются номера ячеек. Д л я лучшего понимания действия этого принципа можно сравнить этот доступ с доступом к данным на магнитной ленте (магнитофон). Данные, записанные на магнитной ленте, также допустимо разбить на элементарные единицы — слова. При произвольном положении магнитной ленты доступно лишь то слово, которое находится под головками чтения — записи. Д л я чтения слова на другом участке магнитной ленты необходимо предварительно переместить этот участок под блок головок, т. е. доступ к этому слову возможен лишь после просмотра предыдущих участков магнитной ленты. Поэтому магнитную ленту принято называть памятью с последовательным доступом. Второй фундаментальный принцип Джона фон Неймана — принцип хранимой программы. Программа решения задачи хранится в оперативной памяти наряду с обрабатываемыми данными. В принципе ЭВМ не различает, что именно хранится в данной ячейке памяти — число, символ или команда. Для решения другой задачи требуется смена в оперативной памяти программы и обрабатываемых данных. Итак, ЭВМ — универсальный инструмент обработки информации. 4. Возникновение информатики как науки связано с появлением ЭВМ и относится к 60-м годам двадцатого столетия. В самом общем смысле под информатикой понимают фундаментальную естественную науку, изучающую процессы передачи, накопления и обработки информации с помощью ЭВМ. Термин «Informatique» введен во Франции на рубеже 6 0 70-х годов. Однако еще раньше в США был введен в употребление термин «Computer Science» для обозначения науки о преобразовании информации с помощью ЭВМ. В настоящее время термины употребляются в эквивалентном смысле. Содержание ядра информатики, на наш взгляд, определяют программные и технические средства. На этом занятии рассмотрено понятие массива, а в материале для чтения дан обзор структуры простой ЭВМ и принципов Джона фон Неймана. Понятие массива носит фундаментальный характер. Эта структура данных наиболее соответствует структуре ЭВМ. Образно выражаясь, оперативная память ЭВМ —
114
Часть вторал
это не что иное, к а к огромный одномерный массив. Элементы массива имеют номера, я ч е й к и п а м я т и имеют номера; массив имеет одно общее и м я , оперативная п а м я т ь имеет одно общее и м я (ПАМЯТЬ). Не из ничего возникло понятие массивэ, а из потребности п р а к т и к и д л я компактного обозначения б о л ь ш и х объемов д а н н ы х , которые необходимо х р а н и т ь при р е ш е н и и задач в п а м я т и компьютера. И, чтобы задействовать эту память, в нее, естественно, необходимо записать данные, и с этими данн ы м и д о л ж н ы быть очень к о м п а к т н ы е и удобные инструменты д л я работы. Этими инструментами я в л я ю т с я ц и к л и ч е с к и е конструкции и структура данных типа массива.
Процедуры и функции-—элементы структуризации программ
115
З а н я т и е № 11. П р о ц е д у р ы План занятия • структура программы; • о в з а и м о д е й с т в и и п р о г р а м м ы и процедуры; • с т а н д а р т н ы е определения; • э к с п е р и м е н т а л ь н а я работа с п р о г р а м м а м и : перестановки з н а ч е н и й п е р е м е н н ы х а, Ь, с в порядке возрастания, выч и с л е н и я в ы р а ж е н и я у=а п + х п +а п _ 1 Л 'х п +...+а 2 *х 2 +а 1 , , 'х 1 +а 0 ; ф о р м и р о в а н и я з н а ч е н и й элементов одномерного массива с п о м о щ ь ю д а т ч и к а с л у ч а й н ы х чисел; поиска элементов, п р и н а д л е ж а щ и х двум массивам одновременно; перестан о в к и частей массива; • в ы п о л н е н и е самостоятельной работы. Структура программы. П р о г р а м м ы на я з ы к е Турбо Паск а л ь состоят из заголовка программы, раздела описаний и тела п р о г р а м м ы . Раздел описаний м о ж е т включать, если так можно в ы р а з и т ь с я , следующие подразделы: меток, констант, типов, п е р е м е н н ы х , процедур и ф у н к ц и й . Последовательность подразделов в структуре п р о г р а м м ы произвольная, но естественно, что если вводится п е р е м е н н а я нового типа, заданного в Туре, то подраздел Туре предшествует подразделу Var. П р и н ц и п нашего я з ы к а п р о г р а м м и р о в а н и я «то, что используется, должно быть описано» с о х р а н я е т с я и д л я раздела описаний. Program <имя программы>; Label <метки>; Const <описание констант>; Туре <описание типов данны\>; Var <описание переменных>; <процедуры и функции>; Begin <основное тело программы>; End. До этого момента времени м ы использовали из раздела описаний т о л ь к о описание переменных и типов. Н а этом з а н я т и и мы начнем и з у ч а т ь процедуры. Структура процедуры повторяет структуру программы. Отличия выделены «полужирным» шрифтом. Procedure <имя процедуры> (<параметры>); Label <метки>; Const <описание констант>; Туре <описание типов данных>;
Часть вторал
116
Var <описание переменных>; <процедуры и функции>; Begin <основное тело процедуры>; End; К а к и е вопросы в о з н и к а ю т п р и з н а к о м с т в е со с т р у к т у р а м и п р о г р а м м ы и п р о ц е д у р ы ? И х много. Н а ч н е м со в з а и м о д е й с т в и я п р о г р а м м ы и п р о ц е д у р ы к а к по у п р а в л е н и ю , т а к и по д а н н ы м . П р и м е ч а н и е В наших решениях задач на протяжении всей книги описание меток мы постараемся не использовать. О взаимодействии программы и процедуры. Ч т о б ы объясн е н и е было н а г л я д н ы м , о б р а т и м с я к п р и м е р у , п р и в е д е н н о м у на р и с у н к е . Слева приведен ф р а г м е н т т е к с т а основной программ ы , с п р а в а — п р о ц е д у р а в ы ч и с л е н и я с у м м ы двух ц е л ы х ч и с е л . Ч т о м ы в и д и м ? П р о ц е д у р а в ы з ы в а е т с я у к а з а н и е м ее и м е н и , которое з а п и с ы в а е т с я после з а р е з е р в и р о в а н н о г о слова Procedure, используемого д л я о б о з н а ч е н и я п р о ц е д у р ы . Begin
~
'иге Sum (\ ,у: Integer
;Var z: Integer)
;
ReadLn (a ,b) Sum (a,b,c) ; WriteLn (с) ; End Л и н е й н ы й ход в ы п о л н е н и я основной п р о г р а м м ы с т а н о в и т с я н е л и н е й н ы м — у п р а в л е н и е в ы ч и с л и т е л ь н ы м процессом перед а е т с я на участок п р о г р а м м н о г о к о д а , з а н и м а е м ы й процедурой. После в ы п о л н е н и я п р о ц е д у р ы о с у щ е с т в л я е т с я возврат на оператор основной п р о г р а м м ы , с л е д у ю щ и й за вызовом процед у р ы (End; в процедуре очень с о д е р ж а т е л ь н ы й оператор, сверхс о д е р ж а т е л ь н ы й — это «стрелка», возврат у п р а в л е н и я в логик у , и з к о т о р о й в ы з ы в а е т с я процедура). М ы не рассматриваем вопросы о том, к а к это в ы п о л н я е т с я . Н а п р и м е р , к а к осуществл я е т с я возврат на оператор WriteLn(c). Н а ш е объяснение идет на уровне к о н с т а т а ц и и фактов, и т о л ь к о . И т а к , взаимодействие п р о г р а м м ы и п р о ц е д у р ы по у п р а в л е н и ю м ы обозначили. Перейдем к в з а и м о д е й с т в и ю по д а н н ы х . В п р о г р а м м е определены пер е м е н н ы е а, Ъ, с. В процедуре — х, у, г её п а р а м е т р ы , но они явл я ю т с я п е р е м е н н ы м и процедуры. П р и ч е м х, у о п и с а н ы без идентификатора Var, а г — с идентификатором Var. Поясним разницу очередным рисунком.
Процедуры и функции -— элементы структуризации программ
117
При вызове процедуры Sum(a,b,c) из основной программы значение переменной а присваивается переменной х процедуры Sum, а значение переменной Ъ становится значением переменной у, и все. Стрелочка на рисунке в одну сторону. Чуть сложнее с переменными с и г . Стрелочка на рисунке и в ту, и в другую стороны. Образно выражаясь, процедура Sum знает о том, где в памяти компьютера находится переменная с, и она знает, что при изменении значения переменной г необходимо произвести соответствующее изменение значения переменной с. Это обратная связь по данным от процедуры к основной программе. На такой тип связи указывает идентификатор Var в описании параметров процедуры. Перейдем к ряду стандартных определений. В любой программе все переменные делятся на глобальные и локальные. В нашем фрагменте программы переменные а, Ь, с — глобальные, а х, у, г — локальные. Глобальные переменные — это переменные из раздела описаний основной части программы, а локальные — из раздела описаний процедур и функций. Если разница только в месте описания переменных, то «стоит ли шкурка выделки»? Добавим ещё одно предложение. Локальные переменные существуют только в течение времени работы процедуры, определяются (создаются) при её вызове и исчезают после завершении работы процедуры (после выполнения «сверхсодержательного» оператора End;). Таким образом, если бы в основной программе было три фрагмента с вызовом процедуры Sum (желательно с различными параметрами), то для программного кода процедуры Sum три раза выделялось место в оперативной памяти, соответственно, и для переменных х, у, г и три раза
освобождалось. Если такое объяснение облегчает понимание разницы между глобальными и л о к а л ь н ы м и переменными, то считаем его п р а в и л ь н ы м . Параметры. При описании процедуры указывается список формальных параметров. К а ж д ы й параметр я в л я е т с я локальным по отношению к описываемой процедуре, к нему можно обращаться только в пределах данной процедуры (в н а ш е м примере х, у, г — формальные параметры). Фактические парамет ры — это параметры, которые передаются процедуре при обращ е н и и к ней (а, Ъ, с — фактические параметры). Число и тип формальных и фактических параметров должны совпадать с точностью до их следования. Параметры-значения. Другими словами, передача параметров по значению. Копия фактического параметра становится значением соответствующего формального параметра. Внутри процедуры можно производить любые действия с данным формальным параметром (допустимые для его типа), но эти изменения н и к а к не отражаются на значении фактического параметра, то есть к а к и м он был до вызова процедуры, то таким же и останется после завершения ее работы (х, у — параметры-значения). Параметры-переменные. Другими словами, передача параметров по ссылке. Это те формальные параметры, перед которыми стоит идентификатор Var. Передается адрес фактического параметра (обязательно переменной), после этого формальный параметр становится его синонимом. Любые операции с формальным параметром выполняются непосредственно над фактическим параметром. Договоримся о том, к а к мы будем стараться использовать процедуры, может быть, в ущерб эффективности программ, например, с точки зрения количества переменных и т. д. К а ж д а я процедура должна иметь одну точку входа и одну точку выхода, использование глобальных переменных в процедуре должно быть минимально, взаимодействие вызывающей логики с процедурой должно осуществляться (по возможности) только через параметры. Зачем нам эти оговорки? Мы осваиваем структурную технологию разработки программ, при этом на каждом этапе т е к у щ а я задача разбивается на ряд подзадач, определяя тем самым некоторое количество отдельных подпрограмм (под программа — это повторяющаяся группа операторов, оформленная в виде самостоятельной программной единицы). При этом мы стараемся структурировать задачу не только по управ-
Процедуры и функции-—элементы структуризации программ
119
лению, но и по данным, используя при этом весьма ограниченный набор инструментов (параметры-значения, параметры-ссылки). К о н ц е п ц и я процедур и ф у н к ц и й — один из м е х а н и з м о в второго в и т к а р а з в и т и я т е х н о л о г и й п р о г р а м м и р о в а н и я , а именно, с т р у к т у р н о г о п р о е к т и р о в а н и я . Экспериментальный раздел работы 1. Составить п р о г р а м м у перестановки з н а ч е н и й переменных а, Ь, с в п о р я д к е в о з р а с т а н и я , т. е. так, чтобы а<Ь<с. Текст реш е н и я простой. Program Myll_l; Var а ,Ь,с:Integer; Procedure Swap (Var Var t:Integer; Begin t:=x; <:=y;y:=t; End; Begin WriteLn('Введите три ReadLn (a ,b, c) ; I f a>b Then Swap (a,b) I f b>c Then Swap (b, c) I f a>c Then Swap (a,c) WriteLn (a, ' ' ,b, ' ' ,c) ReadLn ; End.
x,у:Integer);
числа
') ;
; ; ; ;
Найдите ошибку в решении. Исправлению подлежит имя одной переменной в одной строке п р о г р а м м ы . Составьте д л я пои с к а о ш и б к и п о л н у ю систему тестов. Измените программу так, чтобы а н а л о г и ч н а я задача р е ш а л а с ь д л я четырех вводимых чисел. 2. Составить программу в ы ч и с л е н и я в ы р а ж е н и я у=а п *х п +а г1 _ 1 * * х п + . . . + а 2 * х 2 + а 1 * х 1 + а 0 , где все д а н н ы е — целые ч и с л а . Коэ ф ф и ц и е н т ы a n , a ^ j , ..., а р а 0 я в л я ю т с я п е р в ы м и ч л е н а м и арифметической прогрессии, определяемой первым элементом (q) и разностью м е ж д у соседними э л е м е н т а м и (d). Знач е н и я q, d, n, х вводятся с к л а в и а т у р ы . Н а п р и м е р , q=2, d = 3 , п = 5 и х = 4 . Н а м необходимо в ы ч и с л и т ь в ы р а ж е н и е 2 * 4 5 + 5 * *44+8*43+11*42+14*41+17*4°.
Program Myll_2: Var q, d,n,x,t,w,1: Integer; s: Integer; Procedure Degree(x,у: Integer; Var st: Vai l:Integer; Begin st:=1; For i:=l To у Do st:=st*x; End; Begin WriteLn('Введите исходные данные - четыре больших числа'); ReadLn(q,d,n,х); s:=0;t:=n; For i:=l To n+1 Do Begin Degree(x,t,w);s:=s+q^w; Dec(t);Inc(q,d) ; End; r WriteLn('Результат ,s); ReadLn; End.
Integer);
не
очень
И с п о л ь з о в а н и е п р о ц е д у р ы д л я р е ш е н и я этой з а д а ч и нескол ь к о и с к у с с т в е н н о , но п р о с т и т нас ч и т а т е л ь . Н а ш а ц е л ь — отр а б о т а т ь м е х а н и з м и с п о л ь з о в а н и я п р о ц е д у р , а д л я этого составьте т а б л и ц у и з м е н е н и я з н а ч е н и й п е р е м е н н ы х q, t, w п р и работе п р о г р а м м ы . П р о в е р ь т е п р а в и л ь н о с т ь з а п о л н е н и я т а б л и ц ы , используя отладчик и р е ж и м пошагового исполнения программы. З а ф и к с и р у й т е з н а ч е н и я q, d, х и н а й д и т е э к с п е р и м е н т а л ь н ы м путем ( и л и м о д и ф и ц и р у я п р о г р а м м у ) то з н а ч е н и е п, п р и котором д и а п а з о н а т и п а Integer не д о л ж н о «хватать» д л я х р а н е н и я результата. П р и м е ч а н и е Переменные с именем х в основной программе и в процедуре Degree — это разные переменные! З а п и ш е м н а ш п р и м е р по-другому: 1 7 + 4 * ( 1 4 + 4 * ( 1 1 + 4 * ( 8 + +4*(5+4*2)))). Р е з у л ь т а т в ы ч и с л е н и й , естественно, тот ж е сам ы й . В общем виде з а п и с ь в ы г л я д и т с л е д у ю щ и м образом: а 0 + + х * ( а 1 + х * (а 2 +х*(... +X* ( a n , 1 +х*а п )))). З а п и с ь п о л и н о м о в , чисел, р а з л о ж е н н ы х по с т е п е н я м о с н о в а н и я с и с т е м ы с ч и с л е н и я , н а з ы в а е т с я схемой Горнера. И з м е н и т е п р о г р а м м у т а к , ч т о б ы в ы ч и с л е н и е в ы р а ж е н и я о с у щ е с т в л я л о с ь п о схеме Горнера. Уч-
Процедуры и функции-—элементы структуризации программ
121
тите, что в этом случае Вам до первой и т е р а ц и и необходимо знать п о с л е д н и й член а р и ф м е т и ч е с к о й прогрессии. А что о з н а ч а е т з а п и с ь 4 2 * 5 + 4 * 8 + 4 * 11 + 4 * 14 + 4 * 17+? М о ж н о л и в ы ч и с л и т ь в ы р а ж е н и е ? О к а з ы в а е т с я , да. Берём два ч и с л а 4 и 2 и в ы п о л н я е м с н и м и операцию, з а п и с а н н у ю после н и х , т.е. у м н о ж е н и е . Сохраняем результат и п р о д о л ж а е м в ы ч и с л е н и е . Берём 8 и 5 и в ы п о л н я е м сложение. Потренируйтесь. Н а п и ш и т е н е с к о л ь к о примеров и преобразуйте их запись к т а к о м у виду. Она н а з ы в а е т с я обратной польской записью, в честь м а т е м а т и к а Я. Л у к а ш е в и ч а . Д л я общего случая в н а ш е й задаче о б р а т н а я п о л ь с к а я запись имеет вид: a n х * а п - 1 + ... а 2 х * а : + х * а 0 + . У Вас есть процедура Part, в ы п о л н я ю щ а я одно а р и ф м е т и ч е с к о й действие (умножение и л и сложение) над двум я ч и с л а м и . Н а п и ш и т е программу д л я р е ш е н и я н а ш е й задачи с ее и с п о л ь з о в а н и е м . Procedure Par t (у. ,у: Integer ; t: Boolean/Var z: In teger) ; Begin I f t Then Else z:=x*y; End; У нас есть три р е а л и з а ц и и одной и той ж е задачи. Сравните количество о п е р а ц и й у м н о ж е н и я и с л о ж е н и я , необходимых д л я п о л у ч е н и я результата в к а ж д о й из них. К а к о й вывод долж н ы м ы сделать? 3. З а д а н и е з н а ч е н и й э л е м е н т а м одномерного массива (с помощ ь ю генератора с л у ч а й н ы х чисел) и вывод результата на экран м ы рассмотрели на п р е д ы д у щ е м з а н я т и и . Оформим эти д е й с т в и я к а к процедуры. Program Myll_3; Const n=8;l=-10;h=21; Type MyArray=Array[1..n] Of Integer; Var A:MyArray; Procedure I n i t ( t , v,w:Integer; Var X:MyArray); Var i-.Integer; Begin Randomize; For i :=1 To t Do X [ l ] :=v+Integer (Random (w)) ; EndsProcedure Print (t: In teger ;X: MyArray) ; Var i:Integer;
122
Часть вторал
Begin For i:=l To t Do Write (X [ 1 ] : 5) ; { *Да простят нас читатели за использование числа 5 (5 позиций на экране для вывода X[i]) в этой процедуре, мы еще только учимся. *} WriteLn; End; Begin WriteLn('Формирование значений элементов массива А') ; Irnt(n,l,h,A);{ * Значения элементов массива А формируются из интервала целы', чисел от 1 до h. Количество элементов п.*} Wn teLn (' Выв од') ; Print<п,А);f*п первые элементов массива А выводятся на экран (в данном случае) .*> End. К а з а л о с ь бы, что м ы т о л ь к о п р о и г р а л и . Б ы л о н е с к о л ь к о строк п р о г р а м м н о г о кода, а сейчас ... П о ж е р т в у е м к а ж у щ е й с я простотой п р е д ы д у щ е й версии п р о г р а м м ы . С этого м о м е н т а будем стараться создавать п р о г р а м м ы т а к , чтобы основная прог р а м м а состояла из вызовов процедур и ф у н к ц и й . Н а р и с у й т е схему (по подобию тех, что п р и в е д е н ы в н а ч а л е з а н я т и я ) , отраж а ю щ у ю в з а и м о с в я з ь основной п р о г р а м м ы и п р о ц е д у р к а к по у п р а в л е н и ю , т а к и по д а н н ы м . И з м е н и м заголовок п р о ц е д у р ы Print на Procedure Print(n: Integer;X:Array[l..n] Of Integer) и з а п у с т и м п р о г р а м м у . Результат не заставит себя ж д а т ь . Это о ш и б к а Error 54: OF expected. Курсор н а х о д и т с я на к в а д р а т н о й скобке, т.е. после слова Array о ж и д а е т с я Of. «Пойдем на поводке» у системы программ и р о в а н и я , и з м е н и м на Procedure Print(n:Integer;X:Array Of Integer). Р е з у л ь т а т чуть-чуть и з м е н и л с я , у ж е з н а к о м а я ошибк а — Error 201: Range check error. О т к л ю ч и м к о н т р о л ь в ы х о д а за п р е д е л ы д и а п а з о н а — {$R-j. П р о г р а м м а заработала, точнее, она выдает результат. И т а к , сплошные загадки. Попробуйте дать им разумное объяснение. 4. Д а н ы два одномерных массива. Н а й т и э л е м е н т ы , принадлеж а щ и е и тому и другому массивам. Н а ш а т е х н о л о г и я напис а н и я программ (использование процедур и функций) начинает приносить дивиденды. Процедуру Init при решении задачи требуется использовать два раза с р а з л и ч н ы м и параметрами, процедуру Print — три раза. Сделаем «костяк» п р о г р а м м ы . Процедуры Init и Print, естественно, не приводятся, они у нас
Процедуры и функции-—элементы структуризации программ
123
почти у н и в е р с а л ь н ы и берутся и з предыдущего задания. Прог р а м м а (ее «костяк») д о л ж н а компилироваться. Сохраните ее. Н а б и р а т ь весь текст п р о г р а м м ы , а затем п р и с т у п а т ь к отладк е — это «дурной» тон в программировании, очень «дурной». В о з ь м и т е з а п р а в и л о и всегда его п р и д е р ж и в а й т е с ь — «в любой момент времени у Вас д о л ж н а быть к о м п и л и р у е м а я программа, сохраненная на внешнем носителе». Кроме того, текст любой п р о ц е д у р ы и, естественно, основной п р о г р а м м ы долж е н п о м е щ а т ь с я на э к р а н е и, конечно, быть ч и т а е м ы м . ($R + ) Program Myll_4; Const n=10;la=-10;ha=21; т=8 ; lb=-15;hb=31; Type MyArray-Array[1..n] Of Integer; Var А,В,С:MyArray; к: IntegersProcedure Solve (qx, qy: Integer ; Var qz:Integer; X, Y:MyArray; Var Z: MyArray); Begin EndsBegin Init (n,la,ha, A) ; WriteLn ('Вывод значений элементов массива А') ; Print (п, А) ; Init(m,lb,hb,В) ; WriteLn('Вывод значений элементов массива В') ; Print (m,B) ; Solve(n,m,k,A,B,C); WriteLn ('Вывод тех целых чисел, которые есть и в А, ив В') ; Print (к, С) ; ReadLn; End. Н а ч н е м у т о ч н я т ь н а ш е решение, а именно, процедуру Solve. Procedure Solve(qx,qy:Integer;Var X,Y:MyArray; Var Z: MyArray); Var i , j : IntegersBegin qz:=0; For i:=1 To qx Do For j:=l To qy Do I f X[i]=Y[j] Then Begin Inc(qz);Z[qz]:=X[i];(j:=qy;}End; End;
qz:Integer;
Часть вторал
124
После запуска программы окажется, что п р и некоторых исходных данных результат неправильный. Н а п р и м е р , в первом массиве 10 присутствует один раз, во втором — п я т ь раз. Согласно формулировке задачи в ответе 10 д о л ж н а присутствовать один раз, а она выводится п я т ь раз. Уберем фигурные скобки у оператора j:=qy. Мы «насильно» изменяем управляющую переменную ц и к л а For j:=l ... Выводится п р а в и л ь н ы й результат. Однако этот прием «насильственного», т. е. не самим оператором For, изменения управляющей переменной считаем плохим программированием. Procedure
Solve(qx,qy:Integer;Var qz:Integer; X,Y: My Array ; Va r Z: MyArray) ; Var i,j:Integer; Begin qz:=0 ; For i:=l To qx Do Begin j:=l; While (J<=Q1) And (X [ i ] <>Y [ j ] ) Do I n c ( j ) ; I f J<=qv Then Begin Inc (qz) ; Z (qz ] : =X [ l ] ;End; End; End; Продолжите эксперименты с программой. Определите случаи ее неработоспособности. 5. Дан массив А из п элементов и число от (1<т<п). Не используя дополнительных массивов, переставить первые m элементов в конец массива, а элементы с т + 1 по п — в начало, сох р а н я я порядок элементов. Идея решения. Переворачиваем первые т элементов, переворачиваем элементы, н а ч и н а я с 771+1. Переворачиваем все элементы массива. Пример. 71=10, 771=6., массив А: 5 1 - 4 3 7 2 - 1 9 8 6 . 2 7 3 - 4 1 5 - 1 9 8 6 первое переворачивание т элементов. 2 7 3 -4 1 5 6 8 9 - 1
второе переворачивание элементов с т+1 по п. третье переворачивание элементов с 1 ПО 71. В тексте решения не приводится реализация процедур Init и Print. Для процедуры Rev рекомендуется выполнить «ручную» трассировку при различных значениях t и I. ($R+) Program Myll_5; -1 9 8 6 5 1
-4 3 7 2
Процедуры и функции-—элементы структуризации программ
125
Const п=10;т=6 ; Type MyArray—Array[1..п] Of Integer; Var A:MyArray; Procedure Imt (Var X:MyArray) ; Procedure Print(X:MyArray); Procedure SwapfVar a,b: Integer) ;( *Из первого задания. *) Procedure Rev(t, 1:Integer;Var X:MyArray); Var l:Integer; Begin For l: = t To t+(l-t) Div 2 Do Swap <X[i],X[l-i +t]) ; End; Begin Imt I A) ; Print (A) ; Rev (1 ,m,A) ; Rev (m+1, n ,A) ; Rev (l,n,A) ; Print (A) ; ReadLn; End. Задания для самостоятельной работы 1. Д а н ы два р а з л и ч н ы х в ы р а ж е н и я типа: у - а ^ х ' Ч - а ^ *х п +...+ + а 2 * х 2 + а 1 * х 1 + а 0 , z=b n J r x n +b n _ 1 *x n +...+b 2 ' < x 2 +b 1 , ! ; x 1 +b 0 , где все д а н н ы е — ц е л ы е числа. К о э ф ф и ц и е н т ы в ы р а ж е н и й хранятс я в массивах А и В. П р и заданном з н а ч е н и и х н а й т и максимальное з н а ч е н и е (у,г). 2. Д а н ы два одномерных массива из ц е л ы х чисел. Найти элем е н т ы , которые есть в первом массиве и которых нет во втором массиве. 3. Д а н ы два одномерных массива из ц е л ы х чисел. Найти элем е н т ы , к о т о р ы х нет одновременно и в том и в другом массивах. 4. Р е ш и т ь задачу 4 из раздела экспериментальной работы д л я трех массивов. 5. Д а н ы три одномерных массива из ц е л ы х чисел. Найти элем е н т ы , к о т о р ы е есть в первом массиве и которых нет во втором и третьем массивах. 6. Р е ш и т ь задачу 3 д л я трех одномерных массивов. 7. Д а н ы два одномерных массива из ц е л ы х чисел, разной размерности. Н а й т и среднее арифметическое элементов каждого массива и и х сумму. Р е ш и т ь задачу д л я трех массивов.
126
Часть вторал
8. Даны три одномерных массива из целых чисел одинаковой размерности. Сформировать четвертый массив, к а ж д ы й элемент которого равен максимальному из соответствующих элементов первых трех массивов. 9. Даны два одномерных массива (А, В) одинаковой размерности п и число т . Поменять местами первые элементы массивов и выполнить перестановку элементов массивов в обратном порядке (задание № 5 раздела экспериментальной работы). 10. Дано четное число и; проверить для этого числа гипотезу Кристиана Гольдбаха (1742 год). Эта гипотеза (по сегодняшний день не опровергнутая и полностью не доказанная) заключается в том, что каждое четное п, большее двух, представляется в виде суммы двух простых чисел. Ограничим диапазон проверяемых чисел интервалом 999>п>2. Оформить процедурой логику распознания простых чисел. Примеры: 6=3+3, 12=5+7, 30=7+23, 308=31+277, 992=73+919. Исследовать гипотезу К. Гольдбаха для больших значений п. 11. Известны следующие п р и з н а к и делимости числа п: • для делимости на 2 необходимо, чтобы последняя цифра числа делилась на 2; • для делимости на 3 требуется, чтобы сумма ц и ф р числа делилась на 3; О для делимости на 4 необходимо, чтобы число из последних двух цифр делилось на 4; • для делимости на 5 необходимо, чтобы последняя цифра числа была 0 или 5; • для делимости на 8 необходимо, чтобы число из 4 последних цифр делилось на 8; • для делимости на 9 необходимо, чтобы сумма цифр числа делилась на 9; • для делимости на 11 необходимо, чтобы разность между суммой цифр, стоящих на четных местах, и суммой цифр, стоящих на нечетных местах, делилась на 11. Написать процедуры проверки признаков делимости. Проверить их для различных значений п. Материал для чтения 1. Некоторые факты из элементарной теории вероятностей. Всем нам достаточно часто приходится сталкиваться с ситуациями, когда, зная возможные исходы некоторого события (опыта), мы не можем точно предсказать его результат. Например, при бросании монеты — какой стороной она упадет
Процедуры и функции -— элементы структуризации программ
127
вверх; при бросании игральной кости — к а к а я из шести сторон окажется вверху; при вытаскивании карты из игральной колоды — к а к а я карта будет вытянута и т. д. Главное, что присуще всем этим ситуациям то, что исходы равноправны. Выпадет «орел» или «решка»; выпадет сторона с числом от 1 до 6; вытянута одна из 52 карт. Как оценивать исходы, какую меру взять для оценки того, что произойдет то или иное событие. Например, выпадет четное число при бросании игральной кости. Общее количество исходов 6, число благоприятных исходов 3. Вытянута карта пиковой масти, общее количество исходов 52, благоприятных — 13. Понятие меры в теории вероятности вводится следующим образом. Определяется множество элементарных событий S, его элементы считаются равноправными, равновероятными. Каждое другое событие А — подмножество S, оно состоит из элементарных событий. Отношение IА | / | S I, где через I I обозначено количество элементов в множестве, называется вероятностью события А и обозначается Р(А). Вероятность любого события А заключена между нулем и единицей: 0<Р(А)<1. Примеры: • При бросании двух игральных костей вероятность получения 10 и более очков равна 4 / 3 6 = 1 / 9 , так как всего имеется 36 равновероятных исходов и четыре из них благоприятны — (5,5), (5,6), (6,5) и (6,6). • Вероятность того, что на первой кости очков выпадет меньше, чем на второй, равна 15/36. О Стрелок попадает в цель в среднем 92 раза из 100 выстрелов. Вероятность попадания равна 92/100 (0,92). • На каждую 1000 готовых деталей некоторого предприятия приходится в среднем 16 бракованных. Вероятность изготовления брака для данного производства равна 0,016. • Первый стрелок попадает в цель с вероятностью 0,8, второй — 0,7. Найти вероятность поражения цели, если оба стрелка стреляют одновременно. Цель считается пораженной при попадании в нее хотя бы одной из двух пуль. Пусть производится 100 двойных выстрелов. Примерно в 80 из них цель будет поражена первым стрелком, а в 20 случаях он промахнется. Второй стрелок из 10 выстрелов примерно 7 раз поражает цель, в 20 выстрелах — 14 раз. Таким образом, при 100 выстрелах цель окажется пораженной примерно 94 раза. Вероятность поражения — 0,94.
128
Часть вторал
Событие, вероятность которого равна 1, называется достоверным, — оно наступает всегда. Монета опустится «гербом» и л и «решкой», считаем, что на ребро она встать не может. Событие, вероятность которого равна 0, называется невозможным, — оно не наступает никогда. В нашем примере бросания игральной кости (и других) есть то, что называют элементарным событием — выпадение стороны кости с определенным числом. Остальные события составляются из элементарных событий, например выпадение стороны кости с четным числом. События А и В называют несовместимыми, если появление одного из них исключает появление другого. П р и бросании монеты «герб» и «решка» ие могут выпасть одновременно. Если А и В — несовместимые события, то Р(А+В)=Р(А)+Р(В). Утверждение обобщается на п несовместимых событий. Говорят, что несколько событий A j , А 2 , ..., А^ образуют полную группу, если вероятность того, что произойдет одно из них, я в л я е т с я достоверным событием. Примеры.: • Опыт — бросание двух монет. События «два герба», «две решки» и «один герб, одна решка» образуют полную группу и являются несовместимыми. • Опыт — бросание игральной кости. События «1 или 2 очка», «2 или 3 очка», «3 или 4 очка», «4 или 5 очков» и «5 или 6 очков» образуют полную группу, но не являются несовместимыми. • Опыт — вынимание одной к а р т ы из колоды в 36 карт. Соб ы т и я — вынимание «туза», «короля», «дамы», «валета», «десятки», «девятки», «восьмерки», «семерки» и «шестерки» образуют полную группу и являются несовместимыми. • Опыт — передача в одинаковых условиях трех сообщений равной длины. События «искажено первое сообщение», «искажено второе сообщение» и «искажено третье сообщение» не образуют полную группу и не являются несовместимыми. 2. Информация. Итак, об информатике говорят как о науке по работе с информацией с помощью ЭВМ. Мы называем ЭВМ универсальной машиной по обработке информации. Обсудим кратко термин информация, понимая при этом, что строгого определения нам не дать, ибо понятие информации является первичным, неопределяемым. Слово информация используется в двух значениях — качественном и количественном. С одной
Процедуры и функции -— элементы структуризации программ
129
стороны, мы понимаем под информацией конкретные сведения о чем-либо, представленные в виде речи, текста, изображения, цифровых данных, графиков, таблиц и т. п. С другой стороны — её численную меру, т. е. выраженное в битах количество абстрактной информации. Остановимся на втором аспекте, ибо обсуждение первого заведет нас в дебри словесной эквилибристики. Одним из параметров измерения информации является ее объём: количество бит, байт и т. д. Есть последовательность из единиц и нулей. Длину этой последовательности в одних книгах называют количеством информации, в других — объемом информации. Другой способ оценки количества информации основан на вероятностном подходе. Вводится мера неопределенности, мера нашего незнания чего-либо. Устранение неопределенности достигается за счет получения информации. Если есть «лапоть» для измерения неопределенности, то он может быть использован для наших целей — определения количества информации. Приведем традиционное рассмотрение этой схемы на примере игры «Бар — Кохба». Игра «Бар — Кохба». Ее суть заключается в следующем. Один из игроков что-то загадывает, а второй должен отгадать загаданное, задавая вопросы, которые предполагали бы только ответы типа «да», «нет». Предположим, что в классе 16 учеников и учитель загадал номер по журналу одного из учеников, они по какой-то причине пронумерованы числами от 0 до 15. В начальный момент времени у нас полное незнание того, какой номер загадан. Первая серия наших вопросов. 1-й вопрос. Находится ли загаданный номер в интервале от 8 до 15? Ответ — д а (1). 2-й вопрос. Находится ли загаданный номер в интервале от 12 до 15? Ответ — нет (0). 3-й вопрос. Находится ли загаданный номер в интервале от 11 до 12? Ответ — да (1). 4-й вопрос. Загадан номер 11? Ответ — нет (0). Вторая схема формулировок вопросов. 1-й вопрос. Находится ли загаданный номер в интервале от 0 до 3? Ответ — нет. 2-й вопрос. Находится ли загаданный номер в интервале от 12 до 15? Ответ — нет. 5-452
3-й вопрос. Находится ли загаданный номер в интервале от 4 до 7? Ответ — нет. 4-й вопрос. Находится ли загаданный номер в интервале от 8 до 11? Ответ — да. 5-й вопрос. Находится ли загаданный номер в интервале от 10 до 11? Ответ — да. 6-й вопрос. Загадан номер 11? Ответ — нет. Чем отличаются серии вопросов? Мера нашего незнания в том и в другом случаях одинакова, т. е. получено одно и то же количество информации. Примем за единицу измерения информации (1 бит) количество информации, содержащееся в ответе при условии, что ответы равновероятны. В первом случае к а ж д ы й ответ содержит один бит информации, ибо ответы «да» и «нет» были равновероятны. Во втором случае — нет, в среднем один ответ содержал 4 / 6 бита информации. Всего получено 4 бита информации. Неопределенность полностью устранена, мы знаем загаданный номер. Пусть есть N равновероятных событий и выделено одно из событий (f). Д л я того, чтобы определить t, необходимо получить количество информации I, равное LogyV. Эта формула для вычисления количества информации предложена американским инженером Р. Хартли в 1928 году. Если события не равновероятные, то количество информации вычисляется по формуле американского математика К. Шеннона: I=-(pJLog2p1 +p2Log2p2+...+pNLog2pN), где р, — вероятности событий. Из формулы К. Шеннона получается формула Р. Хартли при p=l/N д л я всех значений i. Действительно, 1=-(1/ N*Log2(l/N)+ ...+l/N*Log2(l/N))=Log2N. В скобках первой серии вопросов указаны цифры 0 или 1. Если выписать ответы в виде двоичного числа 1010 2 и перевести его в десятичную систему счисления, то получим число 10 — номер загаданного ученика! Итак, к а ж д а я двоичная цифра «несет» в себе один бит информации, отсюда и название единицы измерения — БИТ (binary digit — двоичная цифра). В игре «Бар — Кохба» вопросы задаются последовательно, на основании ответов учителя. А можно ли задать сразу четыре вопроса, а затем получить четыре ответа учителя и определить номер загаданного ученика. Оказывается, да. Вопросы формулируются так: «Вы загадали номер, двоичная запись которого содержит единицу в первом разряде». Обратите внимание на то, что и в этом случае ответы «да» и «нет» равновероятны.
Процедуры и функции-—элементы структуризации программ
131
Предположим, что учитель одновременно загадал номера двух учеников (tt и t2) в р а з н ы х классах. Количество учеников в классах — Nt и N2. Необходимо отгадать пару (xt,x2), принадл е ж а щ у ю множеству всех пар — Согласно формуле Х а р т л и необходимо задать Log2(N +N2) вопросов, или получить Log2(N1*N2) бит информации. С другой стороны, номера учеников м о ж н о отгадывать независимо, д л я отгадывания t t — LoggNj вопросов, д л я отгадывания второго — LogzN2 вопросов. Следовательно, общее число вопросов равно Log^N 1+Log2N2. Итак, м ы п о л у ч и л и Log2(N что совпада2)= Log^j+Log^2, ет с хорошо известным свойством логарифмической ф у н к ц и и . Это закон аддитивности информации. Примеры. Имеется 27 монет, 26 настоящих и одна фальшивая, она легче н а с т о я щ и х . Сколько взвешиваний необходимо произвести, чтобы определить фальшивую монету? Ф а л ь ш и в о й может оказаться любая из 27 монет, следовательно, по формуле Х а р т л и количество недостающей информации равно Log227 битов. Любое взвешивание имеет три исхода и может дать нам только Log,3 битов информации. Если мы производим X взвешиваний, то они дадут X*Log23 битов информации. И т а к , X*Log23>Log227=Log233=3-*Log23. Следовательно, Х>3. На самом деле достаточно ровно 3 взвешивания: первое по 9 монет, второе по 3 монеты из найденной группы и, наконец, по одной монете из найденной группы по 3 монеты. Чтобы н а й т и элемент множества, состоящего из 7 элементов, необходимо задать три вопроса, а чтобы найти элемент множества, состоящего из 9 элементов, — 4 вопроса, т. е. по формуле Х а р т л и получить Log27+Log29 битов информации. Но если необходимо отгадать пару элементов из этих множеств, то требуется получить Log2( 7*9 ) битов информации, а это меньше 6 вопросов. Противоречия нет — Log2(7*9) = Log27+Log29= =5,97728<6. Как понимать формулу Хартли, если Log2N не является ц е л ы м числом? Предположим, что мы выполняем отгадывание не один, a k раз, т. е. последовательность из k неизвестных элементов — (хгх2, ...,хк). Таких последовательностей Nk. Очевидно, что для числа вопросов (s k ) в этом случае выполняются следующие неравенства: Log2Nk<sk<Log2Nk+l или Log2N<sk/k<Log2N+l/k. Число sjk показывает, сколько вопросов в среднем необходимо для того, чтобы отгадать один элемент множества, содержащего N элементов. Выбирая значение k достаточно большим, величину 1/k можно сделать сколь угодно малой. Итак, в том
132
Часть вторая
случае, когда N не совпадает со степенью д в о й к и , число вопросов, которое в среднем необходимо задать д л я о т г а д ы в а н и я одного элемента м н о ж е с т в а мощности N, будет о т л и ч а т ь с я от LogzN на сколь угодно м а л у ю величину.
Процедуры и функции-—элементыструктуризациипрограмм
133
З а н я т и е № 12. Ф у н к ц и и План занятия • описание функции; • несколько фактов из комбинаторики; • экспериментальная работа с программами вычисления числа сочетаний из n по т ; получения палиндрома из числа путем последовательного его «перевертывания» и сложений; нахождений общей части п отрезков; • выполнение самостоятельной работы. Описание функции. Функция выглядит почти так же, как и процедура. Почти единственное отличие в том, что заголовок функции начинается с ключевого слова Function и кончается типом возвращаемого данной функцией значения. Function <имя функции> [(СПИСОК параметров)]: <тип результата>; Кроме того, в теле функции обязательно должен быть хотя бы один оператор присвоения, где в левой части стоит имя функции, а в правой — ее значение. Иначе значение функции не будет определено. Несколько фактов из комбинаторики. Перестановкой из п элементов назовём упорядоченный набор из п различных натуральных чисел, принадлежащих интервалу от 1 до п. Пусть п равно 3. Перечислим все перестановки из 3 элементов: (1 2 3), (1 3 2), (2 1 3), (2 3 1), (3 1 2), (3 2 1). Количество перестановок из п элементов (обозначается как Рп) равно п'=1*2*...*п (факториалу числа п). Действительно, есть п способов для выбора элемента на первое место, ( п - 1 ) способ для выбора элемента на второе место и т. д. Общее количество способов — это произведение п*(п-1)*(п-2)*..*2*1. Перечислите все перестановки из 4 элементов. Количество перестановок — 24. Подсчитайте вручную значения п\ для п из диапазона от 5 до 12. Размещения с повторениями. Элементы относятся к п различным видам. Из них составляются всевозможные расстановки по m элементов в каждой. При этом в расстановки могут входить и элементы одного вида. Две расстановки считаются различными, если они отличаются друг от друга или входящими в них элементами, или порядком элементов. Пусть п равно 3, а т — 2 (как обычно, рассматриваем натуральные числа). Размещения с повторениями: (1,1), (1,2), (1,3), (2,1), (2,2), (2,3), (3,1), (3,2), (3,3). Число таких расстановок R(n,m)=n m . Доказывается с помощью математической индукции.
Часть вторал
134
Перечислите все размещения с повторениями при п=4 и т = 2 . Подсчитайте вручную значение R(n,m) для нескольких значений п и т . Размещением без повторений из п элементов по т называется упорядоченный набор из m различных чисел, принадлежащих интервалу от 1 до п. Два размещения считаются различными, если они либо отличаются друг от друга хотя бы одним элементом, либо состоят из одних и тех ж е элементов, но расположенных в разном порядке. Пусть п равно 4, a m — 2. Перечислим все размещения: (1, 2), (2, 1), (1, 3), (3, 1), (1, 4), (4, 1), (2, 3), (3, 2), (2, 4), (4, 2), (3, 4), (4, 3). Количество размещений (A(n,m)) вычисляется по формуле: A ( n , m ) = n A ( n - l ) * . . . * ( n - m + l ) или A(n,m)=n!/(n-m)! Перечислите все размещения из 5 элементов по 2. Подсчитайте вручную значения A(n,m) для фиксированного значения т , например 4, и п из интервала от 8 до 13. Сочетанием из п элементов по m называется множество из m различных чисел, выбранных из диапазона от 1 до п. С понятием мчожества мы еще не знакомы. Пусть множества составляются из натуральных чисел от 1 до 5. Наборы (1, 2, 5) и (2, 5, 1) — это одно и то же множество. Множества (1, 1, 2, 5) не существует, элемент 1 повторяется два раза. Пусть п равно 5, а m — 3. Перечислим все сочетания: (1, 2, 3), (1, 2, 4), (1, 2, 5), (1, 3, 4), (1, 3, 5), (1, 4, 5), (2, 3, 4), (2, 3, 5), (2, 4, 5), (3, 4, 5). Каждому сочетанию соответствует т ! размещений. Обозначим число сочетаний как C(n,m). Таким образом, A(n,m)=C(n,m)*m! или C(n,m)=A(n,m)/m!= n!/(m!*(n-m)!). Перечислите все сочетания из 7 элементов по 5. Подсчитайте число сочетаний для различных значений п и т (не очень больших). При подсчёте как числа сочетаний, так и числа размещений, используется операция деления. Обоснуйте, почему в результате получаются только целые числа. Экспериментальный раздел работы 1. Составить программу подсчета числа сочетаний — C(n,m). Пусть у нас есть функция подсчета факториала числа — Fact(n). Написание основной программы сводится к программированию формулы C(n,m)= n!/(m!*(n-m)!). Обратите внимание на то, что вызывать функцию можно прямо при вычислении выражения, а не в отдельной строке программы, как мы делали при вызове процедур.
Процедуры и функции-—элементы структуризации программ Program Му12_1; Var п,т: Integer; с: Long mt; Function Fact (n: Integer) : Longint; Begin End; Begin WriteLn С Введите пит :'); ReadLn (n,m) ; c:= Fact (л) / (Fact (m) * Fact (n-m)) WriteLn(c); ReadLn; End.
135
;
В этот м о м е н т в р е м е н и у Вас только то, что Вы видите. Тела ф у н к ц и и нет, но п р о г р а м м а к о м п и л и р у е т с я , и Вы ее сохранили на в н е ш н е м носителе. А к о м п и л и р у е т с я ли? Да нет. Ошибка в строке в ы ч и с л е н и я с — Error 26: Type mismatch. — несоответствие типов. В с п о м н и м , т и п ы д а н н ы х определяют не только д и а п а з о н з н а ч е н и й п е р е м е н н ы х , но и допустимые над этим типом о п е р а ц и и . Обычное деление «/» недопустимо д л я переменных целого т и п а . Необходимо использовать операцию Div — с:= Fact(n) Div (Fact(m) * Fact(n-m));. Дополняем программу. Function Var i: rez: Begin rez:=1; For l:=1 Fact:=rez End;
Fact(n:Integer):Longint; Integer; Longint;
To n Do ;
rez:=rez*i;
З а п у с к а е м её д л я р а з л и ч н ы х значений. П р и ге=10, т=5, результат 252, п о к а не в ы з ы в а е т сомнений. П р и га=13, т = 5 , результат 399, что-то не т а к , д о л ж е н быть больше. П р и п=15, т=7, результат 9, программа не работает. Объясните причину. Подс к а з к а с о д е р ж и т с я в м а т е р и а л е этого з а н я т и я . А м о ж н о л и упростить программу? П е р е м е н н а я с л и ш н я я . Попробуйте убрать её. Сколько л и ш н и х в ы ч и с л е н и й (умножений) делает эта программа? Подсчитаем С(13,5) = (1* 2* 3* 4* 5* 6* 7* 8* 9* 10* 11* 12* 13) / (1* 2* 3* 4 * 5) * (1* 2* 3* 4* 5* 6* 7* 8). Вряд ли р а з у м н ы й человек будет в ы п о л н я т ь все у м н о ж е н и я . Он выпол-
Часть вторал
136
нит: 9*11*13 и получит 1287. Почему ж е м ы заставляем компьютер б ы т ь г л у п ы м ? Н а п и ш и т е более э ф ф е к т и в н у ю ф у н к ц и ю вычисления числа сочетаний. Например, даже нижеприведенн ы й в а р и а н т п р о г р а м м ы даст более о б н а д е ж и в а ю щ и е р е з у л ь т а т ы . П р а в и л ь н ы й р е з у л ь т а т п р и С(13,5), а и м е н н о , 1 2 8 7 , у ж е ш а г вперед, но это не п р е д е л у л у ч ш е н и й п р о г р а м м ы . Program Му12_1т; Var п,т:Integer; Function S (п,т:Integer) :LongInt; Var l:Integer; rez,cht:Longlnt; Begin rez:=1;cht:=1; For i:=l To m Do Begin rez:=rez*i;cht:~cht*(n-i+1); end; S:=cht Div rez; End; Begin WriteLn ('Введите два числа: '); ReadLn (n,m) ; WriteLn (S (n,m) ) ; ReadLn; End. П р о д о л ж и м тему, х о т я ф у н к ц и и п р и р е ш е н и и м о д и ф и ц и р о в а н н о й задачи и н е и с п о л ь з у ю т с я . Н а восьмом з а н я т и и м ы поз н а к о м и л и с ь с основной теоремой а р и ф м е т и к и . П у с т ь н а м не требуется в ы ч и с л я т ь С ( п , т ) , а необходимо п р е д с т а в и т ь это з н а ч е н и е в виде p1alp2a2...pqaq, где а >0, a pt — п р о с т ы е ч и с л а . О г р а н и ч е н и я 1<га<100, 1 < т < л . В ы ч и с л я т ь , если т а к м о ж н о выр а з и т ь с я , «в лоб» — б е з н а д е ж н о е з а н я т и е . Ч и с л а о г р о м н ы е , а пока м ы не у м е е м работать с н и м и . Р а с с м о т р и м и д е ю р е ш е н и я на п р и м е р е . В ы ч и с л я е м С(8,5)=8!/(5!*3!). П р е д с т а в и м 8! к а к е д и н и ц ы в соответствующем массиве, н а п р и м е р с и м е н е м Sn, S n [ i ] = l говорит о том, что число i есть в произведении. Просматриваем массив Sn, выбираем очередной элемент. Е с л и число i составное, то находим его разложение на простые сомножители. К элементам Sn, соответствующим сомножителям, прибавляем количество е д и н и ц , равное ч и с л у и х в х о ж д е н и й в i, a Sra[i] обнул я е м . В ы п о л н и м эти д е й с т в и я д л я п.', т! и (п-т)!. После этого останется т о л ь к о провести в ы ч и т а н и е степеней одного и того ж е простого ч и с л а .
Процедуры и функции-—элементы структуризации программ
137
И т а к , С(8,5)=2 Э *7'. Program Му12_2; Const Q=100 ; Type MyArray-Array[1..Q] Of Integer; Var n,m:Integer; P -.MyArray ; Procedure Soli e (sn, sm: Integer; Var Sp:MyArray) ; Begin EndsProcedure Print (pn : Integer ; Pp: MyArray) ; Var i: IntegersBegin For i:=2 To pn Do I f Pp[i]0 Then WriteLn (l: 5, Pp [i] : 5) ; End; Begin WriteLn('Ввод чисел n и m') ; ReadLn (n,m) ; Solve (n,m,P) ; Print (n,P) ; ReadLn; End. И т а к , есть только основная программа с вызовами процедур. Процедура Print приведена в силу ясности её написания. Программа компилируется. Программируем так, к а к говорим: ввели данные; в ы п о л н и л и обработку; вывели результат. Придерживайтесь этого п р и н ц и п а написания. Я з ы к программирования д л я Вас д о л ж е н стать естественным я з ы к о м для выражения мыслей, так ж е к а к , например, русский я з ы к в общении. Уточнение л о г и к и .
138
Часть вторал Procedure Solve(sn,sm:Integer;'Var Sp:MyArray); Var i:Integer; Sni,Smi,Snmi:MyArray; Begin Calc(sn,Sni);{*Сформировали массив, соответствующий n1 *} Calc(sm,Smi);(* Сформировали массив, соответствующий m!*) Calc (sn-sm,Snmi);(*Сформировали массив, соответствующий (n-m)!*) For i:=2 To sn Do Sp[1]:=Sni[1]-Smi[1]-Snmi[1]; End;
Сделав з а г о л о в о к п р о ц е д у р ы Calc с о п е р а т о р а м и Begin и End, В ы п о л у ч и т е о п я т ь к о м п и л и р у е м у ю п р о г р а м м у . В а р и а н т п р о ц е д у р ы Calc, п р и в е д е н н ы й н и ж е , х о т я и р а б о т а е т , но не т а к х о р о ш о ч и т а б е л е н , к а к п р е д ы д у щ а я л о г и к а . В ы п о л н и т е трассир о в к у этой процедуры в р е ж и м е отладки, определите назначение каждого оператора. В а ш а задача у л у ч ш и т ь процедуру. Вспомните о т о м , ч т о в м а т е р и а л е з а н я т и я № 8 п е р е ч и с л е н ы все п р о с т ы е ч и с л а до 100. Procedure Calc(п:Integer; Var X:MyArray); Var l,j,t:Integer;• Begin FillChar(X,SizeOf ( X ) , 0) ;{* SizeOf вычисляет размер области памяти, выделенной для массива X; FillChar - записывает 0 значения в зту область памяти, инициализация X. *) For i:=l То n Do X [i] :=1 ; { * признаки чисел, задействованных в вычислении факториала числа. *) For 1:=4 То n Do Begin t: = i / j:=2; While (3<=(t Div 2)) Do I f t Mod j =0 Then Begin {*j делит t без остатка. *} I n c ( X [ j ] ) ; t:=t Div j ; I f t=j Then I n c ( X [ j ] ) ; End Else I n c ( j ) ; I f t o i Then Begin (*Найдены делители числа i . *) X[iJ:=0;If ( t o j ) Then Inc(X[t]); End; End; End;
Процедуры и функции-—элементы структуризации программ
139
Вы решили исследовать задачу для большего диапазона значений п. Пусть п<10000. Запуск программы принесет только разочарование, даже если изменить тип Integer на Longlnt. Появится ошибка Error 202: Stack of erf low error. He занимаясь детальным выяснением ее сути, считаем, что просто не хватает оперативной памяти компьютера для хранения данных программы. Действительно, массивов введено много. Достаточно одного массива Р при условии, что он объявляется к а к глобальный и не используется при вызове процедур. Вариант программы. ($R+) Program Му12_2т; Const Q=10000; Type MyArray=Array[1..Q] Of Integer; Var n,m:Longlnt; P: MyArray; Procedure lnc_0 (n : Longlnt) ; Var i , j , t:Integer; Begin For l:=2 To n Do Begin t - i ; j:=2; While (j<=(t Div 2)) Do I f t Mod j =0 Then Begin Inc (P[j ]); t: =t Div j; I f t=j Then Inc (P[j]) ; End Else Inc(j) ; I f t o i Then Begin I f ( t o j ) Then Inc(P[ t]) ; End Else P[i) :=1; End; EndsProcedure Dec_0 (n: Longlnt) ; Var i ,j ,t: Integer ; Begin For l:=2 To n Do Begin t:=i; j:=2; While (j<= (t Div 2)) Do If t Mod j=0 Then Begin Dec (P[j ]) ; t : =t Div j; I f t=j Then Dec (P[j ] ) ;End Else Inc(j) ; I f t o i Then Begin I f ( t o j ) Then Dec(P[t]); End Else Dec (P[i]); End; End;
Часть вторал
140 Begin WriteLn ('Ввод 1пс_0(п) ; Dec_0 (т) ; Dec_0 (п-т) ; End.
чисел
п и т');
ReadLn
(п, т) ;
П р о г р а м м а работает, но она о п я т ь не л у ч ш а я (вывод результата не п р и в о д и т с я ) . Код п р о ц е д у р 1пс_0 и Dec O п о ч т и повтор я е т с я . Это п л о х о . И з б а в ь т е с ь от этого н е д о с т а т к а , а м о ж е т быть, В а м и у ж е с д е л а н более д о с т о й н ы й в а р и а н т обработки. В этой п р о г р а м м е з н а ч е н и е к о н с т а н т ы Q м о ж н о у в е л и ч и т ь , нап р и м е р до 3 0 0 0 0 . В о з н и к а е т д р у г а я п р о б л е м а — в р е м я р а б о т ы программы, приходится ждать результат. 2. З а д а н и е 11 з а н я т и я № 7 требовало о п р е д е л и т ь , я в л я е т с я ли введенное ч и с л о п а л и н д р о м о м . О ф о р м и м «перевертывание» ч и с л а в виде ф у н к ц и и . Function Pal (п Var x:Longlnt; Begin x:=0; While n<>0 Do x:=x*10+n Mod n : = n D i v 10; End; Pa 1: =x ; End;
-.Longlnt)
:Longlnt;
Begin 10;
Пусть исходное ч и с л о введено, н а п р и м е р 59. Оно не п а л и н д ром. «Перевернем» его, п о л у ч и м 95. Н а й д е м с у м м у ч и с е л 59 и 95 — 154. «Перевернем» это ч и с л о — 4 5 1 . Н а х о д и м с у м м у — 6 0 5 . Е щ е раз: 506 и 1111. П о л у ч и л и п а л и н д р о м . Н а й т и д л я всех н а т у р а л ь н ы х ч и с е л и з и н т е р в а л а от 50 до 80 количество шагов, н е о б х о д и м ы х д л я сведения и х к п а л и н д р о м а м , с помощью описанной схемы. Program Му12_3; Const а=50;Ъ=80; Var п, t .-Longlnt; cnt,i:Integer; Function Pal (n.-Longln Begin End;
t) :
Longlnt;
Процедуры и функции-—элементы структуризации программ
141
Begin For i:=a То Ь Do Begin cnt:=0;п:=1; While Pal (n) on Do Begin t:=Pal m); n:=n+t; Write f t , ' ' ,n,' ') ; Inc (cnt); End; WriteLn (i,' ',cnt) ; End; End. И з м е н и т е г р а н и ц ы и н т е р в а л а a, b на 89 и 89. Ч и с л о 89, т а к ж е к а к и 98, очень н е п р и я т н о . Н а 16-м ш а г е р е з у л ь т а т поп р е ж н е м у не я в л я е т с я п а л и н д р о м о м , а с у м м а в ы х о д и т за пред е л ы т и п а Longlnt. Изменим программу, используя четвертый п р и м е р и з р а з д е л а э к с п е р и м е н т а л ь н о й р а б о т ы десятого занят и я . П о в т о р и т е м а т е р и а л о работе с б о л ь ш и м и ч и с л а м и . В этой задаче н а м п о т р е б у ю т с я у м е н и я и х с к л а д ы в а т ь и «перевертывать». ($R+) Program Му12_3т; Uses Crt; Const MaxN=lООО; Type MyArray=Array[0..MaxN+1] Of Integer;{*Числа храним в массиве, они большие и т и п а Longlnt не хватает. *} Var A,Z:MyArray; Var cnt,n:Integer; Function Eq:Boolean;(*Является ли число палиндромом ?*} Var i:Integer; Begin i : =1 ; While (l <=A [0] Div 2) And (A [l} =A [A [ 0]-l+l ]) Do Inc (i) ; Eq:=(i>A[0] Div 2) ; End; Procedure Swap(Var a,b:Integer); Var с-.Integer; Begin c:=a;a:=b;b:=c; End; Procedure Rev;{* «Перевертываем» число.*} Var i:Integer; Begin
Часть вторал
142
F o r i:=l То А[0] Div 2 Do Swap (A[i] ,A[A[0]-i+l]) ; End; Procedure Change(n:Integer);{*Переводим его представление в виде массива. Var 1:Integer; Begin i : =1 /
число
в
*}
While n<>0 Do Begin A [ l ] :=n Mod 10; n:=n Div 10; Inc (A[0J ) ;Inc ( l ) ; End; End; Procedure Add;(*Складываем два больших числа. *} Var i,г,w:Integer; Begin l:=1;r:=0; While (l<-A[0]) Or (r<>0) Do Begin w:=A[i]+Z[i]+r; A[i] :=w Mod 10; r : = w Div 10; I f A[A[0] +1] <>0 Then Inc(A[0]); Inc ( l ) ; End; End; Begin ClrScr; n:=100; While (n<1000) Do Begin ( *Исследуем диапазон от 100 до 999. *} FillChar (A, SizeOf (А) , 0) ; Change (n) ; {*Преобразуем число. *) cnt:=0;(*Счетчик количества шагов преобразований числа. *} While (Not Eg) And (A[0] <=MaxN) Do Begin f *Пока не палиндром и нет выхода за пределы массива А считаем. *} Z: =А;Inc (cnt);Rev;Add;(* Вызов процедур, они описаны и очевидны.*) End; WriteLn (n, ' ' , cnt) ; ( *Вывод числа и количества шагов. *) I f cnt>=MaxN Then ReadLn;(*Фиксируем факт выхода за пределы массива А. *) Inc(n) ; End; End.
Процедуры и функции-—элементы
структуризации программ 142
Д л я ч и с е л 196, 295, 3 9 4 , 4 9 3 , 592, 689, 6 9 1 , 788, 790, 879, 8 8 7 , 9 7 8 , 9 8 6 м а с с и в а А из 1000 элементов недостаточно д л я х р а н е н и я р е з у л ь т а т а . А к о н е ч е н л и процесс, всегда л и м о ж н о получить палиндром? Д л я доказательства или опровержения этого ф а к т а т р е б у ю т с я м а т е м а т и ч е с к и е у с и л и я . Н а ш и м и средс т в а м и у с т а н о в и т ь его в р я д л и в о з м о ж н о , они могут т о л ь к о « н а т о л к н у т ь » н а с х е м у д о к а з а т е л ь с т в а . Однако рекомендуем Вам п р о д о л ж и т ь э к с п е р и м е н т ы с п р о г р а м м о й , с этой ц е л ь ю м а с с и в ы в ней о п р е д е л е н ы к а к г л о б а л ь н ы е п е р е м е н н ы е и не пер е д а ю т с я в п р о ц е д у р ы и ф у н к ц и и . М о ж е т о к а з а т ь с я , что операт и в н о й п а м я т и , в ы д е л я е м о й д л я з а д а ч и н а Турбо П а с к а л е , не будет х в а т а т ь п р и этом исследовании. 3. К а к н а х о д и т ь н а и б о л ь ш и й о б щ и й делитель двух чисел, м ы знаем из восьмого з а н я т и я . Оформим эти действия в виде функции. Function Nod(a,b:LongInt):Longlnt; Begin Repeat I f a>b Then a:=a Mod b Else b:=b Mod a; Until (a=0) Or (b=0) ; Nod:=a+b; End; Р а с с м о т р и м с л е д у ю щ у ю задачу. Д а н о п отрезков, д л и н ы кот о р ы х ц е л ы е ч и с л а . Б е р е м два отрезка и из большего вычитаем м е н ь ш и й . Р а з н о с т ь — н о в ы й отрезок. Если ж е д л и н ы отрезков совпадают, то один из н и х и с к л ю ч а е м из дальнейшего рассмотр е н и я . П р о д о л ж а е м процесс. Ответом задачи я в л я е т с я одно число, равное д л и н е оставшегося в результате этих действий о т р е з к а . П р и м е р : п = 4 и д л и н ы 6, 15, 3, 9.
Ответ задачи 3. Р е ш е н и е задачи «в лоб» по приведенной схеме возможно, хотя оно достаточно трудоемко и д л я больших
144
Часть вторал
з н а ч е н и й п требует з н а ч и т е л ь н ы х в р е м е н н ы х затрат. Н а самом деле все сводится к н а х о ж д е н и ю н а и б о л ь ш е г о общего д е л и т е л я п чисел, а в ы ч и т а н и я в ы п о л н я т ь не требуется. Н а п и ш и т е прог р а м м у р е ш е н и я задачи. Исследуйте ее работоспособность д л я б о л ь ш и х з н а ч е н и й п. П р о б л е м ы с вводом и с х о д н ы х д а н н ы х реш а ю т с я с п о м о щ ь ю генератора с л у ч а й н ы х чисел, естественно, что д л и н ы отрезков х р а н я т с я в одномерном массиве. Задания для самостоятельной работы 1. Д а н о п ц е л ы х чисел. Н а й т и среди н и х число, у которого приведенная н и ж е х а р а к т е р и с т и к а имеет м а к с и м а л ь н о е значение: • сумма цифр; • первая ц и ф р а ; • количество делителей; • сумма всех делителей. 2. Дано п ц е л ы х чисел. Н а й т и среди них пару чисел, д л я котор ы х в ы п о л н я е т с я приведенное н и ж е условие: • н а и б о л ь ш и й общий делитель имеет м а к с и м а л ь н о е значение; • н а и м е н ь ш е е общее кратное имеет н а и м е н ь ш е е значение; • есть ли среди з а д а н н ы х чисел «близнецы», т. е. простые числа, разность м е ж д у к о т о р ы м и равна двум. 3. Дано п ц е л ы х чисел. Написать программу подсчета количества чисел, в записи к о т о р ы х нет ц и ф р ы 8. П р и этом требуется использовать ф у н к ц и ю вида: Function Yes8(x:LongInt): Boolean. Без использования компьютера р е ш и т е эту задачу д л я всех т р е х з н а ч н ы х чисел. Ответ — 729. 4. Д а н ы два числа пи k (k< п). П р и этом п означает основание системы счисления, a k — количество знаков в записи числ а . Н а п и с а т ь программу подсчета количества н а т у р а л ь н ы х чисел в л-ичной системе счисления, з а п и с ы в а е м ы х ровно k з н а к а м и . Б е з использования компьютера решите эту задачу д л я всех т р е х з н а ч н ы х чисел. Ответ — 900. 5. Объявлен к о н к у р с на получение грантов. В конкурсе принимают участие п человек. Гранты выдаются первым п я т и учас т н и к а м , н а б р а в ш и м наибольшее количество баллов. Один у ч а с т н и к не может одновременно получить два и более грантов. Написать программу определения количества способов, к о т о р ы м и могут быть распределены гранты. Исследовать её область применимости.
Процедуры и функции-—элементы структуризации программ
145
6. Д а н а ш а х м а т н а я доска размером п*п и п ладей. Определить количество способов расположения ладей на ш а х м а т н о й доске так, чтобы они не могли бить друг друга. Исследовать, для к а к и х значений п работоспособны типы данных Integer, Word, Longlnt. 7. Дано п различных бусин. Сколько различных ожерелий можно составить из них? Исследовать область применимости Вашей программы. При п=7 число ожерелий равно (5040'7):2=360. Делением на двойку учитывается не только поворот (деление на 7), но и переворот ожерелий. 8. Имеются предметы k р а з л и ч н ы х типов. Количество предметов первого типа л,, второго — п2, ..., k-ro — пк. Написать программу подсчета числа перестановок, которые можно сделать из этих предметов, и исследовать ее область применимости. Предварительно попытайтесь обосновать формулу: Р,=л ' / (п,1 *п„ ' * *Пу 1 ) . Есть текст. Его шифруют (кодируют) с помощью перестановки букв. В результате получается новый текст, называемый анаграммой (слова лунка и кулан — анаграммы). Д л я механической р а с ш и ф р о в к и анаграммы требуется выполнить количество перестановок, в ы ч и с л я е м ы х по выше — приведенной формуле, где n t — количество конкретных букв в анаграмме. Предположим, что у Вас есть компьютер с миллиардным быстродействием (1 миллиард операций в секунду). Он выполняет миллиард умножений в секунду. Задайте значения величинам п, п}, п2,.... пк и подсчитайте, какое время Ваш компьютер будет решать задачу. 9. Ч е р е п а ш к а перемещается по кле|Г" точному полю размером n*m. Ей р а з р е ш е н о д в и г а т ь с я вправо и вверх. В н а ч а л ь н ы й момент времени она находится в нижней, левой к л е т к е (А), и ей необходимо попасть в верхнюю, правую клет- 1— к у (В). Сколько различных путей L есть у Черепашки. Подсчитать количество путей для различных значений п и т . Выяснить, для к а к и х значений п и т работоспособны типы данных Integer, Word, Longlnt, т. е. для хранения результата можно исполь-
Часть вторал
146
зовать переменные этих типов. Предположим, что д л я анализа одного пути Черепашки компьютеру с миллиардным быстродействием требуется 100 операций. З а к а к о е в р е м я он проанализирует все пути для к о н к р е т н ы х значений п и т ? 10. Последовательности длины п составляются из t нулей и k единиц, причем т а к и м образом, что н и к а к и е две единицы не находятся рядом. Подсчитать количество таких последовательностей (ответ — С(t+l,k)). Найти способ перечисления всех т а к и х последовательностей для небольших значений t и k. Пример: t=3, k=2. Последовательности: 00101, 01001, 01010, 10001,10010,10100. Материал для чтения 1. Историческая справка о термине «алгоритм». Термин «алгоритм» обязан своим происхождением выдающемуся ученому средневековья Мухамеду ибн Муса ал Хорезми ( в переводе с арабского означает «Мухамед сын Мусы из Хорезма»), сокращенно Ал-Хорезми. Он ж и л приблизительно с 783 по 850 гг. В одном из своих трудов Ал-Хорезми описал десятичную систему счисления и впервые сформулировал правила выполнения арифметических действий над целыми числами и простыми дробями. В латинском переводе труда Ал-Хорезми правила начинались словами Dixit Algorizmi (Алгоризми сказал). В других переводах автор именовался Algorithmus (Алгоритмус). Постепенно люди забыли, что Алгоризми — это автор правил, и стали сами эти правила называть алгоритмами. 2. В настоящее время слово «алгоритм» стало научным термином, обозначающим одно из фундаментальных понятий современной математики и информатики. Мы уже использовали этот термин, не обсуждая его, но не судите за это строго. Термин веками использовался в математике без точного определения, и для этого есть причины. Вспомним алгоритм Евклида нахождения наибольшего общего делителя двух натуральных чисел. Независимо от того, кем (чем) он выполняется, наибольший общий делитель будет найден, ибо все действия однозначно определены и требуется только записать их на понятном для исполнителя языке. Мы записываем их на я з ы к е Турбо Паскаль. По этому алгоритму мы решаем задачу для всех целых чисел, подчеркнем еще раз — для всех, т. е. у этого алгоритма то, что называют массовостью алгоритма и его конечностью, ибо результат будет найден за конечное число действий. Однако
Процедуры и функции-—элементы структуризации программ
147
в с п о м н и м з а д а ч у о преобразовании ч и с л а в палиндром. Алгоритм есть, но д л я н е к о т о р ы х ц е л ы х чисел результат мы не пол у ч и л и . П у с т ь д а ж е м ы д о к а з а л и средствами м а т е м а т и к и , что д л я этих ч и с е л процесс бесконечный. Н о то, что м ы н а п и с а л и , я в л я е т с я алгоритмом или нет? И л и «гипотеза Сиракуз», приведенная в седьмом з а н я т и и — я в л я е т с я она алгоритмом или нет? В ш к о л ь н о й учебной литературе приводятся р а з л и ч н ы е неф о р м а л ь н ы е о п р е д е л е н и я п о н я т и я алгоритм и обсуждаются его свойства: определенности, понятности, массовости, дискретности, конечности. Н а п р и м е р , «под алгоритмом понимается предписание, точное и понятное, определяющее, к а к и е действ и я и в к а к о м п о р я д к е необходимо выполнить д л я р е ш е н и я любой задачи и з данного класса однотипных задач». Позволим себе не в д а в а т ь с я в обсуждение определений и свойств, ибо результат в р я д л и окупит затраты. Отметим только, что определение А. Г. К у ш н и р е н к о и Г. В. Лебедева «Алгоритм — план будущей деятельности, з а п и с а н н ы й в заранее выбранной форм а л ь н о й системе. Составляет алгоритм человек, а выполняет ЭВМ» н а м к а ж е т с я наиболее у д а ч н ы м и больше других соответствует н а ш е м у п о н и м а н и ю этого термина. Формальной системой в н а ш е м случае я в л я е т с я система программирования Турбо П а с к а л ь . Почему формальная? Вспомните, все ошибки устраненные в рассмотренных программах. О с т а н о в и м с я к о р о т к о н а способах з а п и с и а л г о р и т м о в . Их много, м ы будем и с п о л ь з о в а т ь т о л ь к о одну формальную систему — я з ы к п р о г р а м м и р о в а н и я Турбо П а с к а л ь . Р а з л и ч н ы е приемы словесного описания, всевозможные диаграммы, блокс х е м ы не и с п о л ь з у ю т с я в данном учебнике. П р и ч и н а проста. П о я с н и м ее п р и м е р о м . Стандарты на д о к у м е н т а ц и ю по прог р а м м н ы м п р о е к т а м 70-х годов двадцатого столетия требовал и к р о м е т е к с т а самой п р о г р а м м ы о ф о р м л е н и я б л о к - с х е м . Автор п о м н и т огромные тома этих блок-схем и ту титаничес к у ю работу, к о т о р а я с т о я л а за этими томами. А затем прог р а м м н ы е п р о е к т ы ж и л и «своей ж и з н ь ю » , и эти тома ж и л и т о ж е своей ж и з н ь ю — они п о к р ы в а л и с ь п ы л ь ю в х р а н и л и щ а х . Отследить все и з м е н е н и я программ в этих блок-схемах было не под с и л у любому к о л л е к т и в у , и через н е с к о л ь к о лет работы с п р о г р а м м н ы м проектом эти тома у ж е н и ч е м у не соответствовали. Приходилось в ы п у с к а т ь новый комплект блоксхем (они н а з ы в а л и с ь д о к у м е н т а ц и е й , б л о к - с х е м ы требовалось еще описать) д л я х р а н и л и щ а . П р о г р а м м и р о в а н и е благополучно п р о ш л о эту стадию р а з в и т и я . Б л о к - с х е м ы бывают
148
Часть вторая
иногда п о л е з н ы при работе с м и н и а т ю р н ы м и п р о г р а м м а м и и т о л ь к о . О д н а к о «ничто не у х о д и т бесследно», б л о к - с х е м ы , д и а г р а м м ы п о я в и л и с ь вновь в CASE т е х н о л о г и я х , но у ж е д л я о п и с а н и я д р у г и х в е щ е й . То, что п о л у ч и т с я в р е з у л ь т а т е , пок а ж е т этот в и т о к р а з в и т и я т е х н о л о г и й п р о г р а м м и р о в а н и я .
Процедуры и функции-—элементы структуризации программ
149
З а н я т и е № 13. Р е к у р с и я План занятия • понятие рекурсии; О короткие п р и м е р ы д л я иллюстрации рекурсии; • экспериментальная работа с программами перечисления всех последовательностей длины л из чисел от 1 до k\ генерации перестановок чисел от 1 до л; перечисления всех разбиений числа п на сумму слагаемых п=а1+а2+ ... +ак, где k, а3, а2 ак>0; • выполнение самостоятельной работы. Понятие рекурсии. Рекурсией называется ситуация, когда процедура или ф у н к ц и я вызывает сама себя (образно выражаясь, «этот глист страдал глистами, что мучились глистами сами»). Т и п и ч н а я к о н с т р у к ц и я рекурсивной процедуры имеет вид: Procedure Rec (t: Integer) ; Begin <действия на входе в рекурсию>; I f <проверка услович> Then Rec (t+1) ; <действия на выходе из рекурсии>; End; Необходимо осознать два ключевых момента при написании рекурсивной логики: ход вычислительного процесса по управлению и связи в вычислительном процессе по данным. Проиллюстрируем рисунком для конкретной задачи вычисления факториала числа. Function Factorial ( n: Integer): Begin I f n=l Then Factorial :=1 Else Factorial: =n * Factorlal (n-1) End;
Longint;
;
Найдем 51. К а к ж е вычисляется факториал числа? Первый вызов ф у н к ц и и осуществляется из основной программы, например, a:=Factonal(5), переменной а присваиваем значение 51. Действий на входе в рекурсию нет, этап вхождения выделен на рисунке ж и р н ы м и л и н и я м и . Он продолжается до тех пор, пока значение локальной переменной не становится равным 1. После этого начинается выход из рекурсии (тонкие линии на рисунке). Итак, ход вычислительного процесса по управлению является нелинейным! В рекурсивной логике обязательно должно быть условие завершения процесса вхождения в рекурсию (на
Часть вторал
150
жаргоне — «заглушка»). Естественный вопрос — что необходимо знать д л я реализации этого процесса. С входом в рекурсию более понятно осуществляется вызов процедуры или ф у н к ц и и , а д л я реализации выхода из рекурсии требуется помнить, откуда м ы п р и ш л и , т. е. помнить точки возврата в в ы з ы в а ю щ у ю логику. Чтобы помнить, необходимо хранить. Это место, место хранения точек возврата, называется «стеком вызовов», и для него нашей системой программирования выделяется определенная область оперативной памяти. В этом стеке запоминаются не только адреса точек возврата, но и значения всех локальных переменных, образно выражаясь, запоминается «слепок» процедуры или функции. По «слепку» восстанавливается вызывающая логика (процедура или функция), к а к только мы осознаем это утверждение, становятся п о н я т н ы м и связи по данным в рекурсивном вычислительном процессе. 1 вызов (п=5)
1. Вы знакомы с развитием технологий программирования по материалам для чтения из предыдущих занятий. Найдите для себя ответ на вопрос «С какого момента времени развития технологий в системах программирования появилась возможность для реализации рекурсивной логики»? 2. Из вышеприведенного примера. Создаются ли копии программного кода функции Factorial в процессе её работы? 3. Написание рекурсивных процедур по образцу, приведенному ниже, вряд ли можно считать приемлемым.
Процедуры и функции-—элементы структуризации программ Procedure Generate; Begin(*t - глобальная I f t=n Then Exit Else Begin <действия 1>; t:=t+l ; Generate; t:=t-1; <действия 2>; End; End;
переменная.
151
*}
Безусловно, ч т о э к о н о м и т с я место в стеке вызовов (стеке адресов возврата), но сие — простая замена ц и к л а на рекурсию, и последнюю м о ж н о р а с с м а т р и в а т ь к а к трюк и не более, которым она н и к а к не я в л я е т с я . П р и в е д е м н е с к о л ь к о коротких примеров, иллюстрирующих технику написания рекурсивной логики. 1. С л о ж е н и е двух чисел (а+b). Рекурсивное определение операц и и с л о ж е н и я двух чисел:
Function Sum С а, Ь: Integer): Integer; Begin I f b=0 Then Sum:=a Else I f b>0 Then Sum:=Sum( a+1, b-1) Else Sum:=Sum ( a-1, b+1 ) ; End; 2. Перевод натурального ч и с л а из десятичной системы счислен и я в двоичную. Procedure Rec ( п: Integer ) ; Begin I f п>1 Then Rec (n Div 2) ; Wri te (n Mod 2) ; End; К а к и з м е н и т с я процедура при переводе чисел в восьмеричную систему счисления? 3. Определить, я в л я е т с я ли заданное натуральное число простым. Сформулируем по-другому. Верно ли, что заданное натуральное число п не делится ни на одно число, большее или
Часть вторал
152
р а в н о е т , но м е н ь ш е е п. П р и этом з н а ч е н и я ч и с л а m наход я т с я в п р о м е ж у т к е от 2 до п . У т в е р ж д е н и е и с т и н н о в д в у х случаях: • если т = п ; • п не д е л и т с я на т , и и с т и н н о у т в е р ж д е н и е д л я ч и с е л от т + 1 до п - 1 . Function Simple (т,п: Integer): Boolean; Begin I f m=n Then Simple :=True Else Simple: = (n Mod m <> 0) And Simple (m+1, n) ; End; П е р в ы й вызов ф у н к ц и и — S i m p l e ( 2 , n ) , где n — п р о в е р я е м о е число. 4. Д а н о н а т у р а л ь н о е ч и с л о п>1. О п р е д е л и т ь ч и с л о а, д л я которого в ы п о л н я е т с я неравенство: 2 а - 1 < п < 2 а . З а в и с и м о с т ь : [а(п div Function а ( п: Begin
2) + 1, Integer):
п>1 Integer;
I f n=l Then a: =1 Else a:=a(n Div 2) +1; End; 5. Д л я в ы ч и с л е н и я наибольшего общего д е л и т е л я двух ч и с е л м о ж н о использовать р е к у р с и в н у ю ф у н к ц и ю в и д а : Function Begin I f (a=0) Or (b=0) Then Nod:=a+b Else I f a>b Then Nod:=Nod (a-b,b) Else Nod:=Nod(a,b-a); End; 6. Н а х о ж д е н и е м а к с и м а л ь н о г о элемента в г л о б а л ь н о м массиве А р е а л и з у е т с я с помощью с л е д у ю щ е й р е к у р с и в н о й л о г и к и . Procedure Search__Max (п : Integer; Begin I f n=l Then x:=A[1] Else Begin Search_Max (n-1, x) ; I f A[n]>x Then x:=A[n]; End; End;
Var х: Integer)
;
Процедуры и функции-—элементы структуризации программ
153
7. Возведение целого ч и с л а а в целую неотрицательную степень п рекурсивно реализуется так. Function Pow (a ,п : Integer) .-Integer; Begin I f n=0 Then Pow:=1 Else I f n Mod 2=0 Then Pow: =Pow (a*a, n Div Else Pow:=Pow(a,n-l) *a; End;
2)
8. Процедура ввода с к л а в и а т у р ы последовательности чисел (окончание ввода — 0) и вывода ее на э к р а н в обратном пор я д к е имеет вид: Procedure Solve; Var п:Integer; Begin ReadLn (n) ; I f n<>0 Then Solve; Write (n: 5) ; End; 9. К а ж д о е число Фибоначчи равно сумме двух предыдущих чисел при условии, что первые два р а в н ы 1 (1, 1, 2, 3, 5, 8, 13, 21,...). В общем виде п-е число Фибоначчи м о ж н о определить т а к :
{
1, если п = 1 или п = 2 Ф(п - 1) + Ф(п - 2) , еслил > 2 Function Fib (п : Integer) :Integer; Begin I f n<=2 Then Fib: =1 Else Fib: =Fib (n-1) +Fib (n-2) ; End; 10. Н а й т и сумму первых n членов арифметической (геометрической) прогрессии. А р и ф м е т и ч е с к а я прогрессия определяется т р е м я параметрами: первым элементом (а), разностью между соседними элементами (d) и количеством членов прогрессии (п). Очередной элемент прогрессии в ы ч и с л я е т с я из предыдущего прибавлением разности — a B0Boe :=a CTapoe +d. Function Sa (n,a -.Integer) IntegersBegin I f n>0 Then Sa :=a+Sa (n-1, a+d) Else Sa:=0; End;
Часть вторал
154
П р и м е ч а н и е Попробуйте убрать ветвь Else и объяснить полученный результат. Д л я п р и в е д е н н ы х п р и м е р о в п р о р и с у й т е с х е м ы р а б о т ы проц е д у р и ф у н к ц и й д л я к о н к р е т н ы х в х о д н ы х д а н н ы х (не о ч е н ь б о л ь ш и х , естественно). Э к с п е р и м е н т а л ь н ы й раздел р а б о т ы 1. Д а н ы н а т у р а л ь н ы е ч и с л а п и ft. П е р е ч и с л и т ь все п о с л е д о в а т е л ь н о с т и д л и н ы п и з ч и с е л от 1 до k. Н а п р и м е р , п=3, k=2. К о л и ч е с т в о п о с л е д о в а т е л ь н о с т е й — k". Д е й с т в и т е л ь н о , н а к а ж д о е и з п мест р а з р е ш а е т с я з а п и с ы в а т ь ч и с л а от 1 до k, перемножаем и получаем результат. Строгое доказательство осуществляется традиционно, с помощью простой индуктивн о й с х е м ы . П у с т ь п о с л е д о в а т е л ь н о с т и в ы в о д я т с я в следующ е м п о р я д к е : 1 1 1 , 1 1 2 , 1 2 1 , 1 2 2 , 2 1 1 , 2 1 2 , 2 2 1 , 2 2 2. Ч е м он ( п о р я д о к ) х а р а к т е р и з у е т с я ? В о з ь м е м две соседние пос л е д о в а т е л ь н о с т и , н а п р и м е р 1 1 2 и 1 2 1 . Д о к а к о й - т о позиц и и о н и с о в п а д а ю т , в д а н н о м с л у ч а е до второй ( н е с о в п а д е н и е в о з м о ж н о и в первой п о з и ц и и ) , а в этой п о з и ц и и число во второй последовательности больше, чем ч и с л о в первой последовательности. Т а к о й п о р я д о к н а з ы в а е т с я л е к с и к о г р а ф и ч е с к и м . Как из очередной последовательности получать следующую в л е к с и к о г р а ф и ч е с к о м п о р я д к е ? И д е м с п р а в а . Н а х о д и м перв ы й э л е м е н т , не р а в н ы й k. У в е л и ч и в а е м его н а е д и н и ц у , а «хвост» п о с л е д о в а т е л ь н о с т и м а к с и м а л ь н о у х у д ш а е м , т. е. зап и с ы в а е м 1. В п о с л е д н е й п о с л е д о в а т е л ь н о с т и с д е л а т ь т а к о е преобразование, естественно, не п р е д с т а в л я е т с я в о з м о ж н ы м . П р и м е ч а н и е Разумеется, сейчас самое время потренироваться на примерах. Как Вы поняли постановку задачи? Одномерный массив из п элементов — очевидная структура данных д л я х р а н е н и я последовательности. Тип элементов в массиве м о ж н о определить к а к о б ы ч н о Integer, но давайте ч у т ь - ч у т ь и з м е н и м : Туре МуАггау = Array[l..n] Of 0..k; М ы в в е л и о г р а н и ч е н и е на т и п Integer. Д о п у с т и м ы м и з н а ч е н и я м и э л е м е н т о в м а с с и в а А я в л я ю т с я ц е л ы е ч и с л а в и н т е р в а л е от 1 до k, и к о м п ь ю т е р к о н т р о л и р у е т в ы х о д и х з а д о п у с т и м ы й д и а п а зон ( и с п о л ь з о в а л с я о г р а н и ч е н н ы й т и п д а н н ы х л и на п р е д ы д у щ и х занятиях?). Определим «костяк» программы и очевидную процедуру вывода элементов массива.
Процедуры и функции-—элементы структуризации программ ($R+) Program Му13_1; Const п=3; к=2 ; Type MyArray=Array[1. .п] Of 0 . .к; Var A Procedure Print; Var l: Integer; Begin For l: =1 To n Do Write (A [i ] : 3) ; WriteLn ; End; Procedure Solve (t: Integer) ; Begin End; Begin FillCnar (A, SizeOf (A) ,0) ( *Очистка . *) ; Solve (1) ; End.
155
-.MyArray;
Из первого вызова рекурсивной процедуры Solve возникает вопрос — а что определяет параметр рекурсии Позицию в последовательности (номер элемента в массиве А). Следующий вызов — Solve(t+l), при значении t, большем п, необходимо выводить очередную последовательность, а иначе — записать очередной элемент, он определяется значением управляющей переменной г, в позицию t. Procedure Solve (t:Integer); Var l: Integer; Begin I f t>n Then Print Else For i:=l To k Do Begin Aft] :=i; Solve (t+1) ; End; End; Измените программу так, чтобы последовательности выводились в следующей очередности: 2 2 2, 2 2 1, 2 1 2, 2 1 1, 1 2 2, 1 2 1, 1 1 2, 1 1 1. Предположим, что значение k больше значения п. Изменить программу. В последовательности чисел i-й элемент не должен превосходить г. Это простое изменение программы. А сейчас, при этом же предположении (k>n), требуется сгенерировать
Часть вторал
156
в о з р а с т а ю щ и е последовательности ч и с е л . Н а месте t в последов а т е л ь н о с т и д о п у с к а е т с я з а п и с ь ч и с е л и з и н т е р в а л а от A[t-1 ] + 1 до k-(n-t). Д е й с т в и т е л ь н о , t=n, м а к с и м а л ь н о е ч и с л о в позиц и и п р а в н о , е с т е с т в е н н о , k; t=n-l, м а к с и м а л ь н о е ч и с л о в позиц и и п-1 есть k-1 и т. д. Это м ы с л ь п р и в о д и т к с л е д у ю щ е м у изм е н е н и ю п р о ц е д у р ы Solve. Procedure Solve(t:Integer) Var l:Integer; Begin I f t>n Then Print Else For i:=A[t-l]+l A[t]:=i; Solve(t + 1) ; End; End;
;
To к-n + t Do
Begin
Однако только этим изменением в программе ограничитьс я н е л ь з я . П е р в ы й в ы з о в п р о ц е д у р ы Solve( 1) и о п и с а н и е т и п а Type MyАггау= Array [ 1 ..п] Of l..k; п р и в о д и т к з н а к о м о й о ш и б к е Error 201: Range check error, в с т р о к е For i:=A[t-l]+1 To k-n+t Do. П р и ч и н а о ч е в и д н а — в ы х о д и н д е к с а з а п р е д е л ы м а с с и в а А. И з м е н и м о п и с а н и е т и п а н а Type MyArray=Array[0..n] Of l..k;. П р о г р а м м а в ы д а е т р е з у л ь т а т , но с о д е р ж и т л о г и ч е с к у ю н е т о ч н о с т ь . П р и п е р в о м в ы з о в е Solve и с п о л ь з у е т с я н е о п р е д е л е н н о е з н а ч е н и е А [ t - 1 ] , т. е. 1 п р и б а в л я е м н е и з вестно к чему. П р и и з м е н е н и и первого вызова на А[0]:=0; Solvef 1); п р и х о д и м к н о в о й о ш и б к е — Error 76: Constant out of range ( з н а ч е н и е к о н с т а н т ы в ы х о д и т з а д о п у с т и м ы й д и а п а зон). Е щ е одно и з м е н е н и е — Type MyArray=Array[0..п] Of 0..k; и м о ж н о , в п е р в о м п р и б л и ж е н и и , з а к о н ч и т ь э к с п е р и менты с программой. 2. Ч т о т а к о е п е р е с т а н о в к и ч и с е л от 1 до л, о п р е д е л е н о н а пред ы д у щ е м з а н я т и и . Н е о б х о д и м о н а п и с а т ь р е к у р с и в н у ю процедуру г е н е р а ц и и перестановок ч и с е л от 1 до л . В ч е м здесь суть р е к у р с и и ? Ф и к с и р у е м на первом месте очередное ч и с л о и г е н е р и р у е м все п е р е с т а н о в к и этого в и д а . П р и этом поступ а е м точно по т а к о й ж е схеме. Ф и к с и р у е м ч и с л о на втором месте и г е н е р и р у е м все п е р е с т а н о в к и у ж е с ф и к с и р о в а н н ы м и э л е м е н т а м и н а п е р в о м и втором м е с т а х . П у с т ь п р а в н о 3. П е р е с т а н о в к и д о л ж н ы г е н е р и р о в а т ь с я в с л е д у ю щ е й послед о в а т е л ь н о с т и : 1 2 3, 1 3 2, 2 1 3, 2 3 1, 3 2 1, 3 1 2. Е с л и не очень п о н я т н о , то р а с с м о т р и м п р и п = 4 . Вместо м н о г о т о ч и я з а п и ш и т е г е н е р и р у е м ы е п е р е с т а н о в к и 1 2 3 4 , 1 2 4 3 , 1 3 2 4, 1 3 4 2 , 1 4 3 2 , 1 4 2 3, 2 1 3 4, 2 1 4 3, 2 3 1 4 , 2 3 4 1 4 1 2 3.
Процедуры и функции-—элементы структуризации программ
157
Н е о б х о д и м о о с о з н а т ь , что после г е н е р а ц и и всех перестановок с ф и к с и р о в а н н ы м э л е м е н т о м в п о з и ц и и t п е р е с т а н о в к а возвращается в исходное состояние и осуществляется новый обмен з н а ч е н и я м и э л е м е н т а и з п о з и ц и и с н о м е р о м t+1 и i + l . Program Му13_2;(*Проиедура Print совпадает соответствующей процедурой из предыдущей программы. *} Const п=4; Type MyArray=Array [1. .n] Of Integer; Var А:MyArray; i:Integer; Procedure Swap(Var a,b:Integer); Var с:Integer; Begin с: =a ; a : =b;b: =c; EndsProcedure Solve(t:Integer); Var l: IntegersBegin I f t>=n Then Print Else For l:=t+l To n Do Begin Swap (A [ t+1],A [ l ] ) ; Solve (t + 1); Swap (A [ t+1 ] ,A[i]) ; EndsEnd; Begin For i:=l To n Do A[i]:=i;(*Первая 12 3 п.*} Solve(0);{*Первый вызов. *) End.
с
перестановка
-
Д л я п р о с т о т ы будем с ч и т а т ь , ч т о к а ж д ы й р е к у р с и в н ы й в ы з о в п р и в о д и т к с о з д а н и ю « к о п и и » п р о ц е д у р ы . Это не т а к , но н а м л е г ч е п р и э т о м п р е д п о л о ж е н и и ф о р м у л и р о в а т ь вопрос ы . С к о л ь к о « к о п и й » п р о ц е д у р ы Solve с о з д а е т с я п р и п=3? С к о л ь к о р а з с о з д а ю т с я к о п и и с t=2? С к о л ь к о в ы п о л н я е т с я л и ш н и х обменов т и п а A[j]<r>A[j]? На рисунке приводится ч а с т ь с х е м ы в ы з о в о в ( о б о з н а ч е н о с т р е л к а м и ) п р о ц е д у р ы Solие(при п = 3 ) . Д л я о т в е т а н а с ф о р м у л и р о в а н н ы е в о п р о с ы закончите рисунок.
157 Часть вторал
<123> <132>
-*• <213> <231 >
<321 > <312>
Измените описание типа элементов у массива А на Туре MyArray=Array[l..n] Of 1..п; (ограниченный тип). Возникает ошибка (Error 26: Type mismatch) при вызове процедуры Swap. Устраните ее. Не исключено, что Вам придется ввести еще один тип данных. 3. Дано натуральное число п. Необходимо получить все разбиения числа п на сумму слагаемых п=а 1 +а 2 + ... +а к , где к, а х , а 2 , ..., а к >0. Будем считать разбиения эквивалентными, если суммы отличаются только порядком слагаемых. Множество эквивалентных сумм представляем последовательностью а х , а 2 , ..., а к , где а х >а 2 > ... >а к . При п=4 разбиения 1+1+1 + 1, 2+ +1+1, 2+2, 3+1, 4 перечислены в лексикографическом порядке. Для каждой пары соседних разбиений находится номер позиции, в которой число из второго разбиения больше числа из первого разбиения, а до этой позиции последовательности совпадают. Порядок 4, 3+1, 2+2, 2+1+1, 1+1+1+1 определяют как обратный лексикографическому. Наша задача — генерировать разбиения в последнем порядке. Рекурсивная схема реализации просматривается достаточно легко. Мы записали в позицию t число аг Сумма чисел a1+a2+...+at=s, тогда с позиции t+1 мы генерируем все разбиения числа n-s, причем для элемента (и остальных) должно выполняться неравенство А[ t+1 ]<A[t]. Переход к следующему разбиению в позиции t сводится, если это возможно, к вычитанию 1. Уточним некоторые технические детали. Разбиение хранится в глобальном массиве А. Количество элементов в разбиении различно. Значение переменной t определяет текущий размер
Процедуры и функции-—элементы структуризации программ
159
выводимой части массива А. Процедура Print претерпит небол ь ш о е изменение. Procedure Print (t: Integer) ; Var l: IntegersBegin For l: =1 To t Do Write (A[i] : 3) ; WriteLn; End; Р е к у р с и в н а я процедура Solve имеет два параметра: число п и п о з и ц и ю t, н а ч и н а я с которой необходимо получить все разбиения числа п. П е р в ы м разбиением я в л я е т с я разбиение, соответствующее записи числа п в позицию t, а затем ... Последовательно записываем в позицию t числа п-1, п-2 ..., и так до 1, а с п о з и ц и и t+1 генерируем все разбиения чисел 1, 2, ..., п-1 соответственно. Приведем текст решения. ($R+} Program Му13_3; Const п=4; Type MyArray=Array[1..п] Of Integer; Var A:MyArray; Procedure Solve (n, t -.Integer j ; Var l: IntegersBegin I f n=l Then Begin A [t] : =1; Print (t) ;End Else Begin A[ t] : =n ; Print (t) ; For i:=l To n-1 Do Begin A[t] :=n-i; Solve (i,t+l) ; EndsEnd; End; Begin Solve(n,l); ReadLn; End. Однако после запуска программы получается результат, несколько отличный от предполагаемого: 4, 3 1, 2 2, 2 1 1, 1 3, 1 2 1, 1 1 2, 1 1 1 1. Строки 5, 6, 7 лишние. Изменим процедуру. Procedure Solve (п, t: Integer) ; Var i-.IntegersBegin I f n—1 Then Begin A[t] :=1;Print (t) ,-End
Часть вторал
160 Else A[t]
Begin I f n<=A[t~l] Then :=n;Print f t ) ;End; For l : =1 To n-1 Do Begin A[t]:=n-i; Solve<i,t + 1) ; End; End;
Begin
End; П о я в л е н и е и н д е к с а t-1 т р е б у е т и з м е н и т ь о п и с а н и е т и п а масс и в а на Type MyArray=Array[0..n] Of Integer; и о с у щ е с т в л я т ь н а ч а л ь н о е п р и с в о е н и е А[0]:=п перед п е р в ы м в ы з о в о м Solve ( п , 1 ) . Н е в н е с е н и е э т и х и з м е н е н и й п р и в о д и т к и з в е с т н о й ошибк е Error 201: Range check error. Ч т о м ы п ы т а л и с ь с д е л а т ь ? Отсечь л и ш н и е р а з б и е н и я п р и и х в ы в о д е , но не г е н е р а ц и и ! З а п у с к п р о г р а м м ы нас о ч е р е д н о й р а з р а з о ч а р у е т . Р е з у л ь т а т р а б о т ы : 4, 3 1, 2 2, 2 1 1, 1 2 1, 1 1 1 1. И з м е н и м п р о ц е д у р у . Procedure Solve(п,t:Integer); Var l,q:Integer; Begin I f n=l Then Begin A[t]:=1;Print (t) ;End Else Begin I f n<=A[t-l] Then Begin Aft]:=n;Print (t);End; q:=Min (n~l,A[t-l]) ; For l: =q DownTo 1 Do Begin Aft]:=!/ Solve(n-i,t + 1) ; End; End; End; Она потребует ф у н к ц и ю Min, но ничего с т р а ш н о г о , вставьте ее в текст р е ш е н и я . Function Min(a,Ь:Integer):Integer; Begin I f a<b Then Min:=a Else Min:=b; End; П о л у ч а е м п р а в и л ь н ы й р е з у л ь т а т : 4, 3 1 , 2 2 , 2 1 1 , 1 1 1 1 , но ч а с т ь р е ш е н и й м ы п о - п р е ж н е м у «отсекаем» т о л ь к о на выходе. П р о д о л ж и т е исследование. Н а у ч и т е с ь в ы в о д и т ь р а з б и е н и я в следующих порядках: • 1 1 1 1, 2 1 1, 2 2, 3 1, 4; • 1 1 1 1, 1 1 2, 1 3, 2 2, 4; • 4, 2 2, 1 3, 1 1 2, 1 1 1 1.
Процедуры и функции-—элементы структуризации программ
161
П р и м е ч а н и е При отладке программы и достижении понимания ее работы Вам очень поможет отладчик системы программирования. Задания для самостоятельной работы 1. Написать рекурсивную программу вывода на экран следующей картинки: 1111111111111111 222222222222 33333333 4444 33333333 222222222222 1111111111111111
(16 раз) (12 раз) (8 раз) (4 раза) (8 раз) (12 раз) (16 раз)
2. В к о н ц е XIX века в Европе появилась игра под названием «Ханойские башни». Реквизит игры состоит из 3 игл, на которых размещается башня из колец. Цель игры — перенести башню с левой иглы (1) на правую (3), причем за один раз можно переносить только одно кольцо, кроме того, запрещается помещать большее кольцо над меньшим. 1
2
3
Выполните трассировку приведенного ниже решения с использованием отладчика системы программирования. Убедитесь в правильности решения. Попробуйте написать не рекурсивную программу решения задачи. Program Var п: Procedure Integer); Begin 6-452
Myl3_zl; Integer; MoveTown (High,FromI,
Toi,
WorkI:
Часть вторал
162 I f High>0 Then Begin MoveTown(High-1,FromI,WorkI,Tol); Writeln('Перенести кольцо №',High,' с иглы ',FromI,' на иглу ' , Tol); MoveTown(High-1,WorkI,Tol,FromI); End; End; Begin Write('Количество колец - ') ; ReadLn (n) ; MoveTown(n,l,3,2) ; End.
Д а н н а я з а д а ч а своим п р о и с х о ж д е н и е м о б я з а н а и н д у с с к о й легенде, к о т о р а я р а с с к а з ы в а е т , что в б о л ь ш о м х р а м е Б е н а р е с а б р о н з о в а я п л и т а п о д д е р ж и в а е т т р и а л м а з н ы х с т е р ж н я , на один и з к о т о р ы х бог н а н и з а л во в р е м я с о т в о р е н и я м и р а 64 з о л о т ы х д и с к а . С тех пор день и ночь м о н а х и , с м е н я я друг д р у г а , к а ж дую с е к у н д у п е р е к л а д ы в а ю т по одному д и с к у согласно описанн ы м в ы ш е п р а в и л а м . К о н е ц м и р а н а с т у п и т тогда, к о г д а все 64 д и с к а будут п е р е м е щ е н ы , н а что потребуется ч у т ь б о л ь ш е 58 м л р д . лет. 3. Составить рекурсивную ф у н к ц и ю в ы ч и с л е н и я з н а ч е н и я функ ц и и А к к е р м а н а д л я н е о т р и ц а т е л ь н ы х ч и с е л п и т , вводимых с клавиатуры. m + 1, п = О Л(л - 1,1) , п * 0, m = О Л(л - 1, Л(л, m - l ) ) , n > 0 ,
т > 0
Н а й д и т е одну п а р у з н а ч е н и й п и т , п р и к о т о р ы х в о з н и к н е т переполнение с т е к а адресов возврата. 4. Ф у н к ц и я F(n) определена д л я ц е л ы х п о л о ж и т е л ь н ы х ч и с е л с л е д у ю щ и м образом: 1, е с л и л = 1 F(n)
^
F(n div
i) , е с л и л > 2
Процедуры и функции-—элементы структуризации программ
163
Верно л и следующее решение? Function F (п : Integer) Var i , s : Integer; Begin I f n—1 Then s:=1 Else For i:=2 To n Do s : =s +F (n Div 2) ; F:=s; End;
-.Integer;
Можно ли вычислить с помощью этой реализации ф у н к ц и и значения F при п = 5 , 6, 7, ..., 20. 5. Число сочетаний С(п,т) вычисляется по формуле С(п,т)= =С(п-1,т. 1 )+С(п-1,т.), при этом С(п,0)=1 кС(п,п)=1. Напишите рекурсивную функцию для подсчета числа сочетаний. Определите область ее применения (диапазон допустимых значений п и т ) . Материал для чтения 1. П р о д о л ж и м рассмотрение понятия алгоритма. В математике это строгое понятие и теория алгоритмов является по сути одним из ее разделов. Почему возникла эта теория, ведь веками пользовались неформальным определением алгоритма? Дело в том, что появились задачи, для которых не могли найти алгоритмов р е ш е н и я . После безуспешных попыток естественно возник вопрос «Существуют ли вообще алгоритмы решения таких задач»? Опираясь на нестрогое понятие алгоритма, доказать этот факт средствами математики, а других нет, не представляется в о з м о ж н ы м . Примеры задач. А. Тьюринг доказал, что невозможно н а й т и алгоритм, который по произвольной программе определял бы, остановится эта программа на произвольно заданном входе и л и нет. Д л я десятой проблемы Гильберта о разрешимости в целых числах полиномиальных уравнений т а к ж е не найдено решения. 2. Алфавит — это непустое конечное множество символов. Всякую конечную последовательность символов алфавита назовем словом. Допускается существование пустого слова (обозначим Л). Примеры. Натуральные числа можно записать в виде слова в алфавите {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}. Целые — {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -}. Рациональные — {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, —, /}.
164
Часть вторал
Однако для записи натуральных чисел достаточно алфавита, состоящего из одного символа {I}. Слова в этом алфавите выглядят: I, I I , I I I , I N I , M i l l , . . . Дополнение этого алфавита, обеспечивающее запись целых и рациональных чисел, не вызывает трудностей. Рассмотрим квадратные уравнения с целыми коэффициентами, например 7 х 2 - З х + 5 = 0 . В алфавите {О, 1, 2, 3, 4, 5, 6, 7, 8, 9, - , +, *, =} они представимы в виде слов типа 7*х*х-3*х+5=0. Примеры можно продолжить. Главное: входные, промежуточные и конечные результаты любой практически решаемой задачи можно интерпретировать как слова в некотором конечном алфавите. Если это не так, то вопрос о получении результата решения задачи остается открытым. Как обрабатываются слова? Заменой, часть слова или все слово заменяется другим. Эту замену называют подстановкой. Например, есть алфавит { | , #} и подстановка: I # I-» I | . Слово I I # I I I # II М в результате последовательного применения подстановки преобразуется в слово I I I II I II I. 3. Нормальные алгоритмы А. А. Маркова. Дадим определение понятия алгоритма по А. А. Маркову. Пусть А — некоторый алфавит, не содержащий символов «->» и «.». Обычной формулой подстановки в алфавите А называется ориентированная подстановка P-»Q, где Р и Q — слова в алфавите А. Заключительной подстановкой в алфавите А называется подстановка P.Q, где Р и Q — слова в алфавите А. При этом Р называется левой частью формулы подстановки, a Q — ее правой частью. Обыкновенную и заключительную подстановки записывают единым образом P-»yQ, где у есть или символ «.» или пустое слово Л. Определение.Конечная становок: Pi->YIQI P2-+Y2Q2
непустая упорядоченная система под-
Pn-»YnQn называется нормальной схемой в алфавите А, все Р,, Qt — слова в алфавите А для всех i от 1 до п. Нормальный алгоритм задается алфавитом А и нормальной схемой в этом алфавите.
Процедуры и функции -— элементы структуризации программ
165
Схема работы нормального алгоритма. Дано слово Р в алфавите А. Выясняется, есть ли среди подстановок нормальной схемы хотя бы одна с левой частью входящей в Р. Если таких подстановок нет, то алгоритм заканчивает работу и результат — само слово Р. Если же в нормальной схеме есть Р , то берется первая подстановка, удовлетворяющая этому условию, и применяется к слову Р. В результате получается новое слово Р'. В том случае, когда выбранная подстановка была заключительная, алгоритм заканчивает работу, в противном случае — процесс продолжается со словом Р'. Слово Q, получаемое в результате работы алгоритма (к нему не применима ни одна подстановка), есть результат работы алгоритма. Итак, А. А. Марковым дано строгое с математической точки зрения определение алгоритма, нормального алгоритма. Что это дает? Тезис А. А. Маркова: «Если для решения задачи есть алгоритм, то этот алгоритм может быть представлен в виде нормального алгоритма». Тезис не доказывается, ибо, с одной стороны, есть нечеткое, нестрогое понятие алгоритма, а с другой стороны, строгое определение нормального алгоритма. Тезис может быть опровергнут только конкретным примером, которого пока не найдено и, видимо, не будет найдено. Далее. Есть задача. Для этой задачи доказывается (строгое математическое доказательство), что для нее не может быть построена, создана нормальная схема. Вывод — данная задача не имеет алгоритма решения. 4. Проблема слов. Дан алфавит А, нормальная схема и два слова — Р и Q. Вопрос. Можно ли из слова Р получить слово Q, применяя подстановки из нормальной схемы? Если да, то слова считаются эквивалентными. Как установить факт эквивалентности для любых двух слов в алфавите А? Пример. Дан алфавит А={а,Ь} и система подстановок: ba-oab, аа-оЛ и bb-оЛ (обратите внимание на то, что подстановки неориентированные). Решите задачу для нескольких слов. Оказывается, что любое слово сводится к одному из следующих слов: a, b, ab, Л. Схема решения: среди всего множества слов W выделяется подмножество S c W , такое, что любое слово из W преобразуется нормальной схемой к одному из слов, принадлежащих S. Тогда, если слова Р и Q преобразуются в одно слово, то они эквивалентны, иначе — нет. Как находить подмножество S — открытый вопрос. Еще пример. Дан алфавит А={а,Ь} и система подстановок: ba<->aab, ааа<->Л и ЬЬ<->Л. Определить, эквивалентны ли два
Часть вторая
п р о и з в о л ь н ы х слова — Р и Q. Проверьте, что м н о ж е с т в о S состоит из слов: Л, a, b, аа, ab, aab. В з а к л ю ч е н и е о т м е т и м , что н о р м а л ь н ы е алгоритмы А . А. М а р к о в а не е д и н с т в е н н а я схема у т о ч н е н и я п о н я т и я алгор и т м а , придания ему статуса строгого математического понятия со всеми вытекающими из этого факта последствиями. Известны алгоритмическая система Э. Поста (1935 г.), алгоритмическая система А. М. Т ь ю р и н г а (1937 г.), а п п а р а т ч а с т и ч н о рекурсивн ы е ф у н к ц и и и т. д.
Процедуры и функции -— элементы структуризации программ
167
З а н я т и е № 14. С и м в о л ь н ы й и с т р о к о в ы й типы данных План занятия • символьный тип данных; • короткие программы иллюстрации работы с символьным типом данных; • строковый тип данных; • процедуры и функции работы со строковым типом данных; • экспериментальная работа с программами: выделения слов из текста; вставки символа пробела после запятой; нахождения расстояния между строками; генерации всех подстрок строки; определения эквивалентности двух слов; • выполнение самостоятельной работы. Символьный тип данных. Идентификатором типа является зарезервированное слово Char. Значениями типа Char являются элементы конечного и упорядоченного множества символов, т. е. на множестве значение типа Char есть отношение порядка. Американский стандартный код для обмена информацией (ASCII — American Standard Code for Information Interchange) есть система кодирования, в которой алфавитные, цифровые и управляющие символы представлены в виде 8-разрядного двоичного кода. Символы с кодами от 0 до 127 составляют так называемую основную таблицу кодов ASCII. Эта часть идентична на всех IBM-совместимых компьютерах. Так, цифрам от 0 до 9 соответствуют коды от 48 10 (001100 002) до 57 10 (00111001 2 ); буквам латинского алфавита от А до Z — коды от 65 10 (01000001 2 ) до 90 10 (01011010 2 ); буквам от а до z — коды от 97 10 (01100001 2 ) до 122 10 (01111010 2 ); буквам русского алфавита от А до Я — коды от 128 10 (1000 0 0 002) до 159 10 (10011111 2 ). Константы этого типа обрамляются в апострофы, например: '*' '3' 'G'. Для отображения множества символов на их порядковые номера и обратно существуют две функции: Ord и Chr. Функция Ord(w) дает порядковый номер символа w, Chr(i) определяет символ с порядковым номером i. Функции Ord и Chr обратные по отношению друг к другу: Chr(Ord(w))=w и Ord(Chr(i))<=i. Отношение порядка на множестве символов позволяет выполнять операции сравнения. Из двух символов меньше тот, который встречается раньше в кодировке ASCII. Для величин типа Char функции Pred и Succ работают следующим образом: Pred(q)=Chr(Ord(q)-l) и Succ(q)=Chr(Ord(q)+l).
167 Часть втор Короткие программы. В п е р в о м п р и м е р е в ы в о д я т с я символы и соответствующие им коды. Переменная к используется к а к с ч е т ч и к д л я о р г а н и з а ц и и п о с л е д о в а т е л ь н о г о в ы в о д а — по 5 символов. Program Myl4_1; Var г, k:Integer; Begin к :=0 WriteLn('Вывод порядковых номеров (кодов) символов — значение переменной i и самих символов.'); For i:=l То 255 Do Begin Write (i:4, ' Символ ' ,Chr(i)); Inc (k); I f k=5 Then Begin WriteLn;k:=0; End; End; End; Во втором п р и м е р е п о к а з а н о , к а к п е р е м е н н а я с и м в о л ь н о г о т и п а и с п о л ь з у е т с я в к а ч е с т в е у п р а в л я ю щ е й п е р е м е н н о й в операторе For. С и м в о л ы в ы в о д я т с я т а к : А ВВ ссс W W W W .,W W (23 раза) Program Му14_2; Var i:Char; j .-Integer; Begin For i: = 'A' To 'W' Do Begin For j:=l To Ord (i) -Ord (' A') +1 Do Write WriteLn; End; End.
(l) ;
Определите, ч т о в ы в о д и т с я на э к р а н в р е з у л ь т а т е следующ е й не очень з н а ч и т е л ь н о й м о д и ф и к а ц и и п р о г р а м м ы . Program Му14_2т; Var 1:Char; Begin For i :='a' To 'z' Do For j:='a' To i Do Write ( j ) ; ReadLn; End.
Процедуры ифункции-—элементы структуризации программ
169
В следующем примере подсчитывается количество символов, введенных с клавиатуры. Ввод заканчивается символом '.' Вы вводите несколько символов, затем точку и нажимаете клавишу Enter. Программа выдает правильный результат. А если нажимать клавишу Enter после ввода каждого символа? Результат неверный, он к а к будто бы в три раза превышает истинный результат. На самом деле все верно. Нажатие клавиши Еп ter генерирует ввод еще двух символов (управляющих) — возврата каретки (код 13) и перевода строки (код 10). Program Му14_3; Var 1: Char; j:Integer ; Begin Read(i) ; j:=0; While K>'.' Do Begin Inc ( j ) ;Read (l) ;End; Wri teLn ( j ) ; End. Другая версия этой простой программы позволяет отказаться от символа точка как признака конца ввода данных. Символ # перед целым числом говорит о том, что это число следует рассматривать как ASCII код символа. Program Му14_3т; Var 1 : Char; j:Integer; Begin Read(i) ; j:=0; While i<>tl3 Do Begin Inc (J ) ;Read (i) ;End; WriteLn ( j ) ; ReadLn; End. А если изменить строку на While к>#10 Do Begin Inc(j); Read(i);End;, то количество подсчитанных символов на единицу больше. Объясните результат. В следующем примере подсчитывается количество цифр во вводимых с клавиатуры данных. Program Му14_4; Var ch :Char; к:Integer; Begin Read(ch) ;
Часть вторал
170 к:=0; While ch<>#13 Do Begin IF (ch>=' 0') And (ch<='9') Then Read (ch) ; End; WriteLn С Количество цифр равно ' End.
Inc(k);
,к);
Измените программу так, чтобы она • определяла, являются ли введенные данные правильной записью целого числа; • в ы ч и с л я л а сумму цифр введенного числа. Ф у н к ц и я Upcase(ch) п р е о б р а з у е т з н а ч е н и е п е р е м е н н о й ch с и м в о л ь н о г о т и п а , е с л и оно с о о т в е т с т в у е т с т р о ч н о й л а т и н с к о й б у к в е , в к о д п р о п и с н о й б у к в ы , и н а ч е з н а ч е н и е ch о с т а е т с я неи з м е н н ы м . Определите, что за обработка вводимых символов осуществляется в следующем примере. Program Myl4_4m; Var ch:Char; Begin Read(ch); While ch<>#13 Do Begin IF (ch>='a') And (ch<='z') Else Write(ch); Read(ch); End; End.
Then
Write(Upcase
(ch))
Строковый тип данных. Строкой н а з ы в а е т с я последовательность символов определенной длины. Идентификатор типа — слово String. П р и м е р ы о п и с а н и я п е р е м е н н ы х т и п а S t r i n g : Var Strl: String[10]; Str2: String; Str3:String[13], В квадратных скобках указывается м а к с и м а л ь н ы й размер (длина) строки. Е с л и он н е у к а з а н , то д л и н а с т р о к и с ч и т а е т с я р а в н о й 2 5 5 симв о л а м . З а м е т и м , ч т о с т р о к у м о ж н о р а с с м а т р и в а т ь к а к одномерн ы й массив из символов — к к а ж д о м у символу строки допустим о о б р а щ е н и е по его н о м е р у (Strl[i] — это о б р а щ е н и е к i-му э л е м е н т у с т р о к и Strl). П е р в ы й с и м в о л с т р о к и (с и н д е к с о м 0) с о д е р ж и т ф а к т и ч е с к у ю д л и н у с т р о к и . П е р е м е н н ы е т и п а String в ы в о д я т с я н а э к р а н м о н и т о р а п о с р е д с т в о м с т а н д а р т н ы х процед у р Write и WriteLn и в в о д я т с я с п о м о щ ь ю ReadLn и Read. То есть в в о д я т с я и в ы в о д я т с я не п о э л е м е н т н о , к а к м а с с и в ы , а сразу ц е л и к о м . С л е д у ю щ и е п р о с т о й п р и м е р и л л ю с т р и р у е т с к а з а н ное.
Процедуры и функции -— элементы структуризации программ
171
Program Му14_5; Var s:Stnng; w: String [10] ; v:Stnng[5]; i,j:Integer; Begin ReadLn (v); WriteLn(v); WriteLn(Integer(v[0])); ReadLn(w); WriteLn(wj ; WriteLn(Ord(w[0]) ) ; ReadLn (s); WriteLn (s); WriteLn(Integer(s[0])); For i:=1 To 0rd(s[0]) Do Begin For j:=l To l - l Do Write (' ' ) ; WriteLn ( s [ i ] ) ; End; ReadLn ; End. В ы в в о д и т е с т р о к и v и w б о л ь ш е й д л и н ы , ч е м у к а з а н о в опис а н и и . В ы в о д н а э к р а н п о к а з ы в а е т , что п р и этом о н и «обрезаютс я » . О п е р а т о р ы WriteLn(Integer(v[0])) и Wntebn(0rd(w[0])) обеспечивают вывод значения д л и н ы строки. Если Вы измените п е р в ы й о п е р а т о р н а WriteLn(v[0]), то вместо ц и ф р о в о г о з н а ч е н и я н а э к р а н в ы в о д и т с я « н е п о н я т н ы й » с и м в о л . Попроб у й т е о б ъ я с н и т ь этот р е з у л ь т а т и п о н я т ь с м ы с л п р е о б р а з о в а н и я Integer(v[0] ). С л е д у ю щ и е о п е р а т о р ы п р и м е р а д е м о н с т р и р у ю т посимвольное обращение к строке — строка рассматривается к а к s : A r r a y [ 0 . . 2 5 5 ] Of Char;. В ы в о д с и м в о л о в с т р о к и s н а э к р а н осуществляется «лесенкой». Сравнение строк происходит посимвольно слева направо: с р а в н и в а ю т с я к о д ы с о о т в е т с т в у ю щ и х с и м в о л о в до тех пор, п о к а не н а р у ш и т с я р а в е н с т в о , п р и э т о м с р а з у д е л а е т с я в ы в о д о з н а к е н е р а в е н с т в а . Д в е с т р о к и н а з ы в а ю т с я р а в н ы м и , если они р а в н ы по д л и н е и с о в п а д а ю т п о с и м в о л ь н о . Примеры: • 'Balkon'c'balkon' (Ord('B-)<Ord(V)); • 'balkon'>'balken' (Ord('o')>Ord('e')); • 'balkon'>'balk' (длина первой строки больше); • ' к о ш к а ' > ' к о ш к а ' ( т а к к а к д л и н а п е р в о й с т р о к и больше); • ' К о т ' = ' К о т ' ( р а в н ы по д л и н е и с о в п а д а ю т п о с и м в о л ь н о ) . Присваиваемое значение строки (строковая константа), так ж е к а к и о т д е л ь н ы й с и м в о л т и п а Char, з а к л ю ч а е т с я в апостроф ы . Н а п р и м е р , Strl:='y Егорки; Str2:= всегда отговорки; Стандартные процедуры и функции типом данных п р и в е д е н ы в т а б л и ц е .
работы
со
строковым
172
Часть вторал
Тип
Вызов
Процедура
Delete(s,p,n)
Процедура
lnsert(w,s,p)
Параметры
Действие
Var s. String, р,п-Integer
Удаляются n символов из строки S, начиная с позиции р
Var s String, p Integer,
В строку s, начиная с позиции р, вставляется строка w Если результат превысит 255 символов, строка обрывается
||
Процедура
Str( v, s)
Процедура
Val(s,v,w)
Функция
Concat(s1,s2, ,sm) илиs/+s2+ +sm Значение функции типа String
s1,s2, , sm String,
Строки s/+s2+ +sm II записываются одна за другой Если I результат превысит I 255 символов, строка обрывается
Функция
Copy(s,p,n) Значение функции типа String
s. String, p.n- Integer;
Из строки s, начиная с позиции р, выбирается п символов
Функция
Length(s) Значение функции типа Integer
s String;
Функция
Pos(w,s) Значение функции типа Integer
w,s String;
v Integer ИЛИ v Real, Var s'String, s String, Var v Integer или Var v Real, Var w Integer,
Число V преобразуется в строку, результат в s Если строка s состоит из цифр, то они преобразуются в числовое значение переменной v, значение wравно 0 В противном случае, строка состоит нв только из цифр, — преобразование не выполняется, w O — I признак ошибки
Определяется длина s, т. е. число символов из которых она состоит В строке s отыскивается первое вхождение строки w (номер позиции) Если вхождения нет, то выдается 0
Примеры. В приведенных примерах переменные si, s2, s3 имеют тип String, p,q — тип Integer. 1. sl:='y Егорки всегда отговорки'; Delete( si,7,8);
Процедуры и функции-—элементы структуризации программ
173
Р е з у л ь т а т — sl = 'Y Е г о р а о т г о в о р к и ' . 2. sl:= 'У Е г о р к и всегда отговорки'; « 2 : = ' М а т р е н ы и '; Insert(s2,sl,3); Р е з у л ь т а т — sl='У М а т р ё н ы и Е г о р к и всегда отговорки'. 3. р: = 1 2 3 4 ; д : = 3 4 . 5 ; Str(p.sl); Str(q,s2); Р е з у л ь т а т — sl = ' 1 2 3 4 ' , s 2 = ' 3 4 . 5 ' . 4. s . Z : = ' 5 5 5 ' ; s 2 : = ' 2 3 . 3 4 5 ' ; s 3 : = ' 3 4 r r 2 ' ; Val(sl.p.w); Val(s2,q,w); Val(s3,p,w); Р е з у л ь т а т : в первом случае — р = 5 5 5 , w = 0 ; во втором случае — q = 2 3 . 3 4 5 , w = 0 ; в третьем случае — w < > 0 , р = ? ? ? . 5. sl:='Y Е г о р к и всегда отговорки, '; s2:='у М и л а д к и всегда шоколадки'; s3:=Concat(sl,s2); (или s 3 : = s i + s 2 ; ) Р е з у л ь т а т — в З = ' У Е г о р к и всегда отговорки, у М и л а д к и всегда ш о к о л а д к и ' ; 6. s l : = ' y Е г о р к и всегда отговорки, у М и л а д к и всегда шоколадки' s2:=Copy( s,28,26); Р е з у л ь т а т — s 2 = ' y М и л а д к и всегда ш о к о л а д к и ' . 7. s / : - ' y Е г о р к и всегда отговорки'; p:=Length(s); Р е з у л ь т а т — р—25. 8. sl:='У Е г о р к и всегда отговорки'; p:=Pos('o',s ); Р е з у л ь т а т — р=5. П р о д о л ж и т е п р и м е р ы . Н а п и ш и т е программу д л я исследован и я работы перечисленных процедур и функций. Обратите особое внимание на граничные условия, т. е. когда какой-либо параметр выходит за пределы строки. В этой работе можно использовать приведенные ниже короткие примеры. 1. Подсчет количества символов (параметр q) в строке (параметр st). Function QChar(q:Char;st: String): Byte; Var i , k: BytesBegin к: = 0 / For i : =1 To Length (st) Do I f st[i]=q Then Inc (k) ; QChar:=k; End;
Часть вторал
174
2. У д а л и т ь с р е д н ю ю б у к в у п р и н е ч е т н о й д л и н е с т р о к и и две средние буквы при четной длине строки. Procedure Mi Del ( Var st: Var k: Byte; Begin к:=Length (st) ; I f к Mod 2=1 Then Delete(st, Delete(st,k Div 2,2); End;
String);
к Div
2+1,1)
Else
3. З а м е н и т ь все в х о ж д е н и я п о д с т р о к и w в с т р о к е s t н а подстрок у V. Procedure Ins(w, v:String; Var k: Byte; Begin While Pos(w,st)<>0 Do k:= Pos (w,st); Delete(st,k,Length(w)); Insert(v,st,k); End; End;
Var
st:
String);
Begin
4. П о д с ч и т а т ь с у м м у ц и ф р , в с т р е ч а ю щ и х с я в с т р о к е . Function Sum (st: String): Var l , k, d, s: Integer; Begin s: =0 ; For l;=1 To Length ( s t ) Do Val (st [ i ] , d, k) ; I f k=0 Then s:=s+d; End; Sum:=s; End;
Integer;
Begin
Экспериментальный раздел работы 1. Д а н а строка. Считаем ее о т р ы в к о м текста. Г р у п п ы символов, разделенных одним и л и н е с к о л ь к и м и пробелами, назовем словами. П р о б е л ы могут н а х о д и т ь с я к а к в н а ч а л е т е к с т а , т а к и в конце. Н а ш а задача — выделить слова из текста и к а ж д о е слово з а п и с а т ь в с о о т в е т с т в у ю щ и й э л е м е н т м а с с и в а . П р и в е д е н н а я н и ж е п р о г р а м м а р е ш а е т эту з а д а ч у . В ы п о л н и т е ее в р е ж и м е о т л а д ч и к а , н а б л ю д а я з а и з м е н е н и е м з н а ч е н и й переменных.
Процедуры и функции-—элементы структуризации программ
175
Program Му14_6; Const п=20;т=10;(*Количество слов в тексте и количество букв в слове. Естественно, что эти параметры программы допускается изменять. *} Type TString=String[m]; Var А:Array[1..n] Of TString; s:String; k, l .-Integer ; Procedure DelPr(Var s:String);{*Удаляем пробелы в начале текста. *} Begin While (s[l] = ' ' ) And ( s o " ) Do Delete (s, 1,1) ; EndsFunction GetWord (Var s:Strmg) : TString; { *Выделяем слово, при этом оно удаляется из текста, и убираем пробелы после слова. *) Begin GetWord:=Сору(S,1,Pos (' ' ,s) -1) ; Delete(s,1,Pos(' ',s)>; DelPr ( s ) ; End; Begin WriteLn('Введите текст'); ReadLn (s) ; s:=s + ' ';(* Добавляем символ пробела в конец текста. Зачем?*) DelPr(s);(*Удаляем пробелы в начале текста. *) к: =0 ; While s<>" Do Begin (*Пока текст не пустой. *} Inc (k) ; A[k]:=GetWord(s);{*Берем слово из текста.*) End; For-i: =1 To k Do Wri teLn (A [l ] ); ReadLn; End. У д а л и т е в ы з о в п р о ц е д у р ы DelPr(s) из основной п р о г р а м м ы , а в ф у н к ц и и GetWord. переставьте этот вызов в ее начало. Что и з м е н и т с я в работе п р о г р а м м ы ? Н а к а к и х и с х о д н ы х д а н н ы х она не будет п р а в и л ь н о работать? Что произойдет, если в конец т е к с т а не д о б а в л я т ь символ пробела? В процедуре DelPr измен и т е While (s[l]=' ') And (so") Do Delete(s,l,l) на While (s[l]=' •) Do Deletefs,l,l). Н а й д и т е п р и м е р исходного текста,
Часть вторал
176
при котором результат работы программы о к а ж е т с я неверным и л и его вообще н е будет. П р о д о л ж и т е э к с п е р и м е н т ы с программой. 2. П о п р а в и л а м м а ш и н о п и с и после з а п я т о й в т е к с т е всегда став и т с я пробел. П р о г р а м м а исправляет такой тип о ш и б к и в тексте. Program Му14_7; Var 1:Integer; s:String; Begin WriteLn('Введите текст'); ReadLn (s) ; i: =1 ; While KLength (s) Do Begin If (s[i]=',') And Notts [1+1] = ' ') Then Insert Г ' ,s,i+l); Inc (i) ; End; WriteLn (s) ; ReadLn; End. Почему выбран оператор While, а не Fori П о ч е м у в теле цикл а н е л ь з я использовать ф у н к ц и ю Pos(' ' ,s)'> Обратите в н и м а н и е , если з а п я т а я — последний символ текста, то м ы не добавляем пробела. Измените программу д л я и с п р а в л е н и я о ш и б к и т и п а «после символов '!', ' ? ' , '.' д о л ж е н стоять пробел, а затем текст начинается с заглавной буквы». Вспомните еще р я д традиционных ошибок и модифицируйте программу д л я и х и с п р а в л е н и я . 3. Д а н ы две с т р о к и X и У. Н а з о в е м р а с с т о я н и е м (г) м е ж д у X и У количество символов, к о т о р ы м и X и У р а з л и ч а ю т с я м е ж ду собой. Н а п р и м е р : X='abcd', Y='dxxc', r=4; Х='111 1111', Y= 111222', r=7. Н а п и с а т ь программы в ы ч и с л е н и я р а с с т о я н и я м е ж д у строками. Решение. Program Му14_8; Var i ,j,г:Integer; S,X,Y:String; Begin WriteLn ('Первая строка') ; ReadLn (X) ; WriteLn('Вторая строка');ReadLn(Y);
Процедуры и функции-—элементы структуризации программ
177
I f Length(X)>Length (Y) Then Begin S:=X;X:=Y;Y:=S; End;(*Y строка большей длины. *} r:=Length (Y); For l:=1 To Length (X) Do Begin J : =1 ; While (j<=Length (Y) ) And (Y[j]<>X[i]) Do Inc(j); I f j>Length (Y) Then Inc(r) Else Begin Dec(r);Delete(Y,j,1);End;f*Есть совпадение. Уменьшаем расстояние и удаляем символ из Y. *} End; WriteLn('Расстояние ' , г) ; ReadLn; End. К к а к и м п о с л е д с т в и я м приведет и с к л ю ч е н и е и з р е ш е н и я с т р о к и со с р а в н е н и е м д л и н строк и з а п и с и в Y строки наибольш е й д л и н ы ? П р о в е р ь т е . И с к л ю ч и м оператор Delete(Y,],l). Что за р е з у л ь т а т у нас будет п о л у ч а т ь с я ? И з м е н и т е п р о г р а м м у т а к , чтобы в т е к с т е н а х о д и л о с ь два слова, расстояние м е ж д у котор ы м и имеет м а к с и м а л ь н о е ( м и н и м а л ь н о е ) з н а ч е н и е . 4. Д а н а с т р о к а не более чем и з ш е с т и п р о и з в о л ь н ы х р а з л и ч н ы х с и м в о л о в . Р а з р а б о т а т ь п р о г р а м м у в ы в о д а всех в о з м о ж н ы х п о д м н о ж е с т в (подстрок), составленных из символов д а н н о й с т р о к и . Количество подстрок 2 " - 1 , где п — количество символов в строке. Естественно, что к а ж д ы й символ входит в подстроку н е более одного раза. Б у д е м генерировать шестиразр я д н ы е двоичные числа, так, 010101 соответствует подстрока ' b d f ' с т р о к и ' a b c d e f h ' . П р и н ц и п генерации: просматриваем число слева н а п р а в о до первого н у л я , з а м е н я я при этом все 1 н а 0, н а п р и м е р после 1 1 0 0 1 1 получаем 0 0 1 0 1 1 . З а в е р ш е н и е г е н е р а ц и и — число 0 0 0 0 0 0 1 . Решение. Program Му14_9; Const MaxN=6; Type MyArray=Array[l..MaxN+1] Of Var A,Last:MyArray; i:Integer; s:String; Function Eq (X, Y:MyArray;n: Integer) {*Сравнение текущего значения с если п=6.*)
Byte;
:Boolean; 0000001,
178
Часть вторал
Var х:Integer; Begin i : =2 ; While (1<=п) And (X [ i ] = Y [l ]) Do Inc(i); Eg:={i>n); End; Begin WriteLn('Строка '};ReadLn (s); FillChar(A,SizeOf(A),0); FillChar(Last,SizeOf(Last) ,0) ; Last[Length(s)+1]:=1;{*Первое несуществующее разбиение. *} While Not (Eq(A,Last,(Length(s)+1))) Do Begin (*Пока нет совпадения, выполняем генерацию следующей подстроки. *) i : =2 ; While (K=Length (s) ) And (A[i]=l) Do Begin A[i] :=0 ; Inc (i) ;End; A[i]:=1; I f K=Length(s) Then For i:=l To Length(s) Do I f A[i]=l Then Write (S[i]>; {*Вывод подстроки. *} WriteLn; End; ReadLn; End. В схеме генерации реализовано обычное прибавление единиц ы к д в о и ч н о м у ч и с л у , т о л ь к о ч и с л о з а п и с ы в а е т с я слева н а п р а во: 0 0 0 0 0 0 , 1 0 0 0 0 0 , 0 1 0 0 0 0 , 1 1 0 0 0 0 , 0 0 1 0 0 0 и т. д. К а ж д о м у т а к о м у ч и с л у с т а в и т с я в соответствие п о д с т р о к а . И з м е н и т е схему г е н е р а ц и и т а к , ч т о б ы е д и н и ц а п р и б а в л я л а с ь т р а д и ц и о н н ы м способом: 0 0 0 0 0 0 , 0 0 0 0 0 1 , 0 0 0 0 1 0 , 0 0 0 0 1 1 , 0 0 0 1 0 0 и т. д. А можно ли исключить генерации последовательности двоичных чисел из р е ш е н и я задачи? 5. Р а с с м о т р и м с л е д у ю щ у ю з а д а ч у (по м а т е р и а л у д л я ч т е н и я предыдущего з а н я т и я ) . З а д а н ы две строки А и В д л и н ы N (1<W<100), с о с т о я щ и е и з с и м в о л о в 0 и 1 ( д в о и ч н ы е с л о в а ) . Д о п у с к а е т с я с л е д у ю щ е е п р е о б р а з о в а н и е с т р о к . Л ю б у ю подс т р о к у А и л и В, с о д е р ж а щ у ю ч е т н о е ч и с л о е д и н и ц , м о ж н о п е р е в е р н у т ь , т. е. з а п и с а т ь в обратном п о р я д к е . Н а п р и м е р , в с т р о к е 1 1 0 1 0 1 0 0 м о ж н о п е р е в е р н у т ь п о д с т р о к у , составленн у ю и з с и м в о л о в с 3 - й по 6 - ю п о з и ц и и . П о л у ч и т с я с т р о к а
Процедуры и функции-—элементы структуризации программ 178 11101000. Две строки считаются э к в и в а л е н т н ы м и , если одь из н и х м о ж н о получить из другой с помощью описанных npi образований. Определить эквивалентность з а д а н н ы х строк и В, если строки э к в и в а л е н т н ы , предложить один из возмом н ы х способов п р е о б р а з о в а н и я с т р о к и А в строку В. Пример. Д а н ы с т р о к и 1 0 0 0 1 1 1 0 0 , 0 0 1 0 1 1 0 0 1 . Р е ш и т ь дл н и х задачу. Ответ — «да» и п р е о б р а з о в а н и я : 6 9, 3 8, 1 5. Основная идея решения задачи формулируется следующи образом. Н а м н о ж е с т в е всех слов W следует в ы д е л и т ь подмн< жество S (ScW), такое, что д л я любого слова teW м о ж н о ук< зать э к в и в а л е н т н о е ему слово r e S . А л ф а в и т и слова у нас зад; н ы , п р е о б р а з о в а н и я т о ж е . Осталось определить множество £ свести к а ж д о е слово к представителю из м н о ж е с т в а S, и есл п р е д с т а в и т е л и совпадают, то слова э к в и в а л е н т н ы . Из к а к и слов состоит S? Слов в и д а 1 1 . . . 1 0 0 . . . 0 1 0 0 . . . 0 , п р и ч е м к а к n e j в а я г р у п п а е д и н и ц , т а к и п е р в а я г р у п п а нулей, а т а к ж е следук щ а я е д и н и ц а (слово и з одних 0) могут отсутствовать. Основны фрагменты решения. Const Мах=100; Type Tstr=StrmglMax) ; Procedure Reverse(Var S:Tstr;l,j;Integer); (*Достаточно очевидная процедура перевертываем подстроку S с позиции i до позиции J . *} Var ch:Char; Begin While K j Do Begin ch : =S [l] ;S[i] :=S(J] ;S[j] :=ch;Inc(i) ;Dec ( j ) ; End; EndsProcedure Normalize (Var S:Tstr) ; (*Сводим слово к его представителю из S. *) Var i , j , cnt-.Integer ; Begin cnt:=0; For i:=l To Length (S) Do I f S[i]='l' Then Inc (cnt) ; {*Подсчитываем количество 1 в For i:=1 To cnt-1 Do I f S[i] = '0' Then Begin (*Находим слове. *)
«левый»
0 в
180
Часть вторал
Repeat Inc(j) Until S[jJ='1';{*Поиск первой 1.*) Repeat Inc(j) Until S [ J ] = ' 1';(*Поиск второй 1, подстрока содержит четное число 1, ее можно «перевернуть».*} Reverse(S,l,j); End; End; Допишите программу. Заметим, что вывод преобразований, в случае эквивалентности слов, требует дополнительной структуры данных (массива) для запоминания значений i и j при каждом перевертывании. Если исключить из условия задачи вывод преобразований, то её можно решить гораздо проще. Рассмотрим таблицу. Столбцы с заголовком «?» оставлены осознанно.
Внимательно изучив таблицу, догадываемся, что в последних столбцах указано число нулей. Но к а к и х ? В четвертом столбце количество нулей после четного числа единиц, в пятом — после нечетного. И вторая часть нашего вывода — эти числа не изменяются в процессе преобразования слов (строки таблицы с номерами 4 - 6 и 7 - 1 1 ) . После этого можно написать небольшой фрагмент подсчета этих чисел (сразу после их ввода)
Процедуры и функции-—элементы структуризации программ
cnt:=0; chl:=0;ch2:=0 ; For l:=1 To Length(S) Do I f S[i]='0' Then I f cnt Mod 2=0 Then Else Inc(ch2) Else Inc (cnt);
181
Inc(chl)
И т а к , е с л и з н а ч е н и я chl и ch2 д л я слов с о в п а д а ю т , то они эквивалентны в вышеприведенном значении. Нулевая позиция в слове с ч и т а е т с я п р и этом ч е т н о й , н а п р и м е р ч и с л а 1, 5, 4 одн о з н а ч н о о п р е д е л я ю т слово 0 0 0 0 0 1 0 0 0 0 . Обоснуем р е з у л ь т а т . И н в а р и а н т о м п р е о б р а з о в а н и я я в л я е т с я з н а к о ч е р е д у ю щ а я с у м м а в и д а : !.(-1 )'*at, где i — н о м е р единиц ы , а а, — н о м е р п о з и ц и и i е д и н и ц ы . Схема д о к а з а т е л ь с т в а . О б о з н а ч и м ч е р е з s н о м е р п о з и ц и и в п о д с т р о к е , соответствующ и й ц е н т р у п е р е в о р а ч и в а е м о й п о д с т р о к и . П у с т ь х — н о м е р поз и ц и и до « п е р е в о р о т а » , у — н о м е р этой п о з и ц и и п о с л е операц и и « п е р е в о р о т а » . О ч е в и д н о , что (x+y)/2=s, то есть y=2s-x и л и al-^2s-al. К о л и ч е с т в о э л е м е н т о в в с у м м е ч е т н о , з н а к и черед у ю т с я , и Ъ(-1 )l*al =£(~1 )'*(2s-aJ. П о э т о м у п о с л е ввода слов следует п о д с ч и т а т ь к о л и ч е с т в о 1 в к а ж д о м из н и х и с у м м ы указанного т и п а . Е с л и они с о в п а д а ю т , то с л о в а А и В э к в и в а л е н т н ы . Н а п р и м е р , д л я с т р о к и 0 1 0 1 с у м м а р а в н а (-1 )'*2 +(-1 )2* *4=2, после п е р е в о р а ч и в а н и я — 1010 с у м м а — (-1 )'*1 + (-1 )2* *3=2. Они совпадают, строки эквивалентны. Д л я примера ( 1 1 0 1 0 0 и 110001) эти суммы равны - 3 и - 5 , строки неэквивалентны. Задания для самостоятельной работы 1. Н а п и с а т ь п р о г р а м м у в ы в о д а п о с л е д о в а т е л ь н о с т и с и м в о л о в : • ZYYXXX...AA..AA; • ABC...ZZBC...ZZZC...ZZ..ZZ н а э к р а н . 2. С о с т а в и т ь п р о г р а м м у , к о т о р а я в ы в о д и т True, если в с т р о к е б у к в а А в с т р е ч а е т с я ч а щ е , ч е м б у к в а В, и False в п р о т и в н о м случае. 3. П р о в е р и т ь , п р а в и л ь н о л и в з а д а н н о м т е к с т е р а с с т а в л е н ы к р у г л ы е с к о б к и (т. е. находится л и справа от к а ж д о й открыв а ю щ е й с к о б к и соответствующая ей з а к р ы в а ю щ а я скобка, а слева от к а ж д о й з а к р ы в а ю щ е й — соответствующая ей открывающая). 4. П о д с ч и т а т ь к о л и ч е с т в о г л а с н ы х л а т и н с к и х б у к в в строке.
182
Часть вторал
5. Удвоить вхождение некоторой буквы в текст. Например, текст «мама папа» должен иметь вид — «маамаа паапаа». 6. Даны две строки. Вывести буквы, встречающиеся и в той и в другой строках. 7. Дан текст. Вывести все слова, начинающиеся с согласных букв латинского алфавита. 8. Дан текст. Определите • длину самого короткого и самого длинного слов; • количество слов, начинающихся и оканчивающихся одной и той же буквой; • количество слов, в которых содержится хотя бы одна заданная буква; • количество слов, которые содержат определенное количество заданной буквы; • количество слов, я в л я ю щ и х с я палиндромами. 9. Дан текст. Вывести слова, встречающиеся в тексте по одному разу. 10. Дан текст. Вывести различные слова. 11. Дан текст. Вывести все слова, предварительно преобразовав каждое из них по следующему правилу: • удалить из слова все предыдущие вхождения последней буквы; • оставить в слове только первые вхождения каждой буквы. 12. Дан текст. Вывести слова, которые отличны от последнего слова и удовлетворяют следующему условию: • в слове нет повторяющихся букв; • буквы слова упорядочены по алфавиту; • слово совпадает с начальным отрезком латинского алфавита (a, ab, abc, abed,...). 13. Дан текст. Вывести все слова, предварительно выполнив преобразования их по правилу: заменить во всех словах первую букву на заглавную. 14. Написать программы решения ребусов: ОДИН+ОДИН+ +ОДИН+ОДИН+ОДИН=ПЯТЬ; КУБ=(К+У+Б) 3 ; ТРИ+ДВА= =ПЯТЬ; VOLVO+FIAT=MOTOR. При решении ребусов одинаковым буквам соответствуют одинаковые цифры. Например, приведем часть ответов последнего ребуса: 15615+9743=25358, 15715+9643=25358, ..., 72672+9451=82123, их всего 10.
Процедуры и функции-—элементы структуризации программ
183
15. Д а н т е к с т . Н а п и с а т ь программу проверки правильности нап и с а н и я сочетаний « ж и » , «ши», «ча», «ща», «чу» и «щу». Исправить ошибки. 16. Д а н ы три строки. Определить, можно ли из символов первых двух строк п о л у ч и т ь третью строку. 17. Д а н ы две строки. Определить м о ж н о ли, переставляя символ ы в первой строке, получить вторую строку. П р и м е ч а н и е Термин текст в условиях задач следует понимать в смысле первого примера экспериментальной части данного занятия. Материал для чтения 1. О к о д и р о в а н и и . М ы у ж е неоднократно использовали этот т е р м и н . Попробуем разобраться с ним. Из истории. Коды появились в глубокой древности в виде к р и п т о г р а м м , зашифрованн ы х (засекреченных) сообщений. В шифре, получившем название — ш и ф р Ю л и я Ц е з а р я , алфавит размещается к а к бы на круге по часовой стрелке. После последней буквы, например Я в русском алфавите, идет первая буква алфавита (буква А в русском алфавите). Д л я з а ш и ф р о в к и буквы текста заменяются буквами, о т с т о я щ и м и по кругу на заданное число букв (сдвиг) д а л ь ш е по часовой стрелке. Пусть сдвиг равен 2 и сообщение составлено в русском алфавите. Буква А заменяется на букву В, буква Я на букву Б. П р и н ц и п р а с ш и ф р о в к и (декодирования) понятен, если известно значение сдвига. Ш и ф р Ц е з а р я расш и ф р о в ы в а е т с я достаточно легко д а ж е при неизвестном значен и и сдвига. Известны вероятности букв pt, i=l, .... л (л — число букв в алфавите). Подсчитаем частоты букв fl в сообщении (считаем, что оно достаточно длинное). Вычисляем значение Sum(sh)=Abs(p-ft(sh)). Сумма берется по г от 1 до л. Значение sh — это значение сдвига. Найдем минимальное значение Sum, подсчитывая ее д л я р а з л и ч н ы х значений sh от 1 до п. Значение sh, н а котором достигается м и н и м у м значения Sum, считаем сдвигом в шифре Ц е з а р я . С течением времени появились достаточно сложные ш и ф р ы . Клодд Шеннон показал, что возможно построение криптограммы, которая не поддается н и к а к о й расшифровке, при условии, что не известен способ её составления. Итак, создание шифров — объект криптографии, взлом шифров — криптоанализ. В целом эту область деятельности называют криптологией. Кодирование, к а к м ы его понимаем в настоящее время, BO3J н и к л о позднее, и оно связано с проблемой передачи сообщений
184
Часть вторал
по л и н и я м связи (телеграф, телефон, радио, телевидение и т. дои с т о р и ч е с к и п е р в ы й код, п р е д н а з н а ч е н н ы й д л я передачи сообщ е н и й , предложен изобретателем телеграфного а п п а р а т а Сэмюэлем Морзе. В этом коде к а ж д о м у символу соответствует своя последовательность из к р а т к о в р е м е н н ы х (точка) и д л и т е л ь н ы х (тире) импульсов тока, р а з д е л я е м ы х п а у з а м и (точка, тире, пауза — т р и элемента д л я к о д и р о в к и символов, код Морзе троичный). Другим ш и р о к о используемым кодом в телеграфии я в л я ется код Бодо. Д л я кодирования используются два элементарных сигнала — импульс и пауза. Символам сопоставляются кодовые слова одинаковой д л и н ы (из п я т и сигналов). В коде Бодо, в отличие от кода Морзе, используется равномерное кодирование. В коде Морзе наиболее часто используемым символам соответствуют кодовые слова м е н ь ш е й д л и н ы (основополагающая идея в теории оптимального кодирования), поэтому д л я однозначного распознавания (декодирования) и потребовался третий символ — пауза. Коды, использующие два различных элементарных сигнала, называют двоичными. Договоримся обозначать эти сигналы символами 0 и 1. Фрэнсис Б э к о н был первым, кто использовал д л я к о д и р о в а н и я два символа. 2. Общая постановка рассматриваемой задачи поясняется на рисунке. Бук/' \ вами A j , А 2 обозначены два р а з л и ч н ы х / —V алфавита. Сообщение з а п и с ы в а е т с я с I j использованием а л ф а в и т а А г Перевод (преобразование) этого сообщения (W) \ ^/ в сообщение, записанное в алфавите А2, V J у есть кодирование. Обратный перевод (ТО — декодирование. Основные требования к решению задачи W: оптимальность, помехоустойчивость и однозначность декодирования (преобразование W такое, что преобразование V однозначно решает задачу перевода сообщения в исходный алфавит). Под оптимальностью понимается то, что закодированное сообщение должно быть к а к можно меньше (короче). Передача сообщения связана с затратами, при оптимальном кодировании затраты минимизируются. В любом канале передачи данных возможны и с к а ж е н и я . Они приводят к ошибкам. Решение задачи, при котором ошибки и находятся, и, по возможности, устраняются, называется помехоустойчивым кодированием. И наконец, однозначность декодирования. В коде Морзе она достигается за счет введения символа «пауза», в коде Бодо — все символы кодируются одинаковыми по длине последовательно-
Процедуры и функции -— элементы структуризации программ
185
стями. В последующих материалах для чтения рассматриваются схемы кодирования с А2={0, 1}, двоичное кодирование. 3. Ответим еще на один вопрос (повторим). Сколько различных сообщений можно закодировать в m двоичных разрядах? Если ш = 1 , то два — 0 и 1. В общем случае — 2Ш. Строкой из 0 и 1 мы кодировали целые числа, символы (кодировка ASCII). С помощью 0 и 1 кодируются все данные, обрабатываемые ЭВМ. В Computer Science способы кодировки и записи различной информации в двоичном виде называют форматами. Они настолько многочисленны, что их сложно перечислить: форматы графического вывода, форматы сжатия текстов, форматы для звуковых файлов, кинофайлов и т. д.
Часть вторал
186
З а н я т и е № 15. В е щ е с т в е н н ы й т и п д а н н ы х План занятия • вещественный тип данных; • короткие программы иллюстрации работы с вещественным типом данных; • экспериментальная работа с программами: решения уравнения f(x)=0; вычисления площади фигуры; вычисления элементарных функций с использованием представления этих функций в виде некоторых бесконечных сумм; • выполнение самостоятельной работы. Вещественный тип данных. Числа записываются в виде мантиссы (дробной части числа), умноженной на порядок (степень десяти, обозначается буквой Е). Существуют следующие типы вещественных чисел ( наиболее используемый — Real).
J
|
Тип
Диапазон возможных значений
Точность
Real
2 9Е-39 1 7£38
11-12 знаков
6 байт
Single
1 5Е-45 3 4Е38
7 - 8 знаков
4 байта
Double
5.0Е-324 .1 7Е308
15-16 знаков
8 байт
Extended
3 4Е-4932 1 1Е4932
19-20 знаков
10 байт
-9.2Е18 9 2Е18
19-20 знаков
8 байт
Сотр
Формат
|
Примечание Величины типа данных Сотр принимают целочисленные значения, в известной степени этот тип является расширением типа Lon glnt до 19 разрядов. Допустимые арифметические операции: ' + ' сложение, '-' вычитание, '*' умножение, ' / ' деление и операции сравнения. На вещественном типе данных нет отношения порядка, поэтому операцией сравнения ' = ' следует пользоваться с большой осторожностью. Вещественные числа записываются • путем отделения дробной части от целой с помощью точки, например 127.3, 25.0, - 1 6 . 0 0 3 , 200.59, 0.54; • с использованием символа Е, например 0.000009 — 9Е-6, 0 . 6 2 П 0 4 - 0.62£+4, - 1 0 . 8 *10 12 10.8Я12, 20*10- 3 20Я-3.
Процедуры и функции -— элементы структуризации программ
187
П е р е ч и с л и м с т а н д а р т н ы е ф у н к ц и и работы с в е щ е с т в е н н ы м и величинами: Sqr(X)
Квадрат X
SqrtfX)
Квадратный корень из X
Sm(X)
Синус X, X в радианах
Cos(X) Arctan(X) Exp(X) Ln(X) Pi Int(X)
Косинус X, X в радианах Арктангенс X, результат а радианах Число е возводится в степень X Вычисление натурального логарифма X Возвращает значение числа Р/ Возвращает целую часть аргумента X
Frac(X)
Возвращает дробную часть аргумента X
Trunc(X)
X имеет тип Real, результат — целая часть X Тгипс(6 8)=6, Тгипс(-7 9)=7
Round(X)
X имеет тип Real, результат — ближайшее к X целое число Round(5 8)=6, Round(5 5,1=6
Короткие программы. П е р в ы й п р и м е р с в я з а н со сравнением д в у х в е щ е с т в е н н ы х ч и с е л . З а п у с т и т е эту простую программу. Program Му15_1; Var x:Real; у: RealsBegin х:=0.3;у:=0.3; WriteLn(х=у); ReadLn; End.
П о л у ч е н о ж и д а е м ы й результат — значение True. И з м е н и м о п и с а н и е п е р е м е н н о й у н а y.Single. З а п у с к п р о г р а м м ы не пройдет, есть о ш и б к а Error 116: Must be in 8087 mode to compile this.1 Суть в том, что в ы п о л н е н и е а р и ф м е т и ч е с к и х операций с в е щ е с т в е н н ы м и ч и с л а м и осуществляется по-другому, чем с цел ы м и ч и с л а м и . И х р е а л и з а ц и я требует б о л ь ш и х затрат процессорного времени. С этой ц е л ь ю созданы с п е ц и а л ь н ы е быстродействующие сопроцессоры для выполнения операций с числами вещественного т и п а . Только в случае, если на компьютере не установлен арифметический сопроцессор и не включен эмулятор сопроцессора.
187 Часть вторал
Директива {$N+}. Данная директива компилятора осуществляет переключение м е ж д у д в у м я р а з л и ч н ы м и р е ж и м а м и г е н е р а ц и и к о д а д л я вып о л н е н и я о п е р а ц и й с в е щ е с т в е н н ы м и ч и с л а м и . В р е ж и м е {$N-}, а по у м о л ч а н и ю в к л ю ч е н он, к о м п и л я т о р и с п о л ь з у е т библиотек у подпрограмм для работы с 6-байтовыми вещественными ч и с л а м и , т. е. т и п о м Real. В р е ж и м е {$N+} к о м п и л я т о р генерирует п р о г р а м м н ы й код, обеспечивающий п о в ы ш е н н у ю точность в ы ч и с л е н и й и доступ к ч е т ы р е м д о п о л н и т е л ь н ы м вещественным типам данных. И т а к , п р и и с п о л ь з о в а н и и д о п о л н и т е л ь н ы х в е щ е с т в е н н ы х типов д а н н ы х требуется з а п и с ы в а т ь д и р е к т и в у {$N+} в н а ч а л о п р о г р а м м ы . В с т а в и м ее и д о б а в и м еще д в а о п е р а т о р а . Т е к с т п р о г р а м м ы и м е е т вид: {$N+} Program Myl5_lm; Var x:Real; у:Single; Begin x:=0.3;у:=0.3; WriteLn (x=y) ; WriteLn(x); ReadLn; End.
WriteLn(y);
Результат сравнения есть False. П р и ч и н а очевидна — д л я хранения значений х н у выделяется разное количество разрядов. О т м е т и м еще один м о м е н т . П р и работе в р е ж и м е { $ N + } станд а р т н а я п р о ц е д у р а Write о с у щ е с т в л я е т в ы в о д в ф о р м а т е Extended, т а к ж е к а к и з н а ч е н и я , в о з в р а щ а е м ы е с т а н д а р т н ы м и функ ц и я м и , п е р е ч и с л е н н ы м и в ы ш е по т е к с т у . С р а в н и т е работу следующих фрагментов программ (sin(30°)=sin(0.5236...)=0.5) и проведите еще одно небольшое исследование. З а д а й т е в р а з д е л е Const к о н с т а н т ы а и Ъ. З а т е м , вместо оператора WriteLn(Pi) нап и ш и т е WriteLn(Pi:a:b). Н а й д и т е з а в и с и м о с т ь м е ж д у изменен и е м з н а ч е н и й а, Ь и р е з у л ь т а т а м и в ы в о д а ч и с л а Pi н а э к р а н . {$N-} Program Begin WriteLn WriteLn End.
Му15_2; (Sm (0.5236) (Pi);
) ;
Процедуры и функции -— элементы структуризации программ 188 l$N+} Program Му15_2; Begin WriteLn(Sin(0.523 WriteLn(Pi); End.
6));
П р о д о л ж и м н а ш и п р и м е р ы . Известно, что (х/а)*а=х. Пров е р и м это у т в е р ж д е н и е с п о м о щ ь ю с л е д у ю щ е й простой программы. ($N+> Program Му15_3; Var х:Extended; i , cnt: IntegersBegin cnt:=0; For l:=20000 To 30000 Do x:=i/10000; x:=x*10000;If End; WriteLn С Число совпадений WriteLn('Число несовпадений ReadLn; End.
Begin x=i Then
Inc(cnt);
',cnt); ',10001-cnt);
Результат. Ч и с л о с о в п а д е н и й — 8 2 0 9 , число несовпаден и й — 1792. П о п ы т к а в ы в е с т и з н а ч е н и я х и i п р и несовпадения х ничего не дает. Ч и с л а равные, а есть несовпадение, т. е. различ и я в разрядах, которые не улавливаются при выводе значений п е р е м е н н ы х н а э к р а н . Они не у л а в л и в а ю т с я и д р у г и м и способам и . О д н а к о о г р а н и ч и м с я этим, главное в том, что постараемся не и с п о л ь з о в а т ь «лобовое» сравнение в е л и ч и н вещественного т и п а , а будем и с п о л ь з о в а т ь д л я этих целей к о р о т к у ю ф у н к ц и ю . Она п р и в е д е н а в с л е д у ю щ е й м о д и ф и к а ц и и п р о г р а м м ы . ($N-f } Program Му15_3; Const eps=l. 0Е-10; Var x:Extended; i,cnt:Integer;
Fu Begin Eq: -Abs (x-y) End; Begin cnt:=0;
<eps;
Часть вторал
190
For i:=20000 То 30000 Do Begin х:=i/l0000; x:=x*10000; I f Eq ( x f l ) Then Inc(cnt); End; WriteLn (' 'Число совпадений ',cnt); WriteLn С Число несовпадений ',10001-cnt); ReadLn; End. Результат работы программы — 10001 совпадение. Э к с п е р и м е н т а л ь н ы й раздел работы 1. П р и б л и ж е н н о е р е ш е н и е у р а в н е н и я f(x)=0, где f ( x ) — заданн а я ф у н к ц и я . Р е ш и т ь уравнение — з н а ч и т н а й т и такое значение х*, при котором f(х*)=0. Поиск р е ш е н и я осуществляется на интервале [а,Ъ], п р и ч е м f(a)<0, &f(b)>0. П о и с к такого инт е р в а л а — о т д е л ь н ы й вопрос, м ы его не р а с с м а т р и в а е м . Д л я р е ш е н и я и с п о л ь з у е м о д н у и з ф у н д а м е н т а л ь н ы х и д е й информ а т и к и — «разделяй и в л а с т в у й » , в д а н н о м случае «метод дел е н и я п о п о л а м » . Н а х о д и м т о ч к у с — половину о т р е з к а [а,Ь]. Е с л и f(c)>0, то г р а н и ц у Ь и з м е н я е м н а з н а ч е н и е с, а е с л и f(с)<0, то и з м е н я е м а. Процесс п р о д о л ж а е м , п о к а д л и н а инт е р в а л а не будет м е н ь ш е заданной точности.
П у с т ь f(Х)=Х2-2, Т. е. м ы п о п ы т а е м с я н а й т и з н а ч е н и е квадратного к о р н я и з 2. Решение.
($N+1 Program Му15_5; Const eps=l.ОЕ-3; Var a,b,c .-Real; Function Eq(x,y:Real) рассмотрена выше. *}
-.Boolean;
{*Функция
Процедуры и функции-—элементы структуризации программ
191
Function F(x:Real) :RealsBegin F:=Sgr(x)-2; EndsBegin WriteLn (' Введите интервал. ') ReadLn(a,b); I f F(a) *F(b)>0 Then WriteLn ('На этом интервале мы не можем найти решение уравнения.') Else Begin While Not Eq(a,b) Do Begin с: = (a+b) /2; I f F(c}>0 Then b:=c Else a:=c; End; WriteLn('Результат ' ,a); End; ReadLn; End. О б р а т и т е в н и м а н и е н а то, что в ы ч и с л е н и е f ( x ) о ф о р м л е н о как отдельная ф у н к ц и я . Использование программы для других д а н н ы х требует л и ш ь ее и з м е н е н и я . З а п у с к п р о г р а м м ы п р и р а з л и ч н ы х з н а ч е н и я х а и Ъ Вас не очень обрадует. П о л у ч а ю т с я з н а ч е н и я 1 . 4 1 4 0 6 2 5 и л и 1 . 4 1 3 5 7 4 2 1 8 7 5 (для р а з л и ч н ы х интерв а л о в а и Ь), а ф а к т и ч е с к о е з н а ч е н и е равно 1.414213... Измените з н а ч е н и е eps на 1.0Е-6. Точность в ы ч и с л е н и й имеет большое з н а ч е н и е п р и работе с т и п о м д а н н ы х Real. Исследуйте еще р я д функций с помощью приведенной программы: • х2-6х+5=0 • x-cos(x)=0 • х-1п(х)-2=0 • 2х3—9х2-60х+1=0 В о з н и к а е т вопрос, к а к о п р е д е л я т ь и н т е р в а л [а,Ь], удовлетвор я ю щ и й у с л о в и я м з а д а ч и ? А н а ч н и т е с т а б л и ц ы , вначале сос т а в л я й т е её в р у ч н у ю ( н а п р и м е р , д л я первой ф у н к ц и и ) :
Г1 "
х
Знак Цх)
0 +
2 -
3
6
1 I
После р я д а п о п ы т о к В ы вспомните, что р а з у м н а я л е н ь — д в и г а т е л ь прогресса, т а к это н а з ы в а е т с я , и автоматизируете процесс с о с т а в л е н и я т а б л и ц ы , т. е. добавите к программе еще одну процедуру.
192
Часть вторал
2. П у с т ь Ц х ) — н е п р е р ы в н а я п о л о ж и т е л ь н а я ф у н к ц и я н а отрезк е [а,Ъ] (а<Ъ). В ы ч и с л и м п л о щ а д ь ф и г у р ы , о г р а н и ч е н н у ю граф и к о м ф у н к ц и и f(x), осью Л: И п р я м ы м и х=а и х=Ь. Д л я этого р а з о б ъ ё м отрезок [а,Ъ] н а п р а в н ы х о т р е з к о в о д и н а к о в о й длин ы Ax=(b-a)/n\ a=x(0)<x(l)<...<x(i)<x(i+l )< <x(n)=b. На к а ж д о м и з о т р е з к о в [x(i),x(i+l)] к а к н а о с н о в а н и и , построим п р я м о у г о л ь н и к с в ы с о т о й f(x(i)). Площадь прямоугольников м ы у м е е м считать. Сумма п л о щ а д е й всех п р я м о у г о л ь н и к о в даст п р и б л и ж е н н о е з н а ч е н и е п л о щ а д и ф и г у р ы . О б щ а я формула snpu6,=(b~a)/n* (f(x(0)+f(x(l))+...+f(x(n-l)). Естеств е н н о , ч т о ч е м «мельче» будет разбиение, т е м точнее м ы подс ч и т а е м п л о щ а д ь ф и г у р ы . А сейчас в с п о м н и т е один и з тезисов н а ш е й р а б о т ы — «все п о д в е р г а й с о м н е н и ю , все п р о в е р я й » . Где неточность в вышеизложенном материале?
ц и ю — f(x)=x2 2.333...
н а и н т е р в а л е от 1 до 2. П л о щ а д ь ф и г у р ы р а в н а
($N+;) Program Му15_6; Const eps=l.ОЕ-3; Var a,b,s,dx:Real; i,n:Integer; Function Eg(x,y:Real):Boolean;{*Функция рассмотрена выше. *} Function F(x:Real):Real; Begin F:=Sqr(x); End; Begin WriteLn('Введите интервал и число частей, которые он разбивается'); ReadLn (a,b,n);
на
Процедуры и функции-—элементы структуризации программ х:=а; dx;=(b-a)/п; s:=0; For i: =1 То п-1 Do Begin s:=s-bF(x) WriteLn('Результат ', (b-a)/n*s); ReadLn; End.
; x:=x+dx;
193
End;
Запускаем программу при фиксированных границах интервала и р а з л и ч н ы х з н а ч е н и я х п. Результаты удручают. П р и п=6 он равен 1.5277..., п р и п=50 — 2.224992..., при п = 1 0 0 — 2.78749..., п р и п = 3 0 0 — 2.315046... Д а в а й т е и з м е н и м р е ш е н и е . Б у д е м с р а в н и в а т ь д в а соседних п р и б л и ж е н н ы х з н а ч е н и я п л о щ а д и д л я р а з л и ч н ы х п . Е с л и и х разность б о л ь ш е з а д а н н о й точности выч и с л е н и я , то у в е л и ч и м з н а ч е н и е п и снова п о д с ч и т а е м площ а д ь . Н а ч а л ь н о е з н а ч е н и е п в п р о г р а м м е в з я т о 10, ш а г изменен и я 100. Естественно, э т и п а р а м е т р ы п р о г р а м м ы в ы н е с е н ы в раздел к о н с т а н т , т а к ж е к а к и точность в ы ч и с л е н и я . Предыдущая логика оформлена как функция вычисления площади. Мы не с м е ш и в а е м з а д а ч и . Эту часть н а м известно, к а к р е ш а т ь . {$N+;I Program Myl5_6m; Const eps=l.0E-3; nb=l0;ns=l00; Var a,b,wn,ws:Real; I,n:Integer; Function Eq(x,y:Real) -.Boolean; { *Функция рассмотрена выше. *} Function F(x:Real):Real; Function Sq (n : Integer) .-Real ; Var x,dx,s:RealsBegin x:=a; dx: = (b-a) /n ;s : =0; For i:=l To n-1 Do Begin s :=s+F (x) ; x:=x+dx; End; Sq:= (b-a)/n*s; End; Begin WriteLn('Введите границы интервала'); ReadLn (a,b) ; n : =nb ; un : =Sq (n) ; Repeat ws: =un ; n : =n +ns; wn:=Sq(n) ; Until Eq (ws, wn) ; WriteLn('Результат: количество частей и значение площади',n,wn); ReadLn; End.
Часть вторал
194
П о с л е з а п у с к а п о л у ч а е м , ч т о п р и л = 8 1 0 д о с т и г а е т с я требуем а я точность вычислений, результат — 2.32654955.... Однако в у м е м ы п о д с ч и т а л и точнее. И з м е н и м т о ч н о с т ь н а 1.0Е-6. В а ш к о м п ь ю т е р з а д у м а е т с я на некоторое в р е м я , а р е з у л ь т а т л = 2 3 3 1 0 и площадь — 2.33309739... Д л я самоконтроля Вам предлагается вычислить площадь для следующих функций: f(x)=1/(1+х), [0,1]; f(:с;=1/х, [1,3]; f(x)=sin(x), [0,Pi/2]. 3. Д л я в ы ч и с л е н и я э л е м е н т а р н ы х ф у н к ц и й в м а т е м а т и к е ш и р о к о р а с п р о с т р а н е н о п р е д с т а в л е н и е э т и х ф у н к ц и й в виде нек о т о р ы х бесконечных сумм. Н е в д а в а я с ь в обоснование т а к и х представлений, приведем некоторые из них: е*=1 +х+х2/2 1 +Х1 /3.' + +х"/п' + sin (х)=х-х3/3 '+х5/5!-х7/7\ + + (-1) "х2"*1/(2п+1) '+ . cos (х) =1 -Х2/2 ! +х"/4 ! -х6/6 '+ +(-1) "х2"/ (2п) ! + 1п (1+х) =х-х2/2+х3/3-х"/4+ +(-1)"*1х"/п+ (~1<х<1) В к а ж д о м и з р а з л о ж е н и й точность п р е д с т а в л е н и я ф у н к ц и и будет, вообще г о в о р я , т е м в ы ш е , ч е м б о л ь ш е в з я т о с л а г а е м ы х в сумме. П р и ч е м з н а ч е н и я с а м и х с л а г а е м ы х с ростом п с т р е м я т с я к н у л ю . Д л я в ы ч и с л е н и я з н а ч е н и й ф у н к ц и и с н е к о т о р о й зад а н н о й точностью eps поступают следующим образом. Вычисляют и суммируют слагаемые до тех пор, пока очередное слагаемое не станет по абсолютной в е л и ч и н е м е н ь ш е eps и л и абсолютное з н а ч е н и е р а з н о с т и м е ж д у соседними с л а г а е м ы м и не станет мен ь ш е eps (что л у ч ш е ? ) . П о л у ч е н н у ю с у м м у и п р и н и м а ю т за п р и б л и ж е н н о е з н а ч е н и е ф у н к ц и и . П р о д е м о н с т р и р у е м этот метод н а п р и м е р е в ы ч и с л е н и я ф у н к ц и и sin(x). {$N+) Program Му15_7; Const eps=l.ОЕ-3; Var x,sn,ss:Real; p,n:Integer; Function Eq(x,у:Real):Boolean;{*Функция рассмотрена выше.*} Function F(n:Integer;Var x:Real):Real; Var i-.Integer; stLonglnt; Begin s:=l; For i:=2 To n Do s:=s*i; x:=x*Sqr (x); F:=x/s; End; Begin
Процедуры и функции -— элементы структуризации программ
195
WriteLn('Введите значение х ') ; ReadLn (х) ; ss : =0 ; sn :=х ; п: =1 ;р: =1 ; ( *р — для реализации чередования знака слагаемого. *) Repeat ss:=sn;(*Предыдущее значение слагаемого. *} n:=n+2;р:=-1 *р; sn : =ss+p*F (n, х) ; ( *Новое значение слагаемого.*} Until Eq (ss, sn) Or (n>=l2);(*Второе условие требуется по простой причине — факториал числа мы умеем вычислять только при этих значениях п. *} WriteLn('Результат ',sn); ReadLn; End. Известно, что s m ( 3 0 ° ) = l / 2 , sm(45°) = Sqrt(2)/2, 30°=0,5236... радиан, 45°=0,7854... радиан. Проверьте работоспособность программы. П р и к а к о й точности в ы ч и с л е н и я н а м не будет хватать н а ш и х з н а н и й по вычислению факториала числа. И з м е н и т е программу д л я в ы ч и с л е н и я других, приведенных в ы ш е ф у н к ц и й . Исследуйте р е ш е н и я . Задания для самостоятельной работы 1. Квадратное уравнение a x 2 + b x + c = 0 задается своими коэффиц и е н т а м и . Его к о р н и н а х о д я т с я по формуле X j = ( - b + S q r t (b 2 -4*a*c))/(2*a) и x 2 = = ( - b - S q r t ( b 2 - 4 * a * c ) ) / ( 2 * a ) . Написать программу н а х о ж д е н и я корней квадратного уравнения. 2. Написать программы вычисления следующих в ы р а ж е н и й : •
!/
=(l-I)(l-I)...(l-I),n
>2
• • • • „
у = cos(l + cos(2+...+ cos(39 + cos40)...)) у = sin х + sin sin x+...+ sin sin... sin x(rt раз) у = sin x + sin x2 +...+ sin x" у = s i n * + sin 2 *+...+sin" x • у = c o s l c o s l + cos2 К„ cos 1 + К + cosn s i n l s i n l + sin2 s i n l + Л + sinn • У = \х\ + х' • y = |x| + 4 - * 3 - 1 - х 2 + 3-xB
• y = \x-2\
• y = 6 b2 + | b - 3 f 3
-15
• y = ( 3 • * + 18 • x2) • x + 12 • x2 - 5 • y = (d + с + b) • e - б • k - 1
A
Часть вторал
196
3
• у = 3 с +|с
2
3
- 4 с + 7| - 5 с
• i/ = ]* + 4 | - | * 2 - 3 • * + б| 3. Вычислить приближенное значение бесконечной суммы: • J 1-2
•г-,
1
+
+
J _ + - L + ... 2 3 3-4 1
+
1
+ ...
1-3 2-4 3 5 1 1 1 + 1 2 3 2 3-4 3 4 5 4. Написать программы вычисления i - I + !_... + _ i L_ 2 3 9999 10000 следующими ч е т ы р ь м я способами: • последовательно слева направо; • последовательно слева направо вычисляются + 3 + " ' + 9999 И 2 + 4 + " ' + 10000' затем второе значение вычитается из первого; • последовательно справа налево; • последовательно справа налево вычисляются суммы (как во втором случае), а затем выполняется вычитание. Сравните результаты и найдите их разумное объяснение. 5. Рассмотрим бесконечную последовательность чисел у1, у2, у3, ... , образованную по следующему закону:
=
х + т. - 1 2
1 х yt = —((т. - l)yi.x + ——), (i=2, 3, ...), где х — данное действительное число, т — натуральное число. Эта последовательность позволяет получить сколь угодно точные приближен и я числа %[х(х > 0). Составить программу д л я вычисления значения %Гх с заданной точностью eps. 6. Дано п вещественных чисел. Написать программы: • вычисления разности между максимальным и минимальным элементами; • определения количества элементов, которые больше своих «соседей», то есть предыдущего и последующего чисел;
Процедуры и функции -— элементы структуризации программ
197
• определения минимального значения разности двух произвольных чисел. 7. Представим обыкновенную дробь — (т < п) в виде цепной т « — дроби = п
„
,
1 1
П . тт Например, 5— = 7 ,
1 1
.
Процесс преобразования заключается в следующем: 5 / 7 = 1 / (7/5)=1/(1+2/5)=1/(1+1/(5/2))=1/(1 +1/(2+1/2)). Даны числа а2, а2 а к , описывающие цепную дробь. Написать программу вычисления значения цепной дроби. Написать программу вычисления выражения (цепной дроби) 1
4'
1011 — 103 9. Перевод десятичных дробей в двоичное представление выполняется несколько иначе, чем целых чисел. Пусть есть дробь 0.375 1 0 . Схема перевода: 0.375*2 = 0.75, 0.75*2 1.5, 0.5*2 = 1.0, двоичное представление дроби 0.375 10 имеет вид: 0.011 2 . Жирным шрифтом выделены те цифры, которые идут в двоичное представление дроби. После каждого умножения на 2 мы получаем очередную цифру в двоичной записи дроби (почему?). Всегда ли процесс умножения конечен? На какой итерации следует заканчивать перевод, если Вам дано m разрядов для представления дроби? Написать программу перевода действительного числа а (0<а<1) в двоичную систему счисления. Материал для чтения 1. Продолжим знакомство с кодированием. Рассмотрим несколько схем построения двоичных кодов для заданного множества сообщений. Пусть имеется пять сообщений а, Ь, с, d, е, они передаются по каналу связи с вероятностями 0.3, 0.24, 0.21, 0.15 и 0.1. Закодируем их по методу американского математика Р. Фано.
Часть вторал
198
Сообщение
Вероятность
шаг
1-й
2-й шаг
шаг
3-й
Код
а
03
0
00
-
00
b
0 24 0 21
0
01
-
1 1
10
-
10
11
1
11
110 111
110 111
с 1
d
е
0 15 0 1
01
Что мы сделали? На первом шаге произвели разбивку всего множества сообщений на две группы так, чтобы суммарные вероятности сообщений в группах были как можно ближе друг к другу. Первая группа — сообщения а и Ь, вторая группа — сообщения с, d, е. Сообщениям первой группы приписали 0, второй — 1. Процесс продолжается в каждой группе до тех пор, пока в очередной группе не останется одно сообщение. Почему именно такой принцип разбивки на группы? Вспомните игру «Бар — Кохба». Разряд, получаемый на первом шаге, «несет» в себе максимальное количество информации. Точно так же на втором и третьем шагах. Итак, очередной раз мы встречаемся с фундаментальной идеей информатики — деления пополам («разделяй и властвуй»). При равномерном кодировании для пяти сообщений требуется три разряда, т. е. длина кода равна трем. Подсчитаем среднюю длину кода Р. Фано — L =1,*р 1 +1 2 *р 2 +1 3 *р 3 +1 4 *р 4 +1 5 *р 5 = 2*0.3 + 2*0.24 +2*0.21 + 3 * 0 . i 5 +3*0.1=2.25. Итак, получен выигрыш.
Структура данных, изображенная на рисунке, называется двоичным деревом. Черными кружками выделены узлы дерева. Отрезки, соединяющие узлы, называют ребрами. Ребрам приписаны цифры 0 и 1 так, к а к сделано в схеме кодирования. Узлы, из которых не выходят ребра (вниз), называют ли-
Процедуры и функции -— элементы структуризации программ
199
с т ь я м и . И т а к , к а ж д о м у листу соответствует сообщение, а путь к нему от к о р н я (верхнего узла), при выписывании соответствую щ и х цифр, дает код этого сообщения. Обратим внимание на одну особенность кодирования. Кодам соответствуют листья дерева, т.е. ни один код не является началом другого. Такие коды называются префиксными. Код Р. Фано — префиксный код. Проверьте. Если бы некоторому коду соответствовала внутренняя вершина дерева, то это условие не выполнялось бы. Пусть есть последовательность кодовых слов, записанных подряд 0 1 0 1 1 1 1 1 0 1 1 1 1 1 1 1 0 1 0 1 1 0 0 0 . Попробуем декодировать это послание. Просматриваем последовательность слева направо и п ы т а е м с я выделить кодовое слово. 0 не является кодом, 01 — я в л я е т с я , выделяем и отбрасываем. Продолжаем процесс: 0 1 I 0 1 I 111 I10 I 111 I 111 1 1 0 I 1 0 I 1 1 0 I 0 0 (символ I используем к а к разделитель). Сообщение декодировано — bbeceeccda. 2. Рассмотрим схему кодирования по К. Шеннону. Пусть есть п сообщения а : , а 2 , ... ад, которые появляются с вероятностями p j , р 2 , ... р п . Вероятности сообщений даются в порядке возрастания — p t < р 2 < р 3 < ... < р п . Находятся частичные суммы вида S ^ Pj + р 2 + ...+ р1 д л я всех i = l , 2, ...п. Затем определ я ю т с я значения такие, что 2~ш < p t < 2" n l + 1 . Частичные суммы S t представляют в виде двоичной дроби 0.d 1 d 2 d 3 d 4 ... Первые ц и ф р дроби дают кодовое слово д л я сообщения а ; . Значение S n равно 1 (полная группа сообщений, какое-нибудь да присутствует в передаче). Удобнее взять в схеме Шеннона S n =0.999... П р е ж д е чем рассмотрим пример, напомним некоторые факты из ранее рассмотренного материала. Значения отрицательных степеней двойки равны: 2~1=0.5, 2" 2 =0.25, 2" 3 =0.125, 2" 4 = = 0 . 0 6 2 5 , 2" 5 = 0.03125. Тогда для дроби 0.1 выполняются неравенства 2" 4 < 0 . 1 < 2~3 и значение равно 4. Правило перевода дробей в двоичное представление сводится к последовательному умножению на 2 и выделению целых частей результатов умн о ж е н и я . Переведем 0.1 1 0 : 0.1*2=0.2, 0.2*2=0.4, 0.4*2=0.8, 0.8*2=1.6, 0.6*2=1.2, 0.2*2=0.4. Двоичное представление — 0.000110... 2 . Постройте двоичное дерево д л я полученного кода (код префиксный). Отбрасывание л и ш н и х ребер приводит к усеченному коду. Подсчитаем среднюю длину — L c p .=2*0.1 +3*0.15 +3*0.21 +2*0.24 + 2*0.3=2.36. Этот результат лучше, чем при равномерном кодировании сообщений.
Часть вторал
£ 0 1
а.
-
п,
Усеченный
0 S 1 S,
Код
Вероятность
Сообщение
!
200
ООО а
4
0 1
Ь
0 15
I
С
0 21
I
d
0 24
е
0 3
3
00
010
010
011
011
101
10
11
11
0 01000
0 25 3
00111
0.46 3
0 1010
0 70 2 1 00(0 99)
0001 0 00011
0 10
0 1111
3. Рассмотрим еще одну схему построения кода. Ее предлож и л американский математик Д. Хаффмен (1953 г.). Получаемый в результате построения код является оптимальным, т. е. не существует кодов с меньшим значением средней длины. Пусть сообщения расположены в порядке убывания вероятностей и х появления. Построение кода состоит и з двух этапов. На первом сообщения с наименьшими вероятностями объединяются в одно. Получается новое множество сообщений. Осуществляется его перегруппировка так, чтобы опять их вероятности находились в порядке убывания. Процесс, который называют сжатием, продолжается до тех пор, пока не останется два сообщения. Первому из оставшихся сообщений приписываем 0, второму — 1, и начинается процесс расщепления. Его суть. Если сообщение получено объединением двух в одно, то оно расщепляется на два, к текущему коду первого приписывается 0, второго — 1. Процесс продолжается до тех пор, пока мы не дойдем до первоначального множества сообщений. В результате будет получен код исходного множества сообщений. Д л я примера, описанного в предыдущих пунктах материала д л я чтения, схема построения кода Хаффмена приведена в таблице. Проверьте её. Убедитесь, что построенный код является префиксным, а его средняя длина равна 2.25.
201
Процесс расщепления
Процесс сжатия
Вероятность
Сообщение
Процедуры и функции -— элементы структуризации программ
а
03
03
0 45
0 55
Ь
0 24
0 25
03
с
0 21 0 15
0 25 -
0 45 -
d е
0 24 021
0 1
-
-
0 1 -
1
00
00
01 10 11 -
-
-
01 -
-
-
-
00 10 11 010 011
202
Часть вторал
З а н я т и е № 16. Т е к с т о в ы е ф а й л ы План занятия: • файловый тип данных; • операторы работы с файловым типом данных; • короткие программы иллюстрации работы с текстовыми файлами; • экспериментальная работа с программами: подсчета частоты записи целых чисел в файле; правильности расстановки скобок в тексте программы; подсчета количества строк в тексте; • выполнение самостоятельной работы. Файловый тип данных. Данные, размещаемые программой в памяти компьютера, исчезают при выключении питания. Для долговременного хранения данных используются внешние носители, в частности магнитные диски. Единицей хранения информации на диске служит файл. Файл имеет имя, но это имя не Турбо Паскаля, а операционной системы, под управлением которой работает Турбо Паскаль. С другой стороны, этот файл с точки зрения Турбо Паскаля является обычной переменной, иначе бы с ним нельзя было работать, и как любая переменная, он имеет имя. Это другое имя, это имя Турбо Паскаля, рассматривающего файл как переменную. Естественно, чтобы не было «мешанины», должны быть средства установки соответствия между именами. Еще раз. В Турбо Паскале мы работаем с одним именем. Операционная система, фактически осуществляющая чтение и запись на диск, работает с другим именем. Следующий основополагающий момент заключается в том, что с каждым файлом в конкретный момент времени можно работать или в режиме чтения, или в режиме записи. Одновременно нельзя, т. е. мы или читаем, или записываем — другого не дано. Для того чтобы, например, записать в файл, открытый для чтения, необходимо закончить работу с файлом в режиме чтения, а затем начать работу с этим файлом в режиме записи. В рамках данного занятия мы ограничимся работой с текстовыми или последовательными файлами. Текстовые файлы хранят информацию в виде последовательности символов. Символы составляют строки произвольной длины. В конце каждой строки записываются два особых символа: # 1 3 # 1 0 (возврат каретки, перевод строки), которые отделяют строку от следующей. С текстовыми файлами, однако, мы работали, начиная с первого занятия, правда, не говоря об этом. Есть стандартные файлы ввода и вывода. Они связаны с клавиатурой и дисплеем. Имена
Процедуры и функции-—элементы структуризации программ 202
э т и х ф а й л о в в с и с т е м е Турбо П а с к а л ь ( ф а й л о в ы е п е р е м е н н ы е ) Input и Output, и х и м я в о п е р а ц и о н н о й системе Dos —- Con ( к о н с о л ь ) . Т а к и м о б р а з о м , п р и з а п и с и операторов Read и л и Write ф а к т и ч е с к и о с у щ е с т в л я л о с ь ч т е н и е и з с т а н д а р т н о г о файл а ввода с к л а в и а т у р ы и л и запись в стандартный файл вывода, с в я з а н н ы й с д и с п л е е м . Е с т е с т в е н н ы й вопрос. П о ч т и все в р е м я о с у щ е с т в л я л с я ввод и в ы в о д ц и ф р о в о й и н ф о р м а ц и и . О к а з ы в а е т с я , п р и ч т е н и и и з а п и с и ч и с е л и х п р е о б р а з о в а н и е в строков ы й т и п и наоборот в ы п о л н я е т с я а в т о м а т и ч е с к и п р и работе с т е к с т о в ы м и ф а й л а м и , a Input и Output — т е к с т о в ы е ф а й л ы . Операторы работы с файловым типом данных. В прог р а м м е н а Турбо П а с к а л е т е к с т о в ы й ф а й л п р е д с т а в л е н файл о в о й п е р е м е н н о й т и п а Text: Var <имя файловой переменной>: Text;. С в я з ь ф а й л о в о й п е р е м е н н о й с д и с к о в ы м и м е н е м ф а й л а осущ е с т в л я е т с я с п о м о щ ь ю о п е р а т о р а Assign(имя файловой переменной, дисковое имя файла). Второе и м я больше нигде в прог р а м м е не п о я в л я е т с я . О т к р ы т и е ф а й л а д л я ч т е н и я в ы п о л н я е т с я о п е р а т о р о м Reset(<файловая переменная>), для записи — ReWrite(<файловая переменная>), д л я п о п о л н е н и я — Append (файловая переменная). О к о н ч а н и е р а б о т ы с ф а й л о м фиксируе т с я с п о м о щ ь ю о п е р а т о р а Close(<файловая переменная>). П р и м е р оформления работы с текстовым файлом: Var f:Text; s: StringsBegin Assign(f,'a:\data.txt');{*Файл с именем data, находится на диске с логическим именем а. *} Reset(f);{*Файл открывается для чтения. *} ReadLn(f,s);{*Читаем одну строку из файла. *) Close End.
txt
( f );
Т е к с т о в ы й ф а й л д л я оператора ReadLn т а к о й ж е и с т о ч н и к д а н н ы х , к а к и к л а в и а т у р а . Однако в этом случае д а н н ы е следуют д р у г з а д р у г о м не во в р е м е н и , к а к п р и вводе с к л а в и а т у р ы , а в и з м е р е н и и ф а й л а . Е с л и ф а й л представить в виде н е к о й лин е й к и , то единственное р а з р е ш е н н о е д в и ж е н и е от левого к о н ц а л и н е й к и в п р а в о и, чтобы попасть н а 10-й сантиметр, необходим о о б я з а т е л ь н о п р о й т и через все п р е д ы д у щ и е с а н т и м е т р ы . К а к во в р е м е н и , т а к и в и з м е р е н и и ф а й л а м о ж н о п е р е м е щ а т ь с я тол ь к о в одну сторону. Д о п у с к а е т с я л и ш ь последовательное чте-
Часть вторал
204
н и е и з ф а й л а . М ы не м о ж е м в н а ч а л е п р о ч и т а т ь 1 0 0 - ю с т р о к у , а з а т е м 10-ю. П о с л е д о в а т е л ь н ы й ф а й л , к а к л е н т а м а г н и т о ф о н а . П р и этом, к о н е ч н о , м е ж д у э л е м е н т а м и д а н н ы х ф а й л а о б я з а н ы б ы т ь р а з д е л и т е л и , ибо д а н н ы е ф а й л а м о г у т б ы т ь р а з н о й д л и н ы и ф а й л о б я з а н з а к а н ч и в а т ь с я к а к и м - т о п р и з н а к о м к о н ц а файл а . А к а к и н а ч е з а к о н ч и т ь ч т е н и е ? В Турбо П а с к а л е есть специа л ь н а я ф у н к ц и я Eof (<файловая переменная>), к о т о р а я возв р а щ а е т з н а ч е н и е True, е с л и д о с т и г н у т к о н е ц ф а й л а , и False в противном случае. Короткие примеры. Начнем со с в е р х п р о с т о г о п р и м е р а . Вводим данные (строки) из ф а й л а и выводим их н а экран. Program Му16_1; Var f:Text; s:String; Begin Assign(f,'Input.Txt'); Reset(f); While Not E o f ( f ) Do ReadLn ( f , s) ; Wri t eLn (s) ; End; Close ( f ) ; End.
Begin
З а п у с к п р о г р а м м ы даст о ш и б к у Error 2: File not found. Ф а й л а с и м е н е м Input.Txt нет н а д и с к е , его н е о б х о д и м о создать. Открываем новый файл, записываем строки А ВЬ Ссс Dddd Еееее Ffffff Ggggggg
и с о х р а н я е м ф а й л с и м е н е м Input.Txt. П о с л е з а п у с к а м ы в и д и м н а э к р а н е п р а в и л ь н ы й р е з у л ь т а т , но т о л ь к о п р и одном у с л о в и и . В а ш а п р о г р а м м а и ф а й л Input.Txt н а х о д я т с я н а одном и том ж е д и с к е и в одной д и р е к т о р и и (каталоге). И з м е н и т е т е к у щ и й кат а л о г — р е ж и м File/Change Dir — и в ы б е р и т е другой к а т а л о г . З а п у с т и т е п р о г р а м м у . О п я т ь з н а к о м а я о ш и б к а — Error 2: File not found. В а ш а программа находится в одном каталоге, а ф а й л Input.Txt в другом. Перепишите ф а й л Input.Txt в тот каталог, где находится В а ш а программа. Запустите. Результат нормальный.
Процедуры и функции-—элементы структуризации программ
205
И з м е н и т е программу. Вместо ReadLn(f,s) напишите Read(f.s). Результат п л а ч е в н ы й . П р и х о д и т с я использовать Ctrl+Break, чтобы о с т а н о в и т ь п р о г р а м м у , она « з а ц и к л и л а с ь » . П р и ч и н а — не м о ж е т н а й т и п р и з н а к к о н ц а ф а й л а . Откройте окно Debug /Watch, введите и м я п е р е м е н н о й s и в ы п о л н и т е п р о г р а м м у в п о ш а г о в о м р е ж и м е . Первую строку м ы благополучно вводим и выводим, а з а т е м и д у т п у с т ы е с т р о к и . П о п р о б у й т е д а т ь о б ъ я с н е н и е . Обрат и т е в н и м а н и е н а неудобство р а б о т ы . П р и х о д и т с я все в р е м я исп о л ь з о в а т ь F6 д л я того, ч т о б ы п е р е й т и от одного о к н а к другом у . В ы з а б ы л и материал з а н я т и я № 3 , работу с окнами. Измените р а з м е р ы о к о н и п е р е м е с т и т е и х т а к , чтобы о н и не н а к л а д ы в а лись друг на друга. Структура вашего экрана должна иметь вид:
окно
окно
Input.Txt
окно
Watches
My16_1.Pas
А м о ж н о л и читать посимвольно? Изменим программу. Program Му16_1т, Var f-.Text; ch-.CharsBegin Assign(f,'Input.Txt'); Reset ( f ) ; While Not E o f ( f ) Do Begin Read ( f , c h ) ; Write(ch); EndsClose ( f ) ,End.
Часть вторал
206
Результат правильный. Но если Вы измените Read(f,ch); Write(ch); н а Read(f,s); Write(s); где s и м е е т т и п String, то результат опять о т р и ц а т е л ь н ы й — «зацикливание». Осознайте разницу. Найдите еще варианты, при которых Ваша программа будет « з а ц и к л и в а т ь с я » . П р о в е д и т е о ч е р е д н о е и з м е н е н и е п р о г р а м м ы . Ее в и д : Program Myl6_1тт; Var f:Text; ch:Char; Begin Assign (Input,'Input.Txt'); Reset (Input); While Not Eof Do Begin Read (ch); Write (ch) ; End; Close (Input); End. З а п у с к п р о г р а м м ы дает п о л о ж и т е л ь н ы й р е з у л ь т а т . Ч т о м ы с д е л а л и ? П е р е о п р е д е л и л и с т а н д а р т н ы й ввод с к л а в и а т у р ы н а ввод и з н а ш е г о ф а й л а н а д и с к е . Ф а й л о в у ю п е р е м е н н у ю в этом с л у ч а е м о ж н о не з а п и с ы в а т ь в о п е р а т о р а х Read и Write. Добавьте после о п е р а т о р а Close(Input) о п е р а т о р ы ReadLn(s); WriteLn(s), где s и м е е т с т р о к о в ы й т и п д а н н ы х . З а п у с к п р о г р а м м ы п р и в о д и т к о ш и б к е Error 104: File not open for input ( ф а й л не о т к р ы т д л я ввода). В с т а в и м после Close(Input) о п е р а т о р Reset (Input). Программа заработала. Однако вместо ввода с клав и а т у р ы с т р о к и s м ы и м е е м ввод п е р в о й с т р о к и и з ф а й л а Input.Txt. К а к в е р н у т ь с я к вводу с к л а в и а т у р ы ? Продолжим рассмотрение примеров. Запустите программу. Program Му16__2; Uses Crt; Const MaxN=100; Type MyArray=Array[1.. MaxN] Of Var A:MyArray; i,j:Integer; Begin ClrScr; Assign (Input,'Input.Txt'); Reset (Input); i: =0 ; While Not Eof Do Begin
Integer;
Процедуры и функции-—элементы структуризации программ Inc (i) ; Read (A[i ] ) ; End; Close(Input); For j:=l To l Do Write End.
(A [ j ] : 2) ;
Р е з у л ь т а т т р а д и ц и о н н ы й — о ш и б к а Error ric format ( н е п р а в и л ь н ы й ч и с л о в о й формат). ровой м а с с и в А, с и с т е м а преобразует символ не соответствует ч и с л у и к а к следствие — ф а й л Input.Txt на 1 2 3 4 5
207
106: Invalid nume М ы вводим в цифв ч и с л о , но символ ошибка. Изменим
2 3 3 4 4 4 5 5 5 5
П р о г р а м м а работает п р а в и л ь н о . В массиве А эти с а м ы е числа. Изменим файл Input.Txt. 1 2 3 4 5
22 3 333 4 4 4444 5 5 5 55555
Все п о ч т и п р а в и л ь н о . И последние з а п и с и в с т р о к а х правил ь н о п р е о б р а з о в а л и с ь в ч и с л а , правда, к р о м е 55555. Вместо этого ч и с л а в ы в о д и т с я нечто странное - 9 9 8 1 . Е с л и Вы хорошо усвоили м а т е р и а л п р е д ы д у щ и х з а н я т и й , то суть о ш и б к и очевидна. Е с л и нет, то перед п р о г р а м м о й вставьте {$R+} и проанал и з и р у й т е о ш и б к у . В е р н е м с я к посимвольному вводу из ф а й л а . Program Му1б_2т; Uses Crt; Const MaxN=100; Type MyArray=Array[1..MaxN] Var Л:MyArray; l,j,k:Integer; ch: CharsBegin ClrScr; Assign(Input,'Input.Txt'); R e s e t (Input,! ; l: =0 ;
Of
Integer;
Часть вторал
208 While Not Eof Do Begin Read(ch); {Val (ch,j ,k) ;) ( I f k=0 Then Begin) Inc(i); A [ l ] : =Ord (ch) -0rd('0') ; (End;) End; Close(Input); For j:=l To l Do Write (A[j] : 6) ; End.
Результат — странная последовательность: 1 - 3 5 - 3 8 2 - 1 6 2 2 -35 -38 3 но с о п р е д е л е н н о й з а к о н о м е р н о с т ь ю . Объясн и т е ее. У б е р и т е ф и г у р н ы е с к о б к и и в н о в ь з а п у с т и т е программ у . Р е з у л ь т а т : 1 2 2 2 3 3 3 3 3 . . . И з м е н и т е п р о г р а м м у т а к , чтобы р е з у л ь т а т все ж е б ы л п е р в о н а ч а л ь н ы м 1 2 22 3 3 3 3 3 ...., но ввод о с у щ е с т в л я л с я п о с и м в о л ь н о . А м о ж н о л и т а к и м ж е образом в в о д и т ь в е щ е с т в е н н ы е ч и с л а ? В п р о г р а м м е Му16_2 измен и т е Type MyArray=Array[l..MaxN] Of Real; и п р о в е д и т е аналогичные эксперименты, естественно, изменив при этом и файл Input.Txt. В следующем примере показано переопределение выходного ф а й л а . П о с л е з а п у с к а п р о г р а м м ы н а э к р а н е м о н и т о р а ничего нет. Н е о б х о д и м о о т к р ы т ь ф а й л Output.Txt. Р а з м е с т и т е н а экране все т р и ф а й л а Му16_3, Input.Txt, Output.Txt (после первого п р о г о н а п р о г р а м м ы н а д и с к е в т е к у щ е м к а т а л о г е будет создан выходной файл) так, чтобы они были одновременно обозримы. И з м е н и т е н е с к о л ь к о р а з ф а й л Input.Txt, при этом запуская программу. Program Myl6_3; Uses Crt; Var ch:Char ; Begin ClrScr; Assign(Input,'Input.Txt'); Reset Assign(Output,'Output.Txt'); While Not Eof Do Begin Read(ch); Write(ch) End; Close(Input); Close(Output); End. После строки с операторами Close( Input); вьте с л е д у ю щ и е о п е р а т о р ы .
(Input); Rewrite(Output);
Close (Output);
вста-
Процедуры и функции-—элементы структуризации программ
209
Assign(Input,'Con'); Assign(Output,'Con'); Reset (Input); Rewrite(Output); ReadLn (s) ; WriteLn (s); Е с л и п р о г р а м м а будет что-то ж д а т ь , то наберите произвольн ы й т е к с т н а к л а в и а т у р е и н а ж м и т е к л а в и ш у Enter. М ы научил и с ь с В а м и в о з в р а щ а т ь с я к с т а н д а р т н о м у вводу и выводу данных. Экспериментальный раздел работы 1. И з в е с т н о , ч т о в ф а й л е з а п и с а н ы ц е л ы е ч и с л а , н а п р и м е р из д и а п а з о н а от - 1 0 0 до 100. Н е о б х о д и м о подсчитать, с к о л ь к о раз к а ж д о е ч и с л о в с т р е ч а е т с я в ф а й л е . Д л я р е ш е н и я з а д а ч и требуется создать ф а й л с ц е л ы м и числ а м и . П о п р о б у е м это сделать с п о м о щ ь ю с л е д у ю щ е й программы. Program Const
Му16__4 ; MaxN=10000; а=100; Ъ=2С0; с=15; Var 1,j , к:Integer; Begin Randomize; Assign(Output,'Number.Txt'); Rewrite(Output); l:=0; к:=0; While KMaxN Do Begin j:=Integer(Random(b))-a; Inc ( l ) ;Inc (k) ; Write ( j ) ; Write (' ' ) ; I f k=c Then Begin WriteLn;k:=0;End; End; Close(Output); End.
Она работает. Ф а й л Number.Txt создается в т е к у щ е м каталоге. Он с о д е р ж и т MaxN ц е л ы х чисел, р а з б и т ы х н а строки по с ч и с е л в к а ж д о й строке. М о ж н о л и з а п и с а т ь в ф а й л 1 0 0 0 0 0 чисел? Простое и з м е н е н и е к о н с т а н т ы MaxN п р и в о д и т к з а ц и к л и в а н и ю . П р и ч и н а т р а д и ц и о н н а я , м ы з а б ы л и и з м е н и т ь т и п данн ы х у п е р е м е н н о й i н а Longlnt. После каждого числа в файл
210
Часть вторал
з а п и с ы в а е т с я символ пробела и после с чисел осуществляется п е р е х о д н а н о в у ю с т р о к у . О б я з а т е л ь н о л и с и е ? И з м е н и м прог р а м м у , в о з ь м и т е в ф и г у р н ы е с к о б к и т е к с т {Write(' '); If k=c Then Begin WriteLn;k:=0;End;} программного кода. Программа р а д у е т н а с н о в ы м и с о о б щ е н и я м и , о н и в ы д а ю т с я н а с т а д и и вып о л н е н и я п р о г р а м м ы и о ф о р м л е н ы н е с к о л ь к о и н а ч е , ч е м прос т ы е с и н т а к с и ч е с к и е о ш и б к и ( о б р а т и т е н а этот ф а к т в н и м а н и е ) . П е р в о е с о о б щ е н и е : Error. Line too long, truncated (строка о ч е н ь б о л ь ш а я , о н а о к р у г л я е т с я ) ; второе с о о б щ е н и е , но у ж е не к а к о ш и б к а , а к а к п р е д у п р е ж д е н и е : Warning. Error encountered reading file NUMBER.TXT (ожидается о ш и б к а п р и ч т е н и и файла). Вернемся к исходному тексту программы, а в качестве у п р а ж н е н и я п о п р о б у й т е с ф о р м и р о в а т ь ф а й л , у к о т о р о г о и количество чисел в строке формируется с л у ч а й н ы м образом. В т о р а я ч а с т ь з а д а ч и р е а л и з у е т с я с п о м о щ ь ю с л е д у ю щ е й программы. Program Му16_5; Uses Crt; Const MaxN=l00;c=20; Type MyArray=Array[-MaxN..MaxN] Of Integer; Var A:MyArray; l,3:Integer; Begin ClrScr; FillChar(A,SizeOf(A),0);{*Подсчитываем, сколько раз каждое число встречается в файле. Число используется как индекс к элементу массива А. Обратите на этот факт особое внимание. Это «принцип косвенной адресации» — одна из ключевых идей информатики, реализован как на уровне аппаратуры компьютера, так и в любых системах программирования. *) Assign(Input,'Number.Txt'); Reset (Input); While Not Eof Do Begin Read(i);Inc(A[i]);End; Close (Input); Assign(Output,'Count.Txt');{*3аписываем результат в файл Count.Txt, не разбивая на строки. *) Rewrite(Output); For i:=-MaxN To MaxN Do Write (A [ i ] , ' ' ) ; Close(Output);
Процедуры и функции-—элементы структуризации программ Assign(Input,'Count.Txt');{*Открыв Count.Txt для чтения.*} Reset(Input); Assign(Output,'Con');{*Результат экран. *1 Rewrite(Output); j:=0; While Not Eof Do Begin Read (1} ;Inc ( j ) ; Write (1,' '); I f j=c Then Begin на строки, иначе трудно End; End.
а ем
выводим
211 файл
на
WriteLn;j:=0;End;{*Разбиваем просматривать.*}
М е т о д и ч е с к о е з а м е ч а н и е На экране должны быть обозримы все файлы: с первой и второй программами; Number.Txt и Count.Txt. В п р о г р а м м е , х о т я она и работает, с о д е р ж и т с я я в н а я неточность. У с т р а н и т е ее. В с п о м н и т е , что В а ш и п р о г р а м м ы д о л ж н ы р а б о т а т ь п р и всех д о п у с т и м ы х и с х о д н ы х д а н н ы х . Предполож и м , ч т о д а т ч и к с л у ч а й н ы х ч и с е л д а л сбой и с г е н е р и р о в а л 1 0 0 0 0 р а з одно ч и с л о . Ч т о п р о и з о й д е т в этом с л у ч а е ? П о д с ч и т а т ь , с к о л ь к о раз в с т р е ч а е т с я к а ж д ы й с и м в о л в соотв е т с т в и и с к о д и р о в к о й ASCII в н е к о т о р о м текстовом ф а й л е . Н е о б х о д и м о и з м е н и т ь р е ш е н и е з а д а ч и т а к , чтобы она работ а л а и с в е щ е с т в е н н ы м и ч и с л а м и . Е с л и создание ф а й л а Number.Txt не в ы з ы в а е т з а т р у д н е н и й , то л о г и к а подсчета ч а с т о т ы повторяемости каждого числа «расползается». Использовать в е щ е с т в е н н о е ч и с л о в к а ч е с т в е и н д е к с а н е л ь з я , к о с в е н н а я адрес а ц и я не работает. Т р е б у е т с я брать б л и ж а й ш е е целое число, но это у ж е д р у г а я з а д а ч а . В этом случае в е щ е с т в е н н ы е ч и с л а из единичного интервала представлены одним целым числом. А е с л и у м е н ь ш и т ь и н т е р в а л ? Попробуйте исследовать эту проблему. Создается ф а й л с не очень б о л ь ш и м к о л и ч е с т в о м слов. З а т е м ф о р м и р у е т с я м а с с и в и з э т и х слов. С л у ч а й н ы м образом из масс и в а в ы б и р а е т с я слово и з а п и с ы в а е т с я в другой ф а й л , это действие в ы п о л н я е т с я 1 0 0 0 0 раз. После того, к а к ф а й л сформирован, Вам необходимо р е ш и т ь аналогичную задачу — подсчитать, с к о л ь к о раз в с т р е ч а е т с я к а ж д о е слово и результат з а п и с а т ь в третий файл.
212
Часть вторал
2. Д а н т е к с т о в ы й ф а й л , с о д е р ж а щ и й п р о г р а м м у на я з ы к е Турбо П а с к а л ь . П р о в е р и т ь п р а в и л ь н о с т ь р а с с т а н о в к и с к о б о к (кругл ы х , к в а д р а т н ы х , ф и г у р н ы х ) в т е к с т е этой п р о г р а м м ы . Приведем простые п р и м е р ы д л я п о я с н е н и я сути задачи: • (()) — с к о б к и в с т р о к е р а с с т а в л е н ы п р а в и л ь н о ; 1=1 {(] } — с к о б к и в с т р о к е р а с с т а в л е н ы н е п р а в и л ь н о ; Q ({[}]) — с к о б к и в с т р о к е р а с с т а в л е н ы н е п р а в и л ь н о . Между скобками допустима запись любых символов. Итак, е с л и в с т р е ч а е т с я о д н а и з з а к р ы в а ю щ и х с к о б о к , то п о с л е д н я я о т к р ы в а ю щ а я д о л ж н а б ы т ь т а к о г о ж е т и п а . Это о с н о в н о й к р и т е р и й п р о в е р к и п р а в и л ь н о с т и р а с с т а н о в к и с к о б о к в т е к с т е прог р а м м ы . С о з д а д и м п р о с т у ю р а б о т а ю щ у ю п р о г р а м м у д л я пров е р к и работоспособности предполагаемого р е ш е н и я задачи. Program Myl6_sk; Begin WriteLn (y(*9 ( ( ( ) ) ) [ [ [ [ ] ] ] ] * } ' ) ; End. Д а л е е в ы я с н и м , к а к о т л и ч а ю т с я к о д ы (по A S C I I ) р а с с м а т р и в а е м ы х в з а д а ч е с к о б о к . Этот ф а к т у с т а н а в л и в а е т с я с п о м о щ ь ю следующей простой программы. Program Му16_р; Uses Crt; Begin ClrScr; WriteLn (OrdC ( ' ) ) ; WriteLn 40, 41.*} WriteLn (OrdC [ ' ) ) / WriteLn 91, 93. *} WriteLn (OrdC ( ' ) ) ; WriteLn 123, 125.*} ReadLn; End.
(OrdC
) ' ) ) ; { *Ответ:
(OrdC
]')) ;
(*Ответ:
(OrdC
}') ) ;
(*Ответ:
И т а к , р а з н о с т ь к о д о в д л я с о о т в е т с т в у ю щ и х д р у г д р у г у скобок н е п р е в ы ш а е т з н а ч е н и я 2. С л е д у ю щ и й м о м е н т . Х р а н и т ь в е с ь т е к с т и з ф а й л а в п а м я т и н е ц е л е с о о б р а з н о , он м о ж е т б ы т ь о ч е н ь б о л ь ш и м , д а это и н е т р е б у е т с я в з а д а ч е . Н е о б х о д и м о хран и т ь т о л ь к о о т к р ы в а ю щ и е с к о б к и . О п р е д е л и м д л я этой ц е л и м а с с и в с т и п о м э л е м е н т о в Char и п р е д п о л о ж и м , ч т о у р о в е н ь в л о ж е н н о с т и с к о б о к не п р е в ы ш а е т 1 0 0 . Е с л и о ч е р е д н о й с ч и т а н н ы й и з ф а й л а с и м в о л — о т к р ы в а ю щ а я с к о б к а , то она з а п и с ы в а е т с я в м а с с и в . П р и о б н а р у ж е н и и з а к р ы в а ю щ е й с к о б к и срав-
Процедуры и функции-—элементы структуризации программ
213
н и в а е т с я её к о д с к о д о м последней с к о б к и , з а п и с а н н о й в массив. Е с л и з н а ч е н и е р а з н о с т и б о л ь ш е 2, то ф и к с и р у е т с я о ш и б к а в р а с с т а н о в к е с к о б о к , и н а ч е с к о б к а у д а л я е т с я и з м а с с и в а , и просмотр текста продолжается. ($R+) Program Му16_6; Const MaxN=l00 ; Type MyArray=Array[1. .MaxN] Of Char; Var A:MyArray; ch:Char; yk:Integer; ff:Boolean; Begin FillChar(A,SizeOf(A),0);yk:=0;ff:=True; Assign(Input,'MY1 6_SK.PAS'); Reset (Input); While (Not Eof) And f f Do Begin Read (ch); I f (ch='C) Or (ch='[') Or (ch='{') Then Begin Inc(yk);A[yk]:=ch; End Else I f (ch = ' ) ' ) Or (ch=']') Or (ch='}') Then I f Ord (ch)Ord(А[ук])<=2 Then Dec(yk) Else ff:=False; End; Close (Input); I f f f Then WriteLn ( 'Скобки расставлены правильно') Else WriteLn ( 'В тексте программы есть ошибка в расстановке скобок'); End. Н а э к р а н е обозримы две п р о г р а м м ы — п о с л е д н я я и Myl6_sk. М о ж н о п р и с т у п и т ь к э к с п е р и м е н т а м . Вносите и з м е н е н и е во в т о р у ю п р о г р а м м у , с о х р а н я я т е к с т после к а ж д о г о и з м е н е н и я , п е р е х о д и т е в д р у г о е о к н о , д е л а й т е з а п у с к п р о г р а м м у и производите анализ результата. Например, при некоторых исходных д а н н ы х в о з н и к а е т о ш и б к а Error 201: Range check error. Прог р а м м а с о д е р ж и т л о г и ч е с к у ю неточность. У с т р а н и т е её. И з м е н и т е п р о г р а м м у т а к , чтобы, н а п р и м е р , п р о в е р я л а с ь прав и л ь н о с т ь з а п и с и слова WriteLn в т е к с т е п р о г р а м м ы . Придумайте еще ряд модификаций программы.
214
Часть вторал
3. В ы з а п и с а л и с л е д у ю щ у ю п р о г р а м м у на д и с к п о д и м е н е м MY 16J7.PAS. В п р о г р а м м е по д о ч и т ы в а е т с я к о л и ч е с т в о строк в текстовом файле, к а ж д а я строка сохраняется к а к элемент м а с с и в а д л я п о с л е д у ю щ е г о а н а л и з а , с т р о к и в ы в о д я т с я н а экр а н . М ы п о л у ч и л и о д и н и з в а р и а н т о в п р о г р а м м ы в ы в о д а собс т в е н н о г о т е к с т а на э к р а н , п р и ч ё м не л у ч ш и й . В а ш а ц е л ь — п р е д л о ж и т ь н а и б о л ь ш е е к о л и ч е с т в о м о д и ф и к а ц и й з а д а ч и , естественно, с и х р е а л и з а ц и я м и . Program Му16_7; Const MaxN=l00 ; Type Var
MyArray=Array[l..MaxN] Of S t r i n g ; A:MyArray; cnt,i:Integer; Begin cnt:=0; Assign(Input,'MY16_7.PAS'); Reset (Input); While Not Eof Do Begin Inc (cnt); ReadLn (A[ cnt] ) ; End; Close(Input); WriteLn (cnt) ; For i:=l To cnt Do WriteLn (A [ i ] ) ; End. П р о г р а м м ы , в ы в о д я щ и е н а э к р а н свой с о б с т в е н н ы й т е к с т , называют и н т р о с п е к т и в н ы м и . Пользоваться д л я этой ц е л и хран я щ е й с я на диске копией текста запрещено. Т а к и м образом, н а ш а п р о г р а м м а н е я в л я е т с я и н т р о с п е к т и в н о й . Эта т е м а б ы л а одно в р е м я д о в о л ь н о п о п у л я р н а в к о м п ь ю т е р н о й л и т е р а т у р е . Попробуйте свои с и л ы в р е ш е н и и данной проблемы. Задания для самостоятельной работы 1. Д а н т е к с т о в ы й ф а й л , с о д е р ж а щ и й ц е л ы е ч и с л а . Н а й т и • • • • D • • •
количество чисел в файле; м а к с и м а л ь н ы й элемент в файле, в к а ж д о й строке; сумму чисел в файле, в к а ж д о й строке; р а з н о с т ь м е ж д у м а к с и м а л ь н ы м и м и н и м а л ь н ы м элементами в файле, в к а ж д о й строке; арифметическое среднее чисел в файле, в к а ж д о й строке; номер максимального элемента в файле; сумму максимальных элементов в файле; сумму четных чисел в файле.
Процедуры и функции -— элементы структуризации программ
215
2. Дан текстовый файл, содержащий строки. Найти • количество строк, начинающихся с заглавных латинских букв; • количество строк, начинающихся и заканчивающихся одинаковыми символами; • самые короткие строки; • симметричные строки (палиндромы). 3. Дан текстовый файл. Вставить в начало каждой строки ее номер и записать преобразованные строки в новый файл. 4. Даны два текстовых файла. Записать в третий только те строки, которые есть и в первом и во втором файлах. 5. Дан текстовый файл. Дописать в его конце следующие данные: количество строк, количество символов в каждой строке, количество чисел в каждой строке. 6. Даны два файла А и В (тип элементов одинаковый). Поменять местами содержимое этих файлов. Использовать процедуру Rename не разрешается. 7. Содержимое текстового файла копируется в другой файл, при этом к а ж д а я строка циклически сдвигается вправо на п символов. Пример циклического сдвига строки abcdefqwrt на 3 символа, результат — wrtabcdefq. То же самое, но только каждое слово сдвигается на половину своей длины. 8. Дано некоторое конечное множество слов. Исключить их из текстового файла. Например, из файла с текстом программы исключить все слова Begin и End. 9. Считаем, что длина строк текстового файла не превышает 80 символов. Преобразовать файл так, чтобы все строки были отцентрированы: более короткие строки дополняются символами пробела, текст строки размещается по ее центру. 10. Дан файл, в котором встречаются даты. Каждая дата — это число, месяц и год (например, 13.05.1949 г.). Найти наименьшую дату. Материал для чтения 1. В предыдущем материале для чтения мы рассмотрели различные схемы построения двоичных кодов. Однако все они не являются помехоустойчивыми. Искажение в любом разряде приводит к тому, что декодировать код не представляется возможным. Проверьте это утверждение. Замените в произвольном разряде последовательности 010111110111111101011000 значение на противоположное, например в третьем. Получаем 011111110111111101011000.
Часть вторал
216
Декодируем — 01 I 111 I 111 I 01 I 111 I 111 I 011 01 110 I 00, что соответствует beebeebbca. Это послание не соответствует истинному, а именно — bbeceeccda. Единственный путь решения проблемы построения помехоустойчивых кодов — это введение дополнительных разрядов, другого не дано. Вернемся к равномерному кодированию. Пусть для кодирования сообщений используется четыре разряда. Очевидно, что кодируются только 16 различных сообщений. Введем один дополнительный разряд — пятый. В этот разряд записываем 1, если в коде нечетное число единиц и 0, если четное. В таблице символом I обозначены столбцы с исходными кодами, символом II — столбцы с соответствующими кодами, но дополненные пятым разрядом. В канал передачи идут не четыре разряда, а пять.
I I
II
I
II
I
0000
00000
0100
0001
10001
0101
0010
10010
0110
00110
II
10100
1000
11000
00101
1001
01001
1010
01010
I
И
1100
01100
1101
11101
1110
11110
рои 00011 pin Ю111 юн поп n i l ~ o i i n I] Что дает введение одного разряда? Пусть передавался код 10010, произошло искажение в одном разряде, например в первом, и принят код 10011. Подсчитываем число 1, их нечетное количество. Делаем вывод о том, что произошла ошибка в передаче. Установить, в каком разряде нам не под силу, информации недостаточно. А если искажениям подверглись два разряда, например первый и четвертый? Принят код 11011. Число единиц четно, по нашим правилам «игры» мы должны считать этот код правильным. Итак, с помощью одного дополнительного разряда возможно фиксирование факта ошибки в нечетном количестве разрядов и только. Рассмотрим другую схему. Каждый разряд сообщения дублируем т раз ( т — нечетно). Пусть m равно пяти. Тогда 10010 передается к а к 1111100000000001111100000. Н и о к а к о й экономии в этом случае речь не идет, сплошное расточительство. Произошли и с к а ж е н и я и принята последовательность 1100100011001001101100011. Число m известно и в приемном устройстве. Наши действия. Считаем в каждой группе из m разрядов количество 1 и 0 и исправляем разряды по принципу «чего больше». Исправленная последовательность имеет вид: 1111100000000001111100000. Сообщение восстановлено. разряд Мы рассмотрели и много дополнительных два крайних случая разрядов. — один Оба они дополнительный неудовлетво-
Процедуры и функции -— элементы структуризации программ
217
рительны. В первом случае фиксируется одиночная ошибка, о восстановлении сообщения речи не идет, во втором сообщение восстанавливается за счет сверхизбыточности, сверхрасточительности. Истина, к а к обычно, находится посередине. 2. Рассмотрим н а примере построение помехоустойчивого кода по Р. В. Хэммингу (1950 г.), способного обнаруживать и исправлять одиночные ошибки. Но прежде попытаемся дать содержательную интерпретацию решаемой задачи. Пусть д л я код и р о в а н и я используются т разрядов. К а к мы знаем, различн ы х значений, представимых m р а з р я д а м и — 2т (обозначим это множество буквой S). Расстояние по Хеммингу (р(х,у)) между двумя р а з л и ч н ы м и элементами S, — это количество разрядов, в которых они отличаются друг от друга. Пример: т=4, х=1001, у=0101, р ( х , у ) = 2 ; х = 0 0 0 0 , у=1111, р(х,у)=4. Априори известно, что в к а н а л е не может произойти больше t ошибок. Д р у г и м и словами, расстояние по Хеммингу между истинным и и с к а ж е н н ы м кодами не может превышать значения t. В качестве кодов среди 2т элементов множества S следует выбирать такое подмножество элементов (обозначим буквой W), что между любыми двумя его элементами расстояние по Х е м м и н г у долж н о п р е в ы ш а т ь з н а ч е н и е 2*t. В этом случае и с к а ж е н и е истинного к о д а не приводит к неоднозначности. Д л я любого иск а ж е н н о г о кода существует только один истинный код, расстояние между которыми меньше t. Проиллюстрируем сказанное рисунком, точкам и обозначены элементы множества S, точками в к р у ж к а х и соединенн ы х «жирными» отрезками — элементы множества S. Расстояние по Хеммингу между любыми двумя элементами S больше 2*t. И с к а ж е н и я оставляют код в t-окрестности, к р у ж к е , и и с к а ж е н н о м у коду соответствует один истинный. Продолжим рассмотрение примера. Пусть т=4 и t=1. Сколько дополнительных разрядов (k) необходимо ввести, чтобы уметь устранять одиночную ошибку? Эти разряды обычно называют проверочными, или контрольными. Значение k определяется из неравенства 2k>m+k+l. Ошибка может произойти как в контрольных (р), так и в информационных (т) разрядах, плюс 1 учитывает случай отсутствия ошибок. Д л я нашего примера т = 4 , 2 3 = 4 + 3 + 1 . Принцип формирования контрольных разрядов — дополнение до четного числа единиц определенной груп-
Часть вторал
218
пы разрядов кода. В примере рассматриваются 3 г р у п п ы , ибо к о н т р о л ь н ы х р а з р я д а три. В результате проверки в приемном устройстве к а ж д о й из этих групп на четность числа единиц д о л ж е н формироваться номер р а з р я д а , в котором п р о и з о ш л а о ш и б к а . Н а рисунке в овалах у к а з а н ы р а з р я д ы из одной групп ы . Н а д п и с и «да», «нет» говорят о том, есть и л и нет о ш и б к а в этих р а з р я д а х . В п р я м о у г о л ь н и к а х у к а з а н номер и с к а ж е н н о г о р а з р я д а , 0 — нет о ш и б к и .
Осталось р е ш и т ь проблему — где в семи р а з р я д а х р а з м е щ а т ь контрольные р а з р я д ы , а где информационные. В первой группе 1, 3, 5, 7-й р а з р я д ы . Три их н и х д о л ж н ы быть информационные, один к о н т р о л ь н ы й . Пусть он седьмой, в нем фиксируется четность числа е д и н и ц в 1, 3, 5-м р а з р я д а х . Следующий контрольный р а з р я д шестой, ф и к с и р у е т с я четность числа единиц во 2, 3, 7-м р а з р я д а х , и наконец, третий к о н т р о л ь н ы й разряд размещаем на четвертом месте — четность числа е д и н и ц в 5, 6, 7-м р а з р я д а х . Кодировка ц и ф р от 0 до 9 по Хеммингу приведена в таблице. З н а ч е н и я к о н т р о л ь н ы х разрядов формируются по значениям и н ф о р м а ц и о н н ы х и у ж е сформированных контрольных в следующей очередности р х , р 2 , р 3 . Номер разряда1 при перед, аче сообщения 7
Pi 0 1 0 1
1
4 6 I 5 I I з I 2 Размещение контрольных и информационных разрядов Р2
0 1 1
0
т4 0 0 0 0
Рз 0 0 1 1
т3 0 0 0 0
т2 0 0 1 1
I
1 1
т, 0 1 0 1
Процедуры и функции -— элементы структуризации программ
1 0 1 0 1 °
0 1 1
0
0 0 0 0
1
1
0
1
1
1 0 0 1 1
219
1 1
0 0
1
1
1
1
1
0 0
0 0
0 1
1 0
I
Предположим, что передается цифра 5. В кодировке Хемминга она записывается к а к 0101101. Ошибка произошла в третьем разряде, т. е. принят код 0101001. Вычисляем значен и я контрольных разрядов. Сумма единиц в 1, 3, 5-м и 7-м разрядах нечетна — есть ошибка. Проверяем 2, 3, 6, 7-й разряды. Сумма единиц нечетна — есть ошибка. Проверяем сумму единиц в 4, 5, 6-м и 7-м разрядах — нет ошибки. Если слова «есть ошибка» заменить на 1, а слова «нет ошибки» на 0 и записать 0 1 1 2 = 3 1 0 , то получим номер разряда, в котором данные искажены. Выполните еще несколько примеров подобного типа, чтобы полностью понять кодирование по Хеммингу.
Часть третья
Массив — фундаментальная структура данных
Занятие №17. Методы работы с элементами одномерного массива План занятия • соревнование на написание коротких программ работы с элементами массива; • экспериментальная работа с программами вставки и удаления элементов массива; • выполнение самостоятельной работы. Короткие примеры. В этих примерах используется следующее описание данных: Const MaxN= ;(*Максимальное количество элементов в массиве. *) Type MyArray= Array[l..MaxN] Of I n t e g e r ; . А основная программа может иметь вид Begin Init(m,X);1*Ввод элементов массива и инициализация глобальных переменных. *} SolveProc(<параметры>);{*Вызов основной процедуры решения задачи.*} Print(m,X);(*Вывод результата. *} ReadLn; End. Если для решения задачи необходимо использовать функцию, то возможен такой вид основной логики. Begin Init(m,X);{*Ввод элементов массива и инициализация глобальных переменных. *) WriteLn(SolveFun(<параметры>));[*Вызов функции, если результат ее действий выводится с помощью одного оператора WriteLn.*} ReadLn; End.
Массив — фундаментальная структура данных
221
1. З а м е н и т ь о т р и ц а т е л ь н ы е э л е м е н т ы м а с с и в а н а противопол о ж н ы е по з н а к у . Procedure Var 1: Begin For i:=1 End;
NegPos(п:Integer;Var Integer; To n Do I f A[i]<0
A:
Then
MyArray);
A[i]:=-A[i];
2. П р и б а в и т ь к к а ж д о м у э л е м е н т у м а с с и в а ч и с л о 25. Procedure Var i: Begin For i:=l End;
Add25 ( п:Integer;Var IntegersTo n Do A[i]
:=A[i]
A:MyArray);
+ 25;
3. Е с л и э л е м е н т ч е т н ы й , то п р и б а в и т ь к н е м у п е р в ы й , если неч е т н ы й — п о с л е д н и й э л е м е н т ы м а с с и в а . П е р в ы й и последн и й э л е м е н т ы не и з м е н я т ь . Procedure AddFirstLast(п:Integer/ Var l: Integer; Begin For l:=2 To n-1 Do I f A[l] Mod 2=0 Then A[i]:=A(i] A[i] :=A[i]+A[n] ; End;
Var A:
+ A[l]
MyArray);
Else
4. Д а н ы д в а о д н о м е р н ы х м а с с и в а о д и н а к о в о й р а з м е р н о с т и . Пол у ч и т ь т р е т и й м а с с и в т а к о й ж е р а з м е р н о с т и , к а ж д ы й элем е н т к о т о р о г о р а в е н сумме с о о т в е т с т в у ю щ и х элементов данн ы х массивов. Procedure AddArray(п:Integer;A,B:MyArray; Var С: MyArray ); Var i: Integer; Begin For l: =1 To n Do С[l] : =A[i J +B[l] ; End; Д а н ы д в а ц е л о ч и с л е н н ы х массива, состоящие и з одинакового ч и с л а э л е м е н т о в . П о л у ч и т ь т р е т и й м а с с и в той ж е размерности, к а ж д ы й элемент которого р а в е н н а и б о л ь ш е м у из соо т в е т с т в у ю щ и х э л е м е н т о в данного м а с с и в а . Procedure AddMaxArray (п:Integer;A,B:MyArray; Var С: MyArray ); Var i: Integer; Begin
Часть третья
222 For i:=l C[i]:=B[i]; End;
То п Do I f A[i]>B[i]
Then
С[l]:=A[1]
Else
5. Д а н п е р в ы й э л е м е н т а р и ф м е т и ч е с к о й п р о г р е с с и и и р а з н о с т ь между соседними элементами. Сформировать одномерный массив из первых п элементов арифметической прогрессии. П у с т ь one — п е р в ы й э л е м е н т п р о г р е с с и и , a k — р а з н о с т ь . Е с л и и з в е с т н о з н а ч е н и е э л е м е н т а с н о м е р о м i—l, то э л е м е н т с н о м е р о м i в ы ч и с л я е т с я п о п р а в и л у : a=au+k, или а=опе+ +k*(i—l). Procedure FormProg (one, к, n: Integer; MyArray); Var 1: Integer; Begin A[l]:=one; For l : =2 To n Do A[ l ] : =A [ l - l ] +k ; End;
Var
A:
Д л я геометрической прогрессии очередной элемент вычисляе т с я п о п р а в и л у a=ail*k, где k — з н а м е н а т е л ь п р о г р е с с и и . Найти сумму первых п элементов прогрессии. Function FormProgm(one, к, п: Var i , t : Integer;sum:Longlnt; Begin sum:=one;t:=one; For l:=2 To n Do Begin t:=t*k; FormProgm:=sum; End;
Integer):Longlnt;
Inc(sum,t);End;
6. Д а н ы д в а о д н о м е р н ы х м а с с и в а А и В. Н а й т и и х с к а л я р н о е п р о и з в е д е н и е . С к а л я р н ы м п р о и з в е д е н и е м д в у х м а с с и в о в один а к о в о й р а з м е р н о с т и н а з ы в а е т с я с у м м а п р о и з в е д е н и й соответс т в у ю щ и х элементов. Она з а п и с ы в а е т с я в виде: А[1]*В[1]+ +А[2]*В[2] +... + А[п-1 ]*В[п-1 ] + А[п]*В[п], где п — это количество элементов в массивах. Function Product(л:Integer;А,В: Var i: Integer;s: Longlnt; Begin For i : =1 To n Do s :=s+A[i] Product:=s/ End;
MyArray):
*B[i]
;
Longlnt;
Массив — фундаментальная структура данных
223
7. Н а й т и м а к с и м а л ь н ы й п о м о д у л ю э л е м е н т м а с с и в а . Function MaxAbs(п:Integer; A:MyArray):Integer; Var l,max:Integer; Begin max:=Abs (A[1]); For l : =2 To n Do I f Abs (A [l ] ) >max Then max:=Abs (A[i]); MaxAbs:=max; End; Н а й т и среднее арифметическое значение элементов. Function Average(п:Integer; Var i,sum:Integer; Begin sum:=A[1]; For l:=2 To n Do Inc(sum,A Aver age:=sum/n; End;
A:MyArray):Real;
[l] ) ;
И з м е н и т ь з н а к у м а к с и м а л ь н о г о по м о д у л ю э л е м е н т а массива. Procedure SignMaxElem(п:Integer; Var A:MyArray); Var l,j,Max:Integer; Begin Max:=Abs(A[1]);j:=1; For l: =2 To n Do I f Abs (A [l ] ) >Max Then Begin Max:=Abs(A[i]);j:=i;End; A[J]:=-A[J] ; End; 8. Все ч е т н ы е э л е м е н т ы м а с с и в а возвести в к в а д р а т , а нечетные удвоить. Procedure MaxEven(п:Integer; Var i:Integer; Begin For i:=1 To n Do I f A[i ] Mod 2 =0 Then A[i] Else A[i] :=2*A[i] ; End;
Var
:=A[i]
A:MyArray);
*A[i]
9 . И з п о л о ж и т е л ь н ы х э л е м е н т о в м а с с и в а в ы ч е с т ь э л е м е н т с номером k j , а отрицательные увеличить на значение элемента с н о м е р о м к 2 , н у л е в ы е э л е м е н т ы о с т а в и т ь без и з м е н е н и я .
Часть третья
224 Procedure ChangeElem(п,kl,к2:Integer; A .-MyArray) ; Var l,w,v:Integer; Begin w:=A[kl];v:=A[k2]; For 1:=1 To n Do I f A[i] >0 Then A[i]:-A[i]+w A[i] :=A[i]-v; End;
Var
Else
I f A[i]<0
Then
Операторы w : = A [ k l ] v:=A[k2] обязательны. Использование конструкции I f A[i] Then
>0 Then Ah] :=A[i]+A[kl] All]:=A[i]-A[k2];
Else
If
A[i]<0
приводит к нежелательным последствиям. Проверьте. 10. И з э л е м е н т о в м а с с и в а А с ф о р м и р о в а т ь э л е м е н т ы м а с с и в а В по правилу: B[i]:=A[l]+A[2]+...+A[i]. Procedure SumElem(п:Integer; А:МуАггау;Var В:МуАггау); Var l:Integer; Begin В [1 ] : =А[1 ] ; For i: =2 То п Do В [i ] : =В [i -1 ] +А [ 1 ] ; End; Экспериментальный раздел работы 1. Удалить из массива п о с л е д н и й (в том случае, если и х нескол ь к о ) м а к с и м а л ь н ы й э л е м е н т . Д л я р е ш е н и я з а д а ч и необходимо: О н а й т и н о м е р м а к с и м а л ь н о г о э л е м е н т а — k; • с д в и н у т ь все э л е м е н т ы , н а ч и н а я с fe-ro, н а один элемент влево; • последнему э л е м е н т у п р и с в о и т ь з н а ч е н и е 0. ($R+) Program Myl 7_1 ; Const MaxN^lO;la=-10;ha=21;{*Размер интервал значений. *) Type MyArray=Array[1..MaxN] Of Var A .-MyArray; k:Integer; Procedure Init(t,v,w:Integer;Var X:MyArray);l'Напомним процедуру значений элементов массива. *}
массива Integer;
формирования
и
Массив — фундаментальная структура данных 224 Var 1:Integer; Begin Randomize; For l: =1 To t Do X[i] :=v+Integer(Random(w)) ; EndsProcedure Print(t:Integer;X:MyArray);(*Вывод t первых элементов массива X. *) Var l:Integer; Begin For l:=1 To t Do Write(X[i] :5) ; WriteLn; EndsFunction Max(t:Integer;X:MyArray) -.Integer; (*Поиск номера последнего максимального элемента. *} Var i,Mx: IntegersBegin Mx:=l; For l: =2 To t Do I f X[i]>=X[Mx] Then Mx:=i; Max:=Mx; EndsProcedure Sz(t,q:Integer;Var X:MyArray);(*Сдвиг элементов массива влево на одну позицию, начиная с элемента с номером q+1. *} Var i:Integer; Begin For i:=q To t-1 Do X[l]:=X[i+1]; X[t]:=0; EndsBegin n : =MaxN; Imt (n, la, ha, A) ; WriteLn( 'Исходный массив.'); Print(n,A); k:=Max(n,A); Sz(n,k,A); WriteLn(Массив после удаления элемента.') ; Print (п-1,А) ; ReadLn; End. П р е д п о л о ж и м , что м а к с и м а л ь н ы й элемент встречается нес к о л ь к о р а з . Во-первых, следует и з м е н и т ь ф у н к ц и ю М а х . Требуется н а х о д и т ь не номер м а к с и м а л ь н о г о элемента, а его значение.
225 Часть третья Function Maxft:Integer;X:MyArray) Var l,Mx:Integer; Begin Mx:=X[l]; For i:=2 To t Do I f X[i]>Mx Max:=Mx; End;
.-Integer;
Then
Mx:=X[i]
;
Затем повторно просматриваем массив, у д а л я я элементы, равные максимальному, и уменьшая количество элементов в массиве. Procedure Solve(Var t: Integer;Var X:MyArray); Var k,l:Integer; Begin k:=Max(t,X); i:=t+l; While i>l Do Begin Dec (1) ; I f X[i]=k Then Begin Sz ( t , 1, X) ;Dec (t) ; End; End; End; И з м е н и т е п р о г р а м м у т а к , чтобы п р и у д а л е н и и м а с с и в прос м а т р и в а л с я не с к о н ц а , а с н а ч а л а . 2. Вставка элемента в массив. Н е о б х о д и м о в с т а в и т ь э л е м е н т после первого м а к с и м а л ь н о г о элемента массива (пусть его номер р а в е н q). О п е р а ц и я в ы п о л н я е т с я с л е д у ю щ и м образом: • п е р в ы е q элементов м а с с и в а о с т а ю т с я без и з м е н е н и й ; • все э л е м е н т ы , н а ч и н а я с (д+2)-го, н е о б х о д и м о с д в и н у т ь н а одну п о з и ц и ю ; • н а место (q+1) з а п и с ы в а е м з н а ч е н и е к о н с т а н т ы
InsC.
($R+i Program Му17_2; Const MaxN=l0;la=l;ha=6;InsC=l00; Type MyArray=Array[l..MaxN+1] Of Integer; {*Вставляем один элемент. Резервируем для него место. *} Var А.-МуАггау; п,k:Integer; Procedure Init (t,v,w:Integer;Var X:MyArray) ; ( * Формируем исходный массив . *} Procedure Print(t:Integer;X:MyArray);(*Вывод значений элементов массива.*} Function Max(t:Integer;X:MyArray):Integer;
Массив — фундаментальная структура данных
227
(*Поиск номера первого максимального элемента. *} Var 1,Мх:Integer; Begin Мх:=1; For l : =2 То t Do I f X[i]>X[Mx] Then Mx:=i; Max:=Mx; EndsProcedure InsPs (t,q: Integer ;Var X -.MyArray) ; ("Сдвиг на одну позицию вправо и вставка на место q+1 (после заданного элемента) элемента, равного InsC. *) Var i:Integer; Begin For i:=t DownTo q+1 Do X[i+1]:=X[i]; X[q+11:=InsC; End; Begin n:=MaxN; Imt (n, la ,ha, A} ; WriteLn('Вывод массива до вставки элемента.'); Print (n,A); k:=Max(n,A);("Определяем место первого максимального элемента. *} InsPs(n,k,A); WriteLn( 'Вывод массива после вставки элемента.'); Print(n+1,А) ; ReadLn; End. Д л я в с т а в к и э л е м е н т а п е р е д з а д а н н ы м э л е м е н т о м требуется н е з н а ч и т е л ь н о е и з м е н е н и е п р о ц е д у р ы InsPs. Procedure InsPs(t,q:Integer; Var i-.Integer; Begin For l: = t DownTo q Do X[l+l X[q]:=InsC; End;
Var X:•
MyArray);
] : =X[l ] ;
И з м е н и м з а д а ч у — т р е б у е т с я в с т а в и т ь InsC после всех м а к с и м а л ь н ы х э л е м е н т о в м а с с и в а . В о - п е р в ы х , следует и з м е н и т ь р а з м е р м а с с и в а . Е с л и м а с с и в состоит из о д и н а к о в ы х элементов, то в р е з у л ь т а т е н а ш и х д е й с т в и й он у в е л и ч и т с я в д в а р а з а . И т а к , Type MyArray=Array[1..2*MaxN] Of Integer. В о - в т о р ы х , п р е д ы д у щ е е р е ш е н и е в п р и н ц и п е не и з м е н я е т с я . Ф у н к ц и я
227 Часть третья
k:=Max(nA) и п р о ц е д у р а InsPs(n,kA) включаются в новую процедуру Solve. Procedure Solve(Var t: Integer/Var X:MyArray) ; Var к, l:Integer; Begin к:=Max(t,X);{'Определяем значение максимального элемента. *} i: =1 ; While К= t Do Begin I f X[i]=k Then Begin InsPs(t.l,X) ;Inc ( t ) ; End; {'Вставляем элемент и увеличиваем размер массива. *} Inc (1) ; End; End; А сейчас вопрос. П р и всех л и и с х о д н ы х д а н н ы х эта программа работает правильно? Оказывается, нет. Н е трогая программ н ы й код, а и з м е н я я только з н а ч е н и я констант в программе, найдите о ш и б к у . Эксперименты с п р о г р а м м о й приведут Вас к з н а к о м о й о ш и б к е : Error 202: Range check error, а о н а у ж е «натолкнет» на неточность в программе. Измените программу так, чтобы вставка в ы п о л н я л а с ь перед в с е м и м а к с и м а л ь н ы м и э л е м е н т а м и м а с с и в а . П о с л е э т о г о необходимо добиться, чтобы вставка выполнялась и перед максимал ь н ы м и и после м а к с и м а л ь н ы х элементов. З а д а н и я для с а м о с т о я т е л ь н о й р а б о т ы 1. З а м е н и т ь • • • • • •
э л е м е н т ы с krvo по k2-n н а п р о т и в о п о л о ж н ы е п о з н а к у ; первый отрицательный элемент нулем; м а к с и м а л ь н ы й элемент на п р о т и в о п о л о ж н ы й по знаку; п е р в ы й э л е м е н т , к р а т н ы й 5, н у л е м ; элементы с нечетными номерами на квадрат их номера; последний положительный элемент на второй элемент массива; • нулями элементы между минимальным и максимальным элементами массива; • все э л е м е н т ы , к р а т н ы е 3, н а т р е т и й э л е м е н т м а с с и в а ; • все э л е м е н т ы с н е ч е т н ы м и н о м е р а м и р а з д е л и т ь н а ц е л о на первый элемент.
Массив — фундаментальная структура данных
229
2. И з э л е м е н т о в м а с с и в а А с ф о р м и р о в а т ь м а с с и в В той ж е размерности по правилу • п е р в ы е 10 элементов — B[i]:=A[i]+i,
остальные —
B[i]:=
• е с л и н о м е р ч е т н ы й , то B[i]:=i*A[i], если н е ч е т н ы й , то B[i]:=A[i]; • э л е м е н т ы с 3-го по 12-й — B[i]~A[i]*A[i], все остальн ы е — B[i]~A[i] -1; • е с л и н о м е р ч е т н ы й , то B[i]:=A[i]*A[i], если н е ч е т н ы й , то B[i]:=A[i] Div i. 3. С ф о р м и р о в а т ь м а с с и в А с п о м о щ ь ю д а т ч и к а с л у ч а й н ы х ч и с е л ц е л ы м и ч и с л а м и из с л е д у ю щ и х интервалов: [ - 2 1 , 53], [ - 4 3 , 32], [ - 1 1 , 67], [ - 3 5 , 7 5 ] , [ - 4 5 , 9 5 ] . 4. У д а л и т ь и з массива все э л е м е н т ы • с цифрой 5 в записи; • состоящие из одинаковых цифр (включая однозначные числа); • п о с л е д н я я ц и ф р а к о т о р ы х ч е т н а я , и само ч и с л о д е л и т с я н а нее; • первая цифра которых четная; • к р а т н ы е 7 и п р и н а д л е ж а щ и е п р о м е ж у т к у [а, Ь] (а и Ь вводятся с клавиатуры); • меньшие нуля; • б о л ь ш и е д а н н о г о ч и с л а А (.А вводится с к л а в и а т у р ы ) ; • ч е т н ы е э л е м е н т ы , с т о я щ и е н а н е ч е т н ы х местах; • к о т о р ы е п о в т о р я ю т с я , оставив т о л ь к о и х п е р в ы е вхожден и я , то есть п о л у ч и т ь м а с с и в и з р а з л и ч н ы х элементов; • к р а т н ы е 3 и л и 5; • н а ч и н а я с krro по kz-й (k1 и k2 вводятся с к л а в и а т у р ы ) . 5. В с т а в и т ь ч и с л о k (все ч и с л а , у к а з а н н ы е в ф о р м у л и р о в к а х задач, вводятся с клавиатуры) • после всех элементов, к р а т н ы х своему номеру; • перед всеми э л е м е н т а м и , в к о т о р ы х есть ц и ф р а 1; • перед и после всех элементов, з а к а н ч и в а ю щ и х с я на данную ц и ф р у ; • после всех элементов, б о л ь ш и х заданного числа, а число t — перед всеми э л е м е н т а м и , к р а т н ы м и 3; • м е ж д у в с е м и соседними э л е м е н т а м и , к о т о р ы е образуют п а р у элементов с о д и н а к о в ы м и з н а к а м и ; • после первого отрицательного элемента; О перед п о с л е д н и м о т р и ц а т е л ь н ы м элементом;
229 Часть третья
• после максимального элемента, а число t — перед максим а л ь н ы м элементом; • перед всеми элементами, к р а т н ы м и заданному числу; • перед всеми отрицательными элементами; • после всех элементов, больших данного числа Р , а второе число t — перед всеми элементами, м е н ь ш и м и числа Р; • перед всеми элементами, большими k, а число t — после всех элементов, м е н ь ш и х его. 6. Переставить в массиве • первые три и последние три элемента местами, сохраняя их следование; • первый положительный и последний отрицательный элементы; • первый элемент и максимальный; • второй и минимальный; • первый и последний отрицательный; • элементы следующим образом: А [ 1 ] , А [ 12], А [ 2 ] , А [ 11 ] , .... А[5], А[8], А[6], А[7]\ • первые k элементов в конец, то есть: A[k+1 ], A[k+2] А[п], А[1 ], А[2] A[h]• • в обратном порядке часть массива между элементами с номерами k t и k 2 , включая их. • первый элемент с последним, второй с предпоследним и так далее; • в обратном порядке элементы, расположенные между минимальным и максимальным элементами; • элементы по следующим правилам (2*п элементов в массиве): • А[п+1 ], А[п+2] А[2п], А[1 ], А[2],..., А[п]; ш А[п+1], А[п+2],..., А[2п], А[п], А[п-1 ],..., А[1]; • А[1], А[п+1], А[2], А[п+2] А[п], А[2п]; ш А[2п], А[2п-1 ] А[п+1 ], А[1], А[2],..., А[п]. 7. Дан одномерный массив из целых чисел. Н а й т и количество различных чисел среди элементов массива. 8. Дан одномерный массив из целых чисел. Известно, что значен и я элементов массива находятся в интервале от А до В. Найти количество различных чисел среди элементов массива. 9. Дан одномерный массив из целых чисел и число д. Переставить элементы массива так, чтобы слева от некоторой границы были записаны числа, меньшие q, а справа от границы — числа, большие или равные q. Дополнительные массивы не разрешается использовать.
Массив — фундаментальная структура данных
231
Модифицировать р е ш е н и я д л я случая: меньшие q, равные q, большие q. 10. Дан массив из N целых чисел и число М (M<N). Д л я каждого участка из М стоящих рядом элементов массива вычислить значение суммы (количество участков N-M+1). Материал для чтения А л г о р и т м ы и их сложность. Оценка сложности алгоритмов (методов) обычно осуществляется по времени р е ш е н и я и затратам на объем требуемой п а м я т и д л я х р а н е н и я исходных данных задачи. Рассмотрим первую характеристику понятия сложности алгоритма. С к а ж д о й конкретной задачей связывается некое число N, называемое ее размером и в ы р а ж а ю щ е е меру количества входных д а н н ы х . В случае одномерного массива — это размер массива. Время, затрачиваемое на решение задачи, выраженное через значение N, называется временной сложностью алгоритма. Поведение этой х а р а к т е р и с т и к и (сложности) при увеличении размерности задачи (значения N) называют асимптотической временной сложностью. Эффективность алгоритма определяется именно асимптотической временной сложностью. Если алгоритм обрабатывает задачу размера N за время c*N2, где с — некоторая постоянная, то говорят, что временная сложность алгоритма есть 0(N2) (читается «порядка iV2»). Наиболее часто встречаемые оценки временной сложности приведены в таблице. Значение при N=1024
I Временная сложность
Тип зависимости
0(N)
Линейная
1024
O(N'LogN)
Логарифмическая
10240
Полиномиальная
~10 6 или 10э
Экспоненциальная
~Ю 3 0 0
O f N 2 ) или OfN3) 1
0(2»,
Эффективный алгоритм — это тот, который решает задачу за меньшее время. Пусть есть два алгоритма At и А2, решающ и х одну и ту ж е задачу. Первый алгоритм решает задачу размерности jV=10 6 aa N2 операций, а второй — за 10*N*LogN. Первый алгоритм выполняется на суперкомпьютере с быстродействием 10 8 операций в секунду, а второй — на простом, с быстродействием 10 6 операций в секунду. Найдем время реше-
Часть третья
232
н и я задачи в том и в другом случаях: f ; = 1 0 1 2 / 1 0 8 - 2 , 8 часа; f2=(6*107*Logl0)/106 - 2 0 0 секунд. П о и с к м а к с и м а л ь н о г о элемента в одномерном массиве за N - 1 сравнение мы умеем делать. Mm For
:=А[1]; 1 :=2 То N Do I f A[i]<Mm
Then
Mm:=A[i]
;
А если необходимо найти и м и н и м а л ь н ы й элемент? Решение за t j = 2 * N - 2 о п е р а ц и й с р а в н е н и я очевидно. М о ж н о л и улучш и т ь р е з у л ь т а т (N — ч е т н о е ч и с л о ) ? I f А[1]>А[2] Then Begin Max:=А[1];Min:=А[2];End Else Begin Mm : =A [ 1 ] ;Max: =A [2] ;End; i:=3; While i<=N~l Do Begin I f A[i]>A[i+l] Then Begin I f A[i]>Max Then Max:=A [l] ; I f A[i+1] <Mm Then Mm : =A[i+l ] ; End Else Begin I f A[i+1]>Max Then Max:=A[i+l]; I f A[l] <Min Then Mm:=A[i]; End; Inc (i , 2) ; End; Н а к а ж д у ю п а р у чисел, к р о м е первой, требуется т р и сравнен и я . Н а первую — одно. В р е м я р е ш е н и я t2 = 3* p V / 2 ] - 2 (в количестве о п е р а ц и й сравнения), где с и м в о л а м и обозначено б л и ж а й ш е е целое число, превышающее значение N / 2 . Д л я с р а в н е н и я методов найдите разность t1~t2, н а п р и м е р , п р и 2V=10 9 . Одним из основных приемов решения задач является реализ а ц и я п р и н ц и п а « р а з д е л я й и в л а с т в у й » , т. е. з а д а ч а р а з б и в а е т ся на подзадачи, решаемые независимо, а затем результаты объединяются. Пусть размерность задачи описывается значением N. Н а м н е о б х о д и м о о ц е н и т ь в р е м я р е ш е н и я T(N). Сложность задачи обычно определяется числом и размером подзадач и в м е н ь ш е й с т е п е н и д е й с т в и я м и , н е о б х о д и м ы м и д л я разбиен и я з а д а ч и н а п о д з а д а ч и . С л е д у ю щ а я т е о р е м а ( п р и в о д и т с я без
Массив — фундаментальная структура данных
233
доказательства) является основой оценки времени решения задач при использовании принципа «разделяй и властвуй». Пусть а, Ь, с — неотрицательные постоянные. Решение уравнений: О T(N)=b, при N= 1, О T(N)= a*T(N/c)+b*N, при N>1, где N — степень числа с, имеет вид: О T(N)=0(N), если а<с, О T(N)=0(N*LogN), если а=с, О T(N)=0(Nl°eca), если а>с.
Часть третья
234
З а н я т и е № 18. М н о ж е с т в е н н ы й т и п д а н н ы х План занятия О о п и с а н и е м н о ж е с т в е н н о г о т и п а д а н н ы х , о п е р а ц и и с велич и н а м и этого т и п а д а н н ы х ; • э к с п е р и м е н т а л ь н а я работа с п р о г р а м м а м и : в ы в о д а ц и ф р , не в х о д я щ и х в д е с я т и ч н у ю з а п и с ь ч и с л а п, п о и с к а прос т ы х ч и с е л с п о м о щ ь ю « р е ш е т а Эратосфена», р е ш е н и я ребусов; О в ы п о л н е н и е с а м о с т о я т е л ь н о й работы. Множество в я з ы к е Турбо П а с к а л ь — это ограниченный набор р а з л и ч н ы х элементов одного (базового) т и п а 1 . Б а з о в ы й тип — это с о в о к у п н о с т ь з н а ч е н и й , из к о т о р ы х могут б ы т ь о б р а з о в а н ы м н о ж е с т в а . М о щ н о с т ь ( к о л и ч е с т в о р а з л и ч н ы х элементов) не превышает 256. Значение переменной множественного типа м о ж е т с о д е р ж а т ь любое к о л и ч е с т в о р а з л и ч н ы х э л е м е н т о в базового т и п а . И н ы м и с л о в а м и , в о з м о ж н ы м и з н а ч е н и я м и переменн ы х м н о ж е с т в е н н о г о т и п а я в л я ю т с я все п о д м н о ж е с т в а значен и й базового т и п а . В е щ е с т в е н н ы й т и п д а н н ы х не и с п о л ь з у е т с я в к а ч е с т в е базового (нет о т н о ш е н и я п о р я д к а , н е л ь з я ввести о г р а н и ч е н и я на тип). Описание Туре <имя типа> = Var <имя переменной <имя
Set Of <тип множественного
элементов>; типа> :
множественного
типа> :
типа>;,
или Var <имя переменной <тип элементов>;.
Set
Of
Пример: Type Var
Mn_Char= Set Of Char; mnl ; Set Of Char; mn2: Mn_Char; mn3: Set Of 'A'. . 'Z'; si: Set Of Byte; s2: Set Of 100..120;
З н а ч е н и я м и п е р е м е н н ы х mnl и mn2 я в л я ю т с я м н о ж е с т в а , составленные и з р а з л и ч н ы х символов, т п З — м н о ж е с т в а и з бол ь ш и х л а т и н с к и х букв; si — м н о ж е с т в а и з ц е л ы х ч и с е л от 0 до 2 5 5 (тип Byte с о д е р ж и т ц е л ы е ч и с л а от 0 до 255), s2 — множеЭлементы множества должны быть порядкового типа.
Массив — фундаментальная структура данных
235
с т в а и з ц е л ы х ч и с е л от 1 0 0 до 1 2 0 . В п р о г р а м м е э л е м е н т ы множ е с т в а задаются в к в а д р а т н ы х скобках, через запятую. Если э л е м е н т ы и д у т п о д р я д д р у г з а д р у г о м , то и с п о л ь з у е т с я обознач е н и е и н т е р в а л а (две т о ч к и ) . Пример: Type Var
s
Digit :
= Set Digit;
Of 1.
.5;
Количество р а з л и ч н ы х значений переменной s 32. Перечислим их: • [ ] — пустое множество; • [1], [2], [3], [4], [ 5 ] — о д н о э л е м е н т н ы е м н о ж е с т в а ; • [ 1 , 2], [ 1 , 3 ] , . . . , [ 2 , 4 ] , [ 4 , 5 ] — д в у х э л е м е н т н ы е м н о ж е с т ва; • [1, 2, 3], [ 1 , 2 , 4 ] , . . . , [ 3 , 4 , 5 ] — т р е х э л е м е н т н ы е м н о ж е с т ва; • [1, 2 , 3, 4], [ 1 , 2 , 3 , 5 ] , [ 1 , 2 , 4 , 5 ] , [ 1 , 3 , 4 , 5 ] , [ 2 , 3 , 4 , 5 ] — ч е т ы рехэлементные множества; • [ 1 , 2, 3 , 4 , 5] — м н о ж е с т в о и з в с е х э л е м е н т о в базового типа. Операции над множествами. Объединением двух данных множеств называется множество элементов, п р и н а д л е ж а щ и х о б о и м м н о ж е с т в а м . З н а к о п е р а ц и и — « + ».
Примеры: • • • •
['A', ' F ' ] + ['В', 'D'] = ['A', 'F', 'В', 'D']; [1..3,5,7,11] + [3..8,10,12,15..20] = [1..8,10..11,15..20]; S i : = [ 1 . . 5 , 9 ] , S 2 : = [ 3 . . 7 Д 2 ] . S : = S1 + S 2 = [ 1 . . 7 , 9 , 1 2 ] ; Aj:=['a'..'z']; A ^ A j + ['А']. Результат — A ^ ' A ' , V . . ' z ' ] .
Пересечением двух данных множеств называется множество э л е м е н т о в , п р и н а д л е ж а щ и х одновременно и первому, и второму м н о ж е с т в у , то есть это общие э л е м е н т ы . З н а к операц и и — «*».
236
Часть третья
СЮ А
В
А* В
Примеры: • [ ' A ' , ' F ' ] * ['В', 'D'] = [ ] — так к а к общих элементов нет; • [1..3,5,7,11] * [3..8,10,12,15..20] = [3,5,7]; • S j : = [1..5.9], S 2 := [ 3 . . 7 Д 2 ] . S:= S,, * S 2 =[3..5]. В результате операции вычитания формируется множество, состоящее из тех элементов первого множества, которые не являются элементами второго множества. Знак операции — « - » .
GO A
A-B
Примеры: • ['A', ' F ' ] - ['В', 'D'] = [ ' A ' , ' F ' ] — общих элементов нет; • [1..3,5,7,11] - [3..8,10,12,15..20] = [1..2Д1]; • S l : = [1.-5,9]; S2:= [3..7Д2]; S;= S I - S2 =[1..2,9]; • A1:=['A'..'Z']; A1:=A1 — ['A']=['B'..'Z']. Операция определения принадлежности элемента множеству. Эта логическая операция обозначается служебным словом In. Результат операции — значение True, если элемент принадлежит множеству, и False — в противном случае. Примеры: О 5 In [3 .. 7] дает значение True, так к а к 5 е [3 .. 7]; • 'a" In ['А' .. 'Z'] дает значение False, буквы 'а' нет среди больших латинских букв. Операцию проверки принадлежности удобно использовать д л я исключения более сложных проверок. Например, оператор вида If (ch = 'a') or (ch='b') or (ch='x') or (ch='y'j Then s;
Массив — фундаментальная структура данных 236 п е р е п и с ы в а е т с я в более к о м п а к т н о й и н а г л я д н о й форме: I f ch In [ ' а ' , ' b ' , ' х ' , 'у' ] Then s;. Сравнение множеств. Д л я с р а в н е н и я м н о ж е с т в используются обычные операции: О = — р а в е н с т в о (совпадение) д в у х м н о ж е с т в ; О о — неравенство двух множеств; • < = , < — п р о в е р к а на в х о ж д е н и е первого м н о ж е с т в а во второе м н о ж е с т в о ; О > = , > — п р о в е р к а н а в х о ж д е н и е второго м н о ж е с т в а в первое м н о ж е с т в о . П е р в о е м н о ж е с т в о м е н ь ш е и л и равно второму (А < В):
True
False
True
П е р в о е м н о ж е с т в о м е н ь ш е второго (А < В):
True
False
False
Экспериментальный раздел работы 1. Д а н о натуральное число п. Составить программу вывода цифр, не в х о д я щ и х в д е с я т и ч н у ю з а п и с ь ч и с л а п (в п о р я д к е возрастания). Program Му18_1; Type Mn=Set Of 0. .9; Var s:Mn ; n:Longlnt; i : Integer; Begin WriteLn('Введите число ReadLn (n) ; s:=[0..9]; While n<>0 Do Begin
');
237 Часть третья s:=s-[п Mod 10];{*Исключаем цифру. *) п:=л Div 10; End; For i: =0 То 9 Do I f l In s Then Write (i :2) ; WriteLn; ReadLn; End. Измените программу так, чтобы находились общие ц и ф р ы в записи т чисел. 2. « Р е ш е т о Э р а т о с ф е н а » . Н а й т и п р о с т ы е ч и с л а в и н т е р в а л е от 2 до п. Н а п о м н и м , что простым числом н а з ы в а е т с я число, не имеющее других делителей, кроме е д и н и ц ы и самого себя. Решен и е без и с п о л ь з о в а н и я м н о ж е с т в е н н о г о т и п а д а н н ы х п р и в е д е но н и ж е . Program Му18_2; Const п=1ООО;{'Интервал чисел, в котором находятся простые числа. *} Var 1:Integer; Function Simple(m:integer):Boolean; Var i:Integer; Begin i: =2 ; While fi<= m Div 2) And (m Mod i<>0) Do Inc(i) ; Simple:=(i>m Div 2); End; Begin For i : =2 To n Do I f Simple (i) Then Write ( i , ' ' ) ; ReadLn; End. Измените программу так, чтобы находилась первая 1000 простых чисел. О т к а ж е м с я от этого простого р е ш е н и я . П р и м е н и м и д е ю «реш е т а Э р а т о с ф е н а » , ибо н а ш а ц е л ь — и з у ч е н и е м н о ж е с т в е н н о г о т и п а д а н н ы х . Суть м е т о д а — с ч и т а е м все ч и с л а и н т е р в а л а прос т ы м и , а з а т е м « в ы ч е р к и в а е м » те, к о т о р ы е н е у д о в л е т в о р я ю т т р е б о в а н и ю простоты. К а к о с у щ е с т в л я е т с я в ы ч е р к и в а н и е ? Н а х о д и м о ч е р е д н о е н е в ы ч е р к н у т о е ч и с л о , оно п р о с т о е , и у д а л я е м все ч и с л а , к р а т н ы е е м у . П о с л е т а к о г о « п р о с е и в а н и я » в исходном множестве останутся только простые числа.
Массив — фундаментальная структура данных
239
Program Му18_2т; Const п=255; Type Mn=Set Of 0. .п; Var Sim:Мп; 1,3 :Integer; Begin Sim:=[2..n]; j:=2; While j<=n Div 2 Do Begin I f j In Sim Then Begin {*Поиск очередного простого числа.*} i:=j+j; While 1<=п Do Begin Sim:=Sim-[i];Inc <i,j); End;{*Вычеркивание. *} End; Inc(j); End; For l: =2 To n Do I f 1 In Sim Then Write (i:4) ; {*Вывод оставшихся после вычеркивания чисел, простые. *} ReadLn; End.
они
П о и с к п р о с т ы х ч и с е л и з и н т е р в а л а , большего, чем 0.. 255, « у п и р а е т с я » в о г р а н и ч е н и е м н о ж е с т в е н н о г о т и п а д а н н ы х — не более 2 5 6 з н а ч е н и й базового т и п а . У й д е м от этого о г р а н и ч е н и я п у т е м ввода м а с с и в а , э л е м е н т а м и которого я в л я ю т с я множеств а . Н о п р е ж д е , ч е м р а с с м о т р и м р е ш е н и е , н е б о л ь ш о й фрагмент: ($R + ) Program Myl8_2mm; Var Mn: Set Of 1..255; a: Word; Begin Mn : = [1. .255]; a:=258; I f a In Mn Then WriteLn ReadLn; End.
(' Yes')
Else
WriteLn
('No')
;
П о с л е з а п у с к а п р о г р а м м ы в и д и м з н а к о м у ю до боли ошибк у — Error 202: Range check error. З н а ч е н и е переменной а вых о д и т за д о п у с т и м ы й д и а п а з о н з н а ч е н и й . Учтем этот ф а к т при н а п и с а н и и очередной версии п р о г р а м м ы .
240
Часть третья
($R+1 Program Му18_2ттт; Uses Crt; Const m=255;n=l000 ; TypeMn=Set Of l..m; OMyArray=Array [0 . . (n Div m) ] Of Mn; VarSim:Mn; A:OMyArray; i,j,k:Integer; Begin ClrScr; к:=(n Div m); For l:=0 To к Do A[i]: = [!. .m] ; j:=2; While j<=n Div 2 Do Begin I f ( j Mod m) In A[j Div m] Then Begin i:=j+j; While K=n Do Begin A f i Div m] :=A[i Div m] - [l Mod m];Inc(i,j);End; End; Inc(j); End; For i:=2 To n Do I f (l Mod m) In A[i Div m] Then Write ( l , ' ' ) ; ReadLn; End. 3. Р е ш е н и е ребусов м ы р а с с м а т р и в а л и на п р е д ы д у щ и х з а н я т и я х . С и с п о л ь з о в а н и е м м н о ж е с т в е н н о г о т и п а д а н н ы х программ н ы й к о д п о л у ч а е т с я более к о м п а к т н ы м . П о д с ч и т а е м к о л и чество р е ш е н и й ребуса М У Х А + М У Х А = С Л О Н . Program Му18_3; Type Mn=Set Of 0. .9; Var i , j , cnt:Integer; Sm,Se:Mn; Procedure Change(t:Integer; Var S:Mn);{*Из цифр числа формируем множество. *j Begin S:=[]; While t<>0 Do Begin S:=S4-[t Mod 10];t:=t Div 10; End; End;
Массив — фундаментальная структура данных
241
Function Qw(S:Мп):Integer;{"Подсчитываем количество элементов в множестве. *) Var 1,cnt:Integer; Begin cnt:=0; For l: =0 To 9 Do I f i In S Then Inc (cnt); Qw:=cnt; EndsBegin cnt:=0;{*Счетчик числа решений. *) For l:=1000 To 4999 Do Begin{"Результат четырехзначное число, поэтому слагаемое не превышает 4 999. *} Change(i,Sm); I f Qw {Sm) =4 Then Begin { *Если все цифры числа различны, то выполняем дальнейшие вычисления. *) О: =2*i; Change ( j , Se) ; I f (Sm*Se=[]> And (Qw(Se)=4) Then Inc(cnt); {*Числа состоят из различных цифр, и все цифры результата различны. *) End; End; WriteLn (cnt); End. Задания для самостоятельной работы 1. Д а н т е к с т . Н а й т и м н о ж е с т в а , э л е м е н т а м и к о т о р ы х я в л я ю т ся в с т р е ч а ю щ и е с я в тексте: О ц и ф р ы от ' 0 ' до ' 9 ' и з н а к и а р и ф м е т и ч е с к и х операций; • б у к в ы от'А' до 'F' и от 'X' до 'Z'; О з н а к и п р е п и н а н и я и б у к в ы от до W . 2. В ы в е с т и э л е м е н т ы м н о ж е с т в а , составленного и з произвольн ы х б у к в от А ... Z, в а л ф а в и т н о м п о р я д к е . 3. Д а н т е к с т . Н а й т и м н о ж е с т в о с т р о ч н ы х л а т и н с к и х букв, вход я щ и х в него. П о д с ч и т а т ь к о л и ч е с т в о з н а к о в п р е п и н а н и я и цифр в тексте. 4. Д а н т е к с т . Вывести в а л ф а в и т н о м п о р я д к е все б у к в ы текста, в х о д я щ и е в него: • не менее д в у х раз; • не более д в у х р а з ; • более д в у х раз;
Часть третья
242
•
по о д н о м у
разу.
5. Д а н текст. П о д с ч и т а т ь к о л и ч е с т в о г л а с н ы х и с о г л а с н ы х букв. 6. Н а п и с а т ь п р о г р а м м ы р е ш е н и я р е б у с о в :
ЛОБ+ТРИ=САМ,
ИСК+ИСК=КСИ, ТОЧКА+КРУГ=КОНУС, =MOTOR, АВ+ВС + СА
=АВС.
VOLVO+FIAT=
Массив — фундаментальная структура данных
243
З а н я т и е № 19. М е т о д ы с о р т и р о в к и План занятия • постановка задачи сортировки данных, изучение простых методов сортировки; • экспериментальная работа с методом сортировки подсчетом; • выполнение самостоятельной работы. Постановка задачи. Д л я решения многих задач необходимо упорядочить данные по определенному признаку. Процесс упорядочения заданного множества объектов по заданному признаку называется сортировкой. Д л я простоты изложения рассматривается одномерный массив из целых чисел ( Т у р е МуАгr ау= Array [l..NMax] Of Integer; Var A:MyArray, значение Nmax определяется в разделе констант). Суть большинства алгоритмов сортировки от такого упрощения не изменяется. Алгоритм ы сортировки отличаются друг от друга степенью эффективности, под которой понимается количество сравнений и количество обменов, произведенных в процессе сортировки. В основном м ы будем оценивать эффективность количеством операций сравнения (порядком этого значения). Заметим, что элементы массива м о ж н о сортировать: • по возрастанию — к а ж д ы й следующий элемент больше предыдущего — А[1] < А[2] <... < A[N]; • по неубыванию — к а ж д ы й следующий элемент не меньше предыдущего, то есть больше или равен А[1 ] < А[2] < ... ZA[N];. • по убыванию — к а ж д ы й следующий элемент меньше предыдущего А[1 ] > А[2] > ... > A[N];. • по невозрастанию — к а ж д ы й следующий элемент не больш е предыдущего, то есть меньше или равен А[1] > А[2] > ... > A[N]. Научившись выполнять одну сортировку, изменить ее, чтоб ы получить другую, не составляет особого труда. Сортировка простым выбором. Рассмотрим идею на примере. Пусть исходный массив А состоит из 10 элементов: 5 13 7 9 1 8 16 4 10 2. После сортировки массив должен выглядеть так: 1 2 4 5 7 8 9 11 13 16. Процесс сортировки представлен ниже. Максимальный элемент текущей части массива заключен в кружок, а элемент, с которым происходит обмен, в квадратик. Скобкой помечена рассматриваемая часть массива.
Часть третья
244
1-й шаг. Рассмотрим весь массив и найдем в нем максимальный элемент — 16 (стоит на седьмом месте), поменяем его местами с последним элементом — с числом 2. 5 13 7 9 1 8 (f|_4_10j2] М а к с и м а л ь н ы й элемент записан на свое место. 2-й ш а г . Рассмотрим часть массива — с первого до девятого элемента. М а к с и м а л ь н ы й элемент этой части — 13, стоящий на втором месте. Поменяем его местами с последним элементом этой части — с числом 10. 5 (П| 7 9 1 8 2 4 [ТО] 16 Отсортированная часть массива состоит теперь у ж е из двух элементов. 3-й шаг. Уменьшим рассматриваемую часть массива на один элемент. Здесь нужно поменять местами второй элемент (его значение — 10) и последний элемент этой части — число 4. 5 (Г§Т7 9 1 8 2JT[13 16 В отсортированной части массива — 3 элемента. 4-й шаг. 5 4 7(§)1_8[2]10 13 16 5-й шаг. Максимальный элемент этой части массива я в л я е т с я последним в ней, поэтому его нужно оставить на старом месте. 5 4 7 2 тГЁПэ 10 13 16 6-й шаг. 5 4 Q 2 j T | 8 9 10 13 16
(5)4_l[2]7 8 9 10 13 16
| 2(4fT]5
7 8 9 10 13 16
Г Ц 5 7 8 9 10 13 16
Массив — фундаментальная структура данных
245
Фрагмент программной реализации. Procedure Sort(N:Integer; Var A:MyArray); Var l , j , k: Integer; m: In teger;{*3начение максимального элемента рассматриваемой части массива. *} Begin For i:=N DownTo 2 Do Begin 1*Цикл по длине рассматриваемой части массива. *} (*Поиск максимального элемента и его номера в текущей части массива. *} k:=i; m:=А[i];("Начальные значения максимального элемента и его индекса в рассматриваемой части массива. *} For j:=l То 1-1 Do I f A[j ] >w Then Begin k:=j; m:=A[j] End; I f k o i Then Begin I "Перестановка элементов . *} A[k] :=A[i] ;A[i] :=m; End; EndsEnd; К о л и ч е с т в о с р а в н е н и й . П р и п е р в о м просмотре N-1, п р и втор о м — N-2, п р и последнем — 1. Общее количество C=N-l+N-2+ ...+1=N*(N-1 )/2 (сумма э л е м е н т о в а р и ф м е т и ч е с к о й прогрессии) и л и C=0(N2). Сортировка простым обменом. Р а с с м о т р и м идею метода на п р и м е р е . О т с о р т и р у е м по в о з р а с т а н и ю м а с с и в и з 5 элементов: 5 4 8 2 9. Д л и н а т е к у щ е й ч а с т и м а с с и в а — N-k+1, где k — н о м е р п р о с м о т р а , i — н о м е р первого элемента п р о в е р я е м о й п а р ы , н о м е р п о с л е д н е й п а р ы — N-k. Первый просмотр: р а с с м а т р и в а е т с я весь массив. 1=1
5 4 8 2 9 > меняем 4 5 8 2 9 < не меняем 1=3 4 5 8 2 9 > меняем 1=4 4 5 2 8 9 < не меняем 9 н а х о д и т с я на своем месте. i=2
Второй просмотр: р а с с м а т р и в а е м часть массива с первого до предпоследнего э л е м е н т а .
Часть третья
246 1=1
4
1=2
4
5 < 5
1=3
4
2
2 8 9 не м е н я е м 2 8 9 > меняем 5 8 9 < не м е н я е м
8 — на с в о е м м е с т е . Третий просмотр: р а с с м а т р и в а е м а я ч а с т ь м а с с и в а с о д е р ж и т три первых элемента. 1=1 4 2 5 8 9 > меняем 1=2 2 4 5 8 9 < не м е н я е м 5 — на своем месте. Четвертый ментов. i=l
просмотр:
р а с с м а т р и в а е м п о с л е д н ю ю п а р у эле-
2
4 5 8 9 < не меняем 4 — н а с в о е м м е с т е . Н а и м е н ь ш и й э л е м е н т — 2 о к а з ы в а е т с я на первом месте. Количество просмотров элементов массива равно N-1. И т а к , н а ш м а с с и в о т с о р т и р о в а н . Этот м е т о д т а к ж е н а з ы в а ю т м е т о д о м « п у з ы р ь к а » . Н а з в а н и е это п р о и с х о д и т от о б р а з н о й интерпретации, при которой в процессе в ы п о л н е н и я сортировки более « л е г к и е » э л е м е н т ы ( э л е м е н т ы с з а д а н н ы м с в о й с т в о м ) мало-помалу всплывают на «поверхность». Procedure Sort(N:Integer;Var A:MyArray); Var k,i,w:Integer;{*k - номер просмотра, изменяется от 1 до N-1; i — номер первого элемента рассматриваемой пары; w — рабочая переменная для перестановки местами элементов массива. *} Begin For k:=l То N-1 Do (*Цикл по номеру просмотра. *} For i:=1 То N-k Do I f A[i]>A[i+l] Then {'Перестановка элементов.*} Begin w:=A[i];A[i]:=A[i+l];A[i+l]:=w; End; End;
Массив — фундаментальная структура данных
247
П р и с о р т и р о в к е методом «пузырька» в ы п о л н я е т с я N-1 просмотров, на к а ж д о м i-просмотре п р о и з в о д и т с я N-i сравнений. Общее к о л и ч е с т в о С равно N*(N-1 )/2, и л и 0(N2). Сортировка простыми вставками. Сортировка э т и м методом п р о и з в о д и т с я последовательно ш а г за ш а г о м . Н а г-м ш а г е с ч и т а е т с я , ч т о ч а с т ь массива, с о д е р ж а щ а я п е р в ы е i - l элементов, у ж е у п о р я д о ч е н а , то есть А[1 ]<A[2]<...<A[i-l]. Далее бер е т с я г-й элемент, и д л я него подбирается место в отсортированной части массива, такое, что после его вставки упорядоченность не н а р у ш а е т с я , то есть требуется н а й т и такое j (0<j<i-l), что A[j]<A[i]<A[j+l] (при j=0 происходит только одно сравнение, если не и с п о л ь з о в а т ь «барьерную» т е х н и к у ) . З а т е м выполняется в с т а в к а э л е м е н т а A[i] на место j. Н а к а ж д о м ш а г е отсортир о в а н н а я ч а с т ь массива у в е л и ч и в а е т с я . Д л я в ы п о л н е н и я полной с о р т и р о в к и потребуется в ы п о л н и т ь N-1 ш а г . Р а с с м о т р и м этот процесс на примере. Пусть требуется отсорт и р о в а т ь массив из 10 элементов по возрастанию. 1-й шаг
2-й шаг
3-й шаг
4-й шаг
5-й шаг. 6-й шаг.
8-й шаг.
9-й шаг.
13.6 8 11 3 1 5 9 15 7 Рассматриваем часть массива из одного элемента 13 Вставляем второй элемент массива 6 так, чтобы упорядоченность сохранилась. Так как 6< 13, записываем 6 на первое место Отсортированная часть массива содержит два элемента (6 13) 6 138 11 3 1 5 9 15 7 Возьмем третий элемент массива 8 и подберем для него место в упорядоченной части массива 8>6 и 8< 13, следовательно, его место второе. Следующий элемент — 11 Он 6 8 13 11 3 1 5 9 15 7 записывается в упорядоченную часть массива на третье место, так как 11 >8, но 11<13 6 8 11 13 3 1 5 9 15 7 Далее, действуя аналогичным образом, определяем, что 3 необходимо записать на первое место 3 6 8 11 13 1 5 9 15 7 По той же причине 1 записываем на первое место 1 3 6 8 11 13 5 9 15 7 Так как 5>3, но 5<6, то место 5 в упорядоченной части — третье Место числа 9 — шестое 1 3 5 6 й 9 11 13 15 7 Определяем место для предпоследнего элемента 15 Оказывается, что этот элемент массива уже находится на своем месте. 1 Я SB R 9 11 13 15 7 Осталось подобрать подходящее место для последнего элемента 7. 1 3 5 6 7 8 9 11 13 15 Массив отсортирован полностью.
247 Часть третья Процедура сортировки. Procedure Sort(N:Integer;Var Var l,j,w:Integer; Begin For l:=2 To N Do Begin w:=Л[i]; j:=i-l; While (J>0) And (w<A[j]> Dec ( j ) ;End; A[j+l]:=w; End; End;
A:MyArray);
Do Begin
A [ j +1 ] : —A [ j ] ;
Оценим эффективность метода. Д л я массива, например, 1 2 3 4 5 6 7 8 п о т р е б у е т с я N-1 с р а в н е н и е ( н а п о м н и м , ч т о м ы сортируем в порядке возрастания), а для массива 8 7 6 5 4 3 2 1 — N*(N-1 )/2 и л и C=0(N2). Э к с п е р и м е н т а л ь н ы й раздел работы Сортировка подсчетом. П у с т ь д а н м а с с и в (А) и з 10 элементов: 10, 5, 11, - 5 , 1, - 4 , 13, 12, - 4 , 13. В о з ь м е м п е р в ы й элем е н т , он б о л ь ш е п я т и э л е м е н т о в . З а п и ш е м 5 в д о п о л н и т е л ь н ы й м а с с и в с ч е т ч и к о в (Count). В ы п о л н и м э т у о п е р а ц и ю д л я всех э л е м е н т о в м а с с и в а А . В м а с с и в е Count и м е е м : 5, 4, 6, 0, 3, 1, 8, 7, 2 , 9 . Е с л и р а з р е ш а е т с я и с п о л ь з о в а т ь д о п о л н и т е л ь н ы й м а с с и в д л я х р а н е н и я о т с о р т и р о в а н н ы х д а н н ы х , то о с т а е т с я п е р е п и с а т ь к а ж д ы й э л е м е н т и с х о д н о г о м а с с и в а на с о о т в е т с т в у ю щ е е м е с т о в р е з у л ь т и р у ю щ е м м а с с и в е (В). Procedure Sort(N:Integer;A:MyArray;Var Var i,j:Integer; Count:MyArray; Begin FillChar(Count,SizeOf(Count),0); For i:=N DownTo 2 Do For j:=i-l DownTo 1 Do I f A[i]<A[j] Then Inc(Count Inc(Count [i]); For i : =1 To N Do В [Count [i]+l End;
B:MyArray);
[j]) ] :=A[i]
Else ;
Е с т е с т в е н н о е ж е л а н и е — у б р а т ь м а с с и в В . Н и ж е по т е к с т у п р и в е д е н а с о о т в е т с т в у ю щ а я п р о ц е д у р а . Р а з б е р и т е её. П о к а ж и те, ч т о она д е й с т в и т е л ь н о работоспособна. В к а ч е с т в е подсказ-
Массив — фундаментальная структура данных 248 к и п р и в е д е м з н а ч е н и я э л е м е н т о в м а с с и в а Count, на е д и н и ц у . 6 5 7 1 4 2 9 8 3 I I I I
увеличенные
10
Procedure Swap(Var х,у:Integer); Var w: IntegersBegin w: =x;x: =y; у: =w; EndsProcedure Sort (N: Integer ; Var A:MyArray); Var l,J:Integer; Count:MyArray; Begin FillChar(Count,SizeOf(Count),0) ; For l:=N DownTo 2 Do For j:=i-l DownTo 1 Do I f A[i]<A[j] Then Inc(Count[j]) Else Inc(Count [l]) ; For i:=l To N Do Inc(Count[l]); For l:=1 To N Do While Count[l]<>i Do Begin Swap(A[l],A[Count[l]]);Swap(Count[l],Count [Count [ i ] ] ) ; EndsEnd; П р е д п о л о ж и м , ч т о з н а ч е н и я элементов массива А принадлеж а т и н т е р в а л у [и,и]. Н а п р и м е р , А состоит и з элементов: 2, 4, 3, 2, 4, 2, 3, 4, 3, 2. Подсчитаем, сколько раз к а ж д о е значение встречается в массиве: д в о й к а — 4 раза, тройка — 3 раза и четв е р к а — 3 р а з а (массив Count: 4, 3, 3). И з м е н и м з н а ч е н и я Count на 4, 7, 10. П р и этих з н а ч е н и я х (Count) элементы массива А м о ж н о сразу з а п и с ы в а т ь на свое место в результирующий массив В . Procedure Sort (N: Integer ;A:MyArray ; Var B:MyArray) {*Считаем, что значения элементов массива А принадлежат интервалу [1..4]. *} Const u=l;v=4; Var i:Integer; Count .-Array [u. .v] Of IntegersBegin FillChar (Count,SizeOf(Count) ,0) ; For i : =1 To N Do Inc (Coun t [A[i] ]) ;
;
Часть третья
250
F o r i : =и+1 То v Do Inc ("Count [i] , C o u n t [l-l } ) ; For i:=l To N Do Begin В[Count[A[l]]]:=A[l];Dec(Count[A[l]]); End; End; А м о ж н о л и не и с п о л ь з о в а т ь м а с с и в В ? О к а з ы в а е т с я , д а . В с л е д у ю щ е й в е р с и и п р о ц е д у р ы Sort п о к а з а н о , к а к это с д е л а т ь . Procedure Sort f t : I n t e g e r ; V a r A:MyArray); Const u=l;v=4; Var l,j,q:Integer; Count:Array[u..v] Of Integer; Begin FillChar(Count,SizeOf(Count),0); For i:=l To t Do Inc (Count [A[i] ]) ; q:=l! For i:=u To v Do For j:=l To Count[l] Do Begin A[q] : =1; Inc (q) ; End; End; З а д а н и я для самостоятельной работы 1. И з м е н и т ь р е ш е н и я в т р е х р а с с м о т р е н н ы х м е т о д а х т а к , чтобы осуществлялась сортировка: • четных элементов массива; • э л е м е н т о в , з а п и с а н н ы х на н е ч е т н ы х м е с т а х ; • отрицательных элементов массива и т.д. 2. Д а н , н а п р и м е р , м а с с и в 12 3 5 7 9 10. З а о д и н п р о с м о т р метод о м « п у з ы р ь к а » он с т а н о в и т с я о т с о р т и р о в а н н ы м , о с т а л ь н ы е п р о с м о т р ы н и ч е г о не д а ю т . И с к л ю ч и т ь л и ш н и е п р о с м о т р ы . 3. Массив 1 2 3 5 7 9 1 0 с о р т и р у е т с я методом « п у з ы р ь к а » за один п р о с м о т р , а м а с с и в 5 7 9 10 1 2 3 — з а п я т ь (N-1). Я в н о е нер а в н о п р а в и е . Устранить его м о ж н о п у т е м с м е н ы н а п р а в л е н и й п р о с м о т р о в , т . е. п е р в о н а ч а л ь н о в н а п р а в л е н и и получаем 5 7 9 10 3 12, а з а т е м в н а п р а в л е н и и <- р е з у л ь т а т — 3 5 7 9 10 12. И т а к , чередуем н а п р а в л е н и я , п о к а м а с с и в не будет отсортирован. 4. О б ъ е д и н и т ь т р е б о в а н и я з а д а н и й 2 и 3 в единое целое. Этот метод н а з ы в а е т с я « ш е й к е р - с о р т и р о в к о й » . Р е а л и з о в а т ь его. 5. В с о р т и р о в к е п р о с т ы м и в с т а в к а м и у б р а т ь п е р е м е н н у ю х, т. е. в н у т р е н н и й ц и к л з а п и с а т ь в в и д е : While А[0]<А[j] Do. Под-
Массив — фундаментальная структура данных
251
с к а з к а . Массив А необходимо сделать типа Array[O..Nmax] Of Integer и i-й элемент записывать на 0 место, так называем ы й прием «барьерного элемента». 6. Метод вставок. На момент вставки элемента с номером i элементы массива с номерами от 1 до i - l отсортированы. Выберем из них средний элемент (или один из двух средних) и сравним его с элементом A[i], Если А[i] меньше этого элемента, то поиск места вставки следует продолжать в левой половине, иначе — в правой половине отсортированного массива. Эта схема получила название сортировки бинарными вставками. Она предложена Д ж . М о ч л и в 1946 году и была одной из первых п у б л и к а ц и й по методам компьютерной сортировки. Реализовать данную схему. 7. Д. JI. Ш е л л в 1959 году предложил следующую модификацию метода — «сортировку с убывающим шагом». Ее суть пок а ж е м на примере. Массив из 8 элементов. Делим его на 4 группы по 2 элемента, сортируем элементы в каждой группе. Затем — на 2 группы по 4 элемента, выполняем для них сортировку и, наконец, сортируем одну группу из 8 элементов. При простых вставках мы перемещали элемент только в соседнюю позицию, а в этом методе перемещаем на большие расстояния, что приводит к получению более отсортированного массива и, в конечном итоге, к повышению эффективности метода. З н а ч е н и я 4, 2, 1 (приращения) не являются догмой. Можно выполнить сортировку при 5, 3, 1. Последний элемент должен быть равен 1. Д. Кнут дает оценку метода при грамотном выборе приращений — 0(N' 2). Лучшим считается выбор в качестве значений приращений взаимно простых чисел. Реализовать «сортировку с убывающим шагом» д л я заданных значений приращений. 8. Пусть N является точным квадратом натурального числа, например 3. Разделим массив на Sqrt(N) групп по Sqrt(N) элементов в каждой. Выберем максимальный элемент в каждой группе... Проще рассмотреть пример. Дан массив 7 10 3 5 15 9 6 12 8. При разбивке на группы — (7 10 3) (5 15 9) (6 12 8). Максимальные элементы 1 0 1 5 12. Максимальный из них 15, он во второй группе. Если оставшиеся элементы из второй группы, а это 5 и 9, меньше 10 и 12, то мы нашли сразу три элемента, записываемые на свои места. Если нет, то заменяем максимальные элементы элементами из группы. Этот метод предложен Э. Х.Фрэндом в 1956 году и получил название метода квадратичного выбора. Количество сравнений имеет порядок 0(N*Sqrt(N)). Реализовать метод.
Часть третья
252 Занятие №
20. Методы быстрой
сортировки
План занятия • изучение методов сортировки с временной 0( N*LogN ); • выполнение самостоятельной работы.
оценкой
Сортировка слияниями. П р е ж д е ч е м о б с у ж д а т ь метод, рассмотрим следующую задачу. Объединить («слить») упорядоченн ы е ф р а г м е н т ы м а с с и в а A A[k],...A[m] и А[т+1] A[q] в о д и н A[k] A[q], е с т е с т в е н н о , т о ж е у п о р я д о ч е н н ы й (kmq). О с н о в н а я и д е я р е ш е н и я состоит в с р а в н е н и и о ч е р е д н ы х элементов к а ж д о г о ф р а г м е н т а , в ы я с н е н и и , к а к о й и з э л е м е н т о в меньш е , п е р е н о с е его во в с п о м о г а т е л ь н ы й м а с с и в D ( д л я п р о с т о т ы ) и п р о д в и ж е н и и по т о м у ф р а г м е н т у м а с с и в а , и з к о т о р о г о в з я т э л е м е н т . П р и этом следует н е з а б ы т ь з а п и с а т ь в D о с т а в ш у ю с я ч а с т ь того ф р а г м е н т а , к о т о р ы й не у с п е л себя « и с ч е р п а т ь » . Пример. П у с т ь п е р в ы й ф р а г м е н т состоит и з 5 э л е м е н т о в : 3 5 8 11 16, а в т о р о й — и з 8: 1 5 7 9 12 13 18 20. Р и с у н о к и л л ю с т р и рует л о г и к у о б ъ е д и н е н и я ф р а г м е н т о в .
Procedure SI(к,т,q:Integer;Var A:MyArray) ; Vari, j, t:Integer; D:MyArray; Begin i:=k; j:=m+I; t:=l; While (i<=m) And (j<=q) Do Begin {*Пока не закончился хотя бы один ф р а г м е н т . * } I f A[i]<=A[j] Then Begin D[t]:=A[i]; Inc(i);
End
Массив — фундаментальная структура данных 252 Else Begin D[t]:=А[j]; I n c ( j ) ; End; Inc(t) ; End;("Один из фрагментов обработан полностью, осталось перенести в D остаток другого фрагмента. *} While K=m Do Begin D[t] :=A(i] ; Incd) ; Inc(t) End; While J<=q Do Begin D[t] :=A[j]; Inc(j) ; Inc(t) End; For l: =1 To t-1 Do A [k+i-1 ] : =D [1 ] ; End; П а р а м е т р чп и з з а г о л о в к а процедуры м о ж н о убрать. М ы «сливаем» ф р а г м е н т ы одного массива А. Достаточно оставить н и ж н ю ю и в е р х н ю ю г р а н и ц ы фрагментов, т. е. — Sl(k,q:Integer;Var A:MyArray), где k — н и ж н я я , a q — в е р х н я я г р а н и ц ы . В ы ч и с л е н и е m (эта п е р е м е н н а я становится л о к а л ь н о й ) сводится к п р и с в о е н и ю : m:=k+(q-k) Div 2. П р и этом уточнении приведем процедуру с о р т и р о в к и . П е р в ы й вызов процедуры — Sort(l,N). Procedure Sort(i,j:Integer}; Var t:Integer; Begin I f K j Then ("Обрабатываемый фрагмент массива состоит более, чем из одного элемента. *} I f J-1=1 Then Begin I f A[j]<A[i] Then Begin {"Обрабатываемый фрагмент массива состоит из двух элементов. *} t: =А [1 ] ; А [ i ] : =А [ j ] ;А [ j ] := t; EndsEnd Else Begin Sort ( i , i + ( j - i ) Div 2);{"Разбиваем заданный фрагмент на два. *} Sort (i+(j-i> Div 2 + 1, j ) ; SI (i,J,A) ; EndsEnd; Метод с л и я н и й — один и з первых в теории алгоритмов сорт и р о в к и . Он п р е д л о ж е н Д ж . фон Н е й м а н о м в 1945 году. В лог и к е р е а л и з о в а н один из ф у н д а м е н т а л ь н ы х п р и н ц и п о в информ а т и к и — «разделяй и властвуй». Р е к о м е н д у е т с я проделать с у ч а щ и м и с я «ручную» трассировку работы процедуры н а разл и ч н ы х п р и м е р а х . Это, во-первых, позволит в очередной раз г л у б ж е понять суть п р и н ц и п а и, во-вторых, проверить понимание и усвоение т е м ы «рекурсия». Эффективность алгоритма, по Д. К н у т у , составляет C=0(N*logN).
Часть третья
254
Быстрая сортировка. Метод предложен Ч. Э. Р . Хоаром в 1962 году. Эффективность метода достаточно высокая, кроме специально подобранных данных, поэтому автор назвал его «быстрой сортировкой». Идея метода. В исходном массиве А выбирается некоторый элемент X (барьерный элемент). Нашей целью является запись X «на свое место» в массиве, пусть это будет место ft, такое, что слева от X были элементы массива, меньшие или равные X , а справа — элементы массива, большие X, т. е. массив А будет иметь вид: (А[ 1 ], А[2], .... A[k-1]), A[k] (X). (A[k+1], ..., А[п]). В результате элемент A[k] находится на своем месте и исходный массив Л разделен на две неупорядоченные части, барьером между которыми является элемент A [ k ] . Дальнейшие действия очевидны — независимо сортировать полученные части по той же логике до тех пор, пока не останутся части массива, состоящие из одного элемента, то есть пока не будет отсортирован весь массив. Пример. Исходный массив состоит из 8 элементов: 8 12 3 7 19 11 4 16. В качестве барьерного элемента возьмем средний элемент массива (7). Произведя необходимые перестановки, получим: (4 3) 7 (12 19 11 8 16), элемент 7 находится на своем месте. Продолжаем сортировку. Левая часть: (3) 4 7 (12 19 11 8 16) 3 4 7 (12 19 11 8 16) Правая часть: 3 4 7 (8) 11 (19 12 16) 3 4 7 8 11 (19 12 16) 3 4 7 8 11 12 (19 16) 3 4 7 8 11 12 (16) 19 3 4 7 8 11 12 16 19 Из вышеизложенного описания явно просматривается рекурсивная схема реализации, параметрами которой являются н и ж н я я и верхняя границы изменения индексов сортируемой части исходного массива. Приведем процедуру быстрой сортировки из книги классика информатики Н . Вирта. Procedure Quicksort(т,t: Vari,j,x,w: Integer; Begin i : = m ; j;=t;x:=A[ (m+t) Div Repeat While A [i] <x Do Incd);
Integer); 2];
Массив — фундаментальная структура данных
While A[j]>x Do Dec(j); I f K=j Then Begin w:=A[i]; Inc (i) ; Dec ( j ) ; End Until i>j; I f m<j Then Quicksort (m,j); I f Kt Then Quicksort (l, t) ; End;
255
A[i]:=A[j];
A[j]:=w;
П р о с т а я процедура, но в о з н и к а е т « т ы с я ч а и один вопрос». Н а ч н е м . Соответствуют л и р е з у л ь т а т ы работы п р о ц е д у р ы расс м о т р е н н о м у в ы ш е п р и м е р у , естественно, п р и тех ж е и с х о д н ы х д а н н ы х ? О к а з ы в а е т с я , что нет. Л у ч ш е , если в этом у ч а щ и е с я у б е д я т с я с а м о с т о я т е л ь н о . Р е з у л ь т а т ы работы п р о ц е д у р ы выг л я д я т с л е д у ю щ и м образом (первые два столбца содержат парам е т р ы п р о ц е д у р ы , т р е т и й — массив А). т
t
1
8
А 4 12 3 7 19 11 8 16
1
8
4 7 3 12 19 11 8 16
1
3
4 3 7 12 19 11 8 16
1
2
3 4 7 12 19 11 8 16
4
8
3 4 7 8 19 11 12 16
4
8
3 4 7 8 11 19 12 16
4
5
3 4 7 8 11 19 12 16
6
8
3 4 7 8 11 12 19 16
7
8
3 4 7 8 11 12 16 19
П р о д о л ж и м н а ш и р а с с у ж д е н и я . В процедуре есть сравнение «If i<=j Then < п е р е с т а н о в к а элементов А[i] и А[j]>». Получается, что п р и равенстве м ы в ы п о л н я е м перестановку элементов м а с с и в а . Эту к о н с т р у к ц и ю з а м е н и м следующей «If i<j Them. Р е з у л ь т а т — б е с к о н е ч н а я работа процедуры ( « з а ц и к л и в а н и е » ) . Р е к у р с и в н ы й в ы з о в процедуры о с у щ е с т в л я е т с я п р и сравнении «If т<] Then ...». А если просто в ы з в а т ь ? Р е з у л ь т а т не замедл и т с к а з а т ь с я . Произойдет переполнение стека адресов возврата. К о н с т р у к ц и я «Repeat ... Until i>j;» работает и п р и ;=/', что к а ж е т с я неестественным. З а м е н и м ее на «Until i>=j>. Что произойдет? В ц и к л е «Repeat... Until i>j;» еще два ц и к л а т и п а While, п р и ч е м по п е р е м е н н ы м внешнего ц и к л а . К а к этот ф а к т т р а к т у е т т е х н и к а п р о г р а м м и р о в а н и я , м о ж е т быть, к а к «дурной тон»? П р и и з у ч е н и и т е м ы «рекурсия» всегда обращается вним а н и е н а то, что в т а к о й л о г и к е обязательно д о л ж н а б ы т ь «заг л у ш к а » . К а к она р е а л и з о в а н а здесь? З а к л ю ч и т е л ь н ы м «ак-
Часть третья
256
кордом» обсуждения процедуры может быть предложение о н а п и с а н и и в е р с и и л о г и к и , с о о т в е т с т в у ю щ е й п р и м е р у , рассмотренному в начале параграфа. Один из вариантов р е а л и з а ц и и п р и в о д и т с я н и ж е по т е к с т у . Procedure Quicksort (т,t: Integer); Var i,j,x,w: Integer; Begin i:=m; j:=t;x:=A[(m+t) Div 2]; While K=j Do I f A [1] <x Then Inc (i) Else I f A[j]>x Do Dec ( j ) Else Begin w:=A[i]; A[i]:=A[j]; Inc (1);Dec(j);End; I f m<j Then Quicksort (m,j); I f Kt Then Quicksort (i, t) ; End;
A[j]:=w;
С этой п р о ц е д у р о й м о ж н о п р о д е л а т ь « э к з е к у ц и ю » , аналогичную той, что выполнена с п р е д ы д у щ е й процедурой: измен и т ь о п е р а т о р ы с р а в н е н и я , и л и п а р а м е т р ы р е к у р с и в н о г о вызова, и л и у с л о в и е ц и к л а While. В к о н е ч н о м итоге п о л у ч а е т с я и н т е р е с н о е з а н я т и е , особенно е с л и с о в м е щ а т ь « р у ч н у ю » трассировку логики с пошаговым выполнением процедур. Оценим эффективность метода. П р е д п о л о ж и м , что размер м а с с и в а р а в е н ч и с л у , я в л я ю щ е м у с я с т е п е н ь ю д в о й к и (N=21), и п р и к а ж д о м р а з д е л е н и и э л е м е н т X н а х о д и т с я т о ч н о в середине массива. В этом случае при первом просмотре выполняется N с р а в н е н и й и м а с с и в р а з д е л и т с я н а две ч а с т и р а з м е р а м и -N/2. Д л я к а ж д о й и з э т и х ч а с т е й N/2 с р а в н е н и й и т . д. Следовательно C=N+2*(N/2)+4*(N/4)+...+N*(N/N)=0(N*logN). Е с л и N не я в л я е т с я с т е п е н ь ю д в о й к и , то о ц е н к а будет и м е т ь тот ж е порядок. Д л я н е к о т о р ы х и с х о д н ы х д а н н ы х в р е м я с о р т и р о в к и пропорц и о н а л ь н о 0(N2). В э т о м с л у ч а е н а к а ж д о м ш а г е р а з м е р сортир у е м о й ч а с т и массива у м е н ь ш а е т с я только на единицу. Попробуем построить т а к и е последовательности д л я р а з л и ч н ы х з н а ч е н и й N . Ограничимся случаем, когда исходными данными является п е р е с т а н о в к а ч и с е л от 1 до N. Л о г и к у б ы с т р о й с о р т и р о в к и можно дополнить счетчиком числа сравнений, генерировать все п е р е с т а н о в к и к а к и с х о д н ы е д а н н ы е и з а п о м и н а т ь те, котор ы е д а ю т м а к с и м а л ь н о е ч и с л о с р а в н е н и й . А если N б о л ь ш о е ч и с л о , н а п р и м е р до 10000? Этот путь исследования не подходит. Попробуем по-другому. П р и N = 3 одной и з и с к о м ы х последовательностей я в л я е т с я 1, 3, 2. П р и первом просмотре 3 переставляет-
Массив — фундаментальная структура данных
257
ся с 2 и рекурсивно вызывается обработка последовательности из двух п е р в ы х элементов. П р и N=4 п е р е с т а н о в к а элементов на первой и т е р а ц и и д о л ж н а п р и в о д и т ь к тому, что на второй итер а ц и и с о р т и р у е т с я 1, 3, 2, а это последовательность 1, 4, 2, 3. И с х о д я и з этого п р и н ц и п а , п р о д о л ж и м «ручное» построение последовательности. Р е з у л ь т а т ы д л я п е р в ы х з н а ч е н и й N приведены в таблице.
Е с л и с г е н е р а ц и е й первой части последовательности сложностей нет ( н а ч и н а е т с я с 1, а затем ч е т н ы е ч и с л а , н а ч и н а я с 4), то в т о р у ю п о л у ч и т ь н е с к о л ь к о с л о ж н е е . П р и н е ч е т н ы х значен и я х N на месте N Div 5 +1 з а п и с ы в а е т с я N. А затем н а ч е т н ы х местах j ч и с л а j-1 и н е ч е т н ы х j — первое четное число, получаемое в р е з у л ь т а т е о п е р а ц и и ]:-=(j+l) Div 2, и з которого т о ж е в ы ч и т а е т с я единица. И с к л ю ч е н и я д л я в ы ч и т а н и я единицы следует сделать д л я з н а ч е н и я j, равного 2. П р о г р а м м а г е н е р а ц и и последовательности, д л я которой сортировка Х о а р а имеет врем е н н у ю с л о ж н о с т ь О ( N 2 ) , имеет вид. Program Var i , j Begin WriteLn ReadLn Write( i : =2 ; While Write Inc(i) End; 9—452
sequence; , N: Integers(' Длина последовательности (N) ; '1',' '); (2*1<=N) Do (2*1, ' ' ) ; ;
Begin
');
Часть третья
258 I f (N>1) And Odd(N) Then Begin Write (N,' '); Inc ( l ) ; End; While i<=N Do Begin J : =i ; While Odd(j) Do J:=(J+1) Div 2; I f j>2 Then Write ( j - 1 , ' ' ) Else Write Inc (H ; End; WriteLn; End.
(J , '
');
Пирамидальная сортировка. Метод предложен Д ж . У . Д ж . У и л ь я м с о м и Р . У. Ф л о й д о м в 1 9 6 4 году. Элемент ы м а с с и в а А о б р а з у ю т п и р а м и д у , е с л и д л я всех з н а ч е н и й i вып о л н я ю т с я у с л о в и я : A[i]<А[2*i] и A[i]<А[2*i+l]. П р и м е р . Элем е н т ы м а с с и в а 8 10 3 6 1 3 9 5 12 не о б р а з у ю т п и р а м и д у (А[1 ]>А[3], А[2]>А[4] и т. д.), а э л е м е н т ы 3 6 5 10 13 9 8 12 — образуют. Очевидно, что часть элементов массива A[N/2+1..N] н е з а в и с и м о от и х з н а ч е н и й о б р а з у е т п и р а м и д у . П р е д п о л о ж и м , что н а м н е о б х о д и м о о т с о р т и р о в а т ь э л е м е н т ы м а с с и в а А (8 10 3 6 1 3 9 5 12) по н е в о з р а с т а н и ю . И д е ю с о р т и р о в к и п о я с н и м с пом о щ ь ю следующей т а б л и ц ы (курсивом в ы д е л е н а ч а с т ь элементов массива, о б р а з у ю щ и х п и р а м и д у , ж и р н ы м ш р и ф т о м — отсортиров а н н а я ч а с т ь массива). В к о н е ч н о м итоге массив отсортирован. 1 2 3 4 5 6 7 8 9 10 11 12
А 8 10 3 6 13 9 5 12 8 10 3 6 13 9 5 12 8 10 3 6 13 9 5 12 863 10 13 9 5 12 3 6 5 10 1398 12 1265 10 13983 1265 10 13983 1265 10 13 983 1265 10 13983 568 10 139 123 1268 10 1 3 9 5 3 1268 10 139 5 3
Комментарии || Строим пирамиду из элементов Ас 1-го по 8-й
16 17
9 108 12 1 3 6 5 3 9 10 6 / 2 / 3 6 5 3
Меняем местами 1-й и 6-й элементы Строим пирамиду из элементов Ас 1-го по 5-й
Меняем местами 1-й и 8-й элементы Строим пирамиду из элементов Ас 1-го по 7-й
Меняем местами 1-й и 7-й элементы Строим пирамиду из элементов А с 1-го по 6-й
Массив — фундаментальная структура данных 258
П у с т ь м ы умеем строить пирамиду и з элементов массива А от 1 до q (процедура Pyram(q)), тогда процедура сортировки имеет вид: Procedure Sort; Var t,w:Integer; Begin t:=N; Repeat Pyram(t);("Строим пирамиду из t элементов."} w: =A [ 1 ] ; A [ 1 ] : =A [ t ] ; A [ t ]:=w;("Меняем местами и t-й элементы."} Dec (t) ; Until t<2; End;
1-й
Из т а б л и ц ы м ы видим, что процесс построения п и р а м и д ы н а ч и н а е т с я с к о н ц а массива. Последние q Div 2 элементов являю т с я п и р а м и д о й по той простой п р и ч и н е , что удвоение индекса выводит нас за пределы массива. А затем берется очередной справа элемент и «проталкивается» по массиву до тех пор, пока он не будет м е н ь ш е элемента с удвоенным индексом по отношению к индексу рассматриваемого элемента. Процесс продолжается до тех пор, п о к а м ы «не поставим» на свое место первый элемент массива. Приведем текст процедуры. Procedure Pyram(q:Integer); Var r,i,j,v:Integer; pp:boolean; Begin r:=q Div 2+1;("Эта часть массива является пирамидой."I While г>1 Do Begin("Цикл по элементам массива, для которых необходимо найти место в пирамиде.") Dec (г) ; i:=r; v:=A[i];{"Индекс рассматриваемого элемента и сам элемент."}
259 Часть третья j:=2*i;{'Индекс элемента, с которым происходит сравнение. *} pp:=False;{*Считаем, что для элемента не найдено место в пирамиде. *} While (J<=q) And Not pp Do Begin I f j<q Then I f Л[J]>A[j+1] Then Inc ( j ) ; l *Сравниваем с меньшим элементом. *} I f v<=A[j] Then pp:=True (*Элемент находится на своем месте. *I Else Begin A [i] :=А [ j ] ; 1: =j ; j : =2*i ;Ena; {*Переставляем элемент и идем дальше по пирамиде.*} End; A[i]:=v; End; End; Метод имеет эффективность п о р я д к а 0(N^logN). Однако если рассмотреть в ы ш е п р и в е д е н н у ю л о г и к у , то о к а ж е т с я , что ее о ц е н к а 0(N2*logN). П р и сортировке п р о ц е д у р а Pyram вызывается N-1 раз. Эта процедура состоит и з двух в л о ж е н н ы х циклов сложности N и — logN. Итак, упорядочивание одного элемента требует не более logN действий, построение полной пирамиды NHogN действий. В в ы ш е п р и в е д е н н о й л о г и к е после к а ж д о й перестановки элементов вновь строится п и р а м и д а д л я оставшихся элементов массива. А зачем? П и р а м и д а почти есть. Требуется только «протолкнуть» верхний элемент на свое место (строки т а б л и ц ы с н о м е р а м и 7 - 9 , 1 2 - 1 4 , 1 7 - 1 8 , 2 1 - 2 2 , 2 5 - 2 6 , 28). Выд е л и м «проталкивание» одного элемента в отдельную процедуру Pr( r,q), где г — номер п р о т а л к и в а е м о г о элемента, q — верхнее з н а ч е н и е и н д е к с а . Procedure Pr(г,q:Integer); Var г,i,j,v:Integer; pp:Boolean; Begin i:=r; v:=A[i];(*Индекс рассматриваемого элемента и сам элемент. *) J:=2*i;{*Индекс элемента, с которым происходит сравнение. *} pp:=False;(*Считаем, что для элемента не найдено место в пирамиде. *} While ( j <=q} And Not pp Do Begin I f J<q Then I f A [ j ] >A [ j +1 ] Then I n c ( j ) ;
Массив — фундаментальная структура данных 260 I"Сравниваем с меньшим элементом. ") I f v<=A[j] Then pp:=True {"Элемент находится своем месте. *} Else Begin А[l]:=А[j];i:=j;j:=2"1/End; {"Переставляем элемент и идем дальше по пирамиде. *} End; A[i]:=v; End;
на
К а к и е и з м е н е н и я произойдут с процедурой S o r t ? Procedure Sort; Var t,ы,i:Integer; Begin t:=N Div 2+1;{"Эта часть массива является пирамидой. *} For i:=t-l DownTo 1 Do Pr(l,N);{"Строим пирамиду (только один раз). "} For i: =N DownTo 2 Do Begin w:=A[l];A[1]:=A[i];A[i]:=w;{"Меняем местами 1 -й и i-й элементы. *} Pr(1,1-1);{"Проталкиваем 1-й элемент.") EndsEnd; Сортировка 5 элементов за 7 сравнений. Этот метод «быстрее» р а с с м о т р е н н ы х ранее (5*Log5). Рассмотрим массив А из п я т и элементов. С п о м о щ ь ю процедуры Swap по значению индексов г, j п е р е с т а в л я ю т с я два элемента. Procedure Var Begin
Swap (i,j:Integer); t:Integer; t:=A[i];A[i]:=A[j];A[j]:=t;
End;
Д е й с т в и я , п р и в е д е н н ы е н а р и с у н к е (3 сравнения), приводят все 120 р а з л и ч н ы х п е р в о н а ч а л ь н ы х ситуаций к виду: А[1 ]<А[2]<А[4] и А[3]<А[4]. Ч т о м ы сделали? Сравнили п о п а р н о п е р в ы е ч е т ы р е элемента и с р а в н и л и м а к с и м а л ь н ы е э л е м е н т ы пар. Ч т о осталось сделать? Вставить А[5] в отсортир о в а н н ы й массив и з трех элементов, а затем вставить А[3] в отс о р т и р о в а н н ы й массив и з ч е т ы р е х элементов с учетом установленного ф а к т а А[3]<А[4]. Н а то и другое действие отводится по два с р а в н е н и я . Л о г и к а и х в ы п о л н е н и я приводится на рисунк а х . В н е к о т о р ы х с л у ч а я х , н а п р и м е р если А[5]<А[4], перестан о в к а элементов массива не в ы п о л н я е т с я . Объясните, почему возможны такие ситуации?
261 Часть третья
Swap(1,2) Swap(1,2) Swap(1,2)
Массив — фундаментальная структура данных
263
Т а к о й способ сортировки п я т и элементов п р е д л о ж е н Г. Б. Дем у т о м в 1956 году. Н и ж е п р и в о д и т с я ф р а г м е н т п р о г р а м м н о й р е а л и з а ц и и этой л о г и к и . Begin I f А[1]>А[2] Then Swap (1,2); I f А[3]>А[4] Then Swap (3,4),• I f A[2]>A[4] Then Begin Swap (2,4);Swap (1,3);End; I f A[5]<A[2] Then I f A[5]<A[1] Then Begin Swap(5,4);Swap(4,2);Swap(2,l);End Else Begin Swap(2,5);Swap (4,5); End Else I f A[5]<A[4] Then Swap(5,4); I f A[3]<A[2] Then I f A[3]<A[1J Then Begin Swap(2,3);Swap(1,2);End Else Swap (2,3) Else I f A[3]>A[4] Then Swap(3,4); End. Задания для самостоятельной работы 1. Д а н м а с с и в 7 9 13 1 8 4 10 11 5 3 6 2. В и д и м в о з р а с т а ю щ и й у ч а с т о к 7 9 13, а справа, если ч и т а т ь справа налево, есть участок 2 6. С л и я н и е э т и х двух ф р а г м е н т о в м а с с и в а дает 2 6 7 9 13. А з а т е м «сливаем» 1 8 и 3 5 11, получаем 1 3 5 8 11. Запис ы в а е м в м а с с и в и получаем: 2 6 7 9 13 4 10 11 8 5 3 1. Обратите внимание на то, к а к записываем! Продолжим. Если с перв ы м и ф р а г м е н т а м и 2 6 7 9 13 и 1 3 5 8 1 1 все ясно, то вторые 4 10 и 10 п е р е к р ы в а ю т с я . Необходимо учесть сей ф а к т и не д у б л и р о в а т ь 10 при з а п и с и в массив. П о л у ч а е м : 1 2 3 5 6 7 8 9 11 13 10 4. Последнее «слияние» дает 1 2 3 4 5 6 7 8 9 10 11 13. Массив отсортирован. Этот метод н а з ы в а е т с я у Д. К н у т а «сортировкой естественным двухпутевым слиянием». Реализ а ц и я его с использованием рекурсии доставит много хлопот, а при получении результата — удовлетворение от хорошо сдел а н н о й р а б о т ы . П р и этом следует не забывать, что тестиров а н и е р е ш е н и й д о л ж н о составлять не менее 30% времени Ваш е й р а б о т ы н а д программой! 2. И з м е н и м схему выбора участков д л я «слияния». Не будем иск а т ь м о н о т о н н ы е у ч а с т к и , а работаем с у ч а с т к а м и фиксиров а н н о й д л и н ы («простое двухпутевое с л и я н и е » ) . П р и м е р . И с х о д н ы й массив: 13 1 7 5 4 3 9 10 6 19 14 16 2 3 2 4 8.
Часть третья
264
1-й ш а г (длина 1).
8 13 2 7 4 16 9 19 10 6 14 3 23 5 4 1.
2-й ш а г (длина 2).
1 4 8 13 3 4 14 16 19 10 9 6 23 7 5 2.
3-й ш а г (длина 4).
1 2 4 5 7 8 13 23 19 16 14 10 9 6 4 3.
4-й ш а г (длина 8).
1 2 3 4 4 5 6 7 8 9 10 13 14 16 19 23.
Р е а л и з о в а т ь д а н н ы й алгоритм сортировки. Размер массива не всегда совпадает с числом, р а в н ы м степени двойки. И н т е г р а ц и я а л г о р и т м а п р о с т ы х вставок с п р е д ы д у щ и м у п р а ж н е н и е м дает новое з а д а н и е . Р а з д е л и м массив на учас т к и о п р е д е л е н н о й д л и н ы . К а ж д ы й из н и х отсортируем с п о м о щ ь ю а л г о р и т м а п р о с т ы х вставок, а з а т е м и с п о л ь з у е м «простое двухпутевое с л и я н и е » . Т а к , в п р е д ы д у щ е м примере слияние используется на 3-м и 4-м ш а г а х , а первые два заменяются работой по алгоритму простых вставок. Обобщите предыдущие задания заменой слова «двухпутевое» на «трехпутевое» («четырехпутевое», ..., «ft-путевое»). Значи тельное увеличение программного кода при этом недопустимо . Считается, что выбор элемента X с л у ч а й н ы м образом в мето де быстрой сортировки оставляет среднюю эффективность не изменной при всех типах массивов (упорядоченный, распре деленный по какому-то закону и т. д.). Р е а л и з у й т е эту идею. , Следует отметить, что метод быстрой сортировки является за тратным с точки зрения использования оперативной памяти Заменить рекурсивную схему реализации логики на не рекур сивную и оценить значение N, при котором В а ш а программа не будет компилироваться по этой причине. Разумеется, такую оценку можно сделать и при рекурсивной реализации, просто тип о ш и б к и будет другой. . Изменить логику работы в методе пирамидальной сортировк и так, чтобы элементы массива Л сортировались в порядке неубывания. . В таблице приводятся наименьшие известные значения числа сравнений, достаточных д л я сортировки последовательностей из N элементов. N
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Число сравнений
0
1
3
5
7
10
13
16
19
22
26
30
34
38
i
42
Разработайте алгоритмы сортировки д л я нескольких значен и й N из таблицы, больших 5.
Массив — фундаментальная структура данных
265
9. Д а н о N о т р е з к о в [x[i], y[i]] н а п р я м о й (i=l..N). Найти макс и м а л ь н о е k, д л я которого с у щ е с т в у е т т о ч к а п р я м о й , п о к р ы тая k отрезками. Число действий 0(N*LogN). П р и м е ч а н и е Записать левые и правые концы отрезков в один массив. Сформировать массив признаков, соответствующий концам отрезков, - 1 — левый коиец, +1 — правый конец. Отсортировать первый массив, одновременно переставляя элементы второго массива. Затем при просмотре массива подсчитывать суммы признаков. Максимальное значение этой суммы есть ответ задачи. 10. З н а ч е н и я м и э л е м е н т о в с о р т и р у е м о г о м а с с и в а я в л я ю т с я числ а 1, 2, 3. С о р т и р о в к а э л е м е н т о в по н е у б ы в а н и ю в ы п о л н я е т с я с п о м о щ ь ю п о п а р н ы х перестановок элементов. Отсортировать м а с с и в з а м и н и м а л ь н о е к о л и ч е с т в о с р а в н е н и й .
Часть третья
266
З а н я т и е № 21. П о и с к д а н н ы х План занятия • и з у ч е н и е р а з л и ч н ы х с х е м п о и с к а д а н н ы х , н а п и с а н и е и исс л е д о в а н и е п р о г р а м м по о б р а з ц а м п р о ц е д у р п о и с к а ; • в ы п о л н е н и е с а м о с т о я т е л ь н о й работы. Линейный поиск. Основной вопрос з а д а ч и п о и с к а : где в заданной совокупности д а н н ы х находится элемент, обладающий з а д а н н ы м свойством? Б о л ь ш и н с т в о з а д а ч п о и с к а с в о д и т с я к п р о с т е й ш е й — к п о и с к у в массиве э л е м е н т а с з а д а н н ы м значен и е м . Р а з б е р е м его. П у с т ь требуется н а й т и э л е м е н т X в массиве А. В д а н н о м с л у ч а е известно т о л ь к о з н а ч е н и е р а з ы с к и в а е м о г о элемента, никакой дополнительной информации о нем или о массиве, в к о т о р о м его надо и с к а т ь , нет. Н а ч н е м последовател ь н ы й п р о с м о т р м а с с и в а и с р а в н е н и е з н а ч е н и я очередного рассматриваемого элемента массива А с X. Н а п и ш е м фрагмент: For i:=l То N Do I f A[i]=X Then k:=i; Н а х о д и т с я место последнего э л е м е н т а в А, р а в н о г о X, при этом м а с с и в п р о с м а т р и в а е т с я п о л н о с т ь ю . А если первого? For i : =N DownTo 1 Do I f A[i]=X Then k:=i; А если и с к л ю ч и т ь л и ш н и е п р о в е р к и ? С о в п а д е н и е найдено, з а ч е м «пустая» работа? i : =1; While С1 <=N) And (A[i]OX) Do Inc(i); I f i=N+l Then <X нет в A> Else « з н а ч е н и е указывает на место совпадения>;
I
Э т и м ф р а г м е н т о м м ы р е ш а е м з а д а ч у п о и с к а первого совпад е н и я . Д л я н а х о ж д е н и я последнего с о в п а д е н и я его требуется изменить. i:=N; While (i>0) I f 1=0 Then на место
And (A[i] <>Х) Do Dec(i); <X нет в A> Else <значение совпадения>;
i
указывает
П р и м е ч а н и я 1. Необходимо обратить внимание на порядок проверки составного условия цикла. Предполагается, что второе условие ие проверяется, если результат логического выражения ясен после проверки первого условия. В противном случае возникает ошибка из-за выхода индекса за границы массива. 2. Использование директивы {$R+} уместно при изучении данной темы. 3. Замену цикла While иа цикл Repeat ... Until можно использовать в качестве упражнения.
Массив — фундаментальная структура данных
267
В с п о м н и м т е х н и к у «барьерных» элементов и з темы «сортировка» и п р и м е н и м ее д л я и з у ч е н и я п о и с к а элемента в массиве. Это требует и з м е н е н и я т и п а массива A (Type MyArray=Array [O.NMax] Of Integer, и л и МуАттау=Array[l..NMax+l] Of Integer-, Var A'MyArray, значение Nmax определяется в разделе констант). i: =1 ;А [N+1 ] : =Х; While А [ 1 ] OX Do Inc(i) ; I f i=N+l Then <X нет в A> Else <значение указывает на место совпадения>;
I
И л и д л я п о и с к а последнего совпадения: 1:=N;A[0]:=Х; While A [l] OX Do Dec (i) ; I f i-О Then <X нет в A> Else на место совпадения>;
<значение
I
указывает
Очевидно, что число сравнений зависит от места нахождения искомого элемента. В среднем количество сравнений равно (N+1) Div 2, в х у д ш е м случае, если элемента нет в массиве, N сравнений. Эффективность поиска O(N). Бинарный поиск. Если ж е заранее известна некоторая информ а ц и я о данных, среди которых ведется поиск, например массив данных отсортирован, то удается сократить время поиска, используя бинарный (двоичный, дихотомический, метод деления пополам — другие термины, обозначающие ту ж е идею) поиск. И т а к , требуется определить место X в отсортированном (например, в п о р я д к е неубывания) массиве Л . Идея бинарного метода стара к а к мир. Еще древние греки говорили — «разделяй и властвуй». Вот и делим, делим пополам и сравниваем X с элементом, к о т о р ы й находится на границе этих половин. Отсортированность А позволяет, по результату сравнения со средним элементом массива, и с к л ю ч и т ь из рассмотрения одну из половин. А остальное, остальное технические детали, требующие очень пунктуального п р о п и с ы в а н и я — необходимого, но, к счастью, не достаточного, н а в ы к а специалиста по информатике. П р и м е р . Пусть Х=6,
а массив А состоит из 10 элементов:
3 5 6 8 12 15 17 18 20 25. 1-й ш а г . Н а й д е м номер среднего элемента: m . - ^
Так
к а к 6<А[57, далее м о ж е м рассматривать только элементы, индексы которых меньше 5. 3 5 6 8 12 15
267 Часть третья 2-й шаг. Р а с с м а т р и в а е м л и ш ь первые 4 элемента массива, нахоГ1 + 41 д и м индекс среднего элемента этой части т=\ =2,
L 2 J
6>А[2], следовательно, первый и второй элементы и з рассмотрения и с к л ю ч а ю т с я : 3 - ё 6 8 12 16 17 18 20 25. 3-й ш а г . Р а с с м а т р и в а е м д в а э л е м е н т а , з н а ч е н и е 3 4 6 8 12 16 17 18 20 26; А [ 3 ] = 6 . Элемент н а й д е н , его н о м е р — 3. Программная реализация бинарного поиска может следующий вид.
иметь
Procedure Search; Var х,j,m:Integer; f:Boolean; Begxn i:=l; j:=N;{*Ha первом шаге рассматривается весь массив. *} f:=False;{'Признак того, что X не найден.*) Whxle (x<=j) And Not f Do Begxn m:=(x+j) Div 2;{*Или m:=1+ ( j - x ) Dxv 2;, так как x+(j~x) Dxv 2= (2*i+ ( j - l ) ) Div 2={i+j) Dxv 2. *) I f A[m]=X Then f:=True {*Элемент найден. Поиск прекращается. *} Else I f A [m] <X Then x : =m+l { *Исключаем из рассмотрения левую часть А. *] Else j:=m-l; {*Правую часть. *) End; End; П р е д л о ж и м е щ е одну, р е к у р с и в н у ю , р е а л и з а ц и ю и з у ч а е м о г о поиска. Значение переменной t является индексом искомого э л е м е н т а и л и н о л ь , если э л е м е н т не н а й д е н . Procedure Search(i,j:Integer;Var t:Integer); {*Массив А и переменная X - глобальные величины*} Var m : I n t e g e r s Began I f i>j Then t:=0 Else Begin m:=(i+j) Div 2; I f Aim] <X Then Search (m+1, j , t ) Else I f A[m]>X Then Search (i,m-l, t) Else t:=m; End; End;
Массив — фундаментальная структура данных 268 К а ж д о е сравнение у м е н ь ш а е т д и а п а з о н п о и с к а приблизительно в два р а з а . Общее количество с р а в н е н и й имеет порядок 0(N*logN ). Р а с с м о т р и м обратную задачу. Д а н а п о з и ц и я э л е м е н т а (Pos) в н е у п о р я д о ч е н н о м массиве А из N элементов и количество допус т и м ы х с р а в н е н и й (L). Определить все и н т е р в а л ы з н а ч е н и й N в д и а п а з о н е от 1 до 10000, п р и к о т о р ы х элемент А с номером Pos м о ж е т б ы т ь найден з а L с р а в н е н и й с п о м о щ ь ю а л г о р и т м а бинарного п о и с к а . Массив А, к а к таковой, в р е ш е н и и задачи не требуется. Необходимо п р о в е р я т ь , совпадает л и средний элемент о т р е з к а в схеме бинарного п о и с к а со з н а ч е н и е м Pos и за с к о л ь к о сравнен и й это совпадение достигается при з а д а н н о м з н а ч е н и и N. Оформим модифицированную схему бинарного поиска к а к функцию Сап. Function Can(N: Integer): Boolean; Var i,p,q,ll: IntegersBegin p ;= 0; q := N-1; 11 := 0; While p <= q Do Begin i := (p+q) shr 1; Inc (11) ; I f l = Pos Then Begin Can : = (11=L) ; Exit; End Else I f l > pos Then q := l-l Else EndsCan := False; End;
p := 2+1;
Поясним схему работы функции данными ручной трассировки логики ее работы д л я различных значений N при Pos= 10 и Ь=3.
270
Часть третья
И т а к , только одно значение N (из рассмотренных в таблице) удовлетворяет условию задачи (N=12). Осталось организовать ц и к л по к о л и ч е с т в у э л е м е н т о в в м а с с и в е и з а п о м и н а т ь и н т е р в а л ы значений, удовлетворяющих условиям поиска (функция Сап в н у т р е н н я я по отношению к процедуре Solve). Procedure Solve; Const MaxN = 10000; MaxCnt = Max N Shr 1; Var l , j : Integer; Res: Array [1 . .MaxCnt,1. .2] Of Integer; Pos, L,Cnt: Integer; Begin ReadLn(Pos, L); Cnt := 0; i := Pos; Repeat j := i ; While ( j <= MaxN) And Can(j) Do m c ( j ) ; I f l < j Then Begin inc(Cnt) ; Res[cnt,1] := i ; Res[cnt,2] := j-1; {'Запоминаем интервал значений. *} End; i := j+l; Until (i >= MaxN); WriteLn (cnt) ; For i : = 1 To Cnt Do WriteLn ( Res[i,l ], ' \ Res[1,2]) ; End; Ответом задачи при P o s = 1 0 и L = 3 я в л я ю т с я 4 интервала: 12-12, 17-18, 29-30, 87-94, а при P o s = 9 0 0 0 и L = 2 т а к и х интервалов нет. Случайный поиск. О р г а н и з а ц и я п о и с к а k-ro э л е м е н т а в неупорядоченном массиве X в о з м о ж н а с л е д у ю щ и м образом. Выбир а е т с я с л у ч а й н ы м о б р а з о м э л е м е н т с н о м е р о м д. М а с с и в X разб и в а е т с я н а т р и ч а с т и : э л е м е н т ы , м е н ь ш и е X[q], р а в н ы е X[q] и б о л ь ш и е Х[q], А з а т е м , в з а в и с и м о с т и от к о л и ч е с т в а э л е м е н тов в к а ж д о й части, выбирается одна из частей д л я дальнейшего п о и с к а . Т е о р е т и ч е с к а я о ц е н к а ч и с л а с р а в н е н и й и м е е т п о р я д о к k*N, т . е. д л я х у д ш е г о с л у ч а я N2, н о н а п р а к т и к е он работает значительно быстрее.
Массив — фундаментальная структура данных 270 Function Search(N,к:Integer;X:MyArray):Integer; ("N - количество элементов в массиве X, к - номер по порядку разыскиваемого элемента. *) Var 1,cnl,cne,cnm,q:Integer; Sl,Se,Sm:MyArray; Begin I f N=1 Then Search:=X[1] Else Begin FillChar(SI,SizeOf(SI),0); FillChar (Se, SizeOf (Se) , 0) ; FillChar (Sm, SizeOf (Sm) , 0) ; cnl:=0;cne:=0;cnm:=0; q: =Integer (Random (N)) +1 ; For l;=1 To N Do{"Разбиение на три части. *} I f X[i)<X[q] Then Begin Inc(cnl); SI [cnl ] :=X[i ] ; End Else I f X[i}=X[q] Then Begin Inc(cne); Se[cne]:=X[1];End Else Begin Inc(cnm);Sm[cnm]:=X[l];End; I f cnl>=k Then Search:=Search (cnl,k,SI)("Выбор части для дальнейшего поиска.*) Else I f cnl+cne>=k Then Search:=X[q] Else Search:=Search(cnm,k-cnl-cne,Sm); End; End; П о и с к э л е м е н т а массива за линейное время. Д л я р е а л и з а ц и и этой с х е м ы поиска требуется уметь сортировать массив и з 5 элементов з а 7 сравнений.
m И д е я п о и с к а п о я с н я е т с я рисунком. И з отсортированных пятиэлементных массивов формируется массив из средних элементов — медиан (М). Он на рисунке изображен в упорядоченном виде. В массиве М выбирается средний элемент ш и относительно него и с х о д н ы й массив разбивается на три части, к а к в преды-
Часть третья
272
д у щ е й с х е м е с л у ч а й н о г о п о и с к а . В ч а с т и А н а р и с у н к е выделен ы э л е м е н т ы , з а в е д о м о б о л ь ш и е т , в ч а с т и В — м е н ь ш и е т. Как м и н и м у м 1/4 часть элементов исходного массива исключае т с я и з д а л ь н е й ш е г о п о и с к а з а счет о д н о к р а т н о г о п р и м е н е н и я действий этой схемы. Function Search {*N — количество элемента (k<N).
(N, к: Integer элементов *)
;Х -.MyArray) : Integer ; в м а с с и в е X, к — номер
Var i , j,cnl,спе,cnm,q,r:Integer; M,SI,Se,Sm,Y:MyArray; Begin I f N<=5 Then Begin Sort(N,X) ; Search : =X[k],-End {*Если количество элементов в массиве меньше или равно 5, то сортируем элементы массива за 7 с р а в н е н и й и выбираем k-й элемент. *) Else Begin i:=1;г:=0; While i<=N Do Begin 3-0; While <з<=5) And (i+j<=N) Do Begin Inc(3);Y[3]:=X[1+3-1];{*Разбиваем на пятиэлементные массивы (не более). *} End; Inc (i,5) ; Sort ( j , Y ) ; Inc (r) ; M[r]:=Y[g Div 2+1];{'Средний элемент отсортированного массива У записываем в массив М. *} End; q: = S e a r c h (г, г D i v 2,М) ,-{'Находим средний по значению элемент в массиве медиан.*) cnl:=0;спе:=0;cnm:=0; FillChar(SI,SizeOf(SI),0); FillChar(Se,SizeOf(Se),0); FillChar(Sm,SizeOf(Sm),0); For i:=l To N Do {'Разбиваем массив на три части.*) I f X[i]<q Then Begin Inc(cnl); SI[cnl]:=X[i]; End Else I f X[i]=q Then Begin Inc(cne); Se[cne]:=X[i];End
Массив — фундаментальная структура данных
273
Else Begin Inc(cnm);Sm[cnm]:=X[l];End; I f cnl>=k Then Search:^Search(cnl,k,SI) f"Выбор части массива для дальнейшего поиска. *} Else I f cnl+cne>=k Then Search:=X[q] Else Search:=Search (cnm,k-cnl-cne,Sm); End; End; О ц е н и м в р е м я р а б о т ы а л г о р и т м а T(N). После п о и с к а элемента m не менее п о л о в и н ы н а й д е н н ы х м е д и а н будут больше и л и р а в н ы т . Следовательно, по к р а й н е й мере половина pV/5~j групп д а д у т п о т р и ч н с л а , б о л ь ш и х и л и р а в н ы х т, к р о м е , м о ж е т быть, п о с л е д н е й г р у п п ы и г р у п п ы , с о д е р ж а щ е й т. И т а к , 3*( элементов и с к л ю ч а ю т с я и з дальнейш е й о б р а б о т к и . Остается 7*А?/10+6 элементов. П о и с к среднего э л е м е н т а в м а с с и в е м е д и а н (выделен ж и р н ы м ш р и ф т о м ) требует T(N/5) в р е м е н и . Р е к у р с и в н ы й в ы з о в п р о ц е д у р ы п о и с к а (выделен ж и р н ы м ш р и ф т о м ) п р и р а з б и в к е м а с с и в а требует !T(7*iV/10+ +6). О с т а л ь н ы е ш а г и а л г о р и т м а , в к л ю ч а я сортировк у 5 э л е м е н т н ы х массивов, в ы п о л н я ю т с я за в р е м я O(N). Т а к и м образом, T(N)<T(N/5)+T(7*N/10+6)+0(N). Сумма к о э ф ф и ц и е н т о в в п р а в о й ч а с т и 1 / 5 + 7 / 1 0 = 9 / 1 0 м е н ь ш е 1, согласно теореме ( з а н я т и е 17) T(N)<c*N д л я некоторой к о н с т а н т ы с. П р и м е ч а н и е В разных источниках приводятся различные схемы оценки времени работы этого алгоритма и значения N (50 или 70), начиная с которых время поиска пропорционально O(N). П р о д о л ж и м т е м у бинарного поиска. Р а с с м о т р и м с л е д у ю щ у ю з а д а ч у . Д а н ц е л о ч и с л е н н ы й массив М и з N элементов ( N — неч е т н о и 5<Д'<1499). З н а ч е н и я элементов массива р а з л и ч н ы и п р и н и м а ю т з н а ч е н и я от 1 до N. П р и м е р : N= 5, А = 2 , 5, 4, 3, 1. Требуется н а й т и номер элемента i т а к о й , что количество элементов М , м е н ь ш и х M [ i ] , равно к о л и ч е с т в у элементов, больш и х , ч е м M[i]. Д л я п р и м е р а i равно 4. Единственной разреш е н н о й о п е р а ц и е й п р и поиске я в л я е т с я M e d 3 , которая вызывается с т р е м я р а з л и ч н ы м и н о м е р а м и элементов массива в качестве а р г у м е н т о в и в о з в р а щ а е т номер элемента, и м е ю щ е г о среднее з н а ч е н и е . Д л я н а ш е г о п р и м е р а Med3{ 1,2,3) возвращает 3, МеёЗ(ЗАЛ) в о з в р а щ а е т 4 и M e d 3 ( 4 , 2 , 5 ) в о з в р а щ а е т 4. Ответ 4. П у с т ь есть с л е д у ю щ и й массив М .
274
Часть третья
Все, что м ы м о ж е м сделать с помощью о п е р а ц и и Med3, это разбить массив на т р и части, выбрав с л у ч а й н ы м образом два н е с о в п а д а ю щ и х номера (а и b) элементов. Не деление пополам, к а к в б и н а р н о м поиске, а деление на т р и ч а с т и — «тринарный» поиск. П у с т ь а=5 и Ь= 12. П р о с м а т р и в а е м все номера элементов, помеченные п р и з н а к о м True. В н а ч а л ь н ы й момент времени д л я всех элементов этот п р и з н а к равен True. Е с л и операция Med3(a,b,i) дает в качестве результата: • значение а, то помечаем элемент с номером i к а к элемент, п р и н а д л е ж а щ и й левой части ( п р и з н а к _Left)\ • значение г, то помечаем элемент с номером г к а к элемент, п р и н а д л е ж а щ и й средней части (признак _Middle)\ • значение Ь, то помечаем элемент с номером г к а к элемент, п р и н а д л е ж а щ и й правой части ( п р и з н а к _Right). В таблице б у к в а м и L, М , R обозначены п р и з н а к и _Left, _Midlle, _Right. Б у к в о й N обозначен п р и з н а к _None, элемент не рассматривается на данном шаге а н а л и з а .
Итак, разбили, а что дальше? Напрашивается единственно разумный ход — выбрать одну из трех частей и продолжить поиск (другого не дано). Вся сложность задачи в выборе параметров поиска и в аккуратном переходе от одного этапа поиска к другому. К а к у ю часть выбрать д л я следующего этапа поиска? Мы имеем в левой части результата поиска 4 элемента, в правой — 4 и в средней (без учета а и Ь) — 3. Первоначально осуществлялся поиск элемента со значением 7 в массиве из 13 элементов. После этого этапа необходимо искать элемент со вторым значением среди элементов, помеченных п р и з н а к о м Middle. Часть параметров поиска определена: количество и признак элементов, среди которых ищется искомый; номер элемента. Еще один параметр — одно из предыдущих значений а или Ь — требует пояснений (это самый сложный момент). Поясним рисунком.
Массив — фундаментальная структура данных 274 Оказалось, что п о и с к следует п р о д о л ж а т ь в левой (по отнош е н и ю к а) части. Генерируем новые з н а ч е н и я а Ь (обозначены к а к an, Ьп). Если после этого средний элемент ( M e d 3 ( a n , b n , a c ) ) н а х о д и т с я не на месте Ьп, то структура последующего поиска н а р у ш а е т с я . Необходимо п о м е н я т ь з н а ч е н и я у an и Ьп. Если после первого этапа п о и с к а м ы в ы я с н и л и , что его следует прод о л ж а т ь в левой части, то и следующее деление на три части д о л ж н о о с у щ е с т в л я т ь с я с учетом этого результата. П р и м е ч а н и я 1. Прервите чтение и проделайте ручной просчет поиска по данным из условия задачи без смены значений. Результат уже на этом простом примере окажется отрицательным. 2. Сделайте еще два рисунка и выясните особенности использования функции Med3 при продолжении поиска в средней и правых частях. Program Median;f"Без ввода элементов исходного массива М и функции Med3. ") Const MaxN = 1500; Type TDir = (_None, _Middle, _ L e f t , __Right) ; Var N: IntegersMas : Array [l..MaxN] Of TDir; I t , Rt : Integer; ("Количество элементов в подмножествах *) Procedure MakeMiddle(Dir: TDir);{"Помечаем элементы, среди которых осуществляется поиск. ") Var i: Integer; Begin For l := 1 To N Do I f Mas [i] = Dir Then Mas[i] := ^Middle Else Mas[i] := _None; End; Procedure Swap(Var a,b: Integer); Var c: Integer; Begin с := a; a := b; b := c; End; Procedure GetNext (Var a,b: Integer; len: Integer); { "Генерируем номера а и b."I Var i,t,ca,cb: Integer; Begin t := 0; ca Random (len) +1; cb Random (len) +1; W h i l e ca=cb Do cb := Random (len) +1;
Часть третья
276
For i := 1 То N Do {'Сложность в том, оставшихся элементов для поиска идут подряд. ') I f Mas [i] =_Middle Then Begin Inc(t); I f b i; End; End; Procedure {'Основная ранее. *)
ca
Doit(Dir: процедура,
= t
Then
a := I ;
TDir; Res, параметры
I f
len, lb: поиска
Var a, b, 1, t: Integer; Begin MakeMiddle(Dir); {'Выделяем элементы, которых осуществляется поиск. *} I f len = 1 Then Begin a := 1; While __Middle Do inc (a);
что не
cb
номера
= t
Then
Integer); описаны
среди Mas[a]<>
WriteLn (a) ; End Else Begin GetNext (a,b,len); I f (Dir=_Right) Then Begin IfMed3(lb,a,b) о Then Swap (a,b) End
a
Else I f Med3(a,b,lb) <> b Then Swap(a,b); {'Корректируем значения а и b с учетом предыдущего поиска. Пояснение приведено на рисунке в тексте.') I f len = 2 Then Begin I f Res = 1 Then WriteLn Else WriteLn(b) End Else Begin I t := 0; rt := 0; Mas[a] := _None; Mas [b] :=_None; For l := 1 To n Do I f Mas[i] = _Middle Then Begin t := Med3<a,b,i);
(a)
I f t = b Then Begin i n c ( r t ) ; Mas[i] := _Right End('Пишем в правую часть. *) Else I f t = a Then Begin m c ( l t ) ; MasfiJ : = _Left End; End;
Массив — фундаментальная структура данных
277
I f Res = 11+1 Then WriteLn (a) Else (*Искомый элемент попал на границу интервала, поиск завершен. *} I f Res = len-rt Then WriteLn (b) Else I f Res < lt + 1 Then Doit (_Left,Res, I t , a) { "Поиск с новыми параметрами. *} Else I f res < len-rt Then Doit (_Middle, Res-It-1,len-lt-rt-2,b) Else Doit(_Right,Res-len+rt,rt,b); End; EndsEnd; Begin ReadLn (N) ; { "И ввод исходного массива "} Randomize; FillChar(Mas, SizeOf(Mas), _Middle) ; Doit (_Middle,N shr 1+1, N, 1) ;{ "Первый вызов, N shr 1+1 - среднее значение, 1- условно считаем, что ранее был первый номер (обеспечивает правильный поиск). *) End. П о и с к второго минимального элемента в массиве. Известно, что второй м и н и м а л ь н ы й элемент в массиве из N элементов м о ж н о н а й т и за N+\~LogN~\-2 сравнений. Так, при N=8 это 9 с р а в н е н и й . Попробуйте свои силы, не ч и т а я дальнейшее излож е н и е , самостоятельно найдите алгоритм р е ш е н и я задачи. Очевидно, что без п о и с к а м и н и м а л ь н о г о элемента н а й т и второй мин и м а л ь н ы й невозможно. Поиск минимального элемента требует N-1 сравнений, от этого н и к у д а не уйти. Осталось \LogN~\-1 сравнение. Е с л и и с к а т ь по п р е ж н е й схеме, исключив найденн ы й элемент, то потребуется N-2 сравнения. Однако п р и этом м ы к а к бы забываем, что д е л а л и на первом этапе. Единственно в о з м о ж н ы й (логически) вывод з а к л ю ч а е т с я в том, чтобы использовать р е з у л ь т а т ы п о и с к а минимального элемента д л я поиск а второго м и н и м а л ь н о г о элемента. Н о к а к это сделать? Пусть N=8 и элементы массива А имеют з н а ч е н и я : 81, 34, 2, 90, 51, 45, 14, 31. Н а р и с у н к е изображена схема поиска минимального элемента. Сравниваем соседние п а р ы элементов, а затем сравниваем м и н и м а л ь н ы е элементы пар и т. д. Если убрать записи в к р у г л ы х с к о б к а х на рисунке, то это обычный поиск минимального элемента в массиве за N-1 сравнение (напишите процедуру такого поиска).
Часть третья
278 8
34
V 34(81)
2
90
V 2(90)
51
45
14
V45(51) V14(31)
2(34,90)
31
14(45,31) 2(14,34,90)
А сейчас о б р а т и м с я к з а п и с я м в к р у г л ы х с к о б к а х . Рассмотр и м , н а п р и м е р , 2(34,90). П о ч е м у м ы не в к л ю ч и л и 8 1 ? П р и ч и н а в том, ч т о этот э л е м е н т не м о ж е т б ы т ь в т о р ы м м и н и м а л ь н ы м э л е м е н т о м . Т о л ь к о , 34 и 90 к а н д и д а т ы на это место. Точно так ж е и 14(45, 31). Элемент 51 и с к л ю ч а е т с я из «цепочки» претендентов. Последнее 7-е с р а в н е н и е о с т а в л я е т в этой «цепочке» 2(14, 34, 90) т о л ь к о три элемента, ибо 45 и 3 1 не могут претенд о в а т ь на эту роль, 14 заведомо и х м е н ь ш е , а этот элемент в к л ю ч е н в « ц е п о ч к у » . И т а к , в с п и с к е 3 э л е м е н т а , за 2 сравнен и я м ы н а х о д и м второй м и н и м а л ь н ы й э л е м е н т , общее количество с р а в н е н и й равно 9. Постройте точно т а к у ю ж е с х е м у д л я массива и з 16 элементов. Ч е р е з 15 с р а в н е н и й в последней «цепочке» о к а ж е т с я 4 э л е м е н т а . З а 3 с р а в н е н и я м ы н а й д е м второй м и н и м а л ь н ы й элемент. Итого, 18 с р а в н е н и й . П е р е й д е м к р е а л и з а ц и и л о г и к и . Описание д а н н ы х программной р е а л и з а ц и и имеет вид: Program SearchTwoMin;{ *Без ввода исходных данных, вывода результата и основной программы. *) Const MaxN=l0000 ; Type MyArray=Array[1..MaxN] Of Integer; Var A, IndNext, IndFirst: MyArray; f*Массив элементов. Номер следующего элемента цепочки. Номера первых элементов цепочек. *} N: Integer;{'Количество элементов в массиве А. *} Cntlf: Longlnt;{*Счетчик числа сравнений.*) res: Integer;{*Второй минимальный элемент. *) П р е д п о л о ж и м , что после п о и с к а м и н и м а л ь н о г о элемента в д о п о л н и т е л ь н о м массиве IndNext з а п и с а н ы и н д е к с ы (номера) элементов последней ц е п о ч к и , н а п р и м е р , т а к : *, 4, 7, 0, *, *, 2, * а индекс м и н и м а л ь н о г о элемента массива (а это 3 д л я рассматриваемого примера) я в л я е т с я з н а ч е н и е м переменной Ind First[1 ]. Символом «*» обозначено не интересующее нас значение, оно м о ж е т б ы т ь любым, а 0 — п р и з н а к конца «цепочки».
Массив — фундаментальная структура данных
279
З а д а ч а р е ш е н а . П р о с м о т р э л е м е н т о в п о с л е д н е й « ц е п о ч к и » по этим данным реализуется с помощью следующей процедуры. Procedure Search; Var t: Integer; Begin t:^IndNextГIndFirst[l]];{"Длч примера значением t является 7. Определяем номер первого элемента «цепочки». ") r e s : = А [ t ] ; { " Н а ч а л ь н о е присвоение. "} t:=IndNext[t];{"Переход ко второму элементу «цепочки». *} While t<>0 Do Begin Inc (CntIf);("Изменяем значение счетчика числа сравнений."} I f A[t]<res Then значение минимального t: -IndNext[t];{"Переадресация элементу «цепочки»."} EndsEnd;
res:-A[t];{"Изменяем элемента."} к следующему
Осталось с ф о р м и р о в а т ь (создать) з н а ч е н и я э л е м е н т о в массивов IndNext и IndFirst в процессе п о и с к а м и н и м а л ь н о г о элем е н т а м а с с и в а А ( н а п о м н и м его з н а ч е н и я д л я р а с с м а т р и в а е м о г о п р и м е р а : 81, 34, 2, 90, 51, 45, 14, 31). В н а ч а л ь н ы й момент в р е м е н и у нас 8 ц е п о ч е к по одному элементу. После первого ш а г а р а б о т ы п о л у ч а ю т с я 4 ц е п о ч к и по два э л е м е н т а . Н о м е р первого э л е м е н т а первой ц е п о ч к и 2 ( I n d F i r s t [ l ] ) , номер следую щ е г о э л е м е н т а 1 (IndNext[2]). Н о м е р первого элемента второй ц е п о ч к и 3 ( I n d F i r s t [ 2 ] ) , номер с л е д у ю щ е г о элемента 4 (Ind Next[3]). Н о м е р первого э л е м е н т а третьей ц е п о ч к и 6 (IndFirst[3]), н о м е р с л е д у ю щ е г о элемента 5 (IndNext[6]). Номер первого э л е м е н т а четвертой ц е п о ч к и 7 (IndFirst[4]), номер след у ю щ е г о э л е м е н т а 8 (IndNext[7]). Р е з у л ь т а т ы д е й с т в и й на первом ш а г е обработки п р и в е д е н ы в т а б л и ц е .
1 Начальные значения A[lndFirst[1]] и A[lndFirst[2]]2 A[lndFirst[3]] и A[lndFirst[4]]2 A[lndFirst[5]] и A[lndFirst[6]]2 A[tndFirst[7]] и A[lndFirst[8]]2
2 2 3 3 3
3 3 3 6 6
IndFirst 4 5 6 4 5 6 4 5 6 4 5 6 7 5 6
7 7 7 7 7
8 8 8 8 8
0 0 0 0 0
0 1 1 1 1
0 0 4 4 4
IndNext 0 0 0 0 0 0 0 0 0 0 0 5 0 0 5
0 0 0 0 8
0 0 0 о I 0
Часть третья
280
В р е з у л ь т а т е о с т а л о с ь 4 э л е м е н т а , т о ч н е е 4 ц е п о ч к и элементов, н о м е р а и х п е р в ы х элементов я в л я ю т с я з н а ч е н и я м и Ind .Firsf[l] - IndFirst[4]. П о с л е второго ш а г а о с т а е т с я две ц е п о ч к и элементов. 2 Начальные значения A[lndFirst[1J] и A[lndFirst[2]] 3 | A[lndFirst[3]] и A[lndFirst[4]] 3
||
IndFirst 3 6 7 3 7
6 6
7 7
0 0 0
1 4 4
4 2 2
IndNext 0 0 5 0 0
0 0
5 8
j 8
0
8 6
0
0
J
Н о м е р п е р в о г о э л е м е н т а п е р в о й ц е п о ч к и 3 ( I n d F i r s t [1 ]), зат е м 2 {IndNext[3]) и 4 (IndNext[2]). Номер первого элемента в т о р о й ц е п о ч к и 7 ( I n d F i r s t [ 2 ] ) , з а т е м 6 (IndNext[7]) и 8 (Ind Next[6]). Осталось последнее сравнение элементов A[Ind First[ 1]] и A[IndFirst[2]]. Э л е м е н т ы м а с с и в а IndNext после корректировки ссылок примут значения: 0, 4, 7 , 0 , 0 , 8 , 2 , 0 , а IndFirstf 1 ] о с т а н е т с я р а в н ы м 3. З н а ч е н и е А[7] б о л ь ш е А[3], п р о ц е п о ч к у седьмого э л е м е н т а з а б ы в а е м , его в к л ю ч а е м перв ы м элементом в цепочку третьего элемента, а старое значение IndNext[3] становится значением IndNext[7]. Procedure Create; Var l , I f , r g , t : Integer; Begin Cntlf:=0;("Счетчик числа сравнений. *} FillChar(IndNext, SizeOf(IndNext), 0); { ' Н а ч а л ь н ы е значения элементов массива IndNext. *) For l: =1 To N Do IndFirst[l]:=i;{* Начальные значения элементов массива IndFirst. *) t:=N; While t>l Do Begin ("Номер шага, t Div 2 количество сравнений на данном шаге. Для примера: 8, 4, 2. *) 1: =2 ; While K-t Do Begin Inc(Cntlf);("Изменяем значение счетчика числа сравнений.*) If:=i-1; rg:=1-1; I f A[IndFirst[l-l]]<A[IndFirst[i]] Then r g:=i Else lf:=i;(*B сравниваемой паре элементов массива A, rg - номер большего, a I f - номер меньшего элементов, определяемых по значению из IndFirst. *}
Массив — фундаментальная структура данных
281
{ *Следующие три строки кода - самый сложный момент логики. Связываем цепочки сравниваемых элементов массива А. Изменяем номер первого элемента новой цепочки. *) IndNext[IndFirst[rg]]:=IndNext [IndFirst[If]1; IndNext[IndFirst[If]]:=IndFirst[rg]; IndFirst[l Div 2]:=IndFirst[lf]; Inc(i, 2) ; End; I f t Mod 2=1 Then IndFirst[ (t+1) Div 2]: = IndFirst[t];{*Учитываем нечетность значения N. *) t : = (t+1) v 2; EndsEnd; Основная программа, к а к обычно, состоит из вызова процедур: Begin Imt; ( *Ввод исходных данных. *} Create; Search; Done; { *Вывод результата . *} End Хеширование. В п р е д ы д у щ е м материале рассматривался пои с к , о с н о в а н н ы й на сравнении значений элементов массива с н е к о т о р ы м з а д а н н ы м з н а ч е н и е м переменной X (ключом). В конечном итоге о п р е д е л я л с я адрес, номер элемента в массиве, где записано данное значение X . Рассмотрим простую задачу. Количество р а з л и ч н ы х буквосочетаний длиной не более 4 символов и з 32 букв русского а л ф а в и т а чуть больше 10 6 . Мы проанал и з и р о в а л и к а к о й - т о текст и в ы п и с а л и в соответствующий массив все встреченные буквосочетания. После этого на вход н а ш е й простой системы а н а л и з а текста поступает некоторое буквосочетание. Н а м необходимо ответить, встречалось оно ранее и л и нет. Очевидно, что к а ж д о м у буквосочетанию соответствует н е к о т о р ы й цифровой код. Поэтому задача сводится к поиск у в массиве некоторого цифрового з н а ч е н и я — ключа. Эта задача рассмотрена ранее (разумеется, что м о ж н о хранить и сами буквосочетания). П р и линейном поиске, в худшем случае д л я ответа на вопрос «есть и л и нет», потребуется 10 6 сравнений, п р и бинарном — п о р я д к а 50. Можно л и уменьшить количество сравнений и ослабить требования по оперативной памяти, резервировать не 10 6 , а, например, 10 3 элементов памяти?
282
Часть третья
Обозначим цифровой аналог буквосочетания символом R, а адрес в массиве (таблице) через А. Если будет найдено преобразование f(R), дающее значение А, такое, что • f выполняется достаточно быстро; • случаев, когда f(R1)=f(R2), будет минимальное количество, то задача поиска трактуется совсем иначе, чем рассмотрено выше. Этот способ поиска называется хешированием (расстановкой ключей, рандомизацией и т. д.). Итак, ключ преобразуется в адрес, в этом суть метода. Требование по быстродействию очевидно - одна из целевых установок любого алгоритма, ибо быстродействие компьютера ограничено. Второе требование не столь очевидно. Предположим, что анализируется текст из специфической страны «Д...», Е которой все слова начинаются с букЕЫ О, и в качестве f выбран цифровой аналог первой буквы. В этом случае получаем одно значение А д л я всех слов (от чего уходили, к тому и пришли, но в стране «Д...» все возможно). Следовательно, необходимо выбирать такое преобразование f , чтобы количество совпадений адресов (А) для различных значений ключей ( R ) , это называют коллизиями, было минимально. Полностью избежать коллизий не удается. Д л я любого преобразования f (хеш-функции) можно подобрать такие исходные данные, что рассматриваемый метод поиска превращается в линейный поиск. Однако цель построения хеш-функции ясна — как можно более равномерно «разбрасывать» значения ключей по таблице адресов (ее размер обозначим через М) так, чтобы уменьшить количество коллизий. Итак, реализация метода логически разбивается на две составляющие: • построение f; • схему разрешения коллизий. Коротко рассмотрим их, резюмируем известные факты из теории информатики. При построении хеш-функции, так или иначе, для значения ключа R находится цифровой аналог, обозначим его через t. В первом способе в качестве М выбирается простое число и находится остаток от деления t на М (t Mod М). Во втором способе находится значение t2. Из этого значения «вырезается» q каких-то разрядов (лучше средних). Значение q выбирается таким образом, чтобы 21 было равно М. В третьем способе w разрядов для представления ключа t разбиваются на части длиной q разрядов. Выполняется, например, логическое сложение этих частей, и его результатом является номер элемента в таблице адресов. Считается (если не заниматься подбором специальных
Массив — фундаментальная структура данных 282 в х о д н ы х данных), что все эти способы дают достаточно хорошие, в смысле количества в о з м о ж н ы х к о л л и з и й , х е ш - ф у н к ц и и . И з и з в е с т н ы х методов р а з р е ш е н и я к о л л и з и й р а с с м о т р и м о д и н — «метод ц е п о ч е к » . Его суть в том, что э л е м е н т о м таблиц ы я в л я е т с я с с ы л к а на с п и с о к элементов, и м е ю щ и х одно знач е н и е х е ш - ф у н к ц и и . З р и т е л ь н ы й образ этого метода представлен на рисунке.
К а ч е с т в о х е ш - ф у н к ц и и о ц е н и в а е т с я средней д л и н о й списков. Р а б о т а со с п и с к а м и обсуждается позднее, поэтому перенесем вопрос о р а з р е ш е н и и к о л л и з и й д а н н ы м методом на последующий материал. Р а с с м о т р и м следующую простую задачу. Дано N (1<N<15000) ч и с е л т и п а L o n g l n t и т а б л и ц а не очень большого р а з м е р а . Оцен и м к а ч е с т в о работы х е ш - ф у н к ц и й по среднему и м а к с и м а л ь н о м у з н а ч е н и ю к о л и ч е с т в а р а з л и ч н ы х чисел, д л я к о т о р ы х в рез у л ь т а т е п р е о б р а з о в а н и я п о л у ч а е т с я один и тот ж е адрес в т а б л и ц е . П о с т р о и м т р и р а з л и ч н ы е х е ш - ф у н к ц и и и проверим и х на с л у ч а й н ы х тестах. Д л я первичного а н а л и з а первой хеш-функ ц и и п р и в е д е м п о л н ы й текст р е ш е н и я . ($R+) Program Const
Var
Му21_1; InputFile-'Input.Txt'; MaxN=15000;I *Максимальное значение количества чисел. *} МахТЫ=547; I "Размер таблицы. ") ТЫ: Array [0. .МахТЫ] Of Longlnt; Count,Dif:Array[0. .МахТЫ] Of Integer; {"Подсчитываем количество совпадающих ключей и количество различных, но дающих один адрес в таблице. *}
284
Часть третья
N:Integer; Procedure RandFile;{"Создание Var i:Integer; Begin Randomizer-
файла
из
N чисел.
Assign (Output,InputFile);Rewrite(Output) N: ^Integer(Random(MaxN) )+1; WriteLn (N);
"}
;
For l : =1 To N Do WriteLn(Longlnt "29999); Close(Output); End;
(Random(65000))
Procedure Solve; Var l,Ind:Integer; nm:Longlnt; Begin FillChar(Dif,SizeOf(Dif),0); FillChar (ТЫ,SizeOf (ТЫ) , 0) ; Assign (Input,InputFile) ,-Reset(Input); ReadLn(N); For l : =1 To N Do Begin ReadLn(nm);l"Читаем число. Ind:=nra Mod МахТЫ;{"Вычисляем таблице. *}
*) адрес
в
I f ТЫ[Ind]=0 Then Begin ТЫ[Ind]:=nm; Count[Ind]:=1; Endf"Первая запись в таблицу по этому адресу."} Else I f пт=ТЫ [Ind] Then Inc (Count [Ind] ) Else Inc(Dif[Ind]);{"Коллизия различные числа дают один адрес в таблице. ") End; Close (Input); End; Procedure Issl; Var s , i , Max,r:Integer; Begin s:=0;Max:=0; For i:=l To МахТЫ Do Begin Inc (s,Dif[i]);("Подсчитываем количество коллизий.") I f Dif[i]>Max Then Max:=Dif[l];("Определяем максимальное значение количества коллизий."}
Массив — фундаментальная структура данных 284 End; r:=Round (s/МахТЫ) ; Assign (Output, 'Con') ;ReWrite (Output) ; WriteLn (N,' ' , r, ' ' ,Max) ; End; Begin RandFile;("Формирование файла из случайных чисел. *) Solve;( "Анализ файла с помощью различных хеш-функций.") Issl;{"Подсчет среднего и максимального значений коллизий. *) End. Р е ш е н и я д л я второй и третьей ф у н к ц и й требуют изменения одной строки в процедуре Solve — Ind:=GetNum(nm), где GetNum обеспечивает в ы ч и с л е н и я адреса элемента таблицы. Д л я второй х е ш - ф у н к ц и и GetNum имеет вид: Function GetNum (nw: Longlnt) : Integer; Var w,q:Longlnt; t: 0. . МахТЫ ; Begin g: = (nw And $FFFF0000 ) Shr 1 6;("Выделяем старшие разряды числа и сдвигаем на 16 разрядов."} w: = (nw And $FFFF) ; ("Выделяем младшие разряды числа. *} t : = (Integer (q"w) And $7 FCO) Shr 6; ("Выделяем 9 бит произведения чисел. *} GetNum:=t; End; Вычисление третьей х е ш - ф у н к ц и и можно организовать след у ю щ и м образом. Function GetNum (nw: Longlnt) :Integer; Var r, t: Integer; Begin r: =In teger (nw And $1FF) ;( "Выделение младших 9 бит числа. *} t:=Integer (nw And $3FE00 Shr 9) ;("Выделение следующих 9 бит числа. *) r:=r Xor t;("Первый шаг формирования адреса элемента таблицы. *} t: =In teger (nw And $7 FC0000 Shr 18) ;( "Выделение следующей группы разрядов числа. ")
Часть третья
286
r : = r Xor t; t:=Integer(nw And SF8000000 оставшихся разрядов. *) r:=r Xor t; GetNuml:=r; End;
Shr
21)
;["Выделение
В т а б л и ц е п р и в е д е н ы р е з у л ь т а т ы подсчета д л я трех рассматриваемых хеш-функций.
Д а н н ы е в т а б л и ц е дают общую « к а р т и н к у » работы хеш-функ ц и й , и не более. Д л я с р а в н и т е л ь н о г о а н а л и з а необходимо по к р а й н е й мере исследовать и х работу на одном и том ф а й л е и з с л у ч а й н ы х ч и с е л . И з м е н и т е п р о г р а м м у т а к , чтобы это требование в ы п о л н я л о с ь , а т а к ж е д л я к а ж д о й х е ш - ф у н к ц и и формиров а л и с ь о ц е н к и N c p e d l i e e , r cpedHee , Max среднее • Сравните работу хешф у н к ц и й по э т и м о ц е н к а м , н а п р и м е р , д л я 100 р а з л и ч н ы х файлов с и с х о д н ы м и д а н н ы м и . Задания для самостоятельной работы 1. Во в х о д н о м ф а й л е д а н массив А и массив и з элементов, поиск к о т о р ы х будет о с у щ е с т в л я т ь с я в массиве А. И з м е н и т ь м а с с и в А т а к и м образом, чтобы с у м м а р н о е к о л и ч е с т в о сравн е н и й п р и п о и с к е элементов было м и н и м а л ь н ы м . П р и м е ч а н и е По данвым из второго массива формируются частоты поиска каждого элемента и элементы массива А сортируются в порядке убывания значений этих частот («метод перемещения в начало»). Задача достаточно легко превращается в небольшое исследование. Необходимо найти значения N, при которых массивы не помещаются в оперативную память, отводимую под программу в системе программирования.
Массив — фундаментальная структура данных
287
2. Д а н м а с с и в А и ч и с л о X. П е р е с т а в и т ь э л е м е н т ы массива так и м образом, чтобы вначале б ы л и з а п и с а н ы э л е м е н т ы , меньш и е и л и р а в н ы е X , а затем — б о л ь ш и е и л и р а в н ы е X . П е р е с т а в и т ь э л е м е н т ы массива т а к и м образом, чтобы вначале б ы л и з а п и с а н ы э л е м е н т ы , м е н ь ш и е X , затем р а в н ы е X и, наконец, большие X. П р и м е ч а н и е Указанные перестановки следует сделать за один просмотр А и без использования дополнительных массивов. 3. Н а й т и р е а л и з а ц и ю (рекурсивную и нерекурсивную) бинарного поиска с и с п о л ь з о в а н и е м «барьерной» т е х н и к и . 4. Н и ж е по тексту п р и в е д е н ы три Еерсии процедуры бинарного п о и с к а . К а к а я и з н и х в е р н а я ? Исправьте о ш и б к и . К а к а я и з н и х более э ф ф е к т и в н а я ? Procedure Search; Var i, j , к: Integer; l9:=l,J
Procedure Search, bar j , k: Integer, i:=I,
=N;
k:=(i+j) Div 2; If X<=A[k] Then j-=k-l; If A[k]<=X Then l•=k+l, Until i>j, End,
j-=N;
If X<AU] then J.-K Else Until i>=j;
I:-k-H,
Procedure Search; Var I, 7, k: Integer; l:=l; j:=N; Repeat k: = (i+j) Div 2 If A[k]<X Then l:=k Else ]:-k; Until lA[k]-Xl Or (i>=j 1; End; 5. Д а н массив из N различных целых чисел. Разрешенными опер а ц и я м и я в л я ю т с я с л о ж е н и е двух элементов массива и сравн е н и е с у м м . Н а й т и н а и б о л ь ш и й элемент массива за минимал ь н о е количество сравнений. 6. Пусть Х[1М] nY[l..N] — два возрастающих массива и з разл и ч н ы х элементов. Н а й т и за время 0(LogN) средний элемент м н о ж е с т в а , полученного объединением этих массивов.
288
Часть третья
Занятие № 22. Д в у м е р н ы е м а с с и в ы . Работа с элементами План занятия П структура двумерного массива и его описание; П шаблон для решения задач на двумерные массивы; • экспериментальная работа с программами поиска максимального элемента в массиве, формирование значений элементов одномерного массива, поиска элементов с определенными свойствами, заполнения массива по заданным правилам; П выполнение самостоятельной работы. Массивы, положение элементов в которых описывается двумя индексами, называют двумерными. Логическая структура такого массива может быть представлена прямоугольной матрицей. Каждый элемент матрицы однозначно определяется указанием номера строки и номера столбца. Образом памяти компьютера в наших предыдущих рассуждениях был одномерный массив ячеек. Как происходит отображение логической структуры двумерного массива в физическую? Известны два способа: отображение строками и отображение столбцами. В конкретной системе программирования используется один из них. Рассмотрим первый, как обычно на примере. Пусть есть двумерный массив А из целых чисел (тип Integer — 2 байта). Количество строк равно 5, количество столбцов — 4. Пусть первый элемент А[1,1] записан в ячейке с номером 1000. Вычислим адрес А[4,3]. Addr(A[4,3])= 1000+2*(4*(4-l)+(3-l))=1028. Что мы делали? В каждой строке записано по 4 элемента. В трех строках — 12 элементов, в 4-й строке до 3-го элемента записано 2 элемента. Складываем и умножаем на 2, ибо 2 байта необходимо для хранения одного элемента. В общем случае для массива A[N,M] из элементов, занимающих V байт памяти, формула имеет вид: Addr(A[i,j])=Addr(A[l ,1 ] )+V*(M*(i-l )+(j-l) ). Двумерный массив на языке Турбо Паскаль определяется по-разному. Примеры: Const MaxN=>...; МахМ=...;{*Максимальные значения количества строк и столбцов двумерного массива.*} Type OMyArray=Array[l.MaxM] Of /Ше#ег;{*Одномерный массив из целых чисел.*} ТMyArray=Array[1..MaxN] Of ОМуАггоу;{*Одномерный массив, элементами которого являются одномерные массивы из целых чисел.*}
Массив — фундаментальная структура данных 288 или Type ТMyArray= Array [ 1 ..MaxN, 1..МахМ] Of Integer; ^ Д в у м е р н ы й массив и з ц е л ы х чисел.*} В у ч е б н и к е отдается предпочтение второму способу описан и я двумерного массива. Д л я работы с д в у м е р н ы м и массивами изготовим шаблон. Он в к л ю ч а е т ввод д а н н ы х и з ф а й л а и вывод элементов двумерного массива в ф а й л . Р а з м е р ы двумерного массива вводятся и з файла. Далее по тексту, если этого не требует задача, процедуры ввода и вывода двумерного массива не п р и в о д я т с я . ($R + ) Program Му22_1; Const MaxN=l0; MaxM=8; Type TMyArray = Array [1..MaxN,l..MaxM] Of Integer; Var A:TMyArray; N,M:Integer; Procedure TInit(Var v,w:Integer;Var X:TMyArray); Var i , J : In teger; Begin Assign (Input, 'Input. Txt') ; Reset (Input) ; ReadLn(v,w);{*B первой строке файла записаны значения N и М. *} For 1:=1 То v Do For j:=l То w Do Read(X{l,j]) ; Close (Input) ; EndsProcedure TPrmt (v, w: Integer;X: TMyArray) ; Var l , j : In teger; Begin Assign(Output,'Output.Txt'); Rewrite(Output); For l:=1 To v Do Begin For j:=l To w Do Write (X [ i , j] , " ) ; WriteLn; End; Close (Output) ; EndsBegin TInit (N,M,A) ; TPrint (N,M,A) ; End. 10—452
Часть третья
290
М а с с и в п р о с м а т р и в а е т с я т а к , к а к это п о к а з а н о на р и с у н к е : э л е м е н т ы п е р в о й с т р о к и , второй и до N-й.
О т м е т и м еще одну т е х н о л о г и ч е с к у ю особенность работы, точнее н а п о м н и м , ибо она у ж е р а с с м а т р и в а л а с ь . Э к р а н д о л ж е н б ы т ь р а з б и т на т р и о к н а , к о т о р ы е о д н о в р е м е н н о о б о з р и м ы . Изм е н е н и е з н а ч е н и й во в х о д н о м ф а й л е (не з а б ы в а й т е его з а п и с ы вать) и з а п у с к п р о г р а м м ы п р и в о д я т к и з м е н е н и ю в ы х о д н о г о ф а й л а . Эти и з м е н е н и я в и д н ы без д о п о л н и т е л ь н ы х о п е р а ц и й по п е р е х о д у от одного о к н а к д р у г о м у . Т е к у щ и м д о л ж е н б ы т ь кат а л о г с ф а й л а м и п р о г р а м м ы , Input.Txt и Output.Txt. Названия входного и в ы х о д н о г о ф а й л о в , естественно, могут б ы т ь другими. Возможно формирование массива с использованием датчика с л у ч а й н ы х ч и с е л и п у т е м ввода д а н н ы х с к л а в и а т у р ы , но м ы не будем о с т а н а в л и в а т ь с я н а этом. П р и м е ч а н и е Обычно значения размерности массива вводят с клавиатуры, а сам массив — из файла. «Разрыв» по данным. Процедура TInit полностью обеспечивает ввод массива — это законченный фрагмент логики с одной точкой входа и одной точкой выхода. Фрагмент работает с конкретными данными и взаимодействует с остальной логикой по определенным правилам. Разрыв не логичен к а к по управлению, так и по данным. Структуризация задачи должна осуществляться как по управлению, так и по данным, иначе какой же переход к объектно-ориентированному программированию? Если на уровне деклараций или формального ознакомления с конструкциями или инструкциями, то это не наш путь. Экспериментальный раздел работы 1. Н а й т и п е р в ы й м а к с и м а л ь н ы й э л е м е н т м а с с и в а и его индексы. Procedure Var
Search(v,w:Integer;X:TMyArray; smax,simax,sjmax:Integer);
Массив — фундаментальная структура данных Var 1,у:Integer; Begin smax : =X [1,1] ; simax:=1; sjmax:=l; For l:=1 To v Do For J:=Z To w Do I f X[i,j]>smax Then smax: =X [i, j ] ; simax:=i; sjmax:=j; EndsEnd;
291
Begin
П р о ц е д у р а Append о т к р ы в а е т с у щ е с т в у ю щ и й ф а й л д л я добавлен и я в него и н ф о р м а ц и и . О п и с а н и е — A p p e n d ( V a r f:Text). Перем е н н а я f я в л я е т с я ф а й л о в о й п е р е м е н н о й т и п а Text, к о т о р а я должна быть связана с внешним файлом с помощью процедуры Assign. П р о ц е д у р а Append о т к р ы в а е т с у щ е с т в у ю щ и й в н е ш н и й ф а й л , и м я к о т о р о г о п о с т а в л е н о в соответствие п е р е м е н н о й / . Е с л и ф а й л у ж е о т к р ы т , то с н а ч а л а он з а к р ы в а е т с я , а з а т е м отк р ы в а е т с я повторно. Указатель текущей позиции (вспомните г о л о в к и ч т е н и я , з а п и с и в м а г н и т о ф о н е ) п р и этом у с т а н а в л и в а ется на конец данного файла. Е с л и с а м а п р о ц е д у р а п о и с к а п е р в о г о м а к с и м а л ь н о г о элемента м а с с и в а в р я д л и в ы з ы в а е т в о п р о с ы , то д л я в ы в о д а результата в ф а й л Output.Txt н а ш и х з н а н и й не х в а т а е т . М ы м о ж е м пер е о п р е д е л и т ь в ы в о д и з н а ч е н и е м а к с и м а л ь н о г о э л е м е н т а с его и н д е к с а м и в ы в о д и т ь н а э к р а н . Д л я этого необходимо: Assign(Output,'Con'); Rewrite(Output); и н а п и с а т ь о п е р а т о р WriteLn(Max," '.iMax,' \jMax). Однако м а с с и в в ы в о д и т с я в ф а й л Output.Txt, а р е з у л ь т а т ы работы — на э к р а н . Где л о г и к а ? В о с п о л ь з у е м с я п р о ц е д у р о й Append. Д о б а в л е н и е к основной п р о г р а м м е м о ж е т и м е т ь и т а к о й вид: Search (п,т,А,Мах, iMax, jMax) ; Append (Output); WriteLn(Max:5,iMax:5,jMax:5) ;
292
Часть третья
И з м е н и т е п р о ц е д у р у Search д л я п о и с к а п о с л е д н е г о м а к с и м а л ь н о г о э л е м е н т а , д л я п о и с к а всех м а к с и м а л ь н ы х э л е м е н т о в и их индексов. 2. С ф о р м и р о в а т ь о д н о м е р н ы й м а с с и в , к а ж д ы й э л е м е н т которого р а в е н с у м м е о т р и ц а т е л ь н ы х э л е м е н т о в с о о т в е т с т в у ю щ е й строки заданной целочисленной матрицы. З а д а ч а т р е б у е т в в е д е н и я о д н о м е р н о г о м а с с и в а , п о э т о м у приведем описание д а н н ы х , процедуры подсчета с у м м ы отрицательных элементов в строке и добавления данных в выходной файл, а т а к ж е основную программу. {$R + ) Program Му22_2; Const MaxN=10; МахМ=8; Type TMyArray=Array[1..MaxN,1..MaxM] Of OMyArray=Array[1..MaxN] Of Integer; Var A:TMyArray; Sum:OMyArray; n,m:Integer; Procedure SumNeg(v, w:Integer;X:TMyArray;Var OMyArray); Var l , j:Integer; Begin For i:=1 To v Do Begin Ssum[l]:=0; For j :=1 To w Do I f X[i,j]<0 Then Inc(Ssum[i], X [ l , j] ) ; End; End; Procedure OPrint(v:Integer;OSum:OMyArray); Var i:Integer; Begin Append(Output); For i : =1 To v Do Write (OSum[i] : 4) ; WriteLn; End; Begin("Основная программа. *} TInit(n,m,A); TPrmt (n,m,A) ; SumNeg (n,m, A, Sum) ; OPrint(n,Sum); End.
Integer;
Ssum:
Массив — фундаментальная структура данных
293
3. О п р е д е л и т ь , е с т ь л и в м а с с и в е э л е м е н т , р а в н ы й 0. Function SNeg(v,w:Integer;X:TMyArray):Boolean; Var l,j:Integer; pp:Boolean; Begin pp:=False; For l:=1 To v Do For j:=1 To w Do I f X[i,j]=0 Then pp:=True; SNeg:=pp; End; Фрагмент и з основной программы. Append (Output) I f SNeg(n,m,A) WriteLn('No')
; Then
WriteLn('Yes')
Else
;
Н е д о с т а т о к ф у н к ц и и SNeg. П у с т ь п е р в ы й э л е м е н т м а с с и в а Х [ 1 , 1 ] равен нулю. Просмотр массива продолжается, несмотря н а его б е с с м ы с л е н н о с т ь . У с т р а н е н и е н е т о ч н о с т и — «задача определяет т и п и с п о л ь з у е м ы х к о н с т р у к ц и й повторения» — требует и с п о л ь з о в а н и я о п е р а т о р о в While и л и Repeat — Until в ф у н к ц и и Sneg. П р о д е л а й т е э т о . 4 . О п р е д е л и т ь , я в л я е т с я л и д а н н ы й к в а д р а т н ы й м а с с и в симметр и ч н ы м о т н о с и т е л ь н о своей г л а в н о й д и а г о н а л и . Э л е м е н т ы на главной диагонали характеризуется совпадением значением и н д е к с о в — i=j, на в т о р о й i=N-j+l, где N — р а з м е р н о с т ь массива. Главная диагональ
Вторая диагональ, но не главная
Часть третья
294 Function Var
Sim(v,w:Integer;X:TMyArrav):Boolean; i,j:Integer; pp:Boolean;
Begin pp:=True; For i:=2 To v-1 Do For j:=i+l To w Do I f X[i, j]<>X[j,1] Then Sim:=pp; End;
pp:=False;
И з м е н и т е ф у н к ц и ю т а к , ч т о б ы с и м м е т р и ч н о с т ь м а с с и в а проверялась относительно второй диагонали. В п р е д ы д у щ е м прим е р е б ы л р а с с м о т р е н н е д о с т а т о к ф у н к ц и и SNeg, Он п р и с у щ и э т о й ф у н к ц и и . У с т р а н и т е его. 5. В м а с с и в е А р а з м е р н о с т ь ю п*т к э л е м е н т а м ч е т н ы х с т о л б ц о в прибавить элемент первого столбца соответствующей строки. Procedure Change ( v, w:Integer;Var Var l , j : Integer; Begin For i:=1 To v Do For j : =1 to w Div 2 Do Inc(X[i,2*j End;
X:TMyArray);
] , X [ i , 1])
;
Особенностью р е ш е н и я задач этого типа я в л я е т с я возможность и з м е н е н и я элемента, к о т о р ы й д о л ж е н использоваться в о б р а б о т к е . Н а п р и м е р , п р и б а в л я е м к э л е м е н т а м н е ч е т н ы х столбц о в . М о д и ф и к а ц и я X[i,2*j-1 ] приведет к тому, что элементы 3, 5 и т . д. с т о л б ц о в у в е л и ч а т с я н а у д в о е н н о е з н а ч е н и е э л е м е н тов первого столбца. П р о в е д и т е к о р р е к т н о е и з м е н е н и е процедуры Change. 6 . З а п о л н и т ь м а с с и в А р а з м е р о м п*т с л е д у ю щ и м о б р а з о м , нап р и м е р п=6 и т=8: 1 16 17 32
2 3 4 15 14 1 3 18 19 2 0 3 1 30 2 9
5 12 21 28
6 7 8 1 1 10 9 22 2 3 24 27 2 6 2 5
3 3 34 35 36 37 38 3 9 40 48 47 4 6 45 44 4 3 42 4 1 То есть з а п о л н я е т с я в виде
Массив — фундаментальная структура данных
295
Правило заполнения. Если номер строки — нечетное число, то A[i,j]=(i-l)*m+j, иначе — A[i,j]=i*m-j+l. Procedure Fill(v, w:Integer;Var X: TMyArray); Var l , j : IntegersBegin For i:=1 To v Do For j:=1 To w Do I f l Mod 2=1 Then X[i, j ] : = ( i - l ) *w+j Else X[i,j]:=i*w-j+l; End; З а п о л н и т е массив по с л е д у ю щ и м 1 2 3 4 8 9 10 11 15 16 17 18 22 23 24 25
5 12 19 26
6 13 20 27
1 3 4 2 5 9 6 8 13 7 14 18 15 17 25 16 26 27
10 12 19 24 28 33
11 20 23 29 32 34
7 14 21 28 21 22 30 31 35 36
правилам. 1 0 8 0
0 5 0 12
2 0 0 6 9 0 0 13
1 2 3 4 5 6
12 11 10 9 8 7
13 14 15 16 17 18
3 0 10 0
0 7 0 14
24 25 36 23 26 35 22 27 34 21 28 33 20 29 32 19 30 31
7. С о с т а в и т ь п р о г р а м м у , з а п р а ш и в а ю щ у ю к о о р д и н а т ы ф е р з я на ш а х м а т н о й доске и п о к а з ы в а ю щ у ю поля доски, находящиеся под боем. З а м е т и м , что ш а х м а т н у ю доску удобно представить в виде д в у м е р н о г о м а с с и в а р а з м е р о м 8 * 8 . К о о р д и н а т ы ф е р з я определ я ю т с я д в у м я ч и с л а м и ( н о м е р с т р о к и и н о м е р с т о л б ц а ) , но в ш а х м а т а х п р и н я т о в в о д и т ь б у к в у и ч и с л о . Б у к в а о т в е ч а е т за н о м е р с т р о к и , а ч и с л о — з а н о м е р с т о л б ц а . П о э т о м у не б у д е м о т с т у п а т ь о т т р а д и ц и й и в в е д е м к о о р д и н а т ы и м е н н о т а к и м обр а з о м . В п р о г р а м м е с д е л а е м п р о в е р к у к о р р е к т н о с т и ввода и е с л и все п р а в и л ь н о , то п е р е в е д е м б у к в у в с о о т в е т с т в у ю щ е е ей ч и с л о ( ' а ' — 1, ' Ь ' — 2, ' с ' — 3 , ' d ' — 4 , V — 5, ' f ' — 6, ' g ' 7 , ' h ' - 8 ). Д л я р е ш е н и я з а д а ч и и с п о л ь з у е м р я д с в о й с т в ш а х м а т н о й д о с к и . Все д и а г о н а л и д о с к и д е л я т с я на в о с х о д я щ и е и нисходящие.
Н а к а ж д о й д и а г о н а л и в ы п о л н я е т с я свойство: • д л я к л е т о к любой в о с х о д я щ е й д и а г о н а л и с у м м а номера с т р о к и и н о м е р а столбца п о с т о я н н а ( i + j = C o n s t ) \ • д л я к л е т о к любой н и с х о д я щ е й д и а г о н а л и р а з н о с т ь номера с т р о к и и номера столбца п о с т о я н н а ( i - j = C o n s t ) . Проверьте, что д л я восходящих диагоналей сумма индексов и з м е н я е т с я от 2 до 16, а д л я н и с х о д я щ и х — р а з н о с т ь от - 7 до 7. Program Му22_3; Const MaxN=8;MaxM=8; Type TMyArray=Array[1.MaxN,1..МахМ] Of Integer; Procedure TInit(v,k, 1: Integer; VarX: TMyArray); Var l , j: Integer; Begin For l:=1 To v Do For j:=1 To v Do I f (i=k) Or (j=l) Or (i+j=k+l) Or (i-j=k-l) Then X[i,j]:=l Else X[l , j ] :=0; { *1 - клетка под боем, 0 — нет. *} X[k,1]:=2; {*В этой клетке находится ферзь. *} End; Procedure Solve; Var A: TMyArray; c: Char;str, stl: Integer; Begin Assign(Input,'Input.Txt'); Reset(Input); Assign(Output,'Output.Txt'); ReWrite(Output); ReadLn(c, stl);{* Координаты ферзя записаны в файле Input.Txt.*)
Массив — фундаментальная структура данных I f (с<' а') Or (c>'h') Or (stl<l) Then WriteLn {'Некорректный Else Begin str:= Ord(ch) -Ord('a') +1; символ в номер строки. *) TInit(MaxN,str,stl,A); TPrint(MaxN,MaxM,A);{"Процедура ранее рассмотренной. *) End; Close (Input) ; Close (Output) ; End;
297 Or ввод.')
(stl>MaxN)
{"Преобразуем
совпадает
с
Основная программа состоит из одной строки — вызов процедуры Solve. Измените решение так, чтобы фиксировались к л е т к и доски, н а х о д я щ и е с я под боем хотя бы одной из фигур — ферзя и ладьи. Задания для самостоятельной работы 1. Дан двумерный массив. Н а й т и сумму и количество элементов в к а ж д о м столбце: • к р а т н ы х к ; или к2ш, • п о п а д а ю щ и х в интервал от А до В; • я в л я ю щ и х с я простыми числами; • п о л о ж и т е л ь н ы х и л е ж а щ и х выше главной диагонали. 2. Д а н двумерный массив. Н а й т и : • сумму элементов в строках с по к 2 \ • номера всех м а к с и м а л ь н ы х элементов; • номера первых отрицательных элементов каждой строки (столбца); • номера последних отрицательных элементов каждой строк и (столбца); • количество элементов в каждой строке, больших (меньш и х ) среднего арифметического элементов данной строки; • номера первой п а р ы неравных элементов в каждой строке. 3. Д а н ы два к в а д р а т н ы х массива А и В. Вывести тот из н и х , у к о т о р о г о след м е н ь ш е (сумма элементов главной диагонали). 4. Д а н д в у м е р н ы й массив. Определить: • есть л и в данном массиве отрицательный элемент; • есть л и два одинаковых элемента;
Часть третья
298
• есть ли данное число А среди элементов массива; • есть ли в заштрихованной области массива элемент, равн ы й А (массив имеет размерность п*п): h
J2
к. — ... .......... —
к2 5. Дан двумерный массив. Определить, есть ли в данном массиве строка (столбец): • состоящая только из п о л о ж и т е л ь н ы х элементов; • состоящая только из п о л о ж и т е л ь н ы х и л и нулевых элементов; • состоящая только из элементов, больших числа А; • состоящая только из элементов, п р и н а д л е ж а щ и х промеж у т к у от А до В. 6. Дан двумерный массив. Выполнить следующие преобразован и я с ним: • в к а ж д о й строке сменить знак максимального по модулю элемента на противоположный; • последний отрицательный элемент к а ж д о г о столбца заменить нулем; • положительные элементы у м н о ж и т ь на первый элемент соответствующей строки, а отрицательные — на последний; • заменить все элементы строки с номером k и столбца с номером I на противоположные по знаку; • к элементам столбца с номером прибавить элементы столбца k2. 7. Написать программу, определяющую поля шахматной доски, н а х о д я щ и е с я под боем к о н я . 8. Ввести координаты ферзя и к о н я и определить: • бьют ли фигуры друг друга; • если к о н ь ходит первым, то бьет ли он ферзя; • бьет ли ферзь коня, если первый ход ферзя. 9. Составить программу заполнения и вывода в файл таблиц сложения и у м н о ж е н и я ц и ф р в заданной (произвольной) системе счисления. Д л я десятичной системы счисления они имеют вид:
Массив — фундаментальная структура данных 298
Ц0_
63
72
81
10. Д а н ы два двумерных массива одинаковой размерности. Создать третий массив той же размерности, каждый элемент которого равен: • сумме соответствующих элементов первых двух; • 1 (True), если соответствующие элементы массивов имеют одинаковый знак, иначе элемент равен 0 (False). 11. Дан двумерный массив. Сформировать одномерный массив, к а ж д ы й элемент которого равен: • произведению четных, положительных элементов соответствующего столбца; • количеству элементов соответствующей строки, больших данного числа; • наибольшему по модулю элементу соответствующего столбца; • количеству отрицательных элементов, кратных 3 или 5, соответствующей строки; • первому четному элементу соответствующего столбца, если такого нет, то нулю.
Часть третья
12. Дан д в у м е р н ы й массив. Определить: • есть ли в данном массиве строка, в которой ровно два отр и ц а т е л ь н ы х элемента; • есть ли в данном массиве столбец, в котором имеются одинаковые элементы; • есть ли в данном массиве строка, в которой имеется два м а к с и м а л ь н ы х элемента всего массива; • есть ли в данном массиве столбец, в котором равное количество п о л о ж и т е л ь н ы х и о т р и ц а т е л ь н ы х элементов; • есть ли в данном массиве строка, с о д е р ж а щ а я больше пол о ж и т е л ь н ы х элементов, чем о т р и ц а т е л ь н ы х . 13. Сформировать двумерные массивы по следующим правилам:
14. Определить, я в л я е т с я ли массив (N*N) м а г и ч е с к и м квадратом (значения элементов массива из интервала от 1 до N*N), то есть суммы по всем горизонталям, в е р т и к а л я м и двум диагоналям д о л ж н ы совпадать. П р и м е р ы м а г и ч е с к и х квадратов. 24
Массив — фундаментальная структура данных
301
П о с к о л ь к у сумма элементов магического квадрата равна l+2+3+...+N2=N2*(N2+l)/2, то соответствующие суммы равны N*(N2+1 )/2. Т а к , при N=5, в к а ж д о й строке, столбце и на г л а в н ы х д и а г о н а л я х сумма элементов равна 5*(5 2 +1)/2=65. 15. Дан д в у м е р н ы й массив (N*N). Зеркально отобразить его элем е н т ы относительно: • • • •
горизонтальной оси симметрии; в е р т и к а л ь н о й оси симметрии; главной диагонали; побочной диагонали.
Часть третья
302
Занятие № 23. Д в у м е р н ы е массивы. Вставка и удаление План занятия • и з у ч е н и е р а з л и ч н ы х м е т о д о в в с т а в к и и у д а л е н и я элементов д в у м е р н о г о м а с с и в а ; • выполнение самостоятельной работы. Э к с п е р и м е н т а л ь н ы й раздел работы 1. В с т а в и т ь с т р о к у и з н у л е й п о с л е с т р о к и с н о м е р о м t . Д л я реш е н и я этой задачи необходимо: • п е р в ы е t с т р о к о с т а в и т ь без и з м е н е н и я ; • все с т р о к и п о с л е t-й с д в и н у т ь н а одну; • э л е м е н т а м с т р о к и t+1 п р и с в о и т ь з а д а н н о е з н а ч е н и е . В процедуре ввода д а н н ы х кроме ввода размеров массива с л е д у е т п р е д у с м о т р е т ь ввод н о м е р а с т р о к и — ReadLn(n,m,t). Ключевая процедура вставки строки имеет вид: Procedure InsertSti(v,w,г:Integer;Var Var l,j:Integer; Begin For i:=v DownTo ri 1 Do For j : =1 To w Do X[i + l , j ] :=X[ 1,3] For 3 :=1 To w Do X[r+l,j]:=0; End;
X:TMyArray);
;
И з м е н и т е п р о ц е д у р у д л я в с т а в к и с т р о к и п е р е д с т р о к о й с заданным номером. 2. В с т а в и т ь строку и з н у л е й после всех строк, с о д е р ж а щ и х м а к с и м а л ь н ы й э л е м е н т м а с с и в а . П р и в е д е м п о л н ы й т е к с т решен и я (для н а п о м и н а н и я о нашей схеме р е ш е н и я задач). ($R + ) Program Му23_1; Const MaxN=8; MaxM=9; Type TMyArray=Array[1..2*MaxN,1..MaxM] Of Integer; (* резервируем 2*MaxN строк на тот случай, если максимальный элемент массива есть в каждой строке. *} Var А:ТМуАггау; п,m:Integer; Procedure TInit(Var v,w:Integer;Var X: TMyArray); Var 1,3:Integer; Begin
Массив — фундаментальная структура данных 302 Assign (Input, 'Input. T x t ' ) ; Reset ReadLn (v, w) ; For l:=1 To v Do For j:=l To w Do Read ( X [ i , j ]) ; Close (Input) ; Ends-
(Input)
;
Procedure TPrmt (v, w: Integer ;X: TMyArray) ; Var i,j-.Integer; Begin Assign (Output, 'Output. T x t ' ) ; Rewrite (Output) For i:=l To v Do Begin For j : =1 To w Do Wri te ( X [ i , j ) : 4) ; WriteLn; End; Close (Output) ; EndsProcedure InsertStr (v, w,r: In teger; Var X:TMyArray); Var l , j : Integer; Begin For i:=v DownTo r + 1 Do For j : =1 To w Do X[i + l , j ] :=X[i,j] ; For J : =2 To w Do X [r+1, j ]-.=0; End; Function TMax (v,w: Integer ;X: TMyArray) -.Integer; (*Поиск максимального элемента. *) Var l,j,max:Integer; Begin max: =X[1,1 ] ; For i:=1 To v Do For j :=1 To w Do I f X[i,j]>max Then max:=X [ l , j ] ; TMax:=max; EndsProcedure Solve(Var X: TMyArray); Var i,j,smax:Integer; Begin smax:=TMax(v,w,X) ; ("Находим элемент. *} i:=l; While i < = v Do B e g i n (*Почему тип цикла?*}
v:Integer;w:Integer;Var
максимальный
используем
этот
;
Часть третья
304
j:=1; While (J<=w) And (X[ 1, j J O s m a x J Do I n c ( j ) ; I f jow+1 Then Begin (*B строке есть максимальный элемент. *] InsertStr(v,w,l,X);{*Вставляем строку. *} Inc(v) ;{"Увеличиваем количество строк. *] Inc (1,2) ("Пропускаем вставленную строку. *) End Else Inc(i) ;("Обычное изменение индекса. *) End; End; Begin {"В основной программе только вызовы процедур - крупных «кирпичиков».*} TInit(n,m,A); Solve(n,m,A); TPrmt (n,m,A) ; End. И з м е н и т е р е ш е н и е д л я в с т а в к и н у л е в ы х с т р о к , п е р е д строк а м и , с о д е р ж а щ и м и м а к с и м а л ь н ы й элемент м а с с и в а . 3. У д а л и т ь с т р о к у с н о м е р о м t . Д л я того, ч т о б ы у д а л и т ь строку с н о м е р о м t, необходимо сдвинуть все с т р о к и , н а ч и н а я с д а н н о й , н а одну в в е р х . Последнюю с т р о к у м о ж н о «обнулить», м о ж н о не р а с с м а т р и в а т ь к а к строку м а с с и в а . Procedure DelStr(v, w,г:Integer;Var X:TMyArray); Var i,j:Integer; Begin For i:=r To v-1 Do For j:=l To w Do X[ i , j } : =X [ i +1, j ] ; For j:=l To w Do X[v,j] :=0; End; 4. У д а л и т ь с т р о к и , с о д е р ж а щ и е м а к с и м а л ь н ы й элемент массива. Procedure Solve(Var v:Integer/w:Integer; X:TMyArray); Var i,j,smax:Integer; Begin smax:=TMax (v,w,X); l: =1 ; While i < = v Do Begin
Var
Массив — фундаментальная структура данных 304 While (]<=w) And (X [ 1, j ] Osmax) I f ]<>w+l Then Begin DelStr(v,w,i,X) ; Dec (v) ; End Else Inc (l) ; End; End;
Do I n c ( j ) ;
С р а в н и т е эту п р о ц е д у р у с процедурой Solve, рассмотренной ранее п р и в с т а в к е строк, и о б ъ я с н и т е о т л и ч и я . 4. П о м е н я т ь м е с т а м и э л е м е н т ы столбцов с н о м е р а м и
и 1г-
С п р о ц е д у р о й Swap м ы у ж е встречались. Н а п о м н и м ее. Procedure Swap (Var х, Var z: Integer; Begin z:=x; x:=y; у: = z ; End;
у:
Integer);
Н а п и с а н и е п р о ц е д у р ы , р е а л и з у ю щ е й требования примера, не в ы з ы в а е т с л о ж н о с т е й . Procedure Change(v,stl,st2:Integer; X:TmyArray); Var i:Integer; Begin For i: =1 To v Do Swap (X[l, stl ], X[l, End;
Var
st2]
) ;
5. У д а л и т ь все с т р о к и и столбцы, с о д е р ж а щ и е м а к с и м а л ь н ы й элемент м а с с и в а . 2
7
9
3
5
6
9
0
4 1
2
1 9
5
8
7
6
9
4
3
5
6
9
2
1
Р а с с м о т р и м п р и м е р . М а к с и м а л ь н ы й элемент равен 9. Если в ы б р а т ь л о г и к у просмотра по строкам, а затем по столбцам, то результат о к а ж е т с я в е р н ы м . П р и просмотре ж е вначале по столбцам, а затем по строкам исключается только третий столбец. Выход и з с и т у а ц и и состоит в з а п о м и н а н и и номеров строк
306
Часть третья
и столбцов, с о д е р ж а щ и х м а к с и м а л ь н ы й элемент массива. Пусть м ы з а п о м н и л и в м а с с и в а х NumStr и NumStl н о м е р а у д а л я е м ы х с т р о к и с т о л б ц о в . Д л я п р и м е р а о н и и м е ю т в и д : (1, 2, 3, 4, 5) и (3, 3, 3, 3, 3). П я т ь р а з у д а л я т ь т р е т и й столбец я в н о нет смысла, поэтому из массивов необходимо удалить повторяющиеся э л е м е н т ы , т. е. во в т о р о м м а с с и в е о с т а в и т ь о д н у 3. О д н а к о э т и м не о г р а н и ч и в а ю т с я в о з н и к а ю щ и е с л о ж н о с т и . П е р в а я с т р о к а у д а л е н а , т р е б у е т с я у д а л я т ь в т о р у ю , но она у ж е в н а ш е й м а т р и це с т а л а п е р в о й . Н е о б х о д и м о после к а ж д о г о у д а л е н и я и з м е н и т ь н о м е р а с т р о к (столбцов) в м а с с и в е NumStr — у м е н ь ш и т ь на е д и н и ц у те н о м е р а , к о т о р ы е б о л ь ш е н о м е р а у д а л е н н о й с т р о к и . Н и ж е по т е к с т у п р и в е д е н а т о л ь к о п р о ц е д у р а Solve, о с т а л ь н ы е части программы совпадают с ранее рассмотренными. Procedure Solve(Var v, w:Integer;Var X:TMyArray); T y p e OMyArray=Array[1..MaxN] Of Integer; ("Определяем одномерный массив для хранения номеров удаляемых элементов. Константа MaxN берется при условии, что количество строк в матрице всегда больше количества столбцов.*) Var 1,smax:Integer; NumStr,NumStl:OMyArray;(*Номера строк и столбцов. *} ykStr,ykStl:Integer;{"Количество массивах NumStr и NumStl. *} Procedure FNumfVar q,r:Integer;Var (*Формирование массивов с номерами с т р о к и столбцов. *) Var 1,j:Integer; Begin q:=0;r:=0; For l:=1 To v Do For j:= 1 To w Do I f X[I,j]=smax Then Begin Inc (q) ;Y[q] ,-=i; Inc (r);Z[r]:=j; End; End; Procedure {*Удаление Var Begin i : =1;
Sz (Var t:Integer; Var и з м а с с и в а повторяющихся i,j,1:Integer;
удаляемых элементов
в
Y, Z: OMyArray) удаляемых
X:OMyArray); элементов.*}
;
Массив — фундаментальная структура данных
While K t Do Begin З - i + l ; While j<=t Do I f X[i]=X[j] Then For l:=j X[t]:=0; Dec ( t ) ; End
To
t-1
307
Begin Do
X[l]:=X[1+1];
Else Inc(j); Inc(i) ; EndsEnd; Procedure Change(t,a:Integer; Var {"Уменьшение на единицу значений массива, превышающих заданную Var 1:Integer; Begin For i:=l To t Do I f X[i]>a Then Dec(X[I]); End;
X:OMyArray); элементов величину.*}
Begin {"Текст основной процедуры."j FillChar(NumStr,SizeOf(NumStr) ,0); ("Инициализация массивов."} FillChar(NumStl,SizeOf(NumStl) ,0) ; smax:=TMax (v, w,X) ; ( "Находим максимальное значение - текст функции не приводится."} FNum(ykStr,ykStl,NumStr,NumStl);("Определяем номера удаляемых строк и столбцов."} Sz (ykStr, NumStr) ; ("Удаляем повторяющиеся элементы."} Sz (ykStl,NumStl); i : =1 ; While i<=ykStr Do Begin DelStr(v,w,NumStr[l],X);{"Удаляем Change(ykStr,NumStr[i],NumStr);{"Корректируем массив номеров строк. *) Dec (v) ; Inc ( i ) ; End; i : =1; While K=ykStl Do Begin DelStl(v,w,NumStl[i],X);("Удаляем
строку."}
столбец."}
Часть третья
308
Change (ykStl,NumStl[±],NumStl) ("Корректируем массив номеров Dec(w); Inc(i); End; End;
; столбцов.*}
О т е х н о л о г и и н а п и с а н и я . Н а п о м и н а е м , что п р о ц е с с р а з р а б о т к и п р о ц е д у р ы Solve з а к л ю ч а е т с я в н а п и с а н и и «тела» процед у р ы с з а г о л о в к а м и с о с т а в л я ю щ и х частей. П р о г р а м м а к о м п и л и руется, с о х р а н я е т с я и т о л ь к о затем д о п и с ы в а ю т с я с о с т а в л я ю щ и е ч а с т и . В п р о ц е д у р е Solve есть д в а п р а к т и ч е с к и с о в п а д а ю щ и х ф р а г м е н т а ( ц и к л ы по i). И с к л ю ч и т е э т и п о в т о р е н и я , о н и недостойны Вас. З а д а н и я для самостоятельной работы 1. В с т а в и т ь • п е р в у ю с т р о к у п о с л е с т р о к и , в к о т о р о й н а х о д и т с я первый встреченный м и н и м а л ь н ы й элемент; • в т о р о й столбец п о с л е п е р в о г о с т о л б ц а , в к о т о р о м все элементы положительны; • н у л е в у ю с т р о к у и н у л е в о й столбец п е р е д с т р о к о й и столбц о м , в к о т о р ы х н а х о д и т с я п е р в ы й м и н и м а л ь н ы й элемент; • п о с л е д н ю ю с т р о к у после всех с т р о к , в к о т о р ы х есть зад а н н о е ч и с л о А; • в т о р о й столбец перед в с е м и с т о л б ц а м и , в к о т о р ы х нет отрицательных элементов; • п е р в у ю с т р о к у п е р е д в с е м и с т р о к а м и , в к о т о р ы х есть 0, и п е р в ы й с т о л б е ц после всех столбцов, в к о т о р ы х есть отрицательные элементы; • столбец и з н у л е й п о с л е с т о л б ц о в с м и н и м а л ь н ы м и элементами; • первую строку между средними строками; • с т р о к у и з н у л е й перед в с е м и с т р о к а м и , п е р в ы й э л е м е н т к о т о р ы х д е л и т с я н а 3. 2. У д а л и т ь • с т о л б ц ы , в к о т о р ы х есть м и н и м а л ь н ы й э л е м е н т ; • с т р о к у с н о м е р о м k и столбец с н о м е р о м I; • все с т о л б ц ы , в к о т о р ы х нет нулевого э л е м е н т а ; • все с т р о к и и с т о л б ц ы , н а п е р е с е ч е н и и к о т о р ы х с т о я т отрицательные элементы; • с р е д н ю ю с т р о к у (строки);
Массив — фундаментальная структура данных
3.
4.
5. 6.
7.
8.
309
• все столбцы, в которых первый элемент больше последнего; • столбец, в котором находится первый четный отрицательный элемент; П все столбцы, в которых первый элемент больше заданного числа А. Заменить • минимальный элемент в каждой строке на противопол о ж н ы й по знаку; • все элементы первых трех столбцов на их квадраты; • все симметричные элементы квадратной матрицы на нули. Поменять местами • средние столбцы; • средние строки с первой и последней; • средние столбцы со вторым и предпоследним; • средние строки; • первый максимальный и последний минимальный элементы; • в каждой строке первый элемент и максимальный по модулю; • в каждой строке первый отрицательный и последний положительный; • первую строку и строку, в которой находится первый нулевой элемент; • вторую и предпоследнюю строки; • первую строку с последней строкой, вторую — с предпоследней и так далее; • столбцы так, чтобы в конечном итоге они имели следующий порядок: • последний, предпоследний, ..., второй, первый; • первый, последний, второй, предпоследний, третий,... Найти максимальный элемент массива, встречающийся более одного раза. Расстоянием между строками назовем сумму произведений соответствующих элементов строк. Найти две строки с максимальным расстоянием. Дан массив размерностью N*N, N - нечетное число. Вывести элементы массива, при обходе его по спирали, начиная с центра. Д л я заданного целочисленного массива (N*N) найти максимум среди сумм элементов диагоналей, параллельных главной диагонали.
309 Ч а с т ь третья
9 . Д л я з а д а н н о г о ц е л о ч и с л е н н о г о м а с с и в а (N*N ) н а й т и м и н и м у м с р е д и с у м м э л е м е н т о в д и а г о н а л е й , п а р а л л е л ь н о й побочной диагонали матрицы. 1 0 . З н а ч е н и я э л е м е н т о в м а с с и в а н а х о д я т с я в и н т е р в а л е от А до В . Д в е с т р о к и м а т р и ц ы назовем п о х о ж и м и , если о н и отличаются только п о р я д к о м элементов. Н а й т и п а р ы п о х о ж и х строк. 1 1 . Э л е м е н т м а с с и в а А[i,j] н а з ы в а ю т с е д л о в о й т о ч к о й , е с л и о н является м и н и м а л ь н ы м в строке с номером i и максимальным в с т о л б ц е с н о м е р о м ]. Н а й т и с е д л о в ы е т о ч к и . 1 2 . Д л я д а н н о г о м а с с и в а н а й т и т а к и е з н а ч е н и я h, ч т о k с т р о к а совпадает с k столбцом. 13. Среди столбцов з а д а н н о г о ц е л о ч и с л е н н о г о м а с с и в а н а й т и столбцы, состоящие только из нечетных элементов. 1 4 . Э л е м е н т A[i,j] н а з о в е м л о к а л ь н ы м м и н и м у м о м , е с л и он строго м е н ь ш е всех своих соседей. С о с е д я м и я в л я ю т с я э л е м е н т ы A[k,l]c (i-l< k< i+1), (j-l< I <j+l), (k,l)* ( i j ) . Н а й т и к о л и ч е с т во л о к а л ь н ы х м и н и м у м о в д л я з а д а н н о г о м а с с и в а . Н а й т и м а к с и м у м среди л о к а л ь н ы х минимумов. 15. Р е ш и т ь з а д а ч у п о и с к а ч и с л а X в д в у м е р н о м массиве А[ 1..N.1..M] (элементы в строках и столбцах упорядочены) за время, пропорциональное 0(N+M).
Массив — фундаментальная структура данных
311
Занятие № 24. Несколько задач на технику работы с двумерными массивами План занятия • разбор и м о д и ф и к а ц и я п р о г р а м м : п е р е м н о ж е н и я м а т р и ц , р е ш е н и я с и с т е м ы л и н е й н ы х у р а в н е н и й , п р и в е д е н и е матр и ц ы к блочному виду, с о р т и р о в к и элементов строк матр и ц ы и п о и с к а с у м м ы м и н и м а л ь н ы х элементов в строках и столбцах матрицы; • в ы п о л н е н и е с а м о с т о я т е л ь н о й работы. Экспериментальный раздел работы 1. Составить п р о г р а м м у в ы ч и с л е н и я п р о и з в е д е н и я двух целоч и с л е н н ы х м а т р и ц А[п,т] и B[m,t], Элементы результирую щ е й , т а к ж е целочисленной, м а т р и ц ы C[n,t] о п р е д е л я ю т с я по ф о р м у л е C[i,j]=A[i.l]*B[l.]]+A[i,2]*B[2.j]+...+A[i,m]* *В[т,]], где (п, т), (m,t), (n,t) — размерности м а т р и ц А, В, С соответственно. П р и в ы п о л н е н и и у м н о ж е н и я количество столбцов в первом массиве равно количеству строк во втором массиве. Пример:
2*3+1*2+5*1 = 13
2*4+1*0+5*2=18
П р и в е д е м п о л н ы й текст р е ш е н и я . В ф а й л Output.Txt запис ы в а ю т с я м а с с и в ы А, В-а. результат — массив С. Независимость ф р а г м е н т о в л о г и к и (процедур TInit, TPrint) дает результаты. Они и с п о л ь з у ю т с я д л я ввода и вывода д в у м е р н ы х массивов любой р а з м е р н о с т и . ($R+) Program Му24_1; Const MaxN=8/ MaxM=8; Type TMyArray=Array[1..MaxN,1..MaxM] Of Integer; Var A,B, C: TMyArray ; n,m,t:Integer; Procedure Init; Begin Assign (Input, 'Input.Txt') ; Reset (Input) ; Assign (Output, 'Output. Txt') ; ReMrite (Output) ;
311
311 Часть третья
End; Procedure TInit(Var v, w:Integer;Var Var i,j:Integer; Begin ReadLn(v,w); For i;=1 To v Do For j:=1 To w Do Read(X[i,j]) ; End;
X:TMyArray);
Procedure TPrint(v,w:Integer;X:TMyArray); Var i,j:Integer; Begin Append (Output) ; For i:=l To v Do Begin For j:=l To w Do Write (X [ i , J ] : 4) ; WriteLn; End; WriteLn; End; Procedure Solve(v,w, Z:TMyArray); Var l , j , t : I n t e g e r ; Begin For i:=1 To v Do For t: =1 To r Do Begin For j:=1 To w Do Z[i,t]:=Z[i,t]+X[i,j]*Y[j,t]; End; End;
r:Integer;X,Y:TMyArray;Var
Z[i,t]
Procedure Done; Begin Close (Input) ; Close(Output); Begin Init; TInit (n,m,A); TPrint (n,m,A); TInit (m,t,B); TPrint(m,t,B); Solve (n,m,t,A,B,C); TPrint (n, t,C) ; Done; End.
:=0;
End;
Пусть A — квадратный массив. Измените программу так, чтобы вычислялось выражение А+А*А+А*А*А.
Массив — фундаментальная структура данных
313
2. Р е ш е н и е с и с т е м ы л и н е й н ы х у р а в н е н и й методом Гаусса. Система и з п л и н е й н ы х а л г е б р а и ч е с к и х у р а в н е н и й с п неизвес т н ы м и имеет вид: 3n*x1+ai2*X2+. a2i*xi+a22*:<2+
+a1„*xln=b1 +Я2„*х2„=Ьг
ani*Xi+3n2*X2+ +a„„*x„„=b„ Р е ш е н и е м с и с т е м ы н а з ы в а е т с я т а к а я у п о р я д о ч е н н а я совок у п н о с т ь ч и с е л Xj=cr х2=с2, ..., хп=ся, к о т о р а я обращает все у р а в н е н и я с и с т е м ы в в е р н ы е р а в е н с т в а . М ы п р е д п о л а г а е м , что система имеет единственное р е ш е н и е (не будем в д а в а т ь с я в теор и ю л и н е й н о й а л г е б р ы ) . В этом с л у ч а е н и ж е о п и с а н н а я схема р е ш е н и я и м е е т право на с у щ е с т в о в а н и е . Суть метода Гаусса п о с л е д о в а т е л ь н о е и с к л ю ч е н и е н е и з в е с т н ы х из у р а в н е н и й (прямой ход метода). После п р и м е н е н и я с х е м ы и с к л ю ч е н и я исходн а я с и с т е м а у р а в н е н и й преобразуется в р а в н о с и л ь н у ю (имеющ у ю то ж е р е ш е н и е ) т р е у г о л ь н у ю : XL+L2*Х2
+ 13*Х3+ Х2+23*Х3+
+ 2 п *Х„ = 1 +2П*Х„=2 х
л=л
И з этой с и с т е м ы последовательно н а х о д я т с я з н а ч е н и я всех н е и з в е с т н ы х хя, xn_v ..., xt (обратный ход метода). З н а ч е н и е хп п о д с т а в л я е т с я во все у р а в н е н и я , з а т е м полученное з н а ч е н и е хп_, и т. д., до первого у р а в н е н и я , из которого н а х о д и т с я значен и е хг Д л я с в е д е н и я с и с т е м ы к треугольному в и д у к о э ф ф и ц и е н т ы первого у р а в н е н и я д е л я т с я на ап, получим: X1 + a12/a11 *х2+. +aln/a11*x„=b1/au и л и (обозначив ч а с т н ы е по-другому) х1+12*х2+гз*х3+ +п*х„=1. Е с л и аП=0, то в п е р в о м столбце и щ е т с я ненулевой коэффиц и е н т , а з а т е м п е р е с т а в л я ю т с я соответствующие с т р о к и . Существование такого коэффициента следует из единственности решен и я системы. Пользуясь полученным результатом, неизвестное х[ и с к л ю ч а е т с я и з д р у г и х у р а в н е н и й с и с т е м ы . Д л я этого и з к а ж дого у р а в н е н и я в ы ч и т а е т с я первое, предварительно умноженное на с о о т в е т с т в у ю щ и й к о э ф ф и ц и е н т п р и хг После этого первое уравнение оставляют в покое, а над остальными совершаются аналогичные преобразования.
314
Часть третья
Program Const
Му24_2; MaxN=l0;
Type
MaxM=MaxN+l;
TMyArray=Array[1..MaxN,1. OMyArray=Array[1..MaxN] Var A:TMyArray; X:OMyArray; n:Integer; Procedure TInit(Var ("Ввод коэффициентов Var i , j Begin
. MaxM] Of Of Real ;
Real;
v:Integer;Var X:TMyArray) системы уравнений. *) -.Integer;
Assign(Input,'Input.Txt')/Reset(Input) ReadLn(v); For i:=l To v Do For j:=l To v+1 Do Read(X[l,j ]) ; Close (Input); End; Procedure TPrint(v:Integer;X:OMyArray); (*Вывод решения. *} Var l:Integer; Begin
;
;
Assign (Output, 'Output. T x t ' ) ; ReWnte (Output) ; For i:=l To v Do Write (X[i] : 6) ; WriteLn; Close (Output); End; Function Solve(v:Integer;A:TMyArray;Var X:OMyArray) :Boolean;{ "Находим решение - массив X, если оно есть. *) Var 1,j,k,1:Integer ; v.Real; Pp:Boolean; Procedure Swap(k,1:Integer);{*Переставляем строки с номерами k, 1.*} Var j : Integer;w:Real; Begin For j : =1 To v Do Begin w:=A[k, j ] ;A [k, j ] : =A[1 , j ] ; A[1,j]:=w;End; End; Function Search(j:Integer):Integer;("Поиск ненулевого коэффициента в столбце с номером j.
Массив — фундаментальная структура данных 314
Поиск осуществляется 3+1 .. v.") Var г:Integer; Begin i:=J+l; While (i<=v) Search:=i; Ends-
And
Begin {"Основная j:=1;Pp:=False;
в строках
(A[i,j]=0)
часть
Then
Pp: =True
Do
номерами
Inc(i);
решения.*)
While (J<=v) And Not (Pp) Do If A[j,J]=0 Then Begin k:=Search(j); If k>v End;
с
Else
If Not Pp Then Ведш{*Если w:=A[j,j];(*Прямой ход. с номером j . *)
Begin
Swap (j ,k) ;
есть решение.*) Преобразуем уравнение
For k:=j То v+1 Do A[j,k]:=A[j,k]/w; For k:=j+l To v Do Begin {"Исключаем ху из остальных уравнений. *) w:=A[k,j]; For l:=j EndsEnd;
To v+1 Do A[k, 1] : =A[k, 1 ]-A [j , 1] *w;
Inc (j);{*Переходим End;
к другой
строке.*}
If Not Pp Then Begin (*Обратный ход. *) For j:=v DownTo 1 Do Begin X[j] :=A[j,v+l] ; For i:=j-l DownTo 1 Do ("Исключаем найденное из всех уравнений.*} A [i, v+1 ] : =А [1, v+1]-А [1,3 ] *X[j] ; End; End; SolveNot Pp; End; Begin("Основная программа.") TInit(n,A);[*Ввод системы уравнений.*} If Solve(n,A,X) Then TPrint(n,X);("Если решение, то его поиск и вывод результата. End.
есть *)
xj
315 Часть третья 3. Д а н а м а т р и ц а и з 0 и 1. Т р е б у е т с я п р и в е с т и ее к б л о ч н о м у по с т о л б ц а м в и д у , т. е. п е р е с т а в и т ь с т о л б ц ы т а к , ч т о б ы в н а ч а л е ш л и с т о л б ц ы с е д и н и ц е й в п е р в о й с т р о к е , з а т е м — во второй (если они есть) и т. д.
Пример: В п е р в о й с т р о к е у к а з а н ы н о м е р а столбцов. Суть р е ш е н и я в просмотре к а ж д о й строки одновременно слева направо и справа налево. Если п р и просмотре с л е в а н а п р а в о н а й д е н столбец с 0, а с п р а в а н а л е в о — с 1, то э т и с т о л б ц ы п е р е с т а в л я ю т с я . П р и переходе к с л е д у ю щ е й с т р о к е п р о с м о т р слева н а п р а в о следует прод о л ж и т ь со с т о л б ц а , на к о т о р о м з а к о н ч е н ы д е й с т в и я п р и обработке п р е д ы д у щ е й с т р о к и . Procedure Solve(v:Integer; Var X:TMyArray); Var i,w:Integer; Procedure Swap(q,r:Integer); Var i,w:Integer; Begin For i:=l To v Do Begin w :=X [ i,q] ;X [l, q] : =X [i , r] ; X[i,r]:=w;End; End; Procedure Change_St (i:Integer;Var k:Integer); ("Обработка строки с номером i . Параметр k номер с т о л б ц а , на котором заканчиваются действия в строке 1. *) Var j:Integer; Begin j :=v; While k<j Do Begin I f X[i,k]=l Then Inc(k)l"Поиск единицы. *} Else I f X[i,j]=0 Then Dec(j){"Поиск нуля.") Else Begin Swap(k,j) ; Inc(k); Dec(j); End; End;
Массив — фундаменталь I f X[i,k]=l Then Inc (к) ;{ "Зачем этот оператор?*} EndsBegin w:=J ; i: =1 ; While (K=v) And (w<v) Do Begin {*Пока не просмотрены все строки и не исчерпаны все столбцы, выполняем действия из тела цикла. *} Change_St (i,w);I*Обрабатываем строку с номером 1.*} Inc(i); {*Изменяем номер строки.*} End; End; Л о г и к а обработки соответствует примеру, приведенному выше по т е к с т у ? В н е к о т о р ы х з а д а ч а х необходимо проверить, является л и м а т р и ц а блочной и по столбцам и по с т р о к а м (сверху и слева у к а з а н ы н о м е р а столбцов и строк). i_ J L J L A J L J L _L _°__L J L _ L J L JL i l i l l l l
1 j L _ L _ ! J L JLJL _2_ _o_ _o_ _o_ _o_ i
1 A A 1 U 5 0 1 0 1 0
L 0
l l l i J L 4_ _0_ _0_ J _ _1_ _0_ _0_ 5 1 1 0 0 0 0
4__2__6_ _5_J_ J3_ J 1_ J 0__0__0__0_ _5_J__1__0__0__0__0_ j4__o__o_ j 3 0 0 0 0
1 1
П р о в е р и т ь , имеет л и м а т р и ц а блочный вид. 4. Р а с с м о т р и м с л е д у ю щ у ю задачу. Д а н а ц е л о ч и с л е н н а я матриц а А. С ф о р м и р о в а т ь м а т р и ц у В т а к и м образом, чтобы в к а ж дой с т р о к е б ы л и з а п и с а н ы номера столбцов м а т р и ц ы А в пор я д к е , соответствующем н е у б ы в а н и ю з н а ч е н и й элементов в той ж е строке м а т р и ц ы А. Пример. Д л я простоты р е ш е н и я и з м е н и м т и п ы д а н н ы х . Д в у м е р н ы й массив определим через о д н о м е р н ы е массивы (найдите место в тексте процедуры, где этот ф а к т используется).
В 3 I 2 I 1 I 6 I 5 I 4 4 6 1 5 2 3 2 6 1 4 3 5_ 4 5 2 3 6 1 3 2 5 1 6 4
Часть третья
318 Type
OMyArray=Array[1..MaxN] TMyArray=Array[l..MaxN]
Of Of
Integer; OMyArray;
Элементы к а ж д о й строки переписываются в дополнительн ы й м а с с и в , а з а т е м с о р т и р у ю т с я о д н и м из и з в е с т н ы х методов с одновременной перестановкой соответствующих элементов в строк е м а т р и ц ы В. Н а ч а л ь н ы й вид В п р и в е д е н в т а б л и ц е . В 1
2
3
4
5
6
1
2
3
4
5
6
1
2
3
4
5
6
1
2
3
4
5
6
1
2
3
4
5
6
1
2
3
4
5
6
Procedure Solve(v:Integer;X:TMyArray;Var Y:TMyArray); Var i,t,j:Integer; Z:OMyArray; Procedure Swap(Var q, r:Integer) ; Var w:Integer; Begin w:=q;q:=r;r:=w; End; Begin For i:=l To v Do Begin Z:=X[i]; For t:=v-l DownTo 1 Do{"Сортировка.*) For j:=1 To t Do I f Z[J]>Z[J+1] Then Begin Swap (Z [ j ] , Z [ j +1 ] ) ;Swap (Y[I , j ] , Y[l, j +1 ] ) ; End; End; End; 5. Д а н а ц е л о ч и с л е н н а я м а т р и ц а А. Требуется в ы п о л н и т ь следующее преобразование: н а й т и в к а ж д о й с т р о к е м и н и м а л ь н ы й э л е м е н т и в ы ч е с т ь его и з всех элементов д а н н о й с т р о к и ; затем аналогичное преобразование в ы п о л н и т ь и со всеми столбц а м и ; п р и в ы п о л н е н и и д е й с т в и й п о д с ч и т ы в а т ь с у м м у миним а л ь н ы х элементов.
Массив — фундаментальная структура данных 318
Сумма м и н и м а л ь н ы х элементов д л я приведенного п р и м е р а равна 31.
Function Solve(v:Integer;Var X:TMyArray):Integer; f "Функция возвращает сумму минимальных элементов. *} Var s,i,w,j:Integer; Function MmStr (t: Integer) : Integer ;{ "Поиск минимального элемента в строке. *} Var j ,mm-.Integer ; Begin mm : =X[ t , 1] ; For j:=l To v Do I f X[t, j] <mm Then mm : =X [ t , j ] ; MmStr: =mm ; EndsFunction MmStl (t: Integer) -.Integer; ("Поиск минимального элемента в столбце. ") Var i,mm: Integer ; Begin min:=X[l,t]; For l: =2 To v Do I f X[i,t]<min Then mm : =X[i, t ] ; MinStl :=minsEnd; Procedure SubStr (i , k: Integer) ; ( "Вычитание из элементов строки с номером i значения к.*}
Часть третья
320 Var j:Integer; Begin For j:=l To v Do X[1,j] :=X[1,J]-k; End; Procedure SubStl(j,к:Integer);{*Вычитание элементов столбца с номером j значения Var 1:Integer; Begin For l: =1 To v Do X[l, J ] : =X[l, j ] -k ; End; Begin s : =0;{"Искомое значение.*} For l: =1 To v Do Begin { * Преобразование строкам. *} w:=MmStr (l) ; I n c (s ,w) ; SubStr ( l , w) ; End; For j:=l To v Do Begin {*Преобразование столбцам. *} w: =MmStl (j); Inc (s, w) ; SubStl (J , w) ; End; Solve:=s;(*Резулътат. *} End;
из к.*}
no
no
М о д и ф и ц и р у й т е р е ш е н и е з а д а ч и с л е д у ю щ и м образом. Из полученной в результате преобразования м а т р и ц ы исключите произвольный столбец и строку, н а пересечении к о т о р ы х получен нуль. С новой м а т р и ц е й в ы п о л н и т е о п и с а н н ы е в ы ш е операции. П р о д о л ж и т е этот процесс до тех пор, п о к а это в о з м о ж н о . Найдите общее з н а ч е н и е с у м м ы м и н и м а л ь н ы х элементов, получаемой в р е з у л ь т а т е э т и х действий. Задания для самостоятельной работы 1. Р е ш и т ь системы л и н е й н ы х у р а в н е н и й : 4,4*, -2,5х г + 192*3 - Ю3х4 = 4,3 5,5*, - 9,3*г - 142*э + 132*4 = 63 7,1*, - 115*2 + 5.3*э " 6,7*4 = -13 142Х, + 23,4хг - 83*э + 5.3*4 = 72
15,7х, + 6,6*2 - 5,7х3 + 1 Х5х4 = -2,4 83*, - 6,7х2 + S3*, - 45*4 = 5,6 6,3*, - 5,7хг - 23,4*3 + 6,6*4 = 7,7 5,6*, + 83*2 " 5.7*3 " 233*4 = 7 . 3
Массив — ф у н д а м е н т а л ь н а я структура д а н н ы х
321
2. Следом к в а д р а т н о й м а т р и ц ы н а з ы в а е т с я сумма элементов, р а с п о л о ж е н н ы х на главной диагонали. Д а н ы к в а д р а т н а я м а т р и ц а А п о р я д к а п, натуральное число т. В ы ч и с л и т ь следы м а т р и ц А, А2, А3, .... Ат. 3. Дана к в а д р а т н а я м а т р и ц а А п о р я д к а п. Переставить строки т а к , чтобы элементы в первом столбце были упорядочены по неубыванию. 4. В м а т р и ц е А п о р я д к а п*т заменить н у л я м и элементы, стоящ и е в строках и столбцах, где имеются нули. 5. Дана к в а д р а т н а я матрица А п о р я д к а га, к а ж д ы й элемент которой равен 0, 1, 5 или 11. Подсчитать в ней количество четверок A[i,j],A[i+l,j], А[i,]+l],A[i+l,j+l], в к а ж д о й из кот о р ы х все элементы р а з л и ч н ы . П р и м е ч а н и е Если все числа различны, то их сумма равна 17. Верно и обратное утверждение. 6. Дана к в а д р а т н а я матрица А п о р я д к а га из 0 и 1. Прямоугольн и к о м назовем часть матрицы, заполненной 1. Известно, что п р я м о у г о л ь н и к и не соприкасаются друг с другом. Н а й т и количество прямоугольников. На рисунке их 4. 0
1 0
1
1
0 0
0
1 1 0
0
1 1 0
1 1 0
1 1 0
1 1 0
7. Дана к в а д р а т н а я матрица А порядка га из натуральных чисел. Находится минимальный элемент А и вычитается из всех элементов матрицы. Затем строки и столбцы, содержащие нулевые элементы после вычитания, «вычеркиваются» из матр и ц ы . Процесс продолжается до тех пор, пока не будет получено одно число. Найти суммы минимальных элементов. 8. Дана к в а д р а т н а я матрица А порядка га из 0 и 1. Считаем, что столбец «покрывается» другим столбцом, если множество строк, представленных 1 в этом столбце, содержится в множестве строк, представленных 1 в другом столбце. Исключить такие столбцы из матрицы.
322
Ч а с т ь третья
9. Д а н а к в а д р а т н а я м а т р и ц а А п о р я д к а п из 0 и 1. И с к л ю ч и т ь и з м а т р и ц ы с т о л б ц ы и с т р о к и , с о д е р ж а щ и е одну единственн у ю 1. Е с л и п о с л е и с к л ю ч е н и я п о я в и л и с ь н о в ы е с т р о к и и с т о л б ц ы , у д о в л е т в о р я ю щ и е этому условию, то п р о д о л ж и т ь процесс и с к л ю ч е н и я . 10. К в а д р а т н у ю м а т р и ц у А п о р я д к а га из 0 и 1 н а з о в е м правильной, если в н е й нет к в а д р а т о в 2*2 и более, с о с т а в л е н н ы х тол ь к о и з 0 и л и 1. П р о в е р и т ь , я в л я е т с я ли А п р а в и л ь н о й . На рисунке А является правильной.
Массив — фундаментальная структура данных
323
Занятие № 25. Комбинированный тип данных (записи) План занятия • описание т и п а д а н н ы х ; • разбор процедур и ф у н к ц и й работы с д а т а м и и простейшим и г е о м е т р и ч е с к и м и объектами; • в ы п о л н е н и е самостоятельной работы. Описание типа данных. П р и работе с массивами основное ограничение з а к л ю ч а е т с я в том, что к а ж д ы й элемент д о л ж е н иметь один и тот ж е тип. Однако при решении многих задач в о з н и к а е т необходимость х р а н и т ь и обрабатывать совокупности д а н н ы х различного т и п а к а к единое целое. Запись — это составной тип данных, содержащий набор элементов р а з н ы х типов. Составляющие запись элементы н а з ы в а ю т с я ее п о л я м и . В записи к а ж д о е поле имеет свое собственное и м я . Чтобы описать запись, необходимо указать ее и м я , имена объектов, с о с т а в л я ю щ и х запись и их типы. Общий вид такой: Туре <ммя записи> = Record <поле 1>:<ТИП 1>; <поле 2>:<тмп 2>; <поле End;
п>:<тип
п>
Пример. О п и ш е м дату. Она состоит и з года, месяца и дня. Определив и х , м ы определяем дату к а к запись, состоящую из соответствующих полей. Туре
уеаг=1583..3000; month_number=l..12; day=l..31; date=Record dyear:year; dwonth:month_number; Dday:daysEnd; Разумеется, что тип date можно было определить и так: Type date=Record dyear: 1583..3000; dmonth: 1..12; dday: 1..31; End;
Часть третья
324
но о с т а н о в и м с я на первом способе, ибо п р и р е ш е н и и з а д а ч с дат а м и н а м п о т р е б у е т с я работа и с т и п а м и д а н н ы х y e a r , month_ number, day. С п о л е м з а п и с и в п р о г р а м м е м о ж н о п о с т у п а т ь , к а к с перем е н н о й того ж е т и п а , ч т о п о л е . О б р а щ а ю т с я к п о л ю по составному имени: <имя записи>.<имя Пример. Е с т ь Var t:date; имеют силу: t.dyear:=1987; t.dmonth:=12 t.dday:=30;
поля>.
Следующие операторы присвоения
;
Д л я не л ю б и т е л е й п и с а т ь много эта з а п и с ь с о к р а щ а е т с я с пом о щ ь ю о п е р а т о р а W i t h : W i t h < и м я з а п и с и > Do < о п е р а т о р > ; . О п е р а т о р ы п р и с в о е н и я в этом случае з а п и с ы в а ю т с я н е с к о л ь к о по-другому. With t Do dyear:=1987 dmonth:=12; dday:=30; End;
Begin ;
Оператор Case состоит и з в ы р а ж е н и я (селектора) и с п и с к а операторов, к а ж д о м у и з к о т о р ы х п р е д ш е с т в у е т либо одна и л и нес к о л ь к о к о н с т а н т ( н а з ы в а е м ы х к о н с т а н т а м и в а р и а н т а ) , либо слово Else. С е л е к т о р д о л ж е н б ы т ь п о р я д к о в о г о т и п а , п р и ч е м п о р я д к о в ы е з н а ч е н и я в е р х н е й и н и ж н е й г р а н и ц этого т и п а долж н ы н а х о д и т ь с я в д и а п а з о н е от - 3 2 7 6 8 до 3 2 7 6 7 . Оператор Case в ы б и р а е т д л я в ы п о л н е н и я тот оператор, перед к о т о р ы м стоит к о н с т а н т а , р а в н а я з н а ч е н и ю селектора и л и диапазону, с о д е р ж а щ е м у з н а ч е н и е селектора. Е с л и в диапазоне выбора не существует т а к о й к о н с т а н т ы выбора и в операторе имеется часть Else, в ы п о л н я е т с я оператор, н а п и с а н н ы й после слова Else. Е с л и часть Else отсутствует, н и один оператор не выполняется. Примеры: Var c:Char; Begin ReadLn (с) Case с Of
Массив — фундаментальная структура данных 324 ' 0' . . ' 9' : Wri teLn ('с — цифра . ' ) ; 'а ' . . ' z ' : WriteLn ('с — маленькая латинская буква . ' ) ; 'А'. . ' Z' -.WriteLn (' с — большая латинская буква. ' ) ; Else WriteLn С с - некоторый другой символ.') End; End; Var i:Integer; Begin ReadLn (1) ; Case l Of 0,2,4,6,8: WriteLn (' l - четное число меньше 10.'); 1,3,5,7,9: WriteLn (' l — нечетное число меньше 10.'); End; End; Экспериментальный раздел работы 1. Работа с д а т а м и . И с п о л ь з у ю т с я т и п ы д а н н ы х year, month_ number, day, date, определенные выше по тексту. Определим р я д п р о ц е д у р и ф у н к ц и й . Год с ч и т а е т с я в и с о к о с н ы м (366 дней), если он не последний год столетия и делится без остатка на 4. Кроме того, последний год любого столетия считается високосным только в том случае, если его номер делится на 400. Function Leap(x.-year) .-Boolean; Begin Leap:=(x Mod 400 =0) Or (x Mod 100O0) And (x Mod 4=0) ; End; Задача определения количества дней в месяце решается с помощью следующей ф у н к ц и и : Function DayM (х: yea г ;y:month_ п umber) -.day; Begin Case у Of 4,6,9,11: DayM: =30 ; { *B апреле, июне, сентябрей ноябре 30 дней. *} 1,3,5, 7,8,10,12: DayM: =31; { *В январе, марте, мае, июле, августе, октябре и декабре 31 день. *) 2: I f Leap (х) Then DayM:=29 Else DayM:=28; (*Если год високосный, то в феврале 29 дней. *) End; End; 12—452
Д л я в ы в о д а д а т ы , н а п р и м е р в виде 3 0 . 1 2 . 1 9 8 7 , и с п о л ь з у е т с я ее п р е о б р а з о в а н и е в т и п String. Function StringDate(w.Date):String; Var s,q:String; Begin str (w.dday,s); I f w.dday<10 Then s : = ' 0 ' + s ; f " П р е о б р а з у е м день даты. *} s : = s +' . ' ; str(w. dmonth,q);{"Преобразуем месяц даты. *} I f w.dmonth<10 Then s : = s + ' 0 ' + q + ' . ' Else s:=s+q+'.'; str(w. dyear,q);{*Преобразуем год даты. *} s:=s+q; StringDate:=s; End; Из текущей даты требуется получить дату следующего дня. П р и р е ш е н и и этой з а д а ч и н е о б х о д и м о у ч и т ы в а т ь , я в л я е т с я л и : • день даты последним днем месяца; • м е с я ц д а т ы п о с л е д н и м м е с я ц е м года. Procedure Begin
Tomorrow(х:date;Var
y.date);
У ••=>:; I f x. ddayODayM (x. dyear, x. dmonth J Then у.dday:=x.dday+1{"День даты не является последним днем месяца . *} Else I f х. dmonth<>12 Then Begin у.dmonth:=x.dmonth+1;{"Месяц даты не является последним месяцем в году."} у.dday:=l; End Else Begin у.dyear:=x.dyear+1; у. dmonth:=1; у. dday:=1; End; End; Д л я о п р е д е л е н и я д а т ы , к о т о р а я н а с т у п и т в б у д у щ е м , через определенное к о л и ч е с т в о д н е й (ж), м о ж н о в о с п о л ь з о в а т ь с я пред ы д у щ е й п р о ц е д у р о й , п р о д в и г а я с ь вперед на один д е н ь х раз. Однако ш а г п р о д в и ж е н и я м о ж е т б ы т ь б о л ь ш е , если w превышает к о л и ч е с т в о дней в году...
Массив — фундаментальная структура данных
327
Procedure Future(х:Integer;Var у:Date);(*х — количество д н е й , у — текущая дата. ") Var 1:Integer; Function Dyears (z:year):Integer;{"Определяем количество дней в году. "I Begin I f Leap(z) Then Dyears:=366 Else Dyears:=365 ; End; Begin For i:=l To y.dmonth-1 Do x: =x+DayM(y.dyear, i) ; I"Возвращаемся в начало года, определенного текущей датой.. ") х:=х+у.dday;{"Прибавляем количество дней из даты. Получаем общее количество дней от начала текущего года. *) While x>Dyears(у.dyear) Do Begin{"Счет идет по годам. *) х: =x-Dyears (у. dyear) ; Inc (у. dyear) ; E n d ; { " Г о д будущей даты сформирован. *) у. dmonth:=1;{"Определяем месяц будущей даты.") While x>DayM(у.dyear,у.dmonth) Do Begin I"Счет идет по месяцам."} х: =x-DayM (у. dyear ,у. dmonth) ; Inc (у. dmonth) ; End;{"Месяц будущей даты получен.") у. dday:=x;{"Оставшиеся дни являются днями будущей даты. *) End; Основная программа д л я работы с датами может иметь след у ю щ и й вид: Program Му25_1; Var t , г .-date; w:Integer; { "Процедуры и функции. "} Begin WriteLn Введите год, месяц, день даты: ' ) ; ReadLn (t. dyear, t . dmonth, t . dday) ; Tomorrow ( t , r) ; WriteLn (StrmgDate (r)) ; WriteLn (' Введите количество дней до будущей даты.'); ReadLn (w) ;
Чвсть третья Future(w,t); WriteLn (StringDate ReadLn; End.
(tj) ;
М н о г о ч и с л е н н ы е м о д и ф и к а ц и и д а н н о й п р о г р а м м ы получаются п р и в ы п о л н е н и и р я д а з а д а н и й д л я с а м о с т о я т е л ь н о й работы. 2. Н а ч а л ь н ы е с в е д е н и я и з к о м п ь ю т е р н о й г е о м е т р и и . Т о ч к а н а плоскости в декартовой прямоугольной системе координат о п и с ы в а е т с я п а р о й в е щ е с т в е н н ы х ч и с е л . В в е д е м соответств у ю щ и й т и п д а н н ы х — з а п и с ь . П р и и с п о л ь з о в а н и и веществ е н н о г о т и п а операции с р а в н е н и я л у ч ш е оформить специальн ы м и ф у н к ц и я м и . П р и ч и н а известна, н а типе Real в системе п р о г р а м м и р о в а н и я Турбо П а с к а л ь н е т о т н о ш е н и я п о р я д к а , п о э т о м у з а п и с и вида а=Ь, где а и Ь в е щ е с т в е н н ы е ч и с л а , лучш е не использовать. По этой ж е п р и ч и н е все в ы ч и с л е н и я с вещественным типом лучше выполнять с какой-то, заранее определенной, т о ч н о с т ь ю . Type TPomt=Record х, у: Real; End; Const Eps: Real=le-7;("Точность вычисления.*) ZeroPnt: TPoint = (x:0; y: 0) ;{"Точка с координатами 0,0. *} Пример реализации операций сравнений: Function RealEqfa, Ь: Real): Boolean; {"Строго равно.*} Begin RealEq:=Abs(a-b)<=Eps; End; А функция проверки имеет вид: Function EqPoint(А, ли точки ?"} Begin EqPoint:=RealEq(А.х, End;
совпадения двух точек на плоскости В:
TPoint):Boolean;{"Совпадают
В.х)
And RealEqfA.y,
В.у);
П р и в ы ч и с л е н и и р а с с т о я н и я м е ж д у д в у м я т о ч к а м и н а плоск о с т и удобнее о п я т ь ж е и с п о л ь з о в а т ь ф у н к ц и ю . Function Dist(А, В: TPoint): Real;("Расстояние между двумя точками на плоскости. *) Begin Dist:=Sqrt(Sqr(A.x-B.x) + Sqr(A.y-B.y)); End;
Массив — фундаментальная структура данных
329
Л ю б а я т о ч к а н а п л о с к о с т и определяет вектор (v) — направл е н н ы й о т р е з о к , с о е д и н я ю щ и й т о ч к у (0,0) с т о ч к о й (х,у). Вектор v м о ж н о з а д а т ь в п о л я р н о й системе к о о р д и н а т через его д л и н у (модуль) v и угол относительно оси ОХ. К о о р д и н а т ы пол я р н о й с и с т е м ы к о о р д и н а т ( и , ) и п р я м о у г о л ь н о й д е к а р т о в о й (х, у) с в я з а н ы с о о т н о ш е н и я м и : x=v*cos(
a),
y=v*sm
(а) ,
v=i/x 2 + у 2 , t a n (а) = у / х .
Р а з р а б о т а е м ф у н к ц и ю о п р е д е л е н и я угла а (в радианах) по к о о р д и н а т а м т о ч к и в д е к а р т о в о й системе к о о р д и н а т . ъ Function GetAngle (w:TPomt) :Real;(*w ненулевая точка на плоскости. *} Var v,angle:Real; Begin v:=Dist(w,ZeroPnt);{"Расстояние. *} I f RealEq(v,0) Then GetAngle:=0 Else Begin I f RealEq (w. x, 0) Then I f w.y>0 Then angle:=Pi/2 Else angle:=3*Pi/2 (*Выделяем особые случаи. Точка находится на одной из осей. *) Else I f RealEq(w.y,0) Then I f w.x>0 Then angle:=0 Else angle:=Pi Else Begin angle:=ArcTan (Abs (w.y/w.x)) ; (*Определяем четверть плоскости. *) I f w.x*w.y>0 Then Begin I f w.x<0 Then angle:=Pi+angle; End
Часть третья
330
Else I f w.x<0 angle Else End; GetAngle:=angle; End;
Then
angle:=Pi/2+ angle:=2*Pi-angle;
End; С к а л я р н ы м п р о и з в е д е н и е м двух векторов н а з ы в а е т с я число, равное п р о и з в е д е н и ю д л и н этих векторов н а к о с и н у с у г л а между н и м и . Е с л и х о т ь один из векторов н у л е в о й , то угол не определен, и с к а л я р н о е произведение по о п р е д е л е н и ю с ч и т а е т с я р а в н ы м н у л ю . Т а к и м образом, (v,w)= v*w*Cos(a), где а — угол м е ж д у в е к т о р а м и v и и>. Н а п о м н и м , что к о с и н у с угла м е ж д у в е к т о р а м и v, w вычисл я е т с я по формуле: Cos(a)=(v.x*w.x+v.y*w.y)/(v*w). Т а к и м образом, с к а л я р н о е п р о и з в е д е н и е (v,w)= v.x*w.x+v.y*w.y. Function ScDec (v, w: TPoint): Real; {"Скалярное произведение векторов в декартовой системе координат. *) Begin ScDec:=v.x*w.x+v.y*w.y; End;
прямоугольной
Вычисление скалярного произведения векторов в полярной системе координат ( ( и , а ) и в ы п о л н я е т с я по формуле: v*w=v.x*w.x+v.y*w.y =v*Cos( а )*ui*Cos(fi )+v*Sm( а )*w*Sin(fi )= =v*w*Cos(a-p). И з этого с о о т н о ш е н и я следует, что с к а л я р н о е произведение • н е н у л е в ы х векторов равно н у л ю тогда и т о л ь к о тогда, когда в е к т о р ы п е р п е н д и к у л я р н ы ; • больше н у л я , если угол м е ж д у в е к т о р а м и острый, и меньш е н у л я , если угол тупой. П р я м а я л и н и я н а плоскости, п р о х о д я щ а я через две т о ч к и p t и р 2 , определяется с л е д у ю щ и м л и н е й н ы м у р а в н е н и е м от двух переменных: (р2.х-р1.х)*(у-р1.у)^(р2.у-руу)*(х-р1.х). После преобразований получаем: ~(р2.у~ргу) *х +(р2.х-р1.х)*у+(р2.у~ргу)* * р,.х - (p2.x-prx) * Pj.y=0, и л и А*х + В*у + С = 0, после соответствующих обозначений. Если п р я м ы е з а д а н ы с п о м о щ ь ю у р а в н е н и й А ^ х + В ^ у + С ^ О и А2*х+В2*у+С2=0, ТО точка их пересечения, в случае ее существования (А,*В 2 -А 2 *В, < > 0 ) , определяется по формулам:
Массив — фундаментальная структура данных 330 х=- (С2 *B2~C2*Bj) / (А2 *В2-А2*В2) , у=(А2*с1-А,*с2) / (А1*В2-А2*В1) . О п р е д е л и м т и п д а н н ы х д л я о п и с а н и я п р я м о й Type TLine= =Record А, В, С: Real;End;, п р о ц е д у р у в ы ч и с л е н и я к о э ф ф и ц и е н тов в у р а в н е н и и п р я м о й , п р о х о д я щ е й через две т о ч к и и ф у н к ц и ю в ы ч и с л е н и я к о о р д и н а т т о ч к и пересечения двух п р я м ы х . Procedure Point2ToLme (А, В: TPomt; Var L: TLine}; { "Определение уравнения прямой по координатам двух точек. *} Begin L. А: =В. у-А. у ; L . В; =Л. х-В. х ; L. С: =- (A.'<*L.A + А.y*L.Ь); End; Function Lme2ToPomt (£L, sL: TLine; Var P: TPomt) : Boolean; {*Определение координат точки пересечения двух линий. Значение функции равно True, если точка есть, и False, если прямые параллельны. *} Var st: Real ; Begin st:=fL.A*sL.B-sL.A*fL.B; I f Not (RealEq(st, 0)) Then Begin Lme2ToPoint :=True; P.x:=-(fL.C*sL.B-sL,C*fL.B)/st; P. y:=(sL.A*fL.C-fL.A*sL.C)/st; End Else Lme2ToPomt :=False; End; Задания для самостоятельной работы 1. Н а п и с а т ь п р о ц е д у р у (или ф у н к ц и ю ) определения: • даты вчерашнего дня; • д а т ы , к о т о р а я была за m дней до у к а з а н н о й д а т ы ; • к о л и ч е с т в а дней, п р о ш е д ш и х от д а т ы t t до t 2 ; Q д н я недели, в ы п а д а ю щ и й на дату ( д л я р е ш е н и я задач и необходимо ввести т и п д а н н ы х «день недели» и опред е л е н н у ю дату «связать» с к о н к р е т н ы м днем недели); • годов с т о л е т и я ( 1 9 0 0 - 2 0 0 0 ) , которые н а ч и н а ю т с я и зак а н ч и в а ю т с я в воскресенье; • годов с т о л е т и я , с о д е р ж а щ и х м а к с и м а л ь н о е число воскресений.
331 Ч а с т ь третья
2. Д а н а д а т а В а ш е г о р о ж д е н и я ( в к л ю ч а я и д е н ь н е д е л и ) . Н а й т и те д а т ы , к о г д а В а ш д е н ь р о ж д е н и я «попадает« н а тот ж е д е н ь недели. 3. Д а н ы д а т ы р о ж д е н и я ч л е н о в с е м ь и и з п я т и ч е л о в е к . Столетн и м и юбилеями семьи я в л я ю т с я дни, когда сумма возрастов всех членов семьи к р а т н а 100. Н а й т и т а к и е д а т ы . 4. Д а н о в р е м я , о п и с а н н о е с л е д у ю щ и м о б р а з о м : Type
t i m e = Record
h:
0..23;т,
s:
0..59
End;
Описать: • логическую функцию для проверки, предшествует ли в р е м я tt в р е м е н и t2 (в р а м к а х с у т о к ) ; • п р о ц е д у р у , п р и с в а и в а ю щ у ю п а р а м е т р у 1 1 в р е м я , н а 1 сек у н д у большее времени t (учесть смену суток). 5. Д а н о с л е д у ю щ е е о п и с а н и е д а н н ы х : Const п=300; Type MyRecord = Record key: Integer; name : String; End; Table=Array[1..n] Of MyRecord. С ч и т а я , ч т о з а п и с и в м а с с и в е и м е ю т р а з л и ч н ы е к л ю ч и , описать: • п р о ц е д у р у , у п о р я д о ч и в а ю щ у ю з а п и с и м а с с и в а по у б ы в а н и ю з н а ч е н и й п о л я key; • л о г и ч е с к у ю ф у н к ц и ю поиск(4,/г,/г), о п р е д е л я ю щ у ю , есть л и в м а с с и в е t (все з а п и с и к о т о р о й у ж е у п о р я д о ч е н ы по в о з р а с т а н и ю з н а ч е н и й п о л я key) з а п и с ь со з н а ч е н и е м п о л я key, р а в н ы м k, и , е с л и есть, п р и с в а и в а ю щ у ю ее ном е р п а р а м е т р у h. 6. Д а н м а с с и в , с о д е р ж а щ и й и н ф о р м а ц и ю об у ч е н и к а х некоторой ш к о л ы (данные вводятся из файла). Написать программы: • ф о р м и р о в а н и я в т о р о г о м а с с и в а с д а н н ы м и об у ч е н и к а х только девятых классов; • определения количества учеников восьмых и девятых классах. 7. Б а г а ж п а с с а ж и р а х а р а к т е р и з у е т с я к о л и ч е с т в о м в е щ е й и общ и м в е с о м в е щ е й . Д а н м а с с и в , с о д е р ж а щ и й с в е д е н и я о багаж е н е с к о л ь к и х п а с с а ж и р о в ( д а н н ы е в в о д я т с я и з ф а й л а ) . Свед е н и я о б а г а ж е к а ж д о г о п а с с а ж и р а п р е д с т а в л я ю т собой запись с д в у м я п о л я м и : одно п о л е ц е л о г о т и п а ( к о л и ч е с т в о в е щ е й ) и о д н о — д е й с т в и т е л ь н о е (вес в к и л о г р а м м а х ) . О п р е д е л и т ь :
Массив — фундаментальная структура данных
333
• б а г а ж , с р е д н и й вес одной в е щ и в котором о т л и ч а е т с я не более ч е м н а 0 , 3 к г от общего среднего веса одной в е щ и ; • ч и с л о п а с с а ж и р о в , и м е ю щ и х более двух в е щ е й , и число п а с с а ж и р о в , количество вещей к о т о р ы х превосходит среднее ч и с л о в е щ е й ; • и м е е т с я л и п а с с а ж и р , б а г а ж которого состоит и з одной в е щ и весом менее 30 к г . 8. Р а з р а б о т а т ь п р о ц е д у р ы перевода к о о р д и н а т т о ч к и и з декартовой в п о л я р н у ю (и обратно) с и с т е м ы к о о р д и н а т . Д л я опис а н и я т о ч к и в п о л я р н о й системе координат использовать тип д а н н ы х Type TPol=Record r,ang:Real; End. Описание процед у р д о л ж н о и м е т ь с л е д у ю щ и й вид: Procedure TurnDecPol (w: TPomt; Var q: TPol); {"Перевод из декартовой системы координат в полярную.*) Procedure TurnPolDec (q: TPol; Var w: TPomt); ("Перевод из полярной системы координат в Декартовую. *) 9. Р а з р а б о т а т ь п р о ц е д у р ы с л о ж е н и я , в ы ч и т а н и я векторов в дек а р т о в о й ( п о л я р н о й ) системе к о о р д и н а т . Пример: Procedure AddPol(а, Ь: TPol; Var с: TPol); {"Сумма двух векторов в полярной системе координат. *} Begin c.ang:=(a.ang+Ь.ang)/2; С. r:=Sqrt (Sqr (a.r*Cos ($ . ang) -b. r"Cos (b.ang)) + Sqr (a . r*Sin (a. ang) -b.r*Sin (b.ang) )) ; End; 10. К о о р д и н а т ы точек о т р е з к а (pvp2) м о ж н о задать д в у м я парам е т р и ч е с к и м и у р а в н е н и я м и от одной независимой переменн о й t: p.x=prx+(p2.x-prx)*t u p.y=pvy^(p2.y-pvx)n. При 0<t<l т о ч к а p ( x , y ) л е ж и т н а отрезке, а при t<0 и л и t>l — вне о т р е з к а н а п р я м о й л и н и и , п р о д о л ж а ю щ е й отрезок. Д а н ы к о о р д и н а т ы двух точек н а плоскости, о п р е д е л я ю щ и х отрезок, и к о о р д и н а т ы третьей т о ч к и . Определить, принадл е ж и т л и т р е т ь я т о ч к а отрезку? 11. Д а н ы д в а о т р е з к а . Н а р и с у н к е т о ч к а пересечения отрезков, е с л и она есть, о б о з н а ч е н а к а к р = ( х , у ) . П е р в ы й отрезок зад а н т о ч к а м и р1=(х1,у2) и р2=(х2,у2), а второй — т о ч к а м и р3=(х3,у3) и р 4 = ( х 4 , у 4 ) . Определить к о о р д и н а т ы точки перес е ч е н и я отрезков.
Ч а с т ь третья
334
Р4
= (Х4,У4)
'Р2 = (Х2,У2)
Pi=(xi,yi) 12. Д а н о N т о ч е к . Н а й т и п а р у т о ч е к с м а к с и м а л ь н ы м р а с с т о я н и ем между ними. 13. Д а н о м н о ж е с т в о п р я м ы х . П о д с ч и т а т ь к о л и ч е с т в о т о ч е к пересечения этих п р я м ы х . 14. Д а н о два м н о ж е с т в а точек. Н а й т и пересечение и разность этих множеств. 15. И з в е с т н о , ч т о д в а о т р е з к а н а х о д я т с я н а о д н о й п р я м о й . Определить их взаимное расположение. 16. Д а н о м н о ж е с т в о точек. В ы б р а т ь и з него две т о ч к и , т а к и е , чтобы количество точек, л е ж а щ и х по р а з н ы е стороны от п р я м о й , проходящей через эти две точки, различалось н а и м е н ь ш и м образом. 17. Д а н ы к о о р д и н а т ы в е р ш и н т р е у г о л ь н и к а и т о ч к и . Определить взаимное расположение этих геометрических объектов. 18. Д а н ы к о о р д и н а т ы в е р ш и н треугольник и отрезка. Определить их взаимное расположение. 19. Д а н ы к о о р д и н а т ы в е р ш и н д в у х т р е у г о л ь н и к о в . О п р е д е л и т ь их взаимное расположение. 20. Д а н о м н о ж е с т в о п р я м ы х . О п р е д е л и т ь к о л и ч е с т в о ч а с т е й , н а которые они разбивают плоскость. П р и м е ч а н и я 1. В задачах все геометрические объекты задаются на плоскости. 2. Исходные данные в задачах вводятся из файла.
Часть четвертая
Динамические структуры данных
Занятие № 26. Динамические структуры данных План занятия • основные сведения о ссылочном типе данных; • линейные списки, изучение основных операций; • экспериментальная работа с программой «считалочка» и реализацией списка через обычные массивы; • выполнение самостоятельной работы. Основные понятия о ссылочном типе данных (указателях). Изученные до настоящего занятия типы данных являлись статическими. Область памяти для их размещения выделялась на стадии компиляции. Перераспределение ее на стадии выполнения программы не допускалось. Поэтому, в частности, приходилось резервировать, например для массивов, максимально возможный в задаче его размер. Выделение памяти для переменных на стадии выполнения программы возможно с использованием нового типа данных — указателей (ссылок). Значением указателя (переменной ссылочного типа) является адрес области памяти (первой ячейки) на переменные заданного базового типа. Итак, не значение переменной (величины), а адрес памяти, в которой находится переменная (принцип косвенной адресации). Для указателей область памяти выделяется статически (как обычно), а для переменных, на которые они указывают, — динамически, т. е. на стадии выполнения программы (они и называются динамическими). Для хранения динамических переменных выделяется специальная область памяти, называемая «кучей». Работая с указателями, мы работаем с адресами величин, а не с их именами. Может быть, поэтому и появляется возможность выделять для динамических переменных память на стадии выполнения? Д л я объявления указателей (переменных ссылочного типа) используется специальный символ «"», после которого указывается тип динамической (базовой) переменной.
Часть четвертая
336 л
Туре <имя_типа>= <базовый тип> ; Var <имя_переменной>: <имя_типа>; или л <имя переменной>: <баэовый тип> ; Например: Type ss = "Integer; Var х, у: ss;{"Указатели на переменные типа. *) a: "Real;{"Указатель на переменную типа. *}
целого вещественного
З а р е з е р в и р о в а н н о е слово Nil о б о з н а ч а е т к о н с т а н т у ссылочн о г о т и п а , к о т о р а я н и н а ч т о не у к а з ы в а е т . В ы д е л е н и е о п е р а т и в н о й п а м я т и (в « к у ч е » ) д л я д и н а м и ч е с к о й п е р е м е н н о й базового т и п а о с у щ е с т в л я е т с я с п о м о щ ь ю проц е д у р ы New(x), где х о п р е д е л е н к а к с о о т в е т с т в у ю щ и й у к а з а тель. О б р а щ е н и е к д и н а м и ч е с к и м п е р е м е н н ы м в ы п о л н я е т с я по п р а в и л у : <имя_переменной>~. Н а п р и м е р , х~:=15 — в область п а м я т и (два б а й т а ) , а д р е с к о т о р о й я в л я е т с я з н а ч е н и е м у к а з а т е л я х, з а п и с ы в а е т с я 15. П р о ц е д у р а Dispose(х) освобождает память, занятую динамической переменной. П р и этом значение у к а з а т е л я х становится неопределенным.
Линейные списки. Списком н а з ы в а е т с я с т р у к т у р а д а н н ы х , к а ж д ы й э л е м е н т к о т о р о й посредством у к а з а т е л я с в я з ы в а е т с я со с л е д у ю щ и м э л е м е н т о м . И з о п р е д е л е н и я следует, ч т о к а ж д ы й э л е м е н т с п и с к а с о д е р ж и т к а к м и н и м у м одно п о л е д а н н ы х (назовем его data и д л я п р о с т о т ы с ч и т а е м его т и п а Integer), оно м о ж е т и м е т ь с л о ж н у ю с т р у к т у р у , и п о л е с с ы л к и на с л е д у ю щ и й э л е м е н т (назовем его next). П о л е с с ы л к и последнего э л е м е н т а
Динамические структуры дачных 336 с п и с к а имеет з н а ч е н и е Nil. У к а з а т е л ь н а начало с п и с к а (перв ы й элемент) я в л я е т с я з н а ч е н и е м отдельной переменной. Пример с п и с к а , с о д е р ж а щ е г о в поле д а н н ы х целые ч и с л а 3, 5, 1, 9, приведен н а р и с у н к е . Rrst
Описание элемента списка, используемого в течение данного з а н я т и я , имеет вид: Type
pt="elem;{"Указатель на элемент списка.*) elem=Record data:Integer;{*Поле данных. *) next:pt;{*Указатель на следующее элемент списка. *} End;
Var f i r s t:pt;
{"Указатель
на первый
элемент
списка.
*}
Основные операции с элементами списка: • просмотр элементов списка; • вставка элемента в список; • удаление элемента из списка. Просмотр элементов списка, если он создан, очевидная процедура. Procedure Print (z:pt) ; Begin While zONil Do Begin Write (z*. data, ' ' ) ; z:=z*.next; End; End; Ее р е к у р с и в н а я р е а л и з а ц и я : Procedure Print (z:pt) ; Begin I f zONil Then Begin Write Print (z*. next) ; End; End;
(z*. data,
'
');
Часть четвертая
338
И з м е н и т е ее т а к , ч т о б ы э л е м е н т ы с п и с к а в ы в о д и л и с ь , н а ч и ная с последнего. В с т а в к а э л е м е н т а в с п и с о к в о з м о ж н а л о г и ч е с к и в его н а ч а ло, к о н е ц и с р е д и н у . Р а з б е р е м э т и с л у ч а и . В с т а в к а в н а ч а л о списка имеет вид: Rrsl
New_first
Procedure Ins_begm (Var f i r s t : p t ; Var new_first: pt; Begin New (new_first) ; ( *1*) new_firstЛ.next:=first;{*2*} new_first".data:=el; first :=new_first;(*3*) End;
el:Integer);
« Ж и р н ы м и » о т р е з к а м и н а р и с у н к е в ы д е л е н ы д е й с т в и я , выполняемые в процедуре. С помощью цифр на рисунке и в тексте процедуры показаны действия соответствующих операторов. В с т а в к а в к о н е ц с п и с к а о с у щ е с т в л я е т с я с п о м о щ ь ю следующ е й п р о ц е д у р ы . Вопрос о т о м , к а к и м о б р а з о м з н а ч е н и е м у к а з а т е л я l a s t стал адрес последнего э л е м е н т а с п и с к а , п о к а о с т а в и м открытым. First
Динамические структуры дачных 338 Procedure Ins_end(Var Begin New (last" . next) ; last: =lasf . next; last".data:=el; last".next:=Nil; End;
last:pt;
el:Integer);
(*l*j { *2*}
В с т а в к у в с р е д и н у с п и с к а п р о и л л ю с т р и р у е м о ч е р е д н ы м рисунком. Рх
Очевидно, что д л я вставки элемента в список необходимо знать, к а к м и н и м у м , адрес предшествующего элемента списка, т. е. элемента, после которого осуществляется вставка. И м ы считаем, что этот элемент не последний. П р и вставке разрывается сущ е с т в у ю щ а я связь (отмечена «крестиком») и появляются две новые связи, выделенные на рисунке «жирными» отрезками. П у с т ь м ы создаем у п о р я д о ч е н н ы й по неубыванию список элементов ( и н ф о р м а ц и о н н а я часть любого элемента списка меньше и н ф о р м а ц и о н н ы х частей следующих за ним элементов списка и л и р а в н а им). Д л я того, чтобы н а й т и место д л я вставки очередного элемента (значение у к а з а т е л я рх на рисунке), следует прос м а т р и в а т ь э л е м е н т ы списка до тех пор, пока вставляемый элемент больше и н ф о р м а ц и о н н о й части текущего элемента.
Г
~| tновое
340
Часть четвертая Procedure Ins_med(Var Var t,new_m:pt; Begin New (new_m) ; t:=first; While el>t".next".data new_m".next:=t".next; new_m".data:=e; tл.next:=new_m; End;
first:pt;
Do
el:Integer);
t:=t".next;
И н а к о н е ц , о б щ а я л о г и к а в с т а в к и э л е м е н т а в упорядоченн ы й с п и с о к . С о з д а в а е м ы й с п и с о к о т л и ч а е т с я от д е к л а р и р о в а н ного в н а ч а л е тем, ч т о он о п и с ы в а е т с я д в у м я у к а з а т е л я м и : на первый и последний элементы списка. Procedure Ins(Var first,last:pt; Begin I f el<first". data Then Ins__begin Else I f el>last".data Then Else Ins_med(first,el); End;
el:Integer); ( f i r s t , el) Ins_end(last,el)
Ввод и с х о д н ы х д а н н ы х п р и с о з д а н и и упорядоченного с п и с к а о с у щ е с т в л я е т с я с к л а в и а т у р ы . Н а п о м н и м , что п р и з н а к к о н ц а ф а й л а ( E o f = True) в этом случае в ы р а б а т ы в а е т с я п р и н а ж а т и и к л а в и ш Ctrl+Z. Все д е й с т в и я по о р г а н и з а ц и и ввода представлен ы в п р о ц е д у р е Solve. Procedure Solve(Var first,last:pt); Var el:Integer; Begin Write('First element: '); Read (el) ; I f Not Eof Then Begin first:=Nil; Ins_begin ( f i r s t , el) ; last:=first; End Else Begin first:=Nil;last:=Nil;End; While Not Eof Do Begin Write ("Next: ' ) ; Read(el); I f Not Eof Then I n s ( f i r s t , l a s t , e l ) ; End; End;
Динамические структуры дачных
341
Проведите н е с к о л ь к о экспериментов и п р о а н а л и з и р у й т е результаты: • и с к л ю ч и т е л о г и к у отдельного ввода первого элемента спис к а и з Solve-, • и с к л ю ч и т е а н а л и з п р и з н а к а к о н ц а файла и з тела ц и к л а While-, • и с к л ю ч и т е переменную Last, список д о л ж е н иметь только один у к а з а т е л ь на начало. Д е й с т в и я п р и удалении элемента из списка р а з л и ч н ы в зависимости от места удаляемого элемента: первый он и л и нет. Н а р и с у н к е п о к а з а н о удаление элемента и з средины списка. Д л я с о х р а н е н и я с т р у к т у р ы списка необходимо помнить адрес элемента с п и с к а , предшествующего удаляемому э л е м е н т у ^ * ) , а т а к ж е з а п о м и н а т ь адрес удаляемого элемента д л я корректного использования процедуры Dispose. dx
х
эПриведем процедуру удаления всех элементов списка, инф о р м а ц и о н н а я часть которых равна заданному числу (el). Procedure Del_el (Var first:pt; el: Integer) ; Var t,x,dx:pt; Begin t:=first;(*Переменная цикла. *} While toNil Do (*Пока список не просмотрен.*) I f t*. data=el Then(*EcTb совпадение. *) I f t=first Then Begin(*Удаляем первый элемент списка. *} х := first;{* Запоминаем, ибо «кучу» засорять не следует. *} First:=first*.next;("Изменяем значение указателя на первый элемент списка. *) Dispose (х) ; ( *Освобождаем место в «куче». *}
Часть четвертая
342
t:=first;{"Переменная цикла изменила свое значение. *} End Else Begin х:=t;{"Запоминаем адрес удаляемого элемента. *) t:=t".next; dx".next:=t; f"Удаление элемента не должно нарушать структуру списка, ключевой оператор процедуры. *} Dispose (х); End Else Begin dx:=t;t:=t".next;End;f"Переход к следующему элементу списка. Адрес текущего запоминается в переменной dx. "} End; О т л а д к а п р о г р а м м , и с п о л ь з у ю щ и х у к а з а т е л и , достаточно непростое з а н я т и е , особенно п р и п е р в о м з н а к о м с т в е . Р е к о м е н д у е т с я с о з д а в а т ь н е б о л ь ш о й с п и с о к , н а п р и м е р и з ч е т ы р е х элементов 1, 2, 3, 4 , и в о к н е Watches о т с л е ж и в а т ь и з м е н е н и е всех с в я з е й . П р и о т л а д к е п р о ц е д у р ы Del_el в п о ш а г о в о м р е ж и м е о к н о Watches м о ж е т и м е т ь в н а ч а л ь н ы й м о м е н т в р е м е н и следую щ и й вид. Watches T.data t'.nexT.data t".next".next" data t" next" next".next" data first".data firstt" next".data firstt" next" next" data first".next".next" next" data
1 2 3 4 1 2 3 4
Экспериментальный раздел работы 1. « С ч и т а л о ч к а » . N р е б я т р а с п о л о ж е н ы по к р у г у . Н а ч а в отсчет от п е р в о г о , у д а л я ю т к а ж д о г о k - г о , с м ы к а я п р и э т о м к р у г . Определить порядок удаления ребят из круга. Д л я х р а н е н и я д а н н ы х об у ч а с т н и к а х и г р ы и с п о л ь з у е м кольц е в о й с п и с о к ( з н а ч е н и е м п о л я N e x t последнего э л е м е н т а я в л я е т с я адрес первого э л е м е н т а с п и с к а ) .
Динамические структуры дачных
343
Program County Const N=10; Type pt="elem; elem=Record
Var
data:Integer; next:pt; end; first:pt;
Procedure Print(u:pt);("Вывод Var t:pt; Begin t:=u; Repeat Write(t".data,' Until t=и;{*До значение адреса WriteLn; End;
тех на
элементов
списка.*}
');t:=tл.next; пор, пока указатель не первый элемент списка.
Procedure I m t (Var f i r s t :pt) формирование списка.*} Var t,last:pt;i:Integer; Begin
;{
New(t);t*,data:=N;t".next:=Nil;first:=t;last:=t; For i:=N-l DownTo 1 Do Begin New ( t ) ; tл.next:=first;t".data:=i; first:=t; last'.next:=t; End; End; Procedure Game(u:pt; k:Integer); Var t:pt; i .-Integer; Begin t : =u ; Repeat For i:=l To k-1 Do Begin ( *Пропускаем
получит *)
*Первоначальное
k-1
элемент.
*}
Часть четвертая
344
t:=u;и:=и . next; End; WriteLn(и".data,' ');f"Удаляемый элемент.*} t".next:=u".next; Dispose (u); u:=t".next; Print(u);{*To, что осталось в списке.*} Until u=u".next;{"Пока не останется один элемент в списке. *} End; Основная
программа.
Begin first:=Nil; Init ( f i r s t ) ; P r i n t ( f i r s t ) ; Game(first,2);{"Удаляем каждого End.
второго
ребенка.
*}
И с с л е д у й т е з а д а ч у д л я р а з л и ч н ы х з н а ч е н и й N. С о с т а в ь т е т а б л и ц у о с т а в ш и х с я р е б я т (t — н о м е р о с т а в ш е г о с я р е б е н к а ) д л я з н а ч е н и й N от 1 до 6 4 . 12 13 14 15 16 17
Экспериментальным путем установите закономерность: • t(l)=l п р и N=1, • t(2*N)=2*t(N)-l п р и N>1, • t(2*N+l)=2*t(N)-l п р и N>1. m т Е с л и i V = 2 + 9 , где 2 — н а и б о л ь ш а я с т е п е н ь 2, не п р е в о с х о д я щ а я N, a q — р а з н о с т ь N-2m, то н о м е р о с т а в ш е г о с я р е б е н к а в ы ч и с л я е т с я п о ф о р м у л е : t(2m+q)=2*q+l п р и т>0 и 0<q<2m. Экспериментально проверьте правильность данной формулы и н а п и ш и т е версию программы, в ы ч и с л я ю щ е й номер оставшегос я р е б е н к а без и с п о л ь з о в а н и я с с ы л о ч н о г о т и п а д а н н ы х . Сравните результаты. 2. П р о м о д е л и р у е м р а б о т у со с п и с к о м , и с п о л ь з у я м а с с и в ы и не и с п о л ь з у я у к а з а т е л е й . В массиве А х р а н и м з н а ч е н и я элементов в п о р я д к е и х п о с т у п л е н и я на обработку, в массиве Next — с с ы л к у н а с л е д у ю щ и й э л е м е н т . С о з д а д и м у п о р я д о ч е н н ы й по в о з р а с т а н и ю список элементов. В п е р е м е н н о й head х р а н и м адрес первого э л е м е н т а с п и с к а . Г л о б а л ь н ы е п е р е м е н н ы е н а ш е й программы:
Динамические структуры дачных 344 Const N= ; N1=7777;{ *N1 - несуществующая следующий элемент. *) Type MyArray=Array[1..N] Of Integer; Var A,Next:MyArray; ind,head:Integer;
ссылка
на
L
П у с т ь на обработку п о с т у п и л и числа 5, 7, 3, 4. Состояние п е р е м е н н ы х о т р а ж е н о на р и с у н к е . После обработки чисел 1 и 9 з н а ч е н и я элементов массивов А и Next и з м е н и т с я на следующее. В массив А э л е м е н т ы з а п и с ы в а ю т с я в п о р я д к е их поступл е н и я на обработку. Д л я т а к о й записи необходимо знать адрес очередной свободной я ч е й к и (значение переменной ind). Нескол ь к о с л о ж н е е с массивом Next. В переменной head у к а з ы в а е т с я место записи наименьшего элемента массива А, а в Next [head] — адрес с л е д у ю щ е г о по возрастанию элемента массива А.
Д л я в с т а в к и очередного элемента необходимо, кроме записи его в т е к у щ у ю п о з и ц и ю Л, и з м е н и т ь с с ы л к и в массиве Next, в Next[ind] з а п и с ы в а е т с я адрес следующего по возрастанию элем е н т а массива Л, a ind я в л я е т с я значением элемента Next, соответствующим предыдущему элементу. Итак, процедура вставк и э л е м е н т а в список имеет вид: П—452
346
Часть четвертая Procedure Insert (Var head:Integer;x:Integer); Var 1,3:Integer; Begin Inc (md) ;A[md] :=x;Next [md] : =0/ ( *Записываем в А по текущему значению переменной md. *} 1:=head;j:=head;{*B j храним адрес предыдущего элемента списка.*} While (1<>0) And (x>A[i]) Do Begin j:=i;i:=Next [1]; End; (*Обычный просмотр элементов списка.*} Next[md]:=1;(*Номер следующего элемента списка является значением переменной 1. *) I f i=head Then head:=md Else Next[3]:=ind; {*Если элемент вставляется в начало списка, то изменяется значение переменной t, иначе корректируется поле ссылки предыдущего элемента. *} End;
Н а п и с а н и е п р о ц е д у р ы у д а л е н и я э л е м е н т а и з с п и с к а не выз ы в а е т особых с л о ж н о с т е й . Н е о б х о д и м о н а й т и у д а л я е м ы й элемент и изменить значение ссылки у предыдущего элемента — Next. В п р о ц е д у р е н а место у д а л я е м о г о э л е м е н т а в м а с с и в е А з а п и с ы в а е т с я 0, а в м а с с и в е Next з н а ч е н и е 7 7 7 7 . Procedure Delete(Var head:Integer;x:Integer); Var 1,3:Integer; Begin 1:=head;3:=head; While (10O) And (xOA [1] ) Do Begin { *Поиск удаляемого элемента.*} j : =1; i : =Next [1 ] ; End; I f iohead Then Begin Next[j]:=Next[1];{*Изменение значения ссылки на следующий элемент списка. *) A[i]:=0; Next[1]:=N1;{*Необязательные операторы. *} End Else Begin j:=head; head:=Next[head]; I*Необязательные операторы. *) Nex t [ j ] : =N1; I * Удаляем первый элемент списка.*} End; End;
Динамические структуры дачных
347
Разработайте полный текст программы для работы со списком. Измените программу так, чтобы запись очередного элемента осуществлялась на первое свободное место массива А, т. е. в Вашей основной программе вставка и удаление элементов д о л ж н ы быть не последовательными процессами, а взаимно чередующимися. Задания для самостоятельной работы 1. Разработать: • функцию, вычисляющую среднее арифметическое элементов непустого списка; • рекурсивную функцию проверки наличия в списке заданного элемента; • процедуру перестановки первого и последнего элементов непустого списка; • процедуру вставки нового элемента перед (после) каждым вхождением заданного элемента; • функцию проверки совпадения списков L t и Ь 2 ; • функцию проверки вхождения списка L t в список Ь 2 ; • процедуру переноса в конец непустого списка L его первого элемента; • процедуру переноса в начало непустого списка его последнего элемента; • процедуру копирования в список L за каждым вхождением заданного элемента всех элементов списка ; • процедуру объединения двух упорядоченных по неубыванию списков L j и Ь 2 В ОДИН упорядоченный по неубыванию список путем построения нового списка L и изменением соответствующим образом ссылок в Lj и Ь 2 , • функцию проверки упорядоченности элементов списка; • функцию подсчета количества слов списка (поле Data имеет тип String), начинающихся и оканчивающихся одним и тем же символом. 2. Разработать процедуру удаления из списка L: • второго элемента, если такой есть; • всех элементов, равных х; • первого отрицательного элемента, если такой есть; • всех отрицательных элементов. 3. Разработать процедуру формирования списка L путем включения в него по одному разу элементов: • входящих хотя бы в один из списков Lj и Ь 2 ; • входящих одновременно в оба списка Ll и L2;
347 Часть четвертая
• в х о д я щ и х в список L j , но не входящих в список Ь 2 , • в х о д я щ и х в один из списков L, и Ь 2 , но в то ж е время не в х о д я щ и х в другой из них. 1 4. Многочлен Р(х )=апхп+ап_1хп +...+а1х+а0 с целыми коэффиц и е н т а м и м о ж н о представить в виде списка, причем если а=0, то соответствующий элемент не включается в список (на рисунке показано общее представление многочлена и пример s(x)=-5x6+3x2-x+7).
р
оч
шш
Описать тип данных, соответствующий такому представлению многочленов, разработать следующие функции и процедуры для работы с этими списками-многочленами: • логическую функцию Equality(p,q), проверяющую равенство многочленов р и д; • функцию Meaning(p.x), вычисляющую значение многочлена в целочисленной точке х; • процедуру Add(p,q,г) вычисления суммы многочленов q и г, результат — многочлен р. 5. Выполнить задания 1, 3 при реализации списков с помощью массивов типа А и Next так, к а к это сделано во втором упражнении экспериментального раздела занятия. 6. В задаче о «считалочке» удаляется к а ж д ы й второй ребенок. Написать программу определения номеров двух последних оставшихся ребят. 7. В задаче о «считалочке» Петя находится на месте i. Существует ли значение k (k-й удаляемый ребенок) такое, что Петя останется последним ребенком в круге. Написать программу поиска значений k. 8. Вставка в линейный список перед заданным элементом выполняется неэффективно, ибо есть ссылка только на следующий элемент списка. При использовании списка с двумя указателями (адресами связи) на следующий и предыдущий элементы этот недостаток устраняется. Напишите программу работы (вставки, удаления элементов) с таким типом списВ заключении занятия отметим ряд преимуществ и недостатков динамических структур. Динамическое размещение структур данных имеет свои преимущества и недостатки. В компетенции
Динамические структуры д а ч н ы х
349
программиста оценивать и х с точки зрения конкретной решаемой задачи. Преимущества • Разумное использование динамических структур данных приводит к сокращению объема памяти, необходимого д л я работы программы. • Динамические данные в противоположность статическим и автоматически размещаемым данным не требуют объявлений их к а к данных фиксированного размера. В большинстве систем программирования д л я «кучи» выделяется достаточно большой объем памяти. • Р я д алгоритмов более эффективен при реализации их с использованием динамических структур. Например, вставка элемента в массив на определенное место требует перем е щ е н и я части элементов массива. При вставке в середину списка достаточно нескольких операторов присваивания. Недостатки • Алгоритмы на динамических структурах обычно более с л о ж н ы , трудны д л я отладки по сравнению с аналогичными алгоритмами на статических данных. • Использование динамических структур требует затрат на п а м я т ь д л я ссылок. В некоторых задачах объем памяти, отводимой д л я ссылок, превосходит объем памяти, выделяемой непосредственно для данных. • Существуют алгоритмы, реализация которых более эффективна на обычных данных. Например, в ряде задач индекс элемента в массиве можно просто вычислять, в то время к а к использование списковых структур потребует обхода списка.
350
Ч а с т ь четвертая
Занятие № 27. Стек План занятия • структура данных стек, основные сведения; • э к с п е р и м е н т а л ь н а я р а б о т а с п р о г р а м м а м и п р о в е р к и правильности расстановки скобок, вычисления в ы р а ж е н и я в п о с т ф и к с н о й ф о р м е , п р е о б р а з о в а н и я в ы р а ж е н и я и з инфиксной в постфиксную форму; • выполнение самостоятельной работы. Стек — у п о р я д о ч е н н ы й н а б о р э л е м е н т о в , в к о т о р о м добавление новых элементов и удаление с у щ е с т в у ю щ и х производится с одного конца, называемого вершиной стека. Простой пример: детская пирамидка. Процесс сборки и разборки пирамидки подобен процессу ф у н к ц и о н и р о в а н и я стека. В любой момент времени доступен л и ш ь о д и н э л е м е н т с т е к а — в е р х н и й . И з о п р е д е л е н и я следует, ч т о и з в л е к а т ь э л е м е н т ы и з с т е к а м о ж н о т о л ь к о в п о р я д к е , обр а т н о м п о р я д к у и х д о б а в л е н и я в стек ( « п е р в ы й п р и ш е л , последний ушел»). О с н о в н ы е о п е р а ц и и со с т е к о м : з а п и с ь э л е м е н т а в с т е к , извл е ч е н и е э л е м е н т а и з с т е к а , п р о в е р к а н а л и ч и я э л е м е н т о в в стеке. Если использовать список д л я представления д а н н ы х стека, то его м о ж н о о п р е д е л и т ь к а к с п и с о к , в к о т о р о м д о б а в л е н и е новых элементов и извлечение имеющихся происходит с начала (или конца) списка. Значением указателя, представляющего с т е к , я в л я е т с я с с ы л к а н а в е р ш и н у с т е к а . К а ж д ы й э л е м е н т стека содержит поле ссылки на следующий элемент. Т а к и м образом, описать стек (элементом данных являются ц е л ы е ч и с л а ) м о ж н о с л е д у ю щ и м образом: Type Var
st:
pt="elem; elem=Record pt;
data:
Integer;next:
pt;End;
Е с л и с т е к п у с т , то з н а ч е н и е у к а з а т е л я st р а в н о Nil. Д л я р а б о т ы со с п и с к а м и будем и с п о л ь з о в а т ь п р о ц е д у р ы , раз о б р а н н ы е на п р е д ы д у щ е м з а н я т и и . Н а п о м н и м и х . Запись в стек. П р о ц е д у р а з а п и с и э л е м е н т а в стек д о л ж н а сод е р ж а т ь д в а п а р а м е т р а : п е р в ы й о п р е д е л я е т у к а з а т е л ь на начало с т е к а , второй — з а п и с ы в а е м о е в стек з н а ч е н и е . З а п и с ь в стек п р о и з в о д и т с я а н а л о г и ч н о в с т а в к е нового элемента в начало списка:
Динамические структуры дачных 350 Procedure MriteStack(Var Var x: pt; Begin New (x) ; x".data:=dig;
u:pt;
dig:Integer);
x".next:=u
End; Извлечение элемента из стека. В результате в ы п о л н е н и я этой о п е р а ц и и некоторой переменной i д о л ж н о быть присвоено з н а ч е н и е первого элемента стека и изменено значение указател я на н а ч а л о с п и с к а . Procedure ReadStack(Var u:pt; Var dig:Integer); Var x: pt; Begin dig:=u".Data; x:=u; u:=u".Next; Dispose (x); End; Н е д о с т а т к о м о п и с а н н о й процедуры я в л я е т с я предположение о т о м , что стек не пуст. Д л я его и с п р а в л е н и я следует разработать л о г и ч е с к у ю ф у н к ц и ю проверки пустоты обрабатываемого стека и перед и с п о л ь з о в а н и е м процедуры ReadStack проверять н а л и ч и е элементов в стеке. Function Free(u.-pt) .-Boolean; Begin I f u=Nil Then Free:=False Else End;
Free:=
True;
Экспериментальный раздел работы 1. Написать программу, проверяющую своевременность закрыт и я скобок т и п а «(, ), {, }, [, ]» в строке символов (строка состоит и з одних скобок этих типов). Д л я р е ш е н и я задачи определим стек, элементами которого являются символы: Type pt="el; el=Record data: Char; next: pt; End; В процессе р е ш е н и я а н а л и з и р у е м символы строки а: String. Е с л и встречена одна и з о т к р ы в а ю щ и х скобок, то она записывается в стек. П р и о б н а р у ж е н и и з а к р ы в а ю щ е й с я скобки, соответс т в у ю щ е й скобке, н а х о д я щ е й с я в в е р ш и н е стека, последняя у д а л я е т с я . П р и несоответствии скобок выдается сообщение об о ш и б к е , которое ф и к с и р у е т с я в логической переменной.
351 Часть четверт Осталось в ы я с н и т ь , к а к о п р е д е л и т ь , соответствует л и очередн а я з а к р ы в а ю щ а я с к о б к а скобке, н а х о д я щ е й с я в в е р ш и н е стека. О к а з ы в а е т с я , ч т о к о д ы с о о т в е т с т в у ю щ и х друг друту скобок отлич а ю т с я не более ч е м на 2: { } и м е ю т к о д ы 1 2 3 - 1 2 5 ; [ ] — 9 1 - 9 3 ; ( ) — 4 0 - 4 1 , п р и ч е м к о д о т к р ы в а ю щ е й с к о б к и м е н ь ш е . То есть в ы п о л н е н и е у с л о в и я If (Ord(a[i])-Ord(stack" .data))<=2 Then... говорит о соответствии о б р а б а т ы в а е м ы х скобок. Program Parenth; Type pt="el/ el=Record data:Char/next:pt/End/ s:String/ f -.Boolean / Procedure ReadStack(Var t:pt/Var x:Char)/(*3Ta следующие процедура и функция описаны ранее. Procedure WriteStack(Var t:pt/x:Char)/ Function Free (t:pt) .-Boolean/ Procedure Solve(a:String/Var pp:Boolean)/ Var head:pt/ i :Integer/ ch:Char/ Begin head:=N11/pp:=True/ i : =1 /
Var
While (i<=Length (a)) And pp Do I f a[i] In [ ' ( ' , ' [ ' , ' { ' ] Then (head, a [i]) Else Begin I f Free(head) Then Begin (head,ch)/ I f ABS (Ord(ch)-Ord(a[i]))>2 pp:=False/ End Else pp:=False/ End/ Inc (i) / End/ End/ Begin WriteLn('Введите строку.')/ ReadLn(s)/ I f s o " Then Begin Solve ( s , f ) /
Begin WriteStack
ReadStack Then
и *}
Динамические структуры дачных
353
I f f Then WriteLn('Строка Else WriteLn('В строке End Else WriteLn('Строка ReadLn; End.
записана содержится
правильно.') ошибка.');
пустая.');
Обработка с т р о к и (((()) с п о м о щ ь ю этой п р о г р а м м ы дает соо б щ е н и е о том, что с т р о к а п р а в и л ь н а я , а строки (()))) — неправ и л ь н а я . У с т р а н и т е это неравноправие. Если в строке содерж а т с я д р у г и е с и м в о л ы , к р о м е скобок, то независимо от способа р а с с т а н о в к и п о с л е д н и х в ы д а е т с я сообщение об ошибке, например д л я с т р о к и (а). И з м е н и т е программу так, чтобы символы, не с о в п а д а ю щ и е со с к о б к а м и , не в л и я л и на результат обработки. 2. Н а п и с а т ь программу вычисления значения выражения, представленного в обратной польской записи (в постфиксной форме). В ы р а ж е н и е состоит из цифр от 1 до 9 и знаков операций. О б ы ч н а я запись: (b+c) *d a + (b+c) *d
Обратная п о л ь с к а я запись: b с + d * a b с + d * +
П р о с м а т р и в а я строку, а н а л и з и р у е м очередной символ, если это: • ц и ф р а , то з а п и с ы в а е м ее в стек; • з н а к , то ч и т а е м два элемента и з стека, в ы п о л н я е м математ и ч е с к у ю о п е р а ц и ю , определяемую этим знаком, и занос и м р е з у л ь т а т в стек. П о с л е просмотра всей строки в стеке должен оставаться один элемент, он и я в л я е т с я р е ш е н и е м задачи. Необходимо в ы я с н и т ь , к а к записать условие: очередной сим вол строки — знак, и к а к перевести символ, обозначающий ц и ф р у , в саму ц и ф р у . Процедура Val(s.x.k) преобразует символ ь н о е представление ц и ф р ы s в соответствующее числовое знач е н и е . П р и этом значение k=0, если такое преобразование возм о ж н о , в противном случае k<>0. Program Polish; Тур pt="el; el=Record Var s:String; f:Boolean; rez:Real; Procedure ReadStack(Var
data:Real;next:pt;End;
t:pt;Var
x:Real);
Часть четвертая
354
Procedure Function
WriteStack(Var Free(t:pt):Boolean;
t:pt;x:Real);
Procedure Operation(ch:Char;a,b:Real; Begin Case ch Of '+':c:=a+b;
Var
c:Real);
:c:=b~a; '*':c:=a*b; '/':c:=b/a; End; End; Procedure Var head:
Solve(a:String;Var pt;
i,k: r,w:
pp:Boolean;Var
z:Real);
Integer; Real;
Begin head:=Nil; 1: =1 ;
pp:=True;
While (K=Length (a)) And pp Do Begin I f a[i]<>' ' Then Begin("Пропускаем пробелы. *} I f Not(a[i] In [ ' + ' , ' - ' , ' * ' , ' / ' ] ) Then Begin Val (a[i] ,r,k) ; I f k=0 Else End Else
Then pp:=False;
WriteStack(header)
Begin I f Free(head) Then ReadStack(head,r) Else pp:=False; I f Free(head) Then ReadStack(head, w) Else pp:=False; I f pp Then Begin Operation (a[i ] , r , w , r ) ; WriteStack(head,r) ; End; End;
End; Inc ( i ) ; End; ReadStack(head,z); End; Begin WriteLnС Введите
строку.');
Динамические структуры дачных
355
ReadLn (s) ; I f s<>" Then Begin Solve (s, f , r e z ) ; I f f Then WriteLn(rez:6:2) Else WriteLn('Выражение не может быть вычислено.') End Else WriteLn ('Строка пустая.'); End.
;
Обработка 3 . 1 6 * даст ответ 6. Измените программу так, чтобы обрабатывались и в ы р а ж е н и я , содержащие вещественн ы е числа. П р и вводе в ы р а ж е н и я 3 4 6 5 + получается результат 11, что явно не соответствует действительности. Выражение ошибочно. Устраните эту неточность. Пусть операция возведения в степень имеет обозначение «"». И з м е н и т е программу так, чтобы осуществлялась обработка и этой операции. 3. Рассмотрим в ы р а ж е н и е А + В . Оно записано в обычной, или и н ф и к с н о й , форме. Запись А В + называют постфиксной, а запись + А В — префиксной. Префиксы *пре», «пост», «ин» говорят об относительной позиции оператора по отношению к обоим операндам. Преобразование ( и вычисление) выражен и я А+В*С в А В С * + (или А В + С *) требует знания того, к а к а я из двух операций выполняется первой. Д л я операций « + », «—», «*», «/» и «"» приоритет считаем традиционным (от высшего к низшему): возведение в степень, умножение/деление, с л о ж е н и е / в ы ч и т а н и е . Н а ш е й задачей является написание программы преобразования в ы р а ж е н и я из инфиксной в постфиксную форму. В предыдущем задании мы брали постфиксную форму (польская запись) к а к нечто заданное и тол ь к о в ы ч и с л я л и выражение. Но прежде чем перейти к разработке, приведем несколько примеров записи одного и того ж е в ы р а ж е н и я в различных формах. Инфиксное представление А+В-С (А+ВПС-D) A'B'C-D+E/F/(C+H) A-B/(C'D~E)
Постфиксное представление АВ+СAB+CD-' AB"C'D-EF/GH+/+ ABCDE-'/-
Префиксное представление -+АВС '+AB-CD +-'ABCD/EF+GH -A/B'C^DE
355 Часть четвертая Процесс преобразования в ы р а ж е н и я в постфиксную форму о т р а ж е н в т а б л и ц е д л я в ы р а ж е н и я (A+B)*(C+D). В первом с т о л б ц е п р и в о д и т с я ч и т а е м ы й с и м в о л и з и с х о д н о й с т р о к и , во в т о р о м с т о л б ц е — р е з у л ь т и р у ю щ а я с т р о к а и в т р е т ь е м столбце — с о с т о я н и е с т е к а . ( A + В ) ( С + D )
A A AB AB+ AB+ AB+ AB+C AB+C AB+CD AB+CD+ AB+CD+*
(
< (+
*( +
Итак, идентификаторы переменных сразу переписываем в результирующую строку, знаки операций и о т к р ы в а ю щ у ю круглую скобку записываем в стек. Если встречается з а к р ы в а ю щ а я с к о б к а , то и з с т е к а с и м в о л ы (до о т к р ы в а ю щ е й с к о б к и ) операц и й п е р е п и с ы в а ю т с я в р е з у л ь т а т . П о с л е того, к а к в с я и с х о д н а я с т р о к а п р о с м о т р е н а , о с т а е т с я д о п и с а т ь (если о н и есть) и з с т е к а знаки операции в строку, содержащую выражение в постфиксной форме. Program Change_expression; Type pt="el; el=Record data:Char;next:pt;End; Var s,rez:String; Procedure ReadStack(Var t:pt;Var x:Char);(*Эти процедуры и функция рассмотрены ранее. Изменения не существенны. *) Procedure WriteStack(Var t:pt;x:Char); Function Free(t:pt):Boolean; Procedure Solve(a:String;Var z:String); Var head:pt; i:Integer; w: ChartBegin head:=Nil;z:='';
Then Else Then
z:=z+a[1] I f a[i] in [' + ' , *'r ' / ' , ' (>j WriteStack(head,a[i]) Else I f a[1]=')' Then Begin{"Считываем из стека до символа « (». *} ReadStack (head, w) ,While wo' (' Do Begin z:=z+w; ReadStack (head, w) ; End; Ends-
End; Inc (1) ; End; While Free(head) Do Begin{"Дополняем строку символами операций, запомненных в стеке. *) ReadStack(head,w); z:=z+w; EndsEnd; Begin WriteLn('Введите выражение в обычной (инфиксной) форме.'); ReadLn (s) ; Solve (s,rez); Wri teLn (rez) ; End. В п р о г р а м м е не предусмотрена обработка ошибок в исходной с т р о к е . У с т р а н и т е этот недостаток, чем больше в о з м о ж н ы х о ш и б о к будет а н а л и з и р о в а т ь с я , тем л у ч ш е . Определите, при к а к и х и с х о д н ы х д а н н ы х п р и в е д е н н а я п р о г р а м м а выдает неправ и л ь н ы й р е з у л ь т а т . Сохранение «костяка» п р о г р а м м ы (миним а л ь н ы е и з м е н е н и я ) говорит о качестве его н а п и с а н и я . И з м е н и т е п р о г р а м м у т а к , чтобы обрабатывались не только о д н о б у к в е н н ы е и м е н а п е р е м е н н ы х , но и вещественные числа. З н а ч е н и я п е р е м е н н ы х могут быть з а д а н ы предварительно в отд е л ь н о м массиве.
Часть четвертая
358
З а д а н и я для с а м о с т о я т е л ь н о й работы 1. Д л я х р а н е н и я э л е м е н т о в с т е к а и с п о л ь з у е т с я о б ы ч н ы й одном е р н ы й массиЕ и з N э л е м е н т о в . Р е а л и з у й т е о п е р а ц и и работ ы со с т е к о м ( R e a d S t a c k , WriteStack, Free) в этом с л у ч а е . Зам е т и м , ч т о п е р е д и с п о л ь з о в а н и е м WriteStack необходимо проверять переполнение стека. 2. Д а н а с т р о к а в и д а : s*w (s, w — с т р о к и и з с и м в о л о в , не содерж а щ и х с и м в о л а *). Ч т е н и е р а з р е ш е н о по о д н о м у с и м в о л у . П р о в е р и т ь , я в л я е т с я л и с т р о к а w о б р а т н о й с т р о к е s. Н а п р и м е р , д л я с л у ч а я s=ABCDEF, w=FEDCBA ответ п о л о ж и т е л ь ный. 3. Преобразуйте к а ж д о е и з п р и в е д е н н ы х н и ж е в ы р а ж е н и й в префиксную и постфиксную формы: • А+В-С • (А+В )*( C-D )"E*F • (A+B)*(C~(D-E)+F)-G • A+(((B-C)*(D~E)+F)/G)"(H-J) • ((A-(B+C))*D)-(E+F) 4. Р а з р а б о т а т ь п р о г р а м м у преобразования в ы р а ж е н и я из и н ф и к сного в п р е ф и к с н о е п р е д с т а в л е н и е . 5. Р а з р а б о т а й т е п р о г р а м м у в ы ч и с л е н и я в ы р а ж е н и я в п р е ф и к сной ф о р м е . 6. Р а з р а б о т а т ь п р о г р а м м у п р е о б р а з о в а н и я п о с т ф и к с н о й ф о р м ы в и н ф и к с н у ю . П р и этом р е з у л ь т и р у ю щ е е в ы р а ж е н и е д о л ж н о быть записано с необходимыми скобками. Например, АВ+С* должно быть преобразовано в (А+В)*С. 7. А б с т р а к т н а я в ы ч и с л и т е л ь н а я м а ш и н а и м е е т о д и н р е г и с т р и шесть инструкций: LD А ST А AD А SB А ML А DV А
Помещает операнд А в регистр, Помещает содержимое регистра в переменную с именем А. Прибавляет значение А к регистру Результат остается в регистре. Вычитает значение А из регистра. Результат остается в регистре. Умножает содержимое регистра на значение переменной с именем А Результат остается в регистре. Делит содержимое регистра на значение А. Результат остается в регистре
Д а н о в ы р а ж е н и е в п о с т ф и к с н о й ф о р м е , с о с т о я щ е е и з одноб у к в е н н ы х о п е р а н д о в и о п е р а ц и й « + », « - » , «*», « / » . Н а п и с а т ь п р о г р а м м у в ы в о д а и н с т р у к ц и й в ы ч и с л и т е л ь н о й м а ш и н ы , необ-
359
Д и н а м и ч е с к и е структуры д а ч н ы х
х о д и м ы х д л я вычисления в ы р а ж е н и я . Результат вычисления д о л ж е н оставаться в регистре, разрешается использовать обозначения Тп д л я временных переменных. Пример. Д л я в ы р а ж е н и я ABC*+DE-/ перечень инструкций имеет вид: LD
О
SB Е ST LP
Т1 А
ST
Т2
LP
Т1
DV
72
359 Ч а с т ь четвертая
Занятие № 28. Очередь План занятия • экспериментальная работа с программами реализации очереди и дека через массив и с помощью двунаправленного списка; • выполнение самостоятельной работы. Очередь — это упорядоченный набор элементов, в котором извлечение элементов происходит с одного его конца, а добавление новых элементов — с другого. Операции с очередью: • запись элемента в очередь, если это возможно, т. е. нет переполнения структуры д а н н ы х , выбранной д л я хранения элементов очереди; • чтение элемента из очереди, естественно, если очередь не пуста. В очереди, в силу её определения, доступны две позиции: ее начало (не с точки зрения обслуживания, а с точки зрения записи), куда заносятся новые элементы, и ее конец, откуда извл е к а ю т с я элементы. Поэтому д л я работы с очередью (независимо от выбранной структуры данных для хранения её элементов) необходимо описать две переменные, назовем их head и tail. Экспериментальный раздел работы 1. Рассмотрим использование одномерного массива к а к структуру данных д л я х р а н е н и я элементов очереди. А 1 '
tail head
На рисунке показан «срез» очереди на какой-то момент времени. В очереди остался один не обслуженный элемент — 7. После следующего чтения очередь окажется пустой. Оговорим значения head и tail. В начальный момент работы они равны
Динамические структуры дачных
361
н у л ю . П о с л е з а п и с и э л е м е н т а з н а ч е н и е h e a d у к а з ы в а е т на запис а н н ы й э л е м е н т , т. е. перед з а п и с ь ю его необходимо увеличивать на 1. Значением tail я в л я е т с я адрес прочитанного элемента, т. е. перед следующим чтением его т а к ж е необходимо увеличить на 1. З а п и с ь в очередь и чтение и з нее о с у щ е с т в л я е т с я по принц и п у « к о л ь ц а » . После з а п и с и в я ч е й к у с номером N выполняется з а п и с ь в п е р в у ю я ч е й к у , если, конечно, она свободна. После ч т е н и я и з я ч е й к и с номером N осуществляется чтение и з первой я ч е й к и , если в нее в ы п о л н я л а с ь запись. Т а к и м образом, в л о г и к е р а б о т ы с очередью необходимо предусмотреть переход через г р а н и ц у — з н а ч е н и е N .
head tail
Второй момент, т р е б у ю щ и й р а з ъ я с н е н и я , это п о н я т и я «очередь пуста», «очередь переполнена». Очень с л о ж н ы й момент, ибо к а к з н а ч е н и е tail может «догонять» значение head — в этом случае «очередь пуста», так и значение head «догоняет» tail — очередь переполнена. К а к р а з л и ч и т ь эти случаи? Отлож и м а н а л и з этого момента л о г и к и и сделаем «костяк» программы. {$R') Program Turn_Array; Const N=10; Type MyArray=Array[l..N] Of Integer; Va rA:MyArray; head,tail,w:Integer; Function Over_Turn (up, down: Integer) .-Boolean; /*Первая версия функции, вряд ли работающая правильно. То же самое относится и к следующей функции. *) Begin
Часть четвертая
362
I f up =down Then Turn:=Fal setEnd; Function Begin
Over_Turn:=True
Else
Empty_Turn(up,down:Integer)
I f up =down Turn:=False; End;
Then
I f up=N+l A[up]:=x; End;
Then
Procedure Extrac_Turn(Var Integer);{"Извлекаем Begin Inc (down); I f down=N+l Then x:=A[down];A[down]:=0; End;
:Boolean;
Empty_Turn:=True
Procedure Add_Turn(Var ("Добавляем элемент Begin Inc (up);
Over_
Else
Empty_
up:Integer;x:Integer); в
очередь.*}
up:=l;
элемент
down:Integer;Var из очереди."}
x:
down:=l;
Procedure Print_Turn;["Рабочий (отладочный) вариант процедуры. Требуется изменить ее так, чтобы просмотр очереди осуществлялся по значениям переменных up, down. Пока значение down меньше или равно up — выводить элемент и изменять значение down. "} Var i:Integer; Begin For l : =1 To N Do Wri te(A[i] WriteLn;
,'
') ;
WriteLn(head, ' ' , t a i l ) ; End; Begin ("Основная программа. *) Randomize; FillChar(A,SizeOf (A),0); head:=0;tail:=0; {"Начальные значения записи и чтения."} WriteLn('Занесение в очередь первого Add_Turn (head, 100) ; While Not(Empty_Turn (head, t a i l ) ) And
указателей элемента.'); Not
Динамические структуры дачных 362 (Over_Turn(head,tail) ) Do Begin{*Пока очередь не пуста (а вдруг окажется операция чтения из очереди) и нет переполнения очереди, выполняем действия.*) I f Random (2)>0 Then Extrac_Turn(tail,w) {"Извлекаем из очереди. ") Else Add^Turn(head,Random(200)+1) {*Записываем в очередь. *}; Pnnt_Turn ; ( "Наблюдаем за изменением состояния очереди. *) End; I f Not(Empty_Turn (head, tail)) Then WriteLn С Очередь переполнена.') ("Если очередь не пуста, то, з н а ч и т , она переполнена.*) Else WriteLn('Очередь пуста.'); WriteLn(head,' ',tail); End. З а п у с к п р о г р а м м ы п о з в о л я е т провести п е р в и ч н ы й а н а л и з ее работы. Л ю б ы е в а р и а н т ы проверки приводят к сообщению «очередь п у с т а » . Д а ж е в том случае, когда «очередь переполнена». П о п ы т к и и з м е н и т ь н а ч а л ь н ы е з н а ч е н и я tail и л и head, а т а к ж е способов з а п и с и и с ч и т ы в а н и я не и з м е н я ю т ситуацию. После р я д а б е з у с п е ш н ы х п о п ы т о к достигается п о н и м а н и е того, что необходима д о п о л н и т е л ь н а я п е р е м е н н а я логического типа д л я а н а л и з а с и т у а ц и и : и л и tail «догоняет» head, и л и head «догоняет» tail. Д о г о в о р и м с я о том, ч т о в первом случае р (логическая п е р е м е н н а я ) равна True, а во втором — False. В н а ч а л ь н ы й мом е н т её з н а ч е н и е равно True и п р и переходе к а к того, т а к и другого у к а з а т е л е й (heap и tail) через значение N она и з м е н я е т свое з н а ч е н и е н а п р о т и в о п о л о ж н о е . М о д и ф и ц и р о в а н н ы й в а р и а н т а н а л и з а и и з м е н е н и я состоян и я очереди и м е е т вид: Function Begin I f (up =down) And Not p Then Else Over_Turn:=False; End;
Function Begin I f (up =down) And p Then Else Empty_Turn:=False; End;
Over_T
Over_Turn:=True
Empty_Turn:=True
Empty_Tu
364
Часть четвертая Procedure Add_Turn (Var up:Integer;x:Integer); Beg±n Inc (up) ; I f up=N+l Then Begin up:=l; p:=Not p;End; A[up]:=x ; End; Procedure Extrac_Turn(Var down:Integer;Var x: Integer); Begin Inc (down} ; I f down=N+1 Then Begin down:=1;p:=Not p;End; x:=A[down];A[down]:=0; End;
Естественно, что p необходимо ввести в описание переменн ы х и п р и с в о и т ь н а ч а л ь н о е з н а ч е н и е р:=Тгие, н а п р и м е р , в той с т р о к е , г д е head:=0 и tail:=0. Н а э т и х и з м е н е н и я х н а ш и б е д ы н е к о н ч а ю т с я . Е с л и в очер е д ь о с у щ е с т в л я е т с я т о л ь к о з а п и с ь и н е б ы л о н и о д н о г о чтен и я , то з н а ч е н и е у к а з а т е л я tail о с т а е т с я р а в н ы м 0, а з н а ч е н и е у к а з а т е л я head « и д е т п о в т о р о м у к р у г у » и з а п и с ь в ы п о л н я е т с я н а з а н я т ы е м е с т а . В ф у н к ц и ю Over_Turn необходимо внести еще одно изменение: I f ((up Then Else
=down) And Not p) Over_Turn:=True Over_Turn:=False;
Or
((down=0)
And
(up=N))
В п р о ч е м , все п р о б л е м ы с а н а л и з о м у к а з а т е л е й head и tail реш а ю т с я и « д е т с к и м » о б р а з о м . П е р е д к а ж д ы м ч т е н и е м и з очеред и в ы п о л н я е т с я п р о в е р к а того, а н е р а в н ы л и все э л е м е н т ы очер е д и н у л ю . Е с л и « д а » , то о ч е р е д ь п у с т а , а е с л и « н е т » , то в ы п о л н я е т с я чтение. Точно так ж е решается и вопрос о занятости очереди. Однако, во-первых, н а р у ш а е т с я п р и н ц и п работы с о ч е р е д ь ю (а это г л а в н о е , о ч е р е д ь — « ч е р н ы й я щ и к » , в з а и м о д е й с т в о в а т ь с к о т о р о й д о п у с к а е т с я т о л ь к о ч е р е з з н а ч е н и я head и tail); в о - в т о р ы х , и м е е т м е с т о л и ш н и й п р о с м о т р э л е м е н т о в массива; в-третьих, предполагается, что из нулевых элементов о ч е р е д ь с о с т о я т ь не м о ж е т . Деком называется структура данных, в которой запись и удаление элементов р а з р е ш а е т с я с обоих концов. Р е а л и з а ц и я д е к а с и с п о л ь з о в а н и е м м а с с и в а т р е б у е т д о п о л н и т е л ь н о г о введен и я д в у х п р о ц е д у р : ч т е н и я «с г о л о в ы » и з а п и с и «с х в о с т а » .
Динамические структуры дачных 364
ISR+} Program Const
Dec_Array; N=10;
Type Var
MyArray=Array[1..N] Of Integer; A:MyArray; head,tail,w:Integer; p-.Boolean; Procedure Prmt_Dec; {"Эта и следующие процедуры функции рассмотрены ранее (изменения только в названиях). *} Procedure Add_Head(Var up:Integer;x:Integer); Procedure Extrac_Tail(Var down:Integer;Var x: Integer) ;
и
Function Over_Dec (up, down : Integer) -.Boolean; Function Empty_Dec (up,down:Integer):Boolean; {*Новые процедуры. *) Procedure Add_Tail(Var down:Integer;x:Integer); Begin A[down]:=x; Dec(down); I f down=0 Then Begin down:=N; p:=Not p;End; End; Procedure Extrac_Head(Var up:Integer;Var x: Integer); Begin x:=A[up];A[up]:=0; Dec(up); I f up=0 Then Begin up:=N;p:=Not p;End; End; Begin(*Может быть, потребуется несколько запусков программы, чтобы отследить все варианты изменения содержимого дека. *) Randomize; FillChar (A, SizeOf (А) , 0) ; head:=0;tail:=0;р:=True; WriteLn('Запись первого элемента в дек.'); Add_Head (head, 100) ; While Not (Empty_Dec (head, t a i l ) ) And Not (Over_Dec (head, t a i l ) ) Do Begin I f Random(2)>0 Then("Запись и чтение с «головы» дека.*) I f Random (3) >0 Then Add_Head (head,Random (50))
365 Часть четверта Else Extrac_Head (head, w) Else("Запись и чтение с «хвоста» дека. *) I f Random(2)>0 Then Begin I f tailOO Then Add_Tail ( t a i l , Random (100) +50) End Else ExtracJTail(tail,w); Print_Dec; ReadLn; End; I f Not (Empty_Dec(head,tail) ) Then WriteLn(' Дек переполнен.') Else WriteLn('Дек пуст.'); WriteLn(head,' ',tail); End. 2. П р и выборе д в у н а п р а в л е н н о г о с п и с к а д л я р е а л и з а ц и и очереди с н и м а е т с я проблема а н а л и з а п е р е п о л н е н и я очереди, а приз н а к о м «пустой очереди» я в л я е т с я з н а ч е н и е у к а з а т е л я на нач а л о с п и с к а (head), р а в н о е Nil. Д л я д а н н о й з а д а ч и м о ж н о и с п о л ь з о в а т ь и о д н о н а п р а в л е н н ы й с п и с о к , но в этом случае п р и к а ж д о м ч т е н и и и з очереди н е о б х о д и м о н а х о д и т ь последн и й элемент (требуется к о р р е к т и р о в к а адреса с в я з и предпоследнего элемента). С т р у к т у р а очереди п р и в е д е н а н а рисунке.
О п и с а н и е д а н н ы х и п р о ц е д у р ы р а б о т ы с э л е м е н т а м и очереди п р и в е д е н ы н и ж е по т е к с т у . Type
pt="elem; elem=Record data:Integer; next,pred:pt; End; Var head,tail:pt; Procedure Wnte_Turn (Var up,down:pt;x: Integer) ; {*Запись в очередь — запись в начало списка, х — записываемое число.*) Var t:pt; Begin New(t) ; t".data:=x; t".next:=up; t'.pred:=Nil;
Динамические структуры дачных 366 I f up=Nil Then down:=t Else up".pred:=t; up:=t; EndsProcedure Read_Turn(Var up,down:pt/Var x:Integer) ; (*Чтение не из пустой очереди, чтение последнего элемента списка.*} Var t :pt ; Begin x:=down".data; t:=down; I f down" . pred=Nil Then Begin down: =Nil ,-up: =Nil ; End Else Begin down:=down".pred/ down".next:=Nil; End; Dispose (t) ; End; Э к с п е р и м е н т а л ь н а я работа в о з м о ж н а с и с п о л ь з о в а н и е м след у ю щ е й основной п р о г р а м м ы (необходимо добавить процедуру Print и о п и с а т ь п е р е м е н н у ю w). Требуется н е с к о л ь к о запусков, чтобы она в ы ш л а из р е ж и м а о д н о к р а т н о й запись в очередь. Изм е н е н и е в операторе If Random(2) н а Random(3), естественно, у в е л и ч и т д л и н у очереди, и, м о ж е т быть, п р и д е т с я и с п о л ь з о в а т ь Ctrl+Break д л я п р е р ы в а н и я работы п р о г р а м м ы . Добавление оператора ReadLn после Print (head) позволит более детально о т с л е ж и в а т ь состояние очереди. Begin Randomize; head:=Nil;tail:=Nil; Write_Turn(head,tail,100);("Запись в очередь первого элемента. *) While headoNil Do Begin ("Пока очередь не пуста. *} I f Random (2) >0 Then Write_Turn (head, tail, Random(50)) {"Записываем в очередь случайное Else Read_Turn(head,tail,w);{"Считываем иэ очереди первый иэ записанных элементов. *} Print(head);("Стандартная процедура вывода элементов списка. *) End; End.
367 Часть четвертая Реализуйте структуру данных дек с помощью двунаправленного списка. 3. Д и а п а з о н п р е д с т а в л е н и я ц е л ы х ч и с е л ( I n t e g e r , Word, Longlnt) о г р а н и ч е н . Об этом не раз у ж е ш л а речь в этом у ч е б н и к е . Т а к , н а п р и м е р , ф а к т о р и а л ч и с л а 13! в ы х о д и т з а д и а п а з о н т и п а Longlnt. И с п о л ь з о в а н и е т и п а Real ч а с т и ч н о р е ш а е т проблем у , но не г а р а н т и р у е т с я точность в ы ч и с л е н и я . Однако р е ш а т ь з а д а ч и с м н о г о з н а ч н ы м и ч и с л а м и п р и х о д и т с я . Н а и б о л е е часто д л я п р е д с т а в л е н и я т а к и х чисел используется одномерный массив. К а ж д а я ц и ф р а числа (или группа цифр) хранится в отдельном элементе массива. Н а этом з а н я т и и м ы рассмотрим п р е д с т а в л е н и е м н о г о з н а ч н о г о ч и с л а в в и д е д в у с в я з н о г о спис к а . К а ж д а я ц и ф р а ч и с л а — элемент с п и с к а . Т а к , ч и с л о 2 5 9 7 4 имеет следующее представление.
Ч и с л о х р а н и т с я «задом н а п е р е д » . Т а к о е п р е д с т а в л е н и е удобно п р и в ы п о л н е н и и а р и ф м е т и ч е с к и х о п е р а ц и й , а д л я в ы в о д а у нас есть а д р е с п р е д ш е с т в у ю щ е г о э л е м е н т а . П р и в е д е м « к о с т я к » программы, а затем рассмотрим несколько арифметических операций. Program Long_Arithmetic; Const Basis=10;{"Основание Type pt="elem;
системы
счисления.
elem=Record data:Integer; next,pred:pt; Var head, tail :pt; Procedure WriteLong(t:pt);{"Вывод элементов списка, начиная с его конца. "} Begin While t<>Nil Do Begin Write(t*.data); t:=t*.pred; End; WriteLn; End; Procedure Insert_Begm(Var up,down:pt;x:Integer) ("Процедура вставки элемента в начало списка рассмотрена ранее."}
*) End;
;
Procedure ReadLong (Var up,down:pt) ; Var ch: Char ; Begin Read(ch) ; While Not (ch In ['0'..'9']) Do Read(ch) ; ("Пропуск символов. Пока не встретим символа цифры. *) While ch In ['0'..'9'] Do Begin Insert_Begm (up,down,Ord(ch) -OrdCO')) ; Read(ch); End; EndsBegin ("Основная программа.*j Assign (Input,'Input. Txt');Reset (Input); Assign (Output, 'Output. Txt') .-Rewrite (Output) ; head:=Nil; tail:=Nil; ReadLong(head,tall); WriteLong (tail); Close(Input); Close (Output) ; End. Примечание Напомним, что одновременно на экране должны быть обозримы три файла: файл с текстом программы; Input.Txt и Output.Txt. Если для отладки потребуется окно Watches, то и для него должно быть место на экране. Рассмотрим операцию сложения двух чисел. Она выполняется как обычно, берутся две очередные цифры, находится их сумма с учетом значения разряда переноса из предыдущего разряда по основанию системы счисления и новое значение разряда переноса. Результат запоминается. Единственная особенность — записывать результат необходимо в конец нашего списка, иначе потребуется новый вариант процедуры вывода результата. Procedure AddLong (x,y:pt;Var up, down :pt) ; ( *x, у указатели на начало списков слагаемых; up, down — указатели на начало и конец списка для представления результата. *) Var w,v,a,b:Integer; Begin v:=0;(* Разряд переноса.*)
370
Часть четвертая While <v<>0) Or (xONil) Or (yONil) Do Begin {*Берем очередную цифру первого слагаемого. *} I f xONil Then Begin a:=x".data;x:=хЛ . next; End Else a:=0; f*Берем очередную цифру второго слагаемого. *} I f yONil Then Begin b : =ул . da t a ; y : = у л . next; End Else b:=0; w:=(v+a+b) Mod Basis;("Находим учетом разряда переноса.*) Insert_End(up,down,w);{*Записываем. v(v+a+b) Div Basis;("Вычисляем значение разряда переноса.*) End; End;
сумму
цифр
с
*} новое
Д л я п о л н о т ы о п и с а н и я п р и в е д е м п р о ц е д у р у в с т а в к и элемента в конец двусвязного списка. Procedure Insert_End(Var up,down:pt;х:Integer) ; Var t:pt; Begin New(t); tЛ.Data:=x;tЛ.pred:=down;t".next:=Nil; I f down=Nil Then u p : = t Else down".next:=t; down:=t; End; Процедура у м н о ж е н и я длинного числа на короткое число ( м е н ь ш е , к а к м и н и м у м , в 9 р а з м а к с и м а л ь н о г о ч и с л а т и п а Integer) о ч е н ь п о х о д и т н а п р о ц е д у р у AddLong. Procedure Multip (х:pt;у:Integer;Var up,down: pt) ; f *x — указатель на первый элемент списка, соответствующего множителю; у - короткое число; up, down - указатели на начало и конец списка для представления результата. *) Var w,v,a:Integer; Begin v:=0;{*Разряд переноса.*} While (v<>0) Or (xONil) Do Begin I f xONil Then Begin a : =x". data ;x: =x". next ;End Else a:=0; w: = ( v + a * y ) Mod Basis;{"Вычисляем цифру результата. *}
371
. • J j 5 -
Iasert_End (upf down, ы) ; {"Записываем v: = (v+a *y) Div Basis; l *Вычисляем значение разряда переноса.*} End; End;
цифру. новое
*}
j Ирмените основную программу для проверки процедур слож е н и я длинных чисел и умножения длинного числа на короткое число. Как изменится программа, если в качестве основания систе' мы счисления (константа Basis) взять не 10, а, например, 1000? Задания для самостоятельной работы 1. З а один просмотр файла f , элементами которого являются целые числа, и без использования дополнительных файлов переписать его элементы в другой файл так, чтобы первоначально были записаны все числа, меньшие заданного а, затем все чнсла из отрезка [а. Ь] и все остальные. Взаимный порядок чисел в каждой из групп должен быть сохранен. Идея решения. В решении задачи числа последовательно считываются из файла. Если очередное число меньше а, то оно записывается в файл, если оно принадлежит отрезку [а, Ь], то заносится в первую очередь, иначе — во вторую очередь. После завершения чтения в выходной файл записываются числа из первой и второй очередей. 2. Содержимое текстового файла Л разделенного на строки, переписать в текстовый файл g, перенося при атом в конец каждой строки все входящие в него цифры, с сохранением взаимного исходного порядка. 3. Д в е с т и в порядке возрастания первые N натуральных чисел, в разложение которых на простые множители входят тоыг?
tjjbjKO,4H&na
сред Д , р
2, 3 и 5.
вча н и е да» p&b « „ „
Dec2
' D e ? 3 ' в котащых хранятся числа, г^ы^ Щще не вылолиея. Д з деков счит т ^ т & ' Ъ Р Щ Я ® выводится
Часть четвертая
372
Вывод элемента
Dec 7
Dec2
Dec3
1
2
3
5
2
4
3
4,6
6, 9
5,10,15
4
6, 8
6, 9, 12
5,10,15,20
3, 6
5, 10
5
6, 8, 10
6, 9, 12, 15
10,15,20,25
6
8, 10, 12
9, 12, 15, 18
10,15,20,25,30
10, 12, 16
9, 12, 15, 18, 24
10, 15, 20 25, 40
8
II
I
Р е ш и т е з а д а ч у д в у м я способами — р е а л и з а ц и е й деков через м а с с и в ы и через д в у н а п р а в л е н н ы е с п и с к и . 4. Р а з р а б о т а т ь ф у н к ц и ю с р а в н е н и я двух д л и н н ы х чисел. 5. Р а з р а б о т а т ь процедуру у м н о ж е н и я д л и н н ы х чисел. 6. Р а з р а б о т а т ь п р о г р а м м у в ы ч и с л е н и я 100!
Д и н а м и ч е с к и е структуры д а ч н ы х
373
Занятие № 29. Поиск в графе План занятия • п о н я т и е графа и его представление в п а м я т и компьютера; • э к с п е р и м е н т а л ь н а я работа с программами поиска в глубину и в ш и р и н у , топологической сортировки; • в ы п о л н е н и е самостоятельной работы. Т е р м и н «граф» впервые п о я в и л с я в работах венгерского мат е м а т и к а Д . К е н и г а в 1936 году, хотя ряд задач по теории графов р е ш а л с я ещё JI. Эйлером в XVIII веке. Пусть V — непустое конечное множество, V<2) — множество всех его д в у х э л е м е н т н ы х подмножеств. П а р а G=(V,E), где Е — произвольное подмножество множества V< 2 \ называется графом (неориентированным графом). Элементы множества V н а з ы в а ю т с я в е р ш и н а м и графа, а элементы множества Е — ребр а м и . Мощность (количество элементов) множеств V я Е будем обозначать б у к в а м и N и М. Говорят, что две в е р ш и н ы и и и графа с м е ж н ы е , если (u,v) я в л я е т с я ребром, т.е. п р и н а д л е ж и т множеству Е. Г р а ф G называется помеченным, если его вершинам присвоены некоторые м е т к и , н а п р и м е р числа 1, 2, ..., N. Отождествление к а ж д о й из в е р ш и н графа с ее номером приводит к у в е л и ч е н и ю количества графов. На рисунке п о к а з а н ы три разл и ч н ы х п о м е ч е н н ы х графа. 1
2
2
1
3
1
Представление графа в п а м я т и компьютера. Рассмотрим два способа из всех существующих: матрицу смежности и перечень списков с м е ж н ы х в е р ш и н . Матрица смежности (Л) имеет размерность N*N. Элемент A[i,j]= 1, если в е р ш и н ы с номерами i, j соединены ребром, и A[i,j]=0, если — i и j не связаны ребром. Представление графа G в виде перечня списков с м е ж н ы х верш и н требует введения N списков, по количеству вершин. К а ж д ы й из списков содержит номера всех с м е ж н ы х вершин. Указатели на первые элементы списков объединены в массив. Пример. Д л я помеченного (метки в центре к р у ж к о в ) графа на рисунке м а т р и ц а смежности и перечень списков с м е ж н ы х вершин приведены н и ж е по тексту.
Ч а с т ь четвертая
374
1 | 2 | з | 4 | 5 | б | 7 ~ 8 | 9 1 0 2 3
0 0 1
1 1
0 1
1
_j0
0
0
0
_0
0 0
0 0
0 0
0
0
1
0
0
0^
4
1
0
_0
0
1
0
0
0
0_
5
0
0
_0
1
0___0
1
1
0_
]
°
0
0
1
1
0
1
1_
0__0
0
1
0
1
0
0_
6 _0_
О J
_7
0__0
_8
0
9
0
0
0
0
0
0
1
0
0
Экспериментальный р а з д е л работы 1. Поиск в глубину. И д е я п о и с к а в г л у б и н у ф о р м у л и р у е т с я след у ю щ и м образом: н а ч и н а я с н е к о т о р о й в е р ш и н ы v (например, первой) и д е м «вглубь», п о к а это в о з м о ж н о . Рассматрив а е т с я в е р ш и н а и, с м е ж н а я с v. Она в ы б и р а е т с я . Процесс п о в т о р я е т с я с в е р ш и н о й и. Е с л и н а очередном ш а г е оказалось, ч т о нет н е п р о с м о т р е н н ы х в е р ш и н , с в я з а н н ы х с текущ е й , то в ы п о л н я е т с я возврат к п р е д ы д у щ е й вершине и ищется н о в а я в е р ш и н а , с в я з а н н а я с ней. Е с л и т а к о й в е р ш и н ы не
Динамические структуры дачных
375
найдено, то в ы п о л н я е т с я еще один ш а г возврата к предыдущ е й вершине и так до тех пор, пока вычислительный процесс не вернется к вершине, с которой начат просмотр. Д л я графа, приведенного выше по тексту, очередность просмотра верш и н п р и п о и с к е в глубину у к а з а н а в к в а д р а т н ы х скобках р я д о м с в е р ш и н а м и . Просмотр н а ч а т с первой в е р ш и н ы и приведена только часть ребер графа, тех, по которым выполн я л с я следующий ш а г поиска.
П р и р е а л и з а ц и и поиска используется второй способ предс т а в л е н и я графа (перечень списков с м е ж н ы х вершин). Описание элементов списка очевидно. Д л я х р а н е н и я значений указателей на первые элементы списков (для каждой вершины графа) используется массив А. Номера вершин в той очередности, в которой они просматриваются в процессе поиска в глубину (то, что в к в а д р а т н ы х скобках на рисунке), храним в массиве Rez. Д л я ф и к с а ц и и п р и з н а к а , просмотрена вершина графа или нет, требуется массив Nnew с элементами логического типа. Program Graph_Depths; Const MaxN-10 ; Type pt="elem; elem=Record data:Integer;next:pt;End; MyArray=Array[1.. MaxN] Of pt; MyNew=Array[1..MaxN] Of Boolean; MyRez=Array[1 . .MaxN] Of Integer; Var A:MyArray; ( "Массив указателей на первые элементы списков. "} Nnew:MyNew;("Массив признаков просмотренных вершин графа. *} Rez:MyRez;("Очередность просмотра вершин графа."} N,cnt:Integer;("Количество вершин в графе. Счетчик числа записей в массив Rez."} Procedure Insert_List_End (Var t: pt ;x: Integer) ;
Часть четвертая
376
{*Вставка рассмотрена Procedure
элемента в конец на предыдущих Init;f*Ввод и
инициализация входного количество
данных. файла: вершин
в первой графа
списка. занятиях.
Процедура "}
Структура
2 3 4
строке (N) ; затем
^ N
о строк, по одной на каждую из вершин 2 графа. Первое число в строке 3 количество ребер, выходящих из 2 вершины, а затем номера вершин, с 4 которыми связана текущая вершина. 2 Справа по тексту приведен пример ^ входного файла для графа, рассмотренного ранее.") Var 1,j,w,q:Integer; Begin Cnt:=0; A s s i g n ( I n p u t , ' I n p u t . t x t ' } ; Reset (Input ReadLn(N);{"Количество вершин графа. *} For i:=l To N Do Begin A[l]:=Nil;Nnew[i]:=True;Rez[i]:=0; End; For i:=l To N Do Begin Read(q);{"Степень вершины графа связанных с данной вершиной. *) For j :=1 То q Do Begin Read (w) ;
1 1 4 3 5 5 у
& о 5 7 8 7 6 8 9 7
);
— число
Insert_List_End (A[i] , w) ; End; End; Close(Input); End; Procedure Inc_Rez(v:Integer); Begin Inc (cnt); Rez[cnt]:=v; End; Procedure Search_ Depths (v:Integer); {"Рекурсивный вариант процедуры поиска в глубину. Var t:pt; Begin Nnew[v] :=False; ( "Помечаем вершину как просмотренную.")
ребер,
"}
Динамические структуры дачных
377
; I 'Начинаем просмотр вершин гоафа, смежных с в е р ш и н о й v . *} While tONil Do Beam I f Nnew[t".data] Then Begin{*Если вершина не просмотрена, то записываем ее номер в результирующий массив и идем «вглубь», т.е. К этой вершине. *} Inc_Rez (t".data); Search_ Depths (t data) ; End; t.next;{*Переход к следующему элементу списка. *) End; End; Procedure Prmt;{"Вывод результата.*) Var l:Integer; Begin Assign(Output,'Output.txt'); ReWrite(Output) ; For i: =1 To N Do Write (Rez [ I ], ' ' ) ;Wri teLn; Close (Output); End; Begin{"Один из вариантов основной программы. *) Imt; Inc_Rez (1) ; Search_ Depths (1) ; Print; End. В ы ш е рассмотрена р е к у р с и в н а я р е а л и з а ц и я п о и с к а в глубину. Уход от р е к у р с и и требует в в е д е н и я стека д л я з а п о м и н а н и я п р о с м о т р е н н ы х в е р ш и н . П о с л е д н я я з а п о м н е н н а я в е р ш и н а , по л о г и к е обработки, д о л ж н а первой в ы б и р а т ь с я на последующую обработку. Р е а л и з у й т е не р е к у р с и в н ы й вариант процедуры поиска в глубину. 2. Поиск в ширину. Поиск начинается с фиксированной вершины v0. Затем проверяются все вершины, смежные с v0 и не просмотренные ранее. И х номера заносятся в очередь. Далее процесс продолжается с первой из вершин, записанной в очередь. И т а к до тех пор, пока очередь не о к а ж е т с я пустой. Н а рис у н к е п р и в е д е н а часть ребер исходного графа, тех, по котор ы м о с у щ е с т в л я л с я «переход» в процесс просмотра в е р ш и н графа при поиске в ш и р и н у . В к в а д р а т н ы х скобках указана очередность просмотра в е р ш и н .
Часть четвертая
378
Program
Graph_Breadth
;
Const Type Var
I"Описание
что
приведено
Procedure
данных
{"Процедуры
совпадают
программе.
*}
Procedure
Init;
Procedure
совпадает
в предыдущей
Inc_Rez
Procedure
с
тем,
программе.
Insert__List__End(Var
*}
t:pt;х:Integer);
с приведенными
в
предыдущей
(v:Integer);
Print;
("Новая процедура. Поиск в ширину."} Procedure Search_Breadth (v:Integer);{ вершины, с которой начинается поиск в Var t:pt;
"v — номер ширину.*}
w:Integer; Type ptr="el_turn ; ( "Для используется двусвязный
Var
el_turn=Record End; head,tail:ptr;
реализации список."}
data:Integer;
Procedure W r i t e _ T u r n ( V a r up, ("Действия с элементами очереди предыдущих занятиях.*)
очереди next,pred:ptr;
down:ptr;x:Integer); рассмотрены на
Procedure Read_Turn (Var up,down:ptr; Var x: Integer); Function Empty_Turn(t,q:ptr):Boolean; Begin(" «Тело» процедуры Search_Breadth. *) head:=Nil;tail:=Nil; Nnew[v]:=False;Write_Turn(head, tail, v);
Динамические структуры дачных
379
{"Записываем в очередь номер вершины графа, с которой начинается просмотр. *) While Not (Empty_Turn(head,tail) ) Do Begin('Пока очередь не пуста, выполняем действия.*} Read_Turn (head,tail,w);("Читаем элемент из очереди. *} t:=A[w]; While tONil Do Begin ("Пока не просмотрены все элементы списка. *} I f Nnew[t".data] Then Begin("Вершина «не посещалась» ранее.") Inc__Rez f t " .data) ; Nnew [ t". data ] :=False; Write_Turn(head,tail,t*.data); End; t:=t л . n e x t ; End; End; End; ("Один иэ вариантов основной программы. ") Begin Imt; Inc_Rez (1) ; Search_Breadth (1) ; Print; End. И з м е н и т ь п р о г р а м м у т а к , чтобы р е а л и з а ц и я очереди выполн я л а с ь через м а с с и в , с о д е р ж а щ и й не более N/2 я ч е е к . Привед и т е п р и м е р ы графов, д л я к о т о р ы х д а н н ы й в а р и а н т п р о г р а м м ы о к а ж е т с я неработоспособным. 3. Топологическая сортировка. Ориентированный граф (или орграф) — это пара G=(V,E), где V - множество вершин, Е — м н о ж е с т в о ориентированных ребер, которые называются дугами. Н а р и с у н к а х дуги о т м е ч а ю т с я с т р е л к а м и . Д л я орграф о в м е т о д ы обхода в е р ш и н г р а ф а , р а с с м о т р е н н ы е в ы ш е , т а к ж е и м е ю т с и л у . П р е д п о л о ж и м , что в орграфе нет ц и к л о в ( и н т у и т и в н о этот т е р м и н понятен) и он связен. О к а з ы в а е т с я , ч т о п р и этом п р е д п о л о ж е н и и в е р ш и н ы , а и м присвоены знач е н и я м е т о к и о н и п р о н у м е р о в а н ы , м о ж н о перенумеровать т а к , что к а ж д а я дуга будет иметь вид ( v ^ v j , где i<j. В этом суть з а д а ч и топологической сортировки. Эта задача, естественно, отличается от рассмотренных ранее задач сортировки. В н и х м ы переупорядочивали элементы массива (в основном), в этой задаче требуется переопределить метки вершин графа. Суть алгоритма. П р и вводе о п и с а н и я графа, а м ы по-прежн е м у работаем с его о п и с а н и е м в виде списков с м е ж н о с т и , форм и р у е м д л я к а ж д о й в е р ш и н ы (i) х а р а к т е р и с т и к у Deg[i] — чис-
Часть четвертая
380
ло дуг, в х о д я щ и х в вершину с д а н н ы м номером г. Просматриваем в е р ш и н ы и номера в е р ш и н г, и м е ю щ и х нулевое значение Deg[i], з а п о м и н а е м в стеке. П о с л е этого, п о к а стек не пуст, в ы б и р а е м п е р в ы й э л е м е н т из с т е к а , п р и с в а и в а е м н о в ы й т е к у щ и й номер в ы б р а н н о й в е р ш и н е и у м е н ь ш а е м степени всех в е р ш и н графа, с в я з а н н ы х с д а н н о й , н а е д и н и ц у . Е с л и о к а з а л о с ь , ч т о п р и этом степень в е р ш и н ы с т а л а н у л е в а я , то ее н о м е р з а н о с и м в стек. И с п о л ь з у е т с я л о г и к а п р о с м о т р а в е р ш и н г р а ф а — метод п о и с к а в г л у б и н у (а м о ж н о л и и с п о л ь з о в а т ь п о и с к в ш и р и н у ? ) . Приведем описание данных нашего решения. Program Topol_Sort; Const MaxN= .; Type pt="elem;{"Тип используется как для формирования описания графа в виде списков связи, так и для организации стека. "} elem=Record data:Integer;next:pt;End; MyArray=Array[1..MaxN] Of pt;{"Массив указателей для хранения адресов на первые элементы списков, связанных с каждой вершиной графа. *I MyRez=Array[1. . MaxN] Of Integer; Var A:MyArray; Deg,NewNum:MyRez;{"Степени вершин и новые номера вершин. "} N:Integer;{"Количество вершин графа."} Р а с с м о т р и м п р и м е р , он приведен на р и с у н к е , и трассировку л о г и к и д л я него (она д а н а в т а б л и ц е ) по п р о ц е д у р е р е а л и з а ц и и топологической с о р т и р о в к и .
Procedure ChangeNum; Var head:pt;I"Указатель nm,v,i:Integer; t:pt; Begin head:=Nil;
на начало
стека."}
Динамические структуры дачных 380 пт:=0;{"Текущий новый номер вершины. *} For 1:=1 То N Do I f Deg[i]=0 Then WriteStack(head,1); ("Первоначальное занесение в стек.*} While Free (head) Do Begin { *Пока стек не пуст. Процедуры WriteStack, ReadStack и функция Free рассмотрены ранее на занятии по стеку. *} ReadStack(head,v);Inc(nm);NewNum[v]:=nm; {*Присваиваем новый номер вершине, выбранной из стека. *} t:=А[v]; While tONil Do Begin {"Просматриваем вершины, \ связанные с данной. *) Dec (Deg[t*.data]);('Уменьшаем степень этой вершины на единицу. *} I f Deg[t".data]=0 Then WriteStack(head, t" . data);{*Если оказалось, что степень вершины после этого равна нулю, то заносим ее в стек. *} t:=t". next; { *Переходим к следующей вершине, связанной дугой с данной.*} End; End; End; J Номер итерации Начальные 1 2 3 4 5 j
6
NewNum Состояние стека Deg 5 2, 2, 2, 1,0, 1 0, 0, 0, 0, 0, 0 2, 2, 1, 0, 0, 1 0,0, 0,0, 1,0 1,2,0, 0,0, 1 0,0, 0,2, 1,0
4 3
0, 2, 0, 0, 0, 0 0, 0, 3, 2, 1,0 0, 1, 0, 0, 0, 0 0, 0,3, 2, 1,4 5,0,3,2, 1,4 0, 0,0,0,0,0 5,6,3, 2, 1,4
6, 1 1 2
nm 0 1 2
3 4 5 6
J
В процедуре Init, рассмотренной ранее, следует добавить ф о р м и р о в а н и е элементов массива Deg. Разработайте п о л н ы й текст р е ш е н и я , проверьте его н а р а з л и ч н ы х п р и м е р а х . Другой способ топологической сортировки состоит в том, чтобы последовательно н а х о д и т ь в е р ш и н ы со степенью 0, запоминать и х ( и л и выводить) и у д а л я т ь из графа вместе со всеми в х о д я щ и м и в н и х д у г а м и . Р е а л и з у й т е эту м о д и ф и к а ц и ю алгоритма.
382
Часть четвертая
Задания для самостоятельной работы 1. Чередующаяся последовательность е ; , и2, е2, ..., е(, и1+1 вершин и ребер графа, т а к а я , что e=vlvl+1 (i=l,...,l), называется маршрутом, соединяющим вершины v1 и vl+1. Очевидно, что маршрут можно задать последовательностью u l t и2, ..., v l+1 его вершин. Маршрут называется цепью, если все его ребра различны, и простой цепью, если все его вершины, кроме, возможно, крайних, различны. Граф называется связным, если любые две его несовпадающие вершины соединены маршрутом. Рассмотренные выше методы обхода вершин графа работают только для связных графов. Если граф несвязен, то просмотр вершин выполняется только в пределах одной связной компоненты. Напишите программу определения количества связных компонент графа. 2. Разработайте программы поиска в глубину и поиска в ширину (рекурсивный и не рекурсивный варианты) при описания графа с помощью матрицы смежности. 3. Маршрут называется циклическим, если его начальная и конечная вершины совпадают. Ц и к л и ч е с к а я цепь называется циклом, а ц и к л и ч е с к а я простая цепь — простым циклом. Определить, есть ли в связном графе простые ц и к л ы нечетной длины. 4. Граф G=(V,E) называется двудольным (V=X>JY), если существует такое разбиение множества его вершин на две части (X и У), что концы каждого ребра принадлежат разным частям. Д. Кёниг сформулировал простой критерий двудольности графа. Для двудольности графа необходимо и достаточно, чтобы он не содержал циклов нечетной длины. При поиске в ширину приписываем вершине, с которой начинаем поиск, номер 0. Всем вершинам, в которые мы попадаем из исходной, приписываем номера 1, всем вершинам, еще не пронумерованным, приписываем номер 2, если мы попадаем в них из вершин с номерами 1. З а п и ш е м в множество X вершины с четными номерами, а в множество У — вершины с нечетными номерами. Осталось проверить, что вершины, принадлежащие X и У, не смежны. Напишите программу проверки двудольности графа, и если он двудолен, то найдите множества Х и У . 5. В ориентированном графе аналогом понятия цепи является понятие «путь». Если (и,*) дуга, то вершины u u t называются её началом и концом соответственно. Вершина t называется достижимой из вершины и, если существует путь из и в t. В ориентированном графе найти множество всех вершин,
i структуры данных
д о с т и ж и м ы х и з заданной вершины v. Найти для к а ж д о й верш и н ы графа множество д о с т и ж и м ы х и з нее вершин. 6. П у с т ь G — с в я з н ы й граф, а и и и — две его несовпадающие в е р ш и н ы . Д л и н а к р а т ч а й ш е г о м а р ш р у т а м е ж д у и и v (он, естественно, я в л я е т с я простой цепью) называется расстоянием м е ж д у в е р ш и н а м и и и v, обозначим к а к d(u,v). Поиск в ширину дает к р а т ч а й ш и е расстояния от первой в е р ш и н ы (обоз н а ч и м к а к и) до всех остальных вершин. Максимальное значение этих расстояний называют эксцентриситетом вершины и. М а к с и м а л ь н о е значение среди всех эксцентриситетов верш и н н а з ы в а е т с я диаметром графа d(G). Минимальное из эксцентриситетов в е р ш и н связного графа называется его радиусом и обозначается через r(G). Написать программу поиска д и а м е т р а и радиуса графа. Н а й т и простые цепи, д л и н ы которых совпадают с диаметром и радиусом графа. 7. В е р ш и н а и и ребро е называются инцидентными, если v является концом ребра е. Степенью вершины графа G называется число инцидентных ей ребер и обозначается deg( и). Напишите программу проверки «леммы о рукопожатиях»: сумма степеней всех вершин графа — четное число, равное удвоенному ч и с л у ребер. П р и м е ч а н и е Проверьте на примерах следствие этой леммы: «в любом графе число вершин нечетной степени четно». 8. Степенью ребра (и,и) назовем неупорядоченную пару (deg(и), deg(v)). Написать программу определения, совпадают ли степени всех ребер заданного графа, и если нет, то можно ли удалить из него одну вершину (вместе с инцидентными ребрами) т а к , чтобы полученный граф обладал этими свойством. 9. Д л я произвольного графа G его дополнение — граф G' — определяется следующим образом. Любые две несовпадающие верш и н ы смежны в G' тогда и только тогда, когда они не смежны в G. Напишите программу проверки утверждения: «для любого графа либо он сам, либо его дополнение является связным». 10. И с т о ч н и к о м в ориентированном графе назовем вершину, от которой д о с т и ж и м ы все другие вершины, стоком — вершину, достижимую от всех других вершин. Написать программу п о и с к а всех источников и стоков данного ориентированного графа.
384
Часть четвертая
Занятие № 30. Двоичные деревья • основные п о н я т и я ; • д в о и ч н ы е деревья, основные о п е р а ц и и ; • э к с п е р и м е н т а л ь н а я работа с п р о г р а м м а м и р е а л и з а ц и и дерева с п о м о щ ь ю массивов, п и р а м и д а л ь н о й сортировки с п о м о щ ь ю двоичного дерева; • в ы п о л н е н и е с а м о с т о я т е л ь н о й работы. Основные понятия. С в я з н ы й граф без ц и к л о в называется деревом. П р и м е р дерева приведен н а рисунке.
Е),
Следующие о п р е д е л е н и я дерева э к в и в а л е н т н ы . Граф G=(V, где 1V| =ЛГ и \Е\=М я в л я е т с я деревом, если: • G — с в я з н ы й граф и M=N~1\ • G — а ц и к л и ч е с к и й граф и M=N-1\ • любые две несовпадающие в е р ш и н ы графа G соединяет единственная простая цепь; • G — а ц и к л и ч е с к и й граф, обладающий тем свойством, что если к а к у ю - л и б о п а р у н е с м е ж н ы х в е р ш и н соединить ребром, то п о л у ч е н н ы й граф будет содержать ровно один цикл. Определим терминологию, с в я з а н н у ю с п о н я т и е м дерева: • к о р н е м дерева н а з ы в а ю т единственную в е р ш и н у , находящ у ю с я вверху «перевернутого» дерева; • самые н и ж н и е в е р ш и н ы дерева н а з ы в а ю т л и с т ь я м и ; • в е р ш и н у н а з ы в а ю т внутренней, если она не я в л я е т с я ни к о р н е м и ни листом; • о в е р ш и н е , к о т о р а я находится непосредственно над другой в е р ш и н о й , говорят, что она — родитель (предок), а в е р ш и н а , к о т о р а я расположена непосредственно под другой в е р ш и н о й , называется потомком.
Динамические структуры дачных
385
Н а р и с у н к е в е р ш и н а с номером 1 — корень; в е р ш и н ы с ном е р а м и 4, 5, 6, 9, 10, 11 — листья; в е р ш и н ы 2, 3, 7, 8 — внутренние; в е р ш и н а 3 — родитель в е р ш и н ы 7; в е р ш и н а 8 — потомок в е р ш и н ы 3. С ч и т а ю т , что к о р е н ь дерева р а с п о л о ж е н на первом уровне. Его п о т о м к и н а х о д я т с я н а втором уровне и т. д. М а к с и м а л ь н ы й уровень к а к о й - л и б о в е р ш и н ы дерева н а з ы в а е т с я глубиной и л и высотой дерева. Ч и с л о потомков в е р ш и н ы называется ее степенью. М а к с и м а л ь н о е з н а ч е н и е этих степеней есть степень дерева. Степень дерева на рисунке, приведенном выше, р а в н а трем. Суть двоичных деревьев, ш и р о к о р а с п р о с т р а н е н н ы х в прог р а м м и р о в а н и и , следует из н а з в а н и я . Степень дерева равна д в у м . В е р ш и н а (узел) дерева м о ж е т иметь не более двух пот о м к о в , и х н а з ы в а ю т л е в ы м и и п р а в ы м и . Двоичные (бинарные ) деревья поиска (подкласс д в о и ч н ы х деревьев) характериз у ю т с я тем, что з н а ч е н и е и н ф о р м а ц и о н н о г о п о л я , связанного с в е р ш и н о й дерева, больше любого соответствующего значен и я и з левого поддерева и м е н ь ш е , чем содержимое любого у з л а его правого поддерева. Описание в е р ш и н ы дерева имеет вид: Type pt="node; node=Record data:Integer;("Информационное поле. left,right:pt;{"Ссылки на левого и потомков.*} End; Var root:pt;{"Указатель на корень
"} правого
дерева."}
Основные операции: • в с т а в к а элемента в дерево; • удаление элемента и з дерева; D обход дерева. В с т а в к а элемента в двоичное дерево поиска реализуется с п о м о щ ь ю следующей простой рекурсивной процедуры. Первон а ч а л ь н ы й вызов — Ins_Tree( root,number). Procedure Ins_Tree (Var t:pt;x: Integer) значение вставляемого элемента.") Begin I f t=Nil Then Begin New(t) ; With t " Do B e g i n left:=Nil; right:=Ni1;
; { "x
-
data:=x;
386
Часть четвертая
Else
End; End I f x<=tn.data Else Ins__Tree
Then Ins_Tree(t".left (t" .right, x) ;
,x)
End; В ы п о л н и т е «ручную» т р а с с и р о в к у л о г и к и п р о ц е д у р ы н а примере в с т а в к и н е с к о л ь к и х э л е м е н т о в в п р о и з в о л ь н о е д в о и ч н о е дерево поиска. Р е а л и з а ц и я у д а л е н и я э л е м е н т а и з дерева чуть с л о ж н е е . Если у з е л и м е е т одного п о т о м к а , то в п о л е с с ы л к и р о д и т е л я удаляем о г о э л е м е н т а з а п и с ы в а е т с я с с ы л к а , не р а в н а я Nil, и н а этом все з а к о н ч е н о . В т о м с л у ч а е , к о г д а у у д а л я е м о г о э л е м е н т а два п о т о м к а , д л я с о х р а н е н и я с т р у к т у р ы д е р е в а п о и с к а на место этого э л е м е н т а н е о б х о д и м о з а п и с а т ь и л и с а м ы й п р а в ы й элем е н т л е в о г о п о д д е р е в а , и л и с а м ы й л е в ы й э л е м е н т п р а в о г о поддерева. Один из двух вариантов, другого не дано, и выбирается один из них. На рисунке приведена схема удаления элемента д л я первого в а р и а н т а (самый правый элемент левого поддерева удаляемого элемента).
Вид дерева до удаления Procedure Del_Tree Var q:pt; Procedure Del(Var элемента в дереве.
(Var
Вид дерева после удаления t:pt;
w:pt);("Поиск *)
x:Integer); самого
правого
Динамические структуры дачных 386 Begin I f w" . rightONil Then Del (w". right) Else Begin q:=w;{"Запоминаем адрес для того, чтобы освободить место в «куче». *} tА.data:=wл.data; left; End; End; Begin I f tONil Then I f x<t".data Then Del_Tree (t*.left,x) Else I f x>f .data Then Del_Tree (t" . right, x) Else Begin q:=t; I f t " . right=Nil Then t:=t*.left {"Правого поддерева нет.") Else I f t л . left=Nil Then t : = t n . r i g h t {"Левого поддерева нет.") Else Del(t*.left);{"Находим самый правый элемент в левом поддереве.") Dispose (q) ; End; End; П е р в о н а ч а л ь н ы й вызов процедуры — Del_Tree(root,number), где number — з н а ч е н и е у д а л я е м о г о элемента. Д л я в ы в о д а з н а ч е н и й элементов двоичного дерева необходимо в ы п о л н и т ь п о л н ы й обход дерева. П р и обходе дерева его отд е л ь н ы е в е р ш и н ы п о с е щ а ю т с я в определенном п о р я д к е . В процедуре Print_Tree, а с её помощью осуществляется обход дерева, м о ж н о ш е с т ь ю способами переставить операторы 1, 2, 3. К а ж д ы й способ п е р е с т а н о в к и даст вывод элементов дерева в определ е н н о й последовательности. Procedure Print_Tree (t:pt); Begin I f tONil Then Begin Print_Tree(t".left); {"Первый оператор (1).") Write (t* .data: 3) ; { "Второй оператор (2).") Print_Tree ( f . right) ; { "Третий оператор (3).") End; End;
387 Часть четвертая
,С) 1
В т а б л и ц е п е р е ч и с л е н ы все о ч е р е д н о с т и в ы в о д а в е р ш и н д е р е в а , п р и в е д е н н о г о н а р и с у н к е , получ а е м ы е с п о м о щ ь ю п е р е с т а н о в к и операторов в процедуре Pnnt_Tree. Способ 1,2,3 1,3,2 2,1,3 2, 3, 1 3,1,2 3, 2, 1
Очередность вывода ВАС В СА ABC АСА СВА CAB
I |
Н а и б о л е е и н т е р е с н ы п е р в ы х т р и способа, и х н а з ы в а ю т , соотв е т с т в е н н о , с л е в а н а п р а в о , с н и з у в в е р х и с в е р х у в н и з . Д л я выр а ж е н и я (А+В/С)*(D-E*F), п р е д с т а в л е н н о г о в в и д е д е р е в а (на р и с у н к е ) , они д а ю т и н ф и к с н у ю (A+B/C*D-E*F — без с к о б о к Л п о с т ф и к с н у ю (ABC/+DEF*-*) и префиксную (*+A/BC~D*EF) формы записи.
В н е к о т о р ы х с л у ч а я х э л е м е н т ы д е р е в а удобно в ы в о д и т ь не в виде с т р о к и , п р и этом с т р у к т у р а д е р е в а не п р о с м а т р и в а е т с я , а выполнять размещение на экране с учетом их в з а и м н ы х связей в дереве. С л е д у ю щ а я п р о с т а я п р о ц е д у р а я в л я е т с я п е р в ы м приб л и ж е н и е м р е ш е н и я этой з а д а ч и . Procedure Step_Tree(t:pt;h:Integer);(*h количество выводимых пробелов, выводом значения информационного Var 1:Integer; Begin I f toNil Then With t A Do Begin
вставляемых поля узла.
перед *}
Динамические структуры дачных Step_Tree For i:=l Step_Tree EndsEnd;
(left,h+5) ; To h Do Write С (right,h+5) /
389
V/WriteLn
(data : 3) ;
Разработать программу работы с двоичным деревом, в которой вставка и удаление элементов осуществляются случайным образом. Экспериментальный раздел работы 1. Рассмотрим работу с двоичным деревом, если для его представления используются обычные массивы. Д л я х р а н е н и я з н а ч е н и й ключей (Key), левых (Left) и правых (Right) ссылок выделим свои массивы. Номер я ч е й к и с корнем дерева я в л я е т с я значением переменной Root. Д л я того, чтобы не иск а т ь свободную ячейку, адрес первой свободной записывается в HeadFree, а сами я ч е й к и связаны адресами в отдельном массиве Next. Описание данных программы и процедура In.it имеют вид: ($R+} Program Tree__Array; Const NMax=20; Type MyArray=Array[1..NMax] Of Integer; Var Key, L e f t , Right, Next: MyArra у ; N,Root,HeadFree:Integer; Procedure Imt; Var i: Integer; Begin FillChar (Key, SizeOf (Key) ,0) ; FillChar (Left,SizeOf (Left) , 0) ; FillChar (Right, SizeOf (Right) ,0) ; ReadLn(N); For i:=l To N-1 Do Next[i]:=i+l; Next[N]:=0;Root:=0;HeadFree:=1; End; Д л я примера, рассмотренного выше по тексту, после формирования дерева значения элементов массива представлены в таблице. Значение переменной Root равно 1, HeadFree — 12.
390
Часть четвертая
Номер элемента массива
Key
Left
Right
Next
1
100 120 20 15 50 30 55 25 35 60 33 0 0 0 0
3 0 4 0 6 8 0 0 11
2 0 5 0 7 9 10 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 13 14
2 3 4 5 6
7 g
9 10 11 12 13 I
20
J
0 0 0 0 0 0
0
Рассмотрим вставку элемента в дерево поиска. Procedure Inset_Tree{х: Var t , q, w: Integer; Begin I f Root=0 в дерево
Then Begin - корня. *}
Integer);
{"Вставка
первого
элемента
Root:=HeadFree;HeadFree:=Next[HeadFree]; Key[Root]:=x;Next[Root]:=0; End Else Begin t:=Root; While t<>0 Do Begin {"Находим место дереве. *} q:=t;{"Номер предыдущего элемента. I f х<Кеу[t] Then t:=Left[t] Else {"Идем по правой или по левой ссылке. End;
элемента
в
*) t:=Right[t]; *)
W:=HeadFree;HeadFree:=Next[HeadFree] ; Next[w]:=0;{"Берем ячейку иэ списка свободных. *) Key [w]:=х; I f x<Key[q] Then Left[q]:=w Else Right[q]:=w; {"Изменяем ссылку у предыдущего элемента.") End; End;
Динамические структуры дачных 390 С у д а л е н и е м э л е м е н т а и з д е р е в а ч у т ь с л о ж н е е (много частн ы х с л у ч а е в ) . П р е ж д е ч е м п е р е й т и к и х разбору, п р и в е д е м простую л о г и к у в о з в р а т а я ч е й к и с н о м е р о м t в с п и с о к свободных. Возвращаемая я ч е й к а становится первой в списке свободных. Procedure Return(t:Integer} список свободных. ") Begin I f tRoot Then Begin Left[t]:=0; Right[t]:=0; Next[t]:=HeadFree; End; End;
;{"Возврат
ячейки
в
Key[t]:=0; HeadFree:=t;
П р и в е д е м «шаблон» п р о ц е д у р ы у д а л е н и я . П р е ж д е ч е м расс м а т р и в а т ь его, п о п ы т а й т е с ь «прорисовать» все в о з м о ж н ы е случ а и у д а л е н и я э л е м е н т а и з дерева и в ы п о л н я е м ы е п р и этом действия. Procedure Del_Tree(х:Integer); Var t,q,w,z:Integer; Begin t: =Root;q:=t;{"Поиск удаляемого элемента.*} While (t<>0) And (xOKey [t]) Do Begin q:=t;{*Запоминаем адрес предыдущего элемента . *} I f x<Key[t] Then t:=Left[t] Else t:=Right[t]; End; I f t<>0 Then Begin{*Если элемент найден, то выполняем удаление. *} I f Left[t]=0 Then{"Левого поддерева у удаляемого элемента нет.*} I f t=Left[qj Then Left[q] —Right[t] {"Удаляемый элемент — левый потомок родителя?"} Else I f t o Root Then Right[q]:=Right[t] Else Root:=Right[t] {"Удаляется корень дерева и он не имеет левого потомка."} Else I f Right[t]=0 Then {"Правого поддерева у удаляемого элемента нет.*) I f t=Left[q] Then Left[q]:=Left[t] {"Удаляемый элемент - левый потомок родителя?"} Else I f toRoot Then
391 Часть четверта Right[q]:=Left[t] Else Root:=Left[t] {"Удаляется корень дерева, и он не имеет правого потомка. ") Else Begin {"Удаляемый элемент имеет два потомка. Находим самый правый элемент в левом поддереве. *} w:=Left[t];z:=t;q:=z;{"Запоминаем сам элемент, его родителя и еще одного родителя (дедушку). Дайте объяснение этому факту.*) While w<>0 Do Begin q:=z;z:=w; w:=Right[w];End; I f Left[q]= z Then Left[q]:=Left[z] {"Родитель удаляемого элемента является левым потомком своего родителя.") Else Right[q]:=Left[z]; Key[t]:=Key[z];("Переписываем значение, например 35, на место удаляемого элемента, равного 50 (в примере) . *) t:=z;("B список свободных ячеек заносится самый правый элемент из левого поддерева. Вся информация из этого узла дерева переписана в как бы удаляемый элемент. ") End; Return (t);("Возвращаем элемент в список свободных. *) End; End; Проверьте п р а в и л ь н о с т ь работы р а с с м о т р е н н ы х процедур. Все л и с л у ч а и у ч т е н ы ? Р а з р а б о т а й т е д и н а м и ч е с к у ю модель работы с д в о и ч н ы м деревом. В с т а в к а и удаление элементов дерева д о л ж н ы в ы п о л н я т ь с я по некоторому с л у ч а й н о м у з а к о н у . П р и этой работе Вам м о ж е т о к а з а т ь с я полезной вспомогательная процедура просмотра элементов массивов. Procedure Var 1 Begin
Print; :Integer;
Динамические структуры дачных 392
F o r г : =1 То N For i:=l To N For i:=l To N For i:=l To N For i:-l To N WriteLn (Root,' End;
Do Write (1: 4) ;Writeln; Do Write (Key [l ] : 4) ;Writeln; Do Write(Left[i]:4);Writeln; Do Write (Right [ I ] : 4) ; Writeln ; Do Write(Next[l]:4);Writeln; ',HeaDFree);
2. В е р н е м с я к теме п и р а м и д а л ь н о й с о р т и р о в к и ( з а н я т и е 20). Д а н н ы е во в х о д н о м ф а й л е представлены в формате: N — число элементов (в первой строке), а затем со второй строки сами д а н н ы е , естественно, не отсортированные. Н а п р и м е р : 7 8 10 3 13 9 5 12 Н а ш е й первой з а д а ч е й я в л я е т с я ч т е н и е д а н н ы х и з ф а й л а и п р е д с т а в л е н и е и х в виде правильного двоичного дерева (максим а л ь н о е к о л и ч е с т в о узлов дерева имеет два потомка). Затем это дерево необходимо преобразовать в п и р а м и д у , т а к к а к это показано н а р и с у н к е . Элементы дерева образуют п и р а м и д у , если з н а ч е н и е и н ф о р м а ц и о н н о г о п о л я в к а ж д о м узле дерева больше з н а ч е н и й у у з л о в потомков. Очевидно, что после этого преобраз о в а н и я в к о р н е дерева записан м и н и м а л ь н ы й элемент. Запис ы в а е м его в р е з у л ь т и р у ю щ и й ф а й л , а н а его место записываем число, заведомо большее (а после первого ш а г а и равное) всех з н а ч е н и й в у з л а х дерева. После этого «проталкиваем» верхний элемент в н и з по дереву. В корне дерева о к а ж е т с я вновь следующ и й м и н и м а л ь н ы й элемент. З а п и с ы в а е м и з а м е н я е м его на м а к с и м а л ь н о в о з м о ж н о е значение. Процесс продолжается N раз.
П о с л е первого «проталкивания» дерево имеет вид, показанн ы й на с л е д у ю щ е м р и с у н к е (до и после «проталкивания»). 15—452
393 Часть четвертая
Program Tree_Pyramid; Type pt="elem; elem=Record data:Integer; l e f t , r i g h t : p t ; End; Var head:pt;("Указатель на корень дерева.*) N:Integer; f:Text; Begin Assign (Input,'Input.Txt')/Beset (Input); {"Стандартные действия no открытию рабочих файлов программы. *) Assign(Output,'Output.Txt');Rewrite(Output); Assign ( f , ' R e s u l t . T x t ' ) ; R e w r i t e ( f ) ; ReadLn (N);head:=Nil;("Значение N требуется для построения «правильного» дерева.") head:=Тгее(N);{"Функция Tree обеспечивает построение «правильного» дерева.") Step_Tree (head,0);{"Вспомогательная процедура вывода дерева. Рассмотрена ранее. Можно исключить, она не играет принципиальной роли в данном решении.") Change(head);{"Строим пирамиду."} Step_Tree(head,0); Solve(1);("Записывает значение из корня дерева в результирующий файл и проталкивает максимальное значение вниз по дереву. Параметр процедуры указывает на номер проталкивания. *} Close(Input);Close(Output);Close (f); End. Р а с с м о т р и м ф у н к ц и ю построения «правильного» дерева. Она в о з в р а щ а е т в основную п р о г р а м м у у к а з а т е л ь на к о р е н ь дерева и я в л я е т с я , естественно, р е к у р с и в н о й . П а р а м е т р ф у н к ц и и (t) о п р е д е л я е т к о л и ч е с т в о элементов, и з к о т о р ы х строится дерево.
Динамические структуры дачных
395
Создается н о в ы й узел, а затем рекурсивно строятся левое и правое поддеревья с количеством узлов в два раза м е н ь ш и м , чем з н а ч е н и е t (для правого поддерева у м е н ь ш а е м количество узлов на единицу — минус, с т р о я щ и й с я узел). Function Tree (t: Integer) :pt; Var x: Integer ; w:pt; Begin I f t=0 Then Tree:=Nil{"Нулевое количество узлов определяет ссылку типа Nil. "} Else Begin Read(x) ; l "Читаем число из входного файла. *) New(w);("Выделяем место в «куче» для нового узла дерева.") With w' Do Begin data:=х; left:=Tree(t Div 2) ; {"Формируем значения левой и правой ссылок узла.") right: =Tree (t-t Div 2 -1) ; End; Tree:=w; EndsEnd; П р и построении п и р а м и д ы необходимо поменять значения в у з л а х р о д и т е л я и потомков, у родителя должно быть наименьшее из трех з н а ч е н и й . Эти действия в ы п о л н я ю т с я при стандартном рекурсивном обходе дерева, например к а к при его выводе в постфиксном виде. P r o c e d u r e Swap(Var х,у:Integer);{"Вспомогательная процедура. *} Var z : I n t e g e r ; Begin I f x>y Then Begin z.-=x;x:=y;y:=z;End; End; P r o c e d u r e Change f t : p t ) ; Begin I f t o N i J Then Begin Change ( f . l e f t ) ; { "Идем в левое поддерево. "} Change ft".right;;("Затем в правое поддерево.") I f t" . leftONil Then Swap (t".data,t*.left*.data); ("Записываем минимальное значение в
395 Часть четвертая корень поддерева.*} I f t" . rightoNil Then (t".data, t".right".data) End; End;
Swap ;
О с н о в н а я п р о ц е д у р а п и ш е т с я по т р а д и ц и о н н о м у п р и н ц и п у «как говорим, так и программируем». Procedure Solve (k:Integer); Begin I f k<=N Then Begin {*Пока данные не отсортированы, выполняем . . .*) Write(f,head".data,' ' ) ;{*3аписываем значение из корня дерева в результирующий файл. *) head".data:=MaxInt;(* Присваиваем значение 32767. *) Push (head);{*Проталкиваем значение из корня дерева вниз по дереву. *) Step_Tree (head,0);(*Вспомогательная процедура. Можно исключить. *} Solve(k+1);(*Переходим к обработке следующего элемента. *) End; End; И н а к о н е ц , п р о ц е д у р а « п р о т а л к и в а н и я » э л е м е н т а по дереву. Procedure Push (t:pt); Var v,w:pt;(*Вспомогательные переменные. Введены для того, чтобы текст процедуры помещался на экране, был обозримым. Вспомним о структурном программировании. *) Begin I f t o Nil Then Begin v:=t".left;w:=t".right;(*Запоминаем адреса корневых элементов левого и правого поддеревьев. *) I f (vONil) And (wONil) Then {*Если оба поддерева есть, то выбираем поддерево с наименьшим значением в корне.*) I f v".data<w".data Then Begin Swap (t".data,v" .data);Push(v);End Else Begin Swap(t".data,w".data);Push(w);End {*Обработка случаев, когда узел имеет одно
Динамические структуры дачных поддерево или является листом. *} Else I f vONil Then Begin Swap (t" . data, v". data) P u s h (v) ;End Else I f wONil Then Begin Swap (t* . data, w".data) ;Push (w) ;End; End; End;
397
;
П р о т е с т и р у й т е р е ш е н и е . Оцените в р е м е н н у ю с л о ж н о с т ь расс м о т р е н н о г о м е т о д а с о р т и р о в к и . Обратите в н и м а н и е н а то, что п а м я т ь к о м п ь ю т е р а используется только (практически) из «кучи». Задания для самостоятельной работы 1. Р а з р а б о т а т ь л о г и ч е с к у ю ф у н к ц и ю : • п р о в е р к и н а л и ч и я заданного э л е м е н т а в дереве; • п о д с ч е т а с у м м ы э л е м е н т о в дерева; • о п р е д е л е н и я н а и б о л ь ш е г о (наименьшего) элемента в дереве; • подсчёта к о л и ч е с т в а элементов в дереве; • с р а в н е н и я двух деревьев Tj и Т2; • о п р е д е л е н и я м а к с и м а л ь н о й высоты дерева; • п р о в е р к и н а л и ч и я в дереве х о т я бы двух о д и н а к о в ы х элементов. 2. Д а н текст (в текстовом файле). Определить частоту вхожден и я к а ж д о г о из слов в текст.
3.
4. 5.
6.
П р и м е ч а н и е Тип элемента в узле дерева String, кроме того, необходимо ввести счетчик числа повторений каждого слова. В з а д а ч е 2 о п р е д е л и т ь количество в е р ш и н дерева, содержащ и х слова: • н а ч и н а ю щ и е с я н а одну и ту ж е букву; • являющиеся палиндромами; • с о д е р ж а щ и е все г л а с н ы е б у к в ы латинского а л ф а в и т а . Н а п и с а т ь процедуру у д а л е н и я из созданного двоичного дерева п о и с к а всех о т р и ц а т е л ь н ы х элементов. Разработать ф у н к ц и и , возвращающие значение указателя на второй м и н и м а л ь н ы й (максимальный) элемент в двоичном дереве п о и с к а . Двоичное дерево считается идеально сбалансированным, если для к а ж д о й его вершины количество вершин в левом и правом
397 Ч а с т ь четвертая
поддеревьях различается не более чем на 1. Написать функцию проверки идеальной сбалансированности двоичного дерева. 7. В описании в е р ш и н ы двоичного дерева поиска добавьте два п о л я типа Integer для ф и к с а ц и и высот (глубин) левого и правого поддеревьев. Разработайте процедуры (вставки, удален и я элемента) работы с таким деревом. Измените два поля на одно, в котором фиксируется разность высот правого и левого поддеревьев. Проведите модификацию программы для этого случая. 8. В файле даны N целых чисел в двоичной системе счисления (М бит каждое). Построить двоичное дерево, в котором числам соответствуют листья дерева, а путь по дереву определяет значение информационного поля этого листа. 9. В текстовом файле записаны целые числа. Построить двоичное дерево, элементами которого я в л я ю т с я числа из файла. Н а п и с а т ь процедуру: • определяющую количество нечетных чисел: • в ы ч и с л я ю щ у ю сумму элементов дерева, к р а т н ы х 3; • определяющую число вершин дерева на к а ж д о м уровне; • число вершин в правом и левом поддеревьях: 10. Построить двоичное дерево из букв строки и написать процедуру:
• определения количества повторяющихся букв в дереве; • определения, к а к и х букв больше — гласных и л и согласных; • вывода самого правого элемента левого поддерева; • вывода самого левого элемента правого поддерева.
Динамические структуры дачных
399
Занятие № 31. Сбалансированные деревья • основные понятия; • операции по восстановлению балансировки при вставке и удалении элемента в АШГ-дерево; • Б-деревья, вставка элемента; • выполнение самостоятельной работы. Основные понятия. Двоичное дерево считается идеально сбалансированным, если для каждой его вершины количество вершин в левом и правом поддеревьях различается не более чем на 1. Д л я одних и тех же данных, например целых чисел от 1 до N (в зависимости от порядка их поступления на обработку), структура двоичного дерева поиска будет различной. Возможны варианты как от идеально сбалансированного дерева, так и простого линейного списка, когда или все левые, или все правые ссылки узлов равны Nil. Реализация операций восстановления идеальной сбалансированности при случайной вставке или удалении элемента из двоичного дерева поиска, разумеется, возможна, однако она не рассматривается в учебной литературе из-за ее сложности. Изменим определение сбалансированности. Эта трактовка понятия сбалансированности предложена Г. М. Адельсоном-Вельским и Е. М. Ландисом в 1962 году. Дерево является сбалансированным тогда и только тогда, когда для каждого узла высота его двух поддеревьев различается не более, чем на 1. Деревья, удовлетворяющие этому условию, называют ABJIдеревьями. Введем в описание каждого узла поле bal, значение которого определяет разность высот его правого и левого поддеревьев. На рисунке в узлах указано значение bal. Дерево слева не удовлетворяет условию АБЛ-сбалансированности, справа — удовлетворяет. Ни то, ни другое дерево не удовлетворяет условию идеальной сбалансированности.
400
Часть четвертая
Рассмотрим операции, в ы п о л н я е м ы е по восстановлению бал а н с и р о в к и АВЛ-дерева. К а ж д ы й тип и з м е н е н и я рассмотрим на определенном наборе исходных д а н н ы х . Так проще. Отслеж и в а н и е изменений на с л у ч а й н ы х ф а й л а х , когда выполняются р а з л и ч н ы е т и п ы балансировок, затруднительно.
Пусть входной файл имеет вид: 8 6 1 0 5 7 9 1 3 3 1 . До вставки 1 балансировка дерева не нарушалась, ибо элементы вставлялись в той очередности, в которой они записаны во входном файле. Мы «находимся» в левом поддереве, и увеличивается (по высоте) левое поддерево. Выполняем «малый п р а в ы й поворот» для восстановления балансировки дерева. Его суть показана на следующем рисунке. 6 га
в -1
[3] У
I »
После действий, выделенных ж и р н ы м и л и н и я м и , в той последовательности, которая указана в квадратных скобках, из левого фрагмента дерева получается правый, из не сбалансированного фрагмента — сбалансированный. Значения указателей
Динамические структуры дачных
401
t vi v о п р е д е л е н ы до в ы п о л н е н и я о п и с а н н ы х действий, и их изм е н е н и я п е р е д а ю т с я обратно в в ы з ы в а ю щ у ю л о г и к у . П е р в ы й в а р и а н т п р о г р а м м н о г о к о д а малого правого поворота имеет вид: Procedure Turn_right_small(Var t,v:pt); Begin tЛ. left:=r".right;I *Первое действие. *) v".right:=t;('Второе действие. ') t".bal:=0/ t:=v;('Третье действие. *) End; А н а л о г и ч н ы й тип поворота м о ж е т в о з н и к н у т ь и п р и удален и и э л е м е н т а дерева. П у с т ь у нас есть фрагмент дерева, изображ е н н ы й н а с л е д у ю щ е м р и с у н к е (левая часть), и у д а л я е т с я элемент 7. И з м е н е н н о е дерево приведено н а правой части р и с у н к а . И с п о л ь з у е т с я м а л ы й п р а в ы й поворот. Отличие в том, что измен я е т с я л о г и к а работы с полем bal. Проверьте ее на следующем ф р а г м е н т е программного кода.
Procedure Turn_right_small (Var t,v:pt;Var h: Boolean) ; Begin WriteLn ( 'Right small ';t".key); t".left:=v".right;{'Первое действие. *} v". right:-t;{'Второе действие.') I f v".bal=0 Then Begin t*.bal:=-1.bal:=1; h:=False; End Else Begin tA.bal:=0;v".bal;=0;End; t:=v;{'Третье действие.'} End;
402
Часть четвертая
Это окончательный вариант процедуры малого правого поворота, используемой в дальнейшем. Необходимо осознать, что является входными параметрами процедуры, к к а к и м результатам она приводит, и попытаться сформулировать условия, при которых она вызывается.
Первый вариант малого левого поворота осознаем на данных: 8 6 1 0 5 7 9 1 3 1 5 1 7 . После вставки элемента 17 требуется балансировка. Выполняем очевидные действия, показанные на следующем рисунке.
Динамические структуры дачных 402 И п е р в ы й в а р и а н т п р о г р а м м н о г о кода, р е а л и з у ю щ е г о м а л ы й л е в ы й поворот: Procedure Turn_left_small(Var t,v:pt); Begin t".right:=v".left;{*Первое действие. '} v".left:=t;{*Второе действие. *} t".bal:=0; t:=v;{'Третье действие. *} End; А н а л о г и ч н а я с и т у а ц и я в о з н и к а е т и п р и у д а л е н и и элементов из дерева. П р и м е р . П у с т ь у д а л я е т с я элемент 12 и з дерева, изобр а ж е н н о г о на л е в о й ч а с т и р и с у н к а . Н а р у ш а е т с я балансировка. В ы п о л н я е м м а л ы й л е в ы й поворот. Вид дерева после поворота на п р а в о й ч а с т и р и с у н к а . Е с л и работа с п о л я м и ссылок совпадает с р а з о б р а н н ы м и в ы ш е , то и з м е н е н и е п о л я bal осуществляется чуть иначе.
P r o c e d u r e T u r n _ l e f t _ s m a l l (Var t,v:pt;Var h:Boolean) ; Begin WriteLn ( 'Left small \ t ".key); t" . right:=v*.left;(*Первое действие. *) v". left:=t;{*Второе действие. *} I f v".bal = 0 Then Begin t".bal:=1;v*.bal:=-1; h:=False;End Else Begin t".bal:=0,т".bal:=0;End; t:=v;('Третье действие.'} End; Это не единственные с л у ч а и н а р у ш е н и я балансировки дерева. И х е щ е два: «большой правый» и «большой левый» поворот ы . И з м е н и м входной ф а й л : 10 6 14 5 8 12 16 2 4. После вставк и элемента 4 балансировка дерева нарушается. Оно имеет вид, п о к а з а н н ы й на очередном рисунке.
М а л ы й п р а в ы й поворот п р и м е н и т ь н е л ь з я — н а р у ш а е т с я п р и н ц и п д е р е в а п о и с к а . В ы п о л н я е м ы е д е й с т в и я по восстановл е н и ю б а л а н с и р о в к и п о к а з а н ы на с л е д у ю щ е м р и с у н к е .
с м ы с л е н н ы . Однако надо и м е т ь в в и д у тот ф а к т , что рассматрив а е м ы е т и п ы поворотов в ы п о л н я ю т с я над л ю б ы м фрагментом дерева, в к о т о р о м н а р у ш е н а б а л а н с и р о в к а . Узел со з н а ч е н и е м 4 в общем случае может иметь потомков. Procedure Turn_right_big Var w:pt; Begin WriteLn ('Right big w:=v".right;
(Var
',t".key);
t,v:pt);
Динамические структуры дачных
405
v* . right:=ы".left;{*Первое действие. *} к" . left:=v;{*Второе действие. *} t" . left:=w".right;{'Третье действие. *} w" . right:=t;{'Четвертое действие. *} I f w".bal=-l Then f.bal:=l Else t".bal:=0; {'Приведите примеры для каждого случая изменения значения поля bal. *} I f w".bal=l Then v".bal:=-l Else v".bal:=0; t:=w;{*Пятое действие. *} w*.bal:=0; End; Е с л и работа с п о л я м и с с ы л о к достаточно очевидна, то о дейс т в и я х с п о л е м bal этого не с к а ж е ш ь . В процедуре приведен код, п о в т о р я ю щ и й л о г и к у действий с п о л е м bal в тех к н и г а х , где рассматриваются АВЛ'-деревья в полном объеме, их очень не много. Это к н и г и Н. Вирта (Алгоритмы + Структуры данных = Программы. М.: Мир, 1985) и С. Д. Кондратьевой (Введение в структуры данных. М.: Издательство Ml ТУ им. Н. Э. Баумана, 2000). Вспомним наше «рабочее» определение программы — «а может быть, это не так, а может быть, это не верно»? Может быть, верен с л е д у ю щ и й п р о г р а м м н ы й код: I f ы". bal=-l I f w".bal=l
Then v".bal:=l Then f.bal:=-l
Else Else
v".bal:=0; t".bal:=0;
Н а х о ж д е н и е и с т и н ы — вопрос о т л а д к и п р о г р а м м ы . Требуетс я н а й т и те в о з м о ж н ы е входные данные, создать т а к у ю ситуац и ю в работе п р о г р а м м ы , п р и которой м о ж н о говорить об ист и н н о с т и того и л и другого в а р и а н т а программного кода. А это у ж е « в ы с ш и й класс (пилотаж)» в освоении материала.
Последний тип поворота рассмотрим на следующих д а н н ы х : 10 6 14 5 8 12 16 20 18. После вставки 18 н а р у ш а е т с я баланси-
Часть четвертая
406
р о в к а д е р е в а . Д л я ее в о с с т а н о в л е н и я н е о б х о д и м о д е й с т в и я , п о к а з а н н ы е на с л е д у ю щ е м р и с у н к е .
Procedure Turn_left_big(Var t,v:pt); Var w: pt; Begin Wri teLn ( 1 Left big \t".key); w : . l e f t ; vA.left:=w".right;{*Первое действие. wn.right:=v;{*Второе действие. *} tright:=w".left;(*Третье действие. w".left:=t;("Четвертое действие. *} I f ыл.Ьа1=-1 Then t".bal:=l Else I f ыл.Ьа1=1 Then v".bal:=-l Else t:=w;l"Пятое действие. *} w".bal:=0; End;
выполнить
*} *} t".bal:=0; v".bal:=0;
О т к р ы т ы й вопрос — работа с п о л е м bal. П р е д л о ж и м другой в а р и а н т и з к н и г и Н . В и р т а [], в ы б о р верного — В а ш а з а д а ч а . If .bal=-l Then v".bal:=l Else v".bal:=0; I f w".bal=l Then f.bal:=-l Else t".bal:=0; И с х о д н ы й м а т е р и а л д л я д а л ь н е й ш е г о р а с с м о т р е н и я есть. С е й ч а с т р е б у е т с я осознать — к а к о с у щ е с т в л я т ь в ы х о д на эти п р о ц е д у р ы , к а к в ы я в л я т ь э т и с и т у а ц и и п р и работе с деревом
Динамические структуры дачных
407
(вставка, у д а л е н и е элементов). О б щ а я л о г и к а процедур включен и я , и с к л ю ч е н и я элементов аналогичны соответствующим процедурам работы с о б ы ч н ы м д в о и ч н ы м деревом п о и с к а за искл ю ч е н и е м одной д е т а л и . В л о г и ч е с к о й переменной Л (значение True) ф и к с и р у е т с я ф а к т в с т а в к и (удаления) элемента в (из) дерево (а), и это з н а ч е н и е передается в в ы з ы в а ю щ у ю л о г и к у . Оно, а т а к ж е з н а ч е н и е п о л я bal узла дерева с л у ж а т основанием д л я п р и н я т и я р е ш е н и я о том, требуется л и б а л а н с и р о в к а относительно р а с с м а т р и в а е м о г о узла. Т а к к а к процедуры поворотов общ и е д л я р е ж и м о в в с т а в к и и у д а л е н и я элементов, то вводится еще одна д о п о л н и т е л ь н а я л о г и ч е с к а я п е р е м е н н а я . Далее с нез н а ч и т е л ь н ы м и и з м е н е н и я м и (они в ы д е л е н ы « ж и р н ы м » курсивом) п о в т о р и м п р о ц е д у р ы работы с о б ы ч н ы м д в о и ч н ы м деревом поиска. Procedure Search (х:Integer; Var t:pt; Var h:boolean); Begin I f t=Nil Then Begin New(t) ; t".key:=x;t'.bal:=0; t".left:=Nil; t".right:=Nil; h:=true;{*Элемент вставлен. *} End Else I f x<t".key Then Begin I*Идем в л е в о е поддерево.*) Search (х, tЛ . l e f t , h) ; Bal_left(t,h,False);{*Проверяем необходимость балансировки при вставке элемента в левое поддерево. Логическая константа False говорит о том, что обращение идет в режиме вставки End Else I f x>t".key Then Begin{*Идем в правое поддерево. *} Search (х, t" . right,h) ; Bal_right (t,h,False) ; { *Проверяем необходимость балансировки правого поддерева. Логическая константа False говорит о том, что обращение идет в режиме вставки элемента. *) End
Часть четвертая
408
Else h:=False;("Элемент (ключ) повторяется. Проверка нарушения балансировки не требуется. "} End; Процедура удаления. Procedure Del^tree(х:integer; Var t:pt; Var h:Boolean); Var v:p t; Procedure Del_el(Var w:pt); Begin I f w" . rightONil Then Begin Del_el (w".right) ; Bal_left(w,h,True);("Выполняем после удаления балансировку левого поддерева. Константа True говорит о том, что балансировка происходит в режиме удаления.") End Else Begin vЛ.key:=w".key; v: =w ; w:=w".left; h :=truerEnd,: End; Begin i f t=Nil then WriteLn ( 'There is no such element here') else i f x<t".key then begin Del_tree(x,t".left,h) ; Bal_right(t,h,True);{"Удаляем из левого поддерева, выполняем правую балансировку. В этом отличие от режима вставки. Ключевой момент для осознания работы с АВЛ-деревьями. *) end else i f x>t".key then begin Del_tree (x,t".right,h); Bal_left(t,h,True);{"Удаляем из правого поддерева. Делаем левую балансировку.") end else begin
Динамические структуры дачных 408 v:=t; i f v*. right=Nil then begin t:=v".left; h:=true; end else i f v". left=Nil then begin t:=v".right; h:=true; end else begin Del_el ( v ^ . l e f t ) ; Bal_right (t,h, True) ; end; end; End; П р и м е ч а н и е В процедуре нет оператора Dispose. Найдите возможность его «безболезненной» вставки. Л о г и к а почти детализирована. Есть процедуры поворотов (это проделано к а к бы по технологии «снизу — вверх»), есть общ а я часть (это проделано к а к бы по технологии «сверху — вниз»). Остался к л ю ч е в о й фрагмент, с в я з ы в а ю щ и й и то, и другое — п р о ц е д у р ы Bal_right и BalJLeft. Но и он детализирован ч а с т и ч н о , ибо строго определены к а к входные, так и выходные п а р а м е т р ы этих процедур. Procedure Bal_left(Var t:pt; Var h:Boolean; Del:Boolean); Var v:pt; Begin I f h Then Case t".bal Of {*Необходимость балансировки анализируется относительно узла t " . 1 :Begin I f Not Del Then h:=false; (*B режиме вставки элемента правое поддерево имело большую высоту. Левое поддерево увеличилось на единицу. Балансировка на требуется. *} t".bal:=0; End; О : Begin I f Del Then h: =False;{ "Удалили элемент из правого поддерева. Балансировка не требуется. Критерий АВЛ-дерева сохраняется. *}
410
Часть четвертая t" . bal:=-1;{"Высота левого поддерева увеличилась. *} End; -1 .-Begin ( "Мы в левом поддереве {в режиме вставки) и увеличилась высота левого поддерева. Требуется балансировка. Аналогичная ситуация возникает при удалении элемента из правого поддерева — высота левого поддерева увеличивается."} v:=t".left;("Левый потомок рассматриваемого узла. *} I f (v".bal=-l) Or ((v".bal=0) And Del) Then Turn_right_small (t,v,h) Else Turn_right_big(t,v);{"Если высота левого поддерева левого потомка была больше высоты правого поддерева левого потомка, то требуется малый правый поворот, иначе — большой правый поворот. Малый правый поворот в режиме удаления требуется и в том случае, если левый потомок узла t л имеет поддеревья равной высоты."} End; End; End;
По а н а л о г и и п и ш е т с я и п р о ц е д у р а в о с с т а н о в л е н и я балансир о в к и правого поддерева. Procedure Bal_right (Var t:pt; Var Del:Boolean); Var v:pt; Begin I f h Then Case tn.bal Of -1:Begin I f Del Then h:=false; t".bal:=0; End; 0: Begin I f Del Then h:=False; t".bal:=1; End; 1:Begin v:=t".right;("Правый потомок
h:Boolean;
узла.")
Динамические структуры дачных 410 I f (v*.bal=l) Or ((v*. bal=0) And Del) Then Turn_left_small(t,v,h)(*Если высота правого поддерева правого потомка была больше высоты левого поддерева правого потомка, то требуется малый левый поворот, иначе — большой левой поворот. В режиме удаления аналогичные действия выполняются и при равных высотах дерева. *) Else Turn_left_big(t, v) ; End; End; End; Д л я справки приведем оставшуюся часть программного кода, связанного с о р г а н и з а ц и е й ввода исходных д а н н ы х и вывода результатов м а н и п у л я ц и й с ABJI-деревом. Procedure Imt (Var t:pt); Var x:Integer; h:Boolean; Begin Assign (Output, ' avl_ans. t x t ' ) ;ReWrite (Output) l'Результат обработки выводится в файл. Это удобнее для анализа, чем вывод на экран, ибо предыстория изменения структуры дерева. *} ClrScr; Assign (Input, ' 1 . t x t ' ) ; ReSet (Input) ; While Not EoLn Do Begin Read (x) ; Search (x,t,h) ; WriteLn; WriteLn ('After input: ' ,x) ; PrintTree (t,0) ; End; Close (Input) ; End;
; есть
У д а л я е м ы е элементы дерева берутся и з другого файла. Procedure Imt_Del (Var t:pt); Var h:Boolean; x:Integer; Begin Assign(Input,'1 del.txt');ReSetflnputj; While Not Eof Do Begin Read (x) ; Del_ Tree(x,t,h); WriteLn; WriteLn (' After delete: ' ,x) ;
412
Ч а с т ь четвертая
PrmtTree ( t , 0) ; End; Close (Input); Close(Output); End; И наконец, основная программа. Begin head:=Nil; Ini t_Del (head)
Init ;End.
(head)
; P r m t T r e e (head,
0) ;
В о з н и к а е т вполне естественный вопрос, а не упростится ли л о г и к а , е с л и в к а ж д о м у з л е в в е с т и а д р е с с в я з и к р о д и т е л ю ? Исс л е д у й т е его. Н а п и ш и т е в а р и а н т п р о г р а м м ы д л я этого с л у ч а я . Д р у г и м т и п о м с б а л а н с и р о в а н н ы х д е р е в ь е в я в л я ю т с я Б-дере• въя (B-tree). О н и п р е д л о ж е н ы Р . Б э й е р о м в 1 9 7 2 г о д у и относятся к классу сильно в е т в я щ и х с я деревьев. Отличие заключается в том, что в у з л е х р а н и т с я не одно з н а ч е н и е к л ю ч а , а несколько и, с о о т в е т с т в е н н о , это к о л и ч е с т в о к л ю ч е й о п р е д е л я е т к о л и ч е с т во п о т о м к о м у з л а . П р и э т о м у з е л у ж е н а з ы в а е т с я с т р а н и ц е й . Б-дерево о п р е д е л я е т с я х а р а к т е р и с т и к о й , н а з ы в а е м о й п о р я д к о м и о б о з н а ч а е м о й N (Б-дерево п о р я д к а Л т ). Определение Б-дерева: • к а ж д а я с т р а н и ц а с о д е р ж и т н е более 2*N э л е м е н т о в ( к л ю чей); • к а ж д а я страница, кроме корневой, содержит не менее N элементов; • к а ж д а я с т р а н и ц а я в л я е т с я л и б о л и с т о м , то е с т ь н е и м е е т п о т о м к о в , л и б о и м е е т т+1 п о т о м к о в , г д е т — ч и с л о н а х о д я щ и х с я в ней ключей; • все л и с т ь я н а х о д я т с я н а о д н о м у р о в н е .
Динамические структуры дачных
413
Д л я в х о д н ы х д а н н ы х : 45, 55, 65, 75, 85, 10, 20, 70, 90, 100, 68, 80, 95, 105, 110, 72, 15 (на обработку поступают именно в т а к о й очередности) Б-дерево п о р я д к а 2 имеет вид, представленн ы й на р и с у н к е . П о своему виду это дерево п о и с к а , т о л ь к о более с л о ж н о е по с т р у к т у р е узла. О п и с а н и е у з л а (страницы), с о д е р ж а щ е й т . к л ю ч е й , д о л ж н о и м е т ь место к а к д л я х р а н е н и я з н а ч е н и й к л ю ч е й , т а к и т+1 с с ы л о к н а потомков. Все это «навязывает» с л е д у ю щ и е т и п ы д а н н ы х программного р е ш е н и я задачи работы с -Б-деревом (пока р а с с м а т р и в а е т с я т о л ь к о обход и в с т а в к а элемента в 5-дерево). Program В_Тгее; Const N=2;("Порядок дерева. "} NMax=2"N;("Максимальное количество ключей."} Typept="page;("Указатель на узел (страницу).*! index=0. .NMax; item=Record key:Integer; p:pt; count:Integer ; End;l'Значение ключа; указатель на правого потомка, если он есть; Счетчик числа повторений ключа (предполагаем, что в файле с исходными данными есть повторяющиеся ключи). *} page=Record(*0писание страницы. *} m:index;("Количество ключей."} рО :pt;("Указатель на самого левого потомка."} A:Array[1..NMax] Of item;{"Массив, каждому ключу страницы соответствует элемент массива. *} End; Var root:pt;("Единственная (явная, если так можно выразиться) переменная нашей программы — указатель на корень дерева."} Р а с с м о т р и м в с т а в к у нового элемента в Б-дерево. Два случая из вышеприведенного примера. 85
65
45, 55, 65, 7 5 45, 55
75, 85
Вставка элемента 85 приводит к расщеплению одного узла — к о р н я на два и к образованию нового узла (корня). Значение
414
Часть четвертая
к л ю ч а в н о в о м у з л е р а в н о с р е д н е м у и з з н а ч е н и й к л ю ч е й расщепляемого узла. В с т а в к а э л е м е н т а 1 0 0 п р и в о д и т к р а с щ е п л е н и ю у з л а и добавлению среднего элемента в узел родителя.
Двоичный поиск элемента в упорядоченном массиве целых ч и с е л ( п о л е key з а п и с е й т и п а item) о с у щ е с т в л я е т с я к а к обычн о . Он о ф о р м л е н в в и д е ф у н к ц и и , з н а ч е н и е к о т о р о й о п р е д е л я е т результат поиска. Если элемент х найден, то значение ф у н к ц и и у к а з ы в а е т на то место в массиве записей, где он находится. Function Find (t:pt;x:Integer):index;{*t — указатель на узел, в массиве записей которого осуществляется поиск. *) Var Begin i : =1;
i,j,к:Integer;
j:=tЛ.m;
While i<j Do Begin к : = (i +j+l) Div 2; I f x<t".A[k].key Then j:=k-l Else I f x>t".A[k].key Then i:=k+l Else Begin i:=k; j:=k;End; End; I f i>t".m Then Find:=t".m Else I f i<l Then Find:=0 Else I f t".A[i] . key>x Then Else Find:=i; End;
Find:=i-1
С а м ы й п р о с т о й т и п в с т а в к и — э т о д о б а в л е н и е э л е м е н т а в сущ е с т в у ю щ и й узел, когда количество элементов в последнем не п р е в ы ш а е т м а к с и м а л ь н о г о з н а ч е н и я Nmax. В х о д н ы м и параметрами логики являются:
Динамические структуры дачных 414 • t — у к а з а т е л ь на у з е л , в к о т о р ы й в с т а в л я е т с я элемент; • г + 1 — место в м а с с и в е з а п и с е й у з л а , на которое вставляе т с я элемент; • и — значение элемента. Р е з у л ь т а т о м р а б о т ы я в л я е т с я не т о л ь к о и з м е н е н и е узла Г , но и и з м е н е н и е з н а ч е н и я п е р е м е н н о й h (вспомните АВЛ-дере въя) — с т р у к т у р а дерева ( к а к нечто целого) с о х р а н и л а с ь , измен е н и е к о с н у л о с ь т о л ь к о одного узла. P r o c e d u r e Ins_Simple (t :pt; Var h .-Boolean ;u : item ; r:Index); Var 1: IntegersBegin With t " Do Begin m:=m+l;{"Изменяем количество элементов в узле. *) For i:=m DownTo r+2 Do A[1]:=A[l-l];{"Сдвигаем элементы в массиве записей. *} A[r+1]:=u;{"Вставляем элемент."} End; h:=False;{"Структура дерева не изменяется.") End; А ч е м о т л и ч а е т с я поиск по В-дереву от п о и с к а по бинарному дереву? В п р и н ц и п е т о л ь к о д е т а л я м и перехода к у з л а м нижнего у р о в н я , к п о т о м к а м и тем, ч т о п р и выходе и з поиска, то есть п р и возврате к р о д и т е л я м , передается не т о л ь к о значение перем е н н о й Л, но и з а п и с ь и, в с т а в л я е м а я в один и з узлов предков. Д е й с т в и т е л ь н о , если родителю передана запись д л я вставки от п о т о м к а , а с а м родитель имеет м а к с и м а л ь н о е количество ключей, то ф о р м и р у е т с я н о в а я запись, к о т о р а я передается д а л ь ш е вверх по дереву. И т а к до к о р н я , если это необходимо. Procedure Search(t:pt; х:Integer; Var h:Boolean; Var и .-item) ; Var i:Integer; v:item; Begin I f t=Nil Then Begin{"ДОШЛИ ДО листа."} h:=True; With u Do Begin {"Формируем запись. "} key:=x ; count:=l; p:=Nil; EndsEnd Else Begin i :=Fmd(t,x) ;
416
Часть четвертая I f tл.A[l].кеу=х Then Begin{"Находим ключ х в узле t". Он найден, есть совпадение. Данные повторные.*} Incft".A[г].count);{*Изменяем значение счетчика числа повторений. "} h:=False;{"Структура дерева не изменяется.*) End Else Begin{"Элемент х не найден. Продолжаем поиск. Значение переменной i определяет адрес потомка. *} I f i<l Then Search(t".pO,x,h,v) Else Search (t".A[1].p,x,h,v); {*Продолжение поиска.*) {*3начение переменной h определяет необходимость вставки элемента в дерево. *) I f h Then I f t".m<Nmax Then Ins_Simple ( t , r , h , v , l ) {* Простой случай вставки элемента в узел. *) Else Ins_Div(t,h,u,v,1);{*Сложный случай вставки, требующий деления узла, его разбивки.*) End; End;
П р о д о л ж и м рассмотрение. Логика сложного случая вставки элемента в дерево: Procedure v:item; Var
Ins_Div(t:pt; i:Integer;); w.pt; J: index;
Var h:Boolean
;Var
u:item;
Begin New(w);{"Создается новый узел. *} I f K=N Then Begin I f i=N Then u:=v Else Div_Left(t,u,v,i) {"Содержимое нового узла формируется из левой части ключей и ссылок узла Г . "} For j:=l То N Do w".A[j] :=t".A[j+N] ; End Else Div_Right (t,w,u,v, i ) ; {"Содержимое нового узла формируется из правой части ключей и ссылок узла t"."} t*.m:=N;{"Изменяем количество ключей в узлах t " И W. "}
Динамические структуры дачных w*-. m:=N; W.pO:=u.p; u,p:=w;{*Запись дереву вплоть
u - передается до корня. *}
417
вверх
по
End; П р и м е ч а н и е Если этот фрагмент логики не очень понятен при первом чтении, то сделайте шаг вперед — рассмотрите процедуры Dw_Left и Div_ Right, а затем вернитесь обратно. Procedure Div_Right f t , w:pt;Var u:item;v:item;1: index); Var j:Integer; Begin l:=1-N;{*Ключ x принадлежал правой части массива ключей узла t". Работаем только с зтой частью. *) u : = t " . A [ N + 1 ] ; { ' З а п о м и н а е м з а п и с ь на месте N+1, ибо она пойдет дальше вверх по дереву. *) For j:=l То 1-1 Do w".A[j] :=t*.A[j+N+l] ; {"Переписываем эту часть узла t " в новый узел w". На месте i должна быть запись и, полученная этой процедурой. *} w".A[l]:=v; For у: =1+1 То N Do w". A [ j ] : =tЛ. Л [ j +N] ; I"Дописываем оставшуюся часть. *} End; Следующая процедура была бы чуть сложнее, если логика обработки элемента (вставляемого) на границе не была «вынесена» в процедуру Ins Div. В этом случае элемент уже является значен и е м к л ю ч а записи и. Необходимо в переписать N записей из Г и на этом закончить обработку на этом уровне дерева. Procedure Div_Left(t:pt;Var и:item;v:item;i: index) ; Begin и:=t".A[N] ;{"Запоминаем запись, которая пойдет к родителю. *} For j ; =N DownTo 1+2 Do t л . A [ j ] :=tл. A[j-1 ] ; { "Сдвигаем элементы массива. "} t". A [i + 1]:=v;{"Вставляем запись, соответствующую ключу х. "} End;
Часть четвертая
418
И т а к , о с н о в н а я ч а с т ь р а б о т ы (разбора) с д е л а н а . Остались п о ч т и п р и в ы ч н ы е д е т а л и : о с н о в н а я п р о г р а м м а ; вспомогательн а я п р о ц е д у р а в ы в о д а д е р е в а и , к о н е ч н о , п р о ц е д у р а организац и и ввода д а н н ы х и з ф а й л а . Д л я вывода элементов В-дерева исп о л ь з у е м его о б х о д « с в е р х у — в н и з » ( р а з о б р а н р а н е е ) . Е г о м о д и ф и к а ц и я с в я з а н а т о л ь к о с и з м е н е н и я м и с т р у к т у р ы дерева. Procedure PrmtTree (р :pt ; level: Integer) ; Var i:Integer; Begin I f pNil Then With p" Do Begin For l: =1 To level Do Write (' V/ For l: =1 To m Do Write(A[l].key:4); WriteLn; PrintTree(pO,level+l) ; For i:=l To m Do PrintTree(A[l].p,level+1); End; End; Основная программа. Begin Assign(Input,'Input.Txt');ReSet(Input); Assign(Output,'Output.Txt');ReWrite(Output); root:=Nil; Solve(root); Close(Output);Close(Input); End. О с н о в н а я п р о ц е д у р а в к л ю ч а е т ввод д а н н ы х и з ф а й л а , измен е н и й к о р н я дерева, е с л и это н е о б х о д и м о , и в ы в о д дерева после каждого изменения в результирующий файл. Procedure Solve(Var root:pt); Var q:pt; и: Item; i,x,NI np:Integer ; Begin ReadLn(NInp);(*Количество э л е м е н т о в в файле Input.Txt. *) For i:=l To NInp Do Begin Read(x);{*Считываем элемент. *) Search(x,root,h,u);("Вставляем в дерево.*) I f h Then Begin{*Необходимо изменить корень дерева. *}
Динамические структуры дачных q ^rootsNew (root); With rootл Do Begin m:=l; pO:=q; A[l]:=u;(*B и запись, которая содержит необходимое значение указателя. *) EndsEnd; PrintTree (root,l) ; ( "Выводим дерево. *) WriteLn; EndsEnd;
419
и
Задания для самостоятельной работы 1. В ф а й л о в о й системе к а т а л о г ф а й л о в организован в виде сбалансированного дерева. К а ж д ы й узел обозначает файл, содерж а щ и й и м я и а т р и б у т ы ф а й л а , в том ч и с л е и дату последнего о б р а щ е н и я к ф а й л у . Н а п и с а т ь п р о г р а м м у , к о т о р а я обходит дерево и у д а л я е т все ф а й л ы , последнее обращение к которым происходило до определенной даты, при этом сбалансированность дерева д о л ж н а с о х р а н я т ь с я . 2. Н а й т и с б а л а н с и р о в а н н о е А В Л - д е р е в о и к о р о т к у ю последоват е л ь н о с т ь у д а л е н и й , п р и к о т о р ы х и с п о л ь з у ю т с я все четыре в и д а б а л а н с и р о в к и х о т я бы по одному разу. 3. Р а з р а б о т а т ь л о г и к у и программу удаления элементов и з Б-дерева. 4. 2 - 3 деревом называется дерево, в котором к а ж д ы й узел, не явл я ю щ и й с я листом, имеет двух и л и трех сыновей, а длины всех путей и з к о р н я в листья одинаковы. Пример дерева приведен на рисунке. Разработать логику и программу работы (вставку, у д а л е н и е элементов) с 2 - 3 деревом.
420
Часть четвертая
5. Д в о и ч н о е д е р е в о п о и с к а н а з ы в а е т с я к р а с н о - ч е р н ы м д е р е в о м , если оно обладает с л е д у ю щ и м и свойствами: • • D •
к а ж д а я вершина либо красная, либо черная; к а ж д ы й л и с т (Nil) — ч е р н ы й ; е с л и в е р ш и н а к р а с н а я , оба ее п о т о м к а ч е р н ы е ; в с е п у т и , и д у щ и е в н и з от к о р н я к л и с т ь я м , с о д е р ж а т одинаковое количество черных вершин.
Описание узла красно-черного дерева имеет вид: Type
pt="Node; Node=Record color:-1..1;{*Цвет +1 — черный*} key: Integer;{*3начение left,right,parent:pt;(*parent родителя. *} End;
вершины,
-1
—
красный,
ключа.*} - ссылка
на
П р и м е р красно-черного дерева.
Разработать
программы вставки и удаления элементов
в
Заключение
В первой части учебника рассмотрены основные операторы системы программирования Турбо Паскаль. Предметной областью для выбора задач является «Теория чисел». Знание этого раздела математики — необходимая компонента математической культуры специалиста по информатике. В материалах для чтения приводится краткий обзор развития технологий программирования. Цель — дать представления о сложнейшей отрасли деятельности — программировании и том месте в этом развитии, которому посвящен учебник. Во второй части учебника начато первое знакомство с одномерными массивами. Более детально работу с массивами предполагается рассмотреть в третьей части. Затем изучаются процедуры и функции, методы написания рекурсивных программ. Таким образом создаются предпосылки для формирования структурного стиля мышления. Весь дальнейший материал по символьному, строковому, вещественному типам данных и текстовым ф а й л а м написан с использованием этих элементов с т р у к т у р и з а ц и и путем последовательного уточнения задачи по технологии «сверху вниз». Предметной областью для выбора задач по-прежнему является «Теория чисел», а также «Комбинаторика» и ряд других разделов математики. В материалах для чтения приводятся сведения по структуре ЭВМ, начальные факты из «Теории вероятностей», а также рассматриваются традиционные темы «Алгоритмы», «Информация» и «Кодирование». Третья часть учебника, в основном, посвящена работе с массивами. При этом в полной мере используется аппарат процедур и функций, рассмотренный во второй части. Достаточно подробно рассмотрены задачи сортировки и поиска элементов массива. Эти алгоритмы — лучшая база для освоения темы «массивы». На занятии по технике работы с двумерными массивами используются задачи, являющиеся составными элементами задач на перебор с возвратом. Занятие по комбинированному типу данных требуется для изучения проблематики четвертой части учебника «динамические структуры данных». В материале для чтения к первому занятию приводятся основные понятия по схемам оценки времени работы алгоритмов. Технология обучения работе с массивами дается в учебной литературе обычно в очень компактном виде, поэтому рекомен-
422
Основы программирования
довать что-либо затруднительно. Причина — то ли материал считается простым, то ли он не достаточно оценен по своим дидактическим возможностям. Автор считает его одним из ключевых в системе обучения программированию. По методам же сортировки и поиска, наоборот, огромное количество публикаций. В любой книге по алгоритмам эти методы, так или иначе, рассматривают, и считается обязательным при этом ссылаться на классику. К классике, в первую очередь, относится энциклопедическое издание Д. Кнута (Кнут Д. Искусство программирования для ЭВМ. Сортировка и поиск. Т. 3. — М.:Мир, 1978. — 844 е.). Но если быть честным, то автор не любил работать с этой книгой и в 1978 году, не говоря уже о настоящем времени. Сверхтяжелый я з ы к написания книги и слово MIX вызывают легкий трепет и очередное откладывание этой трехтомной монографии в дальний угол. Затем по популярности идут книги А. Ахо (Ахо А., Хопкрофт Д ж . , Ульман Д ж . Построение и анализ вычислительных алгоритмов. — М.:Мир, 1979. — 536 е.), Э. Рейнгольда (Рейнгольд Э., Нивергельт Ю., Део Н. Комбинаторные алгоритмы. Теория и практика. — М.: Мир, 1980. — 476 с.) и не так давно появившиеся книга Т. Кормена (Кормен Т., Лейзерсон Ч . , Ривест Р. Алгоритмы. Построение и анализ. — М.: МЦНМО, 1999. — 960 е.), количество ссылок на которую еще «не набрало силу». Книги замечательные, каждая со своим взглядом (подходом) на изучаемый предмет. Ни одну из них нельзя назвать простой, они написаны по курсам, ч и т а е м ы м в классических зарубежных университетах. Возьмем, например, последнюю книгу, весьма объемную, но очень неоднозначную (последние главы, особенно по приближенным алгоритмам, видимо, написаны уже на последнем дыхании). После глав с математической преамбулой рассматривается сортировка с помощью кучи, использующая понятие дерева и т. д. То есть динамические структуры данных лежат в основе всего последующего изложения. Прекрасно. Но опыт работы показывает, что для того, чтобы «довести» школьника, студента до понимания динамических структур, необходимо съесть совместно «не один пуд соли». При подготовке занятия по комбинированному типу данных автор использовал одну из лучших книг своей библиотеки как по содержанию, так и по я з ы к у написания (Дагене В. А., Григас Г. К., Аугутис К. Ф. 100 задач по программированию. — М.: Просвещение, 1993. — 255 е.). Четвертая часть учебника посвящена динамическим структурам данных. При этом в полной мере используется аппарат
Заключение
процедур и функций, массивы, т. е. весь предыдущий материал. Материал трудный, но усилия окупаются. Учащиеся в полной мере начинают понимать сложнейший процесс отладки программы, проверки правильности ее работы при различных исходных данных. При написании этой части учебника автор чаще всего заглядывал в замечательную книгу человека, вклад которого в Computer Science, в образовательную информатику, по мнению автора, просто огромен. Это книга Никлауса Вирта (Вирт Н. Алгоритмы + Структуры данных = Программы. — М.: Мир, 1985. — 406 е.). В книге А. Ахо (Ахо А., Хопкрофт Дж., Ульман Дж. Построение и анализ вычислительных алгоритмов. — М.:Мир, 1979. — 536 с.) этот материал не полный и приведен в весьма своеобразном виде. Работать с ним очень трудно, и попытка изложить его в таком же стиле у автора не получилась. В не так давно появившейся и красиво изданной книге Т. Кормена (Кормен Т., Лейзерсон Ч., Ривест Р. Алгоритмы. Построение и анализ. — М.: МЦНМО, 1999. — 960 с.) нет, например, АВЛ-деревьев. Д л я дополнительной работы можно порекомендовать следующие книги. 1. О системах счисления и представлении чисел в памяти компьютера написано прекрасное учебное пособие. (Андреева Е., Фалина И. Информатика: Системы счисления и компьютерная арифметика. — М.: Лаборатория Базовых Знаний, 1999. — 256 с.) 2. О математической логике можно почитать во второй главе учебно-справочного издания А. И. Гусевой. (Гусева А. И. Учимся информатике: задачи и методы решения. — М.: «Диал о г — М И Ф И » , 1998. — 320 с.) 3. По теории чисел лучше классической книги И. М. Виноградова трудно что-либо найти. Но, конечно, читать ее достаточно сложно, если особенно у Вас не очень основательная дружба с математикой. (Виноградов И. М. Основы теории чисел. — М.: Наука, 1972. — 167 с.) 4. О профессии программиста с большим юмором написана книга А. Н. Венц. В ней Вы найдете и экскурсы в историю программирования. (Венц А. Профессия — программист. — Ростов н/Д: Изд-во «Феникс», 1999. — 384 с. 5. О технологиях программирования лучше всего читать в классической книге Г. Буча, настольной книге любого программиста. (Буч Г. Объектно-ориентированное проектирование с примерами применения: Пер. с англ. — М.: Конкорд, 1992. — 519 с.)
424
Основы п р о г р а м м и р о в а н и я
6. Много п о л е з н ы х п р и м е р о в из и с т о р и и п р о г р а м м и р о в а н и я м о ж н о н а й т и в у н и к а л ь н о й к н и г е компьютерного провидца (так его называют) Д. Васкевича. (Васкевич Д. Стратегия клиент/сервер. Руководство по в ы ж и в а н и ю д л я специалистов по р е о р г а н и з а ц и и б и з н е с а . — К и е в : « Д и а л е к т и к а » , 1996. — 384 с.) 7. Д л я первоначального знакомства с теорией вероятности лучш е б л е с т я щ е й к н и г и Б . В. Гнеденко и А. Я. Х и н ч и н а , выдерж а в ш е й м н о г о ч и с л е н н ы е и з д а н и я , в р я д ли что-либо можно н а й т и . (Гнеденко Б . В., Х и н ч и н А. Я. Элементарное введение в теорию вероятностей. — М.: Н а у к а , 1976. — 168 с.) 8. О комбинаторике написана прекрасная книга Наума Яковлевича В и л е н к и н а . А в т о р очень её любит и не расстается с ней с момента ее выхода. (Виленкин Н. Я. Комбинаторика. — М.: Н а у к а , 1969. — 328 с.) 9. Если не очень сильно о б р а щ а т ь в н и м а н и е на традиционную запись алгоритмов в виде б л о к - с х е м , то о них написана очень н е п л о х а я к н и г а д л я ш к о л ь н и к о в Ю. А. М а к а р е н к о в а и А . А. Столяра. (Макаренков Ю. А., Столяр А. А. Что такое алгоритм? — Мн.: Нар. асвета, 1989. — 127 с.) 10. Интересна для школьников и книга Льва Васильевича Тарасова. В ней можно узнать о том, что такое информация, вероятностный подход к её определению. (Тарасов Л . В. Мир, построенный на вероятности. — М.: Просвещение, 1984. — 191 е.). 11. Д л я д а л ь н е й ш е г о и з у ч е н и я к о д и р о в а н и я л у ч ш е всего подойдет к н и г а М. Н. А р ш и н о в а и Л . Е. Садовского (Аршинов М. Н. , Садовский Л . Е. Коды и м а т е м а т и к а (рассказы о кодировании). — М.: Н а у к а , 1983. — 144 с.) К сожалению, рекомендовать к н и г и по Турбо П а с к а л ю очень трудно, несмотря на и х огромное количество. Часть из них носит описательный х а р а к т е р , т. е. может использоваться только к а к справочный материал. Другие претендуют на обучение, но к а к это делать, трудно понять (мало задач, не разработан «стык» задач с и з у ч а е м ы м и к о н с т р у к ц и я м и и возможностями системы программирования). Поэтому позволим себе воздержаться от рекомендаций.