Проектирование цикла при помощи инварианта
Замена константы переменной
В качестве первого примера использования этого метода построения инварианта рассмотрим простую задачу суммирования элементов массива.
Задача 8.3. Напишите программу, находящую сумму элементов заданного целочисленного массива , элементы которого и величину изменять нельзя. Точные пред- и постусловия: ,
Решение В постусловие входит константа , которую мы можем заменить новой переменной , меняющейся в диапазоне от до включительно. Таким образом, метод замены константы переменной приводит нас к инварианту
Понятно, что в качестве ограничивающей функции следует взять .Истинности инварианта легко добиться обнулением величин и , поэтому искомая программа будет иметь вид "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); } }
Докажите самостоятельно ее правильность и оцените эффективность.