算法学习系列之求最大公约数

特别说明:本文参考学习于Java语言程序设计进阶篇—Y.Daniel Liang著一书
欢迎大家一起学习交流,希望程序的世界有你相伴

题目

给定两个大于0的实数,求出他们的最大公约数

解法

方法1

求两个数的最大公约数,不妨可以从1开始,判断1是否能被m,n整除,如果整除则1是m,n的约数,同理进行2,3,··· max(m,n)。最后在这些约数之间选出最大的那个(实际上,就是最后一个约数,因为1,2,···是递增的)。代码实现如下:

    public static int gcd(int m, int n) {
        int gcd = 1;
        for (int k = 2; k <= m && k <= n; k++) {
            if (m % k == 0 && n % k == 0)
                gcd = k;
        }
        return gcd;
    }

时间复杂度

假设m>=n,很显然该算法的时间复杂度为O(n)。

优化

我们在写完算法的同时,还应有再思考一下,是否有更好的实现,毕竟没有一个程序是完美的,都是走在完美的路上。本方法是从1开始向上寻找约数。那么我们是否可以逆向思维,从上到下的去寻找最大公约数呢?

方法2

首先,我们求出m,n的最小值,不妨设之为n,那么从n开始判断是否能同时被n,m整除,如果能整除,那么n就是最大公约数(因为此时数列是递减的),否则判断n-1,···,1。代码实现如下:

    public static int gcd(int m, int n) {
        int gcd = Math.min(m, n);
        while (gcd > 0) {
            if (n % gcd == 0 && m % gcd == 0) {
                break;
            }
            gcd--;
        }
        return gcd;
    }

时间复杂度

这个算法比前一个效率更高,但是它的最坏情况的时间复杂度依旧是O(n)。

优化

我们再仔细想想,还能不能有更好的算法,我们的计算是否还存在多余的部分。在整除里面,数字n的除数不可能比n/2大,因此我们可以利用该性质,继续优化我们的算法。

方法3

首先,我们求出m,n的最小值,不妨设之为n,我们首先判断n是否为n,m的最大公约数,如果相等,直接返回n。不相等的话,我们从n/2开始判断,之后判断n/2-1,···1。代码实现如下:

    public static int gcd(int m, int n) {
        int gcd = Math.min(m, n);
        if (n % gcd == 0 && m % gcd == 0) return gcd;
        for (int k = gcd / 2; k >= 1; k--) {
            if (n % k == 0 && m % k == 0) break;
            gcd = k - 1;
        }
        return gcd;
    }

时间复杂度

易知该算法的时间复杂度还是O(n),但是也很容易的证明,该算法虽然时间复杂度和前两种一样,但是它的运算效率要比前两种高得多。

方法4

其实,求最大公约数的一个更有效的算法是在公元前300年左右由欧几里得发现的,这是最古老的著名算法之一。它可以递归地定义为:
用gcd(m,n)表示整数m,n的最大公约数:
如果m%n为0,那么gcd(m,n)为n。
否则,gcm(m,n)就是gcm(n,m%n)

不难证明这个算法的正确性。假设m%n=r,那么,m=qn+r,这里的q是m/n的商。能整除m和n的任意数字都必定也能整除r。因此gcd(m,n)和gcd(n,r)是一样的。其中r=m%n。该算法的实现如下:

    public static int gcd(int m, int n) {
        if (m < n) {
            int temp = n;
            n = m;
            m = temp;
        }
        if (m % n == 0) return n;
        else return gcd(n, m % n);
    }

时间复杂度

我们可以证明最坏的情况的时间复杂度O(logn)
假设m>=n,我们可以证明m%n

点赞