算法:辗转相除法

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

解法一:(性能最差)

public static int getGreatestCommonDivisor(int numberA, intnumberB) {
    int smallNumber = numberA < numberB ? numberA : numberB;
    int bigNumber = numberA > numberB ? numberA : numberB;
    if(bigNumber % smallNumber == 0) {
      return smallNumber;
    }
    int greatestCommonDivisor = 1;
    for(int i = 2; i <= smallNumber/2; i ++){
      if(numberA%i==0 && numberB%i==0) {
        greatestCommonDivisor = i;
      }
    }
  return greatestCommonDivisor;
}

思路:暴力枚举的方法,试图寻找一个合适的整数i,
  看看这个整数能否被两个整型参数numberA和numberB同时整除。

解法二:
辗转相除法,又名欧几里得算法(Euclidean algorithm),目的是求出两个正整数的最大公约数。
定理:两个正整数a和b(a>b),它们的最大公约数等于a除以b的余数c和b之间的最大公约数。比如10和25,25除以10商2余5,那么10和25的最大公约数,等同于10和5的最大公约数。

//方法入口
public static int getGreatestCommonDivisor(int numberA, int numberB) {
  int result = 1;
  if (numberA > numberB) {
    result = gcd(numberA,numberB);
  } else {
    result = gcd(numberB,numberA);
  }
  return result;
}
//递归计算最大公约数
private static int gcd(int a, int b) {
  if(a%b == 0) 
    return b;
  else
    return gcd(b, a%b);
}

缺点:当两个整型数较大时,做a%b取模运算能力会比较低


解法三:
更相减损术,出自于中古代的《九章算术》,也是一种求最大公约数的算法。
原理:两个正整数a和b(a>b),它们的最大公约数等于a-b的差值c和较小术的最大公约数。比如10和25,25减去10的差是15,那么10和25的最大公约数,等同于10和15的最大公约数。

public static int gcd(int numberA, int numberB) {
  if(nummberA == numberB)
    return numberB;
  if(numberA < numberB)
    return gcd(numberB - numberA, numberA)
  else
    return gcd(numberA - numberB, numberB);
}

缺点:更相损减数是不稳定的算法,当两数相差悬殊时,比如计算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 numberA, int numberB) {
  if(numberA == numberB)
    return numberA;
  if(numberA < numberB)
    //保证参数A永远大于等于参数B,为减少代码量
    return gcd(numberB,numberA)
  else {
    //和1做按位与运算,判断奇偶
    if(!numberA&1 && !numberB&1)
      return gcd(numberA>>1,numberB>>1) << 1;
    else if(!numberA&1 &&numberB&1)
      return gcd(numberA>>1, numberB);
    else if(numberA&1 && !number&1)
      return gcd(numberA, numberB>>1);
    else
      return gcd(numberA, numberA - numberB);
  }
}

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

其他.....

你可能感兴趣的:(算法:辗转相除法)