"Информатика (базовый и углублённый уровни) (в 2 частях)", Поляков К.Ю., Еремин Е.А.
Доказательство правильности программы показывает, что она получает требуемый результат для всех входных данных, удовлетворяющих заданным условиям, а не только в нескольких удачных запусках. Для этого формулируют предусловие и постусловие, а затем проверяют команды и конструкции алгоритма. Запись {P} S {Q} связывает исходные условия P, программу S и результат Q. Особенно важен инвариант цикла: он помогает проследить, что сохраняется на каждом шаге и почему полученный результат верен.
Текст подготовлен нейронной сетью по теме параграфа и не заменяет учебник — важные факты и формулировки сверяйте с ним.
Предусловие — условие, которое должно выполняться до запуска программы, чтобы её работу можно было рассматривать. Например, для алгоритма деления оно может требовать, чтобы делитель не был равен нулю. Предусловие задаёт границы доказательства: мы не обязаны рассматривать входы, которые ему не удовлетворяют.
Постусловие — условие, которое должно выполняться после завершения программы при выполненном предусловии. Оно точно описывает требуемый результат, например, что найденное число является наибольшим из заданных. Постусловие нужно, чтобы сравнить реальный результат работы алгоритма с тем, который от него требуется.
Тройка Хоара — запись {P} S {Q}, означающая, что при условии P выполнение программы S приводит к условию Q, если программа завершается. Здесь P — предусловие, S — команда или программа, Q — постусловие. Такая запись позволяет формулировать доказательство не расплывчато, а для конкретных исходных условий и конкретного результата.
Инвариант цикла — условие, которое сохраняется до и после каждого выполнения тела цикла и помогает доказать результат его работы. Например, при подсчёте суммы оно может связывать текущую сумму с уже обработанными числами. Инвариант нужен, чтобы обосновать каждый шаг цикла и связать его итог с постусловием.
Правильность связывают с предусловием и постусловием, потому что слово «правильно» само по себе не задаёт, какие входы и какой результат имеются в виду. Сначала уточняют, что известно о данных до старта, затем формулируют требование к итоговому состоянию. Тогда доказательство отвечает на определённый вопрос: следует ли требуемый результат из исходных условий при выполнении программы.
Для присваивания нужно учесть, что меняется значение переменной, а остальные данные остаются прежними. Поэтому в постусловие подставляют выражение, присвоенное этой переменной, и проверяют, следует ли полученное условие из предусловия. Так проверка показывает не просто новое значение переменной, а то, что команда действительно обеспечивает требуемый результат.
В последовательности команд первая команда меняет состояние, с которым начинает работать следующая. Чтобы рассуждение не прерывалось между ними, формулируют промежуточное условие: оно должно следовать из результата первой команды и быть достаточным для второй. Если после последней команды выполняется постусловие, вся последовательность переводит исходное состояние в требуемое.
Ветвление выбирает одну из команд в зависимости от условия, поэтому недостаточно проверить только часто встречающийся вариант. При истинном условии проверяют первую ветвь, а при ложном — вторую; каждая должна обеспечить общее постусловие. Это доказывает правильность независимо от того, какой путь выберет программа на конкретных данных.
Для цикла заранее выбирают инвариант, который описывает полезное свойство текущего состояния. Затем доказывают, что он верен до начала цикла и сохраняется после каждого выполнения тела; иначе рассуждение не распространяется на все итерации. Наконец, условие выхода из цикла соединяют с инвариантом: вместе они должны обеспечивать постусловие, а не просто сообщать, что повторения прекратились.
| Конструкция | Что требуется доказать | К чему это приводит |
|---|---|---|
| Присваивание | После присваивания выполняется постусловие | Команда переводит состояние из предусловия в постусловие |
| Последовательность команд | Постусловие первой команды является предусловием следующей | Вся последовательность удовлетворяет заданному постусловию |
| Ветвление | Каждая ветвь при своём условии приводит к общему постусловию | Результат верен независимо от выбора ветви |
| Цикл | Инвариант верен до цикла и сохраняется после каждой итерации; выход из цикла вместе с инвариантом даёт постусловие | После завершения цикла получен требуемый результат |
Пользуйтесь таблицей как кратким планом доказательства: сначала определите конструкцию, затем проверьте нужное для неё условие. Для составной программы применяйте проверки последовательно и связывайте соседние этапы промежуточными условиями. Для цикла отдельно проверьте начало, сохранение инварианта и выход.
Задача 1. Для команды x := x + 1 докажите, что из условия x = 4 следует условие x = 5. Требуется проверить переход от заданного предусловия к постусловию именно после выполнения присваивания.
Задача 2. Программа выполняет: если x > 0, то y := x, иначе y := −x. Докажем, что после неё y = |x|. Модуль числа равен самому числу при x > 0 и противоположному числу при x ≤ 0, поэтому достаточно проверить оба случая.
Задача 3. Цикл суммирует числа от 1 до n: на каждом шаге увеличивает i на 1 и прибавляет i к s. Докажем, что после завершения s = 1 + 2 + … + n. Предположим, что перед циклом i = 0 и s = 0, а цикл выполняется, пока i < n.
Считать программу правильной, если несколько запусков дали верный ответ. Так поступают, потому что тесты наглядны и создают ощущение проверки, но они охватывают лишь отдельные входы. Проверьте себя: доказан ли результат для любого входа, удовлетворяющего предусловию, или рассмотрены только выбранные примеры?
Не задавать предусловие или постусловие точно. Без этих условий непонятно, какие входы допустимы и какой именно результат нужно получить; часто их оставляют неявными, полагаясь на контекст задачи. Проверьте себя: можете ли вы записать исходное и требуемое свойства так, чтобы их можно было проверить?
Выбрать инвариант, который истинен в начале цикла, но не сохраняется после тела. Одного совпадения в начале недостаточно: иногда свойство формулируют слишком узко или не учитывают изменение переменных. Проверьте его на произвольном шаге, предположив, что инвариант верен до итерации, и проследив все присваивания.
Забыть связать инвариант с условием выхода. Инвариант может сохраняться, но сам по себе не говорит, что результат при остановке нужный; из-за этого доказательство кажется законченным раньше времени. Проверьте, следует ли постусловие из инварианта и отрицания условия продолжения цикла.
Для доказательства нужны переменные, присваивания, ветвления и циклы: следует уметь прослеживать, как меняются значения при выполнении команд. Если эти конструкции знакомы, можно рассматривать программу как последовательность переходов между состояниями и проверять условия на каждом переходе. Навык доказательства помогает обосновывать алгоритмы, особенно работу циклов, и находить ошибки ещё при разработке. Он дополняет тестирование, но не подменяется проверкой отдельных запусков.
Что означают предусловие и постусловие программы?
Предусловие описывает, что известно о данных до запуска программы. Постусловие задаёт, каким должно быть состояние после её завершения, если предусловие выполнялось.
Как читать запись {P} S {Q}?
Это тройка Хоара: если перед выполнением программы S истинно условие P и программа завершается, то после неё истинно Q. P называют предусловием, а Q — постусловием.
Почему правильной работы на нескольких тестах недостаточно?
Тесты проверяют программу только на отдельных входных данных. Ошибка может проявиться на другом допустимом значении, поэтому общий вывод требует доказательства.
Как выбрать инвариант цикла?
Нужно найти условие, которое описывает полезную связь между переменными и уже обработанной частью данных. Затем проверить, что оно верно до цикла и сохраняется после каждой итерации.
Что именно нужно доказать для цикла?
Сначала доказывают истинность инварианта перед циклом и его сохранение после каждого шага. Затем проверяют, что при выходе из цикла инвариант вместе с условием выхода влечёт требуемое постусловие.
§ 37. Доказательство правильности программ