该文出自于编程之美中关于最大公约数问题一章。
任意给定两个数字,得到其最大公约数 GCD(greatest common divisor),如果两个数字都很大怎么解决。
分析:最大公约数早在公元前300年,欧几里得的《几何原本》里就提出了一个高效率算法---辗转相除法。
解法一:
假设f(x,y)表示x,y的最大公约数,取k=x/y,b=x%y,则x=ky+b,如果一个数字能同时整除x,y,那么必能够同时整除b,y;而能够同时整除b,y的数必能够同时整除x,y,即x,y的公约数与b,y的公约数是相同的,其最大公约数也是相同的,则必有f(x,y)=f(y,x%y)(x>=y>0),从而把两个数字的最大公约数转化为求两个更小数字的最大公约数,直到其中一个数字为0,剩下的另一个数字就是两者最大的公约数。
例如: f(42,30) = f(30,12) = f(12,6) = f(6,0) = 6
代码如下:
int gcd(int x,int y) { return (!y)?x:gcd(y,x%y); }
解法二:
由于辗转相除法中存在一个最大的问题是,取模运算(其中用到了除法运算)是非常昂贵的开销,成为了算法的瓶颈。根据解法一的思路分析,假设一个数能够同时整除x,y,则必能够同时整除x-y,y;而同时能够整除x-y,y的数字也必能够同时整除x,y,即x,y的公约数与x-y,y的公约数相同,其最大公约数也是相同的 即:f(x,y) = f(x-y,y)。这样做就不需要进行大量的去摸运算,而转化为减法运算。
例如:f(42,30) = f(30,12) = f(12,18) = f(12,6) = f(6,6,)=f(6,0) = 6
代码如下:
int gcd(int x,int y) { if (x < y) return gcd(y,x); if (y == 0) return x; return gcd(x-y,y); }
解法三:
解法一处理大整数整除问题比较复杂,解法二处理虽然将大整数问题除法转换为减法运算,降低了复杂度,但是他的问题在于减法迭代次数太多了。最好的方法就是解法一和解法二结合使用。
分析: 对于 y和x来说,如果y = k*y1, x = k*x1。那么有 f(y,x)=k*(y1,x1)。另外,如果x = p*x1,假设p是素数,且y%p!=0(即y不能够被p整除),那么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)=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/2,y) = f(x,y>>1);
若:x,y均为奇数,f(x,y)=f(y,x-y),那么就存在f(x,y)=f(y,x-y)之后,(x-y)是一个偶数,下一步一定会有除以2的操作了。
因此这样的复杂度为 O(log2(max(x,y)));
例如:
f(42,30) = f(1010102,111102)
=2*f(101012,11112)
=2*f(11112,1102)
=2*f(11112,112)
=2*f(11002,112)
=2*f(112,112)
=2*f(02,112)
= 2*112
=6
代码如下:
//奇数 偶数判断 bool isEven(int n) { return (n&1)?false:true; } //高效率gcd int gcd(int x,int y) { if (x<y) return gcd(y,x); if (y == 0) return x; if(isEven(x)) { if(isEven(y)) return (gcd(x>>1,y>>1)<<1); else return gcd(x>>1,y); }else { if(isEven(y)) return gcd(x,y>>1); else return gcd(y,x-y); } }