游乐游手机版
首页/编程语言/文章详情

Python欧几里得算法计算最大公约数过程详解

时间:2026-06-27 06:40
欧几里得算法(辗转相除法)通过反复用较小数除较大数取余,核心公式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 里可以看到第一章到第六章的内容)
来源:https://juejin.cn/post/7654045327083995155
上一篇C#接口设计进阶技巧 掌握关键点写出更稳定代码 下一篇C#接口设计完整指南:从安装配置到调试工程实践
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
详解如何使用Apache服务器进行防盗链配置步骤
编程语言 · 2026-06-30

详解如何使用Apache服务器进行防盗链配置步骤

Apache使用mod_rewrite模块实现图片防盗链,通过 htaccess文件配置Rewrite规则,检查HTTP_REFERER来源,若非本站域名且来源不为空,则对jpg等常见图片格式返回403禁止访问。此方法能有效阻止大多数盗链行为。

Filebeat日志转发实现步骤详解
编程语言 · 2026-06-30

Filebeat日志转发实现步骤详解

Filebeat通过配置输入源读取日志,输出目标转发至Elasticsearch或Logstash。安装后编辑filebeat yml文件,指定日志路径和输出地址。支持直接转发或经Logstash处理。通过systemctl启动并验证数据到达,可选SSL加密和多行日志合并配置。

手把手教你如何在CentOS上使用PhpStorm构建项目的详细步骤
编程语言 · 2026-06-30

手把手教你如何在CentOS上使用PhpStorm构建项目的详细步骤

在CentOS上使用PHPStorm构建项目需先准备环境:安装Java、PHP及扩展、Nginx、MariaDB并开放端口。然后安装配置PHPStorm,设置SSH解释器与Web服务器映射。导入或创建项目后安装Composer依赖,调整php ini。配置SFTP部署并同步文件,最后设置Xdebug进行调试运行。

CentOS下GitLab集成其他工具的详细配置方法与完整指南
编程语言 · 2026-06-30

CentOS下GitLab集成其他工具的详细配置方法与完整指南

在CentOS平台中,GitLab通过Webhooks、API与CI CD配置,深度集成Jenkins、SonarQube、Docker及Slack,构建代码托管、自动构建、质量检查与协作通知的自动化链路,覆盖开发、测试、部署全流程,实现从提交到上线的自动化,大幅提升团队效率与交付质量,推动开发运维一体化。

CentOS设置Node.js定时任务的方法
编程语言 · 2026-06-30

CentOS设置Node.js定时任务的方法

在CentOS上为Node js应用设置定时任务常用两种方案:systemd适合长期运行服务,需创建服务文件并配置开机自启;cron更灵活,适合定期唤醒任务,通过编辑crontab添加时间计划和执行命令。两种方法均需指定Node js路径和应用入口。