1.欧几里得算法求最大公约数
求最大公约数是一个比较基础的问题,欧几里得早在《几何原本》中就阐明了一个高效的算法,据说这大概发生在公元前300年左右。具体是这样的:假设把x和y的最大公约数表示成为f(x,y),并且x>=y>0。现在取k=x/y,b=x%y,则x=k*y+b,变形为b=x - k*y;x和y能被f(x,y)整除,那么b也能被f(x,y)整除;所以
f(x,y) = f(y,x%y)。显然代码相当的简单啊,如下:
int GCD(int x,int y)
{
return (!y) ? x : GCD(y,x%y);
}
2.辗转相除法的变形
想一想辗转相除法有没有什么问题呢?例如,利用上面的函数求GCD(1100100210001,120200021),即对大整数求最大公约数,辗转相除法的效率就出现了瓶颈,实际上对于大整数而言,取模运算(用到除法)的开销非常的昂贵,这就是欧几里得算法的局限性,那么怎么优化一下呢?可以借鉴欧几里得的辗转相除法,既然是取模运算导致的问题,那么我们就不用取模运算,换用“-”运算,即 f(x,y)=f(x-y,y);深入考虑一下发现在算法运行的过程可能会出现x<y的情况,这时候要交换x和y,但是结果不受影响。再来看看代码吧。
BigInt GCD(BigInt x,BigInt y)
{
if(x < y) return GCD(y,x);
return (!y) ? x : GCD(x-y,y);
}
代码中用到的BigInt是大整数类,可以存储成百上千位的整数。这个类怎么实现呢?这里不多说,具体网上有好多的相关文章来讲解高精度算法的,要是看书籍的话,强烈推荐ACM大牛刘汝佳的《算法竞赛入门经典》一书,对高精度的介绍还是非常好的。
对于大数运算问题是解决了,然而,细心的读者会发现,这个算法引入了另外一个问题,那就是当x和y相差很多时,算法的迭代次数会过高,导致了算法的效率较低,例如计算GCD(100000000000001,1)。这种情况确实存在啊,于是我们要考虑其他的优化了。
3.利用移位运算符优化辗转相除法
上面的两种方法一个不适合解决大整数的问题,另一个不适合解决迭代次数过多的情况,那么能付把两种方法结合一下来得到一个能够让人接受的方法呢?答案是肯定的。我们注意到:
(1)如果y=k*y1,x=k*x1.那么有f(x,y)=k*f(x1,y1)
(2)如果x=p*x2,p为素数,并且y%p != 0,那么f(x,y) = f(p*x2,y) = f(x2,y)
于是我们得到下面的解决方法:
将p = 2,
若x,y均为偶数,f(x,y) = 2*f(x/2,y/2) = 2*f(x>>1,y>>1);
若x是偶数,y是奇数,f(x,y) = f(x/2,y) = f(x>>1,y);
若x是奇数,y是欧式,f(x,y) = f(x,y/2) = f(x,y>>1);
若x和y均为奇数,f(x,y) = f(y,x-y)。这时x-y一定是偶数,下一步一定会除以2。
因此,可以看出这种算法的时间复杂度是O(log2(max(x,y))。
代码如下:
BigInt GCD(BigInt x,BigInt y)
{
if(x < y) return GCD(y,x);
if(y == 0) return x;
else
{
if(IsEven(x))
{
if(IsEven(y))
return (GCD(x>>1,y>>1)<<1);
return GCD(x>>!,y);
}
else
{
if(IsEven(y))
return GCD(x,y>>1);
return GCD(y,x-y)
}
}
}
这种方法巧妙的使用了移位运算和减法运算,避开了大整数除法,提高了算法的效率,这是值得学习的。另外这三种算法都是属于尾递归可以转化成循环的方式实现来进一步提高效率,感兴趣的可以自行实现。
学习中的一点总结,欢迎拍砖哦^^