求最大公約數不同演算法的時間比較(輾轉相除法,更相減損術等)
後附完整原始碼:(含有求最大公倍數的函式,此程式碼測試的是面對不同組資料,各種演算法的效能分析,如需輸出最大公約數,只需加個輸出就好!)
一、 題目分析
首先,題目要求我們求出倆個正整數的最大公約數和最小公倍數,並提供了6種不同的演算法(遞迴與非遞迴算倆種)
1. 輾轉相除法:此種方法核心如其名,輾轉,大數放a中,小數放b中。大的除以小的,餘數為零,則小數為最大公約數,不為零則,將餘數與b比較大的放a,小的放b,如此往復!
2. 窮舉法:看到這個窮舉就知道它是最吃力不討好的演算法了。一個個窮舉不僅浪費CPU,還浪費時間對吧,對兩個正整數a,b如果能在區間[a,0]或[b,0]內能找到一個整數temp能同時被a和b所整除,則temp即為最大公約數。
3. 更相減損術:
第一步:任意給定兩個正整數;判斷它們是否都是偶數。若是,則用2約簡;若不是則執行第二步。
第二步:以較大的數減較小的數,接著把所得的差與較小的數比較,並以大數減小數。繼續這個操作,直到所得的減數和差相等為止。
則第一步中約掉的若干個2與第二步中等數的乘積就是所求的最大公約數。
其中所說的“等數”,就是最大公約數。求“等數”的辦法是“更相減損”法。所以更相減損法也叫等值演算法!
4. Stein演算法:
通過數學的思想進行驗證出:對於倆個倆個正整數數(x>y):
均為偶數gcd(x,y)=2gcd(x/2,y/2);
均為奇數gcd(x,y)=gcd((x+y)/2,(x-y)/2);
x奇y偶gcd(x,y)=gcd(x,y/2);
x偶y奇gcd(x,y) =gcd(x/2,y)或gcd(x,y)=gcd(y,x/2);
也就是說可以對倆奇倆偶和一奇一偶的情況進行化簡,使其更容易計算。
又因為該演算法採用位移運算所以減少了很多時間,所以也是很厲害的演算法。
我們的主要目的是為了比較各個演算法的效能優劣,通過程式的執行,輸出不同演算法計算20,40,80…200組甚至更大的資料的計算時間。
二、 演算法構造
可愛的狗狗走錯片場:
① 輾轉相除法(非遞迴):
演算法流程圖:
② 輾轉相除法(非遞迴):
演算法盒圖:
③ 輾轉相除法(遞迴):
④ 輾轉相除法(遞迴盒圖):
⑤ 窮舉法流程圖:
⑥ 窮舉法(盒圖):
⑦ 更相減損術(流程圖):
⑧ 更相減損術(盒圖):
⑨ Stein演算法(流程圖):
⑩ Stein演算法(遞迴盒圖):
三、 演算法實現
#include<iostream>
using namespace std;
#include<math.h>
#include<windows.h>
#include<time.h>
class Math
{
private:
int temp;
public:
int divistor(int,int); //NO.1,輾轉相除法
int multiple(int,int);
int digui(int,int); //NO.2,輾轉相除法遞迴
int multiple1(int,int);
int divistor3(int,int);//NO.3,窮舉法
int multiple3(int,int);
int divistor4(int,int);//NO.4,更相減損術
int multiple4(int,int);
int gcd(int,int); //NO.5,stein演算法遞迴演算法
int multiple5(int,int);//求最大公倍數
int gcd1(int,int); //NO.6,stein演算法非遞迴演算法
int multiple6(int,int);//求最大公倍數
};
int Math::divistor(int x,int y)//x是大數,y是小數,非遞迴實現 NO.1
{
if(x<y)
{
temp=x;
x=y;
y=temp;
}
while(y!=0) //當y是0時,最大公約數就是x;
{
temp=x%y;
x=y; //這3句不能交換位置,否則temp值會出錯
y=temp;
}
return x;
}
int Math::multiple(int x,int y) //求最大公倍數
{
temp=divistor(x,y);//
return ((x*y)/temp);
}
int Math::digui(int x,int y)//無需區別x,y的大小,遞迴方法
{
if(x%y==0)
return y;
else
return digui(y,x%y);
}
int Math::multiple1(int x,int y) //求最大公倍數
{
temp=digui(x,y);
return ((x*y)/temp);
}
int Math::divistor3(int x,int y)
{
temp=(x>y)?y:x;
while(temp>0)
{
if(x%temp==0&&y%temp==0)
break;
else temp--;
}
return temp;
}
int Math::multiple3(int x,int y)
{
int p,q;
p=x>y?x:y;//xiaozhi
q=x>y?y:x;//dazhi
temp=p;
while(1)
{
if(p%q==0)
break;
else
p+=temp;
}
return p;
}
int Math::divistor4(int x,int y)
{
int i=0;
int z=1;
while(x%2==0 && y%2==0) //判斷m和n能被多少個2整除
{
x/=2;
y/=2;
i+=1;
}
if(x<y) //x是大數
{
temp=x;
x=y;
y=temp;
}
while(z)
{
z=x-y;
x=(y>z)?y:z;
y=(y<z)?y:z;
if(y==x)
break;
}
if(i==0)
return y;
else
return (int )pow(2,i)*y;
}
int Math::multiple4(int x,int y) //求最大公倍數
{
temp=divistor4(x,y);
return ((x*y)/temp);
}
int Math::gcd(int x,int y) //stein演算法遞迴演算法
{
if(x<y) //x是大數
{
temp=x;
x=y;
y=temp;
}
if(y==0) return x;
if((x&0x1)==0&&(y&0x1)==0) return 2*gcd(x>>1,y>>1); //倆數都是偶數
if((x&0x1)==0&&(y&0x1)!=0) return gcd(x>>1,y); //一偶一奇
if((x&0x1)!=0&&(y&0x1)==0) return gcd(x,y>>1); //一奇一偶
if((x&0x1)!=0&&(y&0x1)!=0) return gcd((x-y)>>1,y); //倆奇
}
int Math::multiple5(int x,int y) //求最大公倍數
{
temp=gcd(x,y);
return ((x*y)/temp);
}
int Math::gcd1(int a,int b) //stein演算法非遞迴
{
int acc=0;
while((a&0x1)==0&&(b&0x1)==0)
{
++acc;
a>>=1;
b>>=1;
}
while((a&0x1)==0) a>>=1;
while((b&0x1)==0) b>>=1;
if(a<b) {int t=a; a=b; b=t;}
while((a=(a-b)>>1)!=0)
{
while((a&0x1)==0) a>>=1;
if (a<b) {int t=a; a=b; b=t;}
}
return b<<acc;
}
int Math::multiple6(int x,int y) //求最大公倍數
{
temp=gcd1(x,y);
return ((x*y)/temp);
}
int main()
{
int x,y,i,t; //t為陣列的元素個數
char c[20]; //用於判斷整數的正確性
Math m; //建立物件m
double z;
LARGE_INTEGER nFreq; //時間
LARGE_INTEGER nBeginTime;
LARGE_INTEGER nEndTime;
QueryPerformanceFrequency(&nFreq);
cout<<"請輸入小於10000的測試組數(正整數:一組倆個數):"<<endl;
cin.getline(c,7); //7為函式讀取的制定字元
if((c[0]-48)<=0||(c[0]-48)>9) {cout<<"輸入的不是正整數,請重新輸入!"; return 0;}
for(i=1;i<20;i++)
{
if(c[i]=='\0') break;
if((c[i]-48)<0||(c[0]-48)>9) {cout<<"輸入的不是正整數,請重新輸入!"; return 0;}
}
i=atoi(c);
if(i<0||i>10000) {cout<<"輸入數超出範圍,請重新輸入!"; return 0;}
int a[20000];
t=2*i;
srand((unsigned)time(NULL)); //產生隨機數種子,避免產生的隨機數相同
for(i=0;i<t;i++) //初始化陣列
{
a[i]=rand();
}
QueryPerformanceCounter(&nBeginTime);//開始計時
for(i=0;i<t;i++)
{
x=a[i];
y=a[i+1];
i+=1; //讓x,y正好取得陣列中的元素,如果沒有i+=1會出錯
m.divistor(x,y);
//m.multiple(x,y);
}
QueryPerformanceCounter(&nEndTime);//結束計時
z=(double)(nEndTime.QuadPart-nBeginTime.QuadPart)/(double)nFreq.QuadPart;//計算程式執行時間單位為s
cout<<"輾轉相除法非遞迴所用時長:"<<z*1000<<"毫秒"<<endl;
QueryPerformanceCounter(&nBeginTime);//開始計時
for(i=0;i<t;i++)
{
x=a[i];
y=a[i+1];
i+=1; //讓x,y正好取得陣列中的元素,如果沒有i+=1會出錯
m.digui(x,y);
//m.multiple1(x,y);
}
QueryPerformanceCounter(&nEndTime);//結束計時
z=(double)(nEndTime.QuadPart-nBeginTime.QuadPart)/(double)nFreq.QuadPart;//計算程式執行時間單位為s
cout<<"輾轉相除法遞迴所用時長:"<<z*1000<<"毫秒"<<endl;
QueryPerformanceCounter(&nBeginTime);//開始計時
for(i=0;i<t;i++)
{
x=a[i];
y=a[i+1];
i+=1; //讓x,y正好取得陣列中的元素,如果沒有i+=1會出錯
m.divistor3(x,y);
//m.multiple3(x,y);
}
QueryPerformanceCounter(&nEndTime);//結束計時
z=(double)(nEndTime.QuadPart-nBeginTime.QuadPart)/(double)nFreq.QuadPart;//計算程式執行時間單位為s
cout<<"窮舉法所用時長:"<<z*1000<<"毫秒"<<endl;
QueryPerformanceCounter(&nBeginTime);//開始計時
for(i=0;i<t;i++)
{
x=a[i];
y=a[i+1];
i+=1; //讓x,y正好取得陣列中的元素,如果沒有i+=1會出錯
m.divistor4(x,y);
//m.multiple4(x,y);
}
QueryPerformanceCounter(&nEndTime);//結束計時
z=(double)(nEndTime.QuadPart-nBeginTime.QuadPart)/(double)nFreq.QuadPart;//計算程式執行時間單位為s
cout<<"更相減損術法所用時長:"<<z*1000<<"毫秒"<<endl;
QueryPerformanceCounter(&nBeginTime);//開始計時
for(i=0;i<t;i++)
{
x=a[i];
y=a[i+1];
i+=1; //讓x,y正好取得陣列中的元素,如果沒有i+=1會出錯
m.gcd(x,y);
//m.multiple5(x,y);
}
QueryPerformanceCounter(&nEndTime);//結束計時
z=(double)(nEndTime.QuadPart-nBeginTime.QuadPart)/(double)nFreq.QuadPart;//計算程式執行時間單位為s
cout<<"stein演算法非遞迴所用時長:"<<z*1000<<"毫秒"<<endl;
QueryPerformanceCounter(&nBeginTime);//開始計時
for(i=0;i<t;i++)
{
x=a[i];
y=a[i+1];
i+=1; //讓x,y正好取得陣列中的元素,如果沒有i+=1會出錯
m.gcd1(x,y);
//m.multiple6(x,y);
}
QueryPerformanceCounter(&nEndTime);//結束計時
z=(double)(nEndTime.QuadPart-nBeginTime.QuadPart)/(double)nFreq.QuadPart;//計算程式執行時間單位為s
cout<<"stein演算法遞迴所用時長:"<<z*1000<<"毫秒"<<endl;
return 0;
}
四、執行結果:
③結果
通過不同組資料的測試我們發現:
當資料小的時候:輾轉相除法,更相減損術,stein演算法,都比較用時少。
只有窮舉法用時最長。(此時區別還不明顯!)
但資料變大的時候:stein遞迴演算法的好處就顯現出來,次之的就是輾轉相除法(非遞迴)了。
五、總結
這次的上機報告真的收穫了許多,比較演算法我也在上學期做過,比較不同排序的演算法效能,當時用的是clock()函式,因為測試的資料比較龐大,有30000組資料,所以clock()函式已經足夠顯現出演算法的區別。但是這次的程式,讓我學到了一個新的測試時間的函式,它更精確,區別更明顯!
高精度時控函式QueryPerformanceFrequency(),QueryPerformanceCounter()
原理:
QueryPerformanceCounter()這個函式返回高精確度效能計數器的值,它可以以微妙為單位計時.但是QueryPerformanceCounter()確切的精確計時的最小單位是與系統有關的,所以,必須要查詢系統得到QueryPerformanceCounter()返回的嘀噠聲的頻率. QueryPerformanceFrequency()提供了這個頻率值,返回每秒嘀噠聲的個數. 計算確切的時間是從第一次呼叫QueryPerformanceCounter()開始的 假設得到的LARGE_INTEGER為nStartCounter,過一段時間後再次呼叫該函式結的, 設得到nStopCounter. 兩者之差除以QueryPerformanceFrequency()的頻率就是開始到結束之間的秒數.由於計時函式本身要耗費很少的時間,要減去一個很少的時間開銷.但一般都把這個開銷忽略。
還有對字串有了更深一步的理解,cin,cin.getline(),cin.get()函式的區別!
cin.get()函式與cin.getline()函式區別:
cin.getline():在遇到回車符時,結束字串輸入並丟棄回車符。
cin.get():在遇到回車符時,則會保留回車符在輸入佇列。
相關文章
- C語言 用更相減損術求最大公約數,最小公倍數C語言
- python用輾轉相除法求最大公約數Python
- 輾轉相除法求最大公約數——[js練習]JS
- 歐幾里德演算法(又稱輾轉相除法)求最大公約數,以及最小公倍數演算法
- C語言第七篇:輾轉相除法求最大公約數C語言
- 更相減損術的應用
- 時間相減和時間轉換
- 輾轉相除法的原理
- java演算法 求最大公約數Java演算法
- 求最大公公約數(最大公因數)—— 歐幾里得演算法演算法
- 輾轉相除法原理
- 【演算法分析與設計】輾轉相除法演算法
- C++中的輾轉相除法C++
- javascript兩個時間物件相減的作用JavaScript物件
- 透徹理解輾轉相除法
- 輾轉相除法 氣泡排序排序
- 輾轉相除法原理解析
- 輾轉相除法(歐幾里得演算法)(gcd)模板及其原理演算法GC
- oracle 兩個時間相減得秒Oracle
- 除法與GCD演算法的相關分析GC演算法
- 拇指相機 比較
- 漫畫演算法:輾轉相除法是什麼鬼?演算法
- 【C語言】聊聊輾轉相除法C語言
- 最大公約數的演算法演算法
- 【shell 指令碼】求最大公約數指令碼
- 演算法設計與分析:求兩個自然數的最大公約數演算法
- 各大防毒軟體比較與相關防毒技術介紹(轉)防毒
- 《九章算術》中的最大公約數演算法演算法
- oracle 日期相減 轉載Oracle
- 求多個數最大公約數、最小公倍數的一種變換演算法演算法
- 【演算法拾遺】最大公約數演算法
- 最大公約數與歐幾里得演算法演算法
- 手工實現整除除法的比較
- 時間相關的操作
- Http請求相關(轉)HTTP
- 求 PHP 轉 java 的相關教程PHPJava
- acwing246 區間最大公約數
- 時間相關的工具類