笔试面试题目中经常会遇到两个数最大公约数的问题,一种比较流行的方法是,用较大者除以较小者,若整除,较小者为最大公约数,若有余数,则求余数与较小者的最大公约数,递归实现。它是基于这样一个事实,即若x = k*m;y=k*n;那么y/x = n/m; y%x = k*(m%n);即x和y%x也有最大公约数k.这种方法很容易编程实现,但是一个不足时计算机计算取模运算相当于除法,很耗时间,于是有了进一步改进。
版本一:
int gcd_ver_1(int x, int y)
{
if (x > y)
return gcd_ver_1(y, x);
int mod = y%x;
if (!mod)
return x;
else
return gcd_ver_1(x, mod);
}
此版本采用尾递归的形式,因此可以化为循环。
同样地,我们发现y-x = k*(n-m),于是x和y-x也有公约数k,这样就可以用加减法替代取模运算,性能有所提高。
版本二:
int gcd_ver_2(int x, int y)
{
if (x > y)
return gcd_ver_1(y, x);
if (!x)
return y;
else
return gcd_ver_2(x, y-x);
}
但是这样也有个问题,如果数值太大,循环进行的次数比较多,而第一种方法呢,虽然循环次数少,但是有取模运算,能不能把两者结合起来呢。
我们发现,如果一个素数p,即是x的约数又是y的约数,那么,f(x,y) = p*f(x/p,y/p);
如果一个素数p,仅仅是x的约数,由于它是素数,所以,f(x,y) = f(x/p, y);同理,y也一样。
那么,考虑到2是一个素数,况且计算机对2的运算可以变为位运算,所以取p=2;
如果x,y均为偶数,那么f(x,y) = 2 * f(x>>1, y>>1)
如果x为偶数,y为奇数,那么f(x,y) = f(x>>1, y)
如果y为偶数,x为奇数,那么f(x,y) = f(x, y>>1)
如果x、y均为奇数,那么用第二个版本的逻辑,f(x,y) = f(x, y-x);不管是x-y还是y-x,必定有一个数是偶数,那么就可以回归上面的几步了。
int gcd_ver_3(int x, int y)
{
if (x > y)
return gcd_ver_3(y ,x);
if (!x)
return y;
else {
if (!(y & 0x1)) {
if (!(x & 0x1))
return (gcd_ver_3(x>>1, y>>1)) << 1;
else
return gcd_ver_3(x, y>>1);
}
else {
if (!(x & 0x1))
return (gcd_ver_3(x>>1, y));
else
return gcd_ver_3(x,y-x);
}
}
}
摘自《编程之美——最大公约数》