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可以表示八种情况。

 

posted @ 2019-02-15 00:56  gd_沐辰  阅读(1153)  评论(0编辑  收藏  举报