问题描述(Description):
求两个正整数的最大公约数Greatest Common Divisor (GCD)。如果两个正整数都很大,有什么简单的算法吗?
例如,给定两个数1 100 100 210 001, 120 200 021,求出其最大公约数。
分析与解法:
早在公元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);
}
非递归版:
int gcd(int x,int y)
{
int i = 1; // 初始为非0值
while(i != 0)
{
i = x % y;
x = y;
y = i;
}
return x;
}
解法二:
采用类似前面辗转相除法的分析,如果一数能同时整除x和y,则必能同时整除x – y 和 y; 而能够同时整除x – y和y的数也必能同时整除x和y,即x和y的公约数与x – y和y的公约数是相同的,其最大公约数也是相同的,即f(x,y) = f(x – y, y);那么就不需要进行大整数的取模运算,而转换成简单得多的大整数的减法。在实际操作中,如果x < y,可以先交换(x,y),因为f(x,y) = f(y, x),从而避免求一个正数和一个负数的最大公约数情况的出现。一起迭代下去,直到其中一个数为0。
示例如下:
//BigInt为自己实现的一个大整数类
BigInt gcd(BigInt x,BigInt y)
{
if(x < y)
return gcd(y,x);
if(y == 0)
return x;
else
return gcd(x - y, y);
//gcd(y,x - y)或许更合适
}
BigInt类需要重载减法运算符”-“。这个算法免去了大整数除法的繁琐,但同样也有不足之处。最大的瓶颈是迭代的次数过多,如果出现(10 000 000 000 000,1)这类情况,那就相当让人郁闷了。
解法三:
此种方法是将解法一和解法二结合起来,降低计算复杂度的同时也降低迭代次数。
从分析公约数的特点入手。
对于y和x来说,如果y = k * y1, x = k * x1。那么有f(y , x) = k * f(y , x).
另外,如果x = p * x1,假设p是素数,并且y % p != 0,那么f(x , y) = f( p * x1, y) = f(x1, y)。
注意到以上两点后,我们就可以根据这两点对算法进行改进。最简单的方法是,2是一个素数,同时对于二进制表示的大整数而言,可以很容易地将除以2和乘以2运算转换成移位运算,从而避免大整数除法,由此就可以利用2来进行分析。
取p = 2;
1:若 x, y均为偶数,f (x,y) = 2 * f(x / 2, y / 2) = 2 * f(x >> 1, y >> 1)
2:若x为偶,而y为奇,f (x , y ) = f (x / 2, y) = f ( x >> 1, y)
3:若x为奇,y为偶,f ( x, y) = f (x , y / 2) = f(x , y >> 1)
4:若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 ) = 2 * f (21,15) 4
= 2 * f (15,6) 3
= 2 * f (15,3) 4
= 2 * f (3,12) 3
= 2 * f (3,6) 3
= 2 * f( 3,3 ) 4
= 2 * f (3,0)
= 2 * 3
= 6
代码如下:
//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);
}
}
}