



这道题 第三部分编程题第2题《有序网格》,把我们前面学过的几个知识点串在了一起:
二维数组 + 函数 + 循环 + 冒泡式交换 + 行排序 + 列排序。
这道题最重要的地方,是要理解一句话:
先横着整理,再竖着整理。顺序不能反!
下面我们像玩一个“数字方阵整理游戏”一样来学习。
一、数字王国举行“大扫除”
同学们,想象一下:
有一个数字王国,里面有一块巨大的棋盘。
棋盘上每个格子里都住着一个数字:
┌────┬────┬────┐
│ 6 │ 5 │ 4 │
├────┼────┼────┤
│ 3 │ 2 │ 1 │
└────┴────┴────┘
国王觉得:
“这里的数字太乱了!我要让整个棋盘变得有序!”
于是国王制定了两个命令:
命令①:先整理每一行
每一行从左到右:
从小到大排列。
例如:
6 5 4
整理成:
4 5 6
命令②:所有行整理完以后,再整理每一列
每一列从上到下:
从小到大排列。
注意!
国王特别强调:
🚨 必须先整理行,再整理列!
题目要求的正是:
一个二维网格,先对每一行从左到右升序排序,再对每一列从上到下升序排序,最后输出结果。
二、先看一个最简单的例子
假设有:
3行2列
输入:
6 5
4 3
2 1
我们把它画出来:
┌───┬───┐
│ 6 │ 5 │
├───┼───┤
│ 4 │ 3 │
├───┼───┤
│ 2 │ 1 │
└───┴───┘
三、第一关:横向整理每一行
第一行:
6 5
从小到大:
5 6
第二行:
4 3
变成:
3 4
第三行:
2 1
变成:
1 2
现在棋盘变成:
┌───┬───┐
│ 5 │ 6 │
├───┼───┤
│ 3 │ 4 │
├───┼───┤
│ 1 │ 2 │
└───┴───┘
四、第二关:竖向整理每一列
现在开始整理列。
第一列:
5
3
1
从上到下排序:
1
3
5
第二列:
6
4
2
排序:
2
4
6
最终:
┌───┬───┐
│ 1 │ 2 │
├───┼───┤
│ 3 │ 4 │
├───┼───┤
│ 5 │ 6 │
└───┴───┘
所以输出:
1 2
3 4
5 6
这正是试卷中的样例1。
五、这里有一个特别重要的“陷阱”
孩子可能会想:
“既然最后要行和列都排好,那我先排列,再排行,不也一样吗?”
不可以!
题目明确规定:
第一步:行排序
↓
第二步:列排序
↓
最终答案
我们必须严格按照题目的操作顺序。
可以把它想成:
🧹 先扫地,再拖地。
你不能说:
“我先拖地,再把灰扫一遍。”
否则结果当然可能不同。
六、现在问题来了:怎么在 C++ 里“整理一行”?
假设:
6 5 4 2
我们可以玩一个小游戏:
只比较旁边的两个数字。
第一次比较
看:
6 5
发现:
6 > 5
顺序错了!
交换:
5 6 4 2
第二次比较
看:
6 4
发现:
6 > 4
交换:
5 4 6 2
第三次比较
看:
6 2
发现:
6 > 2
交换:
5 4 2 6
我们发现一个很有意思的事情:
最大的数字 6 被“推”到了最右边。
这就是我们熟悉的:
🫧 冒泡排序思想
最大的数字就像一个大泡泡一样:
5 4 2 6
↑
最大的
“泡泡”
一路向右冒。
七、题目里的参考程序就是这么干的
题目给出的参考程序没有直接使用 sort(),而是自己写了相邻元素交换的方法。
核心代码:
for (int j = 1; j < m; j++)
if (a[n][j] > a[n][j + 1]) {
int tmp = a[n][j];
a[n][j] = a[n][j + 1];
a[n][j + 1] = tmp;
}
我们逐步解析。
八、a[n][j] 是什么?
我们有:
int a[N][N];
这是一个二维数组。
可以想成:
a[1][1] a[1][2] a[1][3] …
a[2][1] a[2][2] a[2][3] …
a[3][1] a[3][2] a[3][3] …
第一个数字:
a[i]
代表:
第几行。
第二个数字:
[j]
代表:
第几列。
所以:
a[n][j]
就是:
第 n 行、第 j 列。
九、a[n][j] 和 a[n][j+1]
这两个家伙是谁?
例如:
第2行:
3 8 5 9
↑ ↑
j j+1
那么:
a[2][2]
是:
8
而:
a[2][3]
是:
5
于是程序比较:
if (a[n][j] > a[n][j + 1])
也就是:
如果左边比右边大,就交换它们。
十、交换数字怎么写?
比如:
8 5
我们要交换。
C++不能直接写:
a = b;
b = a;
因为这样会把原来的数字弄丢。
所以我们需要一个“小仓库”:
int tmp;
例如:
左边:8
右边:5
先:
tmp = 8;
变成:
tmp = 8
左边 = 8
右边 = 5
然后:
左边 = 右边;
变成:
左边 = 5
右边 = 5
最后:
右边 = tmp;
得到:
左边 = 5
右边 = 8
成功!
所以代码:
int tmp = a[n][j];
a[n][j] = a[n][j + 1];
a[n][j + 1] = tmp;
就是:
拿一个临时盒子,帮两个数字交换位置。
十一、把“整理一行”写成一个函数
题目参考程序专门写了一个函数:
void sort_row(int n)
这个函数的任务非常简单:
只负责整理第 n 行。
这就是函数思想:
主程序
│
├── 整理第1行 → sort_row(1)
├── 整理第2行 → sort_row(2)
├── 整理第3行 → sort_row(3)
└── …
这样程序就非常清楚了。
十二、sort_row() 里面发生了什么?
参考代码:
void sort_row(int n) {
for (int i = 1; i <= m; i++)
for (int j = 1; j < m; j++)
if (a[n][j] > a[n][j + 1]) {
int tmp = a[n][j];
a[n][j] = a[n][j + 1];
a[n][j + 1] = tmp;
}
return;
}
这里有两层循环。
外层循环
for (int i = 1; i <= m; i++)
相当于:
我要进行若干轮整理。
内层循环
for (int j = 1; j < m; j++)
表示:
从左到右检查相邻两个数字。
比如:
6 5 4 2
↑ ↑
然后:
6 5 4 2
↑ ↑
然后:
6 5 4 2
↑ ↑
不断比较:
第1个和第2个
第2个和第3个
第3个和第4个
十三、为什么要有两层循环?
这就是冒泡排序。
例如:
6 5 4 2
第一轮:
6 5 → 交换
5 6 4 2
6 4 → 交换
5 4 6 2
6 2 → 交换
5 4 2 6
最大的 6 到最右边。
第二轮:
5 4 → 交换
4 5 2 6
5 2 → 交换
4 2 5 6
第二大的 5 到了正确位置。
第三轮:
4 2 → 交换
2 4 5 6
完成!
十四、然后我们要“整理所有行”
主程序:
for (int i = 1; i <= n; i++)
sort_row(i);
如果:
n = 3
就相当于:
sort_row(1);
sort_row(2);
sort_row(3);
也就是:
整理第1行
↓
整理第2行
↓
整理第3行
到这里:
所有行都已经从小到大。
十五、但是故事还没有结束!
现在轮到:
🏰 “列管理员”出场!
题目要求:
所有行排完以后,再把每一列从上到下排序。
所以程序又写了一个函数:
void sort_col(int m)
这个函数负责:
整理第 m 列。
十六、整理列和整理行其实是“镜像操作”
整理行:
→ → → →
整理列:
↓
↓
↓
例如:
5 6
3 4
1 2
我们整理第一列:
5
3
1
相邻比较:
5
3
发现:
5 > 3
交换:
3
5
1
再看:
5
1
交换:
3
1
5
继续下一轮:
1
3
5
于是第一列整理完成。
十七、列排序的代码
参考程序:
void sort_col(int m) {
for (int i = 1; i <= n; i++)
for (int j = 1; j < n; j++)
if (a[j][m] > a[j + 1][m]) {
int tmp = a[j][m];
a[j][m] = a[j + 1][m];
a[j + 1][m] = tmp;
}
return;
}
注意这里:
a[j][m]
第一个下标:
j
不断变化。
所以:
行号在变化。
而:
m
固定。
所以:
列号固定。
这正是:
固定一列,从上往下比较。
十八、孩子特别容易把两个函数弄混
可以这样记:
整理行
a[n][j]
n固定
j变化
所以:
第n行
→ → → →
整理列
a[j][m]
j变化
m固定
所以:
第m列
↓
↓
↓
十九、我们用一个稍微复杂的例子完整走一遍
题目样例2:
1 3 2 5
6 2 4 4
5 4 1 3
这是试卷中的输入样例2。
第一步:每一行排序
第一行:
1 3 2 5
变成:
1 2 3 5
第二行:
6 2 4 4
变成:
2 4 4 6
第三行:
5 4 1 3
变成:
1 3 4 5
所以现在:
1 2 3 5
2 4 4 6
1 3 4 5
二十、第二步:每一列排序
第一列:
1
2
1
排序:
1
1
2
第二列:
2
4
3
排序:
2
3
4
第三列:
3
4
4
已经有序。
第四列:
5
6
5
排序:
5
5
6
最后:
1 2 3 5
1 3 4 5
2 4 4 6
这正是试卷给出的样例2输出。
二十一、现在来看完整程序
题目参考程序如下:
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 15;
int n, m;
int a[N][N];
// 排序第 n 行
void sort_row(int n) {
for (int i = 1; i <= m; i++)
for (int j = 1; j < m; j++)
if (a[n][j] > a[n][j + 1]) {
int tmp = a[n][j];
a[n][j] = a[n][j + 1];
a[n][j + 1] = tmp;
}
return;
}
// 排序第 m 列
void sort_col(int m) {
for (int i = 1; i <= n; i++)
for (int j = 1; j < n; j++)
if (a[j][m] > a[j + 1][m]) {
int tmp = a[j][m];
a[j][m] = a[j + 1][m];
a[j + 1][m] = tmp;
}
return;
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
scanf("%d", &a[i][j]);
// 先排序所有行
for (int i = 1; i <= n; i++)
sort_row(i);
// 再排序所有列
for (int i = 1; i <= m; i++)
sort_col(i);
// 输出
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
printf("%d%c", a[i][j], " \\n"[j == m]);
return 0;
}
二十二、把整个程序想象成“两个机器人”
我们有两个机器人:
🤖 横向机器人
sort_row()
只干一件事:
把一行整理好。
🤖 竖向机器人
sort_col()
只干一件事:
把一列整理好。
主程序就是机器人队长:
👑 队长
│
┌────────┴────────┐
↓ ↓
🤖横向机器人 🤖竖向机器人
sort_row sort_col
│ │
↓ ↓
所有行排序 所有列排序
│ │
└────────┬────────┘
↓
输出
这其实就是非常好的模块化编程思想:
一个函数只负责一件事情。
二十三、要注意行与列:
程序中:
int n, m;
表示:
n = 行数
m = 列数
例如:
3行4列
就是:
n = 3
m = 4
二维数组:
a[i][j]
可以记成:
第 i 行,第 j 列。
二十四、为什么数组开成 a[15][15]?
参考程序:
const int N = 15;
int a[N][N];
题目中的所有数据都能覆盖。
二十五、最后输出部分有一个“小机关”
程序最后:
printf("%d%c", a[i][j], " \\n"[j == m]);
其实它只是在解决一个问题:
每行最后一个数字后面要换行,其他数字后面要空格。
例如:
1 2 3
4 5 6
当:
j == m
说明:
已经到了这一行最后一个数字。
所以:
j == m
为真。
程序选择:
\\n
换行。
如果还没到最后:
j != m
就选择:
' '
空格。
同学们完全可以把它理解成:
if (j == m)
printf("%d\\n", a[i][j]);
else
printf("%d ", a[i][j]);
这样反而更容易理解。
二十六、这道题最重要的“程序结构”
同学们最后一定要能够自己说出:
① 建立二维数组
↓
② 输入整个网格
↓
③ 一行一行排序
↓
④ 一列一列排序
↓
⑤ 输出最终网格
也就是:
⭐ “先横后竖”
这是这道题的灵魂。
二十七、这道题考查的知识点
| 二维数组 | 保存整个数字网格 |
| a[i][j] | 定位第 i 行第 j 列 |
| for循环 | 遍历行、列 |
| if | 判断两个数字是否需要交换 |
| 临时变量 tmp | 完成两个数字交换 |
| 函数 | 分别完成行排序和列排序 |
| 冒泡排序思想 | 完成一行/一列的整理 |
| 函数调用 | sort_row(i)、sort_col(i) |
| scanf/printf | 输入和输出 |
二十八、给孩子一个“考场思维模板”
以后看到类似题目:
二维数组 + 每行排序 + 每列排序
马上在脑袋里出现:
二维数组
↓
a[i][j]
↓
┌───────┴───────┐
↓ ↓
排一行 排一列
↓ ↓
a[n][j] a[j][m]
↓ ↓
j变化 j变化
n固定 m固定
↓ ↓
←→←→←→ ↑↓↑↓↑↓
然后:
先 row
后 col
二十九、容易错的5个地方
❌ 1. 把行和列搞反
a[i][j]
一定记住:
第一个是行,第二个是列。
❌ 2. 行排序时乱改第一个下标
行排序:
a[n][j]
应该:
n固定
j变化
❌ 3. 列排序时乱改第二个下标
列排序:
a[j][m]
应该:
j变化
m固定
❌ 4. 忘记题目规定的顺序
一定:
行 → 列
不是:
列 → 行
❌ 5. 交换时没有临时变量
不要写:
a = b;
b = a;
应该:
int tmp = a;
a = b;
b = tmp;
三十、给大家一个超级好记的口诀
🧙♂️ 有序网格整理术:
第一招:横着排
一行一行排
左小右大
第二招:竖着排
一列一列排
上小下大
第三招:两个数打架怎么办?
左 > 右
↓
交换!
第四招:谁负责什么?
sort_row → 整理行
sort_col → 整理列
第五招:最重要!
先横后竖,顺序不能乱!
附新方法:
除了官方提供的参考程序之外,我们也可以采用“ sort+矩阵转置 ”来解决这个问题。
一、先回到题目:我们到底要做什么?
题目的任务是:
给一个 n × m 的二维网格,先把每一行从小到大排序,再把每一列从上到下排序,最后输出。
比如:
6 5
4 3
2 1
第一步:排每一行。
5 6
3 4
1 2
第二步:排每一列。
1 2
3 4
5 6
二、第一个问题:行排序非常简单
我们已经学过:
sort()
比如:
int a[5] = {6, 5, 4, 3, 2};
sort(a, a + 5);
就可以变成:
2 3 4 5 6
所以二维数组中的一行,也可以直接用 sort()!
假设:
int a[15][15];
第 i 行是:
a[i][1]
a[i][2]
a[i][3]
…
a[i][m]
那么:
sort(a[i] + 1, a[i] + m + 1);
就可以把第 i 行排好。
三、为什么是 a[i] + 1?
这是小学生第一次看到时最容易懵的地方。
假设:
int a[15][15];
我们采用:
1号行
1号列
开始使用。
那么:
a[i][1]
就是这一行的第一个数字。
而:
sort(a[i] + 1, a[i] + m + 1);
表示:
从 a[i][1] 开始,一直到 a[i][m],全部进行排序。
因为 sort() 的右边界是不包含的。
所以:
sort(开始位置, 结束位置)
是:
[开始位置,结束位置)
因此:
sort(a[i] + 1, a[i] + m + 1);
正好排序:
a[i][1] ~ a[i][m]
四、所以第一步只需要几行代码
for (int i = 1; i <= n; i++) {
sort(a[i] + 1, a[i] + m + 1);
}
是不是比手写冒泡排序简单很多?
原来的思路是:
比较
↓
交换
↓
比较
↓
交换
↓
再比较
↓
再交换
现在我们直接:
sort!
就完成了。
五、可是第二步遇到麻烦了
现在假设经过第一步:
1 2 3
4 6 5
7 9 8
我们需要排序每一列。
比如第一列:
1
4
7
第二列:
2
6
9
第三列:
3
5
8
问题来了:
sort() 最擅长的是“一行一行排序”。
但是现在我们需要:
一列一列排序。
怎么办?
六、我们会想到一个办法
我们问自己:
sort() 不会排列,那我能不能把“列”变成“行”?
当然可以!
这就需要一个神奇操作:
⭐ 矩阵转置
七、什么叫“矩阵转置”?
听起来很高深,其实特别简单。
比如原来的表格:
1 2 3
4 5 6
7 8 9
把:
第1行
第2行
第3行
变成:
第1列
第2列
第3列
也就是:
原来的:
1 2 3
4 5 6
7 8 9
转置以后:
1 4 7
2 5 8
3 6 9
你会发现:
原来的第1列:
1
4
7
变成:
新数组的第1行:
1 4 7
太巧妙了!
八、用“搬家”来理解转置
我们可以这样理解:
原来的数字住在:
🏠 原来的二维数组
现在我们准备一栋新房子:
🏠 新的二维数组 b
然后规定:
原来的“第 i 行、第 j 列”的数字,搬到新房子的“第 j 行、第 i 列”。
也就是:
b[j][i] = a[i][j];
这就是转置的核心代码。
九、举一个数字看看
原数组:
a:
1 2 3
4 5 6
7 8 9
比如:
a[2][3]
是多少?
6
按照转置规则:
b[3][2] = a[2][3];
所以:
b[3][2] = 6
再比如:
a[1][2] = 2
那么:
b[2][1] = a[1][2];
所以:
b[2][1] = 2
十、于是整个二维数组就“翻转”了
原来:
a:
1 2 3
4 5 6
7 8 9
执行:
b[j][i] = a[i][j];
以后:
b:
1 4 7
2 5 8
3 6 9
注意:
行和列的位置交换了。
所以叫:
转置
可以让孩子记一句:
转置就是:行列互换。
十一、这时候发生了一件特别神奇的事情
原来:
a:
1 2 3
4 6 5
7 9 8
我们真正想排序的是:
列:
1 2 3
4 6 5
7 9 8
↓ ↓ ↓
但是转置以后:
b:
1 4 7
2 6 9
3 5 8
你发现了吗?
原来的:
第一列:
1
4
7
现在变成:
第一行:
1 4 7
原来的:
第二列:
2
6
9
现在变成:
第二行:
2 6 9
原来的:
第三列:
3
5
8
现在变成:
第三行:
3 5 8
十二、哇!现在可以再次使用 sort() 了!
我们终于把:
排序列
变成了:
排序行
所以:
for (int i = 1; i <= m; i++) {
sort(b[i] + 1, b[i] + n + 1);
}
注意这里有一个非常重要的变化。
原来 a 是:
n 行 × m 列
转置以后 b 是:
m 行 × n 列
所以:
a:
n × m
变成:
b:
m × n
十三、举例说明
假设:
a:
1 2 3
4 6 5
7 9 8
转置:
b:
1 4 7
2 6 9
3 5 8
现在对 b 的每一行排序。
第一行:
1 4 7
不用动。
第二行:
2 6 9
不用动。
第三行:
3 5 8
也不用动。
如果原来的列是乱的,比如:
a:
1 2 3
6 4 5
7 9 8
转置:
b:
1 6 7
2 4 9
3 5 8
如果继续:
sort(b[i] + 1, b[i] + n + 1);
就可以把这些“原来的列”分别排好。
十四、排完以后怎么办?
现在 b 已经排好了。
但是题目最后要求输出的是:
原来的 n × m 网格
所以我们需要:
再转置一次!
也就是:
a[i][j] = b[j][i];
是不是和刚才反过来了?
刚才:
b[j][i] = a[i][j];
现在:
a[i][j] = b[j][i];
十五、为什么转置两次可以回来?
可以把它想成:
🪞 镜子照一次,左右反过来。
🪞 再照一次,又回来了。
比如:
第一次转置:
1 2 3
4 5 6
↓
1 4
2 5
3 6
第二次:
1 4
2 5
3 6
↓
1 2 3
4 5 6
所以:
⭐ 转置两次 = 回到原来的形状
十六、完整算法就出现了
现在整个题目可以浓缩成:
原始二维数组
↓
① 每一行 sort
↓
行已经有序
↓
② 转置
↓
原来的“列”变成现在的“行”
↓
③ 每一行 sort
↓
原来的“列”已经有序
↓
④ 再转置
↓
恢复原来的 n × m 形状
↓
⑤ 输出
是不是非常漂亮?
十七、我们用样例完整走一次
题目样例2:
1 3 2 5
6 2 4 4
5 4 1 3
题目要求最终得到:
1 2 3 5
1 3 4 5
2 4 4 6
这个样例及其输出见题目原文。
第一步:每行 sort
原来:
1 3 2 5
6 2 4 4
5 4 1 3
每行排序:
1 2 3 5
2 4 4 6
1 3 4 5
十八、第二步:转置
现在:
a:
1 2 3 5
2 4 4 6
1 3 4 5
转置以后:
b:
1 2 1
2 4 3
3 4 4
5 6 5
注意看:
b第1行:
1 2 1
它其实就是:
a第1列:
1
2
1
十九、第三步:对 b 的每一行 sort
第一行:
1 2 1
变成:
1 1 2
第二行:
2 4 3
变成:
2 3 4
第三行:
3 4 4
不变。
第四行:
5 6 5
变成:
5 5 6
所以:
b:
1 1 2
2 3 4
3 4 4
5 5 6
二十、第四步:再次转置
把:
b[j][i]
放回:
a[i][j]
得到:
1 2 3 5
1 3 4 5
2 4 4 6
完成!
二十一、最终代码
按照这个思路,可以写成一个非常清晰的版本:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 15;
int n, m;
int a[N][N];
int b[N][N];
int main() {
cin >> n >> m;
// 读入二维数组
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> a[i][j];
}
}
// 第一步:每一行从小到大排序
for (int i = 1; i <= n; i++) {
sort(a[i] + 1, a[i] + m + 1);
}
// 第二步:转置
// 原来的列,变成新的行
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
b[j][i] = a[i][j];
}
}
// 第三步:对转置后的每一行排序
// 其实就是在排序原来的每一列
for (int i = 1; i <= m; i++) {
sort(b[i] + 1, b[i] + n + 1);
}
// 第四步:再次转置,恢复到原来的形状
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
a[i][j] = b[j][i];
}
}
// 第五步:输出
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cout << a[i][j];
if (j == m)
cout << '\\n';
else
cout << ' ';
}
}
return 0;
}
二十二、这份代码和官方参考程序最大的区别
官方参考程序的思路是:
自己写排序
↓
sort_row()
↓
手写相邻比较、交换
↓
sort_col()
↓
再次手写相邻比较、交换
而我们现在的方法:
sort()
↓
行排序
↓
转置
↓
sort()
↓
列变成行
↓
再次排序
↓
转置回来
二十三、这个方法漂亮的地方在哪里?
就在这里:
我想排序列
↓
sort不会直接排列
↓
┌──────────────┐
│ 那就把列变成行 │
└──────────────┘
↓
转置
↓
原来的列 → 新的行
↓
sort()
↓
排序完成
↓
再转置
↓
回来了
这其实是一种非常重要的算法思想:
⭐ “把不会做的问题,转换成会做的问题。”
二十四、这比“记住列排序代码”更重要
以后大家遇到一个问题:
“我不会处理列。”
不要马上想:
“我要不要重新写一套代码?”
而应该问:
“能不能把列变成行?”
如果可以:
列
↓
转置
↓
行
↓
sort()
问题就解决了。
这就是一种很有价值的问题转换思想。
二十五、讲一讲 i 和 j 的变化
这里尤其容易混淆。
原数组:
a[i][j]
转置:
b[j][i] = a[i][j];
也就是:
原来的:
第 i 行
第 j 列
↓
新的:
第 j 行
第 i 列
所以:
i 和 j 交换位置了。
可以记:
🪄 转置魔法
a[i][j]
↓
b[j][i]
一句口诀:
“行列交换,两个下标也交换!”
二十六、为什么 b 的大小也发生变化?
假设:
n = 3
m = 4
那么原数组:
a:
3行4列
转置后:
b:
4行3列
所以:
a:n × m
b:m × n
这也是为什么:
sort(a[i] + 1, a[i] + m + 1);
而第二次变成:
sort(b[i] + 1, b[i] + n + 1);
前者每行有 m 个数字。
后者每行有 n 个数字。
二十七、孩子最容易犯的几个错误
❌ 错误1:转置写成
b[i][j] = a[i][j];
这根本没有转置。
应该是:
b[j][i] = a[i][j];
❌ 错误2:第二次排序仍然使用 m
错误:
sort(b[i] + 1, b[i] + m + 1);
因为 b 已经变成:
m行n列
所以每行有 n 个元素。
正确:
sort(b[i] + 1, b[i] + n + 1);
❌ 错误3:忘记转回来
排序完 b 以后不能直接输出 b,因为:
b 是 m × n
而题目要求输出:
n × m
所以必须:
a[i][j] = b[j][i];
❌ 错误4:把顺序搞反
一定是:
① 排行
② 转置
③ 排行
④ 转置回来
不能一开始就转置。
因为题目要求:
先每行排序,再每列排序。
二十八、最后给同学们画一张“藏宝图”
整道题可以记成:
有序网格
│
↓
┌───────────────┐
│ ① 行排序 │
│ sort() │
└───────┬───────┘
↓
┌───────────────┐
│ ② 转置 │
│ a[i][j] │
│ ↓ │
│ b[j][i] │
└───────┬───────┘
↓
原来的“列”
变成新的“行”
↓
┌───────────────┐
│ ③ 行排序 │
│ sort() │
└───────┬───────┘
↓
原来的列排好了
↓
┌───────────────┐
│ ④ 再转置 │
│ b[j][i] │
│ ↓ │
│ a[i][j] │
└───────┬───────┘
↓
输出
二十九、给大家的最终口诀
🪄 “先横后竖,竖着不会排,就把竖变横;排完以后,再转回来!”
对应代码就是:
// ① 每行排序
sort(a[i] + 1, a[i] + m + 1);
// ② 转置
b[j][i] = a[i][j];
// ③ 再次用 sort 排行
sort(b[i] + 1, b[i] + n + 1);
// ④ 转置回来
a[i][j] = b[j][i];
这道题真正值得小学生学会的,不只是 sort(),也不只是“矩阵转置”,而是一个非常重要的编程思维:
当一个问题不好做的时候,不一定非要硬着头皮解决它;
有时候,把问题换一个方向,它就突然变成自己熟悉的问题了。
这里就是:
“不会排序列”
↓
“把列变成行”
↓
“我会排序行!”
↓
sort()
这就是非常漂亮的算法转换思想。
网硕互联帮助中心




评论前必须登录!
注册