GCD Extends_GCD 欧几里得算法+扩展欧几里得算法详解

来源:互联网 发布:unity3d美术场景外包 编辑:程序博客网 时间:2024/05/16 07:16

1.欧几里得算法:

我们从小学开始老师都会让我们求解一一种问题,叫做最大公约数,这里的最大公约数就叫做GCD
当然求解最大公约数的算法也是非常的重要,我们在这里就引入欧几里得的算法,也就是著名的辗转相除法
首先给出定义:
GCD(a,b)=GCD(b,a%b)
在这里有的人会问a,b到底哪个大才能使对的,其实在这里我们的a,b是没有大小之分的,要是硬说影响的话,是有那么一点影响,但是无关痛痒
首先这里的生命是最好a>b
但是当a<b的时候,我们会发现a%b=a,也就是说这个算法会自动调换会a>b的状态,所以我们说这一点影响无关痛痒,不影响算法的本质
证明这里我们就不在涉及
但是在算法的实现方面我有一些要说的:
from time import *def gcd_1(a,b):    if b==0:        return a    else:        return gcd_1(b,a%b)def gcd_2(a,b):    #一旦数据量差距过大无法应用,但是在数据量相差不多的情况下,还是很优秀的,总而言之不要用,因为不稳定    if a==b:        return a    elif a>b:        return gcd_2(a-b,b)    else:        return gcd_2(b-a,a)def gcd_3(a,b):    if a==b:        return a    elif a<b:        return gcd_3(b,a)    elif not(a&1) and not(b&1):        return gcd_3(a>>1,b>>1)<<1    elif not(a&1) and b&1:        return gcd_3(a>>1,b)    elif not(b&1) and a&1:        return gcd_3(a,b>>1)    else:        return gcd_3(a-b,b)time=clock()a=eval(input("a:"))b=eval(input("b:"))print("gcd_1:%d"%gcd_1(a,b))print("time gcd_1:%lf"%(clock()-time))#time=clock()#print("gcd_2:%d"%gcd_2(a,b))#print("time gcd_2:%lf"%(clock()-time))time=clock()print("gcd_3:%d"%gcd_3(a,b))print("time gcd_3:%lf"%(clock()-time))
实验结果:
a:34567890b:1234567gcd_1:1time gcd_1:2.589272gcd_3:1time gcd_3:0.032302
很显然,虽然朴素的GCD写的很简洁,但是毫无疑问,朴素的GCD因为取模运算的限制太过,我们的位运算+更相孙减术(gcd_2)结合的优势最明显,在我们的工程项目中最好用gcd_3的写法

2.Extends_gcd扩展欧几里得算法:

我们的欧几里得算法很优秀,但是只用来求解最大公约数好像屈才了,所以这里我们对欧几里得算法进行扩展,我们引入扩展欧几里得算法
在讲解扩展欧几里得算法之前,我们先来讲解个很重要的知识:
1.贝祖定理:
对于任何一组不全是0的两个数x,y总是满足
ax+by=gcd(x,y)这是一定成立的(之后我们还要讲一下不成立的另一个中很相近的定理)
这个解是唯一的
但是想要求解出这个等式的整数解确实非常的麻烦,我们需要不断的尝试,这是没有办法忍受的异常繁琐的工作
所以说,这时候,为了快速求解出这个唯一的解,我们就需要用到扩展欧几里得算法
但是这里请注意,扩展欧几里得算法是基于欧几里得算法的,我们在这里只是引入了逆推 的过程
逆推导:
aX0+bY0=gcd(a,b)=gcd(b,b%a)gcd(b,a%b)=bX1+(a%b)Y1....gcd(a,0)=aXn+0*Yn=a    上面已经提到过了,最后的状态一定是a>b的这时候我们让Xn=1,Yn=0就可以逆推导开始:aX0+bY0=gcd(a,b)=gcd(b,a%b)gcd(b,a%b)=bX1+(a%b)Y1gcd(b,a%b)=bX1+(a-b*(a/b))Y1=aY1+b*(X1-a/bY1)=gcd(a,b)显然,我们可以得到X0=Y1Y0=X1-a/bY1
这就是我们的逆推导关系,我们利用C++的引用的手段,可以轻松的实现参数的时时改变,轻而易举实现这个算法
但是小心一点,我们的逆推导的过程是建立在
ax+by=gcd(a,b)上的,也就是说扩展欧几里得算法求出来的解是在等号右边是gcd(a,b)的解,这就引出了我们下面的问题

扩展欧几里得算法的应用:
1.求解不定方程:
ax+by=c
是不是看上去和我们上面的贝祖定理非常的相似?没错这时候我们利用扩展欧几里得求出来ax+by=gcd(a,b)的话,如果这时候c是gcd(a,b)的整数倍的话,我们扩展欧几里得算法得到的解都乘上c/gcd(a,b)得到的就是这个不定方程的解,但是,如果c不是gcd(a,b)的整数倍,说明这个不定方程没有解
为什么?
因为我们的ax+by=gcd(a,b)如果有解的条件就是gcd(a,b)是gcd(a,b)的1倍,但是如果没有说明我们没有办法分解出这个ax+by=gcd(a,b)的式子,那么自然,gcd(a,b)的倍数依旧没有解

2.求解线性同余方程:
我们将ax=b(mod n)(ps:那个等号是三等号,代表同余)满足的解称之为a模b关于n同余,这时候我们根据同余的定义可以得到
n|(ax-b)->ax-b=my->ax-my=b->ax+my=b(m只是一个常数,正负无所谓)
是不是又觉得这个方程眼熟,没错还是扩展欧几里得的不定方程
这时候我们就转化成了求不定方程的问题了,注意这里的同余的结果有很多个
但是这些结果都有一个同性
1.结果总数有gcd(a,n)个
2.自通解以后,每个解相差b/gac(a,n)   //这里我有一个问题,按照程序角度来说,我们会不会少元素的输出?

3.求解模逆元:
模拟元的定义,如果
ax=1(mod n)称x是a的模逆元,这里的条件是a,n必须互素
为什么,我们再转化一下:
ax+ny=1只有我们的gcd(a,n)=1的时候才有解,并且解唯一,gcd(a,n)=1的意思就是互素
所以模逆元存在的条件是a,n必须互素
性质是模逆元唯一
转化一下,这时候我们还是要求一个不定方程

综上,我们的利用欧几里得的问题始终就是求一个不定方程,只不过是不定方程的等号的右边的值不断的改变罢了,只有等号右边的值是gcd的整数倍才有解,有gcd个解,否则没有解

3.Code:

#include"iostream"#include"cstdio"#include"cstring"#include"cstdlib"using namespace std;int gcd(int a,int b)   //GCD{return b?gcd(b,a%b):a;}int Extends_gcd(int a,int b,int& x,int& y)   //扩展欧几里得算法 {if(b==0){x=1;y=0;return a;}int g=Extends_gcd(b,a%b,x,y);int t=x;x=y;y=t-(a/b)*y;return g; } int main(){int x;int y;int a,b,c;scanf("%d%d%d",&a,&b,&c);int g=Extends_gcd(a,b,x,y);printf("GCD:%d\n",g);//以下是求出解得,必须要乘倍数,是因为我们当时算的基础以gcd,但是实际上方程的右边是gcd的倍数(有解的话),所以我们要先乘上一个倍数 ,才是真正的解 if(c%g) printf("No solution\n");else printf("%d*%d+%d*%d=%d\n",a,x*(c/g),b,y*(c/g),c);return 0;} 


1 0
原创粉丝点击