问题描述:
求两个正整数的最大公约数Greatest Common Divisor (GCD)。如果两个正整数都很大,有什么简单的算法吗?
例如,给定两个数1 100 100 210 001, 120 200 021,求出其最大公约数。
分析与解法:
解法一:辗转相除法
欧几里得《几何原本》所载,gcd(x,y)可以使用另外一个等效递归子问题求解,假设k=x/y,b=x%y,则有x=ky+b,一个数能够整除x与y那么它必然能够整除y与b。
实例:f(42,30)=f(30,12)=f(12,18)=f(12,6)=f(6,0);最小公约数为6
下面是代码:
//非递归版本
int gcd(int x,int y)
{
int i=1;//初始化为
while(i){
i=x%y;
x=y;
y=i;
}
return x;//本次迭代x%y=0后,此时x=0,之后x被置为y
}
递归版本:
int gcd(int x , int y)
{
return (!y)?x:gcd(y,x%y);
}
解法二:在解法一用到了取模运算,对大整数,取模运算(用到了除法)是非常昂贵的开销,能不能不用取模运算呢?
可以,类似前面的分析,一个数能整除x与y必然能够整除x-y和y,注意到保证x>y是不让公式出现负数的条件
实例:f(42,30)=f(12,30)=f(30,12)=f(18,12)=f(6,12)=f(12,6)=f(6,6)=f(6,0)
下面是代码:
int gcd(int x , int y)
{
if(y==0)//迭代终止条件
return x;
if(x >= y)//本次迭代如果x>=y
return gcd(x-y,y);
else //本次迭代x<y,互换位置
return gcd(y,x);
}
注意到这种方法迭代次数比以前多多了,如计算gcd(10000000,2)这样的情况得多郁闷啊。上面的算法是用int替代了用户自定义的BigInt大整数类,需要重载比较操作符、减法操作符。下面提供一个BigInt类定义,类函数略去:
//下面代码是大整数类BigInt实现方案
const int MAXLEN = 100;
class BigInt{
private:
int numLen;
int num[MAXLEN];
int flag;//正负标记,true为正
public:
BigInt(){numLen = 0;memset(num,0,MAXLEN*sizeof(int));flag=true;}
~BigInt(){}
void print(void);//打印大整数
void str2BigInt(const char *str);//使用str转换成BigInt数据格式
//加减乘除
void Plus(const BigInt plusNum , BigInt &result);
void Subtract(const BigInt subNum , BigInt &result);
void Multi(const BigInt multsiNum , BigInt &result);
void Divide(const BigInt divideNum , BigInt &result);
//重载运算符
bool operator<(const BigInt &cmp);//操作符重载其实只是一个普通函数,只是名字很特别
void operator=(const BigInt &cmp);
};
当然也可以用vector<int> 来替代定长数组,更加方便,浪费空间更少。主要是因为有resize()函数改变vector长度、push_back()函数压入数据、[]下标随机访问数据、size()获取长度、iterator迭代器遍历与比较数据,使得vector比定长数组实现BigInt更加方便。
解法三:此种方法是将解法一的除法快速迭代和解法二的减法运算简单结合起来,降低计算复杂度的同时也降低迭代次数。
从分析公约数性质入手:
如果y=k*y1 , x=k*x1 ,那么有f(y , x) = k*f(y1 , x1);
另外,如果x=p*x1 , 假设p是素数,并且y不能被p整除,那么p对求解最大公约数没有贡献,因此f(x,y)=f(p*x1 , y) = f(x1 , y)
考虑到计算机中移位操作非常简便,因此考虑2这个素数,
x,y均为偶数 f(x,y) = 2*f(x>>1 , y>>1);
x为偶,y为奇 f(x,y) = f(x>>1 , y);
x为奇,y为偶 f(x,y) = f(x , y>>1);
x为奇,y为奇 f(x,y) = f(x-y , y);//因为俩都是奇,那么x-y必然为偶,所以下一步迭代又可以有某个数除以2了,很快的解决这个问题,最坏情况算法复杂度O(log(max(x,y)));
//BigInt为自己实现的一个大整数类,IsEven()判断是否为偶数
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)) //情形1,x,y均为偶数
return 2 * gcd(x >> 1, y >> 1);
else //情形2,x为偶,y为奇
return gcd(x >> 1, y);
}
else
{
if(IsEven(y)) //情形3,x为奇,y为偶
return gcd(x, y >> 1);
else //情形4,x,y均为奇
return gcd(y, x - y);
}
}
}