一、概念
在计算机编程中,矩阵是一个非常常见的概念,它在各种计算和数据处理任务中发挥着关键作用。在C++中,我们通常使用二维数组来表示矩阵,并使用循环结构进行操作。本文将介绍几个常见的C++矩阵问题,包括旋转和大小控制,以及如何在不使用标准库函数的情况下实现这些操作。
※一定注意,在不会作答的时候不要改了试、改了试,要静下心,重新读题,抓住关键。最好的办法就是画图,可以帮助你高效地理解题目含义和隐藏信息。不要看网上乱七八糟的公式,否则思路中断。相信自己开始的思路。
二、矩阵的旋转
1.顺时针旋转矩阵
(1)画图
顺时针旋转一个矩阵的逻辑看似简单,实际操作还是比较困难的。我们可以尝试不改变数组的值,只改变输出的顺序。这样可以最简洁、最直观地运行正确结果。观察一下未旋转的数字和旋转过的数组:
// 未旋转的数组
// 数字
1 2 3
4 5 6
7 8 9
// 索引
(1,1) (1,2) (1,3)
(2,1) (2,2) (2,3)
(3,1) (3,2) (3,3)
// 旋转后的数组
// 数字
7 4 1
8 5 2
9 6 3
//索引
(3,1) (2,1) (1,1)
(3,2) (2,2) (1,2)
(3,3) (2,3) (1,3)
(2)总结规律
我们可以观察到,每次索引(i,j)的变化应该如下图:
i的变化 | j的变化 |
3 | 1 |
2 | 1 |
1 | 1 |
3 | 2 |
2 | 2 |
1 | 2 |
3 | 3 |
2 | 3 |
1 | 3 |
(3)写代码
i的变化应该是不断减少的,在减少到1的时候会开启下一个循环;j则是每个循环都增加1。知道了这个变化规律以后,我们可以写出如下代码:
#include <iostream>
using namespace std;
int main()
{
int size;
cin >> size; // 题目保证1<=size<=100
int matrix[105][105] = {}; // 为了防止下标越界,多开5格存储
for (int i = 1; i <= size; i++)
{
for (int j = 1; j <= size; j++)
{
cin >> matrix[i][j]; // 按照习惯,索引直接从1开始
}
}
for (int j = 1; j <= size; j++)
{
for (int i = size; i >= 1; i--)
{
cout << matrix[i][j] << " ";
}
cout << endl;
}
return 0;
}
2.逆时针旋转矩阵
(1)画图
一样的方法,换汤不换药。按照之前的方法,先画出图,方便我们理解。
// 未旋转的数组
// 数字
1 2 3
4 5 6
7 8 9
// 索引
(1,1) (1,2) (1,3)
(2,1) (2,2) (2,3)
(3,1) (3,2) (3,3)
// 旋转后的数组
// 数字
3 6 9
2 5 8
1 4 7
//索引
(1,3) (2,3) (3,3)
(1,2) (2,2) (3,2)
(1,1) (2,1) (3,1)
(2)总结规律
索引(i,j)的变化应该是:
i的变化 | j的变化 |
1 | 3 |
2 | 3 |
3 | 3 |
1 | 2 |
2 | 2 |
3 | 2 |
1 | 1 |
2 | 1 |
3 | 1 |
(3)写代码
同样,只是改变输出顺序,并没有改变数组中的值。
#include <iostream>
using namespace std;
int main()
{
int size;
cin >> size; // 题目保证1<=size<=100
int matrix[105][105] = {}; // 为了防止下标越界,多开5格存储
for (int i = 1; i <= size; i++)
{
for (int j = 1; j <= size; j++)
{
cin >> matrix[i][j]; // 按照习惯,索引直接从1开始
}
}
for (int j = size; j >= 1; j--)
{
for (int i = 1; i <= size; i++)
{
cout << matrix[i][j] << " ";
}
cout << endl;
}
return 0;
}
3.镜面翻转和上下翻转矩阵
(1)画图
·镜面翻转
// 未翻转的数组
// 数字
1 2 3
4 5 6
7 8 9
// 索引
(1,1) (1,2) (1,3)
(2,1) (2,2) (2,3)
(3,1) (3,2) (3,3)
// 翻转后的数组
// 数字
3 2 1
6 5 4
9 8 7
//索引
(1,3) (1,2) (1,1)
(2,3) (2,2) (2,1)
(3,3) (3,2) (3,1)
·上下翻转
// 未翻转的数组
// 数字
1 2 3
4 5 6
7 8 9
// 索引
(1,1) (1,2) (1,3)
(2,1) (2,2) (2,3)
(3,1) (3,2) (3,3)
// 翻转后的数组
// 数字
7 8 9
4 5 6
1 2 3
//索引
(3,1) (3,2) (3,3)
(2,1) (2,2) (2,3)
(1,1) (1,2) (1,3)
(2)总结规律
索引(i,j)的规律如下:
·镜面翻转
i的变化 | j的变化 |
1 | 3 |
1 | 2 |
1 | 1 |
2 | 3 |
2 | 2 |
2 | 1 |
3 | 3 |
3 | 2 |
3 | 1 |
·上下翻转
i的变化 | j的变化 |
3 | 1 |
3 | 2 |
3 | 3 |
2 | 1 |
2 | 2 |
2 | 3 |
1 | 1 |
1 | 2 |
1 | 3 |
(3)写代码
·镜面翻转
#include <iostream>
using namespace std;
int main()
{
int size;
cin >> size; // 题目保证1<=size<=100
int matrix[105][105] = {}; // 为了防止下标越界,多开5格存储
for (int i = 1; i <= size; i++)
{
for (int j = 1; j <= size; j++)
{
cin >> matrix[i][j]; // 按照习惯,索引直接从1开始
}
}
for (int i = 1; i <= size; i++) // 变得慢的放在外面
{
for (int j = size; j >= 1; j--) // 变得快的放在里面
{
cout << matrix[i][j] << " ";
}
cout << endl;
}
return 0;
}
·上下翻转
#include <iostream>
using namespace std;
int main()
{
int size;
cin >> size; // 题目保证1<=size<=100
int matrix[105][105] = {}; // 为了防止下标越界,多开5格存储
for (int i = 1; i <= size; i++)
{
for (int j = 1; j <= size; j++)
{
cin >> matrix[i][j]; // 按照习惯,索引直接从1开始
}
}
for (int i = size; i >= 1; i--) // 变得慢的放在外面
{
for (int j = 1; j <= size; j++) // 变得快的放在里面
{
cout << matrix[i][j] << " ";
}
cout << endl;
}
return 0;
}
三、矩阵的形状变化
从这一部分开始,就是最难的了。一般情况下,我们还是用三件套:画图、总结规律、写代码。也许,你的灵感产生于你的画图,可见画图是多么重要。
现在我们来看几个基础的矩阵形状变化的例题。
1.贪吃蛇矩阵
(1)了解题意,画图
贪吃蛇矩阵,就像贪吃蛇一样,会以标准的90度角扭来扭去。比如说:
1 2 3 4
8 7 6 5
9 10 11 12
16 15 14 13
这里,我们可以看到,有一条贪吃蛇从索引(1,1)的位置开始,向右扭到(1,4),然后向下走一个坐标。接着向左扭到(2,1),再向下一格……以此类推。
一样的,反之字矩阵就是下面的样子(其实就是贪吃蛇矩阵的变形):
16 15 14 13
9 10 11 12
8 7 6 5
1 2 3 4
(2)总结规律
下面是行数(l)、方向(dir)和索引(i,j)的关系图:
l | dir | i1 | j1 | i2 | j2 | i3 | j3 | i4 | j4 |
1 | 右 | 1 | 1 | 1 | 2 | 1 | 3 | 1 | 4 |
2 | 左 | 2 | 4 | 2 | 3 | 2 | 2 | 2 | 1 |
3 | 右 | 3 | 1 | 3 | 2 | 3 | 3 | 3 | 4 |
4 | 左 | 4 | 4 | 4 | 3 | 4 | 2 | 4 | 1 |
这里我们可以观察到,灰色的行贪吃蛇都是朝右的,且j1至j4是不断增加的;白色的行贪吃蛇都是朝左的,且j1至j4是不断减少的。根据这个规律,可以列出公式:
1. 在l ≡ 1 (mod 2)的情况下,dir为"右";否则为"左"。 |
2. 在l ≡ 1 (mod 2)的情况下,j(n) = j(n-1) + 1;否则j(n) = j(n-1) - 1。 |
3. 无论如何,i永远等于l。 |
其中,l ≡ 1 (mod 2)意思是,l除以2后余数为1,也就是行数为基数的情况下。
所以,我们就得到综合式:
l ≡ 1 (mod 2), dir = "右", j(n) = j(n-1) + 1
l ≡ 0 (mod 2), dir = "左", j(n) = j(n-1) - 1
(3)写代码
根据综合式,我们可以尝试写出代码。
#include <iostream>
using namespace std;
int main()
{
int size;
cin >> size;
int matrix[105][105] = {};
int number = 1;
for (int i = 1; i <= size; i++)
{
for (int j = 1; j <= size; j++)
{
if (i % 2 == 1)
{
matrix[i][j] = number;
}
else
{
matrix[i][size+1-j] = number;
}
number++;
}
}
for (int i = 1; i <= size; i++)
{
for (int j = 1; j <= size; j++)
{
cout << matrix[i][j] << " ";
}
cout << endl;
}
return 0;
}
代码的核心在存储的过程中。首先要判断所在的行是不是奇数,是就从这个一位数组(也就是这一行)的第一个元素到最后一个元素进行正序存储,否则从第一个元素到最后一个元素进行倒序存储。
反之字矩阵也是一样:
#include <iostream>
using namespace std;
int main()
{
int size;
cin >> size;
int matrix[105][105] = {};
int number = size * size;
for (int i = 1; i <= size; i++)
{
for (int j = 1; j <= size; j++)
{
if (i % 2 == 1)
{
matrix[i][j] = number;
}
else
{
matrix[i][size+1-j] = number;
}
number--;
}
}
for (int i = 1; i <= size; i++)
{
for (int j = 1; j <= size; j++)
{
cout << matrix[i][j] << " ";
}
cout << endl;
}
return 0;
}
2.扩张式矩阵
(1)了解题意,画图
例如,输入3,输出:
1 1 1 1 1
1 2 2 2 1
1 2 3 2 1
1 2 2 2 1
1 1 1 1 1
看起来非常难,但是我们可以总结出数字开始的索引:
1: (1,1)
2: (2,2)
3: (3,3)
…
(2)总结规律
根据数字和索引的规律,我们可以发现数字number开始的位置应该是(number,number)。知道了这个规律,我们就有了思路:重复地按照范围填充数字。比如说1,按照输入3的例子,它的范围是5*5,2是3*3,3是1*1。所以,我们可以增加一个范围变量,来存储其所在的范围。
(3)写代码
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
int matrix[209][209] = {}; // 数组的宽度是1+(n-1)*2
int number = 1;
int scope = n;
while (number <= n)
{
for (int i = number; i <= n-number; i++) // i从number开始
{
for (int j = number; j <= n-number; j++) // j也从number开始
{
matrix[i][j] = number;
}
}
number++; // 每次存储完数字,都要存储下一个数字
scope--; // 范围变得更小
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
cout << matrix[i][j] << " ";
}
cout << endl;
}
}
以上就是一个可以实现生成回字形矩阵的代码。
这是我第一次写博客,如果有什么错误,可以及时评论纠正。感谢浏览!下期会讲更难的矩阵哦,尽情期待~