几种求最大公约数的方法

560 阅读1分钟

几种求最大公约数的方法

欧几里得算法(辗转相除法)

使用

gcd(a,b)=gcd(b,amodb)gcd(a,b)=gcd(b,a \bmod b)

使用该算法,可以递归取模,迭代到a能被b整除时,即可得最大公约数b。

证明

令 r 为 a÷b 的余数则 r=akb假设 x 为 a 和 b 的公约数,则存在 m,n 使得 a=mx,b=nx则 r= mxknx= (mkn)x所以 x 也是余数 r 的约数即 x 也是 b 和 r 的公约数公约数相同,那么最大公约数也是一致的即 gcd(a,b)=gcd(b,amodb)当一个数能被除数整除时,除数即为最大公约数递归此公式,直到能够被整除,即可得最大公约数为当前的除数这里不需要去判断 a 和 b 的大小,被除数小的话,得到的余数就是被除数本身即 gcd(a,b)=gcd(b,amodb)=gcd(b,a)令\ r\ 为\ a \div b\ 的余数 \\ \\ \\ 则\ r = a - kb\\ 假设\ x \ 为\ a\ 和\ b\ 的公约数,则存在\ m,n\ 使得\ a = mx,b=nx\\ \begin{aligned} 则\ r =& \ mx - knx\\ =& \ (m-kn)x\\ \end{aligned}\\ 所以\ x\ 也是余数\ r\ 的约数\\ 即\ x \ 也是\ b\ 和\ r\ 的公约数\\ 公约数相同,那么最大公约数也是一致的\\ 即\ gcd(a,b)=gcd(b,a \bmod b)\\ 当一个数能被除数整除时,除数即为最大公约数\\ 递归此公式,直到能够被整除,即可得最大公约数为当前的除数\\ 这里不需要去判断\ a\ 和\ b\ 的大小,被除数小的话,得到的余数就是被除数本身\\ 即\ gcd(a,b)=gcd(b, a \bmod b)=gcd(b,a)

代码实现

public static int gcd(int a, int b) {
    int r = a % b;
    if (r != 0) {
        return gcd(b, r);
    }
    return b;
}

时间复杂度

O(log N)假如 a>b ,则 amodb<a/2所以每次递归,都会至少向前推进当前一半的进度故时间复杂度为 O(log N)O(log\ N)\\ 假如\ a > b\ ,则\ a \bmod b < a/2\\ 所以每次递归,都会至少向前推进当前一半的进度\\ 故时间复杂度为\ O(log\ N)

如果 ba/2 \ b \le a/2\ ,则余数小于b,故成立。

如果 b>a/2 \ b \gt a/2\ ,余数即为 ab ,ab<a/2 \ a-b\ ,a-b \lt a/2\ ,故成立。

更相减损术(尼考曼彻斯法,辗转相减法)

更相减损术是出自《九章算术》中的一种约分的方法,也可以用来求最大公约数。

可半者半之,不可半者,副置分母、子之数,以少减多,更相减损,求其等也。以等数约之。

使用

gcd(a,b)=gcd(min(a,b),ab)gcd(a,b)=gcd(min(a,b),|a-b|)

使用该方法计算最大公约数,假设 a>b \ a>b\ ,求 a 和 ba\ 和\ b 的最大公约数即是求 b 和 ab\ b\ 和\ a-b 的最大公约数,递归到当两个数相等时,该数即为 a 和 ba\ 和\ b 的最大公约数。

证明

xab的公约数,则a=k1x,b=k2x假设a>bd=ab=(k1k2)xx也是d的约数gcd(a,b)=gcd(b,d)=gcd(b,ab)迭代到两个数相等,两个一样的数的最大公约数就是其本身x为a和b的公约数,则a=k_1x,b=k_2x\\ 假设a>b\\ \begin{aligned} 令d =& a-b\\ =&(k_1-k_2)x\\ \end{aligned}\\ 即x也是d的约数\\ 即gcd(a,b)=gcd(b,d)=gcd(b,a-b)\\ 迭代到两个数相等,两个一样的数的最大公约数就是其本身

代码实现

public static int gcd(int a, int b) {
    if (a < b) {
        return gcd(b, a);
    } else if (a == b) {
        return a;
    } else {
        return gcd(b, a - b);
    }
}

时间复杂度

辗转相减法和辗转相除法思路很类似,但如果当一个数很大,另一个数很小时,需要很多次减法才能达到一次除法的效果,使得此方法的时间复杂度退为 O(N) \ O(N)\ ,而辗转相除法则稳定于 O(log N)\ O(log\ N)

Stein算法(更相减损术优化版)

辗转相减法的缺点在于如果一个数很大,另一个数很小时,迭代次数会比较多。

Stein算法则对辗转相减法做了一些优化:

使用

在更相减损法中,若两个是偶数则同除以2,结果乘以2。若为一奇一偶则偶数除以2,结果不变(奇数必然不会被2约分)。若为两个奇数才相减。

详细流程参考代码

证明

只是辗转相减法的优化,证明参考辗转相减法。

代码实现

public static int gcd(int a, int b) {
  int k = 1;
  while ((a & 1) == 0 && (b & 1) == 0) {
    a = a >> 1;
    b = b >> 1;
    k = k << 1;
  }
  while (a != b) {
    while ((a & 1) == 0) {
      a = a >> 1;
    }
    while ((b & 1) == 0) {
      b = b >> 1;
    }
    if (a == b) {
      break;
    }
    if (a < b) {
      b = b - a;
    } else {
      int c = b;
      b = a - b;
      a = c;
    }
  }
  return a * k;
}
//使用递归的方式实现
public static int gcd(int a, int b) {
  return _gcd(a, b, 1);
}

private static int _gcd(int a, int b, int k) {
  if ((a & 1) == 0 && (b & 1) == 0) {
    return _gcd(a >> 1, b >> 1, k << 1);
  } else if ((a & 1) == 0) {
    a = a >> 1;
  } else if ((b & 1) == 0) {
    b = b >> 1;
  }
  if (a == b) {
    return a * k;
  } else if (a > b) {
    return _gcd(b, a - b, k);
  } else {
    return _gcd(a, b - a, k);
  }
}

时间复杂度

O(log N)O(log\ N)\\

与辗转相除法相比,每次迭代的运算要更简单,特别是数字较大时的取模运算可能会比较耗时,因此Stein算法的性能在一定程度上会更优秀一些。