GCD算法

GCD(get Greatest Common Divisor)获得最大公约数的方法。

辗转相除法

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

定理两个正整数 a 和 b (a>b),它们的最大公约数等于a除以 b的余数 c 和 b 之间的最大公约数

例如:求10 和 25的最大公约数,可以先求25除以10商2余5,那么10和25的最大公约数,等同于10和5的最大公约数,依次类推。

代码实现:

//迭代,辗转相除法
public static int gcd(int a, int b) {
    //判断a和b谁大
    if (a < b) {
        a ^= b;
        b ^= a;
        a ^= b;
    }
    int sum = a % b;
    while (sum != 0) {
        a = b;
        b = sum;
        sum = a % b;
    }
    return b;
}

//递归,辗转相除法
public static int gcd2(int a, int b) {
    if (a < b) {
        a ^= b;
        b ^= a;
        a ^= b;
    }
    if (a % b == 0) {
        return b;
    }
    return gcd2(b, a % b);
}

更相减损术

更相减损术也是一种求最大公约数的算法。

原理:两个正整数a和b(a>b),它们的最大公约数等于 a-b 的减值c和较小数b的最大公约数。

例如:求10 和 25的最大公约数,可以先求25减10的差是15,那么10和25的最大公约数,等同于10和15的差,依次类推。

代码实现:

//迭代,更相减损术
public static int gcd3(int a, int b) {
    if (a < b) {
        a ^= b;
        b ^= a;
        a ^= b;
    }
    int sum = a - b;
    while (sum != 0) {
        a = b;
        b = sum;
        sum = a - b;
    }
    return b;
}

//递归,更相减损术
public static int gcd4(int a, int b) {
    if (a < b) {
        a ^= b;
        b ^= a;
        a ^= b;
    }
    if (a - b == 0) {
        return b;
    }
    return gcd4(b, a - b);
}

位运算优化

a和b的4种情况分析:

  1. 当a和b均为偶数时,gcd(a,b) = 2 * gcd(a / 2 , b / 2) = 2 * gcd(a >> 1, b >> 1)
  2. 当a为偶数,b为奇数时,gcd(a, b) = gcd(a / 2, b) = gcd(a >> 1, b)
  3. 当a为奇数,b为偶数时,gcd(a, b) = gcd(a, b / 2) = gcd(a, b >> 1)
  4. 当a和b均为奇数时,先用更相减损术运算一次,gcd(a, b) = gcd(b, a - b),这时 a - b一定是偶数,然后就可以继续使用位运算。

例如:求10和25的最大公约数步骤如下。

  • 整数10通过移位,可以转换成求5和25的最大公约数。
  • 利用更相减损术,计算出25−5=20,转换成求5和20的最大公约数。
  • 整数 20通过移位,可以转换成求5和10的最大公约数。
  • 整数 10通过移位,可以转换成求5和5的最大公约数。
  • 利用更相减损术,因为两数相等,所以最大公约数是 5。

代码实现:

public static int gcd5(int a, int b) {
    //a,b相等直接返回
    if (a == b) {
        return b;
    }
    //四种情况
    if ((a & 1) == 0 && (b & 1) == 0) {// a,b都为偶数
        return gcd5(a >>> 1, b >>> 1) << 1;// 等价于 gcd5(a/2,b/2)*2
    } else if ((a & 1) == 0 && (b & 1) == 1) {// a为偶数,b为奇数
        return gcd5(a >>> 1, b);
    } else if ((a & 1) == 1 && (b & 1) == 0) {// a为奇数,b为偶数
        return gcd5(a, b >>> 1);
    } else {//都为奇数
        //进行一次更相减损术,求绝对值
        return gcd5(b, Math.abs(a - b));
    }
}

时间复杂度分析

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

你可能感兴趣的:(数据结构与算法,算法,数据结构,java)