目录
前言
最近有一位友人的选题是关于矩阵的C++实现,因而我也得以对其中一些部分稍稍接触,其中对于矩阵的求逆的运算挺有意思的,它看上去简单明了,但实际上要想去实现它还是值得思考的。做完后,觉得还是挺有价值的,所以记录下我的过程,以供分享。
建立矩阵类
在这里,我认为对于矩阵的各项属性和方法,我认为用类描述更为方便,因为以后若不只是想求逆还想进行其他运算的话加,可以直接在类里添加,明了很多。
以下是具体实现的代码:
类的成员:
class Matrix
{
public:
int Count; //有关这个矩阵类的阶数的参数
double **data; //这个矩阵的具体内容
Matrix(int); //矩阵类的有参构造函数
void Set_data(); //声明一个函数,作用是给矩阵赋初值
void Show(); //声明一个函数,作用是打印这个矩阵内容
~Matrix(); //矩阵类的析构函数
Matrix Inverse_Matrix ();//声明一个函数,作用是求该矩阵的逆矩阵
};
这里因为是有要求逆的环节,所以我默认了这个矩阵类代表的是方阵,所以我直接用一个参数"Count"来表示这是几阶方阵,然后我用了一个二级指针data来表示这个矩阵内部的各个具体数值。
有参构造函数Matrix(int)和析构函数~Matrix():
因为我们在建立一个矩阵对象的时候,我们一定要对这个矩阵的阶数进行规定,所以我在这里设置了一个有参构造函数:
Matrix(int); //矩阵类的有参构造函数
传入的参数就可以用来规定阶数了。
下面是有参构造函数的内容:
Matrix::Matrix(int M)
{
int Count = M;
double **data = new double* [M];
//意思就是为data这个二重指针分配了内存,内存大小为M个指针,data指向这M个指针,即这个矩阵有M行
for (int i = 0; i < M; ++i) {
data[i] = new double [M];
//意思就是给这M个指针分配了内存,内存大小为M个double类型的数据,即这个矩阵有M列
}
}
其中分配内存的方式就可以理解为:我为data分配了M个指针的内存,同时这M个指针我又分配给他们M个double类型的内存。
这样,一个M行,M列的矩阵就建立了。
之后在析构函数中要注意要把内存释放:
Matrix::~Matrix(){
delete data;
}
打印矩阵void Show():
这个没太多好说的,就是将矩阵打印出来
void Matrix::Show() //这个函数的功能是打印这个矩阵
{
for (int i = 0; i < Count; ++i) {
for (int j = 0; j < Count; ++j) {
cout << data[i][j] << '\t';
}
cout << '\n';
}
}
矩阵赋初值void Set_data():
void Matrix::Set_data()
{
//这个要自己写了,就是把文件中的数据记录到矩阵中
}
这里是因为我的友人是利用文件输入的,所以需要他自己书写,如果就是手动输入的话,其实就是打印矩阵的逆向输入,很好实现。
实现矩阵求逆
重头戏来了,对于矩阵求逆的实现:
之前有想过利用伴随矩阵求逆,但是仔细思考发现:
虽然矩阵的行列式还是有办法求,但是伴随矩阵实在是太难了
这样的话,自然想到了利用初等变换:
矩阵的初等变换求逆:
这边援引百度知道上的概述:
我们假设给了一个A矩阵,则如何求A得逆du矩阵呢
我们知道如果PA=E1,则P矩阵是A的逆矩阵。
然而A矩阵的每一次行变换都相当于A矩阵左乘了一个初等矩阵P1,所以A的所有行变换可以看为多个初等矩阵左乘A矩阵,即P1P2P3…Pn=P,还有一个条件就是PE2=P,由此可以看出,当A和E2做相同的行变换,且A变成E1矩阵时,E2矩阵变为P矩阵,即A的逆矩阵,这里E矩阵标12是为了帮助理解区分,E1 E2都是单位矩阵。
接下来你只需要在A矩阵右边加一个单位矩阵,然后在对这个组合矩阵进行行变换,使A矩阵变为E矩阵,右边则得到了P矩阵,即A的逆矩阵。
那么具体实现呢:
Matrix Matrix::Inverse_Matrix()
{
Matrix result(Count); //建立一个结果矩阵,用来表示这个矩阵的逆矩阵
//将结果矩阵变为单位阵
for (int i = 0; i < Count; ++i) {
for (int j = 0; j < Count; ++j) {
if (i == j)
result.data[i][j] = 1;
else
result.data[i][j] = 0;
}
}
//第一次变换(矩阵化成三角阵)
for (int k = 0; k < Count-1; ++k) {
for (int i = k+1; i < Count; ++i) {
double change = data[i][k]/data[k][k];
for (int j = 0; j < Count; ++j) {
data[i][j] -= change*data[k][j];
result.data[i][j] -= change*data[k][j]; // 这边单位矩阵进行相同的变换
}
}
}
//第二次变换(三角阵化成对角阵)
for (int j = Count - 1; j >= 1 ; --j) {
for (int i = j-1; i >= 0 ; --i) {
double change = data[i][j] / data[j][j];
data[i][j] -= change*data[j][j];
result.data[i][j] -= change*data[j][j];
}
}
//第三次变换(对角阵化为单位阵)
for (int l = 0; l < Count; ++l) {
data[l][l] /= data[l][l];
result.data[l][l] /= result.data[l][l];
}
//返回得到的逆矩阵
return result;
}
我们新建立了一个新的矩阵对象result,作用是作为增广的单位阵,来参与矩阵的变换。在每一次原矩阵的变换同时,单位阵也进行的相同的变换。最终当原矩阵变为单位阵后,它也将转变为原矩阵的逆矩阵。
其中变换的代码实现方法值得我们品味,其中最为重要的就是化为三角阵的第一步,我们用到了三重循环。第一重循环的作用是确定以哪一行作为减法的底,之后的其他行就是来减这一行的。第二重循环就是确定被减的一行,在这里,我们可以确定相减的倍数,这里我用参数change来表示。最后一重循环,则是让被减行的每一个数字都执行相同的运算。最终循环下来,三角阵就形成了。这里的三重循环是矩阵求逆的最难点,把此处理解了,也就没什么很难得地方了。
一些待完善的地方
这里我还留了一些小的BUG,比方说应该还增加一个过程,判断矩阵的逆是否存在。其实这里也比较好完成,可以判断矩阵的行列式是否为零(其实一般不是直接说是否等于零,而是判断是否小于eps(可以表示的最小数))
尾声
个人认为算是比较好理解了?如果有任何不足的话欢迎评论一起探讨。如果认为有帮助的话也请点个赞吧。蟹蟹!