最大公约数算法分析
看到《编程之美》的最大公约数一节时,理解了一个以前一直不理解的高效GCD算法,此处总结一下。
最大公约数GCD算法中,较为经典的要数《几何原本》里面的辗转相除法,用f(x, y)表示x与y的最大公约数,则f(x, y) = f(y, x%y),当y为0时返回x。该算法最大的缺陷是需要模运算,对于大数模运算是非常耗时的,模运算本质上是除运算,而除运算可以由基本的加减运算来替代,因此一个改进算法如下:
F(x, y) = f(y, x-y),(x >=y,x<y时交换x、y位置),这样一直迭代下去,直至其中一个为0,这样避免了耗时的除法运算,但是却引起了过多的迭代运算,对于f(11111111111, 1)这种类型的参数性能较差。
通过分析,可以看出,如果y = k*y1, x = k*x1,那么f(x, y) = k * f(x1, y1),另外如果x = p*x1,且p为素数, 并且y%p != 0,那么f(x, y) = f(p*x1, y) = f(x1, y)。而2就是个素数,并且对于2的乘除操作可以转换为高校的位操作。当p = 2时,
若x、y均为偶数则 f(x, y) = 2*f(x/2, y/2)
若x为偶数则f(x, y) = f(x/2, y)
若y为偶数则f(x, y) = f(x, y/2)
若x、y均为奇数,则x-y为偶数(此处假设x>y),f(x, y) = f(y, x-y)
该思想的一个基本实现如下:
int gcd(int x, int y)
{
if (x < y)
swap (x, y);
if (y == 0)
return x;
else
{
if ((x&1) == 0)
{
if ((y&1) == 0)
return gcd(x >>1, y>> 1) << 1;
else
return gcd(x >> 1, y);
}
else
{
if ((y&1) == 0)
return gcd(x, y>> 1);
else
return gcd(y, x-y);
}
}
}
该实现是《编程之美》上面给出的,已经比较高效了,但是却采用了递归的形式,对算法性能有一定影响,以前在网上看到一个更为高效的算法,一直没看懂,知道看到书上相关的解析后才恍然大悟,先把代码贴出。
unsigned int gcd(unsigned int x,unsigned int y)
{
unsigned int i,j,t;
if (x == 0 ) return y;
if (y == 0 ) return x;
for (i = 0 ; 0 == (x & 1 );x >>= 1 , ++ i);
for (j = 0 ; 0 == (y & 1 );y >>= 1 , ++ j);
for (;;)
{
if (x < y) t = y,y = x,x = t;
if ( 0 == (x -= y)) return y << i;
for (; 0 == (x & 1 );x >>= 1 );
}
}
在看了书上的算法后,这个算法的思想也较为明了了,首先把x和y右移变为奇数(如果他们是偶数的话),较小的移位次数是x、y公共操作,可以理解为y = k*y1, x = k*x1,f(x, y) = k * f(x1, y1),然后进入for循环,首先保证x >= y,如果x == y,则算法结束,返回y<<i(即y*k),否则,由于此时x、y都为奇数,且x > y,故x -= y为偶数,继续将x右移为奇数,相当于书上isEven(x) && isOdd(y)时的处理:return gcd(x >> 1, y)。
总体来说两个算法核心思想是一样的,只是后面的算法使用了迭代法,效率高些,在需要高效计算GCD的时候,可以使用上面的算法,特别是在ACM中某些题目需要频繁计算GCD的地方,用时可以有较大的提升。