[Python] 体验用欧几里得算法计算最大公约数的过程

背景

我们在小学时,学过最大公约数( Greatest Common Divisor\text{Greatest Common Divisor} Greatest Common Divisor)的知识,但是当时并没有学习比较高效的求最大公约数的方法。使用欧几里得算法( Euclidean Algorithm\text{Euclidean Algorithm} Euclidean Algorithm),可以高效地计算出自然数的最大公约数(两个自然数不能同时为 00 0)。本文带您体验这一过程。

正文

根据定义暴力计算

根据最大公约数的定义,我们可以用暴力的方式进行计算自然数 aa a 和自然数 bb b 的最大公约数( a=0a=0 a=0 和 b=0b=0 b=0 不能同时成立)。对应的 Python\text{Python} Python 代码如下

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

由于所有的自然数都能被 11 1 整除,所以上述代码求出的最大公约数 gcd≥1gcd\ge 1 gcd≥1 总是成立。

观察

如果固定 bb b 的值,让 aa a 变化(也可以固定 aa a,让 bb b 变化,两种方式没有本质区别),我们看一看求出的结果是否有特殊的规律。

b=1b=1 b=1 时

b=1b=1 b=1 时,对任意自然数 aa a, gcd(a,1)=1gcd(a,1)=1 gcd(a,1)=1 显然成立。

b=2b=2 b=2 时

b=2b=2 b=2 时,我们可以列举比较小的 aa a 值所对应的计算结果

看起来 gcd(a,2)gcd(a,2) gcd(a,2) 的值是在 11 1 和 22 2 之间交替出现。解释如下 ⬇️

  • aa a 是偶数时, 2∣22\mid 2 2∣2 并且 2∣a2\mid a 2∣a,所以 gcd(a,2)=2gcd(a,2)=2 gcd(a,2)=2
  • aa a 是奇数时
    • 因为 2∤a2\nmid a 2∤a,所以 gcd(a,2)≠2gcd(a,2)\ne 2 gcd(a,2)=2
    • 那么 gcd(a,2)=1gcd(a,2)=1 gcd(a,2)=1

b=3b=3 b=3 时

b=3b=3 b=3 时,我们可以列举比较小的 aa a 值所对应的计算结果

看起来 gcd(a,3)gcd(a,3) gcd(a,3) 的值会以 3,1,13,1,1 3,1,1 这样周期反复出现。解释如下 ⬇️

  • a≡0(mod3) a\equiv 0 \pmod 3 a≡0(mod3) 时, 3∣33|3 3∣3 并且 3∣a3|a 3∣a,所以 gcd(a,3)=3gcd(a,3)=3 gcd(a,3)=3
  • a≡1(mod3) a\equiv 1 \pmod 3 a≡1(mod3) 时
    • 3∤a3\nmid a 3∤a,所以 gcd(a,3)≠3gcd(a,3)\ne 3 gcd(a,3)=3
    • 2∤32\nmid 3 2∤3,所以 gcd(a,3)≠2gcd(a,3)\ne 2 gcd(a,3)=2
    • 那么 gcd(a,3)=1gcd(a,3)=1 gcd(a,3)=1
  • a≡2(mod3) a\equiv 2 \pmod 3 a≡2(mod3) 时
    • 3∤a3\nmid a 3∤a,所以 gcd(a,3)≠3gcd(a,3)\ne 3 gcd(a,3)=3
    • 2∤32\nmid 3 2∤3,所以 gcd(a,3)≠2gcd(a,3)\ne 2 gcd(a,3)=2
    • 那么 gcd(a,3)=1gcd(a,3)=1 gcd(a,3)=1

b=4b=4 b=4

b=4b=4 b=4 时,我们可以列举比较小的 aa a 值所对应的计算结果

