Python欧几里得算法计算最大公约数过程详解
欧几里得算法(辗转相除法)通过反复用较小数除较大数取余,核心公式gcd(a,b)=gcd(b,amodb),快速计算两个自然数的最大公约数,直至余数为零。Python递归实现代码极简,效率高;图形验证显示结果呈周期性规律。该算法历史悠久,广泛应用于数论与密码学。
首先明确一个核心观点:求解最大公约数(GCD)是小学数学中的经典问题,但当时多数人只记住了定义,并未掌握真正高效的算法。欧几里得算法(又称辗转相除法)正是处理这一问题的利器——它能快速计算出两个自然数的最大公约数,前提是这两个数不能同时为零。本文将带你一步步透彻理解这一算法。
从暴力枚举开始
我们先从最直接的暴力方法入手。按照定义硬算,思路非常简单:从较小的数开始逐一递减,直到找到能同时整除两个数的那个最大整数。逻辑上没有问题,但效率实在不敢恭维。
对应的Python代码如下:
def brute_force_gcd(a, b):
if a == 0 and b == 0:
raise ValueError("a and b cannot be both 0")
if a == 0:
return b
if b == 0:
return a
candidate = min(a, b)
while True:
if a % candidate == 0 and b % candidate == 0:
return candidate
candidate -= 1
因为所有自然数都能被1整除,所以计算出的最大公约数至少为1,这一点没有问题。
寻找规律:固定一个数,观察结果
接下来,我们来做一件数学家经常做的事:寻找规律。固定其中一个数 b 不变,让 a 变化(反过来也一样,没有区别),看看最大公约数的结果是否存在某种模式。
b = 1 时
当 b=1 时,无论 a 取何值,gcd(a,1) 都等于1,显然成立。
b = 2 时
当 b=2 时,我们来列举几个 a 的值:

规律非常明显——结果要么是1,要么是2,交替出现。为什么呢?
当 a 是偶数时,2能整除 a 和 2,因此 gcd=2
当 a 是奇数时,2不能整除 a,最大公约数不可能等于2,所以 gcd=1
b = 3 时
同理,我们来看 b=3 的情况:

周期为3,1,1,反复循环。原因如下:
a ≡ 0 (mod 3) 时,3能同时整除 a 和 3,gcd=3
a ≡ 1 (mod 3) 时,3不能整除 a,2也不能整除3,因此 gcd=1
a ≡ 2 (mod 3) 时,同理可得 gcd=1
b = 4 时
再来看 b=4:

结果是4,1,2,1,周期为4。解释起来也不复杂:
a ≡ 0 (mod 4) → gcd=4
a ≡ 1 (mod 4) → 4不能整除a,3不能整除4,2不能整除a,只能等于1
a ≡ 2 (mod 4) → 4不能整除a,但2可以同时整除a和4,所以 gcd=2
a ≡ 3 (mod 4) → 全都不能整除,gcd=1
推广到一般情形
观察完这几个小例子后,我们尝试提炼出一个更通用的规律。假设 a ≥ b,下面这个等式似乎成立:
gcd(a, b) = gcd(a - b, b)
我们来尝试证明它。记 gcd(a,b)=g,gcd(a-b,b)=g'。
先从左边推导到右边:
因为 g|a 且 g|b,所以 g|(a-b)。g 既是 a-b 的约数,又是 b 的约数,那么它一定是 a-b 和 b 的一个公约数。既然是公约数,它肯定小于或等于它们最大的公约数,因此 g ≤ g'。
再从右边推导到左边:
因为 g'|(a-b) 且 g'|b,所以 g'|((a-b)+b),即 g'|a。g' 既是 a 的约数,又是 b 的约数,那么 g' ≤ g。
两边结合起来:g ≤ g' 且 g' ≤ g,所以 g 必然等于 g'。证毕。
在此基础上,我们可以一直减下去:
gcd(a,b) = gcd(a-b,b) = gcd(a-2b,b) = ... = gcd(a mod b, b)
这就是欧几里得算法的核心思想。
用Python实现欧几里得算法
以55和34为例,我们计算一下:
gcd(55,34) = gcd(55 mod 34, 34) = gcd(21,34)
= gcd(34 mod 21, 21) = gcd(13,21)
= gcd(21 mod 13, 13) = gcd(8,13)
= gcd(13 mod 8, 8) = gcd(5,8)
= gcd(8 mod 5, 5) = gcd(3,5)
= gcd(5 mod 3, 3) = gcd(2,3)
= gcd(3 mod 2, 2) = gcd(1,2)
= gcd(2 mod 1, 1) = gcd(0,1)
= 1
翻译成代码,简单到令人难以置信:
def gcd(a, b):
if (a, b) == (0, 0):
raise ValueError("a and b cannot be both 0")
if b == 0:
return a
return gcd(b, a % b)
为了直观地观察函数调用的过程,我在 trae 的帮助下加了一个 show_stack 方法——不必纠结它的实现细节,只要能看清调用栈即可:
import inspect
def gcd(a, b):
if (a, b) == (0, 0):
raise ValueError("a and b cannot be both 0")
if b == 0:
show_stack()
return a
return gcd(b, a % b)
def show_stack():
for frame_info in inspect.stack()[:][1:]:
frame = frame_info.frame
args = []
for name in frame.f_code.co_varnames[:frame.f_code.co_argcount]:
if name in frame.f_locals:
args.append(f"{name}={frame.f_locals[name]}")
print(f"{frame_info.function}({', '.join(args)})")
if __name__ == "__main__":
gcd(55, 34)
保存为 calc_gcd.py,运行 python3 calc_gcd.py,输出如下:
gcd(a=1, b=0)
gcd(a=2, b=1)
gcd(a=3, b=2)
gcd(a=5, b=3)
gcd(a=8, b=5)
gcd(a=13, b=8)
gcd(a=21, b=13)
gcd(a=34, b=21)
gcd(a=55, b=34)
()
看到没有?这些调用顺序倒过来,就是完整的计算路径。
验证一下
我们可以通过画图来验证。下面这段代码会调用欧几里得算法并生成一张图表:
import matplotlib.pyplot as plt
def gcd(a, b):
if (a, b) == (0, 0):
raise ValueError("a and b cannot be both 0")
if b == 0:
return a
return gcd(b, a % b)
def plot_gcd_results(nums, gcd_results, b):
plt.figure(figsize=(12, 8))
plt.bar(nums, gcd_results, color='skyblue', edgecolor='black')
plt.xlabel('a', fontsize=12)
plt.ylabel('GCD(a, %d)' % b, fontsize=12)
plt.title('GCD(a, %d) for a from %d to %d' % (b, nums[0], nums[-1]), fontsize=14)
plt.xticks(nums)
plt.yticks(range(max(gcd_results) + 1))
plt.grid(axis='y', linestyle='--')
plt.tight_layout()
plt.show()
if __name__ == "__main__":
b = 4
nums = range(5 * b + 1)
gcd_results = [gcd(num, b) for num in nums]
plot_gcd_results(nums, gcd_results, b)
运行结果如下:

