欧几里得算法
欧几里得算法通过反复做带余除法求两个整数的最大公约数。它不必先分解质因数,因此即使整数很大,也可直接从一串余数读出答案。对整数 且 ,按除法定理写成 ,其中 ;核心等式为
余数不会改变公约数
任何同时整除 的整数也整除 ;反过来,任何同时整除 的整数也整除 。因此两对数有完全相同的公约数,其最大正公约数相同。每次把 换成 时,新的第二个非负数严格小于原来的 ,不可能无限下降,算法终会遇到零。最后一对是 ,答案便是 。
一次完整的计算
求 : 最后一个非零余数是 18。为看清它不只是“算出来的因子”,可回代为 这给出了贝祖等式的一组系数,并说明 252 与 198 的任何公约数都整除 18;反向检查 、,所以 18 确实是最大公约数。回代也是扩展欧几里得算法求模逆元的起点。
零、负数与终止速度
本篇约定 为非负数,且两数不同时为零。于是 ,; 不在此定义之内。负号不影响公约数,例如 ,实现时可先取绝对值。
对 ,看连续两次除法。第一次余数若小于 ,第二个参数已经减半;若第一次余数至少为 ,下一次余数小于 。因此至多每两步,较小参数就减至一半以下,除法次数为 。这与试除所有可能因子相比,解释了算法在大整数上的效率。
可运行的实现
函数接收任意两个不全为零的 Python 整数,返回非负的最大公约数;它不修改输入,也不依赖外部库。
def euclidean_gcd(a, b):
if a == 0 and b == 0:
raise ValueError("gcd(0, 0) is undefined here")
a, b = abs(a), abs(b)
while b:
a, b = b, a % b
return a
print(euclidean_gcd(252, 198)) # 18
print(euclidean_gcd(-252, 198)) # 18
print(euclidean_gcd(0, 7)) # 7
只保存当前两个整数,辅助空间为 个整数;按整数位数计量时,大整数除法自身也有成本,不能把“对数次除法”误说成固定机器时间。算法与线性同余方程衔接:先求 ,就能判断 是否可能有解。
从公约数求模逆元
算法若算出 ,回代到 后, 就是 在模 下的模逆元。例如 所以 。倒过来代入,,于是 。核验 。这里的“逆元”不是实数倒数 ,而是一个整数剩余类;若最大公约数大于 1,这样的整数就不存在。
可把同一过程用于约分。分数 先用算法得公约数 18,再分子分母同除以 18,得到 。反过来,若要自己核算代码,对 依次得到余数 ,答案 6;把输入交换成 时,最初一步余数为 18,然后进入完全相同的过程,答案不应改变。
“互素就有逆元”也有反向。若某个整数 满足 ,则存在整数 使 。任何同时整除 的正整数也必整除右侧的 1,因而只能是 1;即 。所以先运行欧几里得算法判互素,再回代求逆元,不只是常用手法,而是可解性的完整判据。例如 在模 14 下没有逆元,因为算法第一步就会得到 。这与线性同余方程中能否直接约去系数的问题完全一致。
从程序角度核对正确性也只需抓住一个不变量:每次进入 while 循环时,当前 a, b 的最大公约数始终等于最初输入的最大公约数;赋值 a, b = b, a % b 正是前面证明的等式。当 b 变成零,a 就是那个一直未改变的公约数。这个说明比只看几个输出样例更能解释代码对所有合法输入都成立。