5算法与算术运算
【例1】一次考试,共考了五门课。统计五十个学生中至
少有三门课成绩高于90分的人数。
问题分析:若一个学生五门课的成绩分别记为:a1,a2,a3,a4,a5,则要表示有三门课成绩高于90分,有C35=10组关系逻辑表达式,每组三个关系表达式。无论书写还是运行效率都极低。但通过算法运算就能很简便地解决这类问题。
算法设计:
1)对每个同学,先计算其成绩高于90分的课程数目,若超过3,则累加满足条件的人数中。
2)用二重循环实现以上过程,外层循环模拟50个同学,内层循环模拟五门课程。
算法如下:
main( ) {int a[5],i,j,s,num=0; for ( i=1;i<=50;i=i+1) {s=0; for( j=0;j<=4;j=j+1) {input(a[j]); if(a[j]>=90) s=s+1; } if(s>=3) num=num+1; } print(“The number is”,num); }
算法说明:对于计算其成绩高于90分的课程数目,还可以简单地这样实现:
s=0;
for( j=0;j<=4;j++)
{input(“a[j]);
s=s+(a[i]>=90);
}
【例2】开灯问题:有从1到n依次编号的n个同学和n 盏灯。1号同学将所有的灯都关掉;2号同学将编号为2的倍数的灯都打开;3号同学则将编号为3的倍数的灯作相反处理(该号灯如打开的,则关掉;如关闭的,则打开);以后的同学都将自己编号的倍数的灯,作相反处理。问经n个同学操作后,哪些灯是打开的?
问题分析:
1)用数组表示某种状态,这里定义有n个元素的a数组,它的每个下标变量a[i]视为一灯,i表示其编号。a[i]=1 表示灯处于打开状态,a[i]=0表示灯处于关闭状态。
2)实现将第i 灯作相反处理的操作,可以用条件语句if表示:当a[i]为1时,a[i]被重新赋为0;当a[i]为0时, a[i]被重新赋为1。但通过以下算术运算: a[i]=1-a[i]就很好地模拟“开关”灯的操作。
main( ) { int n,a[1000],i,k; print(“input a number”); input(“%d”,&n); for( i=1;i<=n;i++) a[i]=0; for( i=2;i<=n;i++) { k=1; while ( i*k<=n) { a[i*k]=1-a[i*k]; k=k+1;} } for( i=1;i<=n;i++) print( a[i]); }
算法说明:算法中第二个for循环i枚举的不是灯的编号,而是编号为I的同学,其内层循环中,就将包含i因素的灯的编号为“i*k”的灯,改变其状态。
【例3】右图中所示的圆圈中,我们把相隔一个数据的两个数(如1和10,3和5,3和6)称作是“一对数”,试编程求出乘积最大的一对数和乘积最小的一对数。输出格式如下:
max=?*?=?
min=?*?=?
其中?表示:找到的满足条件的数和乘积。
算法设计:
1)题目中的数据有前后“位置”关系,因此必须用数组来
存储。设数组定义为a[num],则有a[0]—a[num-1]共
num个元素。
2)用i代表下标,题目就是顺序将a(i-1)与a(i+1)相
乘,求出乘积的最大值和最小值即可。
3)关键问题是使i=num-1时,保证i+1的“值”是0;当i=0
时,保证i-1的“值”是num-1,把数组当成圆圈操作通
过求余运算很容易实现将线性的数组当成圆圈操作:
i=num-1时,(i+1)%num等于0;
当i=0时(num+i-1)%num等于num-1,且这样的表达式当
i为其它值时也是同样适用的。
通过求余运算,“避免了”判别数组起点与终点的操作。
4)用变量Max记录当前最大的乘积,m、n为对应的两个乘
数;变量min记录当前最小的乘积,s、t为对应的两个
乘数。
算法如下:
main( ) {int max=1,min=32767,a[100],num,i,k,m,n,s,t; print(“input a number”); input(num); for( i=0;i<num;i=i+1) input(a[i]); for( i=0;i<num;i=i+1) {p=(num+i-1) mod num;q=(i+1) mod num; k=a[p]*a[q]; if (k>max) {max=k; m=a[p]; n=a[q]; } if (k<min) {min=k; s=a[p]; t=a[q]; } } print(”max=” m,”*”,n,”=”,max); print(”min=” s,”*”,t,”=”,min); }
标志量的妙用
【例1】冒泡排序算法的改进
问题分析:假设原有的数据本来就是从小到大有序的,则原有算法仍要做n-1趟比较操作,事实上一趟比较下来,若发现没有进行过交换,就已经说明数据已全部有序,无需进行其后的比较操作了。为提高效率可以对冒泡排序算法进行改进,当发现某趟没有交换后就停止下一趟的比较操作。用标志量来记录每趟交换数据的情况,如flag=0表示没有进行过交换,一但有数据进行交换则置flag为1,表示已进行过交换。当一趟比较交换完成后,若flag仍为0时,则无需进行下一趟操作,否则若flag为1时,只能继续进行下一趟操作。
改进后的算法如下:
main() { int i,j,t,n,a[100],flag; print(“input data number(<100):”); input(n); print(“input data:”,n); for(i=0;i<n;i++) input(a[i]); flag=1; for(i=1;i<=n-1 and flag==1;i++) {flag=0; for(j=n-1;j>=i;j--) if(a[j]<a[j-1]) { t=a[j]; a[j]=a[j-1]; a[j-1]=t;flag=1;} for(i=0;i<n;i++) print(a[i]); } }
算法说明:
1)排序前“for(i=1;i<=n-1 and flag==1;i++),for循环之前”的flag=1;是为了保证循环的开始。
2)内层循环外的flag=0;是假设这趟比较中没有交换,一但发生交换操作在内层循环中就置flag=1;,以保证继续下一趟操作。
【例2】编程判定从键盘输入n个数据互不相等。
问题分析:
这里要判定n个数互不相等,是无任何限定的数据。若用逻辑表达式表示需要:
n-1 + n-2 + n-3 +……+1=n*(n-1)/2
个关系表达式。显然书写和运行效率都很低。
算法设计:
下面,通过引入标志量记录数据是否有重复的情况,避免了复杂的逻辑表达式。
算法如下:
main( ) { int a[100] ,i,j,t,n; input(n); for (i=1;i<=n;i++) input( a[i]); t=1; for (i=1;i<=n-1;i++) for (j=i+1;j<=n;j++) if (a[i]==a[j]) {t=0; break;} if (t=1) print(“No repeat”); else print(“repeat ”);} }
算法说明:算法中通过二重循环,交叉比较所有数据,用标志变量t=0标识可能的重复。若循环结束,t仍为1,说明数据没有重复,互不相同。
【例3】输入三个数值,判断以它们为边长是否能构成的三角形,属于那种特殊三角形:等边、等腰、直角。
问题分析:这个题目表面看起来非常简单,但要做到合理输出却并不容易。这里我们先讨论一下可能的输出情况:
1)不构成三角形、2)构成等边三角形、3)构成等腰三角形、
4)构成直角三角形5)构成一般三角形。
分析以上情况,输出情况5)与2)、3)、4)不应该同时出现,而输出情况3)4)有可能同时出现;只用嵌套或并列的if很容易出现不合理的输出,如会出现既输出“是一个三角形”又输出了“是等边三角形”等不合理的结果。
输出情况1)与其它情况是“否则”关系,容易分支。输出情况5)是在三角形不属于2)3)4)三种情况时的输出。特别地,情况4)与情况3)不是简单的否则关系,因为3)4)两种情况可能同时输出。
算法设计:
算法中我们需要避免情况5)与情况2)3)4)之一同时输出。设置一标志变量flag,当数据能构成三角形时就置flag=0表示情况5),一但测试出数据属于情况2)3)4)中的一种情况时,就置flag=1表示构成了特殊三角形,最后就不必输出“构成一般三角形”了;若flag最后仍保持0,则输出“构成一般三角形”。
算法如下:
main( ) { int a,b,c,flag; print(“Input 3 number:”); input(a,b,c); if(a>=b+c or b>=a+c or c>=a+b) print(“don’t form a triangle”); else {flag=0; if (a*a=b*b+c*c or b*b=a*a+c*c or c*c=a*a+b*b) {print(“form a right-angle triangle”); flag=1;} if(a=b and b=c) {print(“form a equilateral triangle”);flag=1;} else if(a=b or b=c or c=a) { print(“form a equal haunch triangle”);flag=1;} if(flag=0) print(“form a triangle”); } }
【例4】编写算法,求任意三个数的最小公倍数。
算法设计:
1)用短除法求三个已知数的最小公倍数的过程就是求它们的因数之积,这个因数可能是三个数共有的、两个数共有或一个数独有的三种情况。
2)在手工完成这个问题时,我们的大脑可以判断三个数含有哪些因数,及属于哪种情况。用算法实现就只能利用尝试法了。尝试的范围应该是2——三个数中最大数之间的因数。
无论因数属于以下三种情况之一,都只算作一个因数,累乘一次。
若某个数是三个数的共有的因数,如:2是2,14,6的因数;
若某个数是其中两个数的因数,如:2 是2,5,6的因数;
若某个数是其中某一个数的因数,如:2 是2,5,9的因数。
以上三种情况例子中,因数2都只累乘一次
3)再看例子2,4,8中2是的因数,为避免因数重复计算,一定要用2整除这三个数得到1,2,4。注意到2仍是(1,2,4)的因数,所以在尝试某数是否是三个数的因数时,不是用条件语句if,而是要用循环语句while,以保证将三个数中所含的某个因数能全部被找出,直到三个数都不含这个数做因数时循环结束。
4)由于某数i是已知三数的因数有多种情况,以上讨论了三大类,后两类又能细分出更多小的类别。如是两个数共有的因数时,可能是第一、三个数的因数;或是第一、二个数的因数;或是第二、二个数的因数。总之,很难用一个简单的逻辑表达式来表示各种复杂的情况。
不过借助3.3.1小节“算术运算的妙用”中介绍的方法,用表达式:
k=( x1 mod i =0)+( x2 mod i=0)+ ( x3 mod i=0)
的值,可以区分某数i是否为已知三个数的因数,k=0表示i不是三个数的因数,k>0表示i是三个数的因数。
为避免因数重复计算,每次都需要除掉三个整数中已找到的因数(即用因数去除含有它的整数)。而以上逻辑表达式无法识别i具体是哪一个数的因数,要对哪个数进行整除i的运算。下面我们采用标致量的方法,来解决这里的问题。
算法如下:
main( ) { int x1,x2,x3,t=1,i,flag,x0; print(“Input 3 number:”); input(x1,x2,x3); x0=max(x1,x2,x3); for (i=2;i<=x0;i=i+1) {flag=1; while(flag=1) { flag=0; if (x1 mod i=0) {x1=x1/i; flag=1;} if(x2 mod i=0) {x2=x2/i; flag=1;} if(x3 mod i=0) {x3=x3/i; flag=1;} if (flag=1) t=t*i; }//while结束符 x0=max(x1,x2,x3) }//for结束符 print(“The result is ”,t); } max(int x,int y,int z ) { if(x>y&&x>z) return(x); else if(y>x and y>z) return(y); else return(z); }
算法说明:在while循环体外将flag置为1,是为了能进入循环。一进入循环马上将其置为0,表示假设i不是三个数的因数,以下用三个条件语句测试:发现i是某个数的因数,则用因数去除对应整数,并将flag置为1,表示i是某个数的因数;循环体最后测试flag的值,若为1则累乘i因数;否则,i不是任意一个数的因数,为了提高运行效率再一次找出去除因数后,三个数的最大值,以决定是否继续for循环。
信息数字化
【例1】警察局抓了a,b,c,d四名偷窃嫌疑犯,其中只有一人是小偷。审问中
a说:“我不是小偷。”
b说:“c是小偷。”
c说:“小偷肯定是d。”
d说:“c在冤枉人。”
现在已经知道四个人中三人说的是真话,一人说的是假话,问到底谁是小偷?
问题分析:将a,b,c,d将四个人进行编号,号码分别为1,2,3,4。则问题可用枚举尝试法来解决。
算法设计:用变量x存放小偷的编号,则x的取值范围从1取到4,就假设了他们中的某人是小偷的所有情况。四个人所说的话就可以分别写成:
a说的话:x<>1
b说的话:x=3
c说的话:x=4
d说的话:x<>4或not(x=4)
注意:在x的枚举过程中,当这四个逻辑式的值相加等于3时,即表示“四个人中三人说的是真话,一人说的是假话”。
算法如下:
main( ) { int x; for(x=1;x<=4;x++) if((x<>1)+(x=3)+(x=4)+(x<>4)==3) print(chr(64+x),“is a thief .”); } 运行结果: c is a thief .
【例2】三位老师对某次数学竞赛进行了预测。他们的预测如下:
甲说:学生A得第一名,学生B得第三名。
乙说:学生C得第一名,学生D得第四名。
丙说:学生D得第二名,学生A得第三名。
竞赛结果表明,他们都说对了一半,说错了一半,并且无并列名次,试编程输出A、B、C、D各自的名次。
问题分析:用数1,2,3,4分别代表学生a,b,c,d获得的名次。问题就可以利用三重循环把所有的情况枚举出来。
算法设计:
1)用a,b,c,d 代表四个同学,其存储的值代表他们的名次。
设置第一层计数循环a的范围从1到4;
设置第二层计数循环b的范围从1到4;
设置内计数循环c的范围从1到来4;
由于无并列名次,名次的和为1+2+3+4=10,由此可计算出d的名次值为10-a-b-c。
2)问题的已知内容,可以表示成以下几个条件式:
(1) (a=1)+(b=3)=1
(2) (c=1)+(d=4)=1
(3) (d=2)+(a=3)=1
若三个条件均满足,则输出结果,若不满足,继续循环搜索,直至循环正常结束。
算法如下:
main( ) {int a,b,c,d; for( a=1;a<=4;a=a+1) for( b=1;b<=4;b=b+1) if (a<>b) for( c=1;c<=4;c=c+1) if (c<>a and c<>b) {d=10-a-b-c; if (d<>a and d<>b and d<>c ) if(((a=1)+(b=3))=1 and ((c=1)+(d=4))=1 and ((d=2)+(a=3))=1) print( “a,b,c,d=”,a,b,c,d); } }
【例3】填写运算符
输入任意5个数x1,x2,x3,x4,x5每相邻两个数之间填上一个运算符。在填入四个运算符后,使得表达式值为一个指定值y(y由键盘输入)。求出所有满足条件的表达式。
算法设计:
1)枚举法解题:先填四个‘+’。检验条件表达式
x1+x2+x3+x4+x5=y,如果不成立,则将第四个运算符 改为‘-’,以后改‘*’、改‘/’。轮完一遍,把第三个运算 符改为‘-’,第四个运算符按‘+、-、*、/’顺序再轮一遍,……如此下去,直至第一个运算符,由‘+’至‘/’轮完为止。
2)若当前运算符轮到‘/’则运算符右端的数必须非零,因为 零不能当除数。
3)为了便于循环我们在算法中,把‘+、-、*、/’数字化作1、 2、3、4。五个数据间需四个运算符,用一个有四个元 素数组i[1]…i[4] 来代表它们。
4)为了解决运算的优先级问题,我们设置如下变量:
f----减去标志。减法运算时,置f=-1,否则f=1;
q----若当前运算符为+(-)时,q存贮运算符的左项值;
若当前运算符为*(/)时,q存贮两数乘(除)后结果;
p----累加器。每填一个算符p=p+f*q。
在每填入四个算符前,设置f=1、q=第1项的值,p=0,然后由左至右计算表达式的值。当分析至第四个算符 f = 是第四次运算符的减标志,q是第五项值, p是前三次运算的累加值。此时只要检验p+f*q=y是否成立。若成立,则为一个方案;否则重新循环搜索新的一组算符。
算法如下:
main( ) { int j, k,f,i[5], total; float n[6],p,q; char c[5] ={‘ ‘,’+’,’-’,’*’,’/’}; print(“input five number”); for(j=1;j<=5;j++) input(n[j]); print(“ input result:”); input(n[0]); total=0; for (i[1]=1 ;i[1]<=4 ;i[1]++) if ((i[1]<4) || (n[2]!=0) ) for (i[2]=1 ;i[2]<=4 ;i[2]++) if( (i[2]<4) || (n[3]!=0) ) for (i[3]=1 ;i[3]<=4 ;i[3]++) if ((i[3]<4) ||(n[4]!=0) ) for (i[4]=1 ;i[4]<=4 ;i[4]++) if((i[4]<4) || (n[5]!=0) ) {p=0; q=n[1]; f=1; for (k=1;k<=4;k++) switch (i[k]) { case 1: p=p+f*q; f=1; q=n[k+1]; break; case 2: p=p+f*q; f=-1; q=n[k+1]; break; case 3: q=q*n[k+1]; break; case 4: q=q/n[k+1]; } if ( p+f*q==n[0]) { total++; print (“total: ” ,total); for (k=1;k<=4;k++) print (n[k],c[i[k]]); print (n[5], n[0]); if (total mod 20==0 ) {每20个方案为一屏,逐屏显示} {print (“press enter to continue”); getchar( );} } } }
算法说明:
1)算法中四个for循环后的四个if语句,为了保证不进行除数为0的运算。
2)在枚举四个运算符时,用了四个数组元素i[1]、i[2]、 i[3]、i[4]代表四个运算符。若用四个普通变量代表四个运算符,则运算过程需四个switch语句。而算法是通过四次循环完成运算过程的,又一次体现了利用数组构造循环“不变量”的技巧。
【例4】有10箱产品每箱有1000件,正品每件100克。其中的几箱是次品,次品每件比正品轻10克,问能否用秤只称一次,就找出哪几箱是次品。
问题分析:
1) 假设只有一箱是次品:先将箱子编码1,2,3,4……10。再从1号箱取1件产品,2号箱取2件产品,3号箱取3件产品……10号箱取10件产品,称它们的重量,若比标准重量轻10克则1号箱为次品;比标准重量轻20克,则2号箱为次品;比标准重量轻30克,则3号箱为次品;……比标准重量轻100克,则10号箱为次品
设取出产品重量为w,则次品的箱号为((1+2+3+……+10)*100-w)/10。
2) 若不止一箱次品时,以上的方法就行不通了。但以上方法的基本思想还是可利用的,即根据取出每箱产品数量的差异,和取出产品的总重量与标准重量误差,来分析推算次品的箱号。
数字化过程:先将箱子编码1,2,3,4……10。用枚举方法分析问题,由小到大讨论:
从“1”号箱取1件产品,若最后总重量比标准重量轻10克,则1号箱为次品;
从“2”号箱取2件产品,若最后总重量比标准重量轻20克,则2号箱为次品;
从“3”号箱若取3件产品,若最后总重量比标准重量轻30克,无法识别哪些箱是次品。但从3号箱取4件产品,若最后总重量比标准重量轻40克,肯定3号箱为次品;
再看“4”号箱,
①取5件产品,若最后总重量比标准重量轻50克,无法识 别哪些是次品:可能是1、3号箱(分别取1件、4件)或5号箱为次品。
② 取6件产品,若最后总重量比标准重量轻60克,无法识别哪些是次品:可能是2、3号箱(分别取2件、4件)或5号箱为次品。
③ 取7件产品,若最后总重量比标准重量轻70克,也无法识别哪些是次品:可能是1、2、3号箱(分别取1件、2件、4件)或5号箱为次品。
④ 取8件产品,则最后总重量比标准重量轻80克,则可以肯定4号箱为次品;无需继续枚举就可看出:1,2,3,4……10号箱取产品的件数分别为20,21,22,23,24,25,26,27,28,29,即1,2,4,8,16,32,64,128,256,512件。
3)根据以上方法,取出产品称量后:
轻10克1号箱为次品。
轻20克2号箱为次品。
轻30克1、2号箱为次品。
轻40克3号箱为次品。
轻50克1、3号箱为次品。
轻60克2、3号箱为次品。
轻70克1、2、3号箱为次品。
轻80克4号箱为次品。
轻90克1、4号箱为次品。
…………
算法设计:用算式或算法来识别误差与次品箱号.首先是计算标准总重量,存储在变量w1中。输入称取的总重量,存储在变量w2中。然后计算出比标准重量轻多少,仍存储在变量w1中。
1)当w/10=2k 时,则k+1号箱为唯一的一箱次品。
2)当w/10>2k k取最大时记为k1,k1+1号箱为的次品箱;
继续讨论w/10-2 k1 >2 k k取最大时记为k2, k2+1号箱为的次品箱;
…………
直到取等号w/10-2 ki-1 =2 ki ,ki+1号箱为的次品箱;结束。
算法如下:
main( ) { int i,k,n,w1=0,w2=0,t; print(“Input the number of boxes:”); input(n); t=1; for (i=1;i<=n;i++) {print(i,“box take” , t,” units.”); w1=w1+t;t=t*2;} w1=w1*100; print(“normal weight ”,w1); print(“ Input reality weight”); input(w2); w1=(w1-w2)/10; { k=0; t=1; while( w1-t>=0) { t=t*2; k=k+1;} print(k,“is bad”); w1=w1-t/2; } }
算法说明:算法中的最后一个内层while 循环及其后语句,是用于计算最接近当前重量w1的2 k,则k号箱为次品箱。这个过程也可以通过使用函数来完成。
main( ) { int i,k,n,w1=0,w2=0,t; print(“Input the number of boxes:”);input(n); t=1; for (i=1;i<=n;i=i+1) {print(i, “box take”,t,”letter.”); w1=w1+t; t=t*2; } w1=w1*100; print(“\n normal weight ”,w1); print(“Input reality weight”);input(w2); w1=(w1-w2)/10; while( w1<>0) {k=log(w1)/log(2); //以2为底取对数 print(k+1 ,“is bad”); w1=w1-pow(2,k); } // pow(2,k)的功能是求2k }
【例5】编写算法对输入的一个整数,判断它能否被3,5,
7整除,并输出以下信息之一:
(1) 能同时被3,5,7整除;
(2) 能被其中两数(要指出哪两个)整除;
(3) 能被其中一个数(要指出哪一个)整除;
(4) 不能被3,5,7任一个整除。
算法1:
main( ) {long n; int k; print(“Please enter a number:”); input(n); k=(n mod 3=0)+(n mod 5=0)+(n mod 7=0) switch (k) { case 3: print(“\n All”); break; case 2: print(“\n two”); break; case 1: print(“\n one”); break; case 0: print(“\n none“); break; }
算法2:
main( ) {long n; int k; print(“ Please enter a number:”); input(n); k=(n mod 3=0)+(n mod 5=0)*2+(n mod 7=0)*4 switch (k) { case 7: print(“ All”); bresk; case 6: print(“ 5 and 7 ”); break; case 5: print(“ 3 and 7 ”); break; case 4: print(“ 7 ”); break; case 3: print(“ 3 and 5 ”); break; case 2: print(“ 5 ”); break; case 1: print(“ 3 ”); break; case 0: print(“ none“); break; }
算法说明:算法中k表示整除的情况值。算法1中,k的范围是0——3可以表示四种情况,而算法2中,k的范围是0——7可以表示八种情况。