可以看到,gcd(a,4) 的值确实以4为周期反复出现(4,1,2,1...),与前面的分析完全一致。感兴趣的话,也可以把 b 改成其他数字试试,这里就不再一一展示了。

参考资料
- A Friendly Introduction to Number Theory 中的第五章(在 Chapter 1~6 里可以看到第一章到第六章的内容)
游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。
同类文章
AWS RDS 数据库配置入门与基础操作指南
本文介绍了AWSRDS的基本概念与核心价值,即提供托管式关系数据库服务,简化运维。详细阐述了创建RDS实例的关键配置步骤,包括引擎选择、实例规格、存储与网络设置。最后,指导读者如何通过多种方式安全连接至数据库实例,并开始进行数据操作,为后续应用开发奠定基础。
PHP MVC中AJAX请求无法调用控制器方法的原因与解决方案
PHPMVC中AJAX请求返回整页HTML的常见原因是控制器方法未正确输出响应或未终止执行,导致框架渲染视图。解决方法是在控制器中设置JSON响应头、输出数据后调用exit()明确终止,同时前端使用小写url和dataType: "json "。
Go语言手动构造rsa.PublicKey:正确初始化大整数模数N完整指南
手动构造RSA公钥时,模数N为*big Int类型,不能直接使用超长十进制字面量,需通过SetString或UnmarshalText方法解析字符串。公钥指数E可直接赋值,推荐65537。生产环境应使用rsa GenerateKey生成密钥对,避免手动构造引发的安全和格式错误。
Go语言实现HTTP定时轮询监控多URL响应时间与状态检测
使用Go语言实现HTTP定时轮询监控,通过按行分割与Tab解析URL列表,避免闭包陷阱和nil指针,每个URL启动独立ticker安全并发请求,并配置超时控制与资源关闭,确保响应时间与状态码准确检测。
Tkinter中Label标签在主循环动态更新的正确方法
在Tkinter中正确动态更新标签的方法:将标签组件的textvariable参数绑定到一个StringVar变量,然后通过调用该变量的 set()方法更新其值,界面会自动刷新。这样避免直接修改text属性或调用update()。此做法实现数据与界面的解耦,代码更简洁,响应更及时,避免手动同步的闪烁,推荐做法。
- 热门数据榜
相关攻略
2026-07-10 06:41
2026-07-10 06:40
2026-07-10 06:39
2026-07-10 06:39
2026-07-10 06:39
2026-07-10 06:39
2026-07-10 06:39
2026-07-10 06:39
热门教程
- 游戏攻略
- 安卓教程
- 苹果教程
- 电脑教程

