求兩個數a和b的最大公約數,可以想到的是從[1,min(a,b)]枚舉每個正整數:
#include<iostream> using namespace std; int gcd(int a,int b) { int ans=1; for(int i=2;i<=min(a,b);++i) { if(a%i==0 && b%i==0) ans=i; } return ans; } int main() { int a,b; cin>>a>>b; cout<<gcd(a,b); return 0; }
不過當a和b規模比較大時,這種算法是不夠快的。有更快更優雅的算法。
- 首先給出一個定理:
gcd(a,b)=gcd(b,a-b) (a>=b)
證明:
設gcd(a,b)=m (m>=1)
則a%m=0,b%m=0
(a%m-b%m)%m=0
(a-b)%m=0
因為a%m=0,b%m=0,(a-b)%m=0,gcd(a,b)=m
所以gcd(a,b,a-b)=m;
下面證明gcd(b,a-b)=m,然后可以得到gcd(a,b)=gcd(b,a-b)。
設c=a-b
因為gcd(a,b,c)=m;
所以gcd(b,c)!=m的充要條件是存在一個數d (d>=1)使得(b/m)%d=0且(c/m)%d=0且(a/m)%d!=0。
下面用反證法:
設存在這樣的d
(c/m)%d=0
((a-b)/m)%d=0
((a/m)-(b/m))%d=0
((a/m)%d-(b/m)%d)%d=0
已知(b/m)%d=0,代入得((a/m)%d)%d=0
又已知(a/m)%d!=0,所以(a/m)%d的結果屬於(0,d)
而x屬於(0,d),x%d不可能等於0,因此矛盾。
所以不存在這樣的d
所以gcd(b,a-b)=gcd(b,c)=m
gcd(a,b)=gcd(b,a-b) (a>=b) 該定理證明完畢
於是就可以用這個算法來計算,其中gcd(a,0)=a:
#include<iostream> using namespace std; int gcd(int a,int b) { if(b==0) return a; if(a<b) swap(a,b); return gcd(b,a-b); } int main() { int a,b; cin>>a>>b; if(a<b) swap(a,b); cout<<gcd(a,b); return 0; }
當然數據規模大的時候棧可能會溢出,所以改成非遞歸即可。
還可以更快?(感謝一位同學的證明)
- 再給出第二個定理:
gcd(a,b)=gcd(b,a-k*b) 其中k=0,1,2,3,4...且a>=k*b
這個定理證明同上
- 經化簡可得下面這個定理:
gcd(a,b)=gcd(b,a%b)
這就是輾轉相除法(歐幾里得算法)。
#include<iostream> using namespace std; int gcd(int a,int b) { if(b==0) return a; return gcd(b,a%b); } int main() { int a,b; cin>>a>>b; cout<<gcd(a,b); return 0; }