扩展欧几里得算法(extended Euclidean algorithm)的证明与实现
首先,我们还是回顾一下欧几里得算法相关的一些基本概念的定义:
定义(整除) 如果a和b为整数且a=0,我们说a整除b是指存在整数c使得b=a⋅c。如果a整除b,我们还称a是b的一个因子,且称b是a的倍数。
如果a整除b,则将其记为a∣b,如果a不能整除b,则记其为a∤b。
定义(最大公因子) 不全为零的整数a和b的最大公因子是指能够同时整除a和b的最大整数。
a和b的最大公因子记作(a,b)。有时也记作gcd(a,b)。
注意当n为正整数时,(0,n)=(n,0)=n。
虽然所有的正整数都能够整除0,我们还是定义(0,0)=0。这样可以确保关于最大公因子的相关结论在所有情况下均成立。
定理(欧几里得算法) 令整数r0=a,r1=b满足a≥b>0,
如果连续做带余除法得到rj=rj+1⋅qj+1+rj+2,
且0<rj+2<rj+1(j=0,1,2,⋯,n−2),rn+1=0,
那么(a,b)=rn,它是最后一个非零余数。
具体的,令r0=a,r1=b是正整数且满足a≥b,那么通过连续运用带余除法,我们求得
r0r1rjrn−3rn−2rn−1=r1⋅q1+r2=r2⋅q2+r3⋮=rj+1⋅qj+1+rj+2⋮=rn−2⋅qn−2+rn−1=rn−1⋅qn−1+rn=rn⋅qn0≤r2<r10≤r3<r20≤rj+2<rj+10≤rn−1<rn−20≤rn<rn−1
最终(a,b)=rn,
然后我们介绍一下,用线性组合的方法来表示最大公因数的理论支持:
贝祖定理(裴蜀定理) 如果a和b为正整数,且(a,b)=d,那么对于任意的整数x和y,
x⋅a+y⋅b都一定是d的倍数,特别地,一定存在整数s和t,
使s⋅a+t⋅b=d成立。
定义 如果a和b为正整数,则使得(a,b)=s⋅a+t⋅b的整数s和t,
称为a和b的贝祖系数。等式(a,b)=s⋅a+t⋅b称为贝祖恒等式。
根据贝祖定理,我们知道任意两个正整数的最大公约数可以表示为这两个整数的整系数的线性组合。
接下来我们会给出两种方法,用于找出两个正整数的线性组合以使之等于其最大公约数。
第一种方法要对欧几里得算法的除法步骤做反向处理,所以需要将欧几里得算法的步骤正反向各走一遍。
我们用一个例子来解释这种工作方法的工作原理,并给出python语言的实现。
例子1 通过欧几里得算法的反向处理,试把(252,198)=18表示为252和198的线性组合。
首先我们先回顾一下用欧几里得算法求(252,198)的步骤:
2521985436=198×1+54=54×3+36=36×1+18=18×2
我们将这些步骤总结在下表中:
| j | rj | rj+1 | qj+1 | rj+2 |
|---|
| 0 | 252 | 198 | 1 | 54 |
| 1 | 198 | 54 | 3 | 36 |
| 2 | 54 | 36 | 1 | 18 |
| 3 | 36 | 18 | 2 | 0 |
最后一个非零除数(在最后一列倒数第二行的那个数)就是252和198的最大公因子。因此(252,198)=18
观察用欧几里得算法求(252,198)的倒数第二步,
18=54−36×1
它的前面一步是
36=198−54×3
这意味着
18=54−(198−54×3)×1=54×4−198×1
同样,由第一步,我们得
54=252−198×1
因此
18=(252−198×1)×4−198×1=252×4−198×5=4×252−5×198
最后一个等式将18=(252,198)写成了252, 198的线性组合的形式。◀
一般地,为了知晓如何使用a, b的线性组合来表示它们的最大公因数d=(a,b),需要涉及欧几里得算法中产生的一系列等式。
由倒数第二个等式有
rn=(a,b)=rn−2−rn−1⋅qn−1
这就是用rn−2和rn−1线性组合表示了(a,b)。倒数第三步可以将rn−1用rn−3和rn−2来表示,即
rn−1=rn−3−rn−2⋅qn−2
用这个等式消去上面的表达式中的rn−1,则有
(a,b)=rn−2−(rn−3−rn−2⋅qn−2)⋅qn−1=rn−3⋅(−qn−1)+rn−2⋅(1+qn−2⋅qn−1)
这就将(a,b)表示成了rn−3和rn−2的线性组合。我们继续沿着欧几里得算法相反的步骤将(a,b)
表示成接下来的余数的线性组合,直到将(a,b)表示成r0=a, r1=b的线性组合。对于特定的j,
如果已经求得
(a,b)=s⋅rj+1+t⋅rj+2
那么,因为
rj+2=rj−rj+1⋅qj+1
我们有
(a,b)=s⋅rj+1+t⋅(rj−rj+1⋅qj+1)=t⋅rj+(s−t⋅qj+1)⋅rj+1
这显示了如何沿着欧几里得算法产生的等式递进,最终使得a和b的最大公因数(a,b)可以表示成它们的线性组合,即
如果已经求得
(a,b)=s′⋅r1+t′⋅r2
那么,因为
r2=r0−r1⋅q1
我们有
(a,b)=s′⋅r1+t′⋅(r0−r1⋅q1)=t′⋅r0+(s′−t′⋅q1)⋅r1
其中a=r0,b=r1,q1=⌊a/b⌋ 算法就此结束。
下面给出该算法的python语言实现:
def extended_Euclid(a:int, b:int):
'''
return s, t, d
s * a + t * b = d
'''
assert a >= b >= 0
if b == 0:
return (1, 0, a)
s, t, d = extended_Euclid(b, a%b)
return (t, (s-t*(a//b)), d)
def extended_gcd(a:int, b:int):
assert a >= 0 and b >= 0
if a < b:
a, b = b, a
return extended_Euclid(a, b)
def print_extended_gcd(a, b):
s, t, d = extended_gcd(a, b)
print("gcd({}, {}) = {}".format(a, b, d))
print(f"({s}) * {a} + ({t}) * {b} = {d}")
if __name__ == "__main__":
print_extended_gcd(252, 198)
这里简单注解一下extended_Euclid的实现:
-
结束条件,b为0时,return (1, 0, a),
因为这时b=0,d=(a,b)=a,于是乎d=1⋅a+0⋅b
-
递归调用, b不为0时,return (t, (s-t*(a//b)), d)
假设当前帧a = rj,b = rj+1,那么由s, t, d = extended_Euclid(b, a%b)这条语句可知,
a%b = rj+2,a//b = qj+1,套用之前的公式
d=(a,b)=s⋅rj+1+t⋅rj+2=s⋅rj+1+t⋅(rj−rj+1⋅qj+1)=t⋅rj+(s−t⋅qj+1)⋅rj+1
并且把a = rj,b = rj+1,a//b = qj+1代回上式,就得到return语句中的三元组了。
另外一种计算方法,叫做扩展的欧几里得算法,和第一种的区别主要在于不需要反向计算步骤,在计算最大公因数的同时,
就将线性组合的系数计算出来了。具体定理如下:
定理(扩展欧几里得算法) 令a, b是正整数,那么
(a,b)=sn⋅a+tn⋅b
其中sn, tn是下面定义的递归序列的第n项:
s0=1,s1=0,t0=0,t1=1,
且
sj=sj−2−qj−1⋅sj−1,tj=tj−2−qj−1⋅tj−1
其中j=2,3,⋯,n,而qj是欧几里得算法求(a,b)时每一步的商。
证明 我们将证明
rj=sj⋅a+tj⋅b(1)
因为(a,b)=rn,一旦等式(1)成立,我们就有
(a,b)=sn⋅a+tn⋅b
我们用数学归纳法来证明(1)。对于j=0,有a=r0=1⋅a+0⋅b=s0⋅a+t0⋅b。
因此对于j=0成立。类似地,b=r1=0⋅a+1⋅b=s1⋅a+t1⋅b,所以(1)对于j=1成立。
现在假设
rj=sj⋅a+tj⋅b(1)
对于j=1,2,⋯,k−1成立。那么,由欧几里得算法的第k步,我们有
rk=rk−2−rk−1⋅qk−1
由归纳假设,得到
rk=(sk−2⋅a+tk−2⋅b)−(sk−1⋅a+tk−1⋅b)⋅qk−1=(sk−2−sk−1⋅qk−1)⋅a+(tk−2−tk−1⋅qk−1)⋅b=sk⋅a+tk⋅b
这就完成了证明。■
下面我们还是用一个例子说明扩展的欧几里得算法如何将(a,b)表示成a, b的线性组合。
例子2 我们在下面的表中总结了用扩展欧几里得算法将(252,198)=18表示为252和198的线性组合的步骤。
| j | rj | rj+1 | qj+1 | rj+2 | sj | tj |
|---|
| 0 | 252 | 198 | 1 | 54 | 1 | 0 |
| 1 | 198 | 54 | 3 | 36 | 0 | 1 |
| 2 | 54 | 36 | 1 | 18 | 1 | -1 |
| 3 | 36 | 18 | 2 | 0 | -3 | 4 |
| 4 | 18 | - | - | - | 4 | -5 |
sj和tj (j=0,1,2,3,4)的值计算如下:
s0s1s2s3s4=1,=0,=s0−q1⋅s1=1−1×0=1,=s1−q2⋅s2=0−3×1=−3,=s2−q3⋅s3=1−1×(−3)=4,t0t1t2t3t4=0,=1,=t0−q1⋅t1=0−1×1=−1,=t1−q2⋅t2=1−3×(−1)=4,=t2−q3⋅t3=−1−1×(4)=−5,
因为r4=18=(252,198)且r4=s4⋅a+t4⋅b,故
18=(252,198)=4×252−5×198
最后一个等式将18=(252,198)写成了252, 198的线性组合的形式。◀
最后我们给出扩展欧几里得算法的python语言实现:
class Memo:
def __init__(self):
self.q = dict()
self.s = dict()
self.t = dict()
self.s[0] = 1; self.t[0] = 0
self.s[1] = 0; self.t[1] = 1
def update_q(self, j:int, q_j:int):
assert j >= 1
self.q[j] = q_j
self.calculate_s_t(j)
def calculate_s_t(self, j:int):
if j < 2:
return
self.s[j] = self.s[j-2] - self.q[j-1]*self.s[j-1]
self.t[j] = self.t[j-2] - self.q[j-1]*self.t[j-1]
def get_s_t(self, j:int):
assert j >= 0
return (self.s[j], self.t[j])
def extended_Euclid(a:int, b:int):
assert a >= b >= 0
memo = Memo()
r = a % b
q = a // b
j = 1
memo.update_q(j, q)
while r != 0:
a = b
b = r
r = a % b
q = a // b
j += 1
memo.update_q(j, q)
s, t = memo.get_s_t(j)
return s, t, b
def extended_gcd(a:int, b:int):
assert a >= 0 and b >= 0
if a < b:
a, b = b, a
return extended_Euclid(a, b)
def print_extended_gcd(a, b):
s, t, d = extended_gcd(a, b)
print("gcd({}, {}) = {}".format(a, b, d))
print(f"({s}) * {a} + ({t}) * {b} = {d}")
if __name__ == "__main__":
print_extended_gcd(252, 198)
参考文献:
- 初等数论及其应用(原书第6版): ISBN 978-7-111-48697-8
- 离散数学及其应用(原书第8版): ISBN 978-7-111-63687-8
- 算法概论: ISBN 978-7-302-17939-9