欧几里得算法
首先我们来回顾一下求解2个数的最大公约数(gcd,Greatest Common Divisor)的欧几里得算法:
这个算法的核心是 g c d ( a , b ) = g c d ( b , a m o d b ) gcd(a,b)=gcd(b,a \ mod \ b) gcd(a,b)=gcd(b,a mod b),即 a a a和 b b b的公约数等于 b b b和 a m o d b a \ mod \ b a mod b的公约数.
至于为什么,oi-wiki上有,我们就不证了,但为了理解这个公式,我们举个例子
g c d ( 3 , 5 ) = g c d ( 5 , 3 ) = g c d ( 2 , 3 ) = g c d ( 3 , 2 ) = g c d ( 1 , 2 ) = g c d ( 2 , 1 ) = g c d ( 1 , 1 ) = g c d ( 0 , 1 ) = g c d ( 1 , 0 ) gcd(3,5)=gcd(5,3)=gcd(2,3)=gcd(3,2)=\\gcd(1,2)=gcd(2,1)=gcd(1,1)=gcd(0,1)=gcd(1,0) gcd(3,5)=gcd(5,3)=gcd(2,3)=gcd(3,2)=gcd(1,2)=gcd(2,1)=gcd(1,1)=gcd(0,1)=gcd(1,0)
然后你会发现,我们将要用1去模0了,但这显然是没有意义的*(a模b的定义为a除以b的余数).并且,1和0是没有公约数的(0没有任何约数),那么我们的程序好像就无法继续进行了.我们又观察到,5和3因为互质,最大公约数是1,恰好是当b=0的时候a的值.那么,其他的数进行gcd操作也会有这样的性质吗?
再来个例子
g c d ( 4 , 2 ) = g c d ( 0 , 2 ) = g c d ( 2 , 0 ) gcd(4,2)=gcd(0,2)=gcd(2,0) gcd(4,2)=gcd(0,2)=gcd(2,0)
好像确实是这样的!我们只需输出当b等于0时a的值即可!
实际上,当b=0时,就代表着前一步的a%b==0,也就是 b ∣ a b|a b∣a,那么我们的最大公约数显然就是b(下一步的a)了.
*如果你编译运行
1%0
这段代码,编译器报错[Warning] division by zero [-Wdiv-by-zero]
,程序RE
这样就可以写出代码:
int gcd(int a,int b)
{
if(b==0) return a;
return gcd(b,a%b);
}
在进入下一部分前,我们先来分析一下这个算法的复杂度.细心的读者已经发现了,上面的例子中有些操作是无效的,他们仅仅交换了a和b的位置,但对于程序运行来说,这是必需的.但他们的数量显然小于等于有效操作的数量,对于时间复杂度的分析来说可以略去.
回顾到取模这个运算,在正数意义下它的代码可以写成这样(你是否知道负数的取模运算?可以参考这篇文章):
while(a>b) a-=b//=a%b
实际上,G++中实现的取模不是这么简单.具体来说,若 a = q b + r