最大公约数
定义
最大公约数即为 Greatest Common Divisor,常缩写为 gcd。
一组整数的公约数,是指同时是这组数中每一个数的约数的数。
一组整数的最大公约数,是指所有公约数里面最大的一个。
那么如何求最大公约数呢?我们先考虑两个数的情况。
欧几里得算法
过程
如果我们已知两个数
不妨设
我们发现如果
我们通过证明可以得到
设
由右边的式子可知
反过来也需要证明:
设
因为左边式子显然为整数,所以
既然两式公约数都是相同的,那么最大公约数也会相同。
所以得到式子
既然得到了
实现
=== "C++"
```cpp
// Version 1
int gcd(int a, int b) {
if (b == 0) return a;
return gcd(b, a % b);
}
// Version 2
int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); }
```
=== "Java"
```java
// Version 1
public int gcd(int a, int b) {
if (b == 0) return a;
return gcd(b, a % b);
}
// Version 2
public int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
```
=== "Python"
```python
def gcd(a, b):
if b == 0:
return a
return gcd(b, a % b)
```
递归至 b == 0
(即上一步的 a % b == 0
) 的情况再返回值即可。
根据上述递归求法,我们也可以写出一个迭代求法:
=== "C++"
```cpp
int gcd(int a, int b) {
while (b != 0) {
int tmp = a;
a = b;
b = tmp % b;
}
return a;
}
```
=== "Java"
```java
public int gcd(int a, int b) {
while(b != 0) {
int tmp = a;
a = b;
b = tmp % b;
}
return a;
}
```
=== "Python"
```python
def gcd(a, b):
while b != 0:
a, b = b, a % b
return a
```
上述算法都可被称作欧几里得算法(Euclidean algorithm)。
另外,对于 C++14,我们可以使用自带的 __gcd(a,b)
函数来求最大公约数。而对于 C++ 17,我们可以使用 <numeric>
头中的 std::gcd
与 std::lcm
来求最大公约数和最小公倍数。
如果两个数
性质
欧几里得算法的时间效率如何呢?下面我们证明,欧几里得算法的时间复杂度为
当我们求
-
,这时候 ; -
,这时候 ,而对 取模会让 至少折半。这意味着这一过程最多发生 次。
第一种情况发生后一定会发生第二种情况,因此第一种情况的发生次数一定 不多于 第二种情况的发生次数。
从而我们最多递归
事实上,假如我们试着用欧几里得算法去求 斐波那契数列 相邻两项的最大公约数,会让该算法达到最坏复杂度。
多个数的最大公约数
那怎么求多个数的最大公约数呢?显然答案一定是每个数的约数,那么也一定是每相邻两个数的约数。我们采用归纳法,可以证明,每次取出两个数求出答案后再放回去,不会对所需要的答案造成影响。
最小公倍数
接下来我们介绍如何求解最小公倍数(Least Common Multiple, LCM)。
定义
一组整数的公倍数,是指同时是这组数中每一个数的倍数的数。0 是任意一组整数的公倍数。
一组整数的最小公倍数,是指所有正的公倍数里面,最小的一个数。
两个数
设
我们发现,对于
最小公倍数等于
由于
所以得到结论是
要求两个数的最小公倍数,先求出最大公约数即可。
多个数
可以发现,当我们求出两个数的
扩展欧几里得算法
扩展欧几里得算法(Extended Euclidean algorithm, EXGCD),常用于求
过程
设
由欧几里得定理可知:
所以
又因为
所以
因为
将 0
递归 x=1,y=0
回去求解。
实现
=== "C++"
```cpp
int Exgcd(int a, int b, int &x, int &y) {
if (!b) {
x = 1;
y = 0;
return a;
}
int d = Exgcd(b, a % b, x, y);
int t = x;
x = y;
y = t - (a / b) * y;
return d;
}
```
=== "Python"
```python
def Exgcd(a, b):
if b == 0:
return a, 1, 0
d, x, y = Exgcd(b, a % b)
return d, y, x - (a // b) * y
```
函数返回的值为
值域分析
万幸的是,若
下面给出这一性质的证明。
时, ,必在下一层终止递归。
得到 ,显然 。 时,设 。
因为
所以
因此 成立。
迭代法编写扩展欧几里得算法
首先,当
成立。
已知
将迭代过程中的
据此就可以得到迭代法求 exgcd。
因为迭代的方法避免了递归,所以代码运行速度将比递归代码快一点。
int gcd(int a, int b, int& x, int& y) {
x = 1, y = 0;
int x1 = 0, y1 = 1, a1 = a, b1 = b;
while (b1) {
int q = a1 / b1;
tie(x, x1) = make_tuple(x1, x - q * x1);
tie(y, y1) = make_tuple(y1, y - q * y1);
tie(a1, b1) = make_tuple(b1, a1 - q * b1);
}
return a1;
}
如果你仔细观察
最后我们知道
矩阵的解释
对于正整数
其中向下取整符号
易发现欧几里得算法即不停应用该变换,有
令
那么
满足
int exgcd(int a, int b, int &x, int &y) {
int x1 = 1, x2 = 0, x3 = 0, x4 = 1;
while (b != 0) {
int c = a / b;
std::tie(x1, x2, x3, x4, a, b) =
std::make_tuple(x3, x4, x1 - x3 * c, x2 - x4 * c, b, a - b * c);
}
x = x1, y = x2;
return a;
}
这种表述相较于递归更简单。
应用
贡献者:@Menci@Untitled_unrevised@vane@Tifa@WenzelTian@WillHou936@Cubik@Zhikai@wangr-x@Koishilll@夜轮_NachtgeistW@Great-designer
本页面最近更新:2/3/2023, 12:00:00 AM,更新历史
发现错误?想一起完善? 在 GitHub 上编辑此页!
本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用