Книга
Книга
2 t 22 t 2Р2Р21GCD( 11 ,12) GQDiA.BlMhlM II- ll| -toCD<l2-ll .11 i^BtlXA1GCD( 11 ,12) GQDiA.BlMhlM II- ll| -toCD<l2-ll .11 i^BtlXA12Л(11=0)Л12- )ЛОЯ2-11Л12-12Л(11=0)Л12- )ЛОЯ2-11Л12-3n2 ♦ n2n(n+l3n2 ♦ n2n(n+l3h+l3h+lp2<J2).ep2(lR)AO<ep2(IR)| С'-рз-оч^рз-и2)! P3;l5'M24I=O)AI1SI2-11.p2<J2).ep2(lR)AO<ep2(IR)| С'-рз-оч^рз-и2)! P3;l5'M24I=O)AI1SI2-11.<nTep3(,Tl=ticD(ep3(A), (B^Sep3(H)Aep3(Ii;= s™<nTep3(,Tl=ticD(ep3(A), (B^Sep3(H)Aep3(Ii;= s™И12)И12)2n(n-2n(n-|GCD(I1 ,I2)=GCD(A|GCD(I1 ,I2)=GCD(Ad-il | -[GCOnd-il | -[GCOn
2 t 2
2 t 2
Р2
Р2
1GCD( 11 ,12) GQDiA.BlMhlM II- ll| -toCD<l2-ll .11 i^BtlXA
1GCD( 11 ,12) GQDiA.BlMhlM II- ll| -toCD<l2-ll .11 i^BtlXA
12Л(11=0)Л12- )ЛОЯ2-11Л12-
12Л(11=0)Л12- )ЛОЯ2-11Л12-
3n2 ♦ n
2n(n+l
3n2 ♦ n
2n(n+l
3h+l
3h+l
p2<J2).ep2(lR)AO<ep2(IR)| С'-рз-оч^рз-и2)! P3;l5'M24I=O)AI1SI2-11.
p2<J2).ep2(lR)AO<ep2(IR)| С'-рз-оч^рз-и2)! P3;l5'M24I=O)AI1SI2-11.
<nTep3(,Tl=ticD(ep3(A), (B^Sep3(H)Aep3(Ii;
= s™
<nTep3(,Tl=ticD(ep3(A), (B^Sep3(H)Aep3(Ii;
= s™
И12)
И12)
2n(n-
2n(n-
|GCD(I1 ,I2)=GCD(A
|GCD(I1 ,I2)=GCD(A
d-il | -[GCOn
d-il | -[GCOn
Р Андерсон
' ДОКАЗАТЕЛЬСТВО
ПРАВИЛЬНОСТИ
5 ПРОГРАММ
[GCDpr772^3CD( А, B)AOSI1AI1SI2A'. I2/ll]->{GCD\ep3(Il),ep3(I2)]=GCI Vp3<B>PSeP3<I1>Aep3(I1>=ep3
(2п)(пЧ)(2nw! ( Jn-Z +
Г-4(2п)(п+1)
(JHIQ-J2+lRA0HRAJ2S!R)4e 9(Jl)=e
е
2/\u = .i V15I2A-v(ll=0)AllS
1=1
X, 2(№р
PROVING PROGRAMS CORRECT
ROBERT В ANDERSON University of Houston .J
John Wiley & Sons
New York Chichester Brisbane Toronto 1979
Р Андерсон
ДОКАЗАТЕЛЬСТВО ПРАВИЛЬНОСТИ ПРОГРАММ
Перевод с английского Б. Н. ЗОБНИНОЙ
лод редакцией д-ра фйз.-мат. наук Д. Б. ПОДШИВАЛОВА
Москва «Мир» 1982
ББК 32.973 А65
УДК 6813.06
Андерсон Р. ■ -■
А65 Доказательство правильности программ: Пер. с англ — М.: Мир, 1982. —168 с., ил.',
В книге американского специалиста изложены основные принципы доказательства правильности программ для ЭВМ. Принципы доказательства иллюстрируются многочисленными примерами программ, представленных блок-схемами или записанных на языках высокого уровня. Материал излагается в простой и доступной форме.
Для пользователей ЭВМ, аспирантов и студентов, изучающих программирование.
. 30502-126
A A^z^x м 126-82, ч.12405000000 ББК 32.973
041(01)—82"
Редакция литературы по новой технике
Copyright © 1979 by John Wiley & Sons, Inc. All rights reserved. Authorized translation from English language edition published by John Wiley & Sons, Inc.
© Перевод на русский язык, «Мир», 1982
ПРЕДИСЛОВИЕ РЕДАКТОРА ПЕРЕВОДА
Почти 15 лет назад было высказано предположение, что правильность программ для ЭВМ можно доказывать так же, как, например, доказываются теоремы в математике. Любой программист либо специалист, использующий ЭВМ в своей практике, отчетливо представляет себе актуальность этой проблемы. С одной стороны, машины стали не только использоваться в научных расчетах, где можно контролировать решение на основе некоторых качественных представлений о его характере, но и включаться в автоматизироваййые или полностью автоматические системы управления, в которых человек не может контролировать процессы или из-за временных ограничений, или просто потому, что от него ускользает именно качественная картина процессов. С другой стороны, дискретная природа современных систем обработки информации не позволяет говорить о «в принципе верной» или «качественно верной» программе: любая самая незначительная ошибка может приводить к самым фантастическим результатам именно в силу этой дискретности. Таким образом, проблема доказательства правильности программ и еще более общая проблема создания надежных программ оказались выдвинутыми на первый план именно благодаря практическим соображениям.
Книга, которую читатель держит в руках, — первая книга на русском языке, в которой довольно объективно обсуждается проблема доказательства правильности программ. От читателя не требуется каких-либо специальных математических знаний для ее понимания. В ней объясняется постановка задачи: что такое «правильность программы» и как можно доказать эту правильность? Книга в основном построена на примерах — фактически это только примеры с небольшими вкраплениями «теории» и рассуждений общего характера.
Автор как опытный педагог не пытается навязать вам заранее свое мнение. Примеры говорят сами за себя. Хотя эта книга и не о программировании, она предназначена для программистов. В ней нет никаких рекомендаций, как писать «хорошие» программы, чем при этом не следует пользоваться, как все это организовать и т.п.— в общем всего того, чем полны современные книги по программированию. В то же время в ней раскрывается суть программирования, рассматриваются математические средства доказательства правильности программ, и поэтому к ней надо и относиться соответственно.
Теперь несколько слов о переводе. Во-первых, в книге идет речь о процессе, т. е. динамике выполнения разных программ. Языки программирования с их терминологией для обсуждения этого аспекта поведения программ приспособлены очень плохд. .Обычно в подобных случаях начинают апеллировать к, некоторому расплывчатому термину «управление» (control). Мы не хотели связывать довольно живое обсуждение процесса выполнения программ с этим «неуклюжим» словом. Поэтому, говоря о поведении программы, мы пытаемся «отождествить себя с этим управлением» и говорим: «мы попадаем в точку» или «при попадании в тачку» и т. п. Надеемся, что такой прием только облегчит восприятие.
Во-вторых, в книге очень много говорится о «высказываниях» и «утверждениях»; можно было с самого начала применять по отношению к ним терминологию математической логики и говорить, например, об истинности высказываний или значениях истинности. Мы этого не делаем и считаем, что наше решение более соответствует духу книги. Поэтому мы говорим о «справедливости» утверждений, о «проверках» (а не о предикатах) и даже о «положительных» результатах таких проверок. .
В заключение нам хотелось бы отметить следующее. Автор, как нам кажется, довольно трезво оценивает возможности доказательства правильности программ. Он, например, откровенно говорит, что доказательство может содержать ошибки и что от них можно избавиться, лишь передав эту работу автоматическим системам. Такие верифицирующие системы, если их когда-либо создадут, будут огромным шагом в программировании. Однако автор не пытается оставить у читателя впечатление, что такие системы уже созданы (но вы пока о них не знаете) или вот-вот будут
созданы. В этом существенное отличие предлагаемой книги от многих других, В то же время знакомство с методами доказательства затрагивает, ца наш взгляд, некоторые принципиальные вопросы программирования и способствует в конечном счете качественному и надежному программированию. '
И наконец, если проблема доказательства правильности, программ вас заинтересует уже как - специалиста (а но просто практика), то автор предисловия рекомендует вам обязательно ознакомиться с работой: De Millo, Lipton, Perlis. Social processes and proofs of theorems and programs, Communication of the ACM, 22, No. 5, pp, 271—280 (1979).
Д-р физ.-мат. наук Д. Б. Подшивалов
ПРЕДИСЛОВИЕ
Задача автора — рассмотреть и пояснить некоторые из основных методов доказательства правильности программ для вычислительных машин. В этой области в последние годы ведутся интенсивные исследования, целью которых является стремление формализовать и «механизировать» сам процесс доказательства. Мы уже будем говорить о некотором неформальном доказательстве правильности, с помощью которого программист может убедиться в том, что его программа верна. Безусловно, мы понимаем, что такое неформальное доказательство может содержать ошибки и отнюдь не есть универсальное средство, предохраняющее от ошибок или позволяющее их все обнаружить. Тем не менее мы надеемся, что такой подход дает программисту некоторое систематическое средство для проверки его программы за столом. Мы полагаем также, что основные методы, используемые при доказательстве правильности, способствуют более глубокому пониманию таких важных понятий программирования, как цикл и рекурсия. Поэтому всем программистам следовало бы изучить основные методы доказательства правильности программ.
Для понимания содержания книги достаточно иметь опыт программирования на языке высокого уровня и некоторые представления о доказательстве в математике. Никаких особых математических знаний не требуется. В гл. 1 приведены основные сведения о математической индукции как о способе, положенном в основу доказательства правильности. В гл. 2 дано представление о методе индуктивных утверждений, который чаще других используется при доказательстве правильности итеративных программ. В гл. 3 описывается применение этого метода длй программ на Фортране и ПЛ/1. Здесь же приведены и основные идеи правил верификации
и их эквивалентности методу индуктивных утверждений. Глава 4 посвящена структурной индукции — основному методу, используемому для доказательства правильности рекурсивных программ. Показано, что этот метод применим и при доказательстве правильности итеративных программ, поскольку именно эти программы чаще всего выполняют рекурсивные процессы. В гл. 5 дан очень краткий обзор некоторых из современных исследований, относящихся к доказательству правильности программ. В конце книги приведена достаточно полная библиография для тех читателей, которые захотят более подробно ознакомиться с исследованиями в этой области.
Книгу можно использовать как дополнительный материал для основных курсов по программированию или курсов по теории вычислений, как вспомогательный материал по дополнительным курсам, посвященным, например, стилю программирования или правильности программ, и как материал для самообучения: она достаточно проста и для ее понимания не требуется глубоких знаний каких-либо разделов математики и программирования.
Роберт Б. Андерсон
ГЛаВа 1
МАТЕМАТИЧЕСКАЯ ИНДУКЦИЯ
.11. ВВЕДЕНИЕ
Математическая ийДукЦйя йрёДсТайляет собой некоторый Общий СНосОб Доказательства в Математике. Ой, хотя об этом йё всегда явно говорят, Положен В основу всех приемов доказательства Правильности программ ДЛИ вычислительных машин. Данная глава знакомит Чйтатейя с математической Индукцией-фундаментальным способом Доказательства в Математике.•
Обычйо математическую йндукцйк) Вводит как метод доказательства утверждений о положительных числах. В разд. 1.2 сформулированы И проиллюстрированы на примерах самые Простые версии этого метода, в разд. 1J дана болеё строгая версии а в разд. 1.4 этот метод обобщен йа блуйай Явобых вполне унорддоченных множеств-, а не только пблбЖНТёйЬйЫк ЧйсёЛ. Дии Понимания кййГй существен лишь разд. 1Д Поэтому читатель может йроггуститъ разд. 1.3 И 1.4 И Вёрнутьей к ИЯМ йозЖе, еслй в этом будет Нёо5бх0ДйМ0ётъ. В разд. 1.4 рассМОЗрёйЫ наиболее атст^мкТйые волосы, пгозтшу читатезд должен яыеть сне- циальную МатёМаТИЧёСкую пОдГОЗОВку.
ПРОСТАЯ ИНДУ КЦИЯО^Оак црЬстей индукфав
ЙуёТЬ S^—некЬторое Высказывание о делом числе я И трёбуеЗей ДйеАЗВТъ, ЧТО Sj%> спрФзёдЛИВо Для йкех положй- тельных Чйсёй *.■
СоГласЙо йростой МатёматиЧескОй индукцйи, Для этого йеобкоДйМО
К -Д<йййатЬ> Чтй ё^аведлйво высказывание 3(1).
2. Доказать-, ЧТО если для Любого положительного Числа л
справедливо высказывание S(n), то справедливо и высказывание 8(п + V.
Из приведенных выше двух утверждений вытекает, что 8(п) справедливо для всех положительных чисел. Этот факт интуитивно очевиден, хотя при аксиоматической трактовке целых чисел сам принцип в такой формулировке следует рассматривать как аксиому. Из первого положения индукции следует, что справедливо 5(1), а из второго —что справедливо 5(2), если справедливо 5(1). Но 5(1) справедливо, следовательно, должно быть справедливо и 5(2). Из второго положения индукции вытекает также, что справедливо 5(3), если справедливо 5(2). Таким образом, поскольку мы знаем, что 5(2) справедливо, то, следовательно, справедливо 5(3) и т. д. Отсюда интуитивно видно, что эти два положения вместе доказывают справедливость 5(1), 5(2), 5(3), ..., 8(п)....
Рассмотрим пример использования простой индукции.
ПРИМЕР 1. Мы хотим доказать, что для любого положительного числа п сумма первых п положительных целых чисел равна п • (п + V/2, т. е.
14‘24‘...4‘Л»«.(й4-1)/2
для. любого положительного числа п. Используя простую индукцию, неооходимо только доказать, что
«Сумма» верна для п = 1, т. е. 1 — 1 • (14-1)/2 Это очевидно.
Если сумма первых п положительных целых чисел равна л-(«4‘1)/2, то сумма первых п +1 положительных целых чисел равна (л+1) • [(л 4-1) +1]/2, т. е. мы предполагаем, что формула 1+ 2 + ». + л“-л• (л+1)/2 справедлива. Это гипотеза индукции. На ее основании мы должны попытаться доказать, ч^о справедлива формула
1 + 2 + ... + л4Чл+1)-(«+1М(л+0^^^
Докажем это следующим образом:
14-24-...4‘П + <л4Л)-(14-2 + ...4-^
[ л. (л +1)/2] 4- (л 4-1) - [(л2 + л)/2] + (л +1) -
(По гипотезе индукции)
[(я2 4- я)/2] + [(2я+2)/2] - (я2 + Зя+2 )/2 -
(л +1). (я+2)/2 - (я 4-1). [(л +1) + Ц /2.
Что и требовалось доказать.
Поскольку мы доказали справедливость двух утверждений, то по индукции формула
1 + 2 + ... + п - п • (и + 1)/2 считается справедливой для любого положительного числа и.
Прищеп модифицированной простой индукции
Иногда необходимо доказать, что высказывание 5(л) справедливо для всех целых п > п0. Для этого можно довольно легко модифицировать принцип простой индукции. Чтобы доказать, что высказывание S(n) справедливо для всех целых и,необходимо: ,
Доказать, ‘что справедливо 5(л0).
Доказать, что если 8(п) справедливо для всех целых и > л0, то справедливо и 5(л +1).
В частности, если требуется доказать справедливость некоторого высказывания 8(п) для любых положительных целых л (т. е. для л>0), то необходимо:
Доказать, что справедливо 5(0).
Доказать, что для всех неотрицательных целых л справедливо 5(л + 1), если справедливо 5(л).
ПРИМЕР 2. Для любого неотрицательного числа л доказать, что
2° + 21+22 + ... + 2” = 2”+1-1
Используя простую индукцию, мы должны
Доказать, что 2° = 2O+1 —1 Это очевидно, так как
2° —1=20+1-1—21-1—2-1 = 1.
Доказать, что если для всех неотрицательных целых л справедлива формула
2о + 21+22+... + 2”»2,,+1 -1, то справедлива и формула
2° + 21+22+...4-2”4-2”+1=2(,,+1,+1 -1.
Высказывание 2° + 21 + 22 +... + 2” = 2”+1 — 1 называется гипотезой индукции. Второе положение доказывается следующим образом:
2° + 2' +22 + ...+2" +2”+' -(20 + 2| +22 + ...+2") + 2”+, - — (2я+1 - 1) + 2я+1—(2я+'+2я+1) -1 —
(По гипо-тезе индук-ции)
^(2.2Л+') - 1 =2п+2 - 1=2*л+^+1 — 1
Иногда нужно доказать справедливость высказывания 8(п) для целых п, удовлетворяющих условию п0< пС т0. Так как между п0 и т0 находится конечное число целых чисел, то справедливость 8(п) можно доказать простым перебором всех возможных вариантов. Однако легче, а иногда и необходимо (если, например, мы не знаем конкретных значений Ио и т0) доказать 8(п) по индукции. В этом случае можно воспользоваться одним из двух вариантов индукции:
Простая нисходящая индукция
Доказать, что справедливо 8(п0).
Доказать, что если справедливо 8(п\ то для любых целых п0 < п < то — 1 справедливо и S(n + 1).
Простая восходящая индукция
Доказать, что справедливо S(m0\
Доказать, что если справедливо 8(п\ то для любых целых п0 + К п< т0 справедливо и 8(п - 1).
Читатель, конечно, интуитивно понимает, что этого достаточно для доказательства справедливости 8(п) при любых п, удовлетворяющих условию п0< пС т0.
Доказательство высказывании, относящихсяк щкираммам для вычислительных машин
Иногда не содеем ясно, доказываем ли мы справедливость 8(п) для всех л, удовлетворяющих условию ИоСпСто или условию По < п. В такой ситуации можно добитьс^ успеха, даже и не зная, с каким из случаев имеем дело. Например, при доказательстве правильности программы иногда необходимо доказать справедливость некоторого высказывания 8 в те моменты, когда выполнение программы достигает некоторой определенной точки. Можно попытаться доказать это методом индукции по п — числу «проходов» через данную точку программы. Однако мы можем и не знать точно, сколько
раз проходим через эту точку: это зависит от данных, исполь- зуемых при выполнении программы. Мы можем проходить через нее и конечное (то), и бесконечное чисйО раз (если программа не заканчивается из-за ошибки). Таким образом, можно попытаться доказать справедливость ' 5( л) для л, удовлетворяющих условию, КлСт0 или усйбвию 1<л. В любом случае мы можем получить результат, не зная, с каким вариантом мы имеем дело. Мы убеждаемся в справедливости 5(л) при каждом проходе через определенную точку, если можем:
Доказать, «По справедливо 5(1), т. е. справедливо высказывание 5 при первом проходе через точку.
Доказать, что если справедливо 5(л) (т. е. при л-ом проходе через точку), то справедливо и 5(л+1), если мы, ; конечно, попадем в точку в л + 1-й раз.
Если мы проходим через точку только т0 раз, то значения л, для которых второе положение, возможно, справедливо, это те значения л, при которых мы можем попасть в точку в л-Н-й раз, т. е<. значения, удовлетворяющие условию 1< л С т0— 1 Если же мы проходим через точку бесконечное число раз, то значения л, для которых может быть справедливо второе положение, это значения, удовлетворяющие условию 1С л. Таким образом, если мы докажем оба положения, то тем самым с помощью простой восходящей или простой индукции мы докажем справедливость высказывания 5(л) для всех требуемых значений л вне зависимости от того, какой вариант встречается в действительности.
УПРАЖНЕНИЯ
Докажите, что для любого положительного числа л справедливы формулы:
В+-—“ +... +———— — ——;
'1-2 23л(л+1)л+1 *
3"*’ -1
3° + 3' + За +... + 3";
13 + 23+... + 3'’--^—.
Обратите внимание, что йз этих формул и примера вытекает справедливость такой интересной формулы:
I3 + 23 + ... + л3 * (1 + 2 + ... + nf.
Приведенные на рис. 1.1 графы представляют собой примеры полных двоичных графов уровней 0, 1, 2 и 3. Полный двоичный граф уровня п подобен приведенным графам: из каждой его вершины, кроме вершин уровня п, выходят по два ребра. Вершины уровня п называются концевыми вершинами или листьями дерева. С помощью индукции по уровню л^цока- жите, что число концевых вершин в полном двоичном дереве уровня л равно 2*. Напишите формулу для числа всех вершин в полном графе (как концевых, так и неконцевых) и докажите ее справедливость для любого положительного числа л.
Рис. 1.1.Рис. 1.1.Уровень ОУровень /Уровень 2Уровень 3Уровень ОУровень /Уровень 2Уровень 3
Рис. 1.1.
Рис. 1.1.
Уровень О
Уровень /
Уровень 2
Уровень 3
Уровень О
Уровень /
Уровень 2
Уровень 3
На рис. 12 даны примеры полных графов, содержащих 1—5 вершинУ Полный граф из л вершин аналогичен приведенным: пара Л1дбых егр , вершин соединена ребром. Напишите формулу для числа всех ребер в полном графе с л вершинами и докажите ее справедливость с помощью индукции йо п.
Найдите ошибку в следующем доказательстве. Доказать, что
22 3" (п -1) • * 2п
для любого положительного целого числа л. Доказательство проводим методом индукции:
„ Для л * 1 формула справедлива, ибо
13 1-23-21
1 2 “ 2-1" 2" 2 *
. Предположим, что формула справедлива для л, т. е.
1 * 22-3 (л - 1) • л2 л
Покажем, что эта формула справедлива для п 4-1:
122 3(л - 1) . л л • (л + 1)
_ Зл-2 !1(Зл -2) • (л + 1)2
2л л-(л+1)2л-(л+1)+ 2л-(л+1)
(По гипотезе
индукции)О
Зл2 + л - 2 + 2 Зл2 + л л • (Зл + 1)
” 2л • (л + 1)" 2л • (л + 1)2л-(л + 1)
_ Зл+1 . 3(л+1)-2
2(л+1),.}2(л+1)'
61212436121243Таким образом, утверждение справедливо и для л+1, Хотя доказательство выглядит как будто правильно, тем не менее оно неверно, так как для л — 4
612124
3
612124
3
Однако
Зл-2 3-4-210 у
2л 2-4" 84
Найдите ошибку в следующем доказательстве. Доказать, что в любой куче камешков все камешки одного цвета. Используем индукцию по числу камешков в куче.
Для л — 1 это высказывание очевидно, так как вся куча состоит из одного камешка и поэтому в куче есть камешки только одного цвета.
Предположим, что наше высказывание справедливо для любой кучи из л камешков. Покажем, что оно справедливо и для кучи из л 4-1 камешков. Кучу камешков можно представить следующим образом:
О О О ... О О
12 3 л л+1
Если мы уберем л 4- 1-й камешек из этой кучи, то останется куча из л камешков:
ООО... О
12 3 л
По гипотезе индукции все камешки в такой куче имеют один цвет. Представим себе, что вместо последнего мы выбрасываем первый камешек из кучи:
О О ... О О!
23 л л+1
Но здесь то же число л камешков и, следовательно, все они одного цвета. Отсюда следует, что и л 4-1 камешков будут одного цвета, так как известно, что камешки
0 0... О 1 2 и одного цвета, а п + 1-й камешек того же цвета, что и л-й (даже больше, его цвет совпадает с цветом второго, третьего и т. д.). Отсюда й следует справедливость нашего утверждения.
СТРОГАЯ ВЕРСИЯМАТЕМАТИЧЕСКОЙ ИНДУКЦИИ
При доказательствах некоторых высказываний о целых числах иногда требуется более строгая версия принципа индукции.'
Принцип строгой индукции
Пусть S(n) — некоторое высказывание о целом числе п и требуется доказать, что S(n) справедливо для всех положительных п. Для этого необходимо: '1. Доказать, что справедливо 5(1).
Доказать, что если справедливы 5(1), 5(2), ..., S(n) для всех положительных гц то справедливо и S(n + 1).
Отметим, что эта версия индукции почти идентична простой индукции, за одним исключением: при доказательстве второго положения в качестве гипотезы предполагается не просто справедливость высказывания S(n), а справедливость высказываний 5(1), 5(2), ..., S(n). Исходя из этой более строгой гипотезы, необходимо, как и выше, доказать справедливость 5(л + D.
Интуитивно ясно, что эти два положения подразумевают справедливость S(n) для любого положительного числа п. Из первого положения известно, что 5(1) справедливо, а из второго следует, что если справедливо 5(1), то справедливо и 5(2). Так как 5(1) действительно справедливо, следовательно, то же ждано сказать и о 5(2). Затем, так как и 5(1), и 5(2) справедливы, из второго положения следует справедливость 5(3). Из справедливости высказываний 5(1), 5(2), 5(3) и второго утверждения вытекает справедливость высказывания 5(4) и т. д.■-.
Рассмотрим несколько примеров, в которых полезно использовать принцип строгой индукции.
ПРИМЕР 1. Простым числом называется положительное число, делящееся безостатка только на 1 и на само себя. Мы хотим
1АУЧЯ АЯ бИБМеТЕКА ."ин. Ге^ькега
МГУ
доказать, что каждое положительное число п можно представить в виде произведения одного и более простых чисел. Докажем это с помощью строгой индукции по п.
Если л = 1, то это число является простым, и, следовательно, его можно представить как «произведение» одного простого числа*
Предположим, что каждое из чисел 1,2, ..*, п может быть записано в виде произведения простых чисел. Необходимо показать, что число л 4-1 также можно представить в виде произведения простых чисел. Если л 4-1 является простым числом, то очевидно* Что его можно записать в виде произведения одного простого числа на 1. Если л 4-1 — не простое Число, тогда существует некоторое положительное число л, такое, Что 1<л<л+1, и оно делит л4-1 без остатка. Другими Словами,
—^«или л4-1 — а • о. а ’
Каждое из чисел а и b Меньше л. Следовательно, по гипотезе индукции и а, и Ъ можно представить в виде произведения простых чисел. Отсюда очевидно, что и л 4-1 можно записать как произведение этих же простых чисел, так как л 4-1ж а • Ь.
Следует отметить, что в этом примере, по существу* требуется строгая индукция. О числах а и b известно лишь только то* что они меньше л. Поэтому, для того чтобы использовать индукцию, необходимо знать, что каждое из положительных чисел I, 2, ... , л может быть представлено в виде произведения простых чисел. Одного предположения о том, Что л можно записать в виде произведения простых чисел, оказывается недостаточно.
ПРИМЕР 2. Рассмотрим Последовательность чисел, называемых числами Фибоначчи. В нее входят Л^О,/^!, Л ^ L/з “ 2>Л — 3,/5 ^ 5,Л ^8,..., где л 4- 1-е число Фибоначчи Определяется как j;+1 =fn +Х_, (для п> 1). Пусть а (14- -/5)/2 и требуется доказать, что fn^ а”-1 для любого неотрица^ тельного числа л. Для доказательства используем метод строгой индукции по л. Так как доказывается высказывание о неотрицательных числах, а принцип индукции сформулирован для положительных* используем очевидную модификацию метода (см. упражнение 1):
Для п^О необходимо показать, что Jo< а0"’. Это справедливо, так как Jq — ОС t ^^ = а-1. Необходимо рассмотреть особый случай, когда я —1. Здесь мы имеем /, —К 1 — а°г= а1"1. _
Предположим, что /тС ат 1 справедливо для всех неотрицательных целых чисел т — 0, 4, 2, ... , п. Нужно показать, чтоЛ+i ^ а 1л+1 > *1 также справедливо. По гипотезе индукции^< а”"1 и/п_АС a Поэтому .
Л.1 =Л +/«-1 Са”"1 +ал"2 - а”"2 • (а+ 1).
Обратите внимание, что
а2, ( 1 +У _ 1+2/5+5 _ 6 + 2/5
“ " X 2/ / ”4”4*
Фактически мы так и выбирали а, чтобы выполнялось условие а +1 — а2. Поэтому мы получаем
/пн =/п ^у Са^Чч* 1)- а””2 (а2)- ал~ а(”+1,‘1
что и требовалось доказать.
Отметим, что необходимо было знать, что и /й, и j^ удовлетворяют гипотезе индукции. Только в этом случае доказательство возможно. Одного только знания о справедливости высказывания для /„ недостаточно. Таким образом, строгая индукция нам необходима по существу. Отметим также, что в данном частном случае нам потребовалось доказывать, что Jo < а0 и /,С а1; мы не можем записать / кaк^+/.ъ так какД не существует.
УПРАЖНЕНИЯ
Измените приведенный в данном разделе принцип индукции для доказательства справедливости высказывания S(n) для целых чисел п, удовлетворяющих условию п >Hq, а не только для любых положительных целых чисел.
Используя простую индукцию, докажите справедливость следующих формул:•"
• а) Л +/i + — +/я = /я+2 - 1 Д®1 всех неотрицательных целых п;
ла л;ла л;Л +Л +Л + ... Vfm вЛп + i-l Для любого положительного чис
ла л;
ла л;
Л2+/2+ — +fn=fn ’fn+y Для всех неотрицательных целых чисел п.
Используя метод строгой индукции, докажите справедливость следующих формул:
а”~2 **/„, где а — 1+2^~ , для любого положительного и;
fn^ (^-)”"1 для всех неотрицательных л.
Найдите ошибку в следующем доказательстве. Мы хотим доказать, что о”"1 — 1 для любого положительного числа п. Используем строгую индукцию:
Для л — 1 эта формула справедлива, так как а”-1 = а1"1 = д° — 1.
Предположим, что высказывание справедливо для 1, 2, 3,..., л,т. е. ^ = 1, а1 — 1, а^ — 1,..., д”"1 — 1. Нужно показать, что д !”+и— 1:
о(Л+1)-1 = ап = д1 . ап-\- 1.1-1.
(По гипотезе индукции)
Сформулируйте два варианта строгой индукции, которые можно использовать для доказательства справедливости S(n) при л, удовлетворяющих условию, л0 < л < т0. Мы будем ссылаться на эти варианты как на строгую восходящую й строгую нисходящую индукцию. Соответствующие версии простой индукции рассмотрены в разд. 1.2.
14. ОБОБЩЕННАЯ ИНДУКЦИЯ
Метод математической индукции можно обобщить и применять не только для доказательства высказывании о, множестве положительных целых чисел, но и о более общих множествах некоторых объектов. Для этого сначала дадим определение, связанное с обобщением структуры .целых чисел.
Определение. Говорят, что бинарное отношение < вполне упорядочивает множество X, или множество вполне упорядочено, если отношение < обладает следующими свойствами?
Если х, у, z принадлежат X. а х<у и y<z, то x<z.
Для х и у из X справедлива одна и только одна из трех возможностей: либо х<у, либоу< х, либо х== у.
Если А — любое непустое подмножество X, то в А существует некоторый элемент х, такой, что хСу для любого у, принадлежащего А. Другими словами, любое непустое подмножество X содержит «наименьший элемент».
Отметим, что это определение касается обобщения структуры положительных (неотрицательных) целых чисел.
Множество положительных целых чисел вполне упорядочено относительно обычного отношения <.
ПРИМЕР 1. Множество всех целых не вполне упорядочено с помощью обычного отношения <. Такое множество обладает свойствами 1 и 2, но не обладает свойством 3. Множество всех отрицательных целых есть непустое подмножество множества целых, но не содержит наименьшего элемента.
ПРИМЕР 2. Множество неотрицательных действительных чисел не является вполне упорядоченным с помощью обычного отношения <. Как и в предыдущем примере, для него выполняются свойства 1 и 2, но не выполняется свойство 3. Например, множество всех действительных чисел больше единицы (такое множество можно обозначить следующим образом: {х:х есть действительное и х> 1}) не содержит наименьшего элемента. Действительно, единица меньше любого элемента множества, но она множеству не принадлежит.
ПРИМЕР 3. Множество всех упорядоченных пар неотрицательных целых чисел вполне упорядочено с помощью отношения лексикографического порядка <. Это отношение можно определить так: отношение (пъ п2)<(п3, л4) справедливо, если и только если (пА < п^ или (пА = п3 и п2< п4). В качестве упражнения покажите, что такое отношение обеспечивает нужную упорядоченность.
Теперь сформулируем принцип обобщенной индукции для доказательства высказываний относительно любых вполне упорядоченных множеств.
Принцип обобщенной индукции
Пусть X—вполне упорядоченное относительно < множество, a S(x) — некоторое высказывание, касающееся элемента х из X. Если требуется доказать справедливость S(x) для всех х, принадлежащих X, то необходимо:
Доказать, что справедливо S(xq\ где хь — наименьший элемент в X.
Доказать для всех х в X, удовлетворяющих условию
Xq < х, что ecjfc справедливо S(y) для всех у< х, то справедливо и ад.
Отметим, что если X— множество положительных целых чисел, а отношение < имеет обычный смысл, то принцип обобщенной индукции идентичен принципу строгой индукции (разд. 1.3).
Чтобы убедиться в действенности принципа обобщенной индукции как метода доказательства, предположим, что Я(х) — некоторое высказывание, для которого уже доказаны оба положения. Мы хотим сделать вывод о том, что 5(х) справедливо дуя любых х в X. Предположим, что это не так. Пусть множество Л —(х:х принадлежит X и S(x) ложно}. Если S(x) не справедливо для всех х в X, то А — непустое подмножеству X. Так как X вполне упорядочено, то известно, что Л'содержит наменьший элемент oq. По определению это наименьший элемент X, для которого S(x) не справедливо. Таким образом, S(y) справедливо для всех у (еслй они есть), удовлетворяющих условию у<«о. Если а0 —наименьший элемент в X, то S^Oq) справедливо, что следует из первого положения. В противном случае из справедливости 5(у) для всех у< а^ и второго положения вытекает, что S(ao) справедливо. Но это противоречит предположению, что Оо принадлежит А и S(ao) ложно. Единственный способ устранить, это противоречие — считать А пустым множеством, т. е. в X нет элементов, для которых высказывание 5(х) ложно.
ПРИМЕР 4. Рассмотрим последовательность чисел, определенную следующим образом: S0)o”0, а для любой другой пары неотрицательных чисел п, т
, =/4»-1л+1, если л-0;
т*п l^_t + l, если п*0.
Например, первые элементы этой последовательности:
50^ — 0, 5^о = ^Qjo ~4" 10 ~1 *— 1 >
^oj-^op+l-l, »м ^^o+l*1 + 1-2, 52,0 — 5Ьо + 1—2, 52,1 “ $2,0+ 1 — 3, ... .
Нужно доказать, что sm,n— m-Vn для любых неотрицательных целых т, п. Используем принцип обобщенной индукции на множестве X упорядоченных пар неотрицательных целых чисел. Множество упорядочено на основе лексикографиче-
ского порядка, описаййого в примере Зив упражнении 1а. Чтобы доказать справедливость s^r тАп для любых < т, п > в X, необходимо:
L Доказать» что х^п^т + п справедливо, если < т, п> — наименьший элемент в X. Но так как наименьший элемент — пара <0, 0>» то нужно показать» что 50,0=0 + 0=0. Это оче- вйдйо по определению зь,0.
2* Доказать, что если ^, — т* + п* справедливо для любых < т\ п'Х <т, в >, то справедливо и ^.^ т + п для любых < 0» ОХ <т> н> в X. Предположим, 'Что формула s^,— м* A-w\ верна ДЛЯ < ж'» #Г > < < т, ' п >. Это гипотеза индукций. НужйО показать» Что й^ Иг + п. Существует две возможности: либо и—0, Либо п^ 0. Если й=0, то по определению s^ Vi,h +Е Однако < т — 1» Л > < < т, п >» и, сЛедо- вательйО» По нашей гипотезе ^.^(/й—1) + «. Поэтому Jm^W^^l+'W^ Если П^0. ГО Sm,nte =^'„^+1 по определению. Но < т.п— 1 > < < тн, л > и по гипотезе ^_^^+(л —1). Следовательно, в этом случае W^n-M “ W + Я — 0 +1 ” ^ + п>что й требовалось
доказать.
упрАжнёнйя
. Пусть I = {< nv й2 >: щ и л? — Неотрицательные целые} и отношение < определяется так: высказывание < nv л2 >< < л3, л4 > справедливо, если й только если (^ < Лд) Или (Л| = л3 и ^ < лД где отношение < есТь традиционное отношение длЯ Целых Чисел. Докажите, что < вполне упорядочивает МйоЖестВд X. Такое отношение называется лексикографическим норяДКбй.
Пусть X = {< xv хб,...,хя>: х^^ и Хд^ - и хлЛ}- Предположим, что Х\ вполне уйорядочейо с порядком < р Х2 — о порядком <4,..., Хп — й по- рядкоМ <h. В этом случае МОжнО определить отношение < на X так: <Хр х2 ,..., х^Х<у\, У$ >..., Уп>, ^слй И только если xt <1^, или (х1 =^ И х^<^ч или ..., ИЛИ (xt =у^ и Х2=уъ И ,..., и хх.<=уп_\ч и х« <пУп). Докажите, ЧТО ПОрядОк < вполне упорядочивает множество X.
Покажите», что если отношение < бйолне упорядочивает Хъ то в X не существует Ъёсконёчйои йоследоватёльйостй хр х2,...,хя,..., такой, что хА>х^ > Хз > ... > Хд >... . Другймй слОйамИ, вйолне уйорядоченйое множество йе содержит бесконечной убывааюЩей последовательности элементов.
ПреЩтЪложиМ, что S<nv л2 ..... ^—Некоторое ВЬгсказывание 6 m Неотрицательных целых числах лр й2 ..... п^. Сформулйруйте утверждения, которые надд Доказать, чтобы показать справедливость высказывания *$Глр «2 ,..., п^ для любых неоТридагёльНык целых чисел лр л2 ..... п^ если для Доказательства используется принцип обобщенной индукции и лексикографичесжйй Порядок.
Рассмотрим последовательность чисел, определяемую следующим образом:
•^1,1 ■ 5,
а для любой пары положительных чисел т, п, кроме пары 1,1,
„= рт-1л + 2, если л - 1;
т’п l^.f+2, если л ^1.
С помощью метода обобщенной индукции докажите, что для любой пары т, л положительных чисел справедливо высказывание
Sm,n ■ 2- (/л + л) + 1.
Рассмотрим последовательность чисел, определяемую следующим образом:
•^0,0 " о,
а для всех других пар т, л неотрицательных чисел
- рт-ъл+J, если л-0;
* ”, если л^О.
Докажите с помощью метода обобщенной индукции, что для всех пар неотрицательных целых т, л справедливо высказывание
л . (л + 1)
$т,п =5
Для последовательности неотрицательных целых чисел; определенной следующим образом: s0,0 — 1, и для других пар неотрицательных целых т, л
„ =+ ”», если л-0;
т*№если л^О,
напишите общую формулу для sm>n и докажите ее методом обобщенной индукции.
7а. Пусть X — множество целых чисел, удовлетворяющих условию л0 < л < /л0. Пусть отношение <t есть обычное отношение порядка < для ограниченных целых чисел из этого множества. Покажите, что отношение <, вполне упорядочивает X, а обобщенная индукция в этом случае есть строгая восходящая индукция (упражнение 4 в разд. 1.3).
Пусть X — то же множество, что и в упражнении 7а, но отношение <2 определяется так: nt <2 л2, если и только если л2 < ль где отношение < есть обычное отношение для целых чисел. Покажите, что отношение <2 вполне упорядочивает множество X. Будет ли оно упорядочивать и множество всех неотрицательных чисел? Покажите, что обобщенная индукция на множестве X, вполне упорядоченном с помощью <2, есть строгая нисходящая индукция (упражнение 4 в разд. 1.3).
Глава 2
ДОКАЗАТЕЛЬСТВО ПРАВИЛЬНОСТИ БЛОК-СХЕМ ПРОГРАММ
ВВЕДЕНИЕ
При составлении программы для вычислительной машины мы стремимся к тому, чтобы программа решала некоторую определенную задачу. Однако каждый программист отчетливо и с горечью осознает, что большинство наших программ содержит ошибки. Мы тратим обычно много времени на тестирование и отладку созданных нами неправильных программ. Даже закончив тестирование и отладку программы, мы не можем быть уверены, что она совершенно правильна. Все, что можно сказать о ней; это то, что программа дает правильные результаты для тех частных данных, которые использовались при ее тестировании. Позже, когда программа будет работать с новыми исходными данными, могут обнаружиться и другие ошибки. Каждый опытный программист знает, что программа в течение долгого периода может работать без ошибок, кажется совершенно правильной, и вдруг внезапно, таинственным образом появляется очередная ошибка. Поэтому независимо от интенсивности тестирования никогда нельзя поручиться за правильность программы.
Было бы идеально, если бы мы могли доказать правильность программы, а не ссылаться только на проведенное тестирование?. Конечно, если даже удастся доказать, что программа правильна, то нельзя быть в этом абсолютно уверенным. В доказательстве, как и в программе, также могут быть ошибки. Тем не менее усилия, затраченные на доказательство правильности программы, способствуют всестороннему пониманию программы. Ведь чтобы доказать, что программа правильна, надо очень хорошо в ней разобраться. Опыт показывает, что именно поэтому доказательство правильности весьма полезно и способствует обнаружению тех ошибок, которые могли быть пропущены, если бы доказательство не проводилось.
Если бы можно было формализовать доказательство правильности и проводить его абсолютно надежным образом (автоматически доказывающим устройством), то ему можно было бы полностью доверять. В будущем это, возможно, окажется реальным (разд, 2.5), но не сейчас. Мы рассмотрим лишь неформально построенные доказательства правильности. Опыт проведения таких доказательств показывает, что при этом возрастают наша уверенность в программе и ее понимание, и поэтому доказательства следует выполнять, хотя они и, не приводят к полной гарантии от ошибок. Неформальное доказательство правильности можно рассматривать как Некоторую форму систематической проверки программы за столом, без машины, т. е. того, что всегда делает хороший программист. В этой главе даны основные идеи такого Доказательства правильности для программ, представленных , блок-схемами.
ОСНОВНЫЕ ПРИНЦИПЫ ДОКАЗАТЕЛЬСТВАПРАВИЛЬНОСТИ ДЛЯ БЛОК-СХЕМ
Если мы хотим доказать, что некоторая программа правильна или верна, то прежде всего должны уметь описать то, что по предположению делает эта программа. Назовем такое описание высказыванием о правильности или просто утверждением. В этом случае доказательство правильности состоит из доказательства того, что программа, будучи запущенной, в конце концов окончится (выполнится оператор STOP), и после окончания программы будет справедливо утверждение о правильности. Часто требуется, чтобы программа работала правильно только при определенных значениях входных данных. В этом случае доказательство правильности состоит из доказательства того, что если программа выполняется при соответствующих входных данных, то она когда-либо закончится, и после этого будет справедливо утверждение о правильности.
Проиллюстрируем эти идеи на примере доказательства правильности для блок-схемы очень простой программы.
ПРИМЕР i. Предположим, что нужно вычислить произведение двух любых целых чисел М, N, причем М>0 и операцию умножения использовать нельзя. Для этого можно использовать операцию сложения и вычислить сумму из М ела-
РИС; 2.1.
гаемых (каждое равно N). В результате получим М • N. Рассмотрим блок-схему, реализующую такое вычисление (рис. 2.1). Нужно доказать, что приведенная программа правильно вычисляет произведение двух любых целых чисел М и N при условии М>0, т. е. если программа выполняется с целыми числами М и N, где М>0, то она в конце концов окончится (достигнув точки 5) со значением
J = MN.
Чтобы удостовериться в правильности работы блок-схемы, можно было бы проверить ее с некоторыми фиксированными данными. Например, давайте «вручную» промоделируем выполнение программы с числами М = 3 и N = 5. Ниже приведены значения переменных I и J в моменты достижения точки 2 на блок-схеме (вход в цикл):
Число проходов п через точку 2Значение IЗначение Jпри выполнении блок-схемы10021532104315Число проходов п через точку 2Значение IЗначение Jпри выполнении блок-схемы10021532104315
Число проходов п через точку 2
Значение I
Значение J
при выполнении блок-схемы
1
0
0
2
1
5
3
2
10
4
3
15
Число проходов п через точку 2
Значение I
Значение J
при выполнении блок-схемы
1
0
0
2
1
5
3
2
10
4
3
15
Когда мы в четвертый раз дойдем до точки 2, значение I будет равно 3, т. е. равно М, поэтому выполнение цикла закончится и мы попадем в точку 5.
При попадании в точку 5 переменная 1 = 15=3-5. Таким образом, мы убеждаемся, что при М = 3 и N = 5 блок- схема работает верно. Хотя это, конечно, и укрепляет нашу уверенность в правильности работы программы, но тем не менее никак не доказывает, что программа будет работать правильно при всех возможных значениях М и N. Можно продолжать проверять программу с другими значениями М и N. Если для некоторых значений М и N будет получен неверный ответ, то надо еще узнать, что он неверен. Однако, если мы даже будем продолжать получать правильные ответы, все равно не будем уверены, что такие ответы будут получаться для любых возможных значений М и N. Всегда остается бесконечное число возможных значений, для которых программа еще не проверялась. Конечно, если речь идет о некоторой конкретной машине, то значение М и N рграничены некоторым конечным интервалом для целых чисел. В принципе в этом случае можно провести исчерпывающую проверку с числами в этом интервале. Однако для большинства машин этот интервал настолько велик, что проводить исчерпывающую проверку было бы не практично. Кроме того, мы составляем программу обычно таким образом, чтобы она работала верно независимо от размеров N и М. Следовательно, мы будем удовлетворены лишь тогда, когда сможем показать, что программа работает правильно независимо от размеров М и N. Методом тестирования мы этого никогда не достигнем.
Предположим, что вместо выполнения программы с конкретными значениями М и N мы начнем ее выполнять с символическими данными М и N. В. этом случае мы получим следующую «трассу» для значений I и J при проходах через точку 2:
Число проходов пчерез точку 21234Число проходов пчерез точку 21234Значение IО123Значение IО123Значение JОNl2-N3-NЗначение JОNl2-N3-N
Число проходов пчерез точку 2
1
2
3
4
Число проходов пчерез точку 2
1
2
3
4
Значение I
О
1
2
3
Значение I
О
1
2
3
Значение J
О
Nl2-N3-N
Значение J
О
Nl2-N3-N
М+1МM-N
Ввдно, что при выполнении цикла М раз (т. е. при М + 1-ом попадании в точку 2) I = M, a J«M N. Так как 1 = М, то мы покидаем цикл и попадаем в точку 5. Таким образом, при достижении точки 5 работа программы заканчивается и мы получаем J = М • N. Это показывает, что программа правильно работает для любых значений М и N. Однако отметим, что наша трассировка предполагает, что М>0, и поэтому значение I в конце станет равным М (оно начинается с 0 и увеличивается на 1 при каждом прохождении цикла). Если М < 0, то цикл будет повторяться, а значение I никогда не станет равным М; следовательно, программа не сможет закончиться. Таким образом, блок- схема в действительности работает правильно' только для М > 0. При любых целых значениях М и N и при условии М^О, как это видно из проведенной трассировки, работа блок-схемы закончится с I — М • N.
Кроме того, отметим, что в таблице есть «пробел» (многоточие). Как определить, какие значения I и J должны стоять в таблице после этого пробела? И как мы можем быть уверены, что они именно такие? На первый вопрос можно ответить следующим образом: прослеживая работу по блок- схеме и заполняя первую часть таблицы, мы начинаем понимать, как изменяются значения I и J при каждом проходе через цикл (у нас есть «образ» этого изменения), и пользуемся этим пониманием для предсказания значений I и J в момент М 4- 1-го попадания в точку 2. Что касается второго вопроса, то для полной уверенности требуется провести доказательство. Из таблицы следует, что всякий раз, когда мы попадаем в точку 2, имеем J=I-N. Попробуем доказать это
методом индукции по п- числу проходов через точку 2 (см. разд. L2, где имеется исчерпывающее объяснение такого индуктивного доказательства).
При первом попадании (n = 1) в точку 2 (сюдЬ мы приходим из точки 7) имеем. 1^0 и 1=0. Утверждение J=I-N=0-N=0, таким образом, справедливо.
Предположим, что J = I • N справедливо при n-ом попадании в точку 2. Нужно показать, что если мы попадем в точку 2 в п+1-й раз, то утверждение J=LN опять будет справедливо. Пусть I и J при n-ом проходе через точку 2 принимают1 значения Itf и Jn. Тогда гипотеза индукции есть Jn=IneN. Единственный способ вновь попасть в точку 2 — выполнить тело цикла (точнее, пройти «по петле»), пройдя через точки 3, 4 и возвратясь опять в точку 2. Если это именно так,; то, однажды проследив выполнение тела цикла, мы видим, что при возвращении в точку 2 новое значение I равно предыдущему значению плюс 1, а новое значение J — предыдущему значению плюс N. Таким образом,
In+1 In+ 1 И Jn+i = Jn + N,
Jn+1 = In • N + N = (In + 1) • N = In+1. N.
(По гипотезеиндукции)
Следовательно, если мы вновь попадем в точку 2 в п + 1-й раз, то J = I-N. Доказательство по индукции на этом заканчивается, и мы видим, что при любом попадании в точку 2 справедливо утверждение J = 1 • N.
Единственный способ закончить выполнение программы (перестать выполнять тело цикла) — попасть в точку 2 при справедливом условии I = М. В этот момент J — I • N — М • N, так как I = М. Дальше мы попадем в точку 5, и работа заканчивается с J=M-N. Отметим, однако, следующее: мы еще не показали, что выполнение действительно закончится. Мы доказали только, что при каждом попадании в точку 2 справедливо утверждение J = I • N, и если выполнение когда- либо закончится, то J = M-N. Точнее, доказательство того, что J =1 • N при каждом проходе через точку 2 не зависит от того, будет ли М>0 или нет. Следовательно, даже если М<0, то доказательство останется корректным и подтверждает справедливость утверждения J = I • N при каждом попадании в точку 2. Но если М<0, то цикл выполняется
«бесконечно», так как I никогда не достигает значения М. Таким образом, выполнение программы никогда не закончится, и значение J=M N нельзя вычислить правильно. Однако даже в этом случае всякий раз при достижении точки 2 будет справедливо утверждение J-LN (если это не так, то наше предыдущее доказательство неверно). Справедливость этого можно проверить, проследив за работой программы с каким-нибудь значением М < 0: работа не кончается, но утверждение J = 1 • N в точке 2 всегда будет справедливым.
Следовательно, необходимо еще доказать, что выполнение программы закончится. Для этого следует предположить, что М>0. Итак, надо доказать, что если М>0, то в конце концов при выполнении программы мы попадем в точку 2 с 1 = М. Это утверждение очевидно: оно следует из блок- схемы программы. Действительно, в начале работы I присваивается значение 0, а заз '.м при каждом проходе через цикл значение увеличивается на 1. Если учесть, что М — целое число и больше или равно 0, то очевидно, что I «дорастет» до М. Однако, чтобы получить более строгое доказательство, используем метод индукции.
Докажем с помощью восходящей индукции по m — переменной, удовлетворяющей условию 0^т<М, — что мы когда-нибудь достигнем точки 2 с I —М.
При первом попадании в точку 2 ймеем 1 = 0, т. е. утверждение справедливо для т=0. Если М = 0, то это уже обеспечивает конечный результат. .
В противном случае предположим, что мы в конце концов попали в точку 2 и I = m при 0 < m М. Требуется показать, что мы пдпадем в точку 2ис1 = ш+1. В точке 2 с I = m и0< m< М отношение 1 = М ложно, так как m< М, и, следовательно, мы пройдем по циклу и вернемся в точку 2. При таком возврате значение I увеличится на 1, т. е. очевидно, что мы попадем в точку 2 со значением I — m + 1, что й требовалось доказать.
Из изложенного выше можно заключить, что если М > 0, то в конце концов мы достигнем точки 2 с 1 = М. В этот момент отношение 1 = М истинно, и мы попадаем в точку 5, где работа программы заканчивается.
Итак, мы доказали (даже слишком подробно), что при выполнении нашей программы с заданными целыми М и N и условием М > 0 выполнение ее в конце концов закончится, и при этом мы получим J = М • N.
.ПРИМЕР 2. Мы провели предыдущее доказательство более подррбно, чем это фактически необходимо: Повторим это доказательство, опуская некоторые детали и формальности доказательства по индукции. В частности, доказывая справедливость некоторого высказывания в момент прохождения через какую-либо из точек внутреннего цикла (как это было в предыдущем примере), мы будем считать, что для этого нужно лишь показать, что: 1) высказывание справедливо при первом попадании в эту точку, и 2) если мы попали в эту точку и в этот момент высказывание справедливо, а затем выполняется цикл и мы вновь возвращаемся в исходную точку, то высказывание будет вновь справедливо. Если мы доказали оба этих утверждения, то, восстанавливая необходимые детали, можем доказать с помощью индукции по числу прохождений через точку, что исходное высказывание справедливо при любом попадании в данную точку. Приводя второй вариант этого доказательства правильности, мы будем «приписывать» ключевые утверждения, которые необходимо доказать, непосредственно тем точкам блок-схемы, к которым они относятся. Это поможет яснее представлять нам этапы доказательства правильности.
Докажем теперь, что приведенная на рис. 2.2 блок-схема правильна, т. е. если ее начать выполнять с М и N, имеющими некоторые целые значения, причем М>0, то выполнение в конечном итоге закончится с J — М • N.
Вначале докажем, что при попадании в точку 2 J— I • N.
При первом попадании в точку 2 при переходе из точки 1 имеем 1=0 и 1=0. Таким образом, утверждение J = I • N =0 • N =0 справедливо.
Предположим, что мы попали в точку 2 и утверждение J = I • N справедливо. Пусть I и J в этой точке принимают значения 1П и Jn, т. е. Jn = In- N. Предположим теперь, что мы прошли по циклу (от точки 2 через точки 3, 4 вновь в точку 2). При возвращении в точку 2 I и J принимают новые значения 1п+1 и Jn+b которые можно представить следующим образом:
In.1 ^П + 1)
Jn+1 = In • N+N = (In+1) ‘ N = In+1 • N. 4
<Таккак3Л-1Л- N) .
Рис. 2.2.
Следовательно, при очередном попадании в точку 2 высказывание J = 1 • N вновь справедливо, что и требовалось доказать, т. е. при любом попадании в точку 2 справедливо высказывание J — I • N.
Теперь докажем, что в конце концов попадем в точку 2 со значением 1 = М. При первом попадании в точку 2 имеем 1=0. При последующих попаданиях, если таковые есть, I каждый раз увеличивается на 1. Так как значение М нигде в программе не изменяется и мы предположили, что М>0, то очевидно, что в какой-то момент I станет равным М.
Если мы попадем в точку 2 при 1 = М, то будет верно и J = I. N = М • N. Отношение I = М в этот момент истинно, и мы попадаем по стрелке с пометкой Т (TRUE — истина) в точку 5 с J = М • N. На этом доказательство правильности программы заканчивается.
ПРИМЕР 3. Докажем правильность программы вычисления целого частного IQ и остатка IR от деления Л на J2, где Л и J2 — любые два целых числа, удовлетворяющие условиям О С Л и 0< J2. Операция деления в программе для вычисления IQ и IR не используется. Один из способов указать, что делает программа, — сказать, что она вычисляет два целых числа IQ и IR, таких, что Л / J2 e IQ + IR/12 и 0< IR < 12. Это можно записать так: Л—TQ-J2 + IR и 0<IR<J2. Как и выше, свяжем непосредственно с блок-схемой программы (рис. 2.3) допущения о входных данных и те утверждения, правильность которых требуется доказать.
Рис. 2.3.
Отметим, что допущения о считываемых значениях для Л и J2 мы поставили непосредственно после ввода. Это указывает на то, что правильность работы программы будет доказываться только для данных, удовлетворяющих этому допущению. Утверждение о правильности блок-схемы связано с ее последней точкой. Кроме того, с начальной точкой цикла связываются два основных факта, которые требуется доказать. Теперь проследим за работой программы с символическими данными для Л и J2 и выясним, как ведут себя инварианты цикла (основные утверждения о цикле):
Число проходов пчерез точку 21234Число проходов пчерез точку 21234IQIR
Число проходов пчерез точку 2
1
2
3
4
Число проходов пчерез точку 2
1
2
3
4
ОЛ
Л - J2
Л-212
Л-3 12
Пока IR < J2
Отметим, что при каждом попадании в точку 2
IR —Л—IQJ2, или
Л = IQJ2 + IR.
Это утверждение идентично утверждению о правильности, за исключением того, что последнее еще включает условие 0< IR < J2. Таким образом, остается лишь показать, что цикл когда-нибудь закончится и при этом будет выполняться условие 0<IR<J2. Легко установить, что высказывания, связанные с точкой 2, утверждают именно эти факты о цикле.
Ддя доказательства правильности программы сначала покажем, что, как только мы попадем в точку 2, Л =IQ • J2 + IR.
При первом проходе через точку 2, двигаясь от точки START к точке 2, имеем IQ—0 и IR = Л, т. е. утверждение
Л—IQ* J2 + IR—0* J2 +Л—0 + Л—Л
справедливо.
Предположим, что выполнение программы дошло до
точки 2 и справедливо утверждение J1=IQ-J2 + IR. Пусть IQ и IR в этот момент принимают значения IQn и IRn. Наше справедливое по предположению утверждение записывается так: Л = IQn -12 + IRn (гипотеза индукции). Пройдем один раз по циклу (от точки 2 через точки 3, 4 опять в точку 2). При возвращении в точку 2 новые значения IQ и IR будут такими: IQn+isIQn+1 и IRn+1 =IRn~ J2. Таким образом,
IQn+1 • J2 + IRn +1 = (IQn + 1) • J2 + (IRn - J2) =
= IQn • J2 + J2 + IRn - J2 = IQn • J2 + IRn = Л,
^ (По гипотезе индукции)
Что и требовалось доказать, т. е. при попадании в точку 2 справедливо высказывание Л = IQ • J2 + IR.
Теперь докажем, что в конце концов мы попадем в точку 2 при условии 0<IR<F2. Для этого покажем вначале, что при всяком проходе через точку 2 имеем 0 ^ IR.
. При первом проходе через точку 2 имеем IR = Л и по предположению 0 < Л. (Отметим, что это единственное место в доказательстве, где используется данное предположение. Таким образом, если бы мы не потребовали IR > 0, то блок- схема должна была бы работать правильно даже для отрицательных значений Л.)
.♦ Предположим, что мы попали в точку 2 и 0<IR. Пусть в этот момент IR принимает значение IRn. Если мы через цикл вернемся вновь в точку 2, то 1R примет новое значение IRn+1 =IRn —J2. Но через цикл мы пройдем только в случае отрицательного ответа на проверку IRn< J2, т. е. если истинно отношение J2<IRn. Но если J2<IRn, то 0^IRn~ — J2 = IRn+i истинно. Таким образом, мы доказали, что О < IR при каждом попадании в точку 2.
Докажем затем, что в конечном итоге мы попадем в точку 2 при справедливом.условии IR < J2 (т. е. в этом случае мы будем иметь 0 < IR < 12). Отметим, что при каждом проходе через цикл значение IR уменьшается на значение J2. Так как значение J2 в программе нигде не изменяется и так как KJ2, то, очевидно, при каждом повторении процесса IR уменьшается по крайней мере на 1. Следовательно, в конце концов значение IR должно стать меньше J2 (с помощью
метода индукции это можно доказать более строго —см. упражнение 7).
Таким образом, в какой-то момент мы попадем в точку 2 со справедливыми условиями 0<IR<J2 и Л = IQ • J2 + IR. В этот момент условие IR<J2, естественно, истинно, и из точки 2 мы идем в точку 5, а затем в точку 6. Так как на пути из точки 2 в точку 6 ни одна переменная не изменяется, то очевидно, что в момент прихода в точку 6 утверждение о правильности будет справедливо. Следовательно, мы доказали, что при выполнении программы с целыми Л и J2, удовлетворяющими условиям 0 < Л и 1CJ2, программа в какой-то момент закончится, и будут справедливы отношения Л—IQ-J2 + IR и 0<IR<J2, т. е. IQ и IR будут целыми частным и остатком от деления Л на J2.
УПРАЖНЕНИЯ
Докажите, что программа (рйс. 2.4) вычисляет произведение М HaN' при условии, что М и N имеют некоторые целые значения и М > 0. Что произойдет, если М < 0?
Предположим, что на блок-схеме (рис. 2.4) по ошибке вместо проверки 1 — 0 стоит проверка I — 1. На каких этапах доказательства правильности это выяснится? Предположим, что J по ошибке получает начальное значение 1, а не 0. Где это выяснится при доказательстве? Представим себе, что J присвоено вначале значение N, а не 0 и, кроме того, проверка идет на 1 — 1. Для каких значений данных программа будет верно вычислять произведение M N? Как это показать, исходя из доказательства правильности?
Докажите, что программа (рис. 2.5) вычисляет и печатает значение М. Предполагается, что М й N — целые числа, причем 1< М и 1 <N. Будет ли эта программа работать правильно, если в качестве N введено значение 0? Измените блок-схему так, чтобы программа работала правильно и при N — 0.
Докажите, что программа (рис. 2.6) вычисляет и печатает значение М! — 1.2 • 3 •... • (М - 1) • М при условии, что в качестве М вводится положительное целое число.
Докажите, что если в программу (рис. 2.7) в качестве значений I и J вводятся соответственно 10 и Jo, причем 0<1о, то печатаемые в конце значения 1и J будут соответственно 0 и Jo + 2 • 10.
Докажите для блок-схемы (рис. 2.8) следующее. Если в качестве входных значений Л и J2 вводятся целые Ло, J20 и К Ло, то программа закончится и 1 будет наименьшим неотрицательным целым, удовлетворяющим условию J20< il0 -21. Указание. Доказывайте, что при достижении последней точки цикла Л — Ло-21 и для всех целых i при условии 0<i<l будет справедливо утверждение Л 0 • 21 < J20.
. Покажите, что для блок-схем, общий вид которых приведен на рис. 2.9,о, в момент окончания цикла I + 1 <F(I) для всех I. Докажите это
Рис. 2.5.
Рис. 2.4.
Рис. 2.6.
Рис. 2.8.Рис. 2.8.
Рис. 2.8.
Рис. 2.8.
строго с помощью индукции по числу попаданий в точку 2. Покажите, в частности, что при попадании в точку 2 в n-й раз I >I0 + п - 1. Таким образом, мы будем знать, что для п0 —М-10+2 (или n0—1, если М <10) по еле "попадания в точку 2 по крайней мере п0 раз 1>10 + (М-10 + + 2) - 1 — М + 1. Следовательно, по крайней мере после п0 раз мы попадем в точку 2 с ложным условием I <М и выйдем из цикла.
Покажите для блок-схем, общий вид которых приведен на рис. 2.9,6, что для любых I при окончании работы G(I)<I - 1. Докажите это строго, используя метод индукций.
В большинстве доказательств правильности мы довольно неформально доказывали конечность цикла. В этой задаче показана необходимость делать это более строго.
Для каждой из программ, блок- схемы которых приведены на рис. 2.10-2.14, определите при каких значениях данных программа будет заканчиваться. Найдите эти значения также для случаев, когда на блок-схемах сделаны следующие замены:
Рис. 2.10.
Глава 2
£
Рис. 2.9.
Доказательства правильности блок-схем программ
Рис. 2.11.
Рис. 2.12.
Глава 2Глава 2
Глава 2
Глава 2
на рис. 2.10 проверка I < М заменена на I = М;
на рис. 2.11 команда I -► I + 1 заменена на I -* 1 + 2;
на рис. 2.14 команда J — J + 1 заменена на J -* J + 2.
Для блок-схем, приведенных на рис. 2.12 и 2.13, дайте формально (по индукции) доказательство окончания работы программы. Например, программа на рис. 2.13 заканчивается при любых X > 0. Докажите это с помощью индукции по числу попаданий в начало цикла. В частности, покажите, что если исходное значение X есть Х0>0, то при n-ом попадании в начало цикла справедливо утверждение X = 2П-1 • Хо.
ДОПОЛНИТЕЛЬНЫЕ ПРИМЕРЫ ДОКАЗАТЕЛЬСТВАПРАВИЛЬНОСТИ БЛОК-СХЕМ ПРОГРАММ
В этом разделе рассмотрены несколько дополнительных примеров доказательства правильности для блок-схем программ с помощью метода индукции (без некоторых деталей).
Читатель должен сам разобраться в ключевых утверждениях, связанных с блок-схемами. Для этого необходимо проследить, как выполняется программа с символическими данными, и обратить внимание ца то, какие отношения истинны в момент прохождения через начало цикла.
ПРИМЕР 1. Мы хотим доказать, что приведенная на рис. 2.15 блок-схема вычисляет и печатает среднее значение и максимальное из всех чисел во входном файле. Сначала докажем, что справедливо утверждение, связанное с точкой 2.
Если программа выполняется с входным файлом, содержащим одно и более вещественных чисел, то можно выполнять первый блок (так как входной файл не пуст), и при первом попадании в точку 2 значение N равно 1, а значение XLGST и SUM— первые числа в исходном входном файле. Таким образом, при первом достижении точки 2 утверждения о N, XLGST и SUM справедливы.
Предположим, что мы находимся в точке 2 и утверждения о N, XLGST и SUM справедливы. Нужно показать, что, если мы пройдем по циклу и вернемся в точку 2, эти утверждения будут оставаться верными. Отметим, что при про-
START
XLGST
Входной файл содержит по
—крайней мере один элемент.
Элементы тайла-вещественные х числа (утверждение, относящееся к данным)
SUM ^ XLGST N
-Конец
файла
Нет
SUM*SUM + X
X < XLGST
XLGST *X
6
При каждом попадании 8 эту точку N равно числу считанных •
-элементов, XLGSTравно значению максимального из считанные элементов, SUM равно ' сумме считанных элементов «иант цикла). В конце кон- < попадаем о эту точку, когда во входном файле не будет 1 больше элемен- AVG *- SUM/N тов (утвержде- | ние о конечности I цикла)
AVG, XLGST
STOP
8 конце концов мы попадаем 6 эту точку со значениями: № -Среднеевсех чисел, входящих в исходный входной файл
XVaSV Максимальное из всех чисел, входящих в исходный файл (утверждение о правильности программы)
Рис. 2.15.
хождении цикла в X считывается очередное значение из входного файла (файл по определению не пуст, иначе цикл не выполнялся бы), значение N увеличивается на 1, а значение SUM становится SUM + X. Поэтому при возврате в точку 2 значение N вновь указывает число считанных из входного файла данных (больше предыдущего на 1), а значение SUM вновь будет суммой всех считанных из файла чисел (сумма всех предварительно считанных чисел плюс последнее прочитанное число). Затем при каждом выполнении цикла переменная XLGST (мы предполагаем, что это наибольший из всех считанных элементов) сравнивается с новым элементом, считанным последним в X из файла. Если отношение Х< XLGST истинно, то по блок-схеме XLGST не изменяется, и это верно, так как ее новое значение представляет собой самое большое число из всех считанных из файла. Если же отношение Х< XLGST ложно, то последний считанный элемент — самый большой из всех считанных, и поэтому в блок-схеме предусмотрена запись значения X в XLGST. Таким образом, при возврате в точку 2 (пройдем ли мы в нее через точки 2,3,4 или через точки 2,3,5,6) XLGST всегда будет наибольшим из всех считанных до этого значений, что и требовалось доказать, т. е. при каждом проходе через точку 2 утверждения, касающиеся N, XLGST и SUM, справедливы.
Ясно, что в конце концов мы попадаем в точку 2, когда во входном файле не останется больше элементов, которые можно было бы прочитать. Это произойдет потому, что при каждом прохождении цикла считывается один очередной элемент файла, а мы (неявно) предполагали, что входной файл содержит конечное число элементов. Следовательно, после конечного числа прохождений через цикл все элементы входного файла будут прочитаны. После этого проверка на конец файла даст ответ ДА, и мы выйдем из цикла, имея следующие значения: N — число элементов чисел, считанных на данный момент; XLGST — наибольший из всех элементов в исходном входном файле; SUM — сумма всех элементов в исходном входном файле. '
При попадании в,точку 8 переменная AVG будет иметь значение AVG — SUM/N, a XLGST — то же значение, которое указано выше. Таким образом, работа программы в конце концов закончится, и, когда это произойдет, AVG и XLGST будут иметь требуемые значения.
Рис. 2.16.
ПРИМЕР й2. Блок-схема, приведенная на рис. 2.16, соответствует алгоритму Эвклида для нахождения наибольшего общего делителя для любых двух положительных целых чисел. GCD(X, Y) — наибольший общий делитель X и Y, т. е. наибольшее целое, делящее без остатка и целое X, и целое Y. REMAINDER (X, Y) —целый остаток от деления целого X на целое Y. Алгоритм Эвклида основан на следующей мате-
матической закономерности: для двух положительных целых чисел 0<X<Y, GCD(X, Y) = GCD[REMAINDER (Y, Х),Х]. Мы используем этот результат для доказательства правильности нашей программы.
Сначала покажем, что при каждом попадании в точку 2 справедливо утверждение: 0< S < L и GCD(S, L) = GCD(A, В).
При первом попадании в точку 2(из точки /) либо S = A и L = В и тогда справедливо А < В, либо 8 = ВиЬ = Аи тогда утверждение А < В ложно. В любом случае S < L и GCD(S, L) — — GCD(A, В). Исходное предположение гласит, что А и В — неотрицательные целые числа, и поэтому при первом попадании в точку 2 независимо от того, выполняются ли равенства S = А или S = В, всегда будет истинно отношение 0<S. Поэтому при первом проходе через точку 2 имеем О <S < L и GCD(S, L) = GCD(A, В).
Предположим, что мы находимся в точке 2 и справедливо утверждение O^S<L и GCD(S, L) = GCD(A, В). Пусть S и L в этот момент принимают значения Sn и Ln, т. е. справедливо утверждение 0<Sn<Ln и GCD(Sn, Ln)“GCD(A, В) (гипотеза индукции). Если мы выполним цикл и вернемся в точку 2, S и L примут новые значения: Sn+1= R = = REMAINDER(Ln,Sn), Ln+1 = Sn. Используя приведенную выше математическую закономерность, мы должны получить:
GCD(Sn+1,Ln+l) = GCD[REMAINDER(Ln,Sn),Sn] =
= GCD(Sn,Ln) = GCD(A,B),
(По закону) (По гипотезе индукции)
Таким образом, 0 < REMAINDER(Ln , Sn) < Sn, и отсюда 0< Sn+1= REMAINDER(Ln, Sn)< Lfl+1= Sn, что и требовалось доказать, т. е. 0<S<L и GCD(S,L) — GCD(A,B) при любом попадании в точку 2. v
Теперь докажем, что когда-нибудь мы попадем в точку 2 с 8=0. Действительно, при каждом выполнении цикла переменная S становится, равной REMAINDERS , S), но известно, что REMAINDERS, S) < S, и поэтому можно считать, что S при каждом выполнении цикла уменьшается по крайней мере на 1. В то же время известно, что 0<S при каждом проходе через точку 2. Следовательно, в процессе уменьшения переменная S когда-нибудь станет равной 0. В этот момент
STARTSTARTSUMSUMSTOPSTOPI < NI < NSUM * SUM + XiSUM * SUM + Xi33
START
START
SUM
SUM
STOP
STOP
I < N
I < N
SUM * SUM + Xi
SUM * SUM + Xi
3
3
в точке 2 мы будем иметь S = 0 и GCP(S, L) = GCP(0, L) = = GCP(A, В). Однако GCD(0, L) = L, так как очевидно, что L делит и 0, и L без остатка и является наибольшим из таких чисел. Вследствие справедливости условия S = 0 мы приходим В точку 6 с L = GCD(0, L) = GCP(A, В).
ПРИМЕР 3. Мы хотим доказать, что приведенная на рис. 2.17 блок-схема обеспечивает правильное вычисление суммы §Х , — X, + Х2 +...+ XN. Сначала покажем, что при каждом попадании в точку "2 выполняются соотношения KKN'+l и SUM» IXJ=X14-... + XI_1.
XoX2,...,Xn- массиб деще-
•ственных чисел. Его длина N>1
При каждом попадании 6 эту точку,—UUNtl и SUM-EXj-X^Xg*......*х1-1 (причем £xtso;
В конце концов мы попадаем в эту /почку при I- N+1
В конце концов мы попа*
—даем в эту точку при
8иМ-ЕХ,-Х*Х2*...*Хм
При первом попадании в точку 2 имеем I — 1 и SUM — 0; следовательно, утверждения, что 1 < I < N 4-1 и SUM — 1-1 о
= SXj — SXj ■ 0, справедливы.
Предположим, что мы находимся в точке 2h1<ICN+1
и SUM= ZXj справедливы. Пусть I и SUM в этой точке при- j=i
нимают значения Jn и SUMn, т. е. lCIn<N+l и SUMn— 1ц—1
— ZXj =Хт + Х2 ++ Х1п_р Если мы пройдем по циклу и j=iп
вернемся в точку 2, то получим In+1 = In4- 1 и
SUMn+1 = SUMn + х,п е (X, +х2+... + xIn-a + XIn =
In (V1HIn+i1
= ZXj= IXj= IXj. J=1 J J=1J=1
Кроме того, если цикл повторялся, то проверка In < N давала положительный ответ, а так как истинно и утверждение
К In< N 4- 1, то, следовательно, 1 < In < N. Но в этом случае при возврате в точку 2 имеем 1 <2< In4-1 =In+1<N4-1. Таким образом, справедливость утверждений 1<I<N4-1 и
. SUM — I Xj при прохождении точки 2 доказана.
Легко видеть, что если 1 < N, то переменная I, увеличиваясь с 1 каждый раз на 1, в конце концов станет равной N4-1.
В этот момент проверка даст отрицательный ответ и мы попадем в точку 4, где имеем SUM”EXjH I=N4-1, 0>M)-1 n J=1
или, что тоже самое, SUM =д Xj”^ XJe Таким образом, мы доказали, что работа программы (достигнув точ
ки 4) прекратится и SUM — EX,. j=i
ПРИМЕР 4. Во многих примерах (блок-схема, о которой идет речь в данном примере, показана на рис. 2.18) мы будем использовать итерационные блоки вида, прйведенного на рис. 2.19. Предполагается, что действия выполняются в следующем порядке: начальная установка, проверка, тело цикла (если проверка истинна), увеличение, проверка и т. д. Проверки выполняются сразу же за начальной установкой и после увеличения (этот порядок несколько отличается от порядка, принятого в циклах Фортрана). Точка 2, фигурировавшая на всех предыдущих блок-схемах, здесь помещается непосредст-
венно перед проверкой, вслед за начальной установкой и увеличением.
Вначале докажем, что при каждом попадании в точку 2 J=0 и ни один из элементов Lb ..., LH не равен К.
При первом попадании в точку2 имеем J=0 и 1-1. Утверждение, что ни один из элементов Lb ..., LI4 = Lo не равен К, очевидно справедливо, так как элементов вообще еще нет.
Предположим, что мы попали в точку 2 и верно утверждение, относящееся к этой точке. Пусть I и J в этот момент принимают значения 1П и Jn. Таким образом, мы имеем Jn 0 и 1 < In< N 4-1, и ни один из элементов Lb ..., LIn t не равен К. Если мы выполняем цикл, т. е. проходим через точки 3, 4 к точке 2, то, вернувшись в точку 2, получаем Jn+i^O (так как в цикле значение J не изменялось) и In+i=I-+L Если цикл выполняется, то условие In<N было истинным, так же как и 1 < In< N 4- 1; следовательно, l<In<N. Но при возврате в точку 2 должно выполняться и соотношение 1<2<1п4-1^ In+1<N + 1. Кроме того, мы знаем, что проверка К = Lin дала отрицательный ответ, и, следовательно, ни один из элементов Lb ..., LIn_1 не равен К и элемент LIn также не равен К. Таким образом, при возврате в точку 2 ни один из элементов Lb ..., LIn_b LIn,In+?1 не равен К. Следовательно, мы доказали, что при попадании в точку 2 справедливы утверждения J = 0, 1 < I < N 4- 1 и ни один из элементов Lb ..., LI_1 не равен К.
Так как N>1, то очевидно, что по мере проходов по циклу значение I будет возрастать от 1 до N 4-1. В последний момент проверка I<N даст отрицательный результат, и мы попадем в точку 5, а затем в точку 7, с I = N4-1 и 1=0. Кроме того, мы знаем, что ни одйн из элементов Lb ..., Ьн^+^^не равен К^ Но существует и еще один возможный путь, ведущий из точки 2 в точку 7,—через точки 3 и 6. Если мы попадем в точку 7 по этому пути, то известно, что J = I / 0 (так как 1<К N4-1), и проверка К = Li оказалась истинной, т. е. Li =Lj = К. Однако мы также знаем, что ни один из элементов Lb ..., L^^j^ не был равен К (так как это справедливо в точке 2 и на пути от точки 2 до точки 7 значение I нигде не изменялось). Из этого следует, что в конце концов мы попадем в точку 7, и будет справедливо приведенное утверждение о правильности.
УПРАЖНЕНИЯ
Постройте блок-схему программы, которая читает последовательность элементов (вещественные числа) по одному и находит максимальный и минимальный из них, определяет общее число элементов и число отрицательных элементов. Прочитав все данные и найдя требуемые четыре результата, программа печатает их и останавливается. Докажите правильность вашей блок-схемы.
Для любых двух положительных целых чисел II, 12 (И<12) известно, что наибольший общий делитель II и 12 есть GCD (И, 12) — —GCD (II, 12 -II)—CGD(I2 - И; И). Кроме того, известно, что если 12 > О, tqGCD (0,12)—12. Используйте эти два факта для доказательства того, что приведенная программа (рис. 2.20) правильно вычисляет GCD(X S),
Постройте блок-схему программы для поиска максимального значения в массиве вещественных чисел Xv Х2 ,..., Хп размером N>1. Исходное
допущение: все элементы массива уже имеют начальные значения. Докажите правильность блок-схемы.
Постройте блок-схему для поиска в обратном порядке в массиве Lt ,..., Ln (N > 1) элемента, равного К (т. е. поиск начинается с LN и кончается на L 0. При завершении работы программы некоторая переменная, например J, должна быть равна индексу первого с конца элемента массива, равного К (Lj = К^ или же нулю, если такого элемента обнаружить не удалось. Докажите, что ваша программа правильна.
Постройте блок-схему программы, меняющей местами значения массивов Хь Х2,..., XN и Yb Y2,..yN (М > 1). Докажите правильность программы.
Постройте блок-схему, по которой вычисляется и печатается значение наименьшего из чисел Фибоначчи, превышающего значение 5000. Определение чисел Фибоначчи дано в разд. 1.3, в примере 2. Докажите правильность программы.
Постройте блок-схему программы для вычисления суммы всех положительных элементов SUMPOS и суммы всех отрицательных^ SUMNEG в массиве Xb...,XN(N > 1) вещественных чисел. Докажите правильность программы.
МЕТОД ИНДУКТИВНЫХ УТВЕРЖДЕНИЙ
Все блок-схемы, о которых шла речь в разд. 2.2 и 2.3, содержали оДин-единственный цикл. Основным приемом при доказательстве правильности этих блок-схем было доказательство того, что при попадании во входную точку цикла всегда будет справедливо некоторое утверждение (инвариант цикла). Чтобы доказать это, мы показывали, во-первых, что это утверждение справедливо при первом попадании на вход цикла, и, во-вторых, если мы попали в эту точку и утверждение справедливо, то после выполнения цикла и возврата во входную точку утверждение будет оставаться справедливым. Если структура блок-схемы становится более сложной, например, за счет нескольких вложенных циклов или если существует несколько возможных путей между ключевыми точками, то рассмотренный выше метод уже трудно использовать для доказательства того, что при каждом входе в цикл справедливо соответствующее утверждение.
Предположим, например, что мы имеем дело с блок- схемой общего вида, приведенной на рис. 2.21. Наш стандартный прием для доказательства справедливости любого из двух утверждений о циклах в данном случае может оказаться не всегда пригодным. Доказать, что утверждение 1 справедливо при -первом попадании в точку 7, вероятно, довольно просто. Однако может оказаться сложным доказа-
Рис. 2.21.
тельство того, что если мы находимся в точке 1 и справедливо утверждение 1, то при проходе по циклу и возврате в точку 1 утверждение 1 останется справедливым. Трудность заключается в том, что это не просто Проход по элементарному циклу и возврат в точку 1. Перед возвратом в точку 1 несколько раз будет выполняться внутренний цикл. Следовательно, первым, вероятно, нужно доказать утверждение 2, а затем уже использовать его для доказательства того, что при возврате в точку 1 утверждение 1 останется справедливым. Пытаясь, однако, доказать справедливость утверждения 2 при каждом попадании в точку 2, мы сталкиваемся с аналогичными трудностями. Фактически нужно показать, что при любом попадании в точку 2 из точки 1 (не только в первой момент) будет справедливо утверждение 2. Но для этого
скорее всего надо будет знать, что утверждение 1 справедливо, а этого-то мы еще и не доказали.
Для того чтобы устранить эту и аналогичные трудности, нужно обобщить метод доказательства, рассмотренный выше. Обобщенный метод мы назовем методом индуктивных утверждений. Прием, которым мы пользовались выше, есть просто некоторое упрощение этого общего метода, приспособленное для программ, содержащих лишь один-единствен- ный цикл. При доказательстве правильности программ методом индуктивных утверждений доказательство конечности программы проводится отдельно от доказательства справедливости некоторых ключевых утверждений при достижении соответствующих точек программы. Для уточнения использованных понятий дадим два определения.
Определение 1. Пусть А — некоторое утверждение, описывающее предполагаемые свойства данных в программе (блок-схеме), а С — утверждение, описывающее то, что мы по предположению должны получить в результате процесса выполнения программы (т. е. утверждение о правильности). Будем говорить, что программа частично правильна (по отношению к А и С), если при каждом выполнении ее с данными, удовлетворяющими предположению А, будет справедливо утверждение С при условии, что программа закончится. Другими словами, если выполнять программу с данными, удовлетворяющими А, то либо она не заканчивается, либо заканчивается и удовлетворяется утвбрждение С.
Таким образом, программа может быть частично правильна, даже если она не заканчивается при некоторых (или всех) входных данных, удовлетворяющих А. Необходимо только, чтобы, если уж программа заканчивается, было справедливо утверждение С с данными, удовлетворяющими А.
Определение 2. Будем называть программу (блок-схему) полностью правильной (по отношению к А и С), если она частично правильна (по отношению к А и С) и заканчивается при всех данных, удовлетворяющих А.
Описаете метода индуктивных утверждений
Для доказательства частичной правильности (по отношению к А и С) программы (блок-схемы) поступим следующим образом. Свяжем утверждение А с началом программы, а утверждение С с конечной точкой программы. Кроме этого, выявим некоторые закономерности, относящиеся к значениям переменных, и свяжем соответствующие утверждения с другими точками программы. В частности, свяжем такие утверждения по крайней мере с одной из точек в каждом из замкнутых путей в программе (в циклах). Для каждого пути в программе, ведущего из точки i, связанной с утверждением Ai, в точку j, связанную с утверждением Aj (при условии, что на этом пути нет точек с какими-либо дополнительными утверждениями), докажем, что если мы попали в точку i и справедливо утверждение Аь а затем прошли от точки i д0 точки j, то при попадании в точку j будет справедливо утверждение Aj. Для циклов точки i и j могут быть одной и той же точкой.
Чтобы убедиться, что таким способом мы действительно доказываем частичную правильность программы, докажем следующую теорему.
Теорема 1. Если можно выполнить все действия, предусмотренные описанным методом индуктивных утверждений для некоторой программы, то эта программа частично правильна (относительно А и С).
Доказательство. Предположим, что мы выполнили доказательство правильности методом индуктивных утверждений. Начнем выполнять программу с начальными данными, удовлетворяющими допущению А. Нам нужно доказать, что если выполнение программы закончится, то утверждение С будет справедливо, но фактически мы докажем нечто более общее. Мы покажем, что каждый раз, когда мы попадаем в некоторую точку программы, с которой связано определенное утверждение, данное утверждение будет справедливым. Это, в частности, предполагает, что, если мы достигнем последней точки программы (т. е. программа закончится), утверждение С будет справедливо. Доказательство проведем с помощью индукции по п —числу точек программы, через которые мы уже прошли (учитываются точки, связанные с некоторыми утверждениями).
Предположим, что в процессе выполнения программы мы попали в первую из этих точек (п — 1). Нужно показать, что связанное с этой точкой утверждение справедливо. Но первая точка —это начальная точка программы, и с ней связано исходное допущение А. Мы знаем, чта оно справедливо, ибо речь идет о выполнении программы с такими начальными данными, для которых оно выполняется.
Предположим, что мы дошли до некоторой точки (n-й), с которой связано утверждение Ain. Допустим, что это утверждение справедливо (гипотеза индукции). Нужно показать, что если выполнение продолжается и мы попадаем из n-й точки в n+1-ю, то связанное с ней утверждение Ajn+1 будет также верным. Очевидно, что между n-й и n+1-й точками существует некоторый путь. Так как речь идет о методе индуктивных утверждений, то, следовательно, мы уже рассматривали этот путь и показали, что если находимся в n-й точке со справедливым утверждением Ain, а затем проходим из n-й в n+1-ю точку, то при ее достижении будет справедливо утверждение Ain+1. Из этого факта вместе с гипотезой о справедливости утверждения A in следует, что при попадании в n+1-ю точку справедливо утверждение Ajn+1, т. е. то, что требовалось доказать.
Таким образом, мы доказали по индукции, что если программа удовлетворяет условиям метода индуктивных утверждений, то при достижении любой из точек программы, с которыми связаны определенные^ утверждения, соответствующие утверждения будут справедливы. Следовательно, если будет достигнута конечная точка программы, то будет справедливо утверждение о правильности, связанное с этой точкой, т. е. рассматриваемая программа является частично правильной.
Для доказательства полной правильности программы сначала методом индуктивных утверждений нужно доказать ее частичную правильность, а затем уже доказать, что программа когда-нибудь завершится. Проиллюстрируем этот прием на нескольких примерах программ, содержащих вложенные циклы. Читателю рекомендуется самому попытаться доказать правильность одной из этих программ с помощью
использованного выше ограниченного метода, для того чтобы лучше оценить эффективность более общего метода.
ПРИМЕР 1. Требуется доказать, что приведенная на рис. 2.22 блок-схема устанавливает переменную XLGST равной максимальному значению в массиве X. Указанные на блок-схеме утверждения — индуктивные утверждения, необходимые для доказательства частичной правильности программы. Так как все эти утверждения имеют аналогичную форму и начинаются с фразы: «При достижении этой точки справедливо», тр это начало фразы мы опускаем. Утверждение о конечности программы мы не помещаем на блок-схеме,
Рис. 121.
Доказательства правильности блок-схем программ 59 поскольку доказательство этого утверждения проводится отдельно от доказательства частичной правильности.
Для доказательства частичной правильности нужно исследовать все пути программы. Рассмотрим их по порядку.
L Путь из точки 1 в точку 2. Предположим, что мы находимся в точке 1 и связанное с ней утверждение справедливо, т. е. данные удовлетворяют исходному допущению. Перейдем из точки 1 в точку 2 Нужно показать, что после прихода в точку 2 связанное с этой точкой утверждение будет справедливо. Если мы попали в точку 2 из точки J, то имеем 1 = 1 и XLGST —ХЬ1. Так как М>1, то очевидно, что 1<1 = 1<М + 1. Поскольку XLGST= Хм и I = 1, то утверждение о XLGST справедливо.
2 Путь из точки 2 в точку 3. Предположим, что мы находимся в точке 2 и справедливо связанное с нею утверждение. Перейдем из точки 2 в точку 3. Требуется показать, что при попадании в точку 3 будет справедливо утверждение, связанное с этой точкой. Если мы дошли до точки 3 (из точки 2), то имеем 1 = 1, а значение XLGST осталось неизменным. Так как N>1, 1<J = 1<N + 1, а значение I после точки 2 не изменялось, то 1<1<М + 1. Однако если мы пришли из точки 2 в точку 3, то известно, что проверка 1<М была истинной, и, комбинируя это отношение с 1<1<М + 1, получаем 1<1<М. Если 1 = 1 и 1 = 1, то из утверждения 2 получим XLGST = Хц. В противном случае (т. е. при 1^1) из утверждения 2 получаем, что XLGST равно максимальному из значений элементов в первых 1 — 1 строках массива X или, более точно, максимальному из значений элементов в первых 1 — 1 строках и из значений первых J —1 = 1 —1=0 элементов в I-й строке. Таким образом, очевидно, что при переходе из точки 2 в точку 3 утверждение, связанное с точкой 3, оказывается справедливым.
Путь из точки 3 через точки 4, 5 к точке 3. Предположим, что мы находимся в точке 3 и справедливо утверждение, связанное с этой точкой. Пройдем через точки 4, 5 к точке 3. Нужно показать, что при возврате в точку 3 соответствующее утверждение останется справедливым. Пусть I и J в исходном положении в точке 3 принимают значения 1П и Jn. Мы имеем 1<1П<М и l<Jn<N+l. При возврате в точку 3 через точки 4 и 5 получим In+1 —1П и Jn+1 = — Jn + 1. Следовательно, опять имеем 1 < In+1= In< М. Если
мы проходим по этому пути, то проверка Jn < N была истинной. Из этого, а такде из соотношения 1 < Jn < N.+ 1 получаем 1 < Jn< N. Таким образом, при возврате в точку 3 снова имеем 1 < 2< Jn+1 = Jn + 1 < N + 1. На всем пути перехода в точку 3 значение XLGST не изменялось, и известно, кроме того, что истинна проверка XIn Jn < XLGST. Если учесть истинность утверждения о XLGST до «прохода» по указанному пути, то данное утверждений остается истинным и при возврате в точку 3 с In+1 = 1П и Jn+1 — Jn4- 1. Так как .Xjn>J < XLGST, то неизменное значение XLGST снова будет.максимальным из значений элементов в первых In+1— 1 — In— 1 строках и из значений первых Jn+1 — 1 — — (Jn + 1) — 1 —, Jn элементов в 1п-й строке массива X.
Путь из точки 3 через точки 4, 6 в точку 3. Рассуждения для этого пути аналогичны приведенным для предыдущего пути, за исключением того, что при возврате в точку 3 XLGST будет иметь значения Х^ . Кроме того, так как выбран этот путь, проверка XInJn< XLGST была ложной и, следовательно, XLGST < XIn Jn. Таким образом, XIn>Jn больше максимального из значений элементов первых In — 1 строк и Jn — 1 элементов в 1п-й строке. Отсюда следует, что при возврате в точку 3 значение XLGST остается максимальным из значений элементов первых In+1 — 1 — In — 1 строк и первых Jn+1 — 1 — (Jn + 1). — 1 == Jn элементов в In+i = In-й строке массива X.
Путь из точки 3 через точку 7 в точку 2. Предположим, что мы находимся в точке 3 и справедливо соответствующее утверждение. Перейдем из точки 3 через точку 7 в точку 2. Нужно показать, что при возврате в точку 2 будет справедливо связанное с нею утверждение. Из точки 3 в точку 7 мы попадем только тогда, когда ложна проверка J < N. Но из утверждения, относящегося к точке 3, известно, что 1 < J < N + 1. Отсюда можно заключить, что J = N 4-1. Пусть I в точке 3 принимает значение 1П. Утверждение в точке 3 гласит: К In< М и XLGST равно максимальному из значений элементов в первых In — 1 строках и первых J — 1 = — (N4-1) — 1~N элементов в 1п-й строке массива X. Но так как в массиве X существует только N столбцов, то XLGST равно максимальному из значений элементов в 1П
строках массива X. Значение XLGST между точками 3 и 2 не изменялось, а значение I изменилось и стало равным I + L Так как в точке 3 было справедливо отношение 1 < In< М, то это означает, что 1 < 2 < In+1 = In+ 1 < М + 1 справедливо в точке 2. Следовательно, при достижении точки 2 будет справедливо утверждение: XLGST равно максимальному из значений элементов в первых I — 1 = = (In + l)j* 1 = In строках массива X.
Путь от точки 2 в точку 8. Предположим, что мы находимся в точке 2, справедливо соответствующее утверждение и мы переходим в точку 8. Необходимо показать, что при достижении точки 8 будет справедливо связанное с нею утверждение. Из точки 2 мы перейдем в тачку 8^ если была ложной проверка 1<М. Однако в точке 2 было справедливо неравенство 1 <I<М +1; следовательно, можно заключить, что I — М+1. Значение XLGST при переходе из 2 в не изменялось, и, таким образом, из утверждения, относящегося к точке 2, можно заключить, что при попадании в точку 8 XLGST равно максимальному из значений элементов в первых I — 1 = (М + 1) — 1 = М строках массива X. Но массив X содержит только М строк; следовательно, XLGST равно максимальному из значений элементов X.
Таким образом, мы доказали частичную правильность нашей программы. Нам еще осталось удостовериться, что программа закончится. Отметим, что единственными местами в программе, где изменяются I и J, являются «изолированные» части двух итерационных блоков, управляющие увеличением параметра цикла. Так как значение J увеличивается на 1, а значение N при выполнении внутреннего цикла не изменяется (путь через точки 3, 4, 5, 3 или через точки 3, 4, 6, 3), то значение J должно в конце концов превысить значение N. Таким образом, попадая в точку 3, мы когда-нибудь (после конечного числа выполнений внутреннего цикла) должны попасть в точку 7, а затем в точку 2. При каждом попадании в точку 2 мы затем попадем либо в точку 8 и процесс закончится, либо в точку 3. Если мы попали в точку 3, мы только что видели, как в конце концов дойдем до точки 7 и вернемся в точку 2. При этом значение I будет увеличиваться на 1, а значение М останется неизменным. Следовательно, после конечного числа шагов
Упорядочены .Не, упорядоченыУпорядочены .Не, упорядочены
Упорядочены .Не, упорядочены
Упорядочены .Не, упорядочены
24KN а
X j ^Х^ ^«е. ^^|;*»»Х^
Рис. 223.
значение I станет больше значения М. В этот момент мы перейдем из точки 2 в точку 3, и программа закончится. Таким образом, мы доказали конечность нашей программы. Если это доказательство объединить с предыдущим доказательством частичной правильности, то отсюда следует полная правильность программы.
ПРИМЕР 2. Требуется доказать, что после завершения программы массив X будет упорядочен (т. е. его элементы будут размещены в неубывающем порядке). Сначала докажем частичную правильность блок-схемы (рис. 2.23). Для этого исследуем такие пути:
Путь от точки 1 к точке 2. Читатель может легко удостовериться, что если мы находимся в точке 1 и справедливо соответствующее утверждение, а затем проходим из точки 1 в точку 2, то утверждение, относящееся к этой точке, будет справедливо. Когда мы попадем в точку 2, будем иметь 1 — 2, и, следовательно, истинно 2<I<N + 1, т. е. утверждение, касающееся X, тривиально, ибо 1 — 1 = 1.
Путь из точки 2 в точку 3. Если мы находимся в точке 2 и верно соответствующее утверждение, а затем переходим из точки 2 в точку 3, то читатель может легко убедиться, что утверждение 3 будет справедливо. Действительно, при движении из точки 2 в точку 3 выполняются только присваивания Y=-XihJ=I — 1. Сам массив остается без изменений, и, следовательно, утверждение, касающееся X, продолжает быть справедливым и при достижении точки 3. В этой точке нужно только проверить, что справедлива первая из двух альтернатив, относящихся к массиву X. Таким образом, если мы прошли от точки 2 к точке 3, то известно, что I<N, и вместе с 2<I<N+ 1 это показывает, что "в точке истинно отношение 1 < I < N.
Путь от точки 3 через точки 4, 5 в точку 3. Предположим, что мы находимся в точке 3, справедливо соответствующее утверждение, проходим через точки 4 и 5 и возвращаемся в точку 3. Пусть I и J в точке 3 до прохода по нашему пути имели значения Ь и Jn. Таким образом, 2 < In< N и либо Jn = I„- 1 и Х1 < ... < XJn, XIn = Y,..., XKh либо 0<Jn<In 2 и X| ^...^Xjn ^Xjn+1 =Xjn+2^— ...<XIn ...XN. Если мы прошли по этому пути, отношение
Jn > 1 истинно, и, следовательно, 0 < Jn — 1. При возвращении в точку 3 1п+1 = 1П (не-изменяется) и Jn+1 = Jn —1. Будут сохраняться соотношения 2<In+1=In<N и 0<Jn+ie = Jn—1<1П—2 (вне зависимости от того, какая из альтернатив была справедлива до прохода по указанному пути). Мы знаем, что Y < XJn и массив X уже изменен: на место Xjn+1 помещен элемент XJn. Таким образом, при возврате в точку 3 имеет 2 < In+i= k< N и 0 < Jn+1 = Jn — 1 < In — 2 = In+r~2 и X1<...<Xjn + 1 = jn-i <Xjn+1=jn = Xj^2+2=jo+I <... ...<X1e..XN (вне зависимости от того, какая из двух «альтернатив быЬа справедлива до прохода по пути).
Путь из точки 3 через точку 6 в точку 7. Предположим, что мы находимся в точке 3 и справедливо соответствующее утверждение. Перейдем из точки 3. через точку 6 в точку 7. Если мы двигались по этому пути, то проверка J>1 была ложной, и, следовательно, J< 1. Вместе с утверждением в точке 3 это свидетельствует о том, что 1 = 0. Следовательно, должна быть справедлива вторая альтернатива утверждения в точке 3, так как если 2<I<N и 1 = 1 — 1, то 1<J, что противоречит утверждению J=0. Из справедливости второй альтернативы в тарке 3 и из равенства J = 0 вытекает, что справедлива вторая альтернатива в точке 7. Вторая альтернатива с J=0 такова: 0< J =0<1-2 и X] =J+1 = =X2=J+2<... < X! ...XN. Утверждение, что Y > каждого из Хъ ..., Xj, тривиально при условии J=0, так как таких элементов просто нет.
Путь из точки 3 через точку 4 в точку 7. Предположим, что мы находимся в точке 3 и справедливо соответствующее утверждение. Пройдем из точки 3 в точку 7 через точку 4. Если мы прошли по этому пути, следовательно, проверка Y < Xj была ложной, т. е. Xj < Y. При прохождении по пути ни одна переменная не изменялась. Таким образом, легко видеть, что каждая альтернатива утверждения в точке 3 подразумевает соответствующую альтернативу утверждения в точке 7 (так как Xj < Y).
Путь из точки 7 в точку 8. Очевидно, что справедливость любой части утверждения в точке 7 вместе с присваиванием XJ+1 значения Y приводит к справедливости утверждения, относящегося к точке 8.
Путь из точки 8 в точку 2. Предположим, что мы нахо-
димся в точке 8 и справедливо соответствующее утверждение. Пройдем из точки 8 в точку 2 Пусть I в точке 8 принимает значение 1П. Тогда 2 < In< N и
Х^.ХХ^Л.
УпорядоченыУпорядоченыНе упорядоченыНе упорядочены
Упорядочены
Упорядочены
Не упорядочены
Не упорядочены
В точке 2 имеем I = In + 1, следовательно, 2 < 3 <Л — In + 1 < < N + 1 и
xl^...cxI_1.In,xl^...cxI_1.In,Xi=in+i ...хп.Xi=in+i ...хп.УпорядоченыУпорядоченыНе упорядоченыНе упорядочены
xl^...cxI_1.In,
xl^...cxI_1.In,
Xi=in+i ...хп.
Xi=in+i ...хп.
Упорядочены
Упорядочены
Не упорядочены
Не упорядочены
Путь из точки 2 в точку 9. Если мы проходим по этому пути, следовательно, проверка I < N была ложной и N < I. Вместе с 2 < I < N + 1 это означает, что I — N + 1. Но утверждение, касающееся массива X, в точке 2 при I — N 4- 1 совпадает с утверждением в точке 9.
Таким образом, мы доказали частичную правильность нашей программы. Для доказательства полной правильности нужно показать, что выполнение программы когда-нибудь закончится* Так как при прохождении внешнего цикла значение N не изменяется, а значение I увеличивается и поэтому в конце концов станет больше N при попадании в точку 2, а значение J при прохождении внутреннего цикла уменьшается и, следовательно, в конце концов станет меньше 1 при попадании в точку 3, то все это вместе гарантирует конечность нашей программы. s
В приведенном доказательстве не учитывается одна важная деталь. Мы показали, что когда-нибудь попадем в точку 9 и при этом будем иметь Хт < Х2 < ... < XN. Но в действительности нам нужно было бы доказать не только это. но и то, что при попадании в точку 9 значения Xv Х2, ... XN представляют собой некоторую перестановку из первоначальных значений. Другими словами, исходное множество значений, образующих вектор, лишь переупорядочивается (и множество значений не изменяется). Мы не стали этого доказывать, чтобы не увеличивать объем доказательства и сохранить его обозримость. Однако читатель может сам убедиться в правоте этого высказывания (и, если необходимо, формально это доказать).
ПРИМЕР 3. Двоичный алгоритм Эвклида. Предполагается, что приведенная на рис. 2.24 блок-схема вычисляет и печатает наибольший общий делитель чисел А и В [GCD(A, В)]. С этой блок-схемой мы сопоставили индуктивные утверждения, которых достаточно, чтобы легко доказать ее правильность методом индуктивных утверждений. Детали этого доказательства мы опускаем. Рассмотрим вопрос: как формулируются эти индуктивные утверждения? Известно, что работа программы основана на том факте, что на каждом такте выполнения алгоритма GCD(S, L) = GCD(A, В), где S, L — две переменные (0<S, L). При каждом прохождении внешнего цикла значения переменных S или К уменьшаются до тех пор, пока не получим либо 8=0, либо S = L. Таким образом, большинство наших утверждений должны быть очевиднымй. .Утверждение 2 относится лишь к основным фактам о S и L. Утверждение 5 аналогично утверждению 2, к которому добавлено лишь условие S<L. Часть программы между точками 2 и 5 строилась на основе только этих фактов, и она должна быть очевидной для человека, формулирующего эти утверждения. Утверждение 6 аналогично утверждению 5, но с более строгим условием 0<S<L. Отрицательный ответ на проверку между точками 5 и 6 предполагает выполнение этого условия и служит явным указанием при формулировании утверждения. Отметим, что при переходе от точки 9 к точке 2 выполняется только установка (присваивание) L<-L —Т. При возврате в точку 2 нужно, чтобы GCD(S, L) = GCD(A, В) и S, L > 0. Следовательно, будет лучше, если в точке 9 мы будем иметь GCD(S, L - Т) = GCD(A, В) при S > 0 и L > Т. Таким образом, исходя из этого предположения, надо сформулировать утверждения, относящиеся к интервалу между точками 6 и 9. Но если разобраться в соответствующем фрагменте программы, то можно увидеть, что здесь только устанавливается исходное значение Т, равное S, а затем оно (Т) несколько раз удваивается. Следовательно, утверждения о Т в точках 7 и 9 должны быть совершенно очевидны. Отметим также, что между точками 6 и 9 значения S, L, А и В остаются неизменными, и, следовательно, в точках 7 и 9 утверждение GCD(S, L) = GCD(A, В) должно быть справедливо, так как оно справедливо в точке 6. Таким образом, мы
Рис. 2.24.
надеемся, что читатель довольно легко может разобраться в том, как формулировать утверждения.
Обратите внимание, что в точке 9 мы написали более строгое утверждение, чем нужно фактически. Мы видели, что нужно было бы следующее: 0<S, 0<T<L и GCD(S, L - Т) = GCD(A, В). Однако для доказательства последнего равенства, вероятно, потребуются другие утверждения, которые мы «привязали» к точке 9. В частности, эти утверждения гласят, что 0<T<L и Hn>0 (Т = = 2" • S и GCD(S, L) = GCD(A, В)), откуда следует GCD(S, L - Т) = GCD(S, L - 2П • S). Но известно (и это можно доказать путем индукции по М), что для любого целого, удовлетворяющего условиям М > 0 и М * S < L, справедливо GCD(S, L - М • S) = GCD(S, L). Вместе с другими утверждениями в точке 9 это утверждение позволяет убедиться в том, что GCD(S, L - Т) = GCD(S, L) = GCD(A, В).
Утверждения в точках 7 и 9 в действительности можно было бы упростить: в точке 7 предполагать, что 0 < S, О < Т < L и GCD(S, L - Т) = GCD(A, В), а в точке 9 предполагать, что 0 < S, 0 < Т < L и GCD(S, L - Т) = GCD(A, В). Такие утверждения из структуры программы явно не следуют, но зато следуют утверждения, которые мы привели выше.
Подробное доказательство частичной правильности мы предлагаем провести самому читателю. Доказательство конечности программы в данном случае довольно простое. Уже внутренний цикл гарантирует, что программа закончится. Сначала Т присваивается начальное значение S > 0, а затем, при каждом проходе по циклу значение Т удваивается, a L остается без изменений. Следовательно, в конце концов Т увеличится настолько, что 2 • Т > L. Из внешнего цикла видно, что до возвращения к началу цикла L уменьшится на Т (1 ♦- L - Т) й 0 < Т. Кроме того, во внешнем цикле S и L изменяются, лишь когда они «меняются» значениями. Таким образом, когда мы начнем выполнять внешний цикл в точке 5, а затем вернемся в нее, то либо S, либо L уменьшится. Но нам известно, что в этой точке всегда 0 < S < L. Следовательно, это отношение должно быть справедливо и после конечного числа «уменьшений» S или L. Однако наступает момент, когда либо S, либо L станут меньше нуля. Отсюда можно заключить, что внешний цикл выполняется конечное число раз и после этого программа завер-
шается. Отметим, что, доказывая конечность программы таким способом, мы не доказываем непосредственно, что когда-нибудь попадем в точку 5 при условиях S = 0 или S = L. Вместо этого мы показываем, что инвариант цикла может оставаться инвариантом лишь при конечном числе повторений цикла, и, следовательно, выполнение цикла должно закончиться после конечного числа повторений. Использование инварианта цикла для доказательства конечности программ некоторых видов, несомненно, полезно. Например, предполагается, что такой способ надо использовать в упражнениях 7, 8 и 10. Кроме того, мы уже неявно применяли его и в предыдущих доказательствах, например в примере в разд. 2.3, где использовали инвариант цикла 0<S.
УПРАЖНЕНИЯ
Составьте блок-схему для вычисления и печати суммы всех элемен- _ тов массива X, состоящего из М строк и N столбцов. Докажите, что ваша блок-схема правильна.
Докажите, что приведенная на рис. 2.25 программа устанавливает L = 0, если массивы X и ¥ не имеют каких-либо совпадающих значений, и L = 1, если имеется по крайней мере один общий элемент.
Предположим, что есть массив X из N строк и N столбцов. Составьте блок-схему, проверяющую симметричность массива относительно главной диагонали. Другими словами, нужно проверить справедливость Xjj—Х;д для всех I и J, удовлетворяющих условиям 1<I<N и 1<J<N. Программа должна устанавливать некоторую переменную, равной нулю при симметричном массиве и равной единице при отсутствии симметрии. Докажите правильность составленной программы.
Докажите, что приведенная на рис. 2.26 программа конечна, т. е. она действительно заканчивается после вывода на печать всех чисел, из входного файла вместе с их факториалами. Входной файл состоит из конечного числа положительных .целых чисел.
Приведенная н? рис. 2.27 блок-схема соответствует некоторой более «прямолинейной» версии алгоритма упорядочения. Докажите, что она полностью верна, т. е. заканчивает работу с упорядоченным в неубывающем порядке массивом X.
Представьте, что в блок-схеме на рис. 227 есть ошибки. Список ошибок приводится ниже. Для каждой из них укажите те этапы доказательства, на которые повлияет эта ошибка. Приведите примеры исходного массива X, при которых программа будет работать неверно.
Список ошибок
пропущена команда SMLST Xj;
пропущена команда L I;
вместо команды J 1 + 1 стоит! *-1;
Рис. 2.25.
вместо команды J I + 1 стоит J *-1;
вместо проверки J < N указана J < N - 1;
в проверке SMLST<Xj неверно указаны (переставлены) выходы по F и Т.
Докажите, что выполнение приведенной на рис. 2.28 «обобщенной» блок-схемы закончится. Для этого сначала докажите, что при каждом попадании в точки 2 и 4 справедливы соответствующие утверждения. Затем, зная это, убедитесь, что если, попав в точку 4, мы переходим в точку 2, то перед этим мы конечное число раз проходили по циклу 4, 5, 4. На следующем этапе покажите, что цикл через точки 2, 4 выполнится конечное число раз, прежде че^будет справедливо отношение К = N и мы перейдем в точку 7, т. е. закончим работу.
Рис. 2.26.
Докажите, что выполнение приведенной на рис. 2.29 «обобщенной» блок-схемы закончится. Для этого сначала свяжите с точками цикла (как в упражнении 7) некоторые утверждения, относящиеся к значениям переменных, и докажите их справедливость в соответствующие моменты. Затем используйте эти факты для доказательств, относящихся к разным циклам.
При доказательстве конечности сложных блок-схем (таких, как в /пр аж нении 8), содержащих несколько вложенных или «пересекающихся» диклов, очень полезно проверять, что значение некоторой переменной, определяющей конечность одного из циклов, не изменяется в каком-либо другом цикле таким образом, что это будет приводить к бесконечной работе программы. Ниже приведена «обобщенная» блок-схема, работа которой не закончится (рис. 2.30). Почему?
Если бы мы имели дело не с такой «скелетной» блок-схемой, а со
схемой, содержащей большее число различных действий в циклах, то обнаружить, что некоторая переменная изменяется в различных циклах, оказалось бы значительно труднее. Отсюда следует, что доказательство конечности таких блок-схем нужно проводить весьма тщательно.
Докажите полную правильность приведенной на рис. 2.31 блок- схемы. Тщательно и детально аргументируйте конечность программы. Возможно, что довольно трудно, доказать, что в конце концов будет достиг-
Рис. 2.27.
Рис. 2.28.
нута точка 2 с II — 12, однако вполне можно доказать, что при достижении точки 2 выполняются условия II > 0 и 12 > 0. Можно доказать, что возврат в точку 2 с сохранением dmx условий происходит конечное число раз. Будет ли программа заканчиваться с любыми входными данными А, В > 0, если проверку II >12 заменить на II >12? Приведенная на рис. 2.31 блок- схема — еще один хороший пример использования инварианта цикла для доказательства конечности программы.
С помощью индукции по М докажите, что если 0<S^L, 0<М и М • S < L, то GCD(S, L- М • S) = GCD(S, L). После этого восстановите детали доказательства частичной правильности в примере 3 (разд. 2.4).
Докажите правильность программы, соответствующей бинарному алгоритму Эвклида. Блок-схема приведена на рис. 2.32.
Рис. 2.29.
Рис. 2.30.
СОКРАЩЕННЫЕ ДОКАЗАТЕЛЬСТВАПРАВИЛЬНОСТИ
По-видимому, доказательство правильности любой программы должно стать стандартным этапом процесса программирования. Каждая программа должна сопровождаться доказательством ее правильности. По мере того как программист становится более искушенным в вопросах доказательства, он может опускать часть деталей доказательства. Это допустимо, поскольку большая часть доказательства
х Рис. 2.31.
вполне традиционна и рутинна. Однако мы уверены, что существует минимум деталей доказательства, которые необходимо явно выписывать, а не просто формулировать (например, все индуктивные утверждения, необходимые для доказательства частичной правильности). Затем программист должен систематически исследовать все возможные пути в программе, по которым может идти выполнение программы, и, не вдаваясь в детали доказательства, убедиться, что доказательство провести можно. Ему нужно также удостовериться, что гарантировано завершение программы; детали можно опять опустить. По-видимому, любой хороший программист так и проверяет свою программу за столом, только индуктивные утверждения у него смутно «зреют» в голове. Мы пытаемся сделать такую проверку более систематичной и поэтому настаиваем, чтобы он сознательно
Рис. 2.32.
сформулировал и выписал основные утверждения, описывающие работу программы, и основные допущения, связанные с данными для программы. Если программист хорошо понимает свою программу, то при соответствующей практике он може сформулировать необходимые утверждения и провести доказательство. Если же такого понимания нет, то процесс формулирования и записи утверждений будет полезен уже потому, что заставит его хорошенько продумать свою программу.
Мы, конечно, отдаем себе отчет в том, что неформальное, сокращенное .доказательство правильности, о котором мы сейчас говорйли, вовсе не обеспечивает правильность программы. Доказательство может содержать и ошибки, и неточности. Тем- не менее мы считаем, что даже такое доказательство оправдывает затраченное на него время. Поскольку хороший программист всегда заранее проверяет свою программу, то мы вовсе не требуем, чтобы он делал что-либо отличное от того, что он всегда делает. Однако нам кажется, что если он станет это делать за столом более систематическим образом, то польза от этого процесса только увеличится.
ФОРМАЛИЗАЦИЯ ДОКАЗАТЕЛЬСТВАС ПОМОЩЬЮ ИДУКТИВНЫХ УТВЕРЖДЕНИЙ
В разд. 2.4 мы сформулировали и показали на примерах, как используется метод индуктивных утверждений. И формулировки индуктивных утверждений, и процесс доказательства проводились довольно неформальным образом. Если использовать какую-либо формальную нотацию для записи индуктивных утверждений и соответствующую дедуктивную систему для проведения доказательств, то можно формализовать весь этот процесс. В данном разделе мы обсудим некоторые аспекты такой формализации. Если мы надеемся, что в конце концов придем к тому, что при поддержке некоторой «механической» системы будем либо выполнять доказательство, либо его проверять, то некоторого типа формализация нам необходима. Читатели, которых вопрос формализации не интересует, данный раздел могут пропустить. Изложенный здесь материал в книге не используется. Наиболее изученной формальной нотацией для формулирова-
ния и проведения математического доказательства является исчисление предикатов. Мы полагаем (только в этом разделе), что читатель знаком с такого рода нотацией.
Напомним, что метод индуктивных утверждений состоит в следующем. Для доказательства частичной правильности некоторой блок-схемы относительно утверждений А и С мы связываем утверждение А с начальной точкой блок-схемы, а утверждение С с конечной. Кроме этого, необходимо выделить и связать с блок-схемой некоторые другие утверждения (описывающие отношения между значениями переменных, справедливые в момент попадания в соответствующую точку программы). Нужно, в частности, связать по крайней мере с одной из точек в замкнутом пути (цикле) одно такое утверждение. Затем необходимо для каждого пути в блок- схеме, ведущего из точки i с утверждением Ai в точку j с утверждение Aj при условии, что на пути из i в j нет промежуточных точек с утверждениями, доказать, что если мы находимся в точке i и справедливо утверждение Ai, а затем переходим по нашему пути в точку), то при попадании в эту точку будет справедливо утверждение Aj. Для формализации такого доказательства необходимо:
Использовать некоторую формальную нотацию для записи утверждений.
Формализовать то, что необходимо доказывать для каждого пути в блок-схеме.
Использовать для выполнения доказательства некоторую формальную систему вывода (дедуктивную систему).
Развитие формальных методов вывода для. выполнения доказательств не входит в круг вопросов, рассматриваемых в данной книге. Заинтересованный читатель может познакомиться с этими вопросами в любом хорошем учебнике логики (см., например, Chang, Lee «Symbolic Logic and Mechanical Teorem Proving», 1973). Мы рассмотрим лишь вопросы, относящиеся к п. 1 и 2. Для цели, указанной в п. 1, можно использовать нотацию исчисления предикатов (или другую формальную нотацию). С ее помощью можно формулировать нужные утверждения. Исчисление предика-
тов не совсем подходит для формулирования индуктивных утверждений. Многие из них трудно (если вообще возможно) выразить с помощью этой нотации. Поиски наиболее подходящей формальной системы для записи индуктивных утверждений и проведения доказательств являются предметом современных исследований. Тем не менее здесь для выражения утверждений будет использоваться исчисление предикатов. Что касается п. 3, то надо дать некоторый систематический метод формирования для каждого из путей блок- схемы подлежащего доказательству формализованного высказывания. Множество всех таких высказываний, которые нужно доказывать для определения частичной правильности программы, иногда называют множеством условий верификации (verificatiop conditons) для этой программы.
Для систематического формирования условий верификации некоторой блок-схемы поступим следующим образом. Рассмотрим по порядку все пути в блок-схеме. Предположим, что речь идет о пути из точки i в точку ]. Утверждение, относящееся к точке i, обозначим через Ai(X, У, ...), где X, Y,... — переменные, входящие в это утверждение. Аналогично обозначим й утверждение для точки j: Aj(X, Y,...). По пути из точки i в точку j значения переменных могут измениться (например, оператором присваивания). Для переменной V через epath (V) (изменение переменной вдоль пути) будем обозначать выражение, определяющее значение переменной V в точке] «втерминах» значений переменных в точке i и значений, присваиваемых переменным по мере прохождения пути. Если, например, на пути из i в j выполняется лишь пересылка X - Y 4-1, то epaty(X) = Y 4-1, a epat]/Y) = Y. Если на этом пути было три оператора присваивания Х*-Х + 1, Y<-Y + Y и X<-X4-Y-2, то e^i/X) == (X 4-1) 4-(Y 4-Y) — 2 = = X 4- 2 • Y — 1 Hepat^Y) = Y + Y — 2 • Y. Однако на пути из точки i в точку j может встретиться несколько точек, где выполняются проверки. Будем обозначать такие проверки через tk(X, Y, ...), где к —указывает точку, к которой относится проверка, а X, Y, ... — переменные, от которых она зависит. Предположим, что tk(X, Y, ...),..., ti(X, Y, ...) —полный набор всех проверок, встречающихся в различных точках на пути из точки i в точку]. Для каждой из этих проверок (tm(X,.Y, ...)) сформулируем высказывание t1^:
t^-t^HX), epalv(Y), ...],
если путь из i в j проходит по ветви, отмеченной Т (истина), и
1^ь =-itm[epath4X), epath' (Y),...],
если путь из i в j проходит по ветви, отмеченной буквой F (ложь). Через path' обозначается часть пути из i в j от точки i до точки ш. Теперь образуем полное высказывание CDath^tk^^-'-Atr81*1. Запишем формализованное условие верификации для пути из точки i в точку j:
[Aj(X,Y,..,) A CPath] “* Aj[epath(X), epath(Y),...].
Если на пути не встречаются проверки, то Срадв высказывание не включается.
ПРИМЕР 1. Рассмотрим блок-схему (рис. 2.33), с которой мы уже имели дело в примере 3 в разд. 2.2. Для записи утверждений, относящихся к разным точкам блок-схемы, использована нотация исчисления предикатов.
Сформируем множество условий верификации.
Путь из точки 1 в точку 2 назовем pL Для этого пути имеем ер1(Л) = Л, ep1(J2) = J2, ep1(IQ) = 0, ep1(IR) = Jl. Так как по пути проверки йе выполяется, то можно написать следующее условие верификации для этого пути:
(ОСЛА KJ2) - [ep1(Jl) = eP1(IQ)-eP1(J2) +
+ eP1(IR) A 0<ep1(IR)], (ОСИ A 1<J2)
- (Л = 0-32 + ЛА0<Л).
Путь из точки 2 через точки 3,4 в точку 2 обозначим через р2. Для этого пути имеем еР2(Л) = Л, ep2(J2) = J2, eP2(IQ) = IQ + 1, eP2(IR) = IR - J2.
На пути есть только одна проверка IR<J2. Ту часть пути р2, которая ведет от точки 2 до этой проверки, обозначим через р2'. Так как путь р2 после проверки проходит по ветви, отмеченной F, то
tP2 — ^ [ep2 (IR) <ep2<J2)] - -• (IR<J2)” J2 <IR.
Поскольку проверка только одна, получаем
О * tp2
VP2”l .
Рис. 2.33.
Отсюда условие верификации для этого пути имеет вид
(Л = IQ J2 + IRA0<IRAJ2<IR)-
■* [еР2(Л)- (ep2)(IQ) • ep2(J2) +
+ ep2(IR)A0<eP2(IR)]
или после преобразования
(Jl-IQJ2 + IRA0<IRA J2<IR) ■* [Л-(IQ +1) • J2 + + (IR —J2)A0<IR —J2],"
Путь из точки 2 через 5 в точку 6 обозначим через рЗ. Для этого пути имеем ерз(Л) = Л, eP3(J2) = J2, ep3(IQ) — IQ, eP3(IR) = IR. На Пути встречается лишь одна проверка IR < J2.
Часть пути от точки 2 до этой проверки обозначим через рЗ'. Как и выше, имеем eP3(IR) = IR и еРз(32) = 12. Так как после проверки путь проходит по ветви, обозначенной Т (истина), то
tP3 = ep3(IR) < eP3<J2) = IR < J2. .
Следовательно, условие верификации для всего пути имеет вид
(Jl“IQJ2 + IRA0<IRAIR< J2)-* (J1=IQI2 +
+ IR А 0<IR A IR< J2).
Для того, чтобы закончить формализованную версию доказательства правильности данной блок-схемы методом индуктивных утверждений, остается лишь доказать справедливость каждого из этих трех верификационных условий с помощью некоторой системы вывода (дедукции). Мы не будем тратить время и обсуждать такую формальную систему в нашей книге. Каждый читатель, знакомый с исчислением предикатов, может представить себе, насколько просто проводится доказательство этих условий с использованием только основных аксиом целой арифметики.
ПРИМЕР 2. Рассмотрим вновь блок-схему, приведенную на рис. 2.20 (рис. 2.34). Запишем утверждения с помощью нотации исчисления предикатов и сформулируем множество условий верификации.
Путь из точки 1 через точки 2, 4 в точку 5 обозначим через pL Для этого пути имеем еР1(А)вА, ер1(В) = В, ept(H)eA, еР1(12) = В. Часть пути от точки 1 до точки 2 обозначим через рГ. Для него имеем ер<(П) = А, еР1'(12) = В. Так как путь pl проходит после проверки по ветви, обозначенной F (ложь), то получаем 1Р1=ер<(11Хер1'(12) = А<В. Таким образом, условие верификации для этого пути записывается в виде
(О^АА ХВА А<В) ->{GCD[epl(Il), еР1(12)] =
= GCD[ep1(A), еР1(В)] Л 0 ^еР1(П) А еР1(П) <еР1(12)}.
или
(0< А А 1 <В А А <В) - [GCD(A, B) = GCD(A, В)Л0< <АЛ А<В].
Рис. 2.34.
Путь из точки 1 через точки 2, 3 в точку 5 обозначим через р2. Для этого пути имеем ер2(А)вА, ер2(В) = В, еР2(И) = В, ер2(12) = А. Обозначим часть пути р2 от точки 1 до 2 через р2'(—рГ). Как и в условии 1, имеем ер2'(П) —А, ер2 (12) = В. Так как путь р2 идет после проверки по ветви, отмененной F (ложь), то
tp22- I [еР2<П)Ор2(12)] = ^ (А^В)-В <А.
Таким образом, условие верификации для этого пути имеет вид
(0<АЛ 1 <ВЛ В <А) - {GCD[ep2(Il), ер2(12)] = ,
= GCD[ep2(A), ер2(В)]Л 0<ер2(11)Л еР2(П) <еР2(12)} или после подстановки
(0< АЛ 1< ВЛ В< А) - [GCD(B, A) = GCD(A, В)Л0< <ВЛВ<А].
Путь из точки 5 через точки 6, 7,2,4 в точку 5 обозначим через рЗ. Для этого пути имеем ерз(А) =.А, ерз(В) — В, ерз(П) = = 11, ер3(12) = 12 — 11. На этом пути решение принимается в двух точках. Первое решение — в точке 5*. На пути от точки 5 до 5* ни одна переменная не изменяется. Так как путь после проверки идет по ветви, обозначенной F (ложь), то имеем ф3 ■ -1(11 = 0). Часть пути от точки 5 до точки 2 обозначим через рЗ'. Для этого пути имеем ерз<11) — II, ер3(12) = 12 — 11. Так как путь рЗ после проверки в точке 2 проходит по ветви, обозначенной Т (истина), то имеем tg3* [ер3 (ПХ ерз'(12)]* ^Il< 12 — 11. Таким образом, Ср3” t^'3 Л t§3= —।(I = 0)ЛII < <12 — 11. Следовательно, условие верификации для этого пути записывается в виде
GCD(I1,I2) = GCD(A,B)A 0 <11ЛII <12Л -. (II = 0)Л
ЛII <12 - И] - {GCD[eP3(Il), ерз(12)] =
= GCD[eP3(A), еРЭ(В)]Л 0<ерз(П)Л ерз11)<ерз(12) или
[GCD(I1, I2)-GCD(A, B)AO<I1AI1<I2A-. (Il = 0)Л AI1<I2-I1] - [GCD(I1, 12 —I1) = GCD(A, B)A O<I1AI1< <:i2—11].
Путь из точки 5 через точки 6, 7,2, 3 в точку 5 обозначим через р4. Для этого пути имеем ер4(А) —А, ер4(В)—В, ер4(П) —12 —II, ep4(I2) —II. Читатель может легко убедиться,, что ^--,(11-0) и t^4 ——.(IKI2 —II) —12 —IKI1. Теперь мы можем легко сформулировать условие верификации для этого пути:
[GCD(I1, I2)-GCD(A, В)Л0< ПАП < 12Л (II-0)Л 12- -IKI1] -* [GCD(I2 —II, I1)-GCD(A, В)Л0<12 —II Л 12 — -II <12-II].
Путь из точки 5 через точку 8 в точку 9 обозначим через р5. Для этого пути имеем ер5(А) = А, ер5(В) = В, ер5(П) = П, ер5(12) = 12. После проверки в точке 5’ путь проходит по ветви, обозначенной Т (истина). Для этого пути читатель легко может составить следующее условие верификации:
[GCD(I1, I2) = GCD(A, В)Л0 < IIЛ 11< 12АII —0] - -*I2 = GCD(A, В).
Формальное доказательство каждого из этих пяти условий верификации проводится довольно просто. Условие 1 тривиально. Оставшиеся четыре условия так же просты, но, кроме аксиом для Делых чисел для них требуются аксиомы, характеризующие функцию GCD. Например, для условия 2 нужна такая аксиома: (X)(Y)[GCD(X, Y) = GCD(Y, X)}. Для условия 3 требуется аксиома вида (X)(Y)[0< X Л Х< У -* GCD(X, У) = = GCD(X, Y - X)J, а для условия 5 — аксиома вида (Y)[GCD(0, Y)=Y]. ‘
УПРАЖНЕНИЯ
. Выберите несколько блок-схем .из примеров или упражнений в разд. 22—2.4. Для каждой из этих блок-схем перепишите индуктивные утверждения в нотации исчислешы предикатов и постройте условия верификации.
Глава 3
ДОКАЗАТЕЛЬСТВО
ПРАВИЛЬНОСТИ ПРОГРАММ, НАПИСАННЫХ НА ОБЫЧНЫХ ЯЗЫКАХ ПРОГРАММИРОВАНИЯ
ЗЛ. ВВЕДЕНИЕ
Метод индуктивных утверждений, о котором шла речь в предыдущей главе, можно непосредственно использовать для доказательства частичной правильности программ, написанных на каком-нибудь традиционном языке программирования, например на Фортране, Алголе или ПЛ/1. Конечность таких программ можно доказывать так же, как мы это делали для блок-схем. При использовании метода индуктивных утверждений необходимо по крайней мере с одной из точек программы в каждом из замкнутых путей (циклов) связать соответствующее утверждение. Конечно, если мы используем язык программирования, то порядок выполнения (пути) неявно определяется структурой управления, тогда как на блок-схемах этот порядок явно указывается стрелками. Следовательно, применяя метод индуктивных утверждений, нужно четко представлять порядок выполнения программы, чтобы быть уверенным, что не пропущен ни один из замкнутых путей в программе. Именно этот вопрос мы и рассмотрим здесь, так как сам метод утверждений мы уже описали выше, а его использование в данном случае аналогично использованию для блок-схем.
В разд. 3.2 метод проиллюстрирован на простых программах, написанных на Фортране, а в разд. 3.3 — нА программах, написанных на ПЛ/1.
32» ПРИМЕРЫ ДОКАЗАТЕЛЬСТВА ПРАВИЛЬНОСТИПРОГРАММ НА ФОРТРАНЕ
ПРИМЕР 1.
READ (5, 1) Л, J2 1 FORMAT (2110)
СЗНАЧЕНИЯ, СЧИТЫВАЕМЫЕ В Л И J2,-
СЦЕЛЫЕ, УДОВЛЕТВОРЯЮЩИЕ УСЛОВИЯМ
С О -LE. Л И 1 .LE. J2.
IQ-0 IR-JI 2 IF (IR .LT.J2) GO TO 4 C JI .EQ. IQ * 12 + IR AND 0 .LE. IR IQ=IQ + 1 IR-IR-J2 3 GO TO 2
, WRITE (6,5) IQ, IR
14 FORMAT (2110)
C JI .EQ. IQ*J2 + IR AND 0 .LE. IR AND
C IR .LT. J2
, 'STOP . END
Приведенная программа на Фортране —это просто некоторый вариант программы, блок-схема которой приведена на рис. 2.3. Напомним, что программа вычисляет целое частное IQ и остаток IR от деления целого Л на целое J2. Мы уже включили в качестве комментариев в программу индуктивные утверждения, необходимые для доказательства частичной правильности. Например, комментарий, помещенный сразу после оператора с меткой 2, нужно рассматривать как комментарий, связанный с точкой, расположенной перед выполняемой в этом операторе проверкой. Таким образом, предполагается, что в момент прихода в точку, находящуюся непосредственно перед проверкой в операторе с меткой 2, справедливы утверждения Л =IQ J2 + IR и 0< IR. Отметим, что в доказательствах мы используем „ = “ как символ равенства, а не присваивания.
После этого доказательство частичной правильности данной программы почти идентично доказательству частичной правильности для блок-схемы на рис. 2.3. При этом необходимо рассмотреть следующие пути:
Путь от оператора READ до оператора с меткой 2. Предположим, что только что выполнился оператор READ и справедливо утверждение, записанное сразу после него. Теперь последовательно выполняются операторы до оператора с меткой 2. Нам нужно показать, что справедливо
утверждение, записанное после этого оператора. Если мы дошли до этой точки, то имеем IQ — О, IR = J1,0 С Л и 1С J2. Таким образом,
Л — IQ - J2 + IR — 0 • J2 +Л — Л
и ОС Л =IR
Следовательно, наше утверждение справедливо.
Путь от оператора с меткой 2 до оператора с меткой 3 и опять к оператору с меткой 2 (основной цикл программы). Предположим, что мы выполняем оператор 2 и справедливо записанное после него утверждение, затем выполняем цикл и возвращаемся к метке 2. Необходимо показать, что указанное утверждение снова будет справедливо. Пусть IQ и IR до выполнения цикла принимают значения IQn и IRn. Тогда Л =IQn • J2 + IRn и ОС IRn. При возврате к метке 2 после прохождения цикла значения IQ и IR будут IQ^ = IQn + 1 и IRn+1 =IRn —J2, а значения Л и J2 останутся без изменений. Таким образом, после возврата к метке 2 имеем
IQn+1 • J2 + IRn+i = (IQ + 1) • J2 + (IRn - J2) =
= IQn J2 + J2 + IRn- J2-IQn* J2 + IRn= Л.
Кроме того, известно, что если мы проходили по циклу, то проверка IRn .LT. J2 дала отрицательный результат, т. е. J2CIR. Отсюда следует, что при возврате к метке 2 имеем О С IR n—J2 — IRn+i.
Путь от оператора 2 к оператору 4. Предположим, что мы выполнили оператор 2, справедливо соответствующее утверждение и переходим к оператору 4. Нужно показать, что справедливо утверждение, записанное ниже этого оператора. Оператор 2 передаст управление на оператор 4 только при положительном результате проверки IR .LT. J2. При переходе от оператора 2 к оператору 4 ни одно из значений переменных не изменяется, и, следовательно, при достижении метки 4 мы имеем Л =IQ • J2 + IR, 0CIR и, кроме того», IR < J2. Таким образом, мы доказали частичную правильность.
Доказательство конечности программы на Фортране идентично доказательству конечности для блок-схем. Для доказательства конечности программы необходимо только показать, что при выполнении программы мы в конце концов выйдем из единственного в программе цикла. Следовательно, надо показать, что проверка IR .LT. J2, управляющая циклом, когда-нибудь даст положительный результат. Так как значение IR при каждом прохождении цикла уменьшается на величину J2, a J.2 остается без изменений и, кроме того, 1 < J2, то можно заключить, что IR уменьшается каждый раз по крайней мере на 1 и Логда-нибудь станет меньше J2. В этот момент условие IR < J2 станет справедливым и мы выйдем из цикла, т. е. программа закончится.
ПРИМЕР 2
Cl X-ВЕЩЕСТВЕННЫЙ МАССИВ, ЕГО РАЗМЕР Cl N .GE. 2
XSMLST —Х(1) DO 101-2, N С2 XSMLST РАВНО МИНИМАЛЬНОМУ ИЗ Х(1),... С2 ...,Х(1-1)
С2 КРОМЕ ТОГО, 2 .LE. IH I .LE. N
IF (XSMLST .LE. X(I)) GO TO 10 XSMLST —X(I)
C3 XSMLST РАВНО МИНИМАЛЬНОМУ ИЗ СЗ X(l),..., X(I-l)
СЗ КРОМЕ ТОГО, 3 .LE. IИ1 .LE. N + 1
10 CONTINUE
С4 XSMLST РАВНО МИНИМАЛЬНОМУ ИЗ Х(1),...
С4 ...,X(N)
Предполагается, что приведенный фрагмент программы должен присвоить XSMLST значение наименьшего элемента из массива Х(1), ... , X(N). В Фортране цикл строится по следующей основной схеме:
DO метка I —М, N
С ПРИМЕЧАНИЕ 1 Тело цикла
С ' ПРИМЕЧАНИЕ 2
метка CONTINUE
Рис. 3.1.
Эта схема работает аналогично приведенной на рис. 3.1 блок-схеме.
Примечание 1. (комментарий), связанное с таким циклом и стоящее непосредственно за оператором DO, нужно рассматривать как связанное с некоторой точкой утверждение 1 на блок-схеме, а примечание 2, стоящее перед концом цикла, нужно рассматривать как утверждение 2 для некоторой точки прогр 1ммы, соответствующей точке на блок-схеме.
Дгя доказательства частичной правильности программы нужно’рассмотреть следующие пути:
L Путь от начала фрагмента до точки 2 (точки, с которой связано утверждение 2). Если мы попадаем в эту точку, то XSMLST = X(1) и 1 = 2, и очевидно, что значение XSMLST равно наименьшему из X(l),..., Х(1 — 1) = Х(2—1) = XI и 2<I = 2<N (так как N>2). (Обратите внимание, что без условия N>2 наше утверждение доказать нельзя, и, следовательно, правильная работа программы не гарантируется, если N = l.)
Путь от точки 2 до точки 3 (точки, с которой связано утверждение 3). Предположим, что мы достигли точки 2, утверждение 2 справедливо, I имеет значение 1П и мы проходим цо телу цикла, возвращаясь в точку 2 В теле цикла XSMLST сравнивается с Х(1П) и, если XSMLST < X(In), XSMLST остается без изменений. В противном случае значение заменяется на Х(1П). Так как до этого известно, что значение XSMLST равно наименьшему из Х(1), ... , X(In — 1), то читатель легко может убедиться, что теперь значение XSMLST будет равно наименьшему из Х(1),..., Х(1П). Однако, прежде чем мы дойдем до точки 3^ значение I увеличится на 1, и, таким образом, 1п+1 — 1п +1;‘следовательно, значение XSMLST снова будет равно наименьшему из Х(1),..., X(In)=X(In+1— 1). Кроме того, из примечания 2;известно, что 2<In<N, и, следовательно, в точке 3
3<In+1 = In+l^N + l.
Путь из точки 3 к точке 2. Мы пройдем по этому пути, если только проверка I<N даст положительный ответ. При этом условии справедливость примечания 3 приводит к справедливости примечания 2 в момент достижения этой точки:
Путь из точки 3 в точку 4. Мы пройдем по этому пути, если только проверка L< N даст отрицательный ответ, т. е. при I>N. Но так как справедливо утверждение в примечании 3, следовательно, I = N+1, и при достижении точки 4 значение XSMLST будет равно наименьшему из Х(1), ... , Х(1 —1) = = X[(N + 1) — 1) = X(N), что и требовалось доказать, т. е. доказана частичная правильность.
Конечность этого фрагмента программы очевидна: здесь есть только один цикл, и он является фортрановским.
Эти два примера показывают, что метод индуктивных утверждений можно распространить непосредственно и на доказательства правильности программ, написанных на Фортране. Затруднение заключается лишь в том, что управление в программах на Фортране не такое явное, как в блок-схемах. Это приводит к тому, что в программах легко не заметить некоторые пути или неправильно их интерпретировать (напри-
мер, можно предположить, что в операторе цикла проверка выполняется перед выполнением тела цикла). Кроме того, нужно приписывание индуктивных утверждений к некоторым точкам в программе на Фортране проводить очень внимательно и все время помнить^с какой неявной точкой связано соответствующее утверждение. Например, один-единственный оператор Фортрана, такой, как оператор DO, фактически состоит из нескольких действий: установки счетчика, увеличения счетчика и проверки. При доказательстве правильности мы должны четко осознавать, с какой из этих точек связано индуктивное утверждение. Использование переходов GO ТО в программах на Фортране тоже порождает проблемы. Если в программе много переходов и при их использовании не придерживались некоторой дисциплины, то программу часто трудно понимать и трудно доказывать ее правильность. Это объясняется тем, что сложно проследить за всеми возможными путями выполнения программы и указать нужные для этих путей индуктивные утверждения.
Очень важной особенностью программирования на Фортране является возможность определять и использовать подпрограммы. Метод индуктивных утверждений мы можем распространить скорее на доказательство правильности самих подпрограмм или программ, чем на обращения к ним. Так как в предыдущих разделах, имея дело с блок-схемами, мы не касались вопроса подпрограмм, то теперь приведем пример доказательства правильности программ, содержащих обращение к подпрограммам.
ПРИМЕР 3
С MAIN PROGRAM
л
Cl О .LE. I AND 1 .LE. J AND A(I, J) CALL DIV(I, J, K, L)
С2О .LE. I AND 1 LE. J AND A(I, J)
C2AND I ,EQJC»J + L AND ft LE. L AND I .LT.
END
SUBROUTINE DIV (JI, J2, IQ, IR)
C3 <0 LE. JI AND 1 LE. J2
IR-J1
IF (IR .LT. J2) GO TO 4
C4,Л EQ .IQ*J2 + IR AND ft .LE. IR
- IQ-IQ+1
IR-IR-J2
GO TO 2
RETURN
C5 JI .EQ. IQ*J2 + IR AND 0 LE. IR AND IR LT. J2 END
В этом примере нас прежде всего будет интересовать, как обрабатывать подпрограммы. Поэтому мы не приводим целиком основную программу, а лишь намечаем некоторую программу, содержащую обращения к подпрограммам. Подпрограмма DIV аналогична программе, приведенной в примере 1, но оформлена каг подпрограмма. Она предназначена для вычисления целых частного и остатка от деления параметра Л на параметр J2; полученные значения возвращаются как значения параметров IQ и IR. Доказательство того, что эта подпрограмма верна, аналогично доказательству, приведенному в примере 1. Поэтому будем считать, что доказательство правильности этой подпрограммы уже проведено, и докажем, что правильна основная программа. Один из этапов такого доказательства заключается в том, чтобы показать, что если непосредственно перед обращением к подпрограмме справедливо утверждение С1, а затем мы пройдем эту точку, выполним подпрограмму и вернемся из нее, то будет справедливо утверждение С2. Мы считаем, что A(I, J) в примечаниях С1 и С2 —некоторые утверждения, относящиеся к переменным I и J. Чтобы доказать этот факт, мы поступим следующим образом. Так как фактические параметры I, J, К, L с
помощью механизма обращения сопоставляются с соответствующими формальными параметрами Л, J2, IQ, IR, то сначала следует доказать, что из утверждения С1 следует утверждение СЗ в начале подпрограммы; для этого имена; параметров Л, J2, IQ и IR в этом утверждении заменяются на имена соответствующих фактических параметров I, J, К, L. Другими словами, нужно доказать, что С1 подразумевает СЗ', где СЗ' — утверждение, полученное путем замены в СЗ Л на I, J2 на J, IQ на К и IR на L. Таким образом, нужно показать, что из О .LE. I, 1 .LE. J и A(I, J) следует О .LE. I и 1 .LE. J. Это очевидно. Кроме того, известно, что справедливо и начальное допущение о правильности параметров подпрограммы. Так как мы считаем, что правильность подпрограммы уже доказана, то нам не надо обращать внимание на детали строения этой программы. Мы знаем, что утверждение С5, связанное с точкой, непосредственно предшествующей оператору RETURN, будет справедливо и после того, как мы вернемся в основную программу. Конечно, это утверждение относится к формальным параметрам, но оно будет справедливо и для фактических параметров после того, как мы вернемся и попадем в точку, с которой связано утверждение С2. Таким образом, для того чтобы убедиться в справедливости утверждения С2 при достижении соответствующей точки программы, следует лишь показать, что из С5' следует С2. Утверждение С5' получается из С5 заменой Л на I, J2 на J, IQ на К и IR на L. Другими словами, из I .EQ. К * J 4- L AND О .LE. L AND L .LT. J следует О .LT. I AND 1 .LE. J AND A(I, J) AND I .EQ. K*J + L AND 0 .LE. L AND L .LT. J. Читатель может легко убедиться, что из С5' не следует целиком все утверждение С2, а следует лишь подчеркнутая часть С2. Как же убедиться, что оставшаяся (невыделенная) часть также справедлива, если мы попадаем в точку, соответствующую С2? Отметим, что эта часть утверждения идентична утверждению С2. Следовательно, если мы будем знать, что переменные I и J (в С1 упоминаются только они) в подпрограмме не изменяются, то можно сделать вывод, что из справедливости С1 перед обращением к подпрограмме следует справедливость соответствующей части С2 после обращения. Так как фактические параметры I, J сопоставляются с формальными параметрами подпрограммы Л, J2, то теперь следует просмотреть подпрограмму: не
изменяются ли значения Л или J2. Читатель может легко проверить, что ни J1, ни J2 не изменяются. Отсюда мы делаем вывод, что утверждение О .LE. I AND 1 .LE. J AND A(I, J) продолжает оставаться справедливым и в момент достижения точки, связанной с утверждением С2.
Приведенное доказательство иллюстрирует общий метод доказательства для случаев с подпрограммами. Сначала выясняется, что делается в подпрограмме по предположению, и это доказывается обычным методом индуктивных утверждений. Далее при доказательстве правильности программы, обращающейся к этой или еще и другим подпрограммам (если некоторый путь содержит такое обращение), поступаем следующим образом. Сначала доказываем, что при обращении к подпрограмме справедливо утверждение, относящееся к ее началу (здесь описываются допущения, касающиеся значений параметров). В примере мы показывали, что из С1 следует СЗ'. Затем доказываем, что из утверждения, связанного с оператором возврата (это утверждение о правильности подпрограммы), следует справедливость всего (или части) утверждения, относящегося к точке (в вызывающей программе), непосредственно следующей за обращением. В примере мы показали, что из С5 следует часть С2. Кроме этого, можно использовать и тот факт, что утверждения, относящиеся к переменным, значения которых не изменяются в подпрограмме, продолжают оставаться справедливыми и после возврата из подпрограммы. В примере показано, что из С1 рле дует оставшаяся часть С 2 (С 1—утверждение в вызывающей программе, включающее лишь переменные, значения которых в подпрограмме не изменились).
УПРАЖНЕНИЯ
Напишите на Фортране программы, блок-схемы которых приведены в примерах и упражнениях гл. 2, и докажите их правильность. Докажите правильность какой-нибудь.программы, где есть обращение к подпрограмме. Напишите, например, по нашей блок-схеме подпрограмму, вычисляющую наибольший общий делитель двух целых чисел. Докажите ее правильность, а затем докажите правильность какого-нибудь фрагмента программы, где есть обращение к такой подпрограмме.
ПРИМЕРЫ ДОКАЗАТЕЛЬСТВА ПРАВИЛЬНОСТИПРОГРАММ НА ПЛ/1
Как для программ на Фортране, так и для программ на языке ПЛ/1 метод доказательства правильности в принципе один и тот же, однако мы хотели бы показать, как на этом методе отражаются механизмы управления, присущие ПЛ/1.
ПРИМЕР 1
^X(1:N) МАССИВ ВЕЩЕСТВЕННЫХ ЗНАЧЕ
НИЙ РАЗМЕРОМ 1<N */ SUM = 0.0; 1= 1;
DO WHILE (I<N);
^ SUM—X(l)4*... 4-X(I —1) AND 1<I<N4-1*/ SUM — SUM 4-X(I);
I —14-1;
END;
Л SUM - X(l) 4-... 4- X(N) */
Предполагается, что приведенный фрагмент программы вычисляет SUM — Х(1) 4-... 4- X(N). Вычисления организуются на основе цикла DO WHILE. Эта конструкция означает, что повторяется нуль или более раз выполнение всех операторов, расположенных между оператором DO WHILE и соответствующим ему оператором END, до тех пор, пока условие в операторе DO WHILE истинно. Утверждение, записанное в форме примечания сразу же за оператором DO WHILE, следует считать связанным с точкой, непосредственно предшествующей проверке в этом операторе, т. е. считается,с что оно должно быть справедливым каждый раз, когда мы попадаем в эту точку.
Доказательство частичной правильности для этой простой программы довольно очевидно. Для этого нужно просмотреть все пути в программе.
Путь от начала фрагмента программы до точки, непосредственно предшествующей проверке в операторе DO WHILE. Когда мы попадем в эту точку, будем иметь SUM —0.0 и I = 1. Утверждение, что SUM — Х(1) + ... 4- X(I —1), очевидно справедливо, так как I — 1 =0, и мы считаем, что «пустая» сумма равна нулю. Утверждение 1 < I < N 4-1 также справедливо, ибо1-0иН>1.
/=4-549
Путь от проверки в операторе DO WHILE через тело цикла вновь к проверке в операторе ЕЮ WHILE. Предположим, что до прохода по этому пути I—1П и справедливо указанное утверждение, т. е. In<N + l и SUM —X(l) + ...+ X(In —1). После того как мы пройдем по нашему пути, получим SUM-[X(l) + ... + X(In-l)] + X(In) и I—1п + 1. Следовательно, опять справедливо утверждение SUM—Х(1)+... ... + Х(1 — 1). Но мы проходим по этому пути лишь в случае истинности условия 1п < N. Таким образом, когда мы вернемся в точку проверки в операторе DO WHILE, будем снова иметь l<In+l-I<JN + l.
Путь от проверки в операторе DO WHILE до точки, расположенной сразу же после конца цикла. По этому пути мы проходим, если условие в операторе DO WHILE ложно, т. е. I<N дает ответ «ложь». Если при этом учесть, что 1<I<N + 1, то очевидно, что I—N + 1. Из этого факта, а также из справедливости утверждения SUM — Х(1) +... ... + Х(1 — 1) следует, что при достижении точки сразу же после цикла DO WHILE
SUM - X(l) +... + X((N +1) -1) - X(D +... + X(N),
что и требовалось доказать.
Для доказательства конечности программы нужно только. показать, что заканчивается сам цикл. Это, однако, очевидно, так как I увеличивается на 1 каждый раз при прохождении цикла, а значение N не изменяется. Следовательно, I в некоторый момент станет больше N, и проверка I<N даст отрицательный результат.
ПРИМЕР 2
Г М, N УЖЕ ИМЕЮТ ЦЕЛЫЕ ЗНАЧЕНИЯ И 0<N */ 1-1; J-M;K-N;
DO WHILE (К-0);
/* I*(J**K) — M**N AND 0<КУ DO WHILE (MOD (K, 2)-0); ^ I* (J**K) = M**N AND 0< К */ K-K/2E0;
J —J*J;
END;
K-K-l;
—I*J;
END;
/»I-M**N*/
Мы хотим доказать, что приведенная программа на языке ПЛ/1 вычисляет I —M**N. Для доказательства частичной правильности нашего фрагмента рассмотрим следующие пути 7 в программе:
Пусть от начала программы до точки, предшествующей непосредственно проверке во внешнем операторе DO WHILE. Когда мы попадем в эту точку, будем иметь 1 — 1, J—М и К—N. Таким образом, I*(J**K) = 1»(M**N) — M**N, кроме того, 0<N—К. Следовательно, утверждение, связанное с точкой, предшествующей проверке во внешнем операторе DO WHILE, справедливо.
Путь от проверки во внешнем цикле до проверки во внутреннем цикле. На этом , пути выясняется лишь, что К^О. Следовательно, связанное с внутренним оператором DO WHILE утверждение должно быть справедливым.
Путь от проверки во внутреннем операторе DO WHILE по внутреннему циклу с возвратом к проверке во внутреннем операторе DO WHILE. Предположим, что до прохода по этому пути I — In, J—Jn и К —Кп. Кроме того, мы знаем, что In*(Jn**Kn)—M**N и 0<Кп. Так как мы проходим по этому пути, то, следовательно, MOD (К„, 2)—0, т. е. Кп без остатка делится на 2. Пройдя по телу цикла, мы получим I—1П, К—Кп/2Е0 и J—Jn* Jn. Следовательно,
I*(J**K) = In* [(Jn* Jn)**Kn/2E0] =
= In * (Jn ** Kn / 2E0) * (Jn *♦ Kn / 2E0) =
= In * (Jn ♦* (Kn / 2E0 + Kn /2E0) =
= In * (Jn ♦* K^) = M ** N,
\ак как Kn делится без остатка на 2 Кроме того, так как 0 < К и К делится без остатка на 2 должно выполняться отношение К—Kn/2E0>Q Таким образом, утверждение, относящееся к внутреннему оператору DO WHILE, в момент возврата к нему должно быть справедливо.
Путь от проверки во внутреннем операторе DO WHILE к окончанию внешнего цикла и возврат к проверке во внешнем операторе DO WHILE. Предположим, что мы находимся /=4*
во внутреннем операторе DO WHILE, I — In, J — Jn и К—Kn. В этот момент справедливы утверждения In* (Jn»*Kn)—M»*N и 0 < Кп. Кроме того, если мы проходим по указанному пути, проверка во внутреннем DO WHILE дает отрицательный ответ, т. е. условие MOD(Kn,2) =0 ложно. Пройдем до конца внещний цикл и вернемся к проверке во внешнем операторе DO WHILE. Tenepi» будем иметь К = Ко — 1,1 — In*Jn и J = Jn. Следовательно, в этот момент
I * (J ** К) = (Ih * Jn) * [Jn ** (Kn- 1)] =
= In * [jn** (Jn ** (Kn - 1))] =
= In*[Jn‘k(l + Kn-l)] =
= In * (Jn ** Kn) = M *» N.
Кроме того, так как 0 < Кп, то 0 < Kn — 1 — К Таким образом, связанное с внешним оператором DO WHILE утверждение снова оказывается справедливым.
Путь от проверки во внешнем операторе DO WHILE до точки, расположенной непосредственно после конца внешнего цикла. Если мы прошли этим путем, условие К^О было ложным, т. е. К = Q Утверждение, соответствующее внешнему циклу, справедливо, т. е. I*(J**K) =M**N. Однако, поскольку мы вышли из цикла,
I*(J**K) = I*(J**0) —1*( 1) — I — M**N, что и требовалось доказать.
Для доказательства выполнения данной программы нужно показать, что если мы попадем во внешний или внутренний цикл, то эти циклы обязательно закончатся. Сначала рассмотрим внутренний цикл. Отметим, что при каждом прохождении цикла значение К устанавливается равным К/2Е0, т. е. К все время уменьшается. Следовательно, в какой-то момент его значение уменьшится настолько, что не будет без остатка делиться на 2 В этот момент условие MOD(K, 2) =0 станет ложным, что и гарантирует окончание внутреннего цикла. Теперь рассмотрим внешний цикл. Отметим, что при каждом прохождении по внешнему циклу значение К уменьшается по крайней ..мере на L Действительно, если при этом мы проходим несколько раз внутренний цикл, то К уменьшается на соответствующую степень 2. Если внутренний цикл не выполнялся, то перед выходом из внешнего цикла значение
К стало равным К — L Таким образом, в любом случае значение К уменьшилось по крайней мере на 1 Так как известно, что 0<К, то значение К в конце концов станет равным. В этот момент условие К—0 будет истинным^ и внешний цикл закончится.
34. АКСИОМАТИЧЕСКИЙ подходК ДОКАЗАТЕЛЬСТВУ ЧАСТИЧНОЙ ПРАВИЛЬНОСТИ
Доказательство правильности для программ, написанных на языках программирования, до сих пор мы проводили так же, как и для блок-схем. Другими словами, мы сопоставляли с некоторыми ключевыми точками в программе индуктивные утверждения. В частности, с каждым из замкнутых путей (циклов) Должно быт£ сопоставлено по крайней мере одно такое утверждение. Затем мы показывали, что при попадании в процессе вычислений в ту или иную точку оказывалось справедливым соответствующее утверждение. Такое доказательство частичной правилта^тййожно провести и в другой форме. Можно показать, что доказательство основывается на некоторых аксиомах или правилах верификации. В этом разделе рассмотрены правила верификации и показана их эквивалентность методу индуктивных утверждений. Мы не приводим полного множества правил верификации для какого- нибудь языка, а лишь на двух примерах поясним основную идею. Для записи правил верификации используем следующую нотацию: {AJ Р (А2}, где А, и А2 — утверждения (индуктивные), а Р — фрагмент программы из*одного или нескольких операторов. Такая запись означает, что если непосредственно перед выполнением Р справедливо Аь то после выполнения Р (есди оно закончится) будет справедливо А2. Теперь перейдем к примерам правил верификации.
ПРИМЕР 1. Представим себе, что в используемом нами языке есть оператор вида IF С THEN SI ELSE S2 и мы хотим доказать (как часть некоторого доказательства правильности), что {Aj IF С THEN SI ELSE S2 {A2}. Другими словами, нужно доказать, что если непосредственно перед выполнением этого оператора было справедливо Аь а затем закончилось выполнение оператора, то непосредственно после этого будет справедливо А2. Мы предполагаем, что оператор имеет смысл, традиционный для языков програм-
мирования, т. е. он эквивалентен приведенному на рис. 3.2 фрагменту блок-схемы. Аксиома, или правило верификации, . которое можно использовать для доказательства приведенного утверждения, гласит:
Если можно доказать, что
{A,AC}S1{A2} и
{AIA-.C}S2{A2},
то отсюда можно заключить, что
{Aj IF С THEN SI ELSE S2 {А2}.
Эквивалентность этой аксиомы методу индуктивных утверждений в форме, приведенной в нашей книге, довольно очевидна. Действительно, доказывая правильность фрагмента программы, мы должны удостовериться в следующем:
. Если мы попали в точку 1 и справедливо утверждение Аь а затем проходим от точки 1 через точку 3 до точки 2, то А2 справедливо. Но для этого же пути известно, что до выполнения S1 справедливы и утверждение Аь и условие С, и мы стремимся показать, что если попадем в точку 2 (т. е. если выполнение S1 закончится), то А2 будет справедливо.
. Если мы попадем в точку 1 и справедливо утверждение Аь а затем проходим от точки 1 через точку 4 до точки 2,
то А2 справедливо. Но для этого пути известно, что до выполнения S2 справедливо А, и ложно условие С (т. е. -,С справедливо). Мы стремимся показать, что если попадем в точку 2 (т. е. при условии окончания S2), то будет справедливо А2.
Так™ образом, положения Г и 2', которые нужно доказывать в методе индуктивных утверждений, фактически идентичны двум предусловиям 1 и 2, которые нужно доказывать, если мы хотим использовать соответствующее правило верификации.
ПРИМЕР 2. Предположим, что мы используем язык, в котором есть оператор вида
DO WHILE (С) S
END;
Смысл этого оператора полностью характеризуется приведенным на рис. 3.3 фрагментом блок-схемы. Если нужно доказать [А^ Р {А2}, где Р — такой же цикл, как DO WHILE,
то можно воспользоваться следующей аксиомой или правилом верификации:
Если можно доказать, что
|A,AC}S|A|} и
из Af Л -]С следует А2,
то отсюда можно заключить, что 3){A1|P|Aj.
Эквивалентность этой аксиомы , методу индуктивных утверждений очевидна, так как при доказательстве частичной правильности нашего фрагмента блок- схемы мы должны удостовериться, что:
. Если мы попали в точку 1 и справедливо утверждение Аъ а затем проходим из точки 1 через точку 2 в точку 7, то снова спра-
ведливо утверждение Аь Но для этого пути нам известно, что до выполнения S справедливы и утверждение А, и условие С. Мы стремимся показать, что если мы вновь попадем в точку 1 (т. е. выполнение S закончится), то At будет справедливым.
2.' Если мы попадем в точку 1 и справедливо условие А1? а затем проходим из точки 1 в точку 5, то А2 будет справедливо. Но для этого пути справедливо At и ложно условие С (т. е. —iC справедливо). Мы стремимся показать, что из At и —»С следует А2.
Следовательно, оба положения Г и 2', которые нужно доказывать в методе индуктивных утверждений, фактически идентичны двум предусловиям 1 и 2, которые мы должны доказывать, если хотим использовать правило верификации.
Приведенное правило верификации иногда записывают в менее общей форме:
Если можно доказать, что
{А,AC} S {А,} , то отсюда можно заключить, что
{At} Р {AiA-rC| , где P-цикл вида DO WHILE. Читатель может легко убедиться в справедливости такого варианта правила, а кроме того, и в том, что он, как и первый вариант, используется лишь для доказательства частичной правильности. Оба варианта не гарантируют, что Р когда- нибудь закончится.
ПРИМЕР 3. Рассмотрим пример доказательства частичной правильности с помощью правил верификации. Для этого нам потребуется еще одно дополнительное правило. Предположим, что Pl, Р2 —два фрагмента программы и необходимо доказать некоторый факт, касающийся фрагмента, состоящего из Р1, за которым непосредственно следует Р2. В этом случае может оказаться полезным следующее правило верификации:
Если можно доказать, что
{Aj Pl {А2} и
{А2} Р2 {А3} , то отсюда можно заключить, что
{А,} Pl; Р2 {А3} .
В доказательстве мы будем использовать следующую запись:
{А,}
Р
{А2}-
Это то же самое, что и запись {Aj Р (Аг) .
Докажем с помощью правил верификации частичную правильность программы на ПЛ/1, соответствующей блок- схеме, для которой мы уже доказали частичную правильность в примере 3 в разд. 2.2. Читатель может сравнить эти два доказательства и убедиться в их идентичности. Фрагмент программы имеет вид
А { 0^ЛЛ1<32} */
IQ=0;
IR = Л;
A { Jl-IQ J2 + IRA0<IR} */
DO WHILE (IR>H);
IQ = IQ+1;
IR = IR-J2;
END ;
A {J1=IQ J2 + IRAOCIR<J1} */
Мы хотим доказать, что {O<J1A1<J2} Р U1—IQ-J2 + IRAO<IR<Jlj , где Р — приведенный выше фрагмент программы. Для этого нам нужно доказать:
{0<ЛА 1<J2} - {А,}
IQ-0;
IR — Л;
{Jl-IQ J2 + IRA0<IR} - {А2}
Это очевидно, так как после выполнения двух операторов из этого фрагмента программы мы будем иметь IQ—0 и IR — J1, а поэтому справедливо Л—0-12 + Л= Л. Кроме того, так как перед выполнением этих действий справедливо 0<Л и значение Л при этом не изменяется, то после их выполнения будет справедливо 0< Л — IR.
{ Л-IQ J2 + IRA0<IR} - {А2}
DO WHILE (IR>J1);
IQ = IQ+1;
IR —IR-J2;
END ;
{Л =IQ-J2 + IRA0< Л} - {A3}
Для доказательства этого положения воспользуемся правилом верификации, приведенным в примере 2. Из правила следует, что мы сможем сделать требуемое заключение, если докажем:
{A2AIR>J1}
IQ-IQ + 1;
IR-IR-J2;
/а2}
Пусть переменные перед выполнением этого фрагмента программЫ''имели значения Jln, J2„, IQn и IRn. Таким образом, Лп — IQn-J2n+IRn и 0<IRnAIRn>Hn. После выполнения программы переменные примут следующие значения: Лп-н ™Лщ J2n+i eI2n, IQn+i =IQn+ 1 и IRn+| "IRn J2n- Нужно показать,, что Лп+1 — IQn+1 • J2n+1 +IRn+1 и 0<IRn+1. Это легко сделать, подставив в наше высказывание Jln+1, J2n+i, IQn-м и IRn+l. После подстановки получим
nn = (IQ+l)J2n + (IRn-J2n) =
= IQn-J2n + J2n + IRn-J2n =
= IQn- J2n + IRn
и 0<IRn-J2n или J2nCIRn.
Оба утверждения по нашему предположению должны быть справедливы.
Из А2 h-.(IR> Л) следует Аэ, т. е. из Л — IQ • J2 + IRA A0<IR и-.(IR>Л) следует Л-IQJ2 + IRA0<IR< Л. Это очевидно, так как из Л = IQJ2+IR следует Л —IQ • J2 + + JR, а из 0< IR и—i(IR> Л) следует 0<IR< Л.
Таким образом, мы доказали:
{А,}
IQ-0;
IR-Л;
DO WHILE (IR>H);
IQ —IQ+1;
IR-IR-J2;
END; -
{Аз}
и, следовательно, по правилу верификации можно заключить, что требуемое утверждение справедливо:
{Aj} - {0<ЛЛ1<12}
IQ-0;
IR-J1;
DO WHILE (IR>J1);
IQ + IQ + 1;
IR —IR —J2;.
END;
{A3} - {JI-IQ J2 + IRA0<IR< Л}
Читатель может легко убедиться, что доказательство с по- мощью правил верификации фактически идентично доказательству, которое можно было бы провести методом индуктивных утверждений.
УПРАЖНЕНИЯ
Представьте, что в используемом вами языке программирования есть оператор вида IF С THEN S и нужно доказать, что (Aj IF С THEN S {Aj. Сформулируйте правило верификации для этого случая.
Представьте, что в используемом языкец программирования есть оператор вида REPEAT S UNTIL С. Этот оператор имеет традиционный для языков программирования смысл: он эквивалентен приведенной на рис. 3.4 блок-схеме. Сформулируйте правило верификации для доказательства справедливости {Aj REPEAT S UNTIL C{Aj.
Предположим, что нужно доказать:
{Aj
DO WHILE (Cl);
IF C2 THEN SI ELSE S2;
END;
{AJ.
Сформулируйте, что необходимо доказывать для этого, если речь идет об использовании правил верификации, приведенных в примерах 1 и 2.
С помощью правил верификации докажите частичную правильность следующего фрагмента программы на языке ПЛ /1:
Л{0<Н}*/
1-0;
J-1;
DO WHILE (К N);
J = J*М; 1=1+1; END;
/*{j = m**n} */
ДОКАЗАТЕЛЬСТВО ЧАСТИЧНОЙ ПРАВИЛЬНОСТИКАК ЧАСТЬ ПРОЦЕССА ПРОГРАММИРОВАНИЯ
Все примеры доказательства правильности в этой книге относились к законченным и небольшим программам. Однако на прак+ике такое доказательство должно проводиться программистом вр время самого процесса программирования. Не следует ожидать, что кто-Нибудь возьмет большую, законченную программу и начнет доказывать ее правильность. Интеллектуальные затраты на такую работу были бы, вероятно, очень велики. Нужно доказывать правильность небольших фрагментов по мере того, как они составляются. Для того, чтобы это можно было делать, такие секции (фрагменты) не должны вместе образовывать запутанный клубок, где не найдешь ни конца, ни начала каждой. Многие утверждают, что программист сможет избежать излишних необходимых усложнений и запутанных структур, если он не будет использовать переходы GO ТО и ограничит себя небольшим множеством операторов управления с одним входом и одним выходом. Такими операторами могут быть: последовательное выполнение, условное выполнение (ветвление) вида IF С THEN SI ELSE S2 и итеративная конструкция основного вида WHILE С DO S. Программы, написанные в таком стиле, часто называют «структурированными» программами. Тем не менее использование такого ограниченного подмножества операторов управления, конечно, не спасает программиста от всех «болезней». И с такими операторами управления, как и с оператором GO ТО, можно написать «мрачную» программу, которую трудно понять и верифицировать. Важно, чтобы программа была ясна по замыслу и при этом не использовались такие операторы управления, которые запутывают связи между отдельными частями (путем переходов вперед или назад с использованием оператора GO ТО).
Доказывая правильность больших частей программы, многие из подчаотей которой уже доказаны, можно такие подчасти трактовать как отдельные абстрактные операторы.
Выполнение таких операторов будет приводить к справедливости некоторых постусловий (утверждений о правильности такой подчасти) при условии, что до выполнения были справедливы предусловия (входные утверждения для этой подчасти). Таким образом, доказывая правильность большого фрагмента, вовсе нет необходимости разбираться во всех его частях. Часто процесс идет в обратном порядке, т. е. программист, работая по принципу «сверху вниз», составляет большой фрагмент, оставляя неопределенным в данный момент некоторые его части. Доказывая правильность этого фрагмента, он исходит из того, что упомянутые части будут написаны так, что их выполнение будет приводить к справедливости некоторых постусловий, если до их выполнения были справедливы предусловия. После этого, опираясь на эти утверждения, доказывается правильность большого фрагмента. Позже, при составлении программ для более мелких частей, для доказательства их правильности нужно лишь показать, что из допущения справедливое™ до их выполнения предусловий следует справедливость после выполнения постусловий.
Как уже говорилось в конце гл. 2, мы не считаем, что программисту необходимо выписывать все пункты доказательства правильности. Однако он должен выписывать все ключевые утверждения (как примечания), а затем удостоверяться в их справедливое™ (без деталей). Программист должен (без лишних подробностей) удостовериться и в том, что программа в конце концов закончит работу.
Основная мысль этого раздела заключается в том, что неформальное доказательство правильности следует проводить. в процессе программирования, а не после того, как программа написана.
УПРАЖНЕНИЯ
На одном из языков программирования напишите программу для каждой из задач и докажите ее правильность.
Предположим, что два массива целых чисел упорядочены в возрастающем порядке Х(1:М), Y(1 :N) [т. е. нет одинаковых элементов и Х(1) < Х(2) <... < Х(М), a Y(l) < Y(2) <... < Y(N)]. Напишите программу, создающую и печатающую два массива: UNION (1: LI), INTERSECTION (1: L2). Массивы построены следующим образом: UNION (1: L1) содержит в возрастающем (без повторений) порядке все элементы, имеющиеся либо
в Х(1:М), либо в Y(1:N), либо в Х(1:М) и Y(1:N); INTERSECTION (1: L2) содержит в возрастающем порядке (без повторении) все элементы, которые есть ив Х(1: М), ив Y(1 :N). •
Напишите программу, которая читает на входе любое положительное число и проверяет, совершенно ли оно. Число N называется совершенным, если и только если оно есть сумма всех целых I, удовлетворяющих условию 1 < I < N и делящих N без остатка. Например, 6 — совершенное число, ибо 6 — 1 + 2 + 3. -
Предположим, что имеется массив целых чисел Х(1: N), который нужно упорядочить, и самые лучшие универсальные алгоритмы упорядочения выполняют эту процедуру за время, пропорциональное N- logN. Однако, если известно, что массив содержит не слишком много различных значении, можно построить ’специализированный ' алгоритм, выполняющий упорядочение за время, пропорциональное N.
а) Предположим, Х(1: N) содержит только значения 1 и 2. Напишите программу упорядочения на основе принципа «смены мест» (т. е. в программе значения Х(1) изменяется только путем «обмена» значением с некоторым X(J), причем считать число элементов каждого типа запрещено). Время работы такой программы пропорционально N и программа заканчивается за один проход по массиву.
• б) Условия, аналогичны условиям задачи 3, п. «а», но в массиве встречаются лишь значения 1,2 и 3.
Предположим, что имеется массив Х(1 :N), значениями которого являются некоторые перестановки целых чисел от 1 до N. Напишите алгоритм «обменного» упорядочения, который „упорядочит массив по крайней мере 3aN обменов, потратив на это время, пропорциональное N.
Напишите программу, которая будет просматривать последовательность целых чисел длиной <N и определять, не равны ли стоящие рядом пары подпоследовательностей (длиной 1 и больше). Например, последовательности 3, 1, 2, 2, 4 и 1,3, 5, 2, 3, 5, 2, 7, 8 содержат по паре стоящих рядом равных подпоследовательностей; в последовательности же 1, 3, 5,2,3,5 такой пары нет. "
Напишите программу для формирования в лексикографическом порядке и печати всех перестановок из чисел 1,2,..., N.
Напишите программу для формирования и печати в возрастающем порядке N наименьших элементов из последовательности 1, 2, 3, 4, 6, 8, 9, 12, 16, 18, ... . Последовательность образована по следующим правилам:
1 — первый элемент последовательности;
если s принадлежит последовательности, то 2 • s и 3 • s также принадлежит последовательности;
в последовательности нет других элементов, кроме тех, которые удовлетворяют первым двум правилам.
Глава 4
ДОКАЗАТЕЛЬСТВО ПРАВИЛЬНОСТИ РЕКУРСИВНЫХ ПРОГРАММ
4JL ВВЕДЕНИЕ .
Во многих языках программирования (например, в Алголе и ПЛ/1) разрешается писать рекурсивные программы, т. е. программы, в которых при вычислениях допускается обращение (рекурсивное) к самой себе аналогично тому, как обычная, нерекурсивная программа обращается к некоторой подпрограмме. Рекурсивные программы особенно удобны в тех случаях, когда речь идет о данных, структура которых определяется рекурсивно (например, списки или деревья). В языке Лисп, созданном специально для «нечисленных» вычислений на основе списков, понятие рекурсии является фундаментальным. В этой главе описаны методы, которые полезны при доказательстве правильности рекурсивных программ. Основной метод в таких доказательствах называется методом структурной индукции. Доказательство методом структурной индукции представляет собой доказательство с помощью математической индукции, но индукция осуществляется по структуре данных, с которыми работает программа.
Если читатель незнаком с понятием рекурсии, он все равно сможет понимать излагаемый здесь материал. Мы не будем пользоваться каким-нибудь стандартным рекурсивным языком программирования, например Алголом или Лиспом, а «изобретем» упрощенный язык, которого нам будет достаточно, чтобы проиллюстрировать понятие рекурсии. В разд. 4.2 введен такой язык, в разд. 4.3 описан метод структурной индукции, а примеры рассмотрены в разд. 4.4. В разд. 4.5 показано, что метод структурной индукции может оказаться полезным и в тех случаях, когда речь идет о нерекурсивных программах. В частности, показано, что метод структурной индукции иногда легче использовать для доказательства правильности, чем метод индуктивных утвержде-
ний, если, например, речь идет о нерекурсивных программах, выполняющих рекурсивные по существу процессы, такие, как просмотр деревьев.
УПРОЩЕННЫЙ ЯЗЫК ПРОГРАММИРОВАНИЯДЛЯ ИЛЛЮСТРАЦИИ ПОНЯТИЯ РЕКУРСИИ
Упрощенный язык программирования нужен нам, чтобы ознакомить читателя с понятием рекурсии. Если читатель знаком с рекурсией в каком-либо из существующих языков программирования, таких, как Алгол или Лисп, ему будет очень просто сопоставить наш упрощенный язык с соответствующими возможностями в этих реальных языках. Однако, если вы даже и незнакомы с понятием рекурсии из существующих языков, это не помешает вам понять содержание данного раздела'
Программа в нашем упрощенном языке программирования состоит из определения функции, заданного в виде
F(X1,... , XN)*IF проверка 1 THEN выражение 1
ELSE IF проверка 2 THEN выражение 2
ELSE IF проверка m THEN выражение m ELSE OTHERWISE выражение m 4-1
Такая программа вычисляет значение F(X1,..., XN). Сначала выполняется проверка 1. Если проверка 1 дает ответ «истина», то значение функции есть значение выражения 1. Если проверка 1 дает ответ «ложь», то выполняется проверка 2. Если эта проверка дает ответ «истина», то значение функции есть значение выражения 2. Процесс идет таким образом до тех пор, пока не будет обнаружена первая из проверок (i-я), дающая значение «истина». Значение функции в этом случае есть значение выражения i. Если ни одна из проверок не окажется положительной, то значение функции есть значение выражения m +1. Рекурсия вводится в этот язык программирования следующим образом: функция F, определяемая в нашей программе, может входить как часть в любое из выражений или проверок в программе. Такое появление F назы-
вается рекурсивным обращением к этой функции. Для каждой такой рекурсивной программы нужно еще указать, для каких значений аргументов XI, ... , XN применима программа, т. е. нужно задать область определения функции. Выполнение программы заключается в применении ее к некоторым конкретным данным из ее области определения.
ПРИМЕР 1. Рассмотрим рекурсивную программу, определенную для любого положительного целого числа X:
F(X)-IF Х = 1 THEN 1'
ELSE OTHERWISE X • F(X -1)
Чтобы понять, как работает такая программа, выполним ее для некоторого конкретного значения аргумента X. Например,
F(4) -4F(3)
[Так как условие 4 — 1 ложно, то F(4) =4 • F(3).
Теперь нужно вычислить F(3), т. е. рекурсивно обратиться к F.]
= 43F(2)
[Так как при вычислении F(3) условие 3 — 1 ложно, то F(3) =
—3-F(2)J'■
= 4-3-2-F(l)
[Так как условие 2 — 1 ложно, то
F(2)-2.F(lj.l
' -4-3-2-1
[Так как условие 1 — 1 истинно, то
= 24-41
F(l)-L]
В разд. 4.3 мы докажем, что F(X)=X! для любого положительного целого числа X.
ПРИМЕР 2. Рассмотрим следующую рекурсивную функцию (функцию Аккермана), применимую для любой пары неотрицательных целых чисел XI, Х2:
А(Х1, Х2) = IF XI - О THEN Х2 + 1
ELSE IF Х2=0 THEN А(Х1-1, 1) ELSE OTHERWISE А(Х1 - 1, А(Х1, Х2 - 1))
А(1, 2) - А(0, А(1, 1))А(1, 2) - А(0, А(1, 1))Проследим, как выполняется такая программа для некоторой пары XI и Х2. Например,
А(1, 2) - А(0, А(1, 1))
А(1, 2) - А(0, А(1, 1))
[Так как условия XI — 0 и Х2=0 ложны, необходимо
вычислять А(1, 1)—рекурсивное обращение.]
— А(0, А(0, А(1,0))) [Так как условия XI—0 и Х2—0 ложны, нужно вычислять А(1,0).]
— А(0, А(0, А(0,1))) [Так как условие XI—0 ложно, а Х2—0 истинно.]
А(0, А(0, 2)) [Так как XI —0 истинно, следовательно, А(0,1) — —1+1—Z]
Аф, 3)[Так как XI —0 истинно, сле
довательно, А(0,2)—2 +1—
;-3.]
4 -[Так как XI —0 истинно,
следовательно,А (0,3)—
-3 + 1-4.]
Из этого примера следует, что при выполнении вычислений для рекурсивной программы мы можем сталкиваться с неоднозначностями, которые требуют уточнений. Например, в приведенных вычислениях при обращении А(0, А(1,1)) мы предполагаем, что А(1, 1) должно быть вычислено прежде, чем мы продолжим вычисления, относящиеся к внешнему обращению к А. Если отдать предпочтение вычислениям, связанным с внешним обращением, то вычисления должны были бы идти в следующем порядке:
А(1, 2) - А(0, А(1, 1))-А(1,1) +1 -
- А(0, А(1, 0)) +1 - (А(1, 0) +1) +1 - - (А(0, 1) +1)+1 —(2 +1) +1 —4.
Значение А(1, 2) остается тем же, хотя последовательность вычислений отлична от предыдущей. Можно доказать, что если к одним и тем же аргументам некоторой рекурсивной функции применять две различные последовательности ее вычисления, то результат будет одним и тем же при условии конечности этих последовательностей. Однако вполне возможно, что одна последовательность не будет заканчиваться, хотя другая последовательность закончится. Рассмотрим следующий пример.
ПРИМЕР 3. Возьмем рекурсивную программу, применимую к любой паре неотрицательных целых XI, Х2:
IF(X1, Х2)—IF XI-О THEN О ■ELSE OTHERWISE F(0, F(X1, X2))
Проследим за вычислением F(l, 1), используя два различных правила вычислений. По первому правилу мы будем стараться сначала заканчивать вычисления, относящиеся к внешнему обращению. По второму правилу мы будем отдавать предпочтение самому внутреннему обращению:
F(l, 1) - F(0, F(l, 1)) [Так как условие XI -О ложно.]
— О[Так как условие XI —О
'истинно для внешнего
обращения к FJ
F(l, 1) - F(0, F(l, 1)) [Так как условие XI -О ложно.]
— F(0, F(0, F(l, 1))) [Так как условие XI —0 для ■внутреннего обращения
ложно.]
- F(0, F(0, F(0, F(l, 1)))) - - F(0, F(0, F(0, F(0, F(l, ]))))) и т. д.
Таким образом, в первом случае последовательность вычислений F(l, 1) конечна и F(l, 1)—0. Во втором случае вычисления не заканчиваются и, следовательно, значение F(l, 1) не определено. Однако отметим, что если для каких- либо аргументов в обоих случаях вычисления заканчиваются, то результат будет одним и тем же. Например, F(0, М)—0 независимо от применяемых правил вычислений.
Итак, чтобы сделать наш язык программирования точным, нам нужно задать правила вычислений для программ, определяющие последовательность вычислений. В данной главе мы будем считать, что правило вычислений отдает предпочтение самому левому и самому внутреннему обращению. Другими словами, в любой из моментов вычислений первым начинает вычисляться левое и внутреннее обращение' к функции F, все аргументы которой уже не содержат F. Это правило — не обязательно наилучшее, иногда оно может приводить к неоканчивающейся последовательности вычислений, хота
1 Для простоты мы «езде слово «самое» будем опускать. — Прия. ped.
другое правило дало бы конечную последовательность (пример 3). Однако во многих существующих языках программирования используется правило вычислении, подобное описанному выше. Кроме того, как мы уже отмечали, если, следуя этому правилу, вычисления заканчиваются, то результат будет тем же, что и при других правилах, приводящих к окончанию. Большинство рассматриваемых нами программ заканчивается при любых значениях аргументов независимо от того, какие правила вычислений мы используем, а следовательно, и результат в любых случаях будет одинаковым. Однако для определенности все же будем считать, что предпочтение отдается левому внутреннему обращению.
Многие из приводимых в этой главе примеров относятся к работе со списками. В таких программах мы используем некоторое подобие «лисповской» нотации. По этой нотации список —набор объектов, отделенных друг от друга пробелами и заключенных в квадратные скобки [ ]. Объектами, входящими в такие списки, являются атомы или другие списки. Атом — строка буквенно-цифровых символов, не содержащая пробелов. Мы будем считать, что атомы должны начинаться с одной из букв А, В, С, D или F, а переменные — с букв, отличных от этих букв. Такой прием позволяет нам при написании программ различать переменные и атомы и обходиться без более общей функции QUOTE, используемой в Лиспе. Например, [А В С] —список из трех элементов* каждый из которых есть атом; [А[В А[С]] D С] —список, в котором (на верхнем уровне) четыре элемента. Второй элемент — сам список [В А[С]]. Третий элемент этого списка — опять список [С]. Для пустого списка, т. е. списка, не содержащего ни одного элемента, мы выделяем специальное имя NIL. Кроме того, мы будем использовать следующие проверки и функции:
Проверка = может применяться как к спискам, так и к числам. Например, проверка [А В] —[А В] истинна, а проверки [А В] —[В А], [А В] —[А [В]], В = A, A —NIL ложны.
Проверка ATOM (X) применима к любым объектам, будь то атомы или списки. ATOM (X) дает значение TRUE (истина), если X — атом или пустой список. Если X — непустой список, то ATOM (X) дает значение FALSE (ложь).
Функция CAR (L) применима к любому непустому
дает А. дает [А В], не Определено, не определено.дает А. дает [А В], не Определено, не определено.списку. В качестве результата выступает первый (на верхнем уровне) элемент этого списка. Примеры:
дает А. дает [А В], не Определено, не определено.
дает А. дает [А В], не Определено, не определено.
CAR([A В С])
CAR([[A В]С])
CAR(NIL)
CAR(A)
Функцию CDR(L) можно применять к любому непустому списку. Результатом является список, полученный из L путем отбрасывания первого его элемента (на верхнем
дает [В С].дает [С].дает NIL (или [ ]). не определено, не определено.дает [В С].дает [С].дает NIL (или [ ]). не определено, не определено.уровне). Примеры: 1) CDR([A В С]) 2) CDR([[A В]С]) 3) CDR([B]) 4) CDR(NIL) 5) CDR(A)
дает [В С].
дает [С].
дает NIL (или [ ]). не определено, не определено.
дает [В С].
дает [С].
дает NIL (или [ ]). не определено, не определено.
Функцию CONS(X, L) можно применять к любому списку L и любому X, будь то атом или список. В результате получается новый список: первый его элемент есть X, а потом идут элементе списка L. Примеры:
CONS(A, [В С]) дает [А В С].
CONS([A В], [В [С]]) дает [[А В] В [С]].
CONS(A, NIL)дает [А].
CONS(A, В)не определено.
В некоторых из наших примеров мы будем использовать и специальные атомы TRUE и FALSE.
ПРИМЕР 4. Как пример использования новой нотации приведем рекурсивную программу, определяющую, является ли X элементом списка L (на верхнем уровне):
MEMBER (X, L)-IF L-NIL THEN FALSE
ELSE IF X-CAR(L) THEN TRUE
ELSE OTHERWISE MEMBER (X, CDR(L))
Проследим, как идет процесс вычислений по такой программе для фактических аргументов С и [А В С D]:
MEMBER(C,[A BCD]) - MEMBER(C, [В С D]) - MEMBER(C,[C D]) - TRUE
Теперь проследим, как проходит вычисление при фактических аргументах С и [А В[С D]]:
MEMBER (С, [А В [С D)D - MEMBER (СДВ[С ОД)
- MEMBER(CJ[C D]]) - MBMBBR(C, NIL) «FALSE
ПРИМЕР 5. Напишем рекурсивную программу, добавляющую список L1 к списку L2, другими словами, в этом списке, состоящем из двух, элементы первого стоят перед элементами второго:
APPEND(L1, L2)-IFL1“NIL THEN L2
ELSE OTHERWISE CONS(CAR(L1),
; APPBND(CDR(L1), L2))
Проследим, как идет процесс вычислений при нескольких различных парах аргументов:
APPEND(IА В], (С D])-CONS(CAR ([АВ]), APPEND (CDR([A В]), [С DD))
-CONS(A, APPEND([B], (С D»
-CONS(A, CONS(CAR([BD, APPBND(CDR([B]), [C D])))
-CONS(A, CONS(B, APPBND(NIL, [C D])))
“CONS(A, CONS(B, [C, D]))
“CONS(A, [В C Dj)
-[A BCD]
APPEND([[A]], [В C]) -CONS([A), APPEND(NIL, [B C] “CONS([A], [В C]) “ЦА]ВС
APPEND([A B], NIL) -CONS(A, APPEND([B], NIL)) - CONS(A, CONS(B, APPEND (NIL, NIL)))
-CONS(A, CONS(B, NIL)) -CONS(A, [B]) -[AB]
УПРАЖНЕНИЯ.
Проследате, как проходит процесс вычиспения функции Аккермана (программа приведена в примере 2 в разд. 42?) при следующих аргументах:
А(1,1);
А(2,1).
Применяйте при этом правило вычисления левого внутреннего обращения,
Покажите для двух процессов, приведенных в упражнении 1, что если вместо правила «левое внутреннее» использовать правило «левое внешнее», то будем получать те же результаты. Вычисление (при аргументе в п. «б»), например, в этом случае начинается так:
А(2,1) — А (1, А (2,0))
- А (0, А (1, А (2,0) - 1))
— А (1, А (2,0) - 1) + 1 и т. д.
Отметим, что на последнем из приведенных этапов вычислений проверка Х2 — 0 превращается в проверку А(2,0) -1—0. Эту проверку нужно проводить, опять же исходя из правила «левое внешнее^. Однако для упрощения работы можно использовать следующие факты:
условие А(2,0) -1 — 0 дает ответ FALSE,
условие А(2,0)-2 —0 дает ответ FALSE,
условие А(2,0)-3 —0 дает ответ TRUE, если их вычислять по правилу «левое внешнее».
Рассмотрим следующую рекурсивную программу, применимую к любой паре целых XI, Х2:
F(X1, Х2) - IF Х2 = 0 THEN 0
ELSE OTHERWISE F(F(X1,X2 - XI),X2 - 1)
Вычислите F(l,l) и F(0,l), используя правила вычисления «левое внутреннее» и «левое внешнее». Отметим, что если вычисления заканчиваются при правиле «левое внутреннее», то они заканчиваются и дают тот же результат и при правиле «левое внешнее». Однако при использовании правила «левое внешнее» процесс может и не закончиться, хотя при другом правиле он заканчивается. В последующих упражнениях, как и далее во всей главе, мы всегда будем использовать правило «левое внутреннее».
Для программы в примере 5 проследите последовательность вычислений для случаев:
APPEND (NIL, [А В С])
APPEND ([А В С], NIL)
APPEND ([[А В] С]6 [D])
Дана функция F(X,Y)* IF Х = 0Л¥ * 0 THEN 0
ELSE IF X = 0AY*0 THEN Y- 1 ELSE IF X * 0Л Y = 0 THEN X - 1 ELSE OTHERWISE F(F(X - 1,Y), F(X,Y - 1))
Выпишите последовательность вычислений для случаев
F(l,l)
F(2,l)
Для рекурсивной программы, применимой к любым двум спискам L1,L2:
INT(L1, L2)- IF LI = NIL THEN NIL
ELSE IF MEMSER(CAR(L1), L2) THEN CONS(CAR(L1), INT(CDR(L1), L2) ) ELSE OTHERWISE INT(CDR(L1), L2)
Выпишите последовательность вычислений для случаев
INT(NIL, [А В С])
INT([B D С], [А В С])
INT([[B D] С], [А В D]) Приведенная программа использует определенную в примере 4 программу MEMBER. При выписывании последовательности вычислений для INT не надо записывать промежуточные этапы вычислений для MEMBER. Считайте, что MEMBER (X,L) дает значение TRUE, если X является элементом (на верхнем уровне) списка L, в противном случае эта программа дает значение FALSE.
СТРУКТУРНАЯ ИНДУКЦИЯ
Рекурсивный программы обычно строятся по следующей схеме: сначала в них явно определяется способ вычисления результата для наипростейших значений аргументов рекурсивной функции, затем способ вычисления результата для аргументов более сложного вида, причем используется сведение к вычислениям с данными более простого вида (с аргументами рекурсивного обращения к той же функции). Таким образом, вполне естественно попытаться доказать правильность таких программ следующим образом:
Доказать, что программа работает правильно для простейших данных (аргументов функции).
Доказать, что программа работает правильно для более сложных данных (аргументов функции) в предположении, что она работает правильно для более простых данных (аргументов функции). *
Такой способ доказательства правильности для рекурсивных функций естествен, поскольку он следует основной схеме вычислений в рекурсивных программах. Читатель уже, конечно, обратил внимание на то, что этапы 1 и 2 фактически являются этапами доказательства с помощью индукции, но индукция осуществляется «по структуре» данных, которые обрабатывает программа. В частности, если можно сопоставить интуитивное понятие более простых данных (допустимых аргументов функции) и более сложных с отношением во вполне упорядоченном множестве значений данных (допустимых аргументов функции), то этапы 1 и 2 будут аналогйчны этапам доказательства правильности работы программы для всех допустимых аргументов с помощью метода обобщенной индукции. Предложенный выше способ доказательства правильности назовем доказательством с
помощью структурной индукции. Проиллюстрируем этот метод на нескольких очень простых рекурсивных программах.
ПРИМЕР 1. Докажем правильность следующей программы (разд. 4.2, пример 1):
F(X)^IF Х = 1 THEN 1
ELSE OTHERWISE X • F(X - 1)
Предполагается, что эта функция вычисляет факториал от аргумента. Нужно доказать, что F(N) = 1 • 2 • 3 •... • N = N! для любого положительного числа N. При доказательстве с помощью структурной индукции используем простую индукцию по положительным целым числам:
Докажем, что F(l) = 1!. Действительно, F(l) =1=1!.
Докажем (для любого положительного числа N), что ecnnF(N) = 1 • 2 - ...-N = N!,toF(N + 1) = 1 • 2 • ... • N • (N + 1) = = (N + 1)!. Следовательно, мы предполагаем, что N — положительное число, a F(N) = N! — гипотеза индукции. Так как N — положительное число, то проверка N + 1 = 1 дает отрицательный ответ, и, прослеживая далее последовательность вычислений, получаем
F(N + 1) = (N + 1) • F((N + 1) - 1) = (N + 1) • F(N) =
= (N + 1) • (N!) = (N + 1) • (1 • 2 • ...-N) =
(По гипотезеиндукции)
= l-2-...-N-(N+l) = (N + l)!
что и требовалось доказать, т, е. F(N) = N! для любого положительного числа N.
ПРИМЕР 2. Докажем правильность следующей программы (разд. 42, примера 4):
MEMBER(X,L) = IF L = NIL THEN FALSE
ELSE IF X = CAR(L) THEN TRUE ELSE OTHERWISE MEMBER (X, CDR(L))
Эта программа применима для любого элемента X и любого списка L. Предполагается, что она дает значение TRUE, если X входит в качестве элемента верхнего уровня в список L; в противном же случае она дает значение FALSE:
MEMBER(X,L') = TRUE, если X элемент списка L'. = FALSE в других случаях.
Для доказательства правильности работы этой программы используем структурную индукцию. Анализ программы показывает, что при рекурсивном обращении к MEMBER из третьей строки программы только второй аргумент обращения выглядит проще, чем ранее. Таким образом, естественно вести индукцию только по второму аргументу функции. При рекурсивном обращении к MEMBER ее второй аргумент CDR(L) представляет собой список, который содержит на один элемент (на верхнем уровне) меньше, чем список L. Следовательно, в структурной индукции можно использовать простую индукцию по числу элементов в списке L. Так как это число всегда неотрицательно, то доказательство основы- . вается на простой индукции по неотрицательным целым
числам. Итак, нужно:
Доказать, что для любого списка, содержащего нуль элементов, MEMBER работает правильно. Поскольку список, имеющий нуль элементов, это просто пустой список NIL, то очевидно, что MEMBER (X,NIL) = FALSE, так как X не есть элемент списка NIL.
Доказать (для любого целого числа N), что если программа MEMBER правильно работает для всех списков L', содержащих N элементов (на верхнем уровне), то она будет правильно работать и для всех списков L, содержащих на верхнем уровне N + 1 элементов. Поэтому предположим, что MEMBER правильно работает для списков L' длиной N, т. е.
MEMBER(X,L') = TRUE если X есть элемент из L', = FALSE в других случаях.
Это гипотеза индукции. Предположим, что L есть список, coдepжaщийN + 1 элементов. Поскольку N + 1>1,toL^NIL. Прослеживая выполнение функции, видим, что
MEMBER(X Л) = TRUE если X - CAR(L),
= MEMBER(X,CDR(L)) в других случаях. Если X = CAR(L) (а мы знаем, что CAR(L) определено, так как L^NIL), то X —элемент списка L, и, следовательно, значение TRUE есть именно то значение, которое требуется. Если X = CAR(L), то X будет элементом списка L, если и только если X будет элементом CDR(L). (Функция
CDR(L) определена, так как L * NIL.) Однако CDR(L) представляет собой список из N элементов, и по гипотезе индукции следует, что MEMBER(X,CDL(L)) будет правильно вычислять значение TRUE или FALSE в зависимости от того, является ли X элементом CDR(L) или нет. Таким образом, если X^CDR(L), то MEMBER(XL) = MEMBER(X,CDR(L)), а последняя функция вычисляет значение либо TRUE, либо FALSE в зависимости от того, будет ли X элементом CDR(L), а следовательно, элементом списка L или нет, что и требовалось доказать.
ПРИМЕР 3. Докажем правильность следующей рекурсивной программы (разд. 42, пример 5):
APPEND(L1,L2) - IF LI - NIL THEN L2
ELSE OTHERWISE CONS(CAR(L1), APPEND(CDR(L1)JL2))
Предполагается, что программа применима к любым двум спискам L1 и L2 и в качестве результата дает список, состоящий из элементов списка L1, за которым следуют элементы списка L2. Анализ программы показывает, что при рекурсивном обращении к APPEND только первый из ее двух аргументов выглядит проще, чем ранее. Таким образом, при доказательстве методом структурной индукции используем простую индукцию по длине (числу элементов) списка L1. Для этого необходимо:
Доказать правильность работы APPEND для любого списка L1 данной 0. Список дайной нуль—пустой список NIL. Утверждетше APPEND(NIL,L2) “ L2 правильно, так как это список, составленный из элементов списка L1 (а он пустойХ за которыми следуют элементы списка L2.
Доказать для любых неотрицательных целых чисел N, что если APPEND правильно работает для любых списков L1' длиной N, то APPEND будет правильно работать и для всех списков L1 дайной N + 1. Предположим, что APPEND для всех списков L1' дайной N работает правильно, т. е. APPEND(L1’JL2) есть список, составленный из элементов L1', за которыми следуют элементы списка L2. (Это гипотеза индукции.) Предположим, что длина списка L1 равна N + 1. Так как N +1>1, то Ll ^ NIL. Прослеживая вычисле-
ние функции, мы видим, что
APPEND(L1, L2) =CONS(CAR(L1), APPEND(CDR(L1), L2)).
(Известно, что CAR(Ll) и CDR(Ll) определены, ибо LI ^ NIL.) Однако CDR(Ll) — список длиной N, и из гипотезы индукции следует, что APPEND(CDR(L1),L2) есть список, состоящий из элементов CDR(Ll) и элементов списка L2. Таким образом, список APPEND(CDR(L1),L2) содержит все элементы списка Ы> кроме первого, за которыми идут все элементы списка L2. Кроме того, так как CONS(CAR(L1), APPEND (CDR(LI)JjI)) образует список, у которого первый элемент CAR(Ll), а затем следуют элементы списка APPEND(CDR (Ы)Х2), то очевидно, что этот список состоит из всех элементов списка L1 (включая и первый элемент CAR(Ll)) и всех элементов списка L2. Следовательно, в этом случае | функция APPEND работает правильно, что и требовалось доказать.\
Замечание|
В начале раздела мы уже говорили о том, что если можно ; было бы сопоставить наше интуитивное представление о | простоте данных (допустимых аргументов функции) с неко- | торым вполне упорядочивающим отношением < на множе- 5 стве значений данных, то доказательство правильности для рекурсивных программ можно было бы проверить методом обобщенной индукции по этим данным. Отметим, что в примере 1 мы проводили доказательство именно этим методом, поскольку данными этой функции были целые числа, а обыч- ное отношение < вполне упорядочивает соответствующие i значения. В примерах же 2 и 3 такого отношения <, опре- I деленного на множестве значений данных для наших про- j грамм (это были списки), уже не было. Вместо него мы ; использовали тот факт, что вполне упорядоченным было I множество некоторых свойств значений данных, а именно ; длины. Длина любого списка — неотрицательное целое число, а обычное Отношение < вполне упорядочивает множество неотрицательных целых чисел. Если мы хотим определить отношение < не на множестве некоторого из свойств данных, а на самих данных (в нашем случае списках), то это можно сделать следующим образом: будем говорить, что LKL2, если и только если длина LKдлины L2. К не-
счастью, для вполне упорядочивающего порядка этого недостаточно, так как отношение < определяет лишь частичный, а не общий порядок. Например, при L1 = [А'В С] и L2 = [DEF] мы не можем сказать, что LKL2, или L2<L1, или LI = L2. Другими словами, существуют данные (списки одинакового размера), не сравнимые между собой, и, следовательно, порядок, заданный отношением <, не является общим. Таким образом, наше множество не будет вполне упорядоченным; иногда такие множества называют обоснованно упорядоченными (well-founded). Оказывается, что существования обоснованного порядка (который более общий, чем вполне упорядочивающий) достаточно для проведения доказательства по индукции. Таким образом, в примерах 2 и 3 доказательство можно было бы проводить с помощью обобщенной индукции по данным, упорядоченным на. основе обоснованного порядка. Обоснованный порядок < можно было бы и превратить во вполне упорядочивающий, если списки равной длины считать не несравнимыми, а равными; но это уже несколько неуклюжий прием. В данной главе мы будем продолжать проводить индукцию на множестве некоторых свойств данных (таких, как длина) и не беспокоиться по поводу формального определения типа существующего порядка.
УПРАЖНЕНИЯ
Рассмотрите следующую рекурсивную программу, применимую для любых неотрицательных целых чисел N:
F(N) ■ IF N = О THEN О
ELSE OTHERWISE F(F(N - 1))
Докажите, что N, F(N) = 0 для любого неотрицательного целого числа N.
Следующая рекурсивная программа применима для любого положительного целого числа N:
F(N)=IF N = 1 THEN 1
ELSE OTHERWISE F(N - 1) + N
Докажите, что F(N) = (N • (N + 1) )/2 при любом положительном целом N.
Рекурсивная программа применима для любых неотрицательных целых чисел N:
F(N) - IF N = О THEN 1
ELSE OTHERWISE 2 • F(N - 1)
Выведите формулу для вычисляемой функции F(N) и докажите ее пра
вильность.
Рассмотрите следующую рекурсивную программу, применимую для любых неотрицательных целых чисел N:
F(N)-IF N<1 THEN N
ELSE OTHERWISE F(N - 2)
Докажите, что для всякого четного неотрицательного целого числа функция F равна 0, т. е. F(2 • N) = 0 для всех неотрицательных целых N. Кроме того, докажите, что для каждого нечетного неотрицательного целого числа значение F равно единице.
Рекурсивная программа применима для любых списков L:
REVERS (L) - IF L = NIL THEN NIL
ELSE OTHERWISE APPEND(REVERSE।(CDR(L), CONS(CAR(L), NIL)»
Докажите, что для любого списка L REVERS(L) представляет собой список, содержащий те же элементы, что и список L, но размещенные в обратном порядке. При доказательстве можно исходить из того, что APPEND работает правильно (как это показано в примере 3).
6а. Рекурсивная программа применима к любому объекту X и любому списку L:
DELETE1 (X,L)*IF I-NIL THEN NIL
ELSE IF X « CAR(L) THEN CDR(L) ELSE OTHERWISE CONS(CAR(L), DELETE1(X,CDR(L)))
Докажите, что DELETE1(X,L) для ^любого списка L представляет собой список, полученный вычеркиванием из L первого вхождевмя элемента X на верхнем уровне (если он там есть). Например,
DELETE 1 (В, [А[В] В С В]) = [A [BJ С В].
Опишите рекурсивную программу DELETE(X,L), которая вычеркивает из L все вхождения на верхнем уровне объекта X. Докажите, что ваша программа работает правильно.
Две приведенные маже функции применимы к любым двум множествам, представляемым списками. Множество есть список без повторяющихся элементов, т. е. ни один из любых двух элементов не равен другому:
INT(LL L2)- IF LI - NIL THEN NIL
ELSE IF MEMBER(CAR(L1),L2)THEN CONS(CAR(L1), INT(CDR(L1),L2))
ELSE OTHERWISE INT(CDR(L1), L2) UNION(LLU)*lF LI = NIL THEN L2
ELSE IF MEMBER(CAR(L1),L2) THEN
UNION (CDR(L1), L2)
ELSE OTHERWISE CONS(CAR(L1), * UNIONS(CDR(L1), L2))
Докажите, что для любых двух множеств L1 и L2 tNT(Ll L2) представляет пересечение (множество) L1 и L2, a UNION(L1, L2) —объединение мно- жествЫмЕТ:
INT(L1, L2) — множество (список без повторяющихся элементов), которое содержит элементы, имеющиеся в списках ЫиЬ2.
UNION(L1} L2) = множество (список без повторяющихся элементов), которое содержит элементы, имеющиеся либо в L1, либо в L2, либо в L1 и в L2.
Можно предположить, что функция MEMBER, как это показано в примере 2, работает правильно.
Рассмотрите следующую рекурсивную программу, аргументами которой могут быть любые два множества L1 и L2:
F(L1, L2) * IF LI - NIL THEN TRUE
ELSE IF MEMBER(CAR(L1), L2) THEN
F(CDR(L1), L2)
ELSE OTHERWISE FALSE
Выясните, что делает эта программа, и докажите ваше утверждение.
Рассмотрите две рекурсивные программы (LEAST применима к любому непустому списку, состоящему из чисел, LI, a SORT применима к любому списку, состоящему из чисел, L2):.
LEAST(Ll) * IF CDR(L1) = NIL THEN CAR(Ll)
ELSE IF CAR(L1)<LEAST(CDR(L1)) THEN CAR(Ll) ELSE OTHERWISE LEAST (CDR(Ll))
SORT(L2) "IF L2 = NIL THEN L2
ELSE OTHERWISE CONS(LEAST(L2), SORT (DELETE1 (LEAST(L2), L2)))
Докажите, что для любого непустого числового списка L2 программа SORT(L2) дает список L2, упорядоченный в неубывающем порядке. Для этого вначале докажите, что при любом непустом числовом списке L1 программа LEAST(Ll) дает наименьшее число из списка L1. Можно предполагать, что DELETE 1 работает так, как описано в упражнении 6.
Требуется исследовать влияние ошибок в программах на проведение доказательства правильности. Для каждой из приведенных ниже ошибочных программ постарайтесь, как и выше, провести доказательство правильности и выявить те места, где доказательство не будет проходить. Обнаружив ошибку в доказательстве, найдите некоторые специфические значения для данных, при которых программа будет давать неверный ответ.
Предположим, программа из упражнения 2 по ошибке записана следующим образом:
F(N)"IF N= 1 THEN 2
ELSE OTHERWISE F(N - 1)
F(N)"IF N = 1 THEN 1
ELSE OTHERWISE F(N - 1) + 1
F(N)"IF N= 1 THEN 1
ELSE OTHERWISE F(N - 1) +N - 1
Представьте себе, что программа из упражнения 6а записана по ошибке следующим образом:
DELETE1 (X, L) " IF L = NIL THEN NIL
ELSE IF X = CAR(L) THEN CDR(L)
ELSE OTHERWISE DELETE1(X,CDR(L))
в) Представьте себе, что по ошибке программа из упражнения 7 записана одним из следующих способов:
1) INT(L1, L2)
-IF
LI = NIL THEN NIL
ELSE OTHERWISE INT(CDR(L1), L2)
2) INT(L1,L2)
-IF
LI = NIL THEN NIL
ELSE IF MEMBER(CAR(L1), L2) THEN CONS(CAR(L1), INT(CDR(L1), CDR(L2)))
ELSE OTHERWISE INT(CDR(L1), L2)
3) INT(Li,L2)
-IF
LI = NIL THEN NIL
ЛELSE IF MEMBER(CAR(L1),L2) THEN
*CONS(CAR(L1), INT(CDR(L1), L2))
ELSE OTHERWISE INT(L1 L2)
UNION(L1 L2) - IF LI = NIL THEN NIL
‘’ ELSE IF MEMBER(CAR(L1), L2) THEN
UNION(CDR(L1), L2)
ELSE OTHERWISE CONS(CAR(L1),UNION(CAR(L1), L2))
IIБОЛЕЕ ТРУДНЫЕ ПРИМЕРЫ ДОКАЗАТЕЛЬСТВА ПРАВИЛЬНОСТИ ПРОГРАММ МЕТОДОМ СТРУКТУРНОЙ ИНДУКЦИИ Доказательства правильности программ методом структурной индукции, приводимые в данном разделе, основываются либо на более строгой версии индукции (см. разд. 1.3 гл. 1), либо на обобщенной индукции (см. разд. 1.4 гл. 1). Если читатель пропустил один или оба эти раздела, то теперь следует вернуться к ним и ознакомиться с их содержанием.
I
I
ПРИМЕР Г. Рассмотрим следующую рекурсивную программу, применимую к любым объекту X и списку L:
MEMBER1(X ,L) = IF X = NIL THEN FALSE
ELSE IF X = CAR(L) THEN TRUE ELSE IF ATOM(CAR(L))THEN MEMBER 1(X,CDR(L))
ELSE IF MEMBER1(XCAR(L)) THEN TRUE
ELSE OTHERWISE
Мы хотим доказать, чтоMEMBER1(X,L) =Мы хотим доказать, чтоMEMBER1(X,L) =MEMBER 1(Х,CD R(L))
Мы хотим доказать, что
MEMBER1(X,L) =
Мы хотим доказать, что
MEMBER1(X,L) =
TRUE, если X встречается в качестве элемента (на любом уровне);
FALSE в противном случае.
Доказательство проведем с помощью строгой индукции по общему числу п вхождений скобок и атомов в списке L. Например, для списка [[А В] С [[В [А]]] число скобок и атомов равно 14. А для списка CAR(L) п = 4. Наименьшее из возможных значений общего числа скобок и атомов в списке достигается для пустого списка NIL = [ ] и равно 2. Для проведения доказательства правильности нужно:
Доказать, что если для списка L п = 2, то программа MEMBER1 работает правильно. Действительно, если п = 2, то список должен быть пустым списком NIL, и отсюда
MEMBER 1(ХД.у= MEMBER 1(X,NIL)
= FALSE
что верно, так как X действительно не встречается в пустом списке.
Доказать (для всех п>2), что если' программа MEMBER 1(ХД/) работает правильно для любого списка L' с п'<п+1 (общее число скобок и атомов), то MEMBER 1(X,L) также будет правильно работать для списка с общим числом скобок и атомов, равным n + 1 (используется строгая индукция по п). Предположим, что программа MEMBER1(X,L') для любого списка L' с n'<n+ 1 работает правильно (гипотеза индукции) и L—список, содержащий n + 1 вхождений скобок и атомов. Мы знаем, что L * NIL. Если X = CAR(L), то значение MEMBER 1(X,L) есть TRUE. И это действительно верно, ибо X встречается в качестве L. Если X = CAR(L) и ATOM(CAR(L)) = TRUE, то MEMBER 1(Х ,L) = MEMBER 1(X,CDR(L)). Однако CDR(L) — список, содержащий n' < n + 1 вхождений скобок и атомов. Поэтому по гипотезе индукции можно предположить, что MEMBER 1(X,CDR(L)) правильно вычисляет значение TRUE или FALSE в зависимости от того, входит X или не входит в число элементов (любого уровня) CDR(L). Так как X ^ CAR(L) и ATOM(CAR(L)) = TRUE, то мы знаем, что X будет входить как элемент в L, если и только если X входит как элемент в CDR(L). Таким образом, в зависимости от того, входит или нет X в число элементов L, мы будем получать верное значение. Если X = CAR(L) и ATOM(CAR (L)) = FALSE Л MEMBER 1(Х,С AR(L)) = TRUE, то значение MEMBER 1(X,L) равно TRUE. И это опять правильно, ибо мы знаем, что в этом случае CAR(L) — список, содержащий
n'<n+l скобок и атомов. Из гипотезы индукции можно заключить, что MEMBER(X,CAR(L)) работает правильно, и, если MEMBER 1(X,CAR(L)) =-TRUE, X действительно встречается в качестве элемента в CAR(L), а следовательно, и в L. Наконец, если X * CAR(L) и ATOM(CAR(L)) = FALSE и MEMBER 1(X,CAR(L)) = FALSE, то MEMBER 1(X L) = = MEMBER1(X,CDR(L)). Однако, так как CDR(L)—список с n' < n + 1 вхождениями скобок и атомов, то из гипотезы индукции можно заключить, что MEMBER 1(X,CDR(L)) правильно вычисляет значения TRUE или FALSE в зависимости от того, входит X в число элементов CDR(L) или "нет. Так как р этом ёлучае X^CAR(L) и ATOM(CAR(L)) = FALSE Л MEMBER 1(X,CAR(L))=.FALSE, то X будет элементом L, если и только если X является элементом CDR(L). Следовательно, й в этом случае значение MEMBER 1(Х L) = = MEMBER(X,CDR(L)) будет вычисляться верно.
ПРИМЕР 2. Рассмотрим снова функцию Аккермана (разд. 42, пример 2):
A(X1^2)"IF XI = О THEN Х2+1
ELSE IF Х2 = О THEN А(Х1 - 1,1)
ELSE OTHERWISE А(Х1 - 1, А(Х1,Х2 - 1))
Эта программа применима для любых двух неотрицательных целых чисел XI и Х2. Покажем с помощью структурной индукции, что А заканчивается для любой пары неотрицательных целых чисел. Отметим, что при первом рекурсивном обращении к А (А(Х1 —1,1) —во второй строчке программы) уменьшается первый аргумент, а при втором рекурсивном обращении (А(Х1 -1, А(Х1,Х2 -1))—в третьей строчке программы) уменьшается первый аргумент внешнего обращения и, кроме того, во внутреннем обращении уменьшается второй аргумент. Это наводит на мысль, что индукция должна быть обобщенной индукцией по множеству упорядо- * ченных пар <Х1,Х2> неотрицательных целых. Упорядочение задается лексикографическим порядком <. Напомним, что отношение < определяется следующим образом: <ХГ,Х2'> <<Х1,Х2>, если и только если Х1'<Х1 или при ХГ —XI Х2'<Х2 (см. пример 3 и упражнение 1 в разд. 1.4). Теперь докажем методом обобщенной индукции, что вычисление А для любых пар неотрицательных чисел XI,Х2 заканчивается. Для этого нужно:
' 1. Доказать, что А(Х1,Х2) заканчивается для наименьшего элемента в упорядоченном множестве. Но наименьший элемент есть <0,0>, и ясно, что в этом случае А(0,0) заканчивается.
Доказать для всех пар неотрицательных целых чисел <Х1,Х2> ><0,0>, что если А(ХГ,Х2') заканчивается для любых <Х1',Х2' > << Х1,Х2>, то заканчивается и А(Х1,Х2). Предположим поэтому, что мы имеем некоторую пару <Х1,Х2> и знаем, что А(Х1',Х2') заканчивается для всех <Х1',Х2'> < <Х1,Х2 > (гипотеза индукции). Если XI —О, то очевидно, что А(Х1,Х2) закончится. Если XI* О и Х2 — О, то по определению А(Х1,Х2) —А(Х1 -1,1). Однако <Х1 -1,1 > << XI,Х2 >, и по гипотезе индукции вычисление А(Х1 -1,1) закончится, а следовательно, закончится и вычисление А(Х1,Х2). Если XI*0 и Х2*0, то А(Х1,Х2) — — А(Х1 - 1,А(Х1,Х2 -1)). Так как мы следуем правилу «левое внутреннее», на следующем шаге нужно вычислять А(Х1,Х2 -1). Но < XI,Х2 -1 > << XIД2 >, и опять по гипотезе индукции вычисление А(Х1,Х2-1) должно закончиться. После этого нужно вычислять А(Х1 -1,Z), где Z—полученное значение А(Х1,Х2 -1). Каким бы ни было значение Z, имеем < XI - 1,Z> <<"Х1,Х2>; следовательно, по гипотезе закончится и вычисление А(Х1 - 1,Z). Таким образом, после вычисления А(Х1,Х2 -1) — Z и А(Х1 - 1,Z) фактически заканчивается вычисление А(Х1,Х2).
ПРИМЕР 3. Рассмотрим следующую программу, применимую к любому целому числу (положительному, отрицательному или нулю) N:
F(N)-IF N > 100 THEN N - 10
ELSE OTHERWISE F(F(N + 11))
Мы хотим доказать, что - F(N) IN-10, еслиН>100;
^ ' * 91 в остальных случаях, т. е. при N < 100.
Эту функцию привел в качестве примера Джон Маккарти, и ее обычно называют 91-й функцией Маккарти. Видно, что при рекурсивном обращении к функции F ее аргумент N +11 увеличивается. Самый простой случай соответствует любым значениям N > 100. Поэтому определим упорядочивающее отношение <' на множестве {N: N —целое и
N<100} таким образом: NK'N2, если и только если N2<N1 (где <'есть обычное отношение для целых). Теперь на'таком упорядоченном множестве с помощью обобщенной индукции докажем, что
FfNl= lN ” ^ есЛИ N>100;
^191 в остальных случаях, т.е. при N ^ 100.
Очевидно, что если N > 100, то F(N) = N - 10. Поэтому для доказательства высказанного утверждения нужно лишь показать, что F(N) = 91 при N < 100. Используя обобщенную индукцию, нужно:
Доказать^, что F(N) = 91 для наименьшего элемента нашего множества. При упорядочении, задаваемом <, наименьшим элементом будет 100. Следовательно,'нужно показать, что F(10Q) = 91. Вычислим
F(100) = F(F(100 + 11)) = F(F(111) = F(101) = 91,
т. e. утверждение F(100) = 91 справедливо.
Доказать для 100 <N (т. е. для N<100 в обычном смысле отношения < ), что еслй F(N')e91 для всех N^'N (т. е. для N<N'<100), то F(N) = 91. Предположим, что 100<N и F(N') = 91 для всех N'<'N (гипотеза индукции). Так как N<100, то мы должны иметь F(N) = F(F(N + 11)). Если N + 11 принадлежит множеству {N: N — целое и N ^ 100}, то N + 11 <'N (т. е. N <N + 11< 100). Мы можем воспользоваться индукцией и заключить, что .
F(N) = F(F(N + 11)) = F(91).
Если N+11 принадлежит множеству, то N +11^100, или N ^89, но в этом случае 91 <'N (т. е. N <91), и опять можно воспользоваться гипотезой индукции:
F(N) = F(F(N + 11)) = F(91) = 91.
Если N + 11 не принадлежит множеству, то гипотезой уже пользоваться для F(F(N + 11)) нельзя. Однако, если N + 11 не принадлежит множеству, нам известно, что N + 11 >100, и поэтому имеем -
F(N) = F(F(N + 11)) = F((N + 11) - 10) = F(N + 1).
Теперь N + 1 должно принадлежать множеству, ибо N < 100, и, следовательно, N + l<100. Кроме того, N+KN (т. е. N<N + l<100), й, применяя гипотезу индукции, получаем
F(N) = F(F(N + 11)) = F((N + 11) - 10) = F(N + 1) = 91, что и требовалось доказать, т. е. F(N) = 91 при N ^ 100.
ПРИМЕР 4. Рассмотрим следующую рекурсивную программу, которая применима к любым двум спискам L1 и L2, состоящим из целых чисел, расположенных в возрастающем порядке:
INTORD(L1^2) = IF L1 = NIL THEN NIL
ELSE IF L2 = NIL THEN NIL
ELSE IF CAR(L1)<CAR(L2) THEN
INTORD(CDR(L1),L2)
ELSE IF CAR(L2)<CAR(L1) THEN INTORD(L1,CDR(L2))
Мы хотим доказать, что для любых двух упорядоченных таким образом списков L1 и L2 INTORD (L1,L2) представляет собой упорядоченный список элементов, входящих в списки L1 и L2, т. е. INTORD(L1,L2) есть «пересечение» упорядоченных списков L1 и L2. Видно, что при рекурсивных обращениях к INTORD либо первый, либо второй, либо оба аргумента становятся проще. Поэтому естественно использовать метод обобщенной индукции по парам возможных длин <LENGTH1, LENGTH2 > для списков LI, L2, лексикографически упорядоченных с помощью отношения <. Для этого нужно:
Доказать для пары упорядоченных списков Н и L2 с наименьшими длинами, что функция работает правильно. Но наименьшие длины есть <0,0>, т. е. L1 и L2 —пустые списки. Следовательно, утверждение INTORD(NIL,NIL) = = NIL действительно верно.
Доказать (для всех пар длин < LENGTH 1, LENGTH2 > > < 0,0 >), что если для всех списков LU и L2' таких, что < LENGTH 1', LENGTH2'> < <LENGTH1, LENGTH2>, правильно работает INTORD (L1',L2'), то правильно работает и INTORD(L1,L2) (L1 и L2 имеют соответственно длины LENGTH1 и LENGTH2). Предположим, что списки L1 и L2 такие, какие требуются, и гипотеза индукции верна. Легко проверить, что если либо L1, либо L2 есть NIL, то утверждение INTORD(L1,L2) = NIL правильно. Поэтому будем предполагать, что LI NIL и L2 ^ NIL. Возможен один из трех случаев: либо a) CAR(Ll) < CAR(L2),
либо б) CAR(L2) < CAR(Ll), либо в) CAR(L1)-CAR(L2). В случае a) INTORD(L1, L2) -INTORD(CDR(L1), L2). Однако длина CDR(L1) меньше длины L1, и, следовательно, < длина CDR(L1), длина L2 > < < LENGTH1, LENGTH2 > из-за лексикографической упорядоченности. Таким образом, применима гипотеза индукции, и, следовательно INTORD(CDR (LI), L2) —действительно упорядоченный список всех чисел, входящих в качестве элементов и в CDR(L1), и в L2. Так как CAR(L1)< CAR(L2) и списки Ll, L2 упорядочены, то, следовательно, CAR(L1) не входит в список L2. Отсюда вытекает, что упорядоченный список всех чисел, которые являются элементами к^к CDR(L1), так и L2, совпадает с упорядоченным списком всех чисел, которые являются элементами как L1, так и L2. Следовательно, в этом случае утверждение INTORD(L1,'L2) = INTORD(CDR(L1),L2) справедливо. В случае 6).INTORD(L1, L2)-INTORD(L1, CDR(L2)) длина CD‘R(L2) меньше длины L2, и, следовательно, < длина L1, длина CDR(L2)>«LENGTH1, LENGTH2> из-за лексикографического порядка. Поэтому к INTORD(L1, CDR (L2)) применима гипотеза индукции, и можно, используя соображения, аналогичные для случая а), заключить, что и в этом случае INTORD(L1, L2) работает правильно. В случае в) имеем INTORD(L1, L2) = CONS(CAR(L1), INTORD(CDR(L1), CDR(L2))). Обе длины CDR(L1) и CDR(L2) меньше соответственно длин L1 и L2, и, следовательно, к INTORD(CDR(L1), CDR(L2)) применима гипотеза индукции. По этой гипотезе INTORD(CDR(L1), CDR(L2)) представляет собой упорядоченный список всех чисел, которые входят как в CDR(L1), так и в CDR(L2). Кроме того, CAR(L1) —CAR(L2), и, следовательно CAR(L1) (или CAR(L2y) должен входить в качестве элемента в результирующий список. Однако CONS(CAR(L1), INTORD(CDR(L1), CDR(L2))) состоит из INTORD(CDR(L1), CDR(L2)), содержащего в заданном порядке общие для CDR(L1) и CDR(L2) элементы, и одного дополнительного элемента CAR(L1), стоящего в начале. Это именно тот список, который требуется, следовательно, в этом случае INTORD(L1, L2) работает правильно, что и требовалось доказать.
Из приведенных выше четырех примеров видно, что фактически для доказательства правильности программ всегда нужна либо строгая версия, либо просто обобщенная
индукция. Простой индукции не достаточно ни в одном примере. Отметим также, что в случае рекурсивных программ двух или более аргументов часто оказывается полезным лексикографический порядок, если в программе есть рекурсивные обращения сначала с одним аргументом (более простым), а затем с некоторыми из других (см. примеры 2 и 4).
УПРАЖНЕНИЯ
Докажите, что для всех неотрицательных чисел XI и Х2 А(Х1, Х2)> > XI + Х2, где А(Х1, Х2) — функция Аккермана, которую мы рассматривали в примере 2.
Рассмотрите, следующую рекурсивную программу, применимую к любому целому числу N:
F(N) - IF N > 202 THEN N - 3
ELSE OTHERWISE F(F(N + 4))
Докажите, что для всех целых N
если N >2«2;
1 ' (200 в остальных случаях, т.е. если N<202.
Рассмотрите следующую рекурсивную программу, применяемую к любому неотрицательному числу X:
F(X) - IF X = 0 THEN 0
ELSE IF EVEN(X) THEN F(X/5) + 5
ELSE OTHERWISE F(3 • X + 1) - 1
Проверка EVEN(X) дает ответ TRUE, если X — четное число, и FALSE, если X — нечетное. Через X /5 обозначается частное от целого деления X на 5. Докажите, что для всех неотрицательных целых чисел X программа заканчивается.
Рассмотрите следующую нерекурсивную программу, применимую к любому целому числу N:
F(N) - IF N > 100 THEN N - 10
ELSE OTHERWISE F(F(F(N + 21)))
Докажите, что для всех целых N
f<n^4^- 10, если N>10°;
^1^1 ,в остальных случаях- т.е. если N < 100.
Приведенная ниже рекурсивная программа применима для любых двух неотрицательных целых чисел XI и Х2:
F(X1, Х2) - IF XI - 0 THEN 0
ELSE IF Х2 = 0 THEN F(X1 - 1, Х2 + 1)
ELSE OTHERWISE F(F(X1 - 1,X2), F(X1$X2- 1))
Определите, что вычисляет F(X1, X2), и докажите это.
Ниже приведена рекурсивная программа для двух целых неотрицательных аргументов XI и Х2:
F(X1, Х2) - IF XI = О THEN Х2
ELSE IF Х2—О THEN F(X1 - 1,Х2 + 1)
ELSE OTHERWISE F(X1, X2 - 1) + 1
Определите, что здесь вычисляется, и докажите это.
Приведенная ниже рекурсивная программа применима к двум неотрицательным целым числам XI, Х2;
F(X1, Х2) w IF Х2 = О THEN XI
ELSE IF XI = О THEN F(1,X2 - 1)
: ELSE OTHERWISE F(F(X1 - 1,X2),X2)
Докажите, что для дробых двух неотрицательных целых чисел XI и Х2 F(X1,X2) = 1^’ есЛИ Х2”0;Y,,A
v 7 U в остальных случаях, т.е. если Х2^0.
Рассмотрите следующую рекурсивную программу, применимую к двум любым целым числам XI, Х2, удовлетворяющим условиям 0< XI ^ Х2 иО<Х2:
F(X1, Х2) - IF XI = О THEN Х2
ELSE IF Х1<Х2- XI THEN F(X1,X2- XI)
ELSE OTHERWISE F(X2-X1,X1)
Докажите, что для любых целых XI и Х2 при условии 0<Х1<Х2 и 0<Х2 F(X1,X2)— GCD(X1,X2), где GCD — наибольший общий делитель. Цри доказательстве используйте тот факт, что для любых рассмотренных выше XI, Х2
GCD(X1,X2)-GCD(X1,X2- X1)-GCD(X2- Х1,Х1).
Приведенная ниже рекурсивная программа применима к любому списку L:.
FLATTEN(L) - IF L * NIL THEN NIL
ELSE IF ATOM(CAR(L)) THEN CONS(CAR(L), FLATTEN(CDR(L)))
ELSE OTHERWISE APPEND(FLATTEN(CAR(L)), FLATTEN(CDR(L)))
Докажите, что для любого списка L функция FLATTEN(L) представляет собой список из всех атомов, встречающихся в L. При этом все внутренние списковые структуры «разрушаются». Например, FLATTEN([[A[B]]C [[D[A В]]]])-[А В С D А В].
Составьте рекурсивную программу UNIONORD (L1,L2), аналогичную INTORD в примере 4. UNIONORD(L1, L2) должна применяться к любым двум спискам целых чисел LI, L2; списки упорядочены в возрастающем порядке. В результате должен получиться упорядоченный список всех целых чисел, которые являются элементами либо списка L1, либо списка L2, либо обоих списков. Таким образом, UNIONORD(L1, L2) дает «объединение» упорядоченных списков LI, L2. Докажите правильность вашей программы.
Рассмотрите следующую рекурсивную программу:
DELETE2(L1, L2) - IF LI = NIL THEN L2
ELSE IF L2 = NIL THEN NIL
ELSE OTHERSIWE DELETE2(CDR(L1), DELETE(CAR(L1), L2))
Программа применима к любым двум спискам L1 и L2. В ней используется функция DELETE, которую вы должны были составить (и доказать ее правильность) в качестве упражнения 66 в разд. 4.3. Докажите, что для двух списков L1 и L2 функция DELETE2(L1, L2) дает список, полученный вычеркиванием из L2 всех элементов, встречающихся (на верхнем уровне) . в списке L1.
Рассмотрите следующую рекурсивную программу, применимую к любым двум атомам XI, Х2 и любому списку L:
SUBS(X1, Х2, L) - IF L = NIL THEN NIL
ELSE IF CAR(L)—X2 THEN
CONS(X1, SUBS(X1,X2, CDR(L))) ELSE IF ATOM(CAR(L)) THEN CONS(CAR(L), SUBS(X1, X2, CDR(L))) ELSE OTHERWISE CONS(SUBS(X1,X2, CAR(L)), SUBS(X1,X2, CDR(L)))
Докажите, что в этом случае SUBS(X1,X2, L) есть список, полученный из L путем подстановки XI вместо каждого вхождения элемента Х2 в L. Например, SUBS(B, С [А В С [А [С]]]) — [А В В [А [В]]].
Выяснить, как ошибки в программе влияют на проведение доказательства правильности. Для каждой из последующих ошибочных программ попытайтесь доказать ее правильность и выявите этапы, где доказательство не проходит. Обнаружив ошибку в таком доказательстве, найдите некоторые специфические значения (значение) для данных, при которых программа будет давать неверный ответ.
Предположим, что по ошибке программа из упражнения 7 имеет вид
F(X1,X2)-IF X2=OTHENX1
ELSE IF XI = 0 .THEN F(X1 - 1,1)
ELSE OTHERWISE F(F(X1 - 1, X2), X2 - D
F(X1, X2) - IF X2 = 0 THEN 1
ELSE IF XI = 0 THEN F(X2 -1,1)
ELSE OTHERWISE F(F(X1 - 1,X2)X2- 1)
F(Xf,X2) - IF X2 = 0 THEN XI
ELSE IF XI = 0 THEN F(l, X2 - 1)
ELSE OTHERWISE F(F(X1 - 1,X2),X2)
Представим, что программа из упражнения 9 ошибочно записана в виде*
FLATTEN(L)-!f ATOM(CAR(L)) THEN CONS(CAR(L), FLATTEN(CDR(L))) ELSE OTHERWISE APPEND(FLATTEN(CAR(L)), FLATTEN(CDR(L)))
FLATTEN(L) - IF L - NIL THEN NIL ELSE IF ATOM(CAR(L) THEN
CONS(CAR(L), FLATTEN(CDR(L))) ELSE OTHERWISE CONS(FLATTEN (CAR(L)), FLATTEN(CDR(L)))
FLATTEN(L) - IF L - NIL THEN NILELSE OTHERWISE CONS(CAR(L),FLATTEN(CDR(L)))
43. СТРУКТУРНАЯ ИНДУКЦИЯДЛЯ НЕРЕКУРСИВНЫХ ПРОГРАММ
В гл. 2 для доказательства правильности нерекурсивных (итеративных) программ мы ввели метод индуктивных утверждений. .Однако если нерекурсивная программа фактически выполняет рекурсивный процесс, то при доказательстве ее правильности иногда легче использовать метод структурной индукции, а не метод индуктивных утверждений. В этом разделе мы рассмотрим несколько примеров, иллюстрирующих подобный случай.
ПРИМЕР L Мы хотим доказать, что если выполнять программу, блок-схема которой приведена на рис. 4.1, с TREE, представляющим собой указатель на корневую вершину некоторого двоичного дерева (TREE = Л, если дерево пустое), то в конце концов выполнение ее закончится, причем будут просмотрены в некотором порядке все вершины этого дерева. (Для ознакомления с используемой в блок-схеме нотацией и алгоритмами просмотра двоичных деревьев см. стр. 394—398 в книге Д Кнута «Искусство программирования для ЭВМ», том 1, изд-во «Мир», 1976.) Просмотр идет следующим образом: для каждой вершины дерева просматриваются одним и тем же способом все вершины левого поддерева, выходящего из данного, затем сама исходная вершина, а затем в том же порядке все вершины правого поддерева. Наша программа фактически реализует некоторый рекурсивный процесс, и ее можно было бы легко написать в «рекурсивной форме». В нашей же нерекурсивной версии алгоритм основан на использовании явного стека. Мы могли бы доказать, что программа правильна, и методом индуктивных утверждений (см. упражнение 1 в конце раздела), однако рекурсивность самого процесса приводит к предположению, что это доказательство удобнее провести с помощью структурной индукции.. Сначала докажем, что справедливо утверждение, связанное в блок-схеме с точкой 2. При доказательстве
STARTSTART'Установить пустой STACK TREE'Установить пустой STACK TREESTOPSTOPР ♦ STACKР ♦ STACKSTACK *Р Р * LLINK(P)STACK *Р Р * LLINK(P)STACKSTACKпуст ?пуст ?Если мы попадаем 8 эту точку с указателем Р*^» указывающим на корневую вершину некоторо- f го двоичного (под) дерева, и 1 STACK* STACK0, то в конце концов мы попадаем в этуЕсли мы попадаем 8 эту точку с указателем Р*^» указывающим на корневую вершину некоторо- f го двоичного (под) дерева, и 1 STACK* STACK0, то в конце концов мы попадаем в этуДаДаточку и тогда, когда дерево, на которое указывает Ро, будет просмотрено всоот- «та»точку и тогда, когда дерево, на которое указывает Ро, будет просмотрено всоот- «та»446 конце концов мы попадаем в эти точку, причем . дерево, заданное TREE, будет просмотрено 8 соответствующем порядке6 конце концов мы попадаем в эти точку, причем . дерево, заданное TREE, будет просмотрено 8 соответствующем порядкеПросмотрвершины РПросмотрвершины Р
START
START
'Установить пустой STACK TREE
'Установить пустой STACK TREE
STOP
STOP
Р ♦ STACK
Р ♦ STACK
STACK *Р Р * LLINK(P)
STACK *Р Р * LLINK(P)
STACK
STACK
пуст ?
пуст ?
Если мы попадаем 8 эту точку с указателем Р*^» указывающим на корневую вершину некоторо- f го двоичного (под) дерева, и 1 STACK* STACK0, то в конце концов мы попадаем в эту
Если мы попадаем 8 эту точку с указателем Р*^» указывающим на корневую вершину некоторо- f го двоичного (под) дерева, и 1 STACK* STACK0, то в конце концов мы попадаем в эту
Да
Да
точку и тогда, когда дерево, на которое указывает Ро, будет просмотрено всоот- «та»
точку и тогда, когда дерево, на которое указывает Ро, будет просмотрено всоот- «та»
4
4
6 конце концов мы попадаем в эти точку, причем . дерево, заданное TREE, будет просмотрено 8 соответствующем порядке
6 конце концов мы попадаем в эти точку, причем . дерево, заданное TREE, будет просмотрено 8 соответствующем порядке
Просмотрвершины Р
Просмотрвершины Р
Р * RLINK(P)
Рис. 4.1.
используется структурная индукция по размеру (числу вершин) дерева, на которое указывает Р. Для этого необходимо:
Доказать, что если размер дерева, на которое указывает Р, равен нулю, то утверждение справедливо. Но если размер дерева, задаваемого Р, равен нулю, то наше утверждение тривиально, ибо мы попадаем в точку 2 с пустым деревом, которое можно считать уже просмотренным в соответствующем порядке (ведь просматривать нечего), причем Р-Л, a STACK остался без изменений.
Доказать (для всех п > 0), что если наше утверждение верно для всех деревьев размером меньше п (гипотеза индукции), то оно также справедливо и для дерева размером п. Предположим, что мы находимся в точке 2 и Р — Ро^А, затем переходим из точки 2 через точки 3, 4 вновь в точку 2.
При возвращении в точку 2 В указывает на левое поддерево первоначального дерева, на которое указывало В (т. е. B = LLINK(P0)), a STACK в состоянии STACK0,P0 (т. е. значение Во уже помещено в вершину стека). Так как размер дерева, на которое теперь указывает В, меньше п, то мы знаем (по гипотезе индукции), что если мы когда- нибудь вернемся в точку 2, то это левое поддерево уже будет просмотрено в соответствующем порядке и, кроме того, В = Л, a STACK опять будет в состоянии STACK0, Во. Так как В—Л, пройдем по пути из точки 2 через точки 6, 7, 8 вновь в точку1* 2. Вернувшись в точку 2, будем иметь B = RLINK(P0), a STACK —в состоянии STACK0. Исходная вершина будет уже просмотрена. Таким образом, уже будут просмотрены в соответствующем порядке вершины левого поддерева и корень (исходная вершина) дерева. Указатель В теперь будет «смотреть» на правое поддерево; оно меньше дерева, на которое указывал Во. Опять можно использовать гипотезу индукции и сделать заключение, что, если в конце концов мы вернемся в эту точку, дерево, на которое указывает В, будет просмотрено (это правое поддерево исходного дерева), В -=Л и STACK будет в состоянии STACK0. В этот момент оказывается, что мы просмотрели левое поддерево, корневую вершину и правое поддерево. Но из этого следует, что мы в соответствующем порядке просмотрели все исходное дерево. Причем Р=Л, a STACK —в состоянии STACK0, что и требовалось доказать.
Изложенное доказательство легко приводит нас и к доказательству правильности, так как при первом попадании в точку 2 (из точки 1) В — TREE, a STACK пуст. Только что доказанное утверждение, связанное с точкой 2, гласит, что при попадании в точку 2 поддерево, на которое указывал В, будет уже надлежащим образом просмотрено и STACK будет в исходном состоянии (пуст), а В— Л. В этот момент, так как В—Л, мы попадем в точку 5, так как STACK пуст, далее — в точку 10; это и показывает справедливость утверждения о правильности, связанного с точкой 10.
ПРИМЕВ 2. Мы хотим доказать, что приведенная на рис. 4.2 программа правильна. Если считанное и присвоенное N значение No больше 100, то очевидно, что мы идем по пути из точки 1 через точки, 2, 3, 8, 9, и когда наконец попадем в
Рис. 42.
точку 10, то напечатанное значение N будет No —10. Таким образом, нужно только доказать, что если исходное считываемое, значение No <100, то мы в конце концов достигаем точки 10, и напечатанное значение будет равно 91. Для доказательства этого утверждения вначале докажем методом
структурной индукции, что утверждение, связанное с точкой 2, справедливо. Доказательство проводится с помощью обобщенной индукции в множестве X— {<n, т>: п, т —целые, п < 100 и т > 1}. Множество упорядочено на основе отношения: < nl, ml > < < п2, m2 >, если и только если nl > п2 или при nl = n2, ml < m2. Так, множество упорядочено следующим образом:
< 100,1 ><< 100,2 > < < 100,3 X... < < 100,п > <...
<<99,1><< 99,2><.„
<< 98,1 >,<< 98,2 X...
Читатель может проверить, что это действительно упорядоченное множество. Для доказательства с помощью обобщенной индукции необходимо:
Доказать справедливость нашего утверждения для N = no и Mem0, где < По, ш0 > —наименьший элемент в указанном множестве. Но наименьший элемент в множестве есть < 100,1 >, и, следовательно, нужно доказать, что если мы попали в точку 2 с N —100 и М — 1, то в конце концов мы достигнем точки 2 с N = 101 и М — 1. Предположим, что мы находимся в точке 2 с N = 101 и М — 1. Проверка N > 100 дает отрицательный ответ, и мы проходим из точки 2 через точки 6, 7 вновь в точку 2. При возвращении в точку 2 мы должны иметь N —100 4-11 —111 и М = 1 + 1 = 2 Теперь проверка N > 100 дает ответ TRUE (истина), и поэтому мы перейдем из точки 2 в точку 3. Так как проверка М = 1 дает отрицательный ответ, то мы пройдем через точки 3, 4, 5 вновь в точку 2. В этот момент в точке 2 мы будем иметь N —111 —10 —101 и М -2 — 1 — 1. Но это как раз то, что нужно было доказать, т. е. в конце концов возвращаемся в точку 2 cN = 101 иМ-1.
Доказать для всех <n, ш>> < 100,1 >, что если утверждение справедливо для N = n' и Msm', удовлетворяющих условию <n',m'> < < n,m > (гипотеза индукции), то утверждение будет справедливо для N = n и М=т. Таким образом, предположим, что мы находимся в точке 2, N = n, M—m и гипотеза верна. Проверка N>100 должна дать отрицательный результат, и, следовательно, мы проходим из точки 2 через точки 6, 7 вновь к точке 2. При попадании в точку 2 имеем N = п+11, а М —m +1. Если п + 11 С100, то < п+ 11, m4-1 > принадлежит множеству X. В этом случае
< п+11, т+1 ><<п,т>, и можно, используя гипотезу индукции, сделать вывод, что когда-нибудь мы попадем в точку 2cN — 101иМ = 1. Итак, нам следует обратить внимание на случай, когда п + И > 100, или, другими словами, п> 89. При этом в точке 2 мы имеем N = n+ll>100, M = m+1. Проверка п> 100 дает положительный результат, а проверка М — 1 — отрицательный, и мы проходим из точки 2 через точки 3, 4, 5 вновь к точке 2. Вернувшись в точку 2, мы имеем N —(п + 11) — 10 —n + 1 иМ = (ш+ 1)-1=-т. Еслип+1< 100, то < п 4-1, m > принадлежит множеству X. В этом случае < n + 1, ш> < < п,ш>, и мы снова можем применить гипотезу индукции и сделать необходимое заключение. Теперь осталось исследовать случай, когда п+1 >100, или п>99. Известно, что. п< 100; следовательно, в этом случае п —100. Так как п—100 и < 100,1 >< <~ n,m^, то отсюда ш>1. Что же происходит, если мы находимся в точке 2 с N — n + 1 = 101 и М — m > 1? Проверка N > 100 дает положительный результат, а М — 1 — отрицательный; следовательно, мы будем двигаться от точки 2 через точки 3, 4, 5 вновь к точке 2. При возвращении в точку 2 N = 101 — 10 = 91 и М — m — 1. В этот момент проверка N > 100 дает отрицательный ответ, и мы будем идти из точки 2 через точки 6 и 7 в точку 2. Вернувшись в точку 2, мы получаем N—91 + 11 —102 и М — (ш — 1) +1 — ш. Можно убедиться, что этот процесс так и будет продолжаться: сначала движение по «левому» пути (петле), затем по «правому» пути до тех пор, пока в конце концов не попадем в точку 2 с N —99 + 11 —ПО и М —ш. В этот момент мы снова пройдем по «левому» пути и попадем в точку 2 с N —110 —10 —100 и M-m — 1. Но <100,т —1> принадлежит множеству X и < 100, m — 1 > < < 100,m >. Следовательно, опять можно использовать гипотезу индукции и заключить, что когда-нибудь мы попадем в точку 2 с N —101 и М —1, что и требовалось доказать, т. е. утверждение, связанное с точкой 2, справедливо. Используем этот факт и закончим полностью доказательство правильности. Нам нужно просмотреть лишь случай, когда считываемое и присваиваемое переменной N значение No < 100. При этом мы первый раз попадаем в точку 2 с N—No< 100 и М —1. Связанное с точкой 2 утверждение гласит, что в конце концов мы попадем в точку 2 с N —101 и М —1. В этот момент проверки N > 100 и М — 1 — положительные результаты, и мы
будем двигаться от точки 2 через точки 3, 8, 9 к точке 10. Таким образом, мы действительно попадем в точку 20, и в этот момент напечатанное значение будет 101 — 10 = 91, что и требовалось доказать.
Интересно сравнить для нерекурсивных (итеративных) программ доказательство методом индуктивных утверждений с доказательством методом структурной индукции. Доказательства методом индуктивных утверждений фактически являются доказательством с помощью индукции по числу попаданий (в Цимеле) в некоторую точку программы. Доказательства с помощью структурной индукции также являются доказательствами по индукции, но индукция проводится* по структуре данных, с которыми работает -программа. Безусловно, именно структура данных определяет, сколько раз мы попадем в различные точки программы, и поэтому оба метода имеют очень много общего. Причем оказывается, что если программа по природе рекурсивна, то структуру данных очень просто использовать для выяснения процесса выполнения программы, и поэтому доказательство правильности обычно легче проводить с помощью структурной индукции, а не индуктивных утверждений. Основная трудность в любом из методов — выбор правильных утверждений, которые связываются с точками в циклах программы, причем для разных методов эти утверждения различаются лишь в одном: индуктивные утверждения не должны содержать какой-либо информации об окончании работы, а утверждения для структурной индукции должны содержать информацию как о частичной правильности, так и об окончании.
УПРАЖНЕНИЯ
1а. Докажите правильность программы, блок-схема которой приведена на рис. 4.1, методом индуктивных утверждений.
Докажите правильность программы, блок-схема которой приведена на рис. 4.2, методом индуктивных утверждений. Доказательство правильности данной программы этим методом представляет некоторые трудности, поэтому читатель не должен удивляться, если ему это не удастся сделать.
Докажите правильность приведенной на рис. 4.3 блок-схемы методом структурной индукции.
За. Составьте блок-схему для прямого просмотра (прохождения) двоичного дерева и докажите методом структурной индукций ее правильность.
Рис. 4.3.
(Для определения прямого и обратного прохождения по двоичному дереву см. стр. 394—395 в книге Д. Кнута «Искусство программирования для ЭВМ», т. 1, изд-во «Мир», 1976.)
Составьте блок-схему для обратного просмотра двоичного дерева и докажите её правильность методом структурной индукции.
Рис. 4.4.
Докажите правильность приведенной на рис. 4.4 блок-схемы методом структурной индукции и с помощью индуктивных утверждений.
Докажите методом структурной индукции, что приведенная на рис. 4.5 блок-схема правильна.
Используя структурную индукцию для доказательства утверждения, связанного с точкой 2, докажите правильность приведенной на рис. 4.6 блок-схемы. Используемая здесь функция A(M,N) — функция Аккермана (см. пример 2 в разд. 4.4) — определяется следующим образом:
А(Х1, Х2) - IF XI = О THEN Х2 + 1
ELSE IF Х2 = О THEN А(Х1 - 1,1)
ELSE OTHERWISE А(Х1 - 1,А(Х1,Х2- 1))
Рис. 4.5.
STARTSTARTХ1,Х2Х1,Х2Установить пустой STACKУстановить пустой STACK-J-JX1-0X1-0Acc*X2+1Acc*X2+1X2-0 ,X2-0 ,STACK-*X1-1 X2* X2-1STACK-*X1-1 X2* X2-1©©X1 ♦ STACK X2*AccX1 ♦ STACK X2*AccSTACKSTACKНетНет©©пуст?пуст?L-£ конце концов мы попадаем, в эту точку прикь^к^П^L-£ конце концов мы попадаем, в эту точку прикь^к^П^
START
START
Х1,Х2
Х1,Х2
Установить пустой STACK
Установить пустой STACK
-J
-J
X1-0
X1-0
Acc*X2+1
Acc*X2+1
X2-0 ,
X2-0 ,
STACK-*X1-1 X2* X2-1
STACK-*X1-1 X2* X2-1
©
©
X1 ♦ STACK X2*Acc
X1 ♦ STACK X2*Acc
STACK
STACK
Нет
Нет
©
©
пуст?
пуст?
L-£ конце концов мы попадаем, в эту точку прикь^к^П^
L-£ конце концов мы попадаем, в эту точку прикь^к^П^
Считанные значения xi, XI-неотрицательные целые числа XI , Х20
।—Если мы попадаем 6 эту точку
I пои Х1-М^0 и Х2=М>0, а
I STACK будет в состоянии STACK., | то когда-нибудь попадаем и в, I точку 3 с Асс-А(М, N), a STACK
I вновь будет в состоянии STACK0
Рис. 4.6.
Глава 5
СОВРЕМЕННЫЕ ИССЛЕДОВАНИЯ, СВЯЗАННЫЕ С ДОКАЗАТЕЛЬСТВОМ ПРАВИЛЬНОСТИ ПРОГРАММ
ВВЕДЕНИЕ
В настоящее время в области доказательства правильное™ программ проводятся интенсивные исследования. Для простоты обсуждения выделим среди этих исследований три основных направления:
Методы доказательства (частичной) правильности или конечности.
Проблемы разработок программ и создания языков программирования.
Механизация процесса доказательства правильности.
Эти направления перекрываются, и мы их рассматриваем не для того, чтобы уточнить области исследований, а просто как некоторое средство организации обсуждения. Само обсуждение будет очень кратким: мы попытаемся лишь выявить характер исследований в данном направлении и отметить те из работ, которые относятся к этому направлению. В конце книги приведена достаточно обширная библиография. Читатель може воспользоваться ею для более подробного ознакомления с тем или иным направлением исследований.
МЕТОДЫ ДОКАЗАТЕЛЬСТВА
В книге мы сконцентрировали внимание на двух основных методах: методе индуктивных утверждений и структурной индукции. Метод индуктивных утверждений впервые был предложен Флойдом [28] и Науром [81]. Барстелл в 1969 г. предложил метод структурной индукции [6]. В разд. 3.4 (гл. 3) очень кратко рассмотрены понятия, относящиеся к правилам верификации и аксиомам для доказательства частичной правильности. Этот метод, впервые предложил Хоар [38].
Существуют и другие методы доказательства правильности программ. Различные методы доказательства полной
правильности итеративных программ приводятся в работах [4, 10, 24, 50, 68, 71]. В работе [7] рассмотрен алгебраический подход к доказательству правильности. В работах [9, 91, 92] обсуждаются методы доказательства правильности программ, использующих сложные структуры или модифицирующие структуру данных. В работе [68] показано, как можно использовать метод индуктивных утверждений для доказательства правильности недетерминированных программ. В работах [1, 56, 58] рассматриваются методы доказательства правильности параллельных программ.
Такой метод доказательства, как метод индуктивных утверждений, где^ечь идет лишь о доказательстве частичной правильности, требует отдельного доказательства конечности итеративных программ. Флойд [28] и Манна [66] использовали вполне упорядоченные (или частично упорядоченные) множества для доказательства конечности. В работе [71] проведено сравнение различных методов доказательства конечности. В работе [86] описаны методы доказательства «чистого» окончания программ.
Для доказательства правильности рекурсивных программ было предложено много индуктивных методов. В работах [72, 79, 84] предлагаются индуктивные методы, в которых индукция ведется по уровню рекурсии. Эти методы отличаются от доказательства методом структурной индукции, описанного в нашей книге, где индукция ведется по структуре данных, с которыми, работает программа. В работе [70] проведено сравнение многих из предложенных индуктивных методов доказательства правильности рекурсивных программ.
В работе [71] дается некоторый формальный подход ко многим известным методам доказательства правильности программ. Рассмотрены методы доказательства правильности и конечности для итеративных и рекурсивных программ.
КОНСТРУИРОВАНИЕ ПРОГРАММИ ЯЗЫКОВ
Желание создавать программы, для которых было бы легко удостовериться в их правильности, привлекло внимание к проблеме конструирования программ и разработки языков
программирования. Работа Дейкстры [20] была одной из первых работ, относящихся к проблемам конструирования программ и доказательству их правильности. Другая его работа [21] также была одной из первых, где вопросы разработки языка связывались с проблемами правильности (речь шла об исключении операторов перехода). Эти же проблемы обсуждались и в работе Наура [82].
Большинство последних работ, где проблемы построения программ связываются с проблемами их правильности, посвящены структурному программированию. Основное внимание в них уделяется таким методам создания программ, которые позволяли бы программисту следить (неформально убеждаться) за правильностью программы на всех этапах процесса программирования. Этой теме посвящены две работы [17, 74]. Вирт [93] написал введение в программирование, где он так же рассматривает структурное программирование и проблемы доказательства правильности. В работе [42] приводится детальное доказательство правильности для одной структурированной программы. Миллс [77, 78] и Бакер [2] рекомендуют использовать структурное программирование при создании программного обеспечения. Проблемы структурного программирования и использования оператора перехода рассматриваются и в работе [57].
После знаменитого письма Дейкстра [21] об операторе перехода (GO ТО) появились работы, где прослеживается влияние проблемы доказательства правильности на разработку языка программирования. Среди языков, в которых проявилось влияние вопросов доказательства правильности, следует отметить такие языки, как Паскаль [93], Alphard [95], Clu [59], Nucleus [32]. В работе [39] обсуждаются возможные ограничения на процедуры и параметры, которые могли бы облегчить доказательство правильности. Морисе [80] рассматривает язык, ориентированный на верификацию. Кларк [13] обсуждает конструкции языка программирования, для которых невозможно получить хорошую систему аксиом, подобную введенной Хоаром. Он показывает, что в языке могут присутствовать некоторые такие общие свойства, при которых будет невозможно построить логичную и полную систему аксиом, как это было сделано Хоаром. Кларк предлагает такие свойства либо как-то видоизменить, либо вообще исключить из языка для облегчения доказательства правильности.
Недавно вышедшая книга Дейкстры [24] посвящена методам доказательства правильности и конструированию программ и языков. В ней вводится новый формализм для проведения доказательства правильности и новый минимальный язык, на котором написаны все примеры программ в книге. Этот язык специально создавался для того, чтобы ограничить программирование очень простыми, но «мощными» конструкциями, приспособленными в то же время и для методов верификации, использованных в работе. Основное содержание книги — программы, для которых автор очень подробно разбирает вопросы и$ создания и доказательства правильности.
5Д. МЕХАНИЗАЦИЯ ПРОЦЕССАДОКАЗАТЕЛЬСТВА ПРАВИЛЬНОСТИ
Целью многих исследований в области доказательства правильности программ является формализация и в конце концов механизация таких доказательств. Если можно будет создать работающую систему механической верификации, то это сыграет важную роль в вычислительной технике. Хотя неформальное доказательство правильности, подобное тому, о котором шла речь в книге, и является полезным, но ведь здесь возможны и ошибки. Хорошо было бы иметь некоторую механическую систему верификации, которая либо помогала проводить доказательство правильности в интерактивном режиме, либо работала как непогрешимый контролер для доказательств. На каком из этих направлений будет достигнут успех —пока неясно. Однако сейчас уже предпринимаются первые попытки построить такие системы.
Если мы хотим использовать механическую систему верификации при проведении дрказательства правильности, то нуйно каким-то образом формализовать и индуктивные утверждения, и сами доказательства. Чаще всего для формализации используется исчисление предикатов первого порядка, хотя такой способ не всегда пригоден для выражения некоторых утверждений и для доказательств. В работах [8, 71] есть примеры формализованных доказательств правильности на основе нотации исчисления предикатов первого порядка. В работе [59] исследуются различные методы спецификации, которые" можно было бы использовать для формальной записи индуктивных утверждений.
153
Большинство уже построенных систем механической верификации основывается на методе индуктивных утверждений. Такие системы в качестве входных данных рассматривают исходную программу, которую нужно верифицировать, и вместе с ней сформулированные программистом индуктивные утверждения. Основываясь на «заложенных в ней знаниях» синтаксиса и семантики языка программирования, система прослеживает все пути, по которым может идти выполнение программы, и формирует множество условий верификации, которые должны обеспечить верификацию всей программы. В разд. 2.6 мы уже показали, как можно формировать такие формализованные условия верификации: для этого используются программы алгебраических и логических упрощений и «доказыватель» (программа) теорем исчисления предикатов первого порядка. Из-за того что программа, доказывающая механически теоремы* скорее всего будет при некоторых доказательствах обнаруживать ошибки, системы обычно делаются интерактивными и позволяют пользователю вмешиваться в те части доказательств, где обнаруживаются неточности. Системы механической верификации такого типа описываются в работах [19, 33, 51, 53, 87]. Системы механической верификации обсуждаются также в работах [16, 26, 47, 88].
Боуер и Мур [5] описывают систему механической верификации для доказательства теорем о программах на Лиспе. Эта система существенно отличается от названных выше систем. Так как она предназначена для доказательства теорем о рекурсивных по определению программах, то в качестве основного метода в ней используется не метод индуктивных утверждений, а метод структурной индукции.
Во всех системах механической верификации, использующих метод индуктивных утверждений, требуется, чтобы индуктивные утверждения задавал пользователь. Было бы очень удобно, если бы система могла сама «задавать» (создавать) некоторые или даже все индуктивные утверждения. В работах [11, 35, 89, 29, 50] описываются некоторые эвристические методы, цель которых — механическое формирование индуктивных утверждений. Хотя ни один их этих методов не пригоден для любых программ, но некоторые комбинации таких методов могли бы быть полезными при формировании многих из индуктивных утверждений.
Некоторые идеи и методы систем механической верификации используются в более ограниченных системах, которые создаются для того, чтобы помочь программисту систематически тестировать и отлаживать свою программу. Реализации таких систем описываются в работах [54, 55, 26].
БИБЛИОГРАФИЯ
Составляя список литературы, мы попытались классифицировать ссылки в соответствии с обсуждаемой в работе темой. Для классификации мы используем следующие обозначения:
ААвтоматические методы доказательства теорем,
используемые в автоматических и полуавтоматических системах верификации
ДКМетоды доказательства конечности
ДПМетоды и примеры доказательства правильности
ДПРМетоды и примеры доказательства правильности
рекурсивных программ
КМетоды конструирования программ, ориентиро
ванные на построение верифицируемых программ
ММеханизация доказательства правильности.
Автоматические и полуавтоматические системы верификации
О Работы общего характера, в которых затронуты многие из перечисленных вопросов
ТМетоды тестирования программ, взаимосвязанные
с доказательством правильности
ФФормализация доказательства правильности
ЭЭвристические методы формирования индуктив
ных утверждений
ЯЯзыки программирования и их связь с доказатель
ством правильности программ
Такая классификация аналогична во многом направлениям, которые мы обсуждали в гл. 5. Она довольно произвольна, и одна тема часто перекрывает другую, но мы надеемся, что для заинтересованного читателя она будет
все-таки некоторым руководством. Мы старались привести наиболее существенные и представительные работы, относящиеся к доказательству правильности программ. Однако мы даже и не пытались составить полную библиографию всех работ, связанных с этой проблемой. Мы лишь перечисляем некоторые важные работы, опубликованные до 1970 г. Довольно полную библиографию работ, вышедших до 1970 г., можно найти в работе [62].
Ashcroft Е., Manna Z. The Translation of ‘GO TO’ Programs to ‘WHILE* Programs,, Proc. IFIP Cong., North-Holland Publ. Co., Amsterdam, pp. 250- 255,1971. К, Я
Baker F. T. Structured Programming in a Production Programming Envioron- ment, Proc. Int. Con. on Reliable Software, Los Angeles, April 21-23, pp. 172-185,1975. К
Basu S. K., Misra J. Proving Loop Programs, IEEE Trans, on Software Engineering, vol. Se-1, No. 1, pp. 76- 86, March 1975. ДП, Ф
Basu S. K., Yeh R. T. Strong Verification of Programs, IEEE Trans, on Software Engineering, Vol. Se-1, No. 3, pp. 339-345, September 1975. ДП, Ф
Boyer R. S., More J. S. Proving Theorems about Lisp Functions, J. ACM, 22 (1), pp. 129-144, January 1975. M
Birstall R. M. Proving Properties of Programs by Structural Induction, Comput. J., 12, pp. 41-48,1969. ДПР, ДК
Burstall R. M., Landin P. J. Programs and their Proofs: An Algebraic Approach, Machine Intelligence, 4, Meltzer B., Michie D. (Eds.), Edinburgh University Press, pp. 17-43,1969. ДП, ДК
Burstall R. M. Formal Description of Program Structure and Semantics in First-order Logic, Machine Intelligence, 5, Meltzer B., Michie D. (Eds.), Edinburgh University Press, pp. 79-98,1970. ДП, Ф
Burstall R. M. Some Techniques for Proving Correctness of Programs Which alter Data Structures, Machine Intelligence, 7, Michie D. (Ed.), American Elsevier, New Y ork, 1972. ДП
Burstall R. M. Program Proving as Hand Simulation with a Little Induction, Information Processing, 74, Proceeding of IFIP Congress 74, Rosenfeld J. L. (Ed.), North-Holland, pp. 308-312, 1974. ДП, ДК
Caplain M. Finding Invariant Assertion For Proving Programs, Proc. Int. Conf, on Reliable Software, Los Angeles, pp. 165-171, April 1975. Э
Chang C-L., Lee R. C-T. Symbolic Logic and Mechanical Theorem Proving, Academic Press, 1973. A
Clark E. M. Programming Language Constructs for Which it is Impossible to Obtain Good Hoare-Like Axiom Systems, Conference Record of the Fourth ACM Symposium on Principles of Programming Languages, Los Angeles, pp. 10-17, January 17-19,1977. Я
Clint M. Program Proving: Coroutines, Acta Informatica, pp. 50-63, 1973. ДП
Clint M., Hoare C. A. Program Proving: Jumps and Functions, Acta Informatica, 1(3), pp. 214-224,1972. ДП, Ф
Copper D. C. Programs for Mechanical Program Verification, Machine
Intelligence, 6. Meltzer В., Michie D. (Eds.), Edinburgh University Press, pp. 43-59, 1972. M.'
Dahl O. J., Dijkstra E. W., Hoare C. A. R. Structured Programming, Academic Press, London, 1972. К .
DeBakker J. W. Recursive Procedures, Mathematical Center, Amsterdam, Holland, 1971. ДПР
Deutsch L. P. An Interactive Program Verifier, Ph. D.' thesis, University . of California, Berkeley, 1973, Also Xerox Pelo Alto Research Center Report CSL-73-l,May 1973. M
Dijkstra E. W. A Constructiva Approach to the Problem of Program Correctness, B.I.T., 8, pp. 174-186,1968. К
Dijkstra E. W. Goto Statement Considered Harmful. Letter to Editor, C. ACM, 11, pp, 147-148, 1968. Reply, Comm. ACM, 11, pp. 538-541, 1968.К,Я.-
Dijkstra E. W. Tne Structure of the ‘THE’ Multiprogramming System, Comm. ACM, 11, pp. 341-346,1968. К
Dijkstra E. W. Guarded Commands, Nondeterminancy and Formal Derivation of Programs,tComm. ACM, 18, pp. 453- 457, August 1975. К, Ф•
Dijkstra E. W. Discipline of Programming, Prentice-Hall, Englewood Cliffs, NJ., 1976. ДП, ДК,К,Я, Ф *
Elspas B. The Semiautomatic Generation of Inductive Assertions for Proving Program Correctness, Stanford Research Institute Project 2686 Interim Report, July 1974. Э..
n 26. Elspas B., Levitt K. N., Waldinger R. J. An Interactive System for the Verification of Computer Programs, Stanford Research Institute Project 1891 Final Report, September 1973. M
E-lspas B., Levitt K. N., Waldinger R. J., Waksman A. An Assessment of Techniques for Proving Program Correctness, ACM Computing Surveys, . 4(2), pp. 97-147, June 1972. ДП,ДК,М
Floyd R. W. Assigning Meanings to Programs, Proc. Symp. Appl. Math., Vol. 19: Mathematical Aspects of Computer Science, Schwartz J. T. (Ed.), American Mathematical Society, Providence, R.I.,. pp. 19-32, 1967. ДП, ДК
German S. M., Wegbreit B. A synthesizer of Inductive Assertions, AFIPS Conference Proceedings, Vol. 44, 1975; National Computer Conference, . AFIPS Press, 1975, Э
Goldstine H. H., von Neuman J. Planning and Coding Problems for an Electronic Computer Instrument. John von Neumann, Collected Works, 5, Taub A. H. (Ed.), Pergamon Press, New York, pp. 80- 235 (see especially p. 92 for the use of an assertion box), 1963. ДП.
Good D. I. Toward a Man-Machine System for Proving Program Correctness, PhD. thesis, University of Wisconsin, June 1970. M
Good D. I., Ragland L. C. Nucleus-A language of Provable Programs, Program Test Methods, Hetzel W. C. (Ed.), Prentice-Hall, pp. 93-117, 1973. Я
Good D. I., London R. L., Bledsoe W. W. An Interactive Program Verification System, IEEE Transactions on Software Engineering,! (1), pp. 59-67, April 1975. M
Creen C. The Application of Theorem Proving to Question-Answer Systems, PhD. thesis, Computer Science Department, Stanford University, 1969. A
Greif I., Waldinger R. A. More Mechanical Heuristic Approach to Program Verification, Proc. Int. Symp. on Programming, Paris, pp. 83-90, April 1974. Э •
HantlerS. С., King J. С. An Introduction to Proving the Correctness of Programs, ACM Computing Surveys, pp. 331-353, September 1976. ДП
Von Henke F. W., Luckham D. C. A Methodology for Verifying Programs, Proc. Int. Conf, on Reliable Software, Los Angeles# April 21-23, 1975. M
Hoare C. A. R. An Axiomatic Basis of Computer Programming, Comm. ACM, 12 (10), pp. 576-583, October 1969. ДП, Ф
Hoare C. A. R. Procedures and Parameters: An Axiomatic Approach, Symposium on Semantics of Algorithmic Languages, Engler E. (Ed.), Springer* Verlag, pp. 102-116,1971. ДП, Я
Hoare C. A. R. Proof of a Program: FIND, Comm. ACM, 14 (1), pp.39-45, January 1971. ДП
Hoare C. A. R. Proof of Correctness of Data Representations, Acta Informa* tica, 1 (4), pp. 271-281, 1972. ДП
Hoare C. A. R. Proof of a Structured Program. The Sieve of Eratosthenes, Computer J., 15 (4), pp. 321-325, 1972. ДП, К
Hoare C. A. R. A Structured Paging System, Computer J., 16 (3), pp. 209- 215, August 1973. К
Howden W. E. Symbolic Testing and the DISSECT Symbolic Evaluation System, Tech. Report No. 11, Department of Applied Physics and Information Science, University of California, San Diego, May 1976. T
Huang J. C. A Method for Program Analysis and its Applications to Program-Correctness Problems, Int. J. of Computer Mathematics, 5 (4), pp. 203-227,1976. ДП, Ф
Hull T. E., Enright W. H., Sedgwick A. E. The Correctness of Numerical Algorithms, Proc. ACM Conference on Proving Assertions About Programs, SIGPLAN Notices, 7 (1), pp. 66-73,1972. ДП
Igarashi S., London R. L., LuckhamD. C. Automatic Program Verification I: A Logical Basis and Its Implementation, ACTA Infofmatica, 4, pp. 145— 182, 1973.M
Kaplan D. M. Recursion Induction Applied to Generalized Flowcharts, Proc. 24th Nat. Ass; Comput. Mach. Conf., pp. 491-504, ACM, New York, 1969. ДПР
Katz S., Manna Z. A Closer Look at Termination, Acta Informatica, pp. 333-352, 1975. ДП
Katz S., Manna Z. Logical Analysis of Programs, Comm. ACM, 19 (4), pp. 188-206, April 1976. ДП, ДК, Э
King J. C. A Program Verifier, Ph.D. thesis, Carnegie*Mellon University, 1969. M
King J. C., Floyd R. W. An Interpretation Oriented Theorem Prover Over Integers, Proc. 2nd Annual ACM Symposium on Theory of Computing, ACM, New York, pp. 169-179,1970. A
King J. C. Proving Programs to be Correct, IEEE Transactions on Computers, 11, pp. 1331-1336, November 1971. M
King J. C. A New Approach to Program Testing, Proc. Int. Conf, on Reliable Software, Los Angeles, pp. 228-233, April 21-23, 1975. Also appears in Programming Methodology, Lecture Notes in Computer Science, 23, Springer-Verlag, New Y ork„pp. 278-290, 1971. T
King J. C. Symbolic Execution and Program Testing, Comm. ACM, 19 (7), pp. 385-394, July 1976. T
Keller R. M. Formal Verification of Parallel Programs, Comm. ACM, - 19(7),pp. 371-384, July 1976. ДП
Knuth D. E. Structured Programming with go to Statements, ACM Computing Surveys, 6 (4), pp. 261-302, December 1974. К, Я
Lipton R. J. Reduction: A Method of Proving Properties of Parallel Programs, Comm. ACM, 18 (12), pp. 717-721, December 1975. ДП
Liskov В. H., Zilles S. Programming with Abstract Data Types, Proceedings of ACM SIGPLAN Conference on Very High Level Languages, SIGPLAN Notices, 9 (3), pp. 50-59, April 1974. Я
London R. L. Proving Programs Correct: Some Techniques and Examples, BIT, 10, PP. 168-182, 1970. ДП, ДК
London R. L. Proof of Algorithms: A New Kind of Certification, Comm. ACM, 13 (6), pp. 371-373, 1970.
London R. L. Bibliography on Proving the Correctness of Computer Programs, Machine Intelligence, 5, Meltzer B., Michie D. (Eds.), American Elsevier Publ. Co., New Yprk, pp. 569-580,1970.0
London R. L. Correctness of a Compiler for a LISP Subset, Proc. ACM Conference on Proving Assertions about Programs, SIGPLAN Notices, 7 (1), pp. 121-127, 1?72. Full proofs in correctness of two compilers for a Lisp subset,. Stanford University Artificial Intelligence Laboratory Memo 151, October 1971. ДП '
London R. L: A View of Program Verification. Proc. Int. Con. on Reliable Software, Los Angeles, pp. 534-545, April 21-23,1975.0
Manna Z. Properties of Programs and First-Order Predicate Calculus, J. ACM, 16 (2), pp. 244-255, April 1969, ДП, ДК, Ф
Manna Z. The Correctness of Non-Deterministic Programs, Artificial Intelligence, An International Journal, 1 (1), pp. 1-26,1970. ДП, Ф
Manna Z., McCarthy J. Properties of Programs and Partial Function Logic, Machine Intelligence, 5, Meltzer B., Michie D. (Eds.), American Elsevier Publ. Co., New York, pp. 27-37, 1970. ДПР, ДК
Manna Z., Pnueli A. Formalization of Properties of Functional Programs, J. ACM, 17 (3), pp. 555-569, 1970. ДП
Manna Z., Vuillemin J. Fixpoint Approach to the Theory of Computatin, Comm. ACM, 15 (7),pp. 528-536, 1972. ДПР
Manna Z., Ness S. Vuillemin J. Inductive Methods for Proving Properties of Programs, Comm. ACM, 16 (8), pp. 491-502, August 1973. ДПР
Manna Z. Mathematical Theory of Computation, McGraw-Hill, New York, 1974. ДП,ДК,Ф
McCarthy J. A Basis for a Mathematical Theory of Computation, Computer . Programming and Formal Systems, Braffort P., Hirschberg D. (Eds.), North Holland, Amsterdam, 1963, pp. 33-70. Also Proceedings of the Western Joint Computer Conference, Spartan Books, New York, pp. 225-238, 1961. дп,дк
McCarthy J., Painter J. A. Correctness of a Compiler for Arithmetic Expressions, Proceedings of a Symposium in Applied Mathematic, Vol. 19, Mathematical Aspects of Computer Science, Schwartz J. T. (Ed.), American Mathematical Society, Providence, Rhode Island, pp. 33-41, 1967. ДП
McHowan C. L., Kelly J. R. Top Down Structured Programming Techniques, Petrocelli / Charter, New Y ork, 1975. К
Marmier E. A. Program Verifier for Pescal, Information Processing 74, Proceedings of IFIP Congress 74, Rosefeld J. L. (Ed.), North-Holland, pp. 177-181,1974. M.
Mills H. D. Top-down Programming in Large Systems, in Debugging Techni-
ques in Large Systems, Randal Rustin (Ed.), Prentice-Hall, Englewood Cliffs,NJ., pp. 41-55,1971. К
Mills H. D. The New Math of Computer Programming, Comm. ACM, 18 (1), pp. 43-48, January 1975. К
Mills H. D. How to Write Correct Programs and Know It, Proceedings of the International Conference on Reliable Software, Los Angeles, April 21-23, pp. 363-370, 1975. К
Morris J. H. Anoter Recursion Induction Principle, Comm. ACM, 14 (5), pp. 351-354. ДПР
Morris J. H., Jr. Verification-Oriented Language Design, University of California, Berkeley, Computer Science Technical Report 7, 1972. Я
Naur P. Pfoor of Algorithms by General Snapshots, B.I.T., 6, pp. 310-316, 1966. ДП
Naur P. Programming by Action Clusters, B.I.T., 9 (3), pp. 250-258, 1969.К,
Oppen D. C., Cook S. A. Proving Assertions About Programs that Manipulate Data Structures, Seventh Annual ACM Symposium on’Theory of Computation, ACM, New York, pp.107-116,1975. ДП
Park D. Fixpoint Induction and Proofs of Program Properties, Machine Intelligence, 5, Meltzer B., Michie D. (Eds.), American Elsevier Publ, Co., New York, pp. 59- 78,1969. ДПР
Scott D. Outline of a Mathematical Theory of Computation, 4th Annu. Princeton Conf. Inform. Sciences and Syst., pp. 169-176,1970. ДПР
Sites R. L. Proving that Computer Programs Terminate Cleanly, Ph.D. thesis, Stanford University, 1974. Also Computer Science Report STAN-CS-74-418, May 1974. ДП
Suzuki N. Verifying Programs by Algebraic and Logical Reduction, Proc. Int. Conf, on Reliable Software, Los Angeles, pp. 478-481, April 21-23, 1975. M
Waldinger R., Levitt K. N. Reasoning about Programs, Artificial Intelligence, 5, Meltzer B., Michie D. (Eds.), pp. 235-316, 1974. M
Wegbreit B. The Synthesis of Loop Predicates, Comm. ACM, 17 (2), pp. 102-112, February 1974. Э
Wegbreit B. Property Extraction in Well-Founded Property Sets, IEEE Trans, on Software Engineering, Vol. SE-1, No. 3, pp. 270-285, September 1975. Э
Wegbreit B. Mechanical Program Analysis, Comm. ACM, 18 (9), pp. 528- 539, September 1975. ДП
Wegbreit B., Spitzen J. M. Proving Properties of Complex Data Structures, J. ACM, 23 (2), pp. 389-396, April 1976. ДП
Wirth N. Systematic Programming: An Introduction, Prentice-Hall, Englewood Cliffs, NJ., 1973. ДП,ДК,К
Wirth N. On the Composition of Well-Structured Programs, ACM Computing Surveys, 6 (4), pp. 247-260, December 1974. К
Wulf W. A. Alphard: Toward a Language to Support Structured Programs, Carnegie-Mellon University Computer Science report, April 1974. Я
ПРЕДМЕТНЫЙ УКАЗАТЕЛЬ
Алгол 112
Алгоритм упорядочения 69
Эвклида 46 J
— двоичный 66, 73
Алгоритмы просмотра двоичных деревьев 138*
Атом 116‘
Восходящая индукция 13
Вполне упорядоченное множество 20, 142
Выполнение цикла 49, 91—93
Высказывание о справедливости см. Утверждение 26
Гипотеза индукции 11, 13, 17, 20, 23
Двоичное дерево 138<
Доказательство конечности программы 55
Инвариант цикла, 32, 35
Индукция восходящая 13
математическая 10
модифицированная простая 12
нисходящая 13
^ обобщенная 20, 21, 128, 133—135
простая 10
.— строгая 17
структурная 120, 128, 138, 144
Исчисление предикатов 79
Конструирование программ 150 — языков 150
Лексикографический порядок 21, 23, 130, 134
Математическая индукция 10
Метод индуктивных утверждений 55, 144 — структурной индукции 120, 128, 138, 144
Методы доказательства правильности 149
Механизация процесса доказательства правильности 152
Механическая система верификации 152
Модифицированная простая индукция 12
Наибольший общий делитель GCD 46, 52, 66, 73, 85
Недетерминированные программы 150
Нисходящая индукция 13
Обобщенная индукция 20, 21, 128, 133, 134, 135
Обоснованно упорядоченное множество 125
Оператор GO ТО 93, 108, 151.
Отношение лексикографического порядка 20, 21
Подпрограмма 93
Полная правильность 55
Полный граф 15
Правила вычислений 115
Простая индукция 10
Простое число 17
Сокращенные доказательства правильности 76
Список 116
Справедливость высказывания 32
Строгая индукция 17
Структурная индукция по размеру 139
Структурированные программы 108
Тело цикла 30
Условия верификации 80
Утверждение см. Высказывание о правильности 26
Утверждение о правильности 34, 35
Факториал 121
Формализация доказательства 78,152
Формальная нотация 81
Фортран 87, 93
Функция Аккермана 113, 118, 130, 135,146
-APPEND 118, 119, 123
- CAR 116
-CDR 116
-CONS 117
DELETE1 126
DELETE2 137
FLATTEN 136
INT 126
INORD 133
LEAST 127
Маккарти 131
-MEMBER 117, 122, 228
REVERS 127
SUBS 151
UNION 126
UNIORD 136
Цикл DO WHILE 97, 98, 103, 105
Частично правильная программа 55
Числа Фибоначчи 18, 19
Язык Лисп 111, 112, 116
-ПЛ/1 87,97, 111
ОГЛАВЛЕНИЕ
Предисловие редактора перевода 5
Предисловие 8
Глава 1. Математическая индукция 10
Введение 10
Простая-индукция. . ‘ 10
Строгая версия математической индукции . . . .17
Обобщеннаяиндукция 20
Глава 2. Доказательство правильности блок-схем программ . . .25
Введение 25
Основные принципы доказательства правильности для блок-схем 26
Дополнительные примеры доказательства правильности блок-схем программ 43
Метод индуктивныхутверждений 53
Сокращенные доказательства правильности . .75
Формализация доказательства с помощью индуктивных утверждений1 78
Глава 3. Доказательство правильности программ, написанных на обычных языках программирования 87
Введение 87
Примеры доказательства правильности программ на Фортране 87
Примеры доказательства правильности программ наПЛ/1 97
Аксиоматический подход к доказательству частичной правильности 101
Доказательство частичной правильности как часть процесса программирования 108
Глава 4.Доказательство правильности рекурсивных программ. . .111
Введение Ш
Упрощенный язык программирования для иллюстрации понятия рекурсии 112
Структурная индукция 120
Более трудные примеры доказательства правильности программ методом структурной индукции 128
Структурная индукция для нерекурсивныхпрограмм138
Глава 5. Современные исследования, связанные с доказательством правильности программ 149
5.1. Введение 149
52. Методы доказательства 149
53. Конструирование программ и языков 150
5.4. Механизация процесса доказательства правильности152
Библиография 154
Предметный указатель 160
। УВАЖАЕМЫЙ ЧИТАТЕЛЬ!
Ваши замечания о содержании книги, ее оформлении, качестве. перевода и другие просим присылать по адресу: 129820, Москва, И-110, ГСП, 1-й Рижский пер., д. 2, издательство «Мир».
Роберт Андерсон
ДОКАЗАТЕЛЬСТВО ПРАВИЛЬНОСТИ ПРОГРАММ
Старший научный редактор И. М. Андреева
Младший научный редактор Л. С. Сысоева
Художник В. И. Харламов
Художественный редактор Л. Е. Безрученков
Технический редактор Е. С. Потапенкова
Корректор М. А. Смирнов
ИБ № 2783
Сдано в набор 07.08.81.
Подписано к печати 28.01.82.
Формат 84 X 108V32.
Бумага офсетная Mi 2.
Гарнитура литературная. Печать офсетная.
Объем 2,63 бум. л. Усл. печ. л. 8,82.
Усл. кр. отт. 9,02.
Уч.-изд. л. 8,16. Изд. Ns 20/1389.
Тираж 20 000 экз. Заказ 549. Цена 60 коп.
Издательство «Мир»
Москва, 1-й Рижский пер., 2..
Ярославский полиграфкомбинат Союзполиграфпрома при Государственном комитете СССР по делам издательств, полиграфии и книжной торговли. 150014, Ярославль, ул. Свободы, 97.
выпускает в свет в 1982 году
ЭТЮДЫ ДЛ^ ПРОГРАММИСТОВ. Уэзерелл Ч.
Пер. с англ., 20 л., цена 1 р. 70 к.
Книга американского специалиста по системному программированию является уникальным сборником задач и упражнений по программированию из разных областей науки и техники: моделирования, точности вычислений, обработки текстов, игр, создания искусственного интеллекта, конструирования компиляторов.
Для всех, кто преподает и изучает программирование.
выпускает в свет в 1982 году
МЕТОДЫ ПРОГРАММИРОВАНИЯ» В 2-х томах.
Мейер Б., Бодуэн К.
Пер. с франц., т. 1—22 л., цена 1 р. 90 к., т. 2—22 л., цена 1 р. 90 к.
В монографии французских ученых систематически излагаются основные понятия информатики, обсуждаются трудные проблемы методологии программирования, дается сравнение известных языков программирования: Фортрана, Алгола W, ПЛ/1 и др. Изложение сопровождается оригинальными упражнениями (с решениями).
Для профессиональных программистов, желающих овладеть современными методами программирования. Может служить учебным пособием по языкам программиро^ вания й алгоритмам.
выпускает в свет в 1982 году
ТЕОРИЯ И ПРАКТИКА СТРУКТУРНОГО ПРОГРАММИРОВАНИЯ.
Линтер Р., Миллс X., Уитт Б.
Пер. с англ., 17; л., цена 1 р. 50 к.
Рассмотрены теоретические и практические методы структурного программирования, позволяющие программисту получать сложные программы без ошибок, а также быстро выявлять ошибки в готовых программах, полученных от других пользователей.
Для специалистов в области системного и программного обеспечения ЭВМ, а также студентов и аспирантов соответствующих специальностей.
60 коп.60 коп.5ВГ66 А№
60 коп.
60 коп.