1.歐幾里得算法求最大公約數
求最大公約數是一個比較基礎的問題,歐幾里得早在《幾何原本》中就闡明了一個高效的算法,據說這大概發生在公元前300年左右。具體是這樣的:假設把x和y的最大公約數表示成為f(x,y),並且x>=y>0。現在取k=x/y,b=x%y,則x=k*y+b,變形為b=x - k*y;x和y能被f(x,y)整除,那么b也能被f(x,y)整除;所以
f(x,y) = f(y,x%y)。顯然代碼相當的簡單啊,如下:
int GCD(int x,int y)
{
return (!y) ? x : GCD(y,x%y);
}
2.輾轉相除法的變形
想一想輾轉相除法有沒有什么問題呢?例如,利用上面的函數求GCD(1100100210001,120200021),即對大整數求最大公約數,輾轉相除法的效率就出現了瓶頸,實際上對於大整數而言,取模運算(用到除法)的開銷非常的昂貴,這就是歐幾里得算法的局限性,那么怎么優化一下呢?可以借鑒歐幾里得的輾轉相除法,既然是取模運算導致的問題,那么我們就不用取模運算,換用“-”運算,即 f(x,y)=f(x-y,y);深入考慮一下發現在算法運行的過程可能會出現x<y的情況,這時候要交換x和y,但是結果不受影響。再來看看代碼吧。
BigInt GCD(BigInt x,BigInt y)
{
if(x < y) return GCD(y,x);
return (!y) ? x : GCD(x-y,y);
}
代碼中用到的BigInt是大整數類,可以存儲成百上千位的整數。這個類怎么實現呢?這里不多說,具體網上有好多的相關文章來講解高精度算法的,要是看書籍的話,強烈推薦ACM大牛劉汝佳的《算法競賽入門經典》一書,對高精度的介紹還是非常好的。
對於大數運算問題是解決了,然而,細心的讀者會發現,這個算法引入了另外一個問題,那就是當x和y相差很多時,算法的迭代次數會過高,導致了算法的效率較低,例如計算GCD(100000000000001,1)。這種情況確實存在啊,於是我們要考慮其他的優化了。
3.利用移位運算符優化輾轉相除法
上面的兩種方法一個不適合解決大整數的問題,另一個不適合解決迭代次數過多的情況,那么能付把兩種方法結合一下來得到一個能夠讓人接受的方法呢?答案是肯定的。我們注意到:
(1)如果y=k*y1,x=k*x1.那么有f(x,y)=k*f(x1,y1)
(2)如果x=p*x2,p為素數,並且y%p != 0,那么f(x,y) = f(p*x2,y) = f(x2,y)
於是我們得到下面的解決方法:
將p = 2,
若x,y均為偶數,f(x,y) = 2*f(x/2,y/2) = 2*f(x>>1,y>>1);
若x是偶數,y是奇數,f(x,y) = f(x/2,y) = f(x>>1,y);
若x是奇數,y是歐式,f(x,y) = f(x,y/2) = f(x,y>>1);
若x和y均為奇數,f(x,y) = f(y,x-y)。這時x-y一定是偶數,下一步一定會除以2。
因此,可以看出這種算法的時間復雜度是O(log2(max(x,y))。
代碼如下:
BigInt GCD(BigInt x,BigInt y)
{
if(x < y) return GCD(y,x);
if(y == 0) return x;
else
{
if(IsEven(x))
{
if(IsEven(y))
return (GCD(x>>1,y>>1)<<1);
return GCD(x>>!,y);
}
else
{
if(IsEven(y))
return GCD(x,y>>1);
return GCD(y,x-y)
}
}
}
這種方法巧妙的使用了移位運算和減法運算,避開了大整數除法,提高了算法的效率,這是值得學習的。另外這三種算法都是屬於尾遞歸可以轉化成循環的方式實現來進一步提高效率,感興趣的可以自行實現。
學習中的一點總結,歡迎拍磚哦^^