C语言矩阵乘法
本篇内容
1)首先介绍了矩阵乘法的基本原理;
2)然后介绍了相对初阶的C语言乘法代码设计;
3)最后根据C语言动态内存规划,提出了更加便捷、优化的代码设计,希望能给大家带来帮助。
更新:
没有想到一篇简单的矩阵乘法博客居然有这么多的阅读量,RTKLIB(一个开源导航算法程序)中matmul中乘法函数有着更加巧妙的思路,我在RTKLIB—matmul一文中进行了详细介绍。
目录
1.原理
左乘和右乘
在线性代数中,矩阵左乘和矩阵右乘是不一样的。
例如:现有矩阵A: ,矩阵B:
。
AB为矩阵B左乘矩阵A:
BA为矩阵B右乘矩阵A:
通过计算发现:左乘和右乘的结果是完全不同的,所以在进行矩阵乘法时,需分清左乘和右乘。
乘法原理
矩阵乘法需要满足的条件:左侧矩阵的列数=右侧矩阵的行数。
结果矩阵的行和列:行数=左侧矩阵的行数,列数=右侧矩阵的列数。
矩阵的相乘过程:
以AB为例:
- A矩阵的第一行中的元素和B矩阵第一列中的元素对应相乘再相加,结果放在第一行第一列中;
- A矩阵的第一行中的元素和B矩阵第二列中的元素对应相乘再相加,结果放在第一行第二列中;
- A矩阵的第一行中的元素和B矩阵第三列中的元素对应相乘再相加,结果放在第一行第三列中;
- ......
- A矩阵的第三行中的元素和B矩阵第三列中的元素对应相乘再相加,结果放在第三行第三列中;
最后的结果为:
2.C语言编写矩阵乘法函数
编写函数(传统形式)
矩阵虽然有左乘和右乘之分,但是只要更改传入参数的位置,即可实现左乘和右乘。
核心思想:
- 利用二维数组将矩阵中的数存放起来,用define定义行和列的数值,方便代码的更改。
- 定义一个矩阵左乘函数Matrix_left_mul,将矩阵以及矩阵的行列长度传给函数。
- 利用3个for循环遍历完成计算。
传参的时候可以用数组的形式传,也可以用指针的形式传,二者本质都一样。两种方法都会进行介绍。
1、我们创建两个数组arr1和arr2来存放两个矩阵
(在平时创建二维数组时,不能省略列,可以省略行;但是在矩阵乘法中,行和列的信息都要有)
//左矩阵的行和列
#define COL1 4
#define ROW1 3
//右矩阵的行和列
#define ROW2 4
#define COL2 3
double arr1[ROW1][COL1] = { 1,2,3,4,5,6,7,8,9,10,11,12};
double arr2[ROW2][COL2] = { 12,11,10,9,8,7,6,5,4,3,2,1};
2、传参以及判断矩阵相称的条件是否成立
利用assert断言条件是否成立,如果不成立系统会提示报错,assert需要包含头文件<assert.h>。
因为数组传参传的是地址,所以函数的类型设置为void即可。
如果不进行判断,传入错误的参数后,二维数组的访问会溢出,返回一个随机数。
数组形式:
void Matrix_left_mul(double arr1[][COL1], double arr2[][COL2], double arr3[][COL2],int row1,int row2,int col1,int col2)
{
assert(col1 == row2);//判定左列是否等于右行,需包含头文件
}
指针形式:
void Matrix_left_mul(double(*arr1)[COL1], double (*arr2)[COL2], double(*arr3)[COL2], int row1, int row2, int col1, int col2)
{
assert(col1 == row2);//判定左列是否等于右行,assert需包含头文件
}
3、for循环遍历计算
创建三个for循环:
- 第一个for循环(i):循环左矩阵的i行
- 第二个for循环(j):循环右矩阵的j列
- 第三个for循环(k):逐个循环左行k元素×右列k元素,将其结果累加
数组形式:
int i = 0;//行
int j = 0;//列
int k = 0;//行列中,第k个元素相乘
for (i = 0; i < row1; i++)//从第i行开始
{
for (j = 0; j < col2; j++)//从第j列开始
{
for (k = 0; k < col1; k++)//i行元素和j列元素相乘,结果累加
{
arr3[i][j] += arr1[i][k] * arr2[k][j];
}
}
}
指针形式:
void Matrix_left_mul(double(*arr1)[COL1], double (*arr2)[COL2], double(*arr3)[COL2], int row1, int row2, int col1, int col2)
{
assert(col1 == row2);//判定左列是否等于右行,assert需包含头文件
int i = 0;//行
int j = 0;//列
int k = 0;//行列中,第k个元素相乘
for (i = 0; i < row1; i++)//从第i行开始
{
for (j = 0; j < col2; j++)//从第j列开始
{
for (k = 0; k < col1; k++)//i行元素和j列元素相乘,结果累加
{
*(* (arr3 + i)+j) += *(*(arr1 + i) + k) * *(*(arr2 +k) + j);
}
}
}
}
用define定义常量的优点就是灵活,方便使用,后续只需要更改输入矩阵的相关参数即可进行运算。
测试
在VS中,F10进入调试模式,监视arr1,arr2,arr3数组。这里为了监视方便,我们暂时将数组改成整型,数组中的参数都是整数。
结果完全正确,如果不放心还可以多试验几组数据。
完整代码
数组形式:
#include<stdio.h>
#include<assert.h>
//左矩阵的行和列
#define COL1 4
#define ROW1 3
//右矩阵的行和列
#define ROW2 4
#define COL2 3
void Matrix_left_mul(double arr1[][COL1], double arr2[][COL2], double arr3[][COL2],int row1,int row2,int col1,int col2)
{
assert(col1 == row2);//判定左列是否等于右行,assert需包含头文件
int i = 0;//行
int j = 0;//列
int k = 0;//行列中,第k个元素相乘
for (i = 0; i < row1; i++)//从第i行开始
{
for (j = 0; j < col2; j++)//从第j列开始
{
for (k = 0; k < col1; k++)//i行元素和j列元素相乘,结果累加
{
arr3[i][j] += arr1[i][k] * arr2[k][j];
}
}
}
}
int main()
{
double arr1[ROW1][COL1] = { 1,2,3,4,5,6,7,8,9,10,11,12};
double arr2[ROW2][COL2] = { 12,11,10,9,8,7,6,5,4,3,2,1 };
double arr3[ROW1][COL2]={0};//结果矩阵的行等于左矩阵的行,列等于右矩阵的列
Matrix_left_mul(arr1, arr2, arr3,ROW1,ROW2,COL1,COL2);
return 0;
}
指针形式
#include<stdio.h>
#include<assert.h>
//左矩阵的行和列
#define COL1 4
#define ROW1 3
//右矩阵的行和列
#define ROW2 4
#define COL2 3
void Matrix_left_mul(double(*arr1)[COL1], double (*arr2)[COL2], double(*arr3)[COL2], int row1, int row2, int col1, int col2)
{
assert(col1 == row2);//判定左列是否等于右行,assert需包含头文件
int i = 0;//行
int j = 0;//列
int k = 0;//行列中,第k个元素相乘
for (i = 0; i < row1; i++)//从第i行开始
{
for (j = 0; j < col2; j++)//从第j列开始
{
for (k = 0; k < col1; k++)//i行元素和j列元素相乘,结果累加
{
*(* (arr3 + i)+j) += *(*(arr1 + i) + k) * *(*(arr2 +k) + j);
}
}
}
}
int main()
{
double arr1[ROW1][COL1] = { 1,2,3,4,5,6,7,8,9,10,11,12 };
double arr2[ROW2][COL2] = { 12,11,10,9,8,7,6,5,4,3,2,1 };
double arr3[ROW1][COL2] = { 0 };//结果矩阵的行等于左矩阵的行,列等于右矩阵的列
Matrix_left_mul(arr1, arr2, arr3, ROW1, ROW2, COL1, COL2);
return 0;
}
3.优化方法
从上文中的代码不难看出,以二维数组进行矩阵运算时非常麻烦,需要输入矩阵的维数,不够智能,接下来将介绍更加方便的矩阵运算操作。
用malloc开辟矩阵
double** Make_Matrix(int row,int col)
{
int i, j;
double** arr = (double**)malloc(sizeof(double*) * row);
if (arr != NULL)
{
for (i = 0; i < row; i++)
{
arr[i] = (double*)malloc(sizeof(double) * col);
}
}
return arr;
}
这方面的知识在C语言——malloc开辟矩阵 中进行了详细讲解,这里不再赘述。
优化后的矩阵乘法代码(仅支持Windows)
需要注意:_msize函数返回指针指向的内存,仅支持winodws,其他系统不适用,可用其他方法代替,稍后会给出另一种版本的。
优化后的矩阵乘法需要具备以下功能:
1)自动判断矩阵的维数
2)自动判断矩阵是否满足乘法条件
3)自动为答案矩阵开辟空间
代码如下
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
typedef double Mattype;//为了之后更改方便
Mattype** Matrix_Mul(Mattype** arr1, Mattype**arr2)
{
if(arr1==NULL||arr2==NULL)exit(-1);
int row1 = (int)_msize(arr1) / (int)sizeof(Mattype*);
int col1 = (int)_msize(*arr1) / (int)sizeof(Mattype);
int row2 = (int)_msize(arr2) / (int)sizeof(Mattype*);
int col2 = (int)_msize(*arr2) / (int)sizeof(Mattype);
if (col1 != row2)
exit(-1);//判断左列是否等于右行
double**res=(Mattype**)malloc(sizeof(Mattype*)*row1);
if (res == NULL)
exit(-1);
int i,j,k;
for (i = 0; i < row1; i++)
{
res[i] = (Mattype*)malloc(sizeof(Mattype) * col2);//创建新矩阵
}
for (i = 0; i < row1; i++)
{
for (j = 0; j < col2; j++)
{
res[i][j] = 0.0;//开辟的新矩阵未初始化,计算前需要进行初始化
for (k = 0; k < col1; k++)
{
res[i][j] += arr1[i][k] * arr2[k][j];//该部分的计算与前文一致
}
}
}
return res;
}
上述代码中:
-
先判断传入参数是否为空,然后判断矩阵的维数,这部分在C语言判断矩阵维数中有详细讲解
- 为了之后更改代码类型更方便,用typedef将double类型重定义为Mattype,之后若是想将代码更改成int,只需要修改此处即可。
- 新开辟的矩阵未进行初始化,必须在计算前将其初始化
测试:
为了更加直观的测试,再写一个Init_Matrix矩阵初始化函数和print打印矩阵的函数:
初始化矩阵
void Init_Matrix(double** arr,double k)
{
int i, j;
int row = (int)_msize(arr) / (int)sizeof(double*);
int col = (int)_msize(*arr) / (int)sizeof(double);
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
arr[i][j] = k;
}
}
}
将矩阵全部元素初始化为k
打印
//打印矩阵
void print(double** arr)
{
putchar('\n');
int i, j, row, col;
row = (int)_msize(arr) / (int)sizeof(double*);
col = (int)_msize(*arr) / (int)sizeof(double);
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
printf("%-10lf ", arr[i][j]);
}
putchar('\n');
}
}
与前文一样,函数内部判断矩阵的维数,‘-’表示左对齐,10表示打印10个有效数字。
主函数测试:
//测试加法
double** a1 = Make_Matrix(3, 3);
double** a2 = Make_Matrix(3, 3);
Init_Matrix(a1, 1);
Init_Matrix(a2, 2);
double** a3 = Matrix_Plus(a1, a2);
print(a3);
计算结果如下:
优化后的矩阵乘法代码(所有平台通用)
即然无法使用_msize函数,又不想每次计算时重复输入矩阵的维数,不妨在创建矩阵时就将其记录下来,保存在一个结构体中。
typedef struct Matrix
{
int row;
int col;
double** data;
}Matrix,*pMatrix;
如上,data里是我们要存放的数据,依旧使用malloc开辟矩阵,只不过在开辟矩阵前,需要将Matrix中的row和col给定,我们可以将创建矩阵部分封装成一个函数,修改后的内容如下:
Matrix MakeMatrix(int row,int col)
{
int i=0;
Matrix arr={0};
arr.row=row;
arr.col=col;
arr.data=(double**)malloc(sizeof(double*)*arr.row);
if(arr.data==NULL)exit(-1);
for(i=0;i<arr.row;i++)
{
arr.data[i]=(double*)malloc(sizeof(double)*arr.col);
memset(arr.data[i],0,sizeof(double)*arr.col);
}
return arr;
}
这样,避免了繁琐的开辟矩阵的过程,也能避免在开辟矩阵时,忘记存入row和col的值,之后将上述乘法矩阵略作修改即可,这样写的好处是代码可以跨平台使用。
Matrix Matrix_Mul(const Matrix left, const Matrix right)
{
if (left.col != right.row)exit(-1);//判断左列是否等于右行
Matrix res={0};
res.data=(double**)malloc(sizeof(double*)*left.row);
if (res.data == NULL)exit(-1);
int i,j,k;
for (i = 0; i < left.row; i++)
{
res.data[i] = (double*)malloc(sizeof(double) * right.col);//创建新矩阵
memset(res.data[i],0,sizeof(double) * right.col);//初始化
}
for (i = 0; i < left.row; i++)
{
for (j = 0; j < right.col; j++)
{
for (k = 0; k < left.col; k++)
{
res.data[i][j] += left.data[i][k] * right.data[k][j];//该部分的计算与前文一致
}
}
}
return res;
}
更多推荐
所有评论(0)