求最大公约数的两种方法

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

欧几里德算法又称辗转相除法,是指用于计算两个正整数a,b的最大公约数。
时间复杂度为\(O(logN)\)

举例

比如:30和42的最大公约数:
\(30 \mod 42 = 30\)
\(42 \mod 30 = 12\)
\(30 \mod 12 = 6\)
\(12 \mod 6 = 0\)
那么,30和42的最大公约数就是6。

代码实现

int gcd(int a, int b) {
    while (b > 0) {
        c = a % b;
        a = b;
        b = c;
    }
    return a;
}

我们也可以使用递归的方法来实现。

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

性能分析

由于辗转相除法的时间复杂度为\(O(logN)\),在遇到比较大的数时(\(N\ge{10^{256}}\)),计算起来就会比较慢了。
这是因为辗转相除法中含有模运算,当\(a\mod{b}\)时,相当于\(a\)减了\(k\)\(b\),也就是\(a - k * b\)
比如:
\(42 \mod 30 = 42 - 1 * 30\)
\(30 \mod 12 = 30 - 2 * 12\)

更相减损术

更相减损术是出自《九章算术》的一种求最大公约数的算法,它原本是为约分而设计的,但它适用于任何需要求最大公约数的场合。
最坏时间复杂度为\(O(N)\)

举例

\(a>b\)时,用\(a-b\);当\(b>a\)时,用\(b-a\)
\(\gcd(30, 42) = \gcd(12, 30) = \gcd(18, 12) = \gcd(6, 12) = \gcd(6, 6)\)
\(a=b\)时,\(a\)就是这两个数的最大公约数。

代码实现

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

更相减损术二分版

上文提到,在两个差距非常大的数(\(a=10000, b=1\)),使用更相减损术的时间复杂度为\(O(N)\)
这个算法的时间复杂度为\(O(logN)\)

代码实现

可以使用按位与(&)来代替模运算。
例如:\((5)_{10} = (101)_{2}\),我们可以使用5&1来达到和模运算符一样的效果。
我们知道,按位左移1位就是乘2,右移一位就是除以2。

int gcd(int a, int b) {
    if (a == b) return a;
    if ((a & 1) && (b & 1)) return gcd(a >> 1, b >> 1) << 2; // a和b都是偶数
    else if ((a & 1) && !(b & 1)) return gcd(a >> 1, b); // a是偶数b是奇数
    else if (!(a & 1) && (b & 1)) return gcd(a, b >> 1); // a是奇数b是偶数
    else if (!(a & 1) && !(b & 1)) { // a和b都是奇数
        if (a > b) return gcd(a - b, b);
        else if (a < b) return gcd(b - a, a);
    }
}

参考

感谢Vita小老师~
【算法小知识】如何求最大公约数(上)
【算法小知识】如何求最大公约数(下)

posted @ 2020-05-09 18:49  RainbowBird  阅读(1522)  评论(0编辑  收藏  举报