Расширенный рекурсивный алгоритм Евклида
Другой способ найти помимо НОД(a,b) и его представление в виде a·x+b·y – это дополнить рекурсивную реализацию алгоритма Евклида.
Procedure RecEuclid (a,b:Integer; Var d:Integer);
Var r:Integer;
Begin
If b=0 Then d:=a
Else Begin
r:=a Mod b;
RecEuclid(b,r,d);
End;
End;
Этот алгоритм, в принципе, выполняет те же вычисления, что и итеративный. Определяется цепочка остатков до наибольшего общего делителя. 2322, 654, 360, 294, 66, 30, 6, 0, при этом вся цепочка запоминается. Кроме того, в соответствии с логикой работы рекурсивных схем реализации осуществляется возврат к началу вычислений (выход из рекурсии). В данном случае «налегке», «в холостую». Вот здесь-то, на обратном пути, мы и «заставим» его работать.
Завершив прямой проход, процедура «смотрит» на два последних числа в цепочке. На b и r. Наибольший общий делитель d равен b. Выразим его через b и r: d=b·1+r·0.
Внимание – снова вопрос. Есть a=q∙b+r, 0≤r<b и известно представление d с помощью b и r: d=b·x/+r·y/. Как найти представление d с помощью a и b?
Ответ. Так как r=a-q∙b, то d=b·x/+r·y/=b·x/+(a-q∙b)·y/=a·y/+b·(x/-q∙y/),
или d=a·x+b·y, где
x = y/, y = x/-q∙y/.
Итак, нам известно, с чего начать обратный путь и как делать шаг по цепочке назад. По формулам выражаем d уже через пару предпоследних чисел в цепочке, продолжаем выходить из рекурсии до тех пор, пока не выразим d через первые два числа.
Пример. Пусть a=2322 и b=654. Строим таблицу (рис. 1.22) в два этапа. На первом – сверху вниз – находим частные и остатки от делений. На втором – снизу вверх – вычисляем множители x и y.
r
x
y
q
2322
654
3
360
1
294
1
66
4
30
2
6
5
0
Рис. 1.22. Пример вычисления представления d в виде a·x+b·y с помощью рекурсивной логики
В самую нижнюю строку автоматически заносим 1 и 0. Это очевидное представление наибольшего общего делителя d=6, с помощью 6 и 0: 6=6·1+0·0.
Заполняем вторую строку снизу. Значение x=0 просто переносим «по диагонали» из строки ниже. Вычисляем y=1-0·5=1. Это представление d с помощью 30 и 6: 6=30·0+6·1.
Определяем третью строку. Значение x=1 вновь берём «по диагонали» из строки ниже, а значение y=0-1·2=-2. Это представление d с помощью 66 и 30. 6=66·1+30·(-2).
«Всё выше и выше». В результате, выразим d через 2322 и 654. Как и в итеративной версии, НОД(2322,654)=6=2322·20+654·(-71).
Программная реализация имеет вид.
Procedure RecExEuclid (a,b:Integer; Var d,x,y:Integer);
Var r,q,x1,y1:Integer;
Begin
If b=0 Then Begin
d:=a;
x:=1; y:=0;
End
Else Begin
r:=a Mod b;
q:=a Div b;
RecExEuclid(b,r,d,x1,y1);
x:=y1;
y:=x1-y1*q;
End;
End;
Заметим, переменные r, q, x1 и y1 здесь совсем не обязательны. Процедура станет «легче», «сбросит» несколько строк. Но понять её уже будет сложнее.
Procedure RecExEuclid (a,b:Integer; Var d,x,y:Integer);
Begin
If b=0 Then Begin
d:=a;
x:=1; y:=0;
End
Else Begin
RecExEuclid(b,a Mod b,d,y,x);
y:=y-x*(a Div b);
End;
End;
Пересмотрим и ручные вычисления. Вернёмся к построению таблице (рис. 1.23). Одно не может не броситься в глаза. Столбец x практически совпадает со столбцом y. Зачем тогда его выписывать?
Рис. 1.23. Измененный вариант вычисления представления d в виде a·x+b·y
В две нижние клетки столбца y сразу помещаем 1 и 0. Заполняем третью снизу строку: y=1–0·5=1, затем четвёртую – y=0–1·2=-2, пятую – y=1–(–2)·4=9. Поднимаемся вверх и ведём вычисления по этому принципу. В результате, получаем искомые значения x и y – 20 и -71.