最大公约数问题

来源:互联网 发布:石家庄淘宝详情图拍照 编辑:程序博客网 时间:2024/06/03 20:51

首先从定义说起,所谓公约数,就是能够同时被若干个整数整除的数。而这些数中最大的那个,就叫做最大公约数(greatest common divisor,简称gcd)。


讲完了定义,下面介绍几种求解最大公约数的算法。

第一种算法就是欧几里得(Euclid)提出的辗转相除法。

记x,y的最大公约数为f(x,y)。如果x,y同时可以整除t,那么y,x%y也可以整除t。理由很简单,令k=x/y(k为整数,因为x/y可能是小数),b=x%y。则x=ky+b。因为x可以整除t,所以(ky+b)也可以整除t。其中ky很明显可以整除t,则需要b也可以整除t。b既是x%y,得证。那么求f(x,y)就可以转换成为求f(y,x%y)。这也就是辗转相除的精髓:用上一次的除数作为新的被除数,用上一次的余数作为新的除数,循环求解。最后当除数为0时,被除数就是最大公约数。


下面举个例子,比如求24和16的gcd:

f(24,16)=f(16,8)=f(8,0)=8

下面上代码:

int gcd(int x,int y){  return y?gcd(y,x%y):x;}
这种算法有一点弊端,就是当数很大的时候(成百上千位),取模的开销会很大。由此我们提出了第二种算法。



第二种求gcd的方法f(x,y)=f(x-y,y)

如果x,y能同时整除t,则x=k1*t,y=k2*t,x-y=(k1-k2)*t(x>y),则(x-y,y)可以同时整除t。值得一提的是,如果x<y则先把x,y位置对调,之后再迭代。当除数为0时,停止迭代,被除数就是gcd。

用这种方法求24和16的gcd如下:

f(24,16)=f(8,16)=f(16,8)=f(8,8)=f(0,8)=f(8,0)=8

继续上代码:

int gcd2(int x,int y){  if(x<y)return gcd2(y,x);  else if(y==0)return x;  else return gcd2(x-y,y);}
该函数仍然是定义成为int类,如果需要求大数的gcd则需要自己定义大数类并重载运算符,本篇只提供求解思路,大数类的定义就不赘述了。

由于仅仅是用了减法,所以计算开销大幅度降低。但是该算法仍然存在一定局限,考虑一种极端情况,求f(1000000000001,1)。这样的例子需要迭代的次数实在太多,有没有更好的算法呢?请看三号算法。


第三种求gcd的算法:

在讲第三种算法之前,首先先明确2点:

1.若x=k*x1,y=k*y1,则f(x,y)=f(k*x1,k*y1)=k*f(x1,y1)很明显k是公约数但不一定是最大公约数。

2.若x=p*x1,其中p是素数且y%p!=0(也就是说p是x的约数而不是y的约数,那么很显然p不是x,y的公约数),则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)=[f(x>>1,y>>1)]<<1

如果x为偶,y为奇数, 则f(x,y)=f(2*x1,y)=f(x1,y)=f(x>>1,y)

如果x为奇,y为偶数, 则f(x,y)=f(x,2*y1)=f(x,y1)=f(x,y>>1)

如果x,y都是奇数的话,则f(x,y)=f(x-y,y)


用这种方法求24和16的gcd:

f(24,16)=2*f(12,8)=4*f(6,4)=8*f(3,2)=8*f(3,1)=8*f(2,1)=8*f(1,1)=8*f(0,1)=8*f(1,0)=8

话不多说,上代码:

int gcd3(int x,int y){  if(x<y)return gcd3(y,x)  else if(y==0)return x  else if(isEven(x) && isEven(y))return (gcd3(x>>1,y>>1)<<1);  else if(isEven(x) && (!isEven(y)))return gcd3(x>>1,y);  else if((!isEven(x)) && isEven(y))return gcd3(x,y>>1);  else return gcd3(x-y,y);}
需要说明一下,isEven是判断是否为偶数 ,在此不予赘述。