求兩個整數的最大公約數

要求方法傳兩個正整形參數,返回值就是他們的最大公約數。

暴力枚舉法

思路就是尋找一個整數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);
                }
            }
        }
    }
}

總結

以上算法的時間複雜度:

  1. 暴力枚舉法:O(min(a,b))
  2. 輾轉相除法:時間複雜度不太好計算,可近似爲O(log(min(a,b))) ,但是取模性能較差。
  3. 更相減損術:避免了取模運算,但是算法不穩定,最壞時間複雜度爲O(max(a,b))
  4. 優化算法:不但避免了取模,而且算法穩定,時間複雜度爲O(log(max(a,b)))

  1. 更相減損術,出自於中國的古代的《九章算術》,也是一種求最大公約數的算法。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章