求最大公约数不同算法的时间比较(辗转相除法,更相减损术等)

后附完整源代码:(含有求最大公倍数的函数,此代码测试的是面对不同组数据,各种算法的性能分析,如需输出最大公约数,只需加个输出就好!)

一、 题目分析

首先,题目要求我们求出俩个正整数的最大公约数和最小公倍数,并提供了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():在遇到回车符时,则会保留回车符在输入队列。

猜你喜欢

转载自blog.csdn.net/qq_42419462/article/details/88313699