要求方法傳兩個正整形參數,傳回值就是他們的最大公約數。
暴力枚舉法
思路就是尋找一個整數 i ,從2開始循環累加,一直累加到兩個整數中較小的一個的一半為止。循環結束後,上一次尋找到的能被兩整數整除的最大i值,就是這兩個整數的最大公約數。
參考代碼:
public static int getGreatestCommonDivisor(int numA, int numB)
{
int smallNum = numA < numB ? numA : numB;
int bigNum = numA >= numB ? numA: numB;
if(bigNum % smallNum == 0)
{
return smallNum;
}
int greatestCommonDivisor = 1;
for(int i = 0; i <= smallNum/2; i++)
{
if(numA % i == 0 && numB % i ==0)
{
greatestCommonDivisor=i;
}
}
return greatestCommonDivisor;
}
輾轉相除法
輾轉相除法,又名歐幾裡得算法,目的就是求出兩個正整數的最大公約數。它是已知的最古老算法,可追溯至公元前300年前。
這條算法基于這樣一個定理:兩個正整數a和b(a>b),它們的最大公約數等于a除以b的餘數c和b之間的最大公約數。比如10和25,25除以10商2餘5,那麼10和25的最大公約數等同于10和5的最大公約數。
有了這條定理,我們可以使用遞歸的方法來把問題逐漸簡化,逐漸把兩個較大整數之間的運算簡化成兩個較小整數之間的運算,直到兩個數可以相除,或者其中一個數減小到1為止。
參考代碼:
public static int getGCD(int numA, int numB)
{
int result=1;
if(numA>numB)
{
result=gcd(numA,numB);
}
else
{
result=gcd(numB,numA);
}
return result;
}
private static int gcd(int a, int b)
{
if(a%b==0)
{
return b;
}
else
{
return gcd(b,a%b);
}
}
更相減損術
以上這就是輾轉相除法的思路。但是,當兩個整數較大時,做a%b取模運算的性能會比較低。此時,另一種算法,更相減損術1,用來優化以上代碼,避免大整數取模的性能問題。
它的原理更加簡單:兩個正整數a和b(a>b),它們的最大公約數等于a-b的內插補點c和較小數b的最大公約數。比如10和25,25-10的內插補點是15,那麼10和25的最大公約數等同于10和15的最大公約數。
同樣可以使用遞歸的方法來計算,參考代碼如下:
public static int getGCD(int numA, int numB)
{
if(numA==numB)
{
return numA;
}
if(numA>numB)
{
return getGCD(numA-numB,numB);
}
else
{
return getGCD(numB-numA,numA);
}
}
優化算法
更相減損術避免了大整數取模的性能問題,但是依靠兩數求差的方式來遞歸運算次數肯定大于輾轉相除的取模方法。它是一種不穩定的算法,當兩數相差懸殊時,比如10000和1,就要遞歸9999次。
最優的方法是把輾轉相除法和更相減損術的優勢結合起來,在更相減損術的基礎上使用移位運算。
衆所周知,移位運算的性能非常快,對于給定的正整數a和b,不難得到如下的結論(其中gcd(a,b)的意思是a,b的最大公約數函數):
- 當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(b,a-b),此時a-b必然是偶數,又可以繼續進行移位運算。
當兩個數比較大時,計算性能就能展現出來。
參考代碼:
public static int gcd(int a, int b)
{
if(a==b)
{
return a;
}
if(a<b)
{
//保證參數a永遠大于b,為減少代碼量
return gcd(b,a);
}
else
{
if(!a&1 && !b&1)
{
return gcd(a>>1,b>>1) <<1;
}
else
{
if(!a&1 && b&1)
{
return gcd(a>>1,b);
}
else
{
if(a&1 && !b&1)
{
return gcd(a,b>>1);
}
else
{
return gcd(b,a-b);
}
}
}
}
}
總結
以上算法的時間複雜度:
- 暴力枚舉法: O(min(a,b))
- 輾轉相除法:時間複雜度不太好計算,可近似為 O(log(min(a,b))) ,但是取模性能較差。
- 更相減損術:避免了取模運算,但是算法不穩定,最壞時間複雜度為 O(max(a,b)) 。
- 優化算法:不但避免了取模,而且算法穩定,時間複雜度為 O(log(max(a,b))) 。
- 更相減損術,出自于中國的古代的《九章算術》,也是一種求最大公約數的算法。 ↩