一、題目要求
利用輾轉相除法、窮舉法、更相減損術、Stein算法求出兩個數的最大公約數或者/和最小公倍數。
最大公約數:指兩個或多個整數共有約數中最大的一個。
例如:【12和24】12的約數有:1、2、3、4、6、12;24的約數有:1、2、3、4、6、8、12、24。它們共有的約數爲:1、2、3、4、6、12,則12和24的最大公約數爲12
最小公倍數:兩個或多個整數公有的倍數叫做它們的公倍數,其中除0以外最小的一個公倍數就叫做這幾個整數的最小公倍數。
例如:【3和4】3的倍數有6、9、12、15、18、21、24……;4的倍數有4、8、12、16、20、24……。它們公有的倍數有12、24……,則3和4的最小公倍數爲12
運行時間:求每個函數運行時間,進行比較獲得最長及最短平均運行時間。
二、算法構建
【輾轉相除法】
具體做法是:用較大數除以較小數,再用出現的餘數(第一餘數)去除除數,再用出現的餘數(第二餘數)去除第一餘數,如此反覆,直到最後餘數是0爲止。如果是求兩個數的最大公約數,那麼最後的除數就是這兩個數的最大公約數。
實質上是以下式子:
根據這一定理可以採用函數嵌套調用和遞歸調用進行求兩個數的最大公約數和最小公倍數,現分別敘述如下:
①函數嵌套調用
求最大公約數:
其算法過程爲:設兩數爲a,b設其中a 做被除數,b做除數,temp爲餘數
1、大數放a中、小數放b中;
2、求a/b的餘數;
3、若temp=0則b爲最大公約數;
4、如果temp!=0則把b的值給a、temp的值給b;
5、返回第二步;
求最小公倍數:
一個簡單的方法直接求:a*b/最大公約數
//輾轉相除法函數嵌套求兩數的最大公約數
int divisor (int a,int b)
{
int temp; //定義整型變量
if(a<b) { //通過比較求出兩個數中的最大值和最小值
temp=a;
a=b;
b=temp;
} //設置中間變量進行兩數交換
while(b!=0) { //通過循環求兩數的餘數,直到餘數爲0
temp=a%b;
a=b; //變量數值交換
b=temp;
}
return a; //返回最大公約數到調用函數處
}
//輾轉相除法函數嵌套求兩數的最小公倍數
int multiple (int a,int b)
{
int divisor (int a,int b);
int temp;
temp=divisor(a,b); //再次調用自定義函數,求出最大公約數
return (a*b/temp); //返回最小公倍數到主調函數處進行輸出
}
②函數遞歸調用
int gcd_1 (int a,int b)
{
if(a%b==0)
return b;
else
return gcd_1(b,a%b);
}
【流程圖】
ps:在此僅給出輾轉相除法函數嵌套的流程圖,如果需要其餘算法的流程圖可以下載:流程圖下載
【n-s盒圖】
ps:在此僅給出輾轉相除法函數嵌套的n-s盒圖,如果需要其餘算法的圖可以下載n-s盒圖:
【窮舉法】
窮舉法(也叫枚舉法)的基本思想是根據題目的部分條件確定答案的大致範圍,並在此範圍內對所有可能的情況逐一驗證,直到全部情況驗證完畢。若某個情況驗證符合題目的全部條件,則爲本問題的一個解;若全部情況驗證後都不符合題目的全部條件,則本題無解。
解題思想:從兩個數中較小數開始由大到小列舉約數,直到找到公約數立即中斷列舉,得到的公約數便是最大公約數 。
解題步驟:
1、求最大公約數
對兩個正整數a,b如果能在區間[a,0]或[b,0]內能找到一個整數temp能同時被a和b所整除,則temp即爲最大公約數。
2、求最小公倍數
對兩個正整數a,b,如果若干個a之和或b之和能被b所整除或能被a所整除,則該和數即爲所求的最小公倍數。
//窮舉法求兩數的最大公約數
int divisor (int a,int b)
{
int temp; //定義義整型變量
temp=(a>b)?b:a; //採種條件運算表達式求出兩個數中的最小值
while(temp>0){
if (a%temp==0&&b%temp==0) //只要找到一個數能同時被a,b所整除,則中止循環
break;
temp--; //如不滿足if條件則變量自減,直到能被a,b所整除
}
return temp; //返回滿足條件的數到主調函數處
}
//窮舉法求兩數的最小公倍數
int multiple (int a,int b)
{
int p,q,temp;
p=(a>b)?a:b; //求兩個數中的最大值
q=(a>b)?b:a; //求兩個數中的最小值
temp=p; //最大值賦給p爲變量自增作準備
while(1){ //利用循環語句來求滿足條件的數值
if(p%q==0)
break; //只要找到變量的和數能被a或b所整除,則中止循環
p+=temp; //如果條件不滿足則變量自身相加
}
return p;
}
【更相減損術】
更相減損術,是出自《九章算術》的一種求最大公約數的算法,它原本是爲約分而設計的,但它適用於任何需要求最大公約數的場合。《九章算術》是中國古代的數學專著,其中的“更相減損術”可以用來求兩個數的最大公約數,即“可半者半之,不可半者,副置分母、子之數,以少減多,更相減損,求其等也。以等數約之。”其中所說的“等數”,就是最大公約數。求“等數”的辦法是“更相減損”法。所以更相減損法也叫等值算法。
解題步驟:
1、任意給定兩個正整數;判斷它們是否都是偶數。若是,則用2約簡;若不是則執行2。
2、以較大的數減較小的數,接着把所得的差與較小的數比較,並以大數減小數。繼續這個操作,直到所得的減數和差相等爲止。
則1中約掉的若干個2與2中等數的乘積就是所求的最大公約數。
//更相減損術求最大公約數
int gcd(int m,int n)
{
int i=0,temp,x;
while(m%2==0 && n%2==0) //判斷m和n能被多少個2整除
{
m/=2;
n/=2;
i+=1;
}
if(m<n){ //m保存大的值
temp=m;
m=n;
n=temp;
}
while(x){
x=m-n;
m=(n>x)?n:x;
n=(n<x)?n:x;
if(n==(m-n))
break;
}
if(i==0)
return n;
else
return ((int)pow(2,i)*n);
}
【Stein算法】
Stein算法由J. Stein 1961年提出,這個方法也是計算兩個數的最大公約數。來研究一下最大公約數的性質,發現有 gcd( k*x,k*y ) = k*gcd( x,y ) 這麼一個非常好的性質。試取 k=2,則有 gcd( 2x,2y ) = 2 * gcd( x,y )。很快聯想到將兩個偶數化小的方法。那麼一奇一個偶以及兩個奇數的情況如何化小呢?
先來看看一奇一偶的情況: 設有2x和y兩個數,其中y爲奇數。因爲y的所有約數都是奇數,所以 a = gcd( 2x,y ) 是奇數。根據2x是個偶數不難聯想到,a應該是x的約數。我們來證明一下:(2x)%a=0,設2x=n*a,因爲a是奇數,2x是偶數,則必有n是偶數。又因爲 x=(n/2)*a,所以 x%a=0,即a是x的約數。因爲a也是y的約數,所以a是x和y的公約數,有 gcd( 2x,y ) <= gcd( x,y )。因爲gcd( x,y )明顯是2x和y的公約數,又有gcd( x,y ) <= gcd( 2x,y ),所以 gcd( 2x,y ) = gcd( x,y )。至此,我們得出了一奇一偶時化小的方法。
再來看看兩個奇數的情況:設有兩個奇數x和y,不妨設x>y,注意到x+y和x-y是兩個偶數,則有 gcd( x+y,x-y ) = 2 * gcd( (x+y)/2,(x-y)/2 ),那麼 gcd( x,y ) 與 gcd( x+y,x-y ) 以及 gcd( (x+y)/2,(x-y)/2 ) 之間是不是有某種聯繫呢?爲了方便設 m=(x+y)/2 ,n=(x-y)/2 ,容易發現 m+n=x ,m-n=y 。設 a = gcd( m,n ),則 m%a=0,n%a=0 ,所以 (m+n)%a=0,(m-n)%a=0 ,即 x%a=0 ,y%a=0 ,所以a是x和y的公約數,有 gcd( m,n )<= gcd(x,y)。再設 b = gcd( x,y )肯定爲奇數,則 x%b=0,y%b=0 ,所以 (x+y)%b=0 ,(x-y)%b=0 ,又因爲x+y和x-y都是偶數,跟前面一奇一偶時證明a是x的約數的方法相同,有 ((x+y)/2)%b=0,((x-y)/2)%b=0 ,即 m%b=0 ,n%b=0 ,所以b是m和n的公約數,有 gcd( x,y ) <= gcd( m,n )。所以 gcd( x,y ) = gcd( m,n ) = gcd( (x+y)/2,(x-y)/2 )。
整理一下,對兩個正整數 x>y :
1、均爲偶數 gcd( x,y ) =2gcd( x/2,y/2 );
2、均爲奇數 gcd( x,y ) = gcd( (x+y)/2,(x-y)/2 );
3、x奇y偶 gcd( x,y ) = gcd( x,y/2 );
4、x偶y奇 gcd( x,y ) = gcd( x/2,y ) 或 gcd( x,y )=gcd( y,x/2 );
現在已經有了遞歸式,還需要再找出一個退化情況。注意到 gcd( x,x ) = x ,就用這個。
//Stein算法非遞歸調用求最大公約數
int Stein(unsigned int x,unsigned int y)
{
int factor = 0;
int temp;
if ( x < y ){
temp = x;
x = y;
y = temp;
}
if ( 0 == y )
return 0;
while ( x != y ){
if ( x & 0x1 ){ //x爲奇數
if ( y & 0x1 ){ //x和y都爲奇數
y = ( x - y ) >> 1;
x -= y;
}
else{ //x爲奇數y爲偶數
y >>= 1;
}
}
else{ //x爲偶數
if ( y & 0x1 ){ //x爲偶數y爲奇數
x >>= 1;
if ( x < y ){
temp = x;
x = y;
y = temp;
}
}
else{ //x和y都爲偶數
x >>= 1;
y >>= 1;
++factor;
}
}
}
return ( x << factor );
}
//Stein算法遞歸調用求最大公約數
int gcd(int u,int v)
{
if (u == 0) return v;
if (v == 0) return u;
//找2的因素
if (~u & 1) // u是偶數
{
if (v & 1) //v是奇數
return gcd(u >> 1, v);
else //u和v都是偶數
return gcd(u >> 1, v >> 1) << 1;
}
if (~v & 1) //u是奇數,v是偶數
return gcd(u, v >> 1);
// 減少較大的參數
if (u > v)
return gcd((u - v) >> 1, v);
return gcd((v - u) >> 1, u);
}
三、算法運行時間
記錄每次運行並乘以一個很大的數(因爲一次運行時間極短接近於0)。
頭文件:
#include <windows.h>
代碼部分:
//求輾轉相除法函數嵌套算法運行時間
float run_time_1;
_LARGE_INTEGER time_start; //算法開始時間
_LARGE_INTEGER time_over; //算法結束時間
double dqFreq; //計時器頻率
LARGE_INTEGER f; //計時器頻率
QueryPerformanceFrequency(&f);
dqFreq=(double)f.QuadPart;
int s=100000; //運行次數以記錄每次運行時間
QueryPerformanceCounter(&time_start); //計時開始
while(s--)
t1=divisor_1(m,n); //輾轉相除法函數嵌套求最大公約數 此處寫運行函數
QueryPerformanceCounter(&time_over); //計時結束
run_time_1=1000000*(time_over.QuadPart-time_start.QuadPart)/dqFreq; //乘以 1000000把單位由秒化爲微秒,精度爲1000 000/(cpu主頻)微秒
printf("輾轉相除法函數嵌套求最大公約數函數平均運行時間爲:%fus\n\n",run_time_1);
四、心得體會
整個程序的源代碼請見:https://pan.baidu.com/s/1Sr1pkDK4G7PMraoTUei5Ww 提取碼:h353 ,可能提示格式無法顯示,直接下載就可以了,cpp格式的
中途遇到的最大問題可能就是運行時間了吧,一開始跑出來總是零,後面經過大佬的提醒纔想到可以通過乘以一個很大的數來轉換,因此也解決了問題XDDD