【算法笔记·数论】最大公约数,扩展欧几里得。详细讲解(超级简单)。C/C++

本文详细介绍了欧几里得算法,也称辗转相除法,用于计算两个数字的最大公约数。通过数学推导证明了gcd(a,b)=gcd(b,a%b),并解释了算法如何确保找到的是最大公约数。此外,还探讨了扩展欧几里得算法,用于求解最大公约数的同时找出x和y的值,使得ax+by=gcd(a,b)。文中提供代码示例,并强调了算法过程中的迭代思路。
摘要由CSDN通过智能技术生成
前言:

欢迎光临大千小熊的博客,我是一只又会MMD又会C++的正派熊,B站和CSDN同步更新,欢迎关注。

正文:

辗转相除法,又称欧几里得算法,是求出两个数字的最大公约数的算法。
常用定理:最小公倍数*最大公约数=两数之积

推导过程:

设gcd(a,b)是计算自然数a和b的最大公约数的函数。
a除以b得到的商和余数分别为p和q。因为 a = b ∗ p + q a=b*p+q a=bp+q,所以gcd(b,q)既可以整除1a又整除b。2 ,也就整除gcd(a,b)。反之因为 q = a − b ∗ p q=a-b*p q=abp,同理可证明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 xy是多少呢?
现在让我们一起来推到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 ax1+by1=gcd
那么下一层的状态是:
(a和b就是上一层的a和b)
b ∗ x 2 + ( a % b ) ∗ y 2 = g c d b*x_2+(a \% b)*y_2=gcd bx2+(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 bx2+(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 ay2+b(x2(a/b)y2)=gcd

所以对比“这一层的状态”和“下一层的状态”,即①和②:
a ∗ x 1 + b ∗ y 1 = g c d a*x_1+b*y_1=gcd ax1+by1=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 ay2+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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值