跳转至

欧几里得算法

又叫辗转相除法,用于求两数的最大公约数(Greatest common divisor)和最小公倍数(Least common multiple)。

引理

不妨设 \(a > b\),这个算法的基础是

\[ \gcd(a,b)=\gcd(b, a \bmod b) \]

由于 \(a > b\),假设

\[ a=bq+(a \bmod b), q \in \mathbb{Z} \]

\(a\)\(b\) 的公约数为 \(c\),根据 整除 的性质有

\[ c \mid a \wedge c \mid b \Longrightarrow c \mid (a-bq) \]

这说明 \(a\)\(b\) 的所有公约数也是 \(b\)\(a \bmod b\) 的公约数。

反过来,设 \(b\)\(a \bmod b\) 的公约数为 \(c\),根据 整除 的性质也有

\[ c \mid b \wedge c \mid (a \bmod b) \Longrightarrow c \mid (bq+(a \bmod b)) \]

这说明 \(b\)\(a \bmod b\) 的所有公约数也是 \(a\)\(b\) 的公约数。

所以,\(a, b\) 的公约数和 \(b,(a \bmod b)\) 的公约数都是一样的,那最大公约数也是一样。

递归实现

利用上面的性质不断递归,直到 \(b\) 变成 \(0\)。这时候,上一轮 \(a \bmod b = 0\),即 \(b \mid a\),最大公约数肯定是 \(b\)(这一轮的 \(a\))。

暂时不考虑负数。

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

这个函数不要求 \(a>b\),如果 \(a<b\) 的话,就会递归调用 \(\gcd(b,a)\)

迭代实现

int gcd(int a, int b)
{
    while (b != 0)
    {
        int tmp = a;
        a = b;
        b = tmp % b;
    }
    return a;
}

最小公倍数

\[ \mathrm{lcm}(a,b)=\frac{ab}{\gcd(a,b)} \]

时间复杂度

为了简单,只考虑 \(a,b\) 均为正数,并且用 floor 方式计算 模运算 的商的情况。

实际上就是计算 \(a \bmod b\) 减小至 \(0\) 的速度。不妨设 \(a>b\),如果 \(b \le \dfrac{1}{2}a\),有

\[ (a \bmod b) < b \le \dfrac{1}{2}a \tag{1} \]

如果 \(\dfrac{1}{2}a < b < a\),由于 \(a\)\(b\) 大,所以 \(\left\lfloor \dfrac{a}{b} \right\rfloor \ge 1\),因此

\[ \begin{align} (a \bmod b) &= a- \left\lfloor \dfrac{a}{b} \right\rfloor b\\ &\le a-b \\ &< a-\frac{1}{2}a = \frac{1}{2}a \tag{2} \end{align} \]

结合 \((1)(2)\) 可得 \((a \bmod b) \le \dfrac{1}{2}a\)

在计算 \(\gcd(a,b)\) 时,会递归调用 \(\gcd(b,a \bmod b)\),再递归调用 \(\gcd(a \bmod b, b \bmod (a \bmod b))\),... 根据刚才的结论,有

\[ b \bmod (a \bmod b) \le \frac{1}{2}b \]

所以,每调用两次 \(\gcd\),第二个参数至少减少一半,时间复杂度为 \(O(\log b)\)


相关文章