线性方程组/矩阵方程求解(方法汇总)

前言

Eigen 是一个基于C++模板的线性代数库,提供了很多求解矩阵方程的方式,如比如LU分解、QR分解、SVD分解等。

本文以实用性为主,重点讨论几种求解矩阵方程的方法,以及不同方法的优缺点。对于每种方法的原理不做过多介绍。

下面给出不同分解方式的对比图,主要是计算速度和精度的对比,同时不同的分解方式对系数矩阵的形式也有不同的要求。

在这里插入图片描述

1 线性方程组(矩阵方程)

线性方程组是各个方程关于未知量均为一次的方程组,可以采用矩阵方程的形式表示。比如已知一个线性方程组
{ a 1 x 1 + b 1 x 2 + c 1 x 3 = d 1 a 2 x 1 + b 2 x 2 + c 2 x 3 = d 2 a 3 x 1 + b 3 x 2 + c 3 x 3 = d 3 \begin{cases} a_1x_1+b_1x_2+c_1x_3=d_1\\ a_2x_1+b_2x_2+c_2x_3=d_2\\ a_3x_1+b_3x_2+c_3x_3=d_3\\ \end{cases} a1x1+b1x2+c1x3=d1a2x1+b2x2+c2x3=d2a3x1+b3x2+c3x3=d3
写成矩阵形式
A x = b Ax=b Ax=b
其中,

A = [ a 1 b 1 c 1 a 2 b 2 c 2 a 3 b 3 c 3 ] A=\begin{bmatrix}a_1&b_1&c_1 \\a_2&b_2&c_2\\a_3&b_3&c_3\\\end{bmatrix} A=a1a2a3b1b2b3c1c2c3 x = [ x 1 x 2 x 3 ] x=\begin{bmatrix}x_1\\x_2\\x_3\\\end{bmatrix} x=x1x2x3 d = [ d 1 d 2 d 3 ] d=\begin{bmatrix}d_1\\d_2\\d_3\\\end{bmatrix} d=d1d2d3

以线性方程组
{ x 1 + 2 x 2 + 3 x 3 = 3 4 x 1 + 5 x 2 + 6 x 3 = 3 7 x 1 + 8 x 2 + 10 x 3 = 4 \begin{cases} x_1+2x_2+3x_3=3\\ 4x_1+5x_2+6x_3=3\\ 7x_1+8x_2+10x_3=4\\ \end{cases} x1+2x2+3x3=34x1+5x2+6x3=37x1+8x2+10x3=4
为例,进行求解。写成矩阵形式为
[ 1 2 3 4 5 6 7 8 10 ] [ x 1 x 2 x 3 ] = [ 3 3 4 ] \begin{bmatrix}1&2&3 \\4&5&6\\7&8&10\\\end{bmatrix}\begin{bmatrix}x_1\\x_2\\x_3\\\end{bmatrix}=\begin{bmatrix}3\\3\\4\\\end{bmatrix} 1472583610x1x2x3=334

可以选择不同的分解方式,这取决于矩阵A的形式,也取决于求解的速度和精度。

2 QR分解

QR分解类中的solution()方法也可以用来求解矩阵方程,有三个QR分解类:

  • HouseholderQR:无旋转(no pivoting),速度很快但不稳定
  • ColPivHouseholderQR:列旋转(column pivoting),速度稍慢但更精确
  • FullPivHouseholderQR:全旋转(full pivoting),速度慢,最稳定

2.1 ColPivHouseholderQR分解

ColPivHouseholderQR分解是一个很好的折衷方案,因为它适用于所有矩阵,同时速度非常快。

代码:

#include <iostream>
#include <Eigen/Dense>
 
using namespace std;
using namespace Eigen;
 
int main()
{
	Matrix3d A;	//系数矩阵A
	Vector3d b;	//常数矩阵b
	Vector3d x;	//未知数矩阵x
	A << 1,2,3,  4,5,6,  7,8,10;
	b << 3, 3, 4;
	cout << "系数矩阵A:\n" << A << endl;
	cout << "常数矩阵b:\n" << b << endl;
	x = A.colPivHouseholderQr().solve(b);
	cout << "未知数矩阵x:\n" << x << endl;

	return 0;
}

输出结果:

系数矩阵A:
 1  2  3
 4  5  6
 7  8 10
常数矩阵b:
3
3
4
未知数矩阵x:
-2
 1
 1

2.2 HouseholderQR分解

相比于ColPivHouseholderQR分解,HouseholderQR分解计算速度更快,但精度不如ColPivHouseholderQR分解。

代码

#include <iostream>
#include <Eigen/Dense>
 
using namespace std;
using namespace Eigen;
 
int main()
{
	Matrix3d A;	//系数矩阵A
	Vector3d b;	//常数矩阵b
	Vector3d x;	//未知数矩阵x
	A << 1,2,3,  4,5,6,  7,8,10;
	b << 3, 3, 4;
	cout << "系数矩阵A:\n" << A << endl;
	cout << "常数矩阵b:\n" << b << endl;
	x = A.householderQr().solve(b);
	cout << "未知数矩阵x:\n" << x << endl;

	return 0;
}

输出结果:

系数矩阵A:
 1  2  3
 4  5  6
 7  8 10
常数矩阵b:
3
3
4
未知数矩阵x:
-2
 1
 1

2.3 FullPivHouseholderQR分解

FullPivHouseholderQR分解相比于前两种QR分解,速度更慢,但精度更高。

代码:

#include <iostream>
#include <Eigen/Dense>
 
using namespace std;
using namespace Eigen;
 
int main()
{
	Matrix3d A;	//系数矩阵A
	Vector3d b;	//常数矩阵b
	Vector3d x;	//未知数矩阵x
	A << 1,2,3,  4,5,6,  7,8,10;
	b << 3, 3, 4;
	cout << "系数矩阵A:\n" << A << endl;
	cout << "常数矩阵b:\n" << b << endl;
	x = A.fullPivHouseholderQr().solve(b);
	cout << "未知数矩阵x:\n" << x << endl;

	return 0;
}

输出结果:

系数矩阵A:
 1  2  3
 4  5  6
 7  8 10
常数矩阵b:
3
3
4
未知数矩阵x:
-2
 1
 1

3 LLT分解

LLT分解要求系数矩阵A是正定矩阵(Positive definite matrix),是所有分解方式中速度最快的一种。用于非正定矩阵的分解时,难以得到正确的结果。

代码:

#include <iostream>
#include <Eigen/Dense>
 
using namespace std;
using namespace Eigen;
 
int main()
{
	Matrix3d A;	//系数矩阵A
	Vector3d b;	//常数矩阵b
	Vector3d x;	//未知数矩阵x
	A << 1,2,3,  4,5,6,  7,8,10;
	b << 3, 3, 4;
	cout << "系数矩阵A:\n" << A << endl;
	cout << "常数矩阵b:\n" << b << endl;
	x = A.llt().solve(b);
	cout << "未知数矩阵x:\n" << x << endl;

	return 0;
}

输出结果:

系数矩阵A:
 1  2  3
 4  5  6
 7  8 10
常数矩阵b:
3
3
4
未知数矩阵x:
 4.4556
-0.3184
 -0.026

3 LDLT分解

LDLT分解要求系数矩阵A是正定矩阵(positive definite matrix)或者半负定矩阵(negative semi-definite matrix)。用于其他矩阵的分解时,难以得到正确的结果。

代码:

#include <iostream>
#include <Eigen/Dense>
 
using namespace std;
using namespace Eigen;
 
int main()
{
	Matrix3d A;	//系数矩阵A
	Vector3d b;	//常数矩阵b
	Vector3d x;	//未知数矩阵x
	A << 1,2,3, 4,5,6,  7,8,10;
	b << 3, 3, 4;
	cout << "系数矩阵A:\n" << A << endl;
	cout << "常数矩阵b:\n" << b << endl;
	x = A.ldlt().solve(b);
	cout << "未知数矩阵x:\n" << x << endl;

	return 0;
}

输出结果:

系数矩阵A:
 1  2  3
 4  5  6
 7  8 10
常数矩阵b:
3
3
4
未知数矩阵x:
-0.206897
  0.37931
 0.241379

4 LU分解

4.1 LU分解

代码:

#include <iostream>
#include <Eigen/Dense>
 
using namespace std;
using namespace Eigen;
 
int main()
{
	Matrix3d A;	//系数矩阵A
	Vector3d b;	//常数矩阵b
	Vector3d x;	//未知数矩阵x
	A << 1,2,3,  4,5,6,  7,8,10;
	b << 3, 3, 4;
	cout << "系数矩阵A:\n" << A << endl;
	cout << "常数矩阵b:\n" << b << endl;
	x = A.lu().solve(b);
	cout << "未知数矩阵x:\n" << x << endl;

	return 0;
}

输出结果:

系数矩阵A:
 1  2  3
 4  5  6
 7  8 10
常数矩阵b:
3
3
4
未知数矩阵x:
-2
 1
 1

4.2 PartialPivLu分解

代码:

#include <iostream>
#include <Eigen/Dense>
 
using namespace std;
using namespace Eigen;
 
int main()
{
	Matrix3d A;	//系数矩阵A
	Vector3d b;	//常数矩阵b
	Vector3d x;	//未知数矩阵x
	A << 1,2,3,  4,5,6,  7,8,10;
	b << 3, 3, 4;
	cout << "系数矩阵A:\n" << A << endl;
	cout << "常数矩阵b:\n" << b << endl;
	x = A.partialPivLu().solve(b);
	cout << "未知数矩阵x:\n" << x << endl;

	return 0;
}

输出结果:

系数矩阵A:
 1  2  3
 4  5  6
 7  8 10
常数矩阵b:
3
3
4
未知数矩阵x:
-2
 1
 1

4.3 FullPivLu分解

代码:

#include <iostream>
#include <Eigen/Dense>
 
using namespace std;
using namespace Eigen;
 
int main()
{
	Matrix3d A;	//系数矩阵A
	Vector3d b;	//常数矩阵b
	Vector3d x;	//未知数矩阵x
	A << 1,2,3,  4,5,6,  7,8,10;
	b << 3, 3, 4;
	cout << "系数矩阵A:\n" << A << endl;
	cout << "常数矩阵b:\n" << b << endl;
	x = A.fullPivLu().solve(b);
	cout << "未知数矩阵x:\n" << x << endl;

	return 0;
}

生成失败:

error C1128: 节数超过对象文件格式限制: 请使用 /bigobj 进行编译

5 SVD分解

当方程个数大于未知数个数时,需要进行最小二乘求解,有效的办法是进行奇异值分解。Eigen提供了两种实现方式,推荐使用 BDCSVD 类,它可以很好地扩展大型矩阵,而对于较小的矩阵,它会自动退回到JacobiSVD类。

注意:采用SVD分解时,要用 动态矩阵 定义系数矩阵A、常数矩阵b、未知数矩阵x

5.1 BDCSVD分解

代码:

#include <iostream>
#include <Eigen/Dense>
 
using namespace std;
using namespace Eigen;
 
int main()
{
	MatrixXd A(3,2);	//系数矩阵A,3×2阶
	VectorXd b(3,1);	//常数矩阵b,3×1阶
	VectorXd x(3,1);	//未知数矩阵x,3×1阶
	A << 1, 2,  4, 5,  7, 8;
	b << 3, 3, 4;
	cout << "系数矩阵A:\n" << A << endl;
	cout << "常数矩阵b:\n" << b << endl;
	x = A.bdcSvd(ComputeThinU | ComputeThinV).solve(b);
	cout << "未知数矩阵x:\n" << x << endl;

	return 0;
}

输出结果:

系数矩阵A:
1 2
4 5
7 8
常数矩阵b:
3
3
4
未知数矩阵x:
   -2.5
2.66667

5.2 JacobiSVD分解

代码:

#include <iostream>
#include <Eigen/Dense>
 
using namespace std;
using namespace Eigen;
 
int main()
{
	MatrixXd A(3,2);	//系数矩阵A,3×2阶
	VectorXd b(3,1);	//常数矩阵b,3×1阶
	VectorXd x(3,1);	//未知数矩阵x,3×1阶
	A << 1, 2,  4, 5,  7, 8;
	b << 3, 3, 4;
	cout << "系数矩阵A:\n" << A << endl;
	cout << "常数矩阵b:\n" << b << endl;
	x = A.jacobiSvd(ComputeThinU | ComputeThinV).solve(b);
	cout << "未知数矩阵x:\n" << x << endl;

	return 0;
}

输出结果:

系数矩阵A:
1 2
4 5
7 8
常数矩阵b:
3
3
4
未知数矩阵x:
   -2.5
2.66667

6 公式法( x = A − 1 b x=A^{-1}b x=A1b

虽然逆(inverse)和行列式(determinant)是基本的数学概念,但在线性代数中,它们不像在纯数学中那么流行。逆运算通常被 solve 运算所代替,行列式通常不是检查矩阵是否可逆的好方法。

但是,对于非常小的矩阵,逆矩阵和行列式是非常有用的。

虽然某些分解(如PartialPivLU和FullPivLU)提供了inverse()和determinate()方法,但也可以直接对矩阵调用inverse()和determinate()。如果矩阵是一个非常小的(最多4x4)静态矩阵,Eigen可使用公式 x = A − 1 b x=A^{-1}b x=A1b比矩阵分解的方式更有效

代码:

#include <iostream>
#include <Eigen/Dense>
 
using namespace std;
using namespace Eigen;
 
int main()
{
	Matrix3d A;	//系数矩阵A
	Vector3d b;	//常数矩阵b
	Vector3d x;	//未知数矩阵x
	A << 1,2,3,  4,5,6,  7,8,10;
	b << 3, 3, 4;
	cout << "系数矩阵A:\n" << A << endl;
	cout << "常数矩阵b:\n" << b << endl;
	if (A.determinant() != 0)
	{
		//矩阵行列式大于0,
		x = A.inverse()*b;
		cout << "未知数矩阵x:\n" << x << endl;
	}
	else
	{
		cout << "矩阵行列式小于0,矩阵方程无解!\a\n" << endl;
	}
	return 0;
}

输出结果:

系数矩阵A:
 1  2  3
 4  5  6
 7  8 10
常数矩阵b:
3
3
4
未知数矩阵x:
-2
 1
 1
  • 14
    点赞
  • 94
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

孙 悟 空

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值