前言:
欢迎光临大千小熊的博客,我是一只又会MMD又会C++的正派熊,B站和CSDN同步更新,欢迎关注。
正文:
辗转相除法,又称欧几里得算法,是求出两个数字的最大公约数的算法。
常用定理:最小公倍数*最大公约数=两数之积
推导过程:
设gcd(a,b)是计算自然数a和b的最大公约数的函数。
a除以b得到的商和余数分别为p和q。因为
a
=
b
∗
p
+
q
a=b*p+q
a=b∗p+q,所以gcd(b,q)既可以整除1a又整除b。2 ,也就整除gcd(a,b)。反之因为
q
=
a
−
b
∗
p
q=a-b*p
q=a−b∗p,同理可证明gcd(a,b)整除b,q。然而gcd的含义是无限逼近的,所以大体上可以理解为贪心。如果不想证明的那么详细的话。
因此最终可以得到:gcd(a,b)=gcd(b,a % b)
[1]整除的含义:若整数a除以非零整数b,商为整数,且余数为零, 我们就说a能被b整除(或说b能整除a),a为被除数,b为除数,即b|a(“|”是整除符号),读作“b整除a”或“a能被b整除”。
举个例子,我们可以说10被2整除。
[2]理解gcd(b,q):您可以认为gcd(b,q)的意思是,取出b和q中的相同一部分(b和q的最大公约数)。回到原来的方程,bq和q由于都包含了刚刚取出的gcd(b,q)。所以当然gcd(b,q)可以整除方程a=bp+q,注意,就算是+号也是可以的,这是因为,gcd取出的是两个项中都有的共同一部分。
重新修订:2021年6月6日。
上面大体上讲的没有问题,但是根据读者反映,还有部分地方容易引起错误。特此指正。
比如有读者会问,凭什么这样做,能保证求出的是“最大”的约数?
好,请往下看。在这个问题之前,请让小熊把前面的内容再过一遍。将内容充实。
重讲欧几里得:
现在给我们两个数字
a
a
a和
b
b
b。
要求我们求出这两个数字的最大公约数。
#1:证明
g
c
d
(
b
,
a
%
b
)
gcd(b,a\%b)
gcd(b,a%b)是
a
a
a和
b
b
b的约数 。
现在,我们当然知道
g
c
d
(
a
,
b
)
gcd(a,b)
gcd(a,b)就是
a
a
a和
b
b
b的约数。(毕竟名字就叫最大公约数)
那么
g
c
d
(
b
,
a
%
b
)
gcd(b,a\%b)
gcd(b,a%b)呢?
现在,我们用6和4来实验。6 ➗ 4 = 1 …2
现在我们不关心1,只关心6 4 2。特别是2,这是因为如果余数是0的话,那我们的绊脚石就消失了。所以,现在我们就把目光放在4 和 2身上。如果能从这两个数中找到一个约数。那么,6 4 2这三个数字都会很满意,因为这是他们的约数。所以,如果能从(4,2)两个数字找出一个约数,那么这个约数一定也是(6,4,2)他们的共同约数。
(为什么(4,2)的约数是他们三个的共同约数,请看上面一个板块的“推导过程”的公式。这是因为a=k*b+q。从b和q中取出一个gcd(b,q)。也就是取出了(4,2)。)
好,现在证明完毕
g
c
d
(
b
,
a
%
b
)
gcd(b,a\%b)
gcd(b,a%b)也是
a
a
a和
b
b
b的约数。
到目前为止,我们体现了一个“辗转”的含义。b从第二个参数变成第一个参数。
#2:证明我们辗转相除法求出的是最大的约数。
这个问题其实很好解决。
假设
a
>
b
a>b
a>b。
那么求出
a
a
a和
b
b
b的最大公约数的这个约数绝对绝对不可能大于
b
b
b。
所以,我们就先猜测
b
b
b就是最大公约数,然后执行
g
c
d
(
a
,
b
)
gcd(a,b)
gcd(a,b)->
g
c
d
(
b
,
a
%
b
)
gcd(b,a\%b)
gcd(b,a%b)
现在
a
%
b
=
=
0
a\%b==0
a%b==0,也就是我们猜测
b
b
b是最大公约数是正确的,所以return b;
同理的
g
c
d
(
6
,
4
)
gcd(6,4)
gcd(6,4)
好了现在猜测4是约数,失败了。
因为
g
c
d
(
6
,
4
)
gcd(6,4)
gcd(6,4)->
g
c
d
(
4
,
2
)
gcd(4,2)
gcd(4,2)
第二个参数不为0,所以4不是6和4的共同约数。
在上文已经明确指出,4不是共同约数,责任全在余数2(6除以4的余数是2)
所以只能继续委屈4变小一点,但是尽可能最大。同理,我们试一试2是不是4和2的约数。因为4和2的共同约数,也不能大于2。
所以
g
c
d
(
2
,
0
)
gcd(2,0)
gcd(2,0)。2是6和4的约数,当然也是余数2的约数。
证毕。是一个贪心法。
辗转相除法代码:
有了上面的推到,我们知道gcd(a,b)=gcd(b,a % b)
第二个参数是不断减小的。所以当a%b==0的时候,那么b就是原来两个参数的最大公约数。详细的内容请看下面的代码:
int gcd(int a, int b) {
if (b == 0)
return a;
else
return gcd(b, a % b);
}
详解扩展欧几里得:
扩展欧几里得的算法是在辗转相除法的上面稍作改动从而得到的。
公式:
a
x
+
b
y
=
g
c
d
(
a
,
b
)
ax+by=gcd(a,b)
ax+by=gcd(a,b)
其中x和y是变量。gcd函数是求出a和b的最大公约数的函数。
现在,我们有
a
=
6
a=6
a=6和
b
=
4
b=4
b=4。gcd(4,6)=2
猜出一个x,y。
6
x
+
4
y
=
2
6x+4y=2
6x+4y=2
x
=
1
,
y
=
−
1
x=1,y=-1
x=1,y=−1
当然方程的解有好多个,这里我只是猜测出了一个。
那么如果gcd(4,6)=gcd(6,4)=gcd(6,2)=gcd(2,0)。
我们来看看gcd(2,0)这么求x和y。我们知道gcd(2,0)=2。
所以:
2
x
+
0
y
=
2
2x+0y=2
2x+0y=2
这个方程的解就是
x
=
1
,
y
=
0
x=1,y=0
x=1,y=0。我们知道当gcd第二个参数是0的时候,第一个参数就是解。所以此时此刻的x和y可以取x=1,y=0。
所以现在gcd(2,0)中 x = 2 , y = 0 x=2,y=0 x=2,y=0。
那么gcd(6,2)中
x
和
y
x和y
x和y是多少呢?
现在让我们一起来推到gcd(6,2)和gcd(2,0)直接的关系。
设gcd是a和b的最大公约数。M是取余%的意思。
这一层的状态是:
a
∗
x
1
+
b
∗
y
1
=
g
c
d
a*x_1+b*y_1=gcd
a∗x1+b∗y1=gcd①
那么下一层的状态是:
(a和b就是上一层的a和b)
b
∗
x
2
+
(
a
%
b
)
∗
y
2
=
g
c
d
b*x_2+(a \% b)*y_2=gcd
b∗x2+(a%b)∗y2=gcd
而
a
%
b
a \% b
a%b等于
a
−
(
a
/
b
)
∗
b
a-(a/b)*b
a−(a/b)∗b。(这里的“/”是C语言的整除的意思)。
所以下一层状态可以改写为:
b
∗
x
2
+
(
a
−
(
a
/
b
)
∗
b
)
∗
y
2
=
g
c
d
b*x_2+(a-(a/b)*b)*y_2=gcd
b∗x2+(a−(a/b)∗b)∗y2=gcd
进一步的通过移项得到:
a
∗
y
2
+
b
∗
(
x
2
−
(
a
/
b
)
y
2
)
=
g
c
d
a*y_2+b*(x_2-(a/b)y_2)=gcd
a∗y2+b∗(x2−(a/b)y2)=gcd②
所以对比“这一层的状态”和“下一层的状态”,即①和②:
a
∗
x
1
+
b
∗
y
1
=
g
c
d
a*x_1+b*y_1=gcd
a∗x1+b∗y1=gcd①
a
∗
y
2
+
b
∗
(
x
2
−
(
a
/
b
)
y
2
)
=
g
c
d
a*y_2+b*(x_2-(a/b)y_2)=gcd
a∗y2+b∗(x2−(a/b)y2)=gcd②
所以:
x
1
=
y
2
x_1=y_2
x1=y2
y
1
=
x
2
−
(
a
/
b
)
y
2
y_1=x_2-(a/b)y_2
y1=x2−(a/b)y2
我们知道了gcd(2,0)的解是下一层状态即 x 2 = 1 , y 2 = 0 x_2=1,y_2=0 x2=1,y2=0。那么根据公式本层的gcd(6,2)的 x 1 = 0 , y 1 = 1 x_1=0,y_1=1 x1=0,y1=1
然后现在gcd(6,2)是“下一个状态”,而“这一层状态是”gcd(6,4)。按照同样的方法可以求出gcd(6,4)最后求出gcd(4,6)的x,y的解。
如果说最大公约数的求解过程是“从左向右”,那么求解扩展欧几里得则是“从右向左”。
比如上面的例子gcd(4,6)=gcd(6,4)=gcd(6,2)=gcd(2,0)。求gcd是从左到右,求x和y是从右向左,最终得到gcd(4,6)的x和y的解。
扩展欧几里得参考程序:
ex_gcd仍然是求解a和b的最大公约数的函数,只是在递归的函数的回调过程中,顺便把x和y求解出来,x和y是全局变量。
#include <iostream>
using namespace std;
int x, y;
int ex_gcd(int a, int b, int& x, int& y) {
if (b == 0) {
x = 1;
y = 0;
return a;
} else {
int ans = ex_gcd(b, a % b, x, y);
int temp = x;
x = y;
y = temp - a / b * y;
return ans;
}
}
int main() {
//&x和&y是引用的意思,将x和y传入引用&x和&y。
ex_gcd(4, 6, x, y);
return 0;
}