最大公约数
- 问题:
写一个程序,求两个正整数的最大公约数。如果这两个整数都很大,有什么简单的算法吗?
- 思路:
求最大公约数是一个很基本的问题,早在公元前300年左右,欧几里得就在他的著作《几何原本》中给出了高效的解法——辗转相除法。
辗转相除法的原理很简单,设两数为a、b(a>b),b最大公约数(a,b)的步骤如下:用b除a,得a=bq......r1(0≤r1)。若r1=0,则(a,b)=b;若r1≠0,则再用r1除b,得b=r1q......r2 (0≤r2).若r2=0,则(a,b)=r1,若r2≠0,则继续用r2除r1,……如此下去,直到能整除为止。其最后一个非零除数即为(a,b)的最大公约数。
原理及其详细证明(来自百度百科http://baike.baidu.com/view/255668.htm)
设两数为a、b(b<a),用gcd(a,b)表示a,b的最大公约数,r=a mod b 为a除以b以后的余数,辗转相除法即是要证明gcd(a,b)=gcd(b,r)。
第一步:令c=gcd(a,b),则设a=mc,b=nc
第二步:根据前提可知r =a-kb=mc-knc=(m-kn)c
第三步:根据第二步结果可知c也是r的因数
第四步:可以断定m-kn与n互素【否则,可设m-kn=xd,n=yd,(d>1),则m=kn+xd=kyd+xd=(ky+x)d,则a=mc=(ky+x)dc,b=nc=ycd,故a与b最大公约数成为cd,而非c】
从而可知gcd(b,r)=c,继而gcd(a,b)=gcd(b,r)。
证毕。
- 程序解法
- 解法一
最简单的递归解法,具体代码如下:
int gcd(int x, int y)
{
return (!y) ? x : gcd (y, x%y);
}
- 解法二
在解法一中,我们用到了取模运算,但对于大整数而言,取模运算(其中用到了除法)是非常昂贵的开销,有没有办法能够不用取模运算呢?
采用类似前面辗转相除法的分析,如果一个数能够同时整除x和y,则必能整除x-y和y;反之亦然。即x和y的公约数与x-y和y的公约数是相同的,即gcd(x, y) = gcd(x - y, y),那么可转换成简单的多的大整数的减法。
具体代码:
long gcd(long x, long y) { if (x < y) { return gcd(y, x); } if (y == 0) { return x; } return gcd(x - y, y); }
- 解法三
解法二的问题在于迭代次数太多,在遇到(10000000, 1)的情况就很悲催了。
从分析公约数的特点入手:对于y和x来说,如果y = k*y1, x = k * x1.那么有gcd(y, x) = k * gcd(y1, x1).
另外,如果 x = p * x1, 假设p是素数,并且y%p != 0(即y不能被p整除),那么gcd(x, y) = gcd(p * x1, y) = gcd(x1, y).
注意到这一点,我们取p = 2,并且将除以2的操作转换为右移一位,即得到以下代码:
1 long gcd(long x, long y)
2 {
3 if (x < y)
4 {
5 return gcd(y, x);
6 }
7 if (y == 0)
8 {
9 return x;
10 }
11 if (IsEven(x)) //判断x是否为偶数
12 {
13 if (IsEven(y))
14 {
15 return (gcd(x >> 1, y >> 1) << 1);
16 }
17 else
18 {
19 return (gcd(x >> 1, y);
20 }
21 }
22 else
23 {
24 if (IsEven(y))
25 {
26 return gcd(x, y >> 1);
27 }
28 else
29 {
30 return gcd(x, x -y);
31 }
32 }
33 }