一、实验目的
1.明确算法的概念和特点
2.通过对问题的分析,设计合理的算法解决问题
二、实验内容
运行最大公约数的常用算法,并进行程序的调式与测试,要求设计风格良好,并添加异常处理模块(如输入非法等)
三、算法设计
1.辗转相除法
辗转相除法(又名欧几里德法)C语言中用于计算两个正整数a,b的最大公约数和最小公倍数,实质它依赖于下面的定理:
根据这一定理可以采用函数嵌套调用和递归调用形式进行求两个数的最大公约数和最小公倍数,现分别叙述如下:
其算法过程为: 前提:设两数为a,b设其中a 做被除数,b做除数,temp为余数
1、大数放a中、小数放b中;
2、求a/b的余数;
3、若temp=0则b为最大公约数;
4、如果temp!=0则把b的值给a、temp的值给a;
5、返回第二步;
#include<stdio.h>
#include<math.h>
#include<time.h>
#include<stdlib.h>
int max(int a,int b) //辗转相除法(函数嵌套调用)
{
int temp1;
if(a<b)
{
temp1=a; //将大数存在变量a中
a=b;
b=temp1;
}
while(b!=0)
{
temp1=a%b; //对b进行求余运算
a=b;
b=temp1;
}
return(a);
}
int main()
{
int t1,t2,m;
printf("请输入两个正整数\n");
scanf("%d %d",&t1,&t2);
if(t1<0&&t2<0) //判断输入的两个正整数是否合法
printf("error");
else
m=max(t1,t2); //调用函数max()
printf("最大公约数为%d\n",m);
return 0;
}
2.穷举法(利用数学定义)
穷举法(也叫枚举法)穷举法求两个正整数的最大公约数的解题步骤:从两个数中较小数开始由大到小列举,直到找到公约数立即中断列举,得到的公约数便是最大公约数 。
定义:对两个正整数a,b如果能在区间[a,0]或[b,0]内能找到一个整数temp能同时被a和b所整除,则temp即为最大公约数。
#include<stdio.h>
#include<math.h>
#include<time.h>
#include<stdlib.h>
int max1(int c,int d) //穷举法
{
int temp2;
temp2=c>d?d:c;
while(temp2>0)
{
if(c%temp2==0&&d%temp2==0) //找到一个能同时被c,d整除的数
break;
temp2--;
}
return(temp2);
}
int main()
{
int t1,t2,m;
printf("请输入两个正整数\n");
scanf("%d %d",&t1,&t2);
if(t1<0&&t2<0) //判断输入的两个正整数是否合法
printf("error");
else
m=max1(t1,t2); //调用函数
printf("最大公约数为%d\n",m);
return 0;
}
- 更相减损法
更相减损术,是出自《九章算术》的一种求最大公约数的算法,它原本是为约分而设计的,但它适用于任何需要求最大公约数的场合。《九章算术》是中国古代的数学专著,其中的“更相减损术”可以用来求两个数的最大公约数,即“可半者半之,不可半者,副置分母、子之数,以少减多,更相减损,求其等也。以等数约之。”
步骤:
第一步:任意给定两个正整数;判断它们是否都是偶数。若是,则用2约简;若不是则执行第二步。
第二步:以较大的数减较小的数,接着把所得的差与较小的数比较,并以大数减小数。继续这个操作,直到所得的减数和差相等为止。
则第一步中约掉的若干个2与第二步中等数的乘积就是所求的最大公约数。
其中所说的“等数”,就是最大公约数。求“等数”的办法是“更相减损”法。所以更相减损法也叫等值算法。
#include<stdio.h>
#include<math.h>
#include<time.h>
#include<stdlib.h>
int max2(int m,int n) //更相减损法
{
int i=0,temp,w;
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(w)
{
w=m-n;
m=(n>w)?n:w;
n=(n<w)?n:w;
if(n==(m-n))
break;
}
if(i==0)
return n;
else
return(int)pow(2,i)*n; //进行指数运算
}
int main()
{
int t1,t2,m;
printf("请输入两个正整数\n");
scanf("%d %d",&t1,&t2);
if(t1<0&&t2<0) //判断输入的两个正整数是否合法
printf("error");
else
m=max2(t1,t2); //调用函数
printf("最大公约数为%d\n",m);
return 0;
}
4.Stein算法
Stein算法由J. Stein 1961年提出,这个方法也是计算两个数的最大公约数。来研究一下最大公约数的性质,发现有 gcd( kx,ky ) = kgcd( 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=na,因为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 );
2.x奇y偶 gcd( x,y ) = gcd( x,y/2 );
3.x偶y奇 gcd( x,y ) = gcd( x/2,y ) 或 gcd( x,y )=gcd( y,x/2 );
现在已经有了递归式,还需要再找出一个退化情况。注意到 gcd( x,x ) = x ,就用这个。
#include<stdio.h>
#include<math.h>
#include<time.h>
#include<stdlib.h>
#define N 10000
int max3(int x,int y) //Stein算法
{
int temp4;
int factor=0;
if(x<y)
{
temp4=x;
x=y;
y=temp4;
}
if(0==y)
return 0;
while(x!=y)
{
if(x&0x1)
{
if(y&0x1) ///x,y同为奇数时
{
y=(x-y)>>1;
x-=y;
}
else //x为奇数,y为偶数时
{
y>>=1;
}
}
else //x为偶数,y为奇数时
{
if(y&0x1)
{
x>>=1;
if(x<y)
{
temp4=x;
x=y;
y=temp4;
}
}
else //x,y都为偶数时
{
x>>=1;
y>>=1;
++factor;
}
}
}
return(x<<factor);
}
int main()
{
int t1,t2,m;
printf("请输入两个正整数\n");
scanf("%d %d",&t1,&t2);
if(t1<0&&t2<0) //判断输入的两个正整数是否合法
printf("error");
else
m=max3(t1,t2); //调用函数
printf("最大公约数为%d\n",m);
return 0;
}
流程图:
总代码:
#include<stdio.h>
#include<math.h>
#include<time.h>
#include<stdlib.h>
#define N 10000
int max(int a,int b) //辗转相除法
{
int temp1;
if(a<b)
{
temp1=a; //将大数存在变量a中
a=b;
b=temp1;
}
while(b!=0)
{
temp1=a%b; //对b进行求余运算
a=b;
b=temp1;
}
return(a);
}
int max1(int c,int d) //穷举法
{
int temp2;
temp2=c>d?d:c;
while(temp2>0)
{
if(c%temp2==0&&d%temp2==0) //找到一个能同时被c,d整除的数
break;
temp2--;
}
return(temp2);
}
int max2(int m,int n) //更相减损法
{
int i=0,temp,w;
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(w)
{
w=m-n;
m=(n>w)?n:w;
n=(n<w)?n:w;
if(n==(m-n))
break;
}
if(i==0)
return n;
else
return(int)pow(2,i)*n; //进行指数运算
}
int max3(int x,int y) //Stein算法
{
int temp4;
int factor=0;
if(x<y)
{
temp4=x;
x=y;
y=temp4;
}
if(0==y)
return 0;
while(x!=y)
{
if(x&0x1)
{
if(y&0x1) ///x,y同为奇数时
{
y=(x-y)>>1;
x-=y;
}
else //x为奇数,y为偶数时
{
y>>=1;
}
}
else //x为偶数,y为奇数时
{
if(y&0x1)
{
x>>=1;
if(x<y)
{
temp4=x;
x=y;
y=temp4;
}
}
else //x,y都为偶数时
{
x>>=1;
y>>=1;
++factor;
}
}
}
return(x<<factor);
}
int main()
{
int l,num,tag=1,c; //c代表多少组数据
int p,q,k[N][2];
int t,w,r;
clock_t start,finish; //定义时间的开始与结束
double totaltime;
printf("****请输入你想要选择的功能****\n");
printf("8****随机产生数据\n");
printf("9****手动输入数据数据\n");
scanf("%d",&t);
if(t==8)
{
srand((unsigned)time(NULL));
printf("请输入产生随机数组数:\n");
scanf("%d",&c);
for(p=0;p<c;p++)
{
for(q=0;q<2;q++)
{
k[p][q]=rand()%1000;
while(k[p][q]==0)
k[p][q]=rand()%1000;
}
}
printf("随机数为:\n");
for(p=0;p<c;p++){
for(q=0;q<2;q++)
{
if(k[p][q]!=0)
printf("%d\t",k[p][q]); //输出随机数
else
printf("error\n");
}
printf("\n");
}
printf("\n");
printf("****请选择算法****\n");
printf("1****辗转相除法\n");
printf("2****穷举法\n");
printf("3****更相减损法\n");
printf("4****Stein算法\n");
while(tag)
{
scanf("%d",&num);
start=clock();
switch(num)
{
case 1:
for(p=0;p<c;p++){
l=max(k[p][0],k[p][1]); //调用函数max(),使用辗转相除法求最大公约数
printf("%d和%d最大公约数为%d\n",k[p][0],k[p][1],l);
long i=10000000L;
while(i--)
finish=clock();
totaltime=(double)(finish-start)/CLOCKS_PER_SEC; //程序运行时间,算差值
printf("程序运行的时间为%f秒\n",totaltime);
}
break;
case 2:
for(p=0;p<c;p++){
l=max1(k[p][0],k[p][1]); //调用函数max1()
printf("%d和%d最大公约数为%d\n",k[p][0],k[p][1],l);
long i=10000000L;
while(i--)
finish=clock();
totaltime=(double)(finish-start)/CLOCKS_PER_SEC; //程序运行时间,算差值
printf("程序运行的时间为%f秒\n",totaltime);
}
break;
case 3:
for(p=0;p<c;p++){
l=max2(k[p][0],k[p][1]); //调用函数max2()
printf("%d和%d最大公约数为%d\n",k[p][0],k[p][1],l);
long i=10000000L;
while(i--)
finish=clock();
totaltime=(double)(finish-start)/CLOCKS_PER_SEC; //程序运行时间,算差值
printf("程序运行的时间为%f秒\n",totaltime);
}
break;
case 4:
for(p=0;p<c;p++){
l=max3(k[p][0],k[p][1]); //调用函数max3()
printf("%d和%d最大公约数为%d\n",k[p][0],k[p][1],l);
long i=10000000L;
while(i--)
finish=clock();
totaltime=(double)(finish-start)/CLOCKS_PER_SEC; //程序运行时间,算差值
printf("程序运行的时间为%f秒\n",totaltime);
}
break;
case 0:
break;
default:return 0;
}
}
}
else if(t==9) //当t=9时选择手动输入两个整数
{
printf("请输入两个正整数\n");
scanf("%d %d",&w,&r);
if(w>0&&r>0)
{
printf("****请选择算法****\n");
printf("1****辗转相除法\n");
printf("2****穷举法\n");
printf("3****更相减损法\n");
printf("4****Stein算法\n");
while(1)
{
scanf("%d",&num);
switch(num)
{
case 1:l=max(w,r);
break;
case 2:l=max1(w,r);
break;
case 3:l=max2(w,r);
break;
case 4:l=max3(w,r);
break;
default:return 0;
}
printf("这两个数的最大公约数为%d\n",l);
}
}
else
printf("输入错误\n");
}
return 0;
}
四、调试,测试截图
①辗转相除法测试
②穷举法测试
③更相减损法测试
④Stein算法测试
⑤随机产生数据测试
⑥手动输入测试
五、心得体会
1.遇到的问题
①在编写这个程序时,循环语句用的比较多。因此在循环这部分刚开始遇到了很多的问题,比如,在程序运行的时候会出现无限循环的情况,并且在输出的时候遇到了出现负值的情况,在检查之后发现是循环体写错了。因此在以后使用循环语句的时候一定要注意,避免出现同样的错误。
②在写时间函数的时候也出现了一系列的问题,例如,将start=clock();放在定义变量后面,显示的时间就会变长,只有放在switch()语句前面,才能准确的计算出程序运行所用的时间。
2.体会
在这次编写程序的过程中,虽然遇到了很多的问题,而且花了一些经历来解决这些问题,但也收获了很多。遇到的问题也都通过与同学讨论或者自己查找资料解决了,我相信这些问题将会是我下一次编程的经验。