一.实验目的
明确算法的概念和特点。
通过对问题的分析,设计合理的算法解决问题;
实验内容
运行最大公约数的常用算法,并进行程序的调式与测试,要求程序设计风格良好,并添加异常处理模块(如输入非法等)。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、返回第二步;
代码:
int divisor (int a,int b) /*自定义函数求两数的最大公约数*/
{
int temp; /*定义整型变量*/
if(a
{ 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); /*返回最小公倍数到主调函数处进行输出*/
}
#include "stdio.h" /*输入输出类头文件*/
main()
{
int m,n,t1,t2; /*定义整型变量*/
printf("please input two integer number:"); /*提示输入两个整数*/
scanf("%d%d",&m,&n); /*通过终端输入两个数*/
t1=divisor(m,n); /*自定义主调函数*/
t2=multiple(m,n); /*自定义主调函数*/
printf("The higest common divisor is %d\n",t1); /*输出最大公约数*/
printf("The lowest common multiple is %d\n", t2); /*输出最小公倍数*/
}
启示:请注意算法中变量数值之间的相互交换方法、如何取模、怎样进行自定义函数及主调函数与被调函数间的相互关系,函数参数的定义及对应关系特点,利用控制语句如何实现。
②函数递归调用
int gcd (int a,int b)
{ if(a%b==0)
return b;
else
return gcd(b,a%b);
}
#include "stdio.h"
main()
{
int m,n,t1;
printf("please input two integer number:");
scanf("%d%d",&m,&n);
t1=gcd(m,n);
printf("The highest common divisor is %d\n",t1);/*最大公约数*/
printf("The least common multiple is %d\n",m*n/t1);/*最小公倍数*/
getch();
}
启示:采用递归调用法要注意递归终止条件的描述,只有找到递归变化的规律,才能有效地解决问题。
2.穷举法(利用数学定义)
穷举法(也叫枚举法)穷举法求两个正整数的最大公约数的解题步骤:从两个数中较小数开始由大到小列举,直到找到公约数立即中断列举,得到的公约数便是最大公约数 。
①定义1:对两个正整数a,b如果能在区间[a,0]或[b,0]内能找到一个整数temp能同时被a和b所整除,则temp即为最大公约数。
代码为:
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); /*返回满足条件的数到主调函数处*/
}
#include "stdio.h"
main()
{
int m,n,t1;
printf("please input two integer number:");
scanf("%d%d",&m,&n);
t1=divisor(m,n);
printf("The higest common divisor is %d\n",t1);
getch();
}
②定义2:对两个正整数a,b,如果若干个a之和或b之和能被b所整除或能被a所整除,则该和数即为所求的最小公倍数。
代码为:
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);
}
#include "stdio.h"
main()
{
int m,n,t2;
printf("please input two integer number:");
scanf("%d%d",&m,&n);
t2=multiple(m,n);
printf("The least common multiple is %d\n",t2);
getch();
}
启示:根据数学定义求任意两个正整数的最大公约数和最小公倍数,相对辗转相除法来说,易懂,容易被学习者接受,但也请读者注意强制退出循环过程的条件、变量的特点及控制语句的使用。
3. 更相减损法
更相减损术,是出自《九章算术》的一种求最大公约数的算法,它原本是为约分而设计的,但它适用于任何需要求最大公约数的场合。《九章算术》是中国古代的数学专著,其中的“更相减损术”可以用来求两个数的最大公约数,即“可半者半之,不可半者,副置分母、子之数,以少减多,更相减损,求其等也。以等数约之。”
翻译成现代语言如下:
第一步:任意给定两个正整数;判断它们是否都是偶数。若是,则用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
{
temp=m;
m=n;
n=temp;
}
while(x)
{
x=m-n;
m=(n>x)?n:x;
n=(n
if(n==(m-n))
break;
}
if(i==0)
return n;
else
return (int )pow(2,i)*n;
}
4.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的约[1]数。因为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 ,就用这个。
①函数非递归调用
int Stein( unsigned int x, unsigned int y )
/* return the greatest common divisor of x and 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 )
{/* when x is odd */
if ( y & 0x1 )
{/* when x and y are both odd */
y = ( x - y ) >> 1;
x -= y;
}
else
{/* when x is odd and y is even */
y >>= 1;
}
}
else
{/* when x is even */
if ( y & 0x1 )
{/* when x is even and y is odd */
x >>= 1;
if ( x < y )
{
temp = x;
x = y;
y = temp;
}
}
else
{/* when x and y are both even */
x >>= 1;
y >>= 1;
++factor;
}
}
}
return ( x << factor );
}②函数递归调用
int gcd(int u,int v)
{
if (u == 0) return v;
if (v == 0) return u;
// look for factors of 2
if (~u & 1) // u is even
{
if (v & 1) // v is odd
return gcd(u >> 1, v);
else // both u and v are even
return gcd(u >> 1, v >> 1) << 1;
}
if (~v & 1) // u is odd, v is even
return gcd(u, v >> 1);
// reduce larger argument
if (u > v)
return gcd((u - v) >> 1, v);
return gcd((v - u) >> 1, u);
}
二.算法构造
绘制出所有算法的流程图以及N-S盒图。
1)辗转相除法
①函数嵌套调用
②函数递归调用
2)穷举法(利用数学定义)
3)更相减损法
4)Stein算法
①函数非递归调用
三.算法实现
程序源代码(请写入必要的注释)。
#include "stdio.h" /*输入输出类头文件*/ #include "time.h"//日期与时间函数 #include"stdlib.h" #include"math.h"//数学函数库 //辗转相除法①函数嵌套调用 int divisor_1 (int a,int b) /*自定义函数求两数的最大公约数*/ { int temp; /*定义整型变量*/ if(ab)?b:a; /*采种条件运算表达式求出两个数中的最小值*/ while(temp>0) { if (a%temp==0&&b%temp==0) /*只要找到一个数能同时被a,b所整除,则中止循环*/ break; temp--; /*如不满足if条件则变量自减,直到能被a,b所整除*/ } return (temp); /*返回满足条件的数到主调函数处*/ } //更相减损法 int gcd_2(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
x)?n:x; n=(n > 1; x -= y; } else {/* when x is odd and y is even */ y >>= 1; } } else {/* when x is even */ if ( y & 0x1 ) {/* when x is even and y is odd */ x >>= 1; if ( x < y ) { temp = x; x = y; y = temp; } } else {/* when x and y are both even */ x >>= 1; y >>= 1; ++factor; } } } return ( x << factor ); } //stein②函数递归调用 int gcd_3(int u,int v) { if (u == 0) return v; if (v == 0) return u; // look for factors of 2 if (~u & 1) // u is even { if (v & 1) // v is odd return gcd_3(u >> 1, v); else // both u and v are even return gcd_3(u >> 1, v >> 1) << 1; } if (~v & 1) // u is odd, v is even return gcd_3(u, v >> 1); // reduce larger argument if (u > v) return gcd_3((u - v) >> 1, v); return gcd_3((v - u) >> 1, u); } int main() { //辗转相除法函数嵌套调用 int m,n,t1; /*定义整型变量*/ srand(time(NULL)); int i,a[50],b[50]; float start,end; // 开始时间,结束时间 start=(float)clock(); for( i=0;i<50;i++) { a[i]=rand()%500;//将随机数存入数组 b[i]=rand()%500; m=a[i]; n=b[i]; t1=divisor_1(m,n); /*自定义主调函数*/ printf("%d %d\n",a[i],b[i]); printf("The higest common divisor is %d\n",t1); /*输出最大公约数*/ } end=(float)clock(); printf("辗转相除法函数嵌套调用的时间是:%f毫秒\n",end-start);//运行时间 //辗转相除法函数递归调用 start=(float)clock(); for( i=0;i<50;i++) { a[i]=rand()%500; b[i]=rand()%500; m=a[i]; n=b[i]; t1=gcd_1(m,n); printf("%d %d\n",a[i],b[i]); printf("The higest common divisor is %d\n",t1); /*输出最大公约数*/ } end=(float)clock(); printf("辗转相除法函数递归调用的时间是:%f毫秒\n",end-start); //穷举法 start=(float)clock(); for( i=0;i<50;i++) { a[i]=rand()%500; b[i]=rand()%500; m=a[i]; n=b[i]; t1=divisor_2(m,n); printf("%d %d\n",a[i],b[i]); printf("The higest common divisor is %d\n",t1); /*输出最大公约数*/ } end=(float)clock(); printf("穷举法的时间是:%f毫秒\n",end-start); //更相减损法 start=(float)clock(); for( i=0;i<50;i++) { a[i]=rand()%500; b[i]=rand()%500; m=a[i]; n=b[i]; t1=gcd_2(m,n); printf("%d %d\n",a[i],b[i]); printf("The higest common divisor is %d\n",t1); /*输出最大公约数*/ } end=(float)clock(); printf("更相减损法的时间是:%f毫秒\n",end-start); //stein函数非递归调用 start=(float)clock(); for( i=0;i<50;i++) { a[i]=rand()%500; b[i]=rand()%500; m=a[i]; n=b[i]; t1=Stein(m,n); printf("%d %d\n",a[i],b[i]); printf("The higest common divisor is %d\n",t1); /*输出最大公约数*/ } end=(float)clock(); printf("stein函数非递归调用法的时间是:%f毫秒\n",end-start); //stein函数递归调用 start=(float)clock(); for( i=0;i<50;i++) { a[i]=rand()%500; b[i]=rand()%500; m=a[i]; n=b[i]; t1=gcd_3(m,n); printf("%d %d\n",a[i],b[i]); printf("The higest common divisor is %d\n",t1); /*输出最大公约数*/ } end=(float)clock(); printf("stein函数递归调用法的时间是:%f毫秒\n",end-start); }
4.调试、测试及运行结果(至少比较4种GCD算法在给定不同规模测试数据的情况下的平均运行时间)
调试:在编完测试代码运行结果出现负数
于是进行调试发现没有将数组赋值给m和n,导致没有进行函数调用
测试:运用随机函数产生40组数,并存入数组,使用time库函数计算程序运行时间
运行结果:改变随机产生的组数,实现不同规模的测试数据的情况下的平均运行时间
1)辗转相除法函数嵌套调用:20组,30组,40组,50组平均运行时间
2)辗转相除法函数递归调用:20组,30组,40组,50组平均运行时间
3)穷举法20组,30组,40组,50组平均运行时间
4)更相减损法20组,30组,40组,50组平均运行时间
5)Stein算法
①函数非递归调用20组,30组,40组,50组平均运行时间
6)stein算法②函数递归调用
5.经验归纳
本次上机作业是计算这几种算法的平均运行时间,看到这个就想起上学期做排序算法比较作业时也计算了运行时间,于是就根据之前的方法运用随机函数产生多组数据,并存入数组,然后调用time库函数计算程序开始结束时间,用结束时间减去开始时间的平均运行时间,在编写主函数代码时,运行结果出现负数,经过调试,由于没有将数组赋值给m,n,导致不能调用函数,以后会多注意此类逻辑问题,通过调试发现并解决问题。