之前介绍了以数组的形式对矩阵进行操作,但是数组有以下几个缺点:
- 需要输入矩阵的维数
- 矩阵大小是固定的,不能更改矩阵维数
本文将介绍更加灵活方便的矩阵操作
目录
用动态内存开辟矩阵
动态内存更多的知识在这篇博客,本文将介绍用malloc开辟矩阵。
malloc是C语言中用来动态开辟内存的,通过malloc函数可以向计算机申请一串连续的内存空间。
因为malloc开辟的内存在堆上,不会随函数的声明周期结束而销毁,因此当该内存不再使用时,需要用free进行内存释放。
malloc和free使用的基本方式:
- void* malloc (size_t size);
- void free (void* ptr);
- size是指定的开辟内存的大小,单位是字节
- size_t的无符号整型则限制程序员误操作开辟负字节的空间
- 如果开辟成功,malloc会返回一个void*类型的指针
- 如果开辟失败,则返回的是空指针,所以在malloc之后需要对指针进行检查
- 当malloc的东西不再使用时,需要free对其进行释放,否则会造成内存泄漏
- malloc和free均需要包含头文件<stdlib.h>
malloc开辟二维矩阵的示例:
以函数的形式表达:输入row行,col列,开辟矩阵并返回指针
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;
}
以上代码可以这样理解:
- 计算机申请了row个double*类型的内存,创建一个二级指针arr接收这串内存空间的地址。
- 计算机申请了col个double类型的内存,将其地址赋给arr中的第i个元素。
- 重复row次,row行col列的矩阵开辟完毕
开辟完矩阵后,还不能立即进行计算,还需要对矩阵进行初始化。
判断矩阵维数
sizeof
以数组创建矩阵时,可以利用sizeof判断内存的大小
例如:
double arr[3][4] = { 0 }; int col = (int)sizeof(*arr)/(int)sizeof(**arr); int row = (int)sizeof(arr)/col/ (int)sizeof(**arr);
上述代码中:
- sizeof(*arr)求出一行中有多少个字节,sizeof(**arr)求出第一行第一个元素所占字节大小,前者除以后者得出列数col
- sizeof(arr)求出整个二维数组的占多少个字节,除以列数得出一列元素的总字节数,再除以每个元素所占字节大小得出行数
- 因为sizeof的返回值是无符号整型,在做计算前最好将其转换成整型
_msize
因为sizeof(指针)并不会求出指针指向内存的大小,只会根据系统的不同返回4或者8。
接下来介绍_msize:
- size_t _msize( void * memblock);
- void*表示可以传入任意类型的指针
- _msize会返回在堆中分配的存储块的大小,返回值为无符号整型,单位为字节
- 需要包含头文件<malloc.h>
例如:
double** arr = Make_Matrix(3, 4);
int row = (int)_msize(arr) / (int)sizeof(double*);
int col = (int)_msize(*arr) / (int)sizeof(double);
上述代码中:
用了前文中Make_Matrix函数,创建了3行4列的矩阵
- _msize(arr):计算arr指向内存的大小;前文创建矩阵时arr中的每个元素为double*类型,除以double*得到矩阵的行数
- _msize(*arr):计算arr第一个元素存储地址所指向内存的大小;除以doiuble类型得到矩阵的行数
利用内存的大小除以每个元素的大小来判断元素个数是非常实用的方法!
有了_msize函数,便可以对以前的矩阵操作函数进行优化,对矩阵进行操作时,不用再输入矩阵维数。
矩阵初始化:
矩阵的初始化一般指的是将矩阵全部初始化为0,可以使用内存函数memset。memset只适合矩阵的清零操作,为矩阵每个元素赋值需要for循环遍历。
memset
memset这个函数通常为新申请的内存做初始化工作,作用是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法。
void * memset ( void * ptr, int value, size_t num );
- ptr为起始位置
- value为要赋的值,只能是字符,0或者-1
- num为赋值的个数,单位为字节
void Init_Matrix(double**arr)
{
int i;
int row= (int)_msize(arr) / (int)sizeof(double*);
for (i = 0; i < row; i++)
{
memset((arr[i]), 0, _msize(*arr));
}
}
上述代码中:
- 把每个arr[i]中存储指针指向的内存,全部设置成0
- _msize(*arr)表示一行有多少个字节
- 如果将arr传入memset,memset会把arr中的原本存储指针的元素置为0,造成内存泄漏
for循环
因为memset以字节为单位进行操作,所以memset只适合对矩阵进行清零操作,如果想将矩阵初始化为特定的数,用for循环遍历最佳。
这里对矩阵的操作与二维数组一致,通过访问指针的下标对其进行操作。
void Init_Matrix(double**arr,double n)
{
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] = n;
}
}
}
矩阵运算
矩阵运算在之前的博客中有过简单的介绍,现在我们利用上文所学的知识,对其进行优化。
大部分矩阵用double类型的足以应对,如果矩阵只进行整数运算,可以用typedef对double进行重命名。
typedef double Mattype;//为了之后更改方便
本章内容不讲矩阵的复数运算,复数的运算用Fortran更加方便(之后会考虑出一个Fortran的教学)。
矩阵加法
矩阵加法遵循:
对应位置相加减,两个相加减的矩阵维数必须一样,更多基础知识请看C语言矩阵加减法。
typedef double Mattype;//为了之后更改方便
Mattype** Matrix_Plus(Mattype** arr1, Mattype** arr2)
{
//判断矩阵维数
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 (row1 != row2||col1!=col2)
exit(-1);//判断左列是否等于右行
double** res = (Mattype**)malloc(sizeof(Mattype*) * row1);
if (res == NULL)
exit(-1);
int i, j;
for (i = 0; i < row1; i++)
{
res[i] = (Mattype*)malloc(sizeof(Mattype) * col1);//创建新矩阵
}
for (i = 0; i < row1; i++)
{
for (j = 0; j < col2; j++)
{
res[i][j] = 0.0;//开辟的新矩阵未初始化,计算前需要进行初始化
res[i][j] = arr1[i][j] + arr2[i][j];
}
}
return res;
}
上述代码中:
- 为了之后更改代码类型更方便,用typedef将double类型重定义为Mattype,之后若是想将代码更改成int,只需要修改此处即可。
- 加入了前文判断矩阵维数的部分,对传入的两个矩阵进行维数计算,然后判断是否符合相乘条件
- 新开辟的矩阵未进行初始化,必须在计算前将其初始化
测试:
为了方便更加直观的测试,再写一个print打印矩阵的函数:
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);
计算结果如下:
矩阵乘法
矩阵的原理请看C语言矩阵乘法,下面将介绍改进后的矩阵乘法:
Mattype** Matrix_Mul(Mattype** arr1, Mattype**arr2)
{
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;
}
上述代码中:
- 加入了前文判断矩阵维数的部分,对传入的两个矩阵进行维数计算,然后判断是否符合相乘条件
- 新开辟的矩阵未进行初始化,必须在计算前将其初始化
优化后的代码只需要传入两个参数,用二级指针变量接收Matrix_Mul的返回值即可,非常方便。
测试:
//测试乘法
double** b1 = Make_Matrix(3, 4);
double** b2 = Make_Matrix(4, 3);
Init_Matrix(b1,1);
Init_Matrix(b2,2);
double** b3=Matrix_Mul(b1, b2);
print(b3);
矩阵转置
矩阵转置的原理:行元素变成列元素,列元素变成行元素
例如:
//矩阵转置
double** Matrix_T(double** arr)
{
int row = (int)_msize(arr) / (int)sizeof(double*);
int col = (int)_msize(*arr) / (int)sizeof(double);
double** T = (double**)malloc(sizeof(double*) * col);
int i = 0;
int j = 0;
if (T != NULL)
{
for (i = 0; i < col; i++)
{
T[i] = (double*)malloc(sizeof(double) * row);
}
}
for (i = 0; i < col; i++)
{
for (j = 0; j < row; j++)
{
T[i][j] = arr[j][i];
}
}
return T;
}
矩阵转置比较简单,创建新矩阵T为转置后的矩阵,赋值时只需要将行列交换赋值即可。
矩阵求逆
矩阵求逆的具体原理和详细讲解请看矩阵求逆,优化了矩阵求逆时需要输入维数的部分。
//声明函数
Mattype Det(Mattype** arr, Mattype** arr2, int n);
Mattype Cof2(Mattype** arr, Mattype** arr2, int i, int n);
Mattype** Cof(Mattype** arr, Mattype** arr2, int i, int j, int n);
Mattype** Matrix_inver(Mattype** arr);
因为涉及到两个函数的相互递归,所以需要先声明函数。
Mattype Det(Mattype** arr, Mattype** arr2, int n)
{
Mattype sum = 0;
int i = 0;
if (n == 1)//1阶行列式直接得出结果
{
sum = arr[0][0];
}
else if (n == 2)
{
sum = arr[0][0] * arr[1][1] - arr[0][1] * arr[1][0];//杀戮法求解
}
else if (n == 3)
{
sum = arr[0][0] * arr[1][1] * arr[2][2]
+ arr[0][1] * arr[1][2] * arr[2][0]
+ arr[1][0] * arr[2][1] * arr[0][2]
- arr[0][2] * arr[1][1] * arr[2][0]
- arr[0][1] * arr[1][0] * arr[2][2]
- arr[1][2] * arr[2][1] * arr[0][0];//划线法求解
}
else
{
for (i = 0; i < n; i++)//按第一行展开
{
if (arr[0][i] != 0)//展开项不为0才计算
{
sum += ((Mattype)pow(-1, i + 2)) * arr[0][i] * (Cof2(arr, arr2, i, n));//2阶以上继续递归
}
else
sum += 0;//展开项为0
}
}
return sum;
}
//找到余子式
Mattype Cof2(Mattype** arr, Mattype** arr2, int i, int n)
{
int k = 0;
int j = 0;
//开辟一个数组用来存放代数余子式
for (k = 0; k < n - 1; k++)//去除0行i列,剩下的组成新的矩阵
{
for (j = 0; j < n - 1; j++)
{
if (j < i)
{
arr2[k][j] = arr[k + 1][j];
}
else
{
arr2[k][j] = arr[k + 1][j + 1];
}
}
}
return Det(arr2, arr, n - 1);//将找到的余子式传给Det
}
Mattype** Cof(Mattype** arr, Mattype** arr2, int i, int j, int n)
{
int m = 0;
int k = 0;
for (m = 0; m < n - 1; m++)
{
for (k = 0; k < n - 1; k++)
{
if (k < j)
{
if (m < i)
{
arr2[m][k] = arr[m][k];
}
else
{
arr2[m][k] = arr[m + 1][k];
}
}
else
{
if (m < i)
{
arr2[m][k] = arr[m][k + 1];
}
else
{
arr2[m][k] = arr[m + 1][k + 1];
}
}
}
}
return arr2;
}
Mattype** Matrix_inver(Mattype** arr)
{
int i, j;
//arr2,arr3为矩阵运算创建的临时参数
int n = (int)_msize(arr) / (int)sizeof(Mattype*);
double** arr2 = (Mattype**)malloc(sizeof(Mattype*) * n);
double** arr3 = (Mattype**)malloc(sizeof(Mattype*) * n);
double** res = (Mattype**)malloc(sizeof(Mattype*) * n);
if (arr2 == NULL || arr3 == NULL)
{
exit(-1);
}
for (i = 0; i < n; i++)
{
arr2[i] = (Mattype*)malloc(sizeof(Mattype) * n);
arr3[i] = (Mattype*)malloc(sizeof(Mattype) * n);
res[i] = (Mattype*)malloc(sizeof(Mattype) * n);
}
double a = 1.0 / (Det(arr, arr2, n));
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
Mattype b = pow(-1, i + j) * Det(Cof(arr, arr2, i, j, n), arr3, n - 1);
res[j][i] = a * b;
}
}
free(arr2);
free(arr3);
return res;
}
求逆部分没有优化太多,只把Matrix_inver函数中需要输入维数的部分给优化了,子函数的递归中,保留了将矩阵的维数n的传参(减少运算)。
测试:
//测试求逆
double** c1 = Make_Matrix(4, 4);
Init_Matrix(c1, 0);
int i, j;
//赋值
for (i = 0; i < 4; i++)
{
for (j = 0; j < 4; j++)
{
c1[i][j] = pow(i, j);
}
}
print(c1);
double** c2=Matrix_inver(c1);
print(c2);