看起来 gcd(a,4)gcd(a,4) gcd(a,4) 的值会以 4,1,2,14,1,2,1 4,1,2,1 这样周期反复出现。解释如下 ⬇️

  • a≡0(mod4) a\equiv 0 \pmod 4 a≡0(mod4) 时, 4∣44|4 4∣4 并且 4∣a4|a 4∣a,所以 gcd(a,4)=4gcd(a,4)=4 gcd(a,4)=4
  • a≡1(mod4) a\equiv 1 \pmod 4 a≡1(mod4) 时
    • 4∤a4\nmid a 4∤a,所以 gcd(a,4)≠4gcd(a,4)\ne 4 gcd(a,4)=4
    • 3∤43\nmid 4 3∤4,所以 gcd(a,4)≠3gcd(a,4)\ne 3 gcd(a,4)=3
    • 2∤a2\nmid a 2∤a,所以 gcd(a,4)≠2gcd(a,4)\ne 2 gcd(a,4)=2
    • 那么 gcd(a,3)=1gcd(a,3)=1 gcd(a,3)=1
  • a≡2(mod3) a\equiv 2 \pmod 3 a≡2(mod3) 时
    • 4∤a4\nmid a 4∤a,所以 gcd(a,4)≠4gcd(a,4)\ne 4 gcd(a,4)=4
    • 3∤43\nmid 4 3∤4,所以 gcd(a,4)≠3gcd(a,4)\ne 3 gcd(a,4)=3
    • 2∣42\mid 4 2∣4,并且 2∣a2\mid a 2∣a,所以 gcd(a,4)=2gcd(a,4)= 2 gcd(a,4)=2
  • a≡3(mod4) a\equiv 3 \pmod 4 a≡3(mod4) 时
    • 4∤a4\nmid a 4∤a,所以 gcd(a,4)≠4gcd(a,4)\ne 4 gcd(a,4)=4
    • 3∤43\nmid 4 3∤4,所以 gcd(a,4)≠3gcd(a,4)\ne 3 gcd(a,4)=3
    • 2∤a2\nmid a 2∤a,所以 gcd(a,4)≠2gcd(a,4)\ne 2 gcd(a,4)=2
    • 那么 gcd(a,4)=1gcd(a,4)=1 gcd(a,4)=1

一般的情形

分析了以上几个值比较小的 bb b 之后,我们尝试从中找规律。假设 a≥ba\ge b a≥b 成立,看起来以下等式是成立的
gcd(a,b)=gcd(a−b,b)gcd(a,b)=gcd(a-b,b) gcd(a,b)=gcd(a−b,b)

我们看看能否证明它。为了方便描述,我们记 gcd(a,b)=ggcd(a,b)=g gcd(a,b)=g, gcd(a−b,b)=g′gcd(a-b,b)=g' gcd(a−b,b)=g′。

我们的目标是证明 g=g′g=g' g=g′ (或者找到两者不相等的反例)。

gcd(a,b)=ggcd(a,b)=g gcd(a,b)=g,根据最大公约数的定义,以下两者成立

  • g∣ag\mid a g∣a
  • g∣bg\mid b g∣b

所以 g∣(a−b)g\mid(a-b) g∣(a−b)。 gg g 既是 a−ba-b a−b 的约数,又是 bb b 的约数,那么 gg g 是 a−ba-b a−b 和 bb b 的公约数 。按照最大公约数 的定义, g≤g′g\le g' g≤g′ 成立(因为 g′g' g′ 是 a−ba-b a−b 和 bb b 的最大公约数)。

反过来看, gcd(a−b,b)=g′gcd(a-b,b)=g' gcd(a−b,b)=g′,根据最大公约数的定义,以下两者成立

  • g′∣(a−b)g'\mid (a-b) g′∣(a−b)
  • g′∣bg' \mid b g′∣b

所以 g′∣((a−b)+b)g' \mid ((a-b)+b) g′∣((a−b)+b),即 g′∣ag' \mid a g′∣a。 g′g' g′ 既是 bb b 的约数,又是 aa a 的约数,那么 g′g' g′ 是 aa a 和 bb b 的公约数 。按照最大公约数 的定义, g′≤gg'\le g g′≤g 成立(因为 gg g 是 aa a 和 bb b 的最大公约数)。

既然 g≤g′g\le g' g≤g′ 和 g′≤gg'\le g g′≤g 同时成立,那么 g=g′g=g' g=g′ 成立。也就是说,对正整数 a,ba,b a,b 而言,当 a≥ba\ge b a≥b 成立时,以下等式总是成立。
gcd(a,b)=gcd(a−b,b)gcd(a,b)=gcd(a-b,b) gcd(a,b)=gcd(a−b,b)

在此基础上,可以推出 ⬇️ ( a,ba,b a,b 都是正整数)
gcd(a,b)=gcd(a−b,b)gcd(a,b)=gcd(a-b,b) gcd(a,b)=gcd(a−b,b)
=gcd(a−2b,b)=gcd(a-2b,b) =gcd(a−2b,b)
=gcd(a−3b,b)=gcd(a-3b,b) =gcd(a−3b,b)
⋯\cdots
=gcd(a  mod  b,b)=gcd(a \bmod b,b) =gcd(amodb,b)

所以对任意正整数 a,ba,b a,b 而言,以下等式成立
gcd(a,b)=gcd(a  mod  b,b)gcd(a,b)=gcd(a \bmod b,b) gcd(a,b)=gcd(amodb,b)

这就是欧几里得算法 ( Euclidean Algorithm\text{Euclidean Algorithm} Euclidean Algorithm) 的核心思想。

Python\text{Python} Python 程序实现欧几里得算法

