Базисные схемы обработки информации
Схема вычисления инвариантной функции
Общая схема итерации значительно упрощается для случая вычисления значений инвариантных функций.
Определение 7.3.
Пусть — некоторое множество,
— заданная
на нем функция, а
— предикат такой, что
легко вычислить ).
Обозначим через
то подмножество множества
, где
.
Если существует преобразование
такое, что
, то функция
называется
-инвариантной или просто инвариантной
функцией.
Простейшим примером инвариантной функции является хорошо известная еще
из средней школы функция ,
. Она является
-инвариантной
относительно преобразования
,
.
Наибольший общий делитель двух целых чисел (greatest common divisor, или просто
) инвариантен
относительно
преобразования
,
задаваемого формулой

Доказательство этого факта, основанное на основной теореме арифметики о
разложении числа на простые множители, является достаточно простым и
оставляется читателю. Обратите только внимание на то, что функция
не определена в точке
.
Для вычисления значения -инвариантной функции
в точке
применяется следующая схема.
Схема вычисления инвариантной функции.
Многократно выполняется преобразование ,
дающее последовательность точек
Если
очередная точка
попадaет в подмножество
, то
итерации завершаются.
По определению инвариантной функции
легко вычисляется и
совпадает с
искомым
.
Рисунок 7.4 содержит графическую иллюстрацию этой схемы.
Схема вычисления инвариантной функции значительно облегчает проектирование
программы "S0;while(e)S;S1;", так как нам изначально известны инвариант и условие продолжения цикла
.
Тело цикла S конструируется, как программная реализация известного
преобразования
, а написание S1, вычисляющей
, не может
представлять трудностей в силу самого определения инвариантной функции.
В качестве иллюстрации применения схемы вычисления инвариантной функции рассмотрим следующую задачу.
Задача 7.4. Напишите программу, находящую наибольший общий делитель
двух целых неотрицательных чисел
и
, не равных одновременно нулю.
Воспользуйтесь следующими свойствами наибольшего общего делителя
(не забудьте научиться доказывать все
эти свойства):
-
,
-
,
-
,
,
.
Решение Если через обозначить множество всех
неотрицательных
целых чисел, представимых в ЭВМ, то
,
,
,
. В качестве преобразования
можно взять

Таким образом, нам известны инвариант и
условие продолжения
. Начальные
присваивания S0 в
данном случае не нужны, тело цикла S пишется по
определению
, a
программа S1, вычисляющая
для
, реализуется с
помощью справедливой для этих значений аргумента формулы
.
Текст программы
public class Gcd { public static void main(String[] args) throws Exception { int x = Xterm.inputInt("x -> "); int y = Xterm.inputInt("y -> "); Xterm.print("gcd(" + x + "," + y + ") ="); while ( (x != 0) && (y != 0) ) { if (x >= y) x -= y; else y -= x; } Xterm.println(" " + (x+y)); } }
Обратите внимание на тот факт, что в построенной программе не понадобилось
наличие переменной, соответствующей значению инвариантной функции .
Рассмотрим еще одну задачу.
Задача 7.5. Напишите программу, перемножающую два целых числа, одно из
которых неотрицательно, без использования операции умножения. Точные
пред- и постусловия требуемой программы, временная
сложность которой не должна превосходить , таковы:
,
. При написании программы величины
и
изменять не
разрешается. Воспользуйтесь тем, что
функция
,
является инвариантной
относительно преобразования
, задаваемого
формулой

В данном случае ,
,
,
.
Функция
и преобразование
заданы в условии
задачи.
Таким образом, нам известны инвариант и
условие продолжения
. Начальные присваивания S0
очевидны ( "x=a; y=b; z=0;" ),
тело цикла S пишется по определению
, a
программа S1, вычисляющая
для
, в
данном случае вырождается в пустой оператор ";", так как для этих значений
аргумента
. В результате получаем уже знакомую нам
программу.
Текст программы
public class MulInv { public static void main(String[] args) throws Exception { int a = Xterm.inputInt("a -> "); int b = Xterm.inputInt("b -> "); int x = a, y = b, z = 0; while (y > 0) { if ((y&1) == 0) { y >>>= 1; x += x; } else { y -= 1; z += x; } } Xterm.println("a * b = " + z); } }