《编程之美》学习笔记之 最大公约数问题

问题描述(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);
	   }
    }
}

 

    原文作者:ajioy
    原文地址: https://blog.csdn.net/ajioy/article/details/7478008
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