5555 55 和 3434 34 为例,我们利用欧几里得算法来计算两者的最大公约数,具体过程如下 ⬇️
gcd(55,34)=gcd(55  mod  34,34)gcd(55,34)=gcd(55 \bmod 34, 34) gcd(55,34)=gcd(55mod34,34)
=gcd(21,34)=gcd(34,21)=gcd(21, 34)=gcd(34, 21) =gcd(21,34)=gcd(34,21)
=gcd(34  mod  21,21)=gcd(34 \bmod 21, 21) =gcd(34mod21,21)
=gcd(13,21)=gcd(21,13)=gcd(13, 21)=gcd(21, 13) =gcd(13,21)=gcd(21,13)
=gcd(21  mod  13,13)=gcd(21 \bmod 13, 13) =gcd(21mod13,13)
=gcd(8,13)=gcd(13,8)=gcd(8, 13)=gcd(13,8) =gcd(8,13)=gcd(13,8)
=gcd(13  mod  8,8)=gcd(13 \bmod 8, 8) =gcd(13mod8,8)
=gcd(5,8)=gcd(8,5)=gcd(5, 8)=gcd(8, 5) =gcd(5,8)=gcd(8,5)
=gcd(8  mod  5,5)=gcd(8 \bmod 5, 5) =gcd(8mod5,5)
=gcd(3,5)=gcd(5,3)=gcd(3, 5)=gcd(5, 3) =gcd(3,5)=gcd(5,3)
=gcd(5  mod  3,3)=gcd(5 \bmod 3, 3) =gcd(5mod3,3)
=gcd(2,3)=gcd(3,2)=gcd(2, 3)=gcd(3,2) =gcd(2,3)=gcd(3,2)
=gcd(3  mod  2,2)=gcd(3 \bmod 2, 2) =gcd(3mod2,2)
=gcd(1,2)=gcd(2,1)=gcd(1, 2)=gcd(2,1) =gcd(1,2)=gcd(2,1)
=gcd(2  mod  1,1)=gcd(2 \bmod 1, 1) =gcd(2mod1,1)
=gcd(0,1)=gcd(0, 1) =gcd(0,1)
=1=1 =1

用代码来实现,可以这样写 ⬇️

python 复制代码
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)

但我们用这段代码计算 gcd(55,34)gcd(55,34) gcd(55,34) 时,会出现这样的函数调用栈 ⬇️ (栈底在上方,栈顶在下方)

参数 aa a 的值 参数 bb b 的值 函数调用的情况
5555 55 3434 34 gcd(55,34)\text{gcd(55,34)} gcd(55,34)
3434 34 2121 21 gcd(34,21)\text{gcd(34,21)} gcd(34,21)
2121 21 1313 13 gcd(21,13)\text{gcd(21,13)} gcd(21,13)
1313 13 88 8 gcd(13,8)\text{gcd(13,8)} gcd(13,8)
88 8 55 5 gcd(8,5)\text{gcd(8,5)} gcd(8,5)
55 5 33 3 gcd(5,3)\text{gcd(5,3)} gcd(5,3)
33 3 22 2 gcd(3,2)\text{gcd(3,2)} gcd(3,2)
22 2 11 1 gcd(2,1)\text{gcd(2,1)} gcd(2,1)
11 1 00 0 gcd(1,0)\text{gcd(1,0)} gcd(1,0)

验证

我们可以用如下的代码来简单验证欧几里得算法的计算结果

python 复制代码
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):
    # plot_gcd_results 里的代码是 trae 帮我生成的,我做了些小调整
    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)gcd(a,4) gcd(a,4) 的值,以 44 4 为周期的(值是 4,1,2,1,⋯4,1,2,1,\cdots 4,1,2,1,⋯),这与我们前面分析的结果是一致的。

可以将第 2424 24 行的 bb b 变量改为其他值来进行验证,这里就不赘述了。

相关推荐
FreakStudio6 小时前
W55MH32L-EVB 上手测评:硬件 TCP/IP 加持的以太网单片机,MicroPython 零门槛开发
python·单片机·嵌入式·大学生·面向对象·并行计算·电子diy·电子计算机
用户0332126663677 小时前
使用 Python 从零创建 Word 文档
python
Csvn12 小时前
Python 两大经典坑点 —— 可变默认参数 & 闭包延迟绑定
后端·python
曲幽13 小时前
别再用网页翻译看源码了!你的私人翻译神器LibreTranslate,部署避坑指南来了
python·docker·web·pot·translate·libretranslate·arogstranslate
用户5569188175314 小时前
#从脚本到独立程序:Python + Playwright 批量抓取的完整踩坑记录
python·自动化运维
兵慌码乱1 天前
基于 MediaPipe 与 PySide2 的手势交互音乐控制系统实现:轻量化视觉交互全流程解析
python·opencv·计算机视觉·人机交互·手势识别·mediapipe·pyside2
luckdewei1 天前
FastAPI 资产管理系统实战:复杂 ORM 关联、Alembic 迁移与 N+1 查询优化
python
aqi002 天前
15天学会AI应用开发(八)使用向量数据库实现RAG功能
人工智能·python·大模型·ai编程·ai应用