Проектирование цикла при помощи инварианта
Замена константы переменной
В качестве первого примера использования этого метода построения инварианта рассмотрим простую задачу суммирования элементов массива.
Задача 8.3. Напишите программу, находящую сумму элементов заданного целочисленного
массива
, элементы которого и величину
изменять нельзя.
Точные пред-
и постусловия:
,
![\displaystyle R=\left(s = \sum_{j=0}^{n-1} b[j]\right).](/sites/default/files/tex_cache/925b14b983a8f78ecce564b23e49c49b.png)
Решение
В постусловие входит константа , которую мы можем заменить
новой переменной
, меняющейся в диапазоне от
до
включительно. Таким образом, метод замены константы переменной
приводит нас к инварианту
![\displaystyle I=\left(0\leqslant i \leqslant n
\land
\left(s = \sum_{j=0}^{i-1} b[j]\right)\right).](/sites/default/files/tex_cache/11b09ec7d71e7193bf0644dedda8da5a.png)

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