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);
}
}
}
參考《漫畫算法-小灰的算法之旅》