欧几里德算法又称辗转相除法,用于计算两个整数a,b的最大公约数 gcd(a,b)。基本算法:设 a = qb + r,其中a,b,q,r都是整数,则 gcd(a,b) = gcd(b,r),即 gcd(a,b) = gcd(b,a%b)。
证明:
a = qb + r
如果 r = 0,那么 a 是 b 的倍数,此时显然 b 是 a 和 b 的最大公约数。
如果 r ≠ 0,任何整除 a 和 b 的数必定整除 a - qb = r,而且任何同时整除 b 和 r 的数必定整除 qb + r = a,所以 a 和 b 的公约数集合与 b 和r 的公约数集合是相同的。特别的,a 和 b 的最大公约数是相同的。
递归实现:
int gcd(int a, int b)
{
return b == 0 ? a : gcd(b, a%b);
}
在数论中,裴蜀等式(英语:Bézout’s identity)或贝祖定理(Bézout’s lemma)是一个关于最大公约数(或最大公约式)的定理。裴蜀定理得名于法国数学家艾蒂安·裴蜀,说明了对任何整数a、b和它们的最大公约数d,关于未知数x和y的线性丢番图方程(称为裴蜀等式):
ax + by = m 有整数解时当且仅当m是d的倍数。
裴蜀等式有解时必然有无穷多个整数解,每组解x、y都称为裴蜀数,可用扩展欧几里得算法(Extended Euclidean algorithm)求得。
例如,12和42的最大公因数是6,则方程12x+42y=6有解。事实上有(-3)×12 + 1×42 = 6及4×12 + (-1)×42 = 6。
特别来说,方程 ax+by=1 有整数解当且仅当整数a和b互素。
裴蜀等式也可以用来给最大公约数定义: d其实就是最小的可以写成ax+by形式的正整数。(这个定义的本质是整环中“理想”的概念。因此对于多项式整环也有相应的裴蜀定理。)
证明:
如果 a
和 b 中有一个是 0,比如 a=0,那么它们两个的最大公约数是 b。这时裴蜀等式变成 by=m,它有整数解 (x,y) 当且仅当 m 是 b 的倍数,而且有解时必然有无穷多个解,因为 x 可以是任何整数。定理成立。。
扩展欧几里得算法就是在求 a,b
的最大公约数 m=gcd(a,b) 的同时,求出贝祖等式 ax + by = m的一个解 (x,y)。
有两个数 a,b
,对它们进行辗转相除法,可得它们的最大公约数——这是众所周知的。然后,收集辗转相除法中产生的式子,倒回去,可以得到 ax+by=gcd(a,b)的整数解。
先来看下这个几乎所有总结扩展欧几里得算法的帖子中都会用到的例子
(可能出自wikipedia,毕竟wikipedia上也是用的这个栗子):
用类似辗转相除法,求二元一次不定方程47x+30y=1的整数解。
47=30*1+17
30=17*1+13
17=13*1+4
13=4*3+1
然后把它们改写成“余数等于”的形式
17=47*1+30*(-1) //式1
13=30*1+17*(-1) //式2
4=17*1+13*(-1) //式3
1=13*1+4*(-3)
然后把它们“倒回去”
1=13*1+4*(-3) //应用式3
1=13*1+[17*1+13*(-1)]*(-3)
1=13*4+17*(-3) //应用式2
1=[30*1+17*(-1)]*4+17*(-3)
1=30*4+17*(-7) //应用式1
1=30*4+[47*1+30*(-1)]*(-7)
1=30*11+47*(-7)
得解x=-7, y=11。
这些式子自己手写一遍才能理解的更清楚。
这个“倒回去”的过程对应的就是代码中递归向上返回的过程。现在的问题就是要找出这个可以倒回去的递推关系。
参照ACM之家中的证明:
设:a>b。
推理1,显然当 b=0,gcd(a,b)=a。此时 x=1,y=0;//推理1
推理2,ab!=0 时
设 ax1+by1=gcd(a,b);
bx2+(a mod b)y2=gcd(b,a mod b);
根据朴素的欧几里德原理有 gcd(a,b)=gcd(b,a mod b);
则:ax1+by1=bx2+(a mod b)y2;
即:ax1+by1=bx2+(a-(a/b)*b)y2=ay2+bx2-(a/b)*by2;
根据恒等定理得:x1=y2 ,y1=x2-(a/b)*y2;//推理2
这样我们就得到了求解 x1,y1 的方法:x1,y1 的值基于 x2,y2.
上面的思想是以递归定义的,因为 gcd 不断的递归求解一定会有个时候 b=0,所以递归可以结束。
这样我们就找到了递推关系:
x1=y2;
递推的终止条件再上面也已经给出:
当b=0,gcd(a,b)=a。此时x=1,y=0;
由此我们可以得到递归的扩展欧几里得算法的代码:
int exgcd(int a, int b, int &x, int &y)
{
if(b == 0)
{//推理1,终止条件
x = 1;
y = 0;
return a;
}
int r = exgcd(b, a%b, x, y);
//先得到更底层的x2,y2,再根据计算好的x2,y2计算x1,y1。
//推理2,递推关系
int t = y;
y = x - (a/b) * y;
x = t;
return r;
}
现在我们知道了 a 和 b 的最大公约数是 gcd ,那么,我们一定能够找到这样的 x 和 y ,使得: a*x + b*y = gcd 这是一个不定方程(其实是一种丢番图方程),有多解是一定的,但是只要我们找到一组特殊的解 x0 和 y0 那么,我们就可以用 x0 和 y0 表示出整个不定方程的通解:
x = x0 + (b/gcd)*t
y = y0 – (a/gcd)*t
算法E
(推广的欧几里得算法). 给定两个正整数m和n, 计算他们的最大公因数d,并计算两个未必为正数的整数 a 和 b, 使得 am+bn=d.┃
假定 m = 1769 , n = 551, 我们相继得到:
1 | 0 | 0 | 1 | 1769 | 551 | 3 | 116 |
0 | 1 | 1 | -3 | 551 | 116 | 4 | 87 |
1 | -4 | -3 | 13 | 116 | 87 | 1 | 29 |
-4 | 5 | 13 | -16 | 87 | 29 | 3 | 0 |
答案是正确的: 5×1769 - 16×551 = 8845 - 8816 = 29, 这是1769和551的最大公因数。
《计算机程序设计艺术》用的数学归纳法证明。
首先我们发现 等式:
a′m+b′n=c,am+bn=d
执行后总是成立。 (1)
证明(1):
算法首次转到E2时,由上表,显然等式成立。
步骤 E2,E3 并不会改变等式的正确性。
所以只需要证明步骤 E4 不改变他们的正确性就可以了。
执行 E4 之前,
a'm + b'n = c (*)
am + bn = d (**)
成立。
a'm + b'n = c = qd + r;
a'm + b'n - q(am + bn) = r;
(a' - qa)m + (b' - qb)n = r (***)
执行 E4 之后,c←d, d←r, t←a', a'←a, a←t-qa, t←b', b'←b, b←t-qb;
所以
(**)变为 a' + b' = c
(***)变为 am + bn = d
所以步骤 E4 不改变等式的正确性,得证。
现在我们对n归纳,证明算法E
是正确的:如果 m 是 n 的倍数,算法显然能够正确执行,因为在第一次达到 E3 的时候就立即完成了。 当 n=1 时这种情况总是出现,剩下的情况仅是 n>1 且 m 不是 n 的倍数。在这种情况下,算法在第一次执行后置 c←n 和 d←r,并且由于 r←n, 按归纳法可以假设 , d 的最终值是 n 和 r 的最大公因数。根据上文欧几里得算法的论证,数偶 {m,n} 和 {n,r} 具有相同的公因数,特别的,它们具有相同的最大公因数。因此 d 是 m 和 n 的最大公因数, 而且由(1)式, am+bn=d。的递推公式:
首先我们发现a′m+b′n=c,am+bn=d
这两个式子是等价的,只是同一个式子的不同阶段。的递推公式。
如果给上表标上序号:
余数等于 | |||||||
---|---|---|---|---|---|---|---|
1 | 0 | 1 | 1769 | 551 | 3 | 116 | 116 = m + (-3)n |
2 | 1 | -3 | 551 | 116 | 4 | 87 | 87 = -4m + 13n |
3 | -4 | 13 | 116 | 87 | 1 | 29 | 29 = 5m +(-16)n |
4 | 5 | -16 | 87 | 29 | 3 | 0 |
第 i
的序列。从上表可以清楚的看到这二者之间的关系。
第一行:a1=0,b1=1
0×1769 + 1×551 = 551 初始条件是成立的,即 a1m+b1n=d1;
第二行:由第一行的 1769 = 3×551 + 116 ,把余数116表示为 116 = 1×1769 + (-3)×551 = m + (-3)n, 我们注意到第一行的余数 r1
等于第二行的 d2 所以 a2=1,b2=−3, a2m+b2n=r1=d2第三行:由第二行的 551 = 4×116 +87, 把余数87表示为 87 = 551 + (-4)×116, 其中551 = n, 而116已经求得为m+(-3)n, 所以 87 = n + (-4)(m+(-3)n) = -4m + 13n ,即 a3=−4,b3=13
, a3m+b3n=r2=d3;
第四行:由第三行的 116 = 1×87 + 29,把余数29表示为 29 = 116 + (-1)×87 = (m + (-3)n) + (-1)(-4m + 13n) = 5m +(-16)n,所以a4=5,b4=−16
, a4m+b4n=r3=d4;。
我们发现 :
di=ri−1=ci−1−qi−1di−1=di−2−qi−1di−1=(ai−2m+bi−2n)−qi−1(ai−1m+bi−1n)
的递推公式。
最后,给出非递归的C++代码:
int exgcd(int m, int n, int &x, int &y) {
if (n == 0) {
x = 1; y = 0;
return m;
}
int a, a1, b, b1, c, d, q, r, t;
a1 = b = 1;
a = b1 = 0;
c = m; d = n;
q = c/d; r = c%d;
while (r) {
c = d;
d = r;
t = a1;
a1 = a;
a = t - q * a;
t = b1;
b1 = b;
b = t - q * b;
q = c/d;
r = c%d;
}
x = a; y = b;
return d;
}