1.4 分支结构程序设计

例题1-4 鸡兔同笼

已知鸡和兔的总数量为n,总腿数为m。输入nm,依次输出鸡的数目和兔的数目。如果无解,则输出No answer。

样例输入:

14 32

样例输出:

12 2

样例输入:

10 16

样例输出:

No answer

【分析】

设鸡有a只,兔有b只,则abn,2a+4bm,联立解得a=(4nm)/2,bna。在什么情况下此解“不算数”呢?首先,ab都是整数;其次,ab必须是非负的。可以通过下面的程序判断:

程序1-11 鸡兔同笼


#include<stdio.h>
int main()
{
  int a, b, n, m;
  scanf("%d%d", &n, &m);
  a = (4*n-m)/2;
  b = n-a;
  if(m % 2 == 1 || a < 0 || b < 0)
    printf("No answer\n");
  else
    printf("%d %d\n", a, b);
  return 0;
}

上面的程序用到了if语句,其一般格式是:


if(条件)
   语句1;
else
   语句2;

注意语句1和语句2后面的分号,以及if后面的括号。“条件”是一个表达式,当该表达式的值为“真”时执行语句1,否则执行语句2。另外,“else语句2”是可以省略的。语句1和语句2前面的空行是为了让程序更加美观,并不是必需的,但强烈推荐读者使用。

提示1-15:if语句的基本格式为:if(条件)语句1;else语句2。

换句话说,“”是一个表达式,其字面意思是“m是奇数,或者a小于0,或者b小于0”。这句话可能正确,也可能错误。因此这个表达式的值可能为真,也可能为假,取决于m、a和b的具体数值。

这样的表达式称为逻辑表达式。和算术表达式类似,逻辑表达式也由运算符和值构成,例如“||”运算符称为“逻辑或”,a||b表示a为真,或者b为真。换句话说,a和b只要有一个为真,a||b就为真;如果a和b都为真,则a||b也为真。和其他语言不同的是,在C语言中单个整数也可以表示真假,其中0为假,其他值为真。

提示1-16:if语句的条件是一个逻辑表达式,它的值可能为真,也可能为假。单个整数值也可以表示真假,其中0为假,其他值为真。

细心的读者也许发现了,如果a为真,则无论b的值如何,a||b均为真。换句话说,一旦发现a为真,就不必计算b的值。C语言正是采取了这样的策略,称为短路(short-circuit)。也许读者会觉得,用短路的方法计算逻辑表达式的唯一优点是速度更快,但其实并不是这样,稍后将通过几个例子予以证实。

提示1-17:C语言中的逻辑运算符都是短路运算符。一旦能够确定整个表达式的值,就不再继续计算。

例题1-5 三整数排序

输入3个整数,从小到大排序后输出。

样例输入:

20 7 33

样例输出:

7 20 33

【分析】

abc这3个数一共只有6种可能的顺序:abcacbbacbcacabcba,所以最简单的思路是使用6条if语句。

程序1-12 三整数排序(1)(错误)


#include<stdio.h>
int main()
{
  int a, b, c;
  scanf("%d%d%d", &a, &b, &c);
  if(a < b && b < c)printf("%d %d %d\n", a, b, c);
  if(a < c && c < b)printf("%d %d %d\n", a, c, b);
  if(b < a && a < c)printf("%d %d %d\n", b, a, c);
  if(b < c && c < a)printf("%d %d %d\n", b, c, a);
  if(c < a && a < b)printf("%d %d %d\n", c, a, b);
  if(c < b && b < a)printf("%d %d %d\n", c, b, a);
  return 0;
}

上述程序看上去没有错误,而且能通过题目中给出的样例,但可惜有缺陷:输入“111”将得不到任何输出!这个例子说明:即使通过了题目中给出的样例,程序仍然可能存在问题。

提示1-18:算法竞赛的目标是编程对任意输入均得到正确的结果,而不仅是样例数据。

将程序稍作修改:把所有的小于符号“<”改成小于等于符号“<=”(在一个小于号后添加一个等号)。这下总可以了吧?很遗憾,还是不行。对于“111”,6种情况全部符合,程序一共输出了6次“111”。

一种解决方案是人为地让6种情况没有交叉:把所有的if改成else if。

程序1-13 三整数排序(2)


#include<stdio.h>
int main()
{
  int a, b, c;
  scanf("%d%d%d", &a, &b, &c);
  if(a <= b && b <= c) printf("%d %d %d\n", a, b, c);
  else if(a <= c && c <= b) printf("%d %d %d\n", a, c, b);
  else if(b <= a && a <= c) printf("%d %d %d\n", b, a, c);
  else if(b <= c && c <= a) printf("%d %d %d\n", b, c, a);
  else if(c <= a && a <= b) printf("%d %d %d\n", c, a, b);
  else if(c <= b && b <= a) printf("%d %d %d\n", c, b, a);
  return 0;
}

最后一条语句还可以简化成单独的else(想一想,为什么),不过,幸好程序正确了。

提示1-19:如果有多个并列、情况不交叉的条件需要一一处理,可以用else if语句。

另一种思路是把abc这3个变量本身改成abc的形式。首先检查ab的值,如果ab,则交换ab(利用前面讲过的三变量交换法);接下来检查ac,最后检查bc,程序如下:

程序1-14 三整数排序(3)


#include<stdio.h>
int main()
{
  int a, b, c, t;
  scanf("%d%d%d", &a, &b, &c);
  if(a > b) { t = a; a = b; b = t; } //执行完毕之后a≤b
  if(a > c) { t = a; a = c; c = t; } //执行完毕之后a≤c,且a≤b依然成立
  if(b > c) { t = b; b = c; c = t; }
  printf("%d %d %d\n", a, b, c);
  return 0;
}

为什么这样做是对的呢?因为经过第一次检查以后,必然有ab,而第二次检查以后ac。由于第二次检查以后a的值不会变大,所以ab依然成立。换句话说,a已经是3个数中的最小值。接下来只需检查bc的顺序即可。值得一提的是,上面的代码把上述推理写入注释,成为程序的一部分。这不仅可以让其他用户更快地搞懂你的程序,还能帮你自己理清思路。在C语言中,单行注释从“//”开始直到行末为止;多行注释用“/*”和“*/”包围起来(6)

提示1-20:适当在程序中编写注释不仅能让其他用户更快地搞懂你的程序,还能帮你自己理清思路。

注意上面程序中的花括号。前面讲过,if语句中有一个“语句1”和可选的“语句2”,且都要以分号结尾。有一种特殊的“语句”是由花括号括起来的多条语句。这多条语句可以作为一个整体,充当if语句中的“语句1”或“语句2”,且后面不需要加分号。当然,当if语句的条件满足时,这些语句依然会按顺序逐条执行,和普通的顺序结构一样。

提示1-21:可以用花括号把若干条语句组合成一个整体。这些语句仍然按顺序执行。