↓ 跳过正文

炼金笔记:辗转相除法是怎么算出最大公因数的?

·1051 字·3 分钟

欣赏代码
#

在深入学习之前,先来欣赏一下辗转相除法的优美代码:

int gcd(int a, int b){
    return b == 0 ? a : gcd(b, a % b);
}

这个算法太优美了,函数内仅仅一行,就可以计算出任意两个数的最大公因数,而且非常易于记忆。这也就成了大部分高校考察编程水平的一个试金石。

那么代价是什么?

显而易见的代码背后,往往隐藏着深邃的数学原理。

数学推导
#

定义 \(a\) , \(b\) 为待求公因数的一对整数,\(d\) 为其公因数(目前不一定是最大公因数,在计算中会逐步确认)。

我们可以引入两个参数 \(k\)、\(r\),使得:

$$a = kb + r \tag{1}$$

显然,如果对 \(k\) 、\(r\) 不做限制的话,其取值有无限多个。为了便于在编程中使用int类型变量,于是在此规定一下 \(k\)、\(r\) 都得是整数,并且 \(k\) 得尽可能大。于是取值就唯一确定了。

既然唯一确定了,我们很自然地就想用 \(a\) 和 \(b\) 把 \(k\)、\(r\)表示出来,如你所见:

$$k=\lfloor \frac{a}{b} \rfloor \\ r=a\bmod b$$

或者用更习惯的C语言写法:k=a/b; r=a%b; (以上变量类型都是int)

所以你发现前面我废话了这么多其实就是为了引入两数相除的商k和余数r

为了便于书写,我这里规定 \(\mid\) 符号为整除符号,比如 \(3\mid12\) 表示12可以被3整除; \(7\mid21\) 表示21可以被7整除。其实不是我擅自规定的,数学家也这么用。

根据定义:

$$d \mid a \\ d \mid b \Rightarrow d \mid kb$$

根据式(1):

$$d \mid kb+r$$

不知道看到这里有没有发现一点端倪。中学里面应该了解过,如果一个数能被整除,那么加上另一个能被整除的数,两数的和也是能被整除的。

所以可以推导出:

$$d \mid r$$

我们把三个并在一起看:

$$d\mid a \\ d \mid b \\ d \mid r$$

把b、r当成新的待求公因数的一对整数,可以继续推导得出:

$$d\mid b \\ d \mid r \\ d \mid b \bmod r$$

所以这个公式就很显然了:

$$\gcd(a,b)=\gcd(b,a\bmod b)$$

在程序中,这个递归会一直运行,直到 \( a \bmod b = 0\) 的时候,递归终止,返回最后的 \(d\) 。那么问题来了,为什么最后的 \(d\) 就是最大公因数,而不是其他随便一个公因数呢?

为什么是最大公因数?
#

假设辗转相除法进行到最后一步(\(a'\)、\(b'\) 都是上一次传递下来的值):

$$a'=kb'+0$$

也就是说:

$$a'=kb'$$

根据整除的定义:

$$b' \mid a'$$

很显然,\(a'\)、\(b'\) 的最大公因数就是 \(b'\) 本身。

接下来只要证明 \(a\)、\(b\) 和 \(a'\)、\(b'\) 的公因数的集合是一样的即可。

我们之前推导出:

$$\left.\begin{matrix} d \mid a \\ d\mid b \end{matrix} \right\} \Rightarrow d \mid r $$

现在返回来推导:

$$\left.\begin{matrix} d \mid b \Rightarrow d \mid kb \\ d\mid r \end{matrix} \right\} \Rightarrow d \mid kb+r \Rightarrow d\mid a$$

所以这两个互为充要条件:

$$d\mid a,\ d\mid b \iff d \mid b,\ d\mid r$$

换句话说,在递归过程中,公因数的集合是不变的。不会扩充也不会缩减。

所以最后一步算出来的最大公因数 \(b'\) 就是整个的最大公因数!

图解
#

Euclidean algorithm 1071 462.gif
算法图解,来自于Wikipedia
摇月
作者
摇月
二十一世纪的赛博魔法师

相关文章