The C Programming Language 程序研究 [持续更新]
第一章 矩阵
一、矩阵的转置
问题描述:
编写函数,把给定的任意一个二维整型矩阵转换为其转置矩阵。
输入:
1 2 3
4 5 6
输出:
1 4
2 5
3 6
分析
题目要求编写一个能转置任意二维矩阵的函数,显然这个函数需要得到一个二维数组的参数,还应该有一个作为输出的二维数组,我们可以将这个数组也传递给函数,然后函数交换行列的下标赋值给输出数组(即b[j][i] = a[i][j])即可。这样看似问题可以得到解决了,但是,二维数组作为参数时必须指定列数,也就是第二维的大小,比如:void transpose ( int a[][3], int b[][2]),那么这个函数就只能将=转置2行3列的矩阵,无法达到题目中转置任意矩阵的要求。
如何让函数具有通用性呢?我们考虑到数组在内存中的存放方式是按行线性连续排列的,比如:
数组
a[2][3] = {
{ 1,2, 3 },
{ 4,5, 6 }
};
b[3][2] = {
{ 1,2 },
{ 3,4 },
{ 5,6 }
};
看起来这两个数组的布局不一样但是它们在内存中的排列方式是相同的:
a:
a[0][0]
a[0][1]
a[0][2]
a[1][0]
a[1][1]
a[1][2]
1
2
3
4
5
6
b:
b[0][0]
b[0][1]
b[1][0]
b[1][1]
b[2][0]
b[2][1]
1
2
3
4
5
6
这样看来,一维数组在某些时候可以代替二维数组,并且最重要的是,一维数组作为参数传递给函数时,不用指定长度,这个正好符合题意。我们能不能使用一维数组完成题目的要求呢?
首先看一下在二维数组中,转置是如何发生的:
1 2 3
4 5 6
转置
→
1 4
2 5
3 6
很明显,在二维视角下,矩阵的转置就是交换行标和列标,即:b[j][i] =a[i][j],而2X3的二维数组中的元素a[i][j],在一维视角下就变为a[i*3+j],转置后的3X2二维数组中对应的元素b[j][i]变为b[j*2+i]。如此a[i*3+j]= b[j*2+i]就可以实现转置了。
至此,我们已经找到了使用一维数组实现二维数组转置的方法,现在看看如何将一个二维数组变成一维数组参数。由于一维数组在作为函数参数时就是一个指向int类型的指针,而二维数组名a是一个指向以为数组的指针,因此*a就是一个一维数组,使用它作为函数参数时也是一个指向int类型的指针。
下面是完整的程序:
/* * 转置矩阵的通用函数 */ void transpose ( int *m1, int *m2, int x, int y ); //转置函数 static int m2[3][2]; //自动初始化 int main ( void ) { int m1[2][3] = { {1, 2, 3}, {4, 5, 6}, }; int i; int j; transpose ( *m1, *m2, 2, 3 ); //使用参数*m1、*m2 for ( i=0; i<3; i++ ) { for ( j=0; j<2; j++ ) printf ( "%3d", m2[i][j] ); printf ( "\n" ); } return 0; } void transpose ( int *m1, int *m2, int x, int y ) { int i; int j; for ( i=0; i<x; i++ ) for ( j=0; j<y; j++ ) m2[j*x+i] = m1[i*y+j]; }
小结:利用二维数组的内存分布特点,可以用一维数组来处理二维数组的问题,并且这种方法还具有通用性。
二、矩阵的乘法
问题描述:编写一个实现两个任意矩阵乘法的函数(两个矩阵中一个的行数和另一个的列数相同)
输入:
m1:
2 -6
3 5
1 -1
m2:
4 -2 -4 -5
-7 -3 6 7
输出:
r:
50 14 -44 -52
-23 -21 18 20
11 1 -10 -12
分析:
我们首先考虑矩阵乘法的法则:
就以上这个例子而言,显然有:
r[0][0] = m1[0][0] * m2[0][0] + m1[0][1] * m2[1][0];
r[0][1] = m1[0][0] * m2[0][1] + m1[0][1] * m2[1][1];
......
观察发现:以上等式中,所有m1的行标都与r的行标一致,所有m2的列标都与r的列标一致,而m1中和m2中剩下的没有确定的下标都是在遍历,更巧的是,这两个没有确定的下标的遍历范围是一致的。
我们假设m1的大小为x*y,m2的为y*z,m3自然就应该是x*z,分别用i、j、k遍历x、y、z。那么,对于一个给定了具体下标值的元素r[i][k](假定r中所有元素的初始值为0),在计算此元素的表达式中,所有m1的行标都已经确定为i,所有m2的列标也确定为k,此时只需要使用j来遍历y,并将j作为m1和m2中那个还没有确定的下标值,可以通过遍历i和k来实现遍历所有r的元素,伪代码如下:
i: 0 → x
k: 0 → z
j: 0 → y
r[i][k] += m1[i][j] * m2[j][k];
最后,我们仍然要使用一维数组来解决通用性的问题:
m1[i][j] = m1[i*y+j];
m2[j][k] = m2[j*z+k];
r[i][k] = r[i*z+k];
最终的函数和测试程序如下:
/* * 矩阵乘法 */ #include <stdio.h> void multiply ( int *m1, int *m2, int *r, int x, int y, int z ); static int r[3][4]; int main ( void ) { int m1[3][2] = { {2, -6}, {3, 5}, {1, -1} }; int m2[2][4] = { {4, -2, -4, -5}, {-7, -3, 6, 7} }; int i,j; multiply ( *m1, *m2, *r, 3, 2, 4 ); for ( i=0; i<3; i++ ) { for ( j=0; j<4; j++ ) printf ( "%5d", r[i][j] ); printf ( "\n" ); } return 0; } void multiply ( int *m1, int *m2, int *r, int x, int y, int z ) { int i, j, k; for ( i=0; i<x; i++ ) for ( j=0; j<y; j++ ) for ( k=0; k<z; k++ ) r[i*z+k] += m1[i*y+j] * m2[j*z+k]; }
/*
* 测试结果:
*
* 50 14 -44 -52
* -23 -21 18 20
* 11 1 -10 -12
*
*/
小结:注意分析乘法法则,得到下标值之间的关系。
补充:软件开发 , C语言 ,