求两个整数的最大公约数

时间:2021-01-07 09:45:51

要求方法传两个正整形参数,返回值就是他们的最大公约数。

暴力枚举法

思路就是寻找一个整数 i ,从2开始循环累加,一直累加到两个整数中较小的一个的一半为止。循环结束后,上一次寻找到的能被两整数整除的最大 i 值,就是这两个整数的最大公约数。

参考代码:

public static int getGreatestCommonDivisor(int numA, int numB)
{
int smallNum = numA < numB ? numA : numB;
int bigNum = numA >= numB ? numA: numB;

if(bigNum % smallNum == 0)
{
return smallNum;
}

int greatestCommonDivisor = 1;

for(int i = 0; i <= smallNum/2; i++)
{
if(numA % i == 0 && numB % i ==0)
{
greatestCommonDivisor=i;
}
}

return greatestCommonDivisor;
}

辗转相除法

辗转相除法,又名欧几里得算法,目的就是求出两个正整数的最大公约数。它是已知的最古老算法,可追溯至公元前300年前。

这条算法基于这样一个定理:两个正整数a和b(a>b),它们的最大公约数等于a除以b的余数c和b之间的最大公约数。比如10和25,25除以10商2余5,那么10和25的最大公约数等同于10和5的最大公约数。

有了这条定理,我们可以使用递归的方法来把问题逐步简化,逐渐把两个较大整数之间的运算简化成两个较小整数之间的运算,直到两个数可以相除,或者其中一个数减小到1为止。

参考代码:

public static int getGCD(int numA, int numB)
{
int result=1;
if(numA>numB)
{
result=gcd(numA,numB);
}
else
{
result=gcd(numB,numA);
}

return result;
}

private static int gcd(int a, int b)
{
if(a%b==0)
{
return b;
}
else
{
return gcd(b,a%b);
}
}

更相减损术

以上这就是辗转相除法的思路。但是,当两个整数较大时,做a%b取模运算的性能会比较低。此时,另一种算法,更相减损术1,用来优化以上代码,避免大整数取模的性能问题。

它的原理更加简单:两个正整数a和b(a>b),它们的最大公约数等于a-b的差值c和较小数b的最大公约数。比如10和25,25-10的差值是15,那么10和25的最大公约数等同于10和15的最大公约数。

同样可以使用递归的方法来计算,参考代码如下:

public static int getGCD(int numA, int numB)
{
if(numA==numB)
{
return numA;
}
if(numA>numB)
{
return getGCD(numA-numB,numB);
}
else
{
return getGCD(numB-numA,numA);
}
}

优化算法

更相减损术避免了大整数取模的性能问题,但是依靠两数求差的方式来递归运算次数肯定大于辗转相除的取模方法。它是一种不稳定的算法,当两数相差悬殊时,比如10000和1,就要递归9999次。

最优的方法是把辗转相除法和更相减损术的优势结合起来,在更相减损术的基础上使用移位运算。

众所周知,移位运算的性能非常快,对于给定的正整数a和b,不难得到如下的结论(其中gcd(a,b)的意思是a,b的最大公约数函数):

  • 当a和b均为偶数时,gcd(a,b)=2 * gcd(a/2,b/2)= 2* gcd(a>>1,b>>1)。
  • 当a为偶数,b为奇数时,gcd(a,b)=gcd(a/2,b)=gcd(a>>1,b)。
  • 当a为奇数,b为偶数时,gcd(a,b)=gcd(a,b/2)=gcd(a,b>>1)。
  • 当a和b均为奇数时,利用一次更相减损术运算一次,gcd(a,b)=gcd(b,a-b),此时a-b必然是偶数,又可以继续进行移位运算。

当两个数比较大时,计算性能就能体现出来。

参考代码:

public static int gcd(int a, int b)
{
if(a==b)
{
return a;
}
if(a<b)
{
//保证参数a永远大于b,为减少代码量
return gcd(b,a);
}
else
{
if(!a&1 && !b&1)
{
return gcd(a>>1,b>>1) <<1;
}
else
{
if(!a&1 && b&1)
{
return gcd(a>>1,b);
}
else
{
if(a&1 && !b&1)
{
return gcd(a,b>>1);
}
else
{
return gcd(b,a-b);
}
}
}
}
}

总结

以上算法的时间复杂度:

  1. 暴力枚举法: O(min(a,b))
  2. 辗转相除法:时间复杂度不太好计算,可近似为 O(log(min(a,b))) ,但是取模性能较差。
  3. 更相减损术:避免了取模运算,但是算法不稳定,最坏时间复杂度为 O(max(a,b))
  4. 优化算法:不但避免了取模,而且算法稳定,时间复杂度为 O(log(max(a,b)))

  1. 更相减损术,出自于中国的古代的《九章算术》,也是一种求最大公约数的算法。