下面的内容涉及二维数组的知识,建议学习过的朋友食用哦!
前言
矩阵转置是将原矩阵的行与列进行互换所得到的新矩阵。
具体来说,给定一个m×n阶矩阵A,其转置矩阵AT是一个n×m阶矩阵,满足AT[i][j] = A[j][i],其中1≤i≤n,1≤j≤m。即转置矩阵的第i行第j列元素等于原矩阵的第j行第i列元素。
求转置矩阵的方法相对简单,只需要创建一个新的空矩阵AT,其行数与原矩阵A的列数相同,列数与原矩阵A的行数相同,然后遍历原矩阵A的每一个元素A[i][j],将其赋值给新矩阵AT的对应位置AT[j][i]即可。
这么讲完你可能还是一头雾水,没关系,接下来就由我来将矩阵转置的过程细细剖析,嚼烂了、教给你^_^
问题
现在我们先有一个二维数组arr:
cpp
#include<stdio.h>
int main()
{
int arr[3][5] = {{1,2,3,4,5},{6,7,8,9,10},{11,12,13,14,15}};
int i, j;//i来控制行的输出,j控制列的输出
for (i = 0; i < 3; i++)
{
for (j = 0; j < 5; j++)
{
printf("%3d ", arr[i][j]);//写成%3d,让打印出来的矩阵更整齐一点
}
printf("\n");//每一行内容打印完一定要换行!
}
return 0;
}
在vs中打印效果如下:
那么现在我们想要的无非就是变成这种效果:
说得直观点,我们就是想把上面那张图中的效果按列打印出来。原本的第一列是1 6 11,现在变成了我们的第一行,原本的第二列2 7 12,现在变成了我们的第二行......
分析
第一处变化
虽然转置了,但本质上还是要打印二维数组,所以仍然需要一个循环嵌套控制行和列的输出。我们可以重复使用i和j进行转置后的输出。
为了达到我们要的效果,第一步我们要做的就是对i和j下手,改造成下方这个样子:
cpp
for (i = 0; i < 5; i++)
{
for (j = 0; j < 3; j++)
{
//这里先不管
}
printf("\n");
为什么要改成这样呢?在理解这一点之前我们必须先明白打印的本质。通俗点讲,难道因为arr是二维数组打印就自动变成这种行和列的形式了吗?并不是的。二维数组在内存中是怎么存放的呢?其实和一维数组一样是连续存放的:
可不要理解成二维数组在内存中的存放就像打印二维数组那样,是以行和列的方式的啊!
所以,真正决定二维数组打印成行和列的形式的其实是变量i和j,学过循环和数组内容的我们知道,i控制打印几行,j控制打印几列,for(i=0;i<3;i++)决定了打印出来是3行,for(j=0;j<5;j++)决定了打印出来是5列,当然还要在每一行内容打印完记得换行才能达到我们要的效果。
那么你可能会问,既然都是连续存放的,那二维数组和一维数组比有什么不同的?或者我们就说,arr[3][5]和arr[15]都是连续存放15个元素,有什么区别吗?其实,你可以把二维数组理解为一个存放了三个一维数组的数组,它的每个元素是一个一维数组,也就是它的每一行。正如我们上面在初始化二维数组arr时赋值内容里写的{1,2,3,4,5},{6,7,8,9,10},{11,12,13,14,15},它们是三个一维数组,也是arr的每一行。
有点扯远了,我们不细讲二维数组的内容,现在我们回到这个问题来。
现在我们要转置,我们稍加观察可以发现,我们原本是要打印3行5列,现在要打印5行3列,所以根据上面说的,如果继续用i控制行输出,j控制列的输出,我们需要for(i=0;i<5;i++),for(j=0;j<3;j++)。
第二处变化
到这一步为止,如果以为已经搞定了,运行时就会出现问题:
cpp
for (i = 0; i < 5; i++)
{
for (j = 0; j < 3; j++)
{
printf("%3d ", arr[i][j]);
}
printf("\n");
}
return 0;
现在这个代码在vs2022中运行会变成这样:
这是个什么情况呢?我们调试看看情况:
(这一部分如果没学过调试可以先跳过不看,影响不大)
右侧监视窗口i==1,j==0(结合左侧指向的位置、打印出的效果)可以看到我们现在是刚打印完arr[1][0],再看监视窗口的arr[1][0]是6,相当于打印完3我们就没能打印4、5,再结合前一张图,可以知道在每行打印完丢失两个数据后,最后我们又打印出了一些奇怪的数据。
我们看看vs是怎么提示的:
这是什么意思?我们arr[3][5]前一个括号内的取值 在0~2,而我们循环的i却是for(i=0;i<5;i++),这已经超出了有效的范围,所以打印不出来有效的值。同样的,因为arr[3][5]后一个括号内的取值在0~4,我们写的却是for(j=0;j<3;j++),所以在行数越界之前,每打印一行都会丢失两个数据,在下一行开始打印的时候跳过了两个数据。
其实,我们应该将代码改成这样:
cpp
for (i = 0; i < 5; i++)
{
for (j = 0; j < 3; j++)
{
printf("%3d ", arr[j][i]);//这里要写成arr[j][i]
}
printf("\n");
}
这段代码的运行效果:
我们分析一下为什么要写成arr[j][i],这一步较难理解。
本质上,我们已经知道i和j的范围决定了我们打印出来的行数、列数,而具体每一个位置上要打印什么数,则是由arr[][]括号里的下标决定的。
我们回到arr数组中看我们要打印第一行的1、6、11++是哪几个元素++ :分别为arr[0][0]、arr[1][0]、arr[2][0]。++我们可以发现对于同一行来说,变化 的是前一个下标,固定的是后一个下标。++
cpp
for (i = 0; i < 5; i++)
{
for (j = 0; j < 3; j++)
{
printf("%3d ", arr[j][i]);//内循环,j每次变化的时候,
//变化的是前一个下标,固定的是后一个下标。
}
printf("\n");
}
是不是正如注释中说的那样?
同时,另一个角度来看,for(j=0;j<3;j++),j最大取值为2,而我们的数组arr[3][5]的前一个下标最大取值就为2,对于i来说也一样,这说明了没有越界的情况发生,打印的都是数组内的有效数据。
怎么样?现在应该能理解为什么要写成arr[j][i]了吧。
小结
总之在矩阵转置的时候我们要从两点入手,第一点我们要改变输出的行数和列数,第二点我们要改变打印的值的写法。而在解决第二点的时候,抓住变与不变的,以及注意数组越界和丢失需要的数据。
希望大家发现文章错误的地方能向我反馈,共同进步!