欧几里得算法

Published: by Creative Commons Licence

  • Tags:

欧几里得算法

要求两个数的最大公约数,我们可以利用欧几里得算法实现。

//要求a >= b >= 0
gcd(a, b){
    if(b == 0){
        return a;
    }
    return gcd(b, a % b)
}

先来讨论正确性,设gab的最大公约数,那么a(modb)=akb=g(agkbg),也是g的倍数。同理设gba(modb)的最大公约数,那么有a=a(modb)+kb=g(a(modb)g+kbg),因此g也是a的因子。结合这两条性质可以直接推出g=g。因此我们可以借助计算ba(modb)的最大公约数g来得到ab的最大公约数g

之后来讨论时间复杂度,若a2b,那么a(modb)<a2。否则a<2b,则a(modb)ab<a2。因此不管哪种情况,较大的数在下次递归时都至少会减少一半。因此可以直接得出gcd递归调用的总次数为O(logab),由于每次递归都是常数时间复杂度,因此这也就是我们要的时间复杂度。

由于ab的最小公倍数等于abg,其中g=gcd(a,b)。因此我们可以直接推出最小公倍数算法:

//要求a >= b > 0
lcm(a, b){
    return a / gcd(a, b) * b; // 先除法后乘法的目的是防止乘法溢出
}

下面来了解以下欧几里得算法的性质。

欧几里得算法还有一个非常重要的特性,就是我们要计算n个数的最大公约数,实际时间复杂度应该是O(n+log2M)而不是O(nlog2M),其中M是这n个数中的最大值。

扩展欧几里得算法

扩展欧几里得算法可以帮助我们解决下面一类问题:

ax+by=c

其中ab已知,且c=gcd(a,b)。要求我们找到一对合法的(x,y)

b=0的时候,我们一定有a1+b0=c,接下来考虑如果我们已知bx+(akb)y=c,其中k=ab。可以得出

ay+b(xky)=c

利用这个公式可以直接推出扩展欧几里得算法。

extgcd(a, b){
    if(b == 0){
        return {1, 0};
    }
    {x, y} = extgcd(b, a % b);
    return {y, x - a / b * y};
}

了解了扩展欧几里得原理后,我们可以利用它在模运算下计算x1。对于模运算下求逆,如果模的数是素数,我们可以用费马小定理计算,其时间复杂度是严格Θ(log2p),其中p是模数。但是如果我们用扩展欧几里得算法来求逆,那得到的是O(log2p),即上界为此,但是实际上扩展欧几里得算法会快得多(实践中辗转次数非常少),并且扩欧不要求p是素数,只要求px互质。

扩欧还有一个非常重要的性质,就是我们可以发现算法中出现了乘法和减法运算,那么是否有可能在计算过程中出现数值溢出而导致结果错误呢?事实上这是不可能的,我们可以证明除非b=0的情况,有(x,y)=(1,0),其余情况下都满足|x|<b|y|<a。这里给一下证明:

考虑上面算法回退的时候,第一次满足b>c,则此时{x, y} = extgcd(b, a % b);得到的x=0,y=1,此时一定满足条件。现在假设该行返回的结果(x,y)满足条件|x|<akb,|y|<b,那么我们接下来证明return {y, x - a / b * y};这一行返回的结果也满足条件。很显然y<b是已经成立的了,因此我们只需要证明|xky|<a即可。

由于bx+(akb)y=c<b,因此一定有x>0y<0x<0y>0中的一方成立。

  • 如果x>0y<0成立,则由于y<0此时xky>0一定成立,根据xky<(akb)ky<akbkb=a,因此有|xky|<a成立。
  • 如果x<0y>0成立,则由于y>0此时xky<0一定成立,根据xky>(akb)ky>a+kbkb=a,因此有|xky|<a成立。

可以发现任意情况下都有|xky|<a。根据归纳法可以证明结论。

题目1:给定整数a1,,an,判断是否存在一组整数变量x1,,xn,满足ni=1aixi=c

这实际上是扩欧的一个拓展,对于n=2的情况实际上就是扩欧。不难推出结果成立当且仅当gcd(a1,,an)能整除c

题目2:给定整数a1,,an,判断是否存在一组非负整数变量x1,,xn,满足ni=1aixi=c

首先如果所有的整数都是同号,那么c必须同号。这个问题变成经典DP,时间复杂度为O(nc)

如果ai中存在负数q和正数p,那么我们可以通过qp+(q1)p=p来得到p,同理可以得到q。同理这样我们可以认为xi可以为负数,这与题目1相同。