入门 · 判断

三个数排序

六种可能顺序,只有一种是对的。你可以用嵌套 if 把它们全写出来 —— 也可以做三次「比较并交换」,让顺序自己浮现。这正是所有真实排序用的同一招。

一句话答案三次「比较并交换」:先保证 <code>a ≤ b</code>,再 <code>a ≤ c</code>,再 <code>b ≤ c</code>;输出 <code>a b c</code>。

题目

输入: 三个整数(各自 −1000 到 1000),空格分隔。允许相等。

输出: 同样三个数按从小到大输出,单个空格分隔。

样例:

输入输出
3 1 21 2 3
5 5 11 5 5

相等值是区分「真排序」和「凑合排序」的测试:5 5 1 必须给出 1 5 5。严格 < 的交换链能处理它 —— 但顺序写错的链就不行。

你的解答

main.cpp — 你的解答
「运行我的输入」用的自定义输入

判题在浏览器本地运行。死循环会在 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;
}