Проектирование цикла при помощи инварианта
Устранение конъюнктивного члена
Этот метод хорош тем, что взяв в качестве условия продолжения цикла устраненный конъюнктивный член, мы автоматически добиваемся истинности третьего пункта теоремы 8.1., гарантирующей правильность построенного цикла.
Рассмотрим в качестве примера следующую задачу.
Задача 8.1 Напишите программу, находящую приближенное значение квадратного корня из заданного неотрицательного целого числа . Вот более точная формулировка пред- и постусловия: , . При написании программы величину изменять нельзя.
Решение Построим инвариант с помощью метода устранения конъюнктивного члена из постусловия: . В качестве условия продолжения цикла e может быть взято отрицание удаленного конъюнктивного члена: , что эквивалентно выбору ограничивающей функции . Истинность инварианта перед началом выполнения цикла легко устанавливается присваиванием "a=0;" и нам остается только понять, как реализовать тело цикла S.
Для того чтобы цикл завершился, величина должна увеличиваться. Простейший способ — увеличивать на единицу на каждой итерации цикла. Легко заметить, что это преобразование сохраняет инвариант, поэтому построение программы завершено. Первый вариант программы будет строго соответствовать ее спецификации.
Текст программы
public class Sqrt { public static void main(String[] args) throws Exception { int n = Xterm.inputInt("n -> "); int a = 0; while ( n >= (a+1)*(a+1) ) a += 1; Xterm.println("sqrt(" + n + ") = " + a); } }
Эту программу можно переписать в чуть более компактном виде:
Фрагмент программы
int a, n = Xterm.inputInt("n -> "); for (a=0; n >= (a+1)*(a+1); a++); Xterm.println("sqrt(" + n + ") = " + a);
Докажем все пять условий ее правильности.
-
-
Полученный предикат заведомо истинен, если ложны или , в противном случае он легко упрощается:
Истинность первого конъюнктивного члена вытекает из предположения о истинности , а истинность второго — из истинности . -
-
-
Следовательно,
Применим метод устранения конъюктивного члена для построения инварианта цикла при решении еще одной задачи.
Задача 8.2 Напишите программу (линейный поиск), определяющую первое вхождение заданного целого числа в заданный массив целых чисел ( ). Известно, что находится в массиве . Значения элементов массива и число в программе изменять нельзя.
Решение Выпишем формально заданные нам пред- и постусловия: , .
Так как добиться истинности третьего конъюнктивного члена одним или несколькими простыми присваиваниями трудно, устраним именно его. Тогда получим . В качестве условия продолжения цикла можно взять отрицание удаленного члена ( ), а инвариант легко сделать истинным, выполняя команду "i=0;", поэтому программа должна иметь вид "i=0;while(x!=b[i])S;" с неизвестным нам пока S.
В качестве ограничивающей функции можно попробовать взять , a для того, чтобы она уменьшалась, достаточно увеличивать на каждой итерации цикла. Понятно, что увеличивая более, чем на единицу, можно пропустить первое вхождение в массив, поэтому одной из команд, входящих в S, должна быть команда "i=i+1;". Так как выполнение данной команды при условии истинности сохраняет инвариант, то эта команда является единственной в теле цикла. Программа построена.
Текст программы
public class SearchL { static int b[], x; public static void main(String[] args) throws Exception { int m = Xterm.inputInt("m -> "); b = new int[m]; for (int k=0; k<m; k++) b[k] = Xterm.inputInt("b["+k+"] -> "); x = Xterm.inputInt("x -> "); int i=0; while (x != b[i]) i += 1; Xterm.println("i = " + i); } }
Докажем правильность этой программы.
- , что следует из .
-
-
-
Следовательно, .