title: Java实现:求两个数的最大公约数
tags:
- java
- 算法
categories: 个人笔记
copyright: true
abbrlink: f202
date: 2019-12-07 16:44:58
求解两个数的最大公约数的几种方法的比较
1. 暴力枚举法
-
优点:思路简单
-
缺点:运算次数多,效率低
-
极端例子:求1000和10001的最大公约数
需要计算1000/2 - 1 = 4999次
// 暴力枚举法
public static int getGreatestCommonDivisor(int a,int b) {
int big = a > b ? a : b;
int small = a < b ? a : b;
if (big % small == 0) {
return small;
}
for (int i = small / 2; i > 1; i--) {
if (big % i == 0 && small % i == 0) {
return i;
}
}
return 1;
}
2. 辗转相除法
- 优点:运算次数少
- 确定:模运算的开销较大
// 辗转相除法
public static int getGreatestCommonDivisor2(int a, int b) {
int big = a > b ? a : b;
int small = a < b ? a : b;
if (big % small == 0) {
return small;
}
return getGreatestCommonDivisor2(big % small, small);
}
3. 更相减损法
- 优点:避免了取模运算,采用减法运算,开销较小
- 缺点:算法性能不稳定,运算次数多
- 极端例子:两数相差悬殊时,如求1和10001的最大公约数,需要递归9999次
// 更相减损法
public static int getGreatestCommonDivisor3(int a, int b) {
if(a == b){
return a;
}
int big = a > b ? a : b;
int small = a < b ? a : b;
return getGreatestCommonDivisor2(big - small, small);
}
4. 结合辗转相除法和更相减损法(位运算优化)
当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(a-b,b),此时a-b必然是偶数,接而继续进行移位运算。
- 优点:不但避免了取模运算,而且算法性能稳定
- 缺点:代码比较复杂
//更相减损法(位运算优化)
public static int getGreatestCommonDivisor4(int a, int b) {
if (a == b) {
return a;
}
if ((a & 1) == 0 && (b & 1) == 0) {// a,b都是偶数
return getGreatestCommonDivisor4(a >> 1, b >> 1) << 1;
} else if ((a & 1) == 0 && (b & 1) != 0) {// a为偶数,b为奇数
return getGreatestCommonDivisor4(a >> 1, b);
} else if ((a & 1) != 0 && (b & 1) == 0) {// a为奇数,b为偶数
return getGreatestCommonDivisor4(a, b >> 1);
} else {// a,b都是奇数
int big = a > b ? a : b;
int small = a < b ? a : b;
return getGreatestCommonDivisor4(big - small, small);
}
}
5. 时间复杂度
- 暴力枚举法:O(min(a,b))
- 辗转相除法:近似于O(log(max(a,b)))
- 更相减损法: O(max(a,b))
- 结合辗转相除法和更相减损法(位运算优化):O(log(max(a,b)))
6. 完整代码
package gcd;
public class GreatestCommonDivisor {
public static void main(String[] args) {
System.out.println(getGreatestCommonDivisor(75, 180));
System.out.println(getGreatestCommonDivisor2(75, 180));
System.out.println(getGreatestCommonDivisor3(75, 180));
System.out.println(getGreatestCommonDivisor4(75, 180));
}
// 暴力枚举法
public static int getGreatestCommonDivisor(int a,int b) {
int big = a > b ? a : b;
int small = a < b ? a : b;
if (big % small == 0) {
return small;
}
for (int i = small / 2; i > 1; i--) {
if (big % i == 0 && small % i == 0) {
return i;
}
}
return 1;
}
// 辗转相除法
public static int getGreatestCommonDivisor2(int a, int b) {
int big = a > b ? a : b;
int small = a < b ? a : b;
if (big % small == 0) {
return small;
}
return getGreatestCommonDivisor2(big % small, small);
}
// 更相减损法
public static int getGreatestCommonDivisor3(int a, int b) {
int big = a > b ? a : b;
int small = a < b ? a : b;
if (big % small == 0) {
return small;
}
return getGreatestCommonDivisor2(big - small, small);
}
//更相减损法(位运算优化)
public static int getGreatestCommonDivisor4(int a, int b) {
if (a == b) {
return a;
}
if ((a & 1) == 0 && (b & 1) == 0) {// a,b都是偶数
return getGreatestCommonDivisor4(a >> 1, b >> 1) << 1;
} else if ((a & 1) == 0 && (b & 1) != 0) {// a为偶数,b为奇数
return getGreatestCommonDivisor4(a >> 1, b);
} else if ((a & 1) != 0 && (b & 1) == 0) {// a为奇数,b为偶数
return getGreatestCommonDivisor4(a, b >> 1);
} else {// a,b都是奇数
int big = a > b ? a : b;
int small = a < b ? a : b;
return getGreatestCommonDivisor4(big - small, small);
}
}
}
参考《漫画算法-小灰的算法之旅》