欧几里得与扩展欧几里得
数论专题的第二篇来了。。。过年嘛,拖了许久hhh。。。。。。
欧几里得算法
简介
欧几里得算法简称gcd算法,用于解决求两数最大公约数(gcd)的问题。
更相减损法
步骤(选自百度百科)
第一步:任意给定两个正整数;判断它们是否都是偶数。若是,则用2约简;若不是则执行第二步。
第二步:以较大的数减较小的数,接着把所得的差与较小的数比较,并以大数减小数。继续这个操作,直到所得的减数和差相等为止。
则第一步中约掉的若干个2与第二步中等数的乘积就是所求的最大公约数。
步骤中还是说的很详细的,因为不是很常用,当a和b差的很大的时候,时间复杂度很有可能退化为 O(n) 因此这里对于更相减损法不多说明。
辗转相除法
先上代码
递归代码
int gcd(int a,int b)
{
if(b==0)return a;
return gcd(b,a%b);
}
迭代代码
int gcd(int a,int b)
{
int t;
while(b)
{
t=a%b;
a=b;
b=t;
}
return a;
}
证明
欲证得: c = gcd (a , b),
设 a = k * b + r , k ,r 为正整数,(很容易看出一定存在k和r满足这一关系式)
已知 c 能被a和b整除 ,即c | a,c | b,而由上一行,将a替换成 k * b + r ,
可得:
c | ( k * b + r )成立,因此c | r 一定成立,
所以gcd ( a , b ) = gcd ( a , r ) = gcd ( b , r )
又因为a%b=r,gcd( a, b ) = gcd ( b , a%b )
就这样找到了递归的关系,那么什么时候找到出口停止递归呢?
我们知道,0除以一个自然数x恒等于零,因此0能整除任意自然数(不知道这么说对不对,反正这么顺着往下理解的),因此零与任意自然数的最大公约数都等于这个自然数本身。
所以我们就找到了递归的出口,当a和b有一个数为零的时候,返回另一个数。
以上欧几里得算法(辗转相除法)证明完毕。
时间复杂度
分析一下时间复杂度,由于a和b不一定,所以该算法的时间复杂度不是固定的,但是我们可以拿出一个斐波那契数列观察一下。
比如: 2,3,5,8,13,21,…
当我们找13和21的最大公约数的时候,按照递归代码的步骤,下一个应该得到的是21%13等于8,于是将8和13继续递归,得到了5和8,由此我们可以发现,这和上面的更相减损法步骤类似,一次一次往下递减。而这一过程便是辗转相除法最慢的一种情况,时间复杂度为 O( logn ),因此欧几里得算法的时间复杂度小于等于O( logn )。
(别问我咋知道斐波那契数列让他最慢的,我也是偶然翻大神博客发现了这一点)
扩展欧几里得算法
简介
扩展欧几里得算法可以用来求二元一次方程的通解,也可以以优于费马小定理的时间复杂度求乘法逆元。
这篇博客只解决二元一次方程通解问题,逆元留着下篇博客。
首先引入贝祖定理(也有叫裴蜀定理的,翻译问题,鬼知道到底叫啥)
总之就是这个东西:
若ax+by = z,则 gcd(a,b)| z
说人话就是满足ax+by = z的二元一次方程,z是a和b最大公约数的倍数。再想一下,可以通过这个定理来逆向判断一个二元一次方程是否有整数解。
最直接的应用:
如果ax+by=1有解,那么gcd(a,b)=1
再次明确一下我们的目的,是要求二元一次方程ax+by = z的解,在有解的情况下,我们先来看一下ax+by = gcd (a,b)这个方程的解(扩展欧几里得实际上就是求得该方程的解)。
根据欧几里得算法 ,当到达了递归的边界即 b = 0的时候,a = gcd (a,b),因此很容易看出一组x,y的解,x = 1,y = 0 然后我们就想,能不能通过递归,一层一层的往回推回该二元一次方程的解。
然后就开始了一系列奇妙的推倒(导):
当我们求ax1+by1 = gcd (a, b)的时候,
假设我们此时在求a和b的最大公约数,而我们已经求得下一个状态的最大公约数,
即 b*x2 + (a%b) *y2 = gcd (b, a%b),
然后我们的目标是找到x1与x2的关系,y1与y2的关系。
首先我们知道 a%b = a - [a/b]*b (中括号表示计算机内除法自动的向下取整)
然后进行替换,得到:
b *x2 + (a%b) * y2 = b *x2 + (a - [a/b]*b) *y2
继续化简得
a *y2 + b * (x2 - [a/b] *y2) = gcd ( a, b) = a *x1+b *y1
由待定系数法,可以得出:
x1 = y2,y1 = x2 - [a/b] *y2 = x2 - [a/b] *y2
以上,扩展欧几里得推导完毕。
下面是代码实现
代码
int exgcd(int a,int b,int &x,int &y)//扩展欧几里得算法
{
if(b==0)
{
x=1;y=0;
return a; //到达递归边界开始向上一层返回
}
int r=exgcd(b,a%b,x,y);//不断递归
int t=y; //开始层层往回迭代
y=x-(a/b)*y;
x=t;
return r; //得到a b的最大公因数
}
终于搞明白了欧几里得和扩展欧几里得,其实刚开始主要迷糊在算法的作用没有搞明白,具体证明还是很清晰明了的。给自己加油!也给所有正在努力的队友们和学长学姐们加油!