【C语言】矩阵转置

下面的内容涉及二维数组的知识,建议学习过的朋友食用哦!

前言

矩阵转置是将原矩阵的行与列进行互换所得到的新矩阵。

具体来说,给定一个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]了吧。

小结

总之在矩阵转置的时候我们要从两点入手,第一点我们要改变输出的行数和列数,第二点我们要改变打印的值的写法。而在解决第二点的时候,抓住变与不变的,以及注意数组越界和丢失需要的数据。

希望大家发现文章错误的地方能向我反馈,共同进步!

相关推荐
冰蓝蓝1 分钟前
np.triu:NumPy中提取上三角矩阵的利器
线性代数·矩阵·numpy
王老师青少年编程3 分钟前
gesp(二级)(16)洛谷:B4037:[GESP202409 二级] 小杨的 N 字矩阵
数据结构·c++·算法·gesp·csp·信奥赛
robin_suli29 分钟前
动态规划子序列问题系列一>等差序列划分II
算法·动态规划
No0d1es34 分钟前
2024年12月青少年软件编程(C语言/C++)等级考试试卷(三级)
c语言·开发语言·青少年编程·电子学会·三级
cxylay1 小时前
自适应滤波算法分类及详细介绍
算法·分类·自适应滤波算法·自适应滤波·主动噪声控制·anc
茶猫_1 小时前
力扣面试题 - 40 迷路的机器人 C语言解法
c语言·数据结构·算法·leetcode·机器人·深度优先
轻浮j2 小时前
Sentinel底层原理以及使用算法
java·算法·sentinel
KevinRay_2 小时前
Numpy指南:解锁Python多维数组与矩阵运算(下)
python·矩阵·numpy·排序·文件读写
Abelard_2 小时前
LeetCode--347.前k个高频元素(使用优先队列解决)
java·算法·leetcode
小猪写代码2 小时前
C语言:递归函数(新增)
算法·c#