入门 · 判断
三个数排序
六种可能顺序,只有一种是对的。你可以用嵌套 if 把它们全写出来 —— 也可以做三次「比较并交换」,让顺序自己浮现。这正是所有真实排序用的同一招。
一句话答案三次「比较并交换」:先保证 <code>a ≤ b</code>,再 <code>a ≤ c</code>,再 <code>b ≤ c</code>;输出 <code>a b c</code>。
题目
输入: 三个整数(各自 −1000 到 1000),空格分隔。允许相等。
输出: 同样三个数按从小到大输出,单个空格分隔。
样例:
| 输入 | 输出 |
|---|---|
3 1 2 | 1 2 3 |
5 5 1 | 1 5 5 |
相等值是区分「真排序」和「凑合排序」的测试:5 5 1 必须给出 1 5 5。严格 < 的交换链能处理它 —— 但顺序写错的链就不行。
你的解答
「运行我的输入」用的自定义输入
判题在浏览器本地运行。死循环会在 3 秒后被自动中断 —— 但还是确保每个循环都有出口。代码随打随存。
卡住了?提示
提示 1 — 交换写一次,复用三次
交换两个数 里那三行,在这里出现三次:if (a > b) { int t = a; a = b; b = t; },a, c 和 b, c 同理。同一个块,换一对变量。
提示 2 — 为什么这个顺序成立
第一次比较后,a 持有原来 a、b 中较小的那个。第二次之后,a 也不大于 c —— 所以 a 是全局最小,已经定了。第三次比较只需要理顺剩下两个。看清这个不变量,那三行就不再像随手写的。
提示 3 — 另一种:嵌套 if
你可以用 if (a <= b && b <= c) ... else if ... 把六种顺序全列出来,也能过。但那是六个分支,每个都重复三行 cout —— 而且加到第四个数就彻底没法写了。比较交换才是能长大的写法。
题解(先自己试试!)
#include <iostream>
using namespace std;
int main() {
int a, b, c;
cin >> a >> b >> c;
if (a > b) { int t = a; a = b; b = t; }
if (a > c) { int t = a; a = c; c = t; }
if (b > c) { int t = b; b = c; c = t; }
cout << a << " " << b << " " << c << endl;
return 0;
}
三次比较交换,就是把循环展开的冒泡排序 —— 通用版见 冒泡排序,必须保持配对的两个数组怎么排序见 区间合并。「反复交换而不是枚举情况」这个直觉值得留住。
深度解析
为什么这样做对
按 (a,b)、(a,c)、(b,c) 的顺序做三次比较交换,就建立了 a ≤ b ≤ c 的不变量:前两次比较之后 a 已经是三者的最小值,所以第三次只需要理顺剩下那一对。这就是把循环展开的冒泡排序。
代价
O(1) —— 至多三次比较、三次交换;通用版冒泡排序是 O(n²),好排序是 O(n log n)。
常见错法
- 比较顺序写错。先做 (b,c) 再做 (a,b),
a就没有和c验证过 —— 经典的「只排好一半」bug。 - 用嵌套 if 枚举全部六种顺序:能过,但六个重复的打印块,加到第四个数就彻底没法写。
- 交换时把临时变量弄丢,一个值重复、另一个消失。
记住这两句
- 按正确顺序三次比较交换就能排好三个值 —— 这就是展开的冒泡排序。
- 前两次比较后
a已是全局最小,第三次只需理顺其余两个。
🐞 这段是错的 —— 点开,亲手让它挂一次
先比较 (b,c) 再 (a,c) —— a 从没和最大的那个比过
#include <iostream>
using namespace std;
int main() {
int a, b, c;
cin >> a >> b >> c;
if (b > c) { int t = b; b = c; c = t; } // bug: wrong order
if (a > b) { int t = a; a = b; b = t; }
cout << a << " " << b << " " << c << endl;
return 0;
}