辗转相除法(欧几里得算法)探微
引言
-
曾记否,我们刚刚开始学习编程语言的时候,无论是python,C语言还是其他的编程语言的时候,一定会遇到一道练习题,常常在课堂上或者老师布置的课后习题里遇到它,–> “求两个数的最大公约数” ,有很多方法。但我记得当时我匆匆的找了个最常见的辗转相除法就将它应付了,因为代码不长,写起来也快.现在回想起来,当时也只是浅尝辄止,并没有什么很深的理解,以至于现在你让我讲出什么玩意出来也是不行的。
-
最近,我又瞥见了它的身影,但是与之前不同的是,再次与它相见,我想这次我希望能够真正意义上的理解它。所以,我想写一篇文章来细细的谈谈这道“简单”的题目,对于辗转相除法,一个非常有趣的答案是:它同时具备 “极其简单” 和** “极其深刻” ** 两个特点。
-
站在使用者如程序员、普通学生的角度不难,它是一个高效、可靠、易于编码的工具。
-
对于学习者,理解其原理是初等数论中的一个重要门槛。它可能是你第一次接触到如何将一个直观的操作(除法)与一个抽象的概念(最大公约数)通过严密的逻辑连接起来。
所以,如果你想从原理层面理解它的话,可以来看一看这篇文章。(如果只是运用,可以找更简洁的其他优秀博主的文章)
当然,我也是站在学习者的角度来看的,如果有错误的地方欢迎指正。
一、最大公约数(GCD)
在深入原理前,需先清晰定义 GCD:对于两个整数 a和 b(不妨设 a>b≥0),它们的最大公约数 gcd(a,b) 是满足以下两个条件的整数 q
整除性:q∣a 且 q∣b(即 q 是 a 和 b的公共约数);
最大性:对任意其他公共约数 q ′,都有q′ ∣q(即 q是所有公共约数中最大的)
- 例如,gcd(18,12)=6,因为 6 是能同时整除 18 和 12 的最大整数。
这里,可以转换一下 q|a <=> a = c1q;
q|b <=> b = c2q;
二、核心定理
gcd(a,b)=gcd(b,amodb)
- 即 a 和 b 的最大公约数 = b 和 amodb 的最大公约数 ;
这是辗转相除法的 “灵魂”—— 它将求 gcd(a,b) 的问题,转化为求更小的数 gcd(b,r) 的问题(其中 r=amodb,即 a 除以 b 的余数,0≤r<b)
要理解这个定理,需分两步证明:
- 证明
gcd(a,b)∣gcd(b,r)
(即 a 和 b 的最大公约数,也是 b 和 r 的约数); - 证明
gcd(b,r)∣gcd(a,b)
(即 b 和 r 的最大公约数,也是 a 和 b 的约数); - 由 “两个数互相整除则相等”,可得 gcd(a,b)=gcd(b,r)。
第一步:证明 gcd(a,b)∣gcd(b,r)
设 q=gcd(a,b),
根据 GCD 的定义,有:
q∣a(即存在整数 k1,使 a=q⋅k1);
q∣b(即存在整数 k2 ,使 b=q⋅k2)。
又因为 r=amodb,根据 “除法算式” 的定义,a 可表示为:a=b⋅c+r(其中 c 是商,整数;0≤r<b)。
将 a=q⋅k1 和 b=⋅k2 代入上式,整理得:r=a−b⋅c=q⋅k1 −q⋅k2 ⋅c=q⋅(k1 −k2⋅c)。这说明 q∣r(因为 k1−k2 ⋅c 是整数)。
此时,q同时满足:q∣b(已知);q∣r(刚证明)。
即 q 是 b和 r 的公共约数。
而gcd(b,r)是 b 和 r 的最大公约数,根据 GCD 的 “最大性”所有公共约数都能整除最大公约数,
因此:q∣gcd(b,r),即 gcd(a,b)∣gcd(b,r)。
第二步:证明 gcd(b,r)∣gcd(a,b)
设 q=gcd(b,r),
根据 GCD 的定义,有:
q∣b(即存在整数 m1,使 b=q⋅m1);
q∣r(即存在整数 m2 ,使 r=q⋅m2)。
再结合除法算式 a =b⋅c+r,将 b=q⋅m1和 r=q⋅m2 代入,得:
a=q⋅m1⋅c+q⋅m 2 =q⋅(m1⋅c+m 2 )。
这说明 q∣a(因为 m1 ⋅q+m2 是整数)。
此时,q同时满足:q∣a(刚证明);q∣b(已知)。
即 q 是 a 和 b 的公共约数。
同理,gcd(a,b) 是 a 和 b 的最大公约数,因此:q∣gcd(a,b),即 gcd(b,r)∣gcd(a,b)。
结论:gcd(a,b)=gcd(b,r)
由第一步 gcd(a,b)∣gcd(b,r) 和第二步 gcd(b,r)∣gcd(a,b),
根据 “整数整除的对称性”(若 x∣y 且 y∣x,则 x=y,且 x,y 均为正整数)
可得:gcd(a,b)=gcd(b,amodb)。
三、算法的终止性:余数逐步缩小至 0
有了核心定理,辗转相除法的 “迭代过程” 就很自然了:
- 每次将问题规模缩小(用 b 代替 a,用 (r = a mod b) 代替 b),由于余数 r 满足 (0 < r < b),每次迭代后,第二个数都会严格减小(从 b 到 r,再到下一次的余数,直到余数为 0)。
- 当余数为 0 时,根据 GCD 的定义,(gcd(x, 0) = x)(因为任何数都能整除 0,且 x 的最大约数是它本身)。
此时,当前的第一个数就是最初两个数的 GCD。
四.实例
都是理论知识也会枯燥,来看一下实例
gcd(48,18) -> 48 / 18 = 2 ......12;
<=> gcd(18,12) ->18/12=1......6;
<=> gcd(12,6) ->12/6 = 0;
<=> gcd(6,0) -> == 6;
#include <iostream>
#include <algorithm> // 用于swap函数
using namespace std;// 简洁的循环版本(推荐)
int gcd(int a, int b) {while (b != 0) {int remainder = a % b;a = b;b = remainder;}return a;
}// 递归版本
int gcd_recursive(int a, int b) {return b == 0 ? a : gcd_recursive(b, a % b);
}// 处理负数的版本
int gcd_with_negative(int a, int b) {a = abs(a);b = abs(b);while (b != 0) {int temp = b;b = a % b;a = temp;}return a;
}int main() {int num1, num2;cout << "请输入两个整数: ";cin >> num1 >> num2;// 使用循环版本cout << "循环版本 - " << num1 << " 和 " << num2 << " 的最大公约数是: " << gcd(num1, num2) << endl;// 使用递归版本cout << "递归版本 - " << num1 << " 和 " << num2 << " 的最大公约数是: " << gcd_recursive(num1, num2) << endl;// 处理负数的版本cout << "处理负数版本 - " << num1 << " 和 " << num2 << " 的最大公约数是: " << gcd_with_negative(num1, num2) << endl;// 测试多个例子cout << "\n测试例子:" << endl;cout << "gcd(48, 18) = " << gcd(48, 18) << endl; // 应该输出6cout << "gcd(56, 42) = " << gcd(56, 42) << endl; // 应该输出14cout << "gcd(17, 13) = " << gcd(17, 13) << endl; // 应该输出1return 0;
}
五、本质总结
用 “整除传递性” 压缩问题规模辗转相除法的数学本质,是利用 “公共约数的传递性” 和 “余数的有界性”:
- 公共约数的传递性:a 和 b 的公共约数,与 b 和 r 的公共约数完全相同,因此最大公约数也相同;
- 余数的有界性:每次余数都比前一个除数小,最终必然缩小到 0,此时算法终止,直接得到 GCD。
这一原理不仅保证了算法的正确性,还决定了其极高的效率(时间复杂度为 (O(log min(a, b))))使其成为数论中最基础、最经典的算法之一。