【题目描述】
输入3个整数,从小到大排序后输出。
【样例输入】
20 7 33
【样例输出】
7 20 33
【解析】
本题解法大概有3种:
1、穷举条件法。
此方法先判断a、b、c大小的所有可能,再根据各种可能性输出不同的排序。
思路是先判断a、b的大小,此时可把a、b想象成一个数轴上的两个点,即将数轴分成3个部分,然后再判断c在这3个部分中的哪一段,由此实现排序输出。
这种方法是正向法,对条件的穷举比较费脑细胞,不够简单明了。
#include<stdio.h>int main(){int a, b, c;scanf("%d%d%d", &a, &b, &c);if(a<b){if(c<a){printf("%d %d %d", c, a, b);}else if(c>b){printf("%d %d %d",a, b, c);}else{printf("%d %d %d",a, c, b);}}else{if(c<b){printf("%d %d %d", c, b, a);}else if(c>a){printf("%d %d %d",b, a, c);}else{printf("%d %d %d",b, c, a);}}return 0;}
2、排列法(最简单的方法)。
这种方法属于逆向思维法,其好处是由结果反推条件,能够避开复杂的逻辑判断。
abc三个数的全排列有6种:abc、acb、bac、bca、cab、cba,所以最简单的方法就是直接用if判断这6种输出对应的条件。
#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”将得不到任何输出!
这个例子说明:样例输出正确不代表程序正确。因为程序要满足对任意符合要求的输入均得到正确的结果,而不仅是样例数据。
上面的问题在于,忽略了输入数据相等的情况。上述6种排列的充分必要条件为:a≤b≤c、a≤c≤b、b≤a≤c、b≤c≤a、c≤a≤b、c≤b≤a。
那把所有的 “<”改成“<=”总可以了吧?很遗憾,还是不行。对于“111”,6种情况全部符合,程序会输出6次“111”。
解决方案是把所有的if改成else if,这样就可以让程序自动排除交叉情况。下面是优化后的最终代码:
#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 if还可以简化成单独的else。
英语单词else表示“其他”,也就是“除此之外”、“否则”。比如一个妹子说:if(你爱我) 我就给你生儿子; else 我就死在你面前;
可见,起到排除交叉作用的正是这个else。
有些人称else if为语句,这是错误的,它们并不共同构成一个独立的语句。因else if虽然写在一起,但它们的关系并没有想象中的那么亲密无间。上面的代码本质上只是一个if…else语句的嵌套。
比如,下面这样一组语句:
if(...) ...;else if(...) ...;else if(...) ...;
它的真实面目是这样的:
if(...) ...;else {if(...) ...;else {if(...) ...;}}
只不过后一种写法有些麻烦,故而把else if凑在一起实现了代码形式上的简洁。
总之,有了else就能让编译器为我们排除条件交叉的情况,从而大大简化逻辑判断。
如果本题只用不含else的if语句,大家就能体会到其逻辑判断有多复杂。
不用else,就要人为设置好所有条件,做到“不重不漏”,这样的条件一共有多少种呢?
如果三整数不相等,那么这道题就简单了,本算法中第一段代码就是正解。
但是如果考虑到等号就比较复杂了,所以本题的关键就是判断输入数据相等的情况。
可以根“相等”的情况将本题的条件分类:
①三个数都不相等;
②有两个数相等;
③三个数都相等。
第①种情况刚才已经讨论过了,第③种情况也很简单(即a=b=c),关键是第②种情况。
第②种情况又可分为三种情形:ab相等、bc相等 、ac相等。
当ab相等时,与c比较又分为两种情形:ab小于c,ab大于c。据此上面三种情形又可细分为6种条件:
a) ab相等时分为两种情形:a=b<c,a=b>c;
b) bc相等时分为两种情形:b=c<a,b=c>a;
c) ac相等时分为两种情形:a=c<b,a=c>b;
综上,本题要判断的条件一共有13种:
①三个数都不相等:6种;
②有两个数相等:6种;
③三个数都相等:1种;
这就是人为判断条件的复杂程度,代码如下:
#include<stdio.h>int main(){int a, b, c;scanf("%d%d%d", &a, &b, &c);//三个数都不相等的6种条件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);//两个数相等的6种条件if(a == b && b < c)printf("%d %d %d\n", a, b, c);if(a == b && b > c)printf("%d %d %d\n", c, a, b);if(b == c && c < a)printf("%d %d %d\n", b, c, a);if(b == c && c > a)printf("%d %d %d\n", a, b, c);if(a == c && c < b)printf("%d %d %d\n", a, c, b);if(a == c && c > b)printf("%d %d %d\n", b, a, c);//三个数都相等的1种条件if(a == b && b == c)printf("%d %d %d\n", a, b, c);return 0;}
这段代码虽然条件很多,但好在理解起来容易。
能不能减少一下判断条件的数量呢,可以的,但是就需要更复杂的逻辑判断,理解起来也更复杂。
思路就是把“两个数相等的6种条件”与“三个数都不相等的6种条件”合并判断,更确切地说是把前者“插入”到后者。
后者表达式中所有的符号都是“<”,所以问题就变成了在后者的表达式中加“=”。
这里面需要注意:合并后的每个条件表达式只能含有一个等号。换句话说,“两个数相等的6种条件”中的每个条件只能与“三个数都不相等的6种条件”中唯一的一个条件合并,6种条件要一一对应。
因为一旦含有两个等号,就会出现重复条件判断。
比如a <= b < c可以分解为:a <b < c,a <=b < c。
而a <= b < =c却会分解为:a <b < c,a <b = c,a=b < c,a=b=c。
如果把要加等号的条件以“&&”为线分为左右两部分,则两侧都不能出现相同的等式,如下图:
总结起来规则有两条:
①每个if条件中只能有一个“=”;
②&&左右两侧都不能出现相同的等式。
假设在第一个条件中的a <b && b < c左侧加入等号,即变成a <=b && b < c,则可以依照上面两条规则从此等号开始按下图路线推出所有要加“=”的地方。
加入“=”后的代码如下:
#include<stdio.h>int main(){int a, b, c;scanf("%d%d%d", &a, &b, &c);//三个数都不相等+两个数相等的6种条件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);//三个数都相等的1种条件if(a == b && b == c)printf("%d %d %d\n", a, b, c);return 0;}
前面讲过,a <= b < =c分解为:a <b < c,a <b = c,a=b < c,a=b=c,所以可以将代码进一步合并:
#include<stdio.h>int main(){int a, b, c;scanf("%d%d%d", &a, &b, &c);//三个数都不相等+两个数相等+三个数都相等的6种条件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;}
这就是不含else的最精简的代码。
之所以弄这么一大通,就是为了让客官体验一下没有else的帮助编写代码将会多么的复杂,而且一不小心等号加错了位置就会前功尽弃。有了else就完全避免了以上复杂的逻辑分析,还不容易出错,真的是又快又准。
3、交换变量排序法:还有一种思路是把a、b、c这3个变量本身改成a≤b≤c的形式。步骤如下:
1)通过交换变量让a变成3个数的最小值。首先让a分别与b、c比较,如果a>b、c,则交换a和b、c(利用前面讲过的三变量交换法),从而保证a≤c,且a≤b,也就是将a变成3个数的最小值。
2)通过交换变量让b变成b、c中的最小值。检查b和c,如果b>c,交换b、c。
#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≤bif(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;}
总结:本题需要注意的是输入限定条件——3个整数。也就是说只要满足3个整数即可,没限定这3个整数相不相等。所以,本题要考虑输入的三个数相等的情况。