线性代数代码实现(七)求解线性方程组(C++)

news/2024/10/29 7:14:48/

前言:

        上次博客,我写了一篇关于定义矩阵除法并且代码的文章。矩阵除法或许用处不大,不过在那一篇文章中,我认为比较好的一点是告诉了大家一种计算方法,即:若矩阵 A 已知且可逆,矩阵 C 已知,并且 AB=C ,求解矩阵 B 。我认为这种初等行变换的方法还是挺好的。

        在本篇文章中,我和大家探讨一下线性代数里面一个重要的知识——线性方程组及其解法。

一、线性代数知识回顾:

我们先探讨一下二元一次方程组的解法:

\left\{\begin{matrix} 2x+y=3 \\ 4x-3y=1 \end{matrix}\right. \Rightarrow \left\{\begin{matrix} 2x+y=3 \\ 0x-5y=-5 \end{matrix}\right. \Rightarrow \left\{\begin{matrix} y=1 \\ x=1 \end{matrix}\right.

        相信这个解法大家已经很熟悉了,将第一个式子的 -2 倍加到第二个式子上,就可以消掉第二个式子中的 x 了,然后第二个式子就只剩下 y 一个未知数了,就可以轻松解出 y ,然后将 y 代入第一个式子,就可以轻松解出 x ,到此,二元一次方程组求解完成。

        从几何意义上看,这两个方程对应着二维平面上的两条直线,并且这两条直线交于一点(1,1),因此有唯一解

        有人可能会想到,不是所有的二元一次方程组都有解,因此我们看一看下面的例子: 

\left\{\begin{matrix} x+y=1 \\ 2x+2y=2 \end{matrix}\right. \Rightarrow \left\{\begin{matrix} x+y=1 \\ 0+0=0 \end{matrix}\right.

         大家看到,这个这个将第二个式子中两个未知数都化没了,并且观察这个方程组可知,它有无数解,其实容易看到,第二个式子就是第一个式子的 2 倍,实际上,这两个式子是等价的,第二个式子所描述的信息完全可以用第一个式子描述,也就是说这个方程组中 “有效方程” 的个数为 1 ,而只靠一个式子无法固定两个未知数,因此方程组有无数解。

        从几何意义上看,这两个式子就是二维平面中的两个直线,而这两个直线是重合的,也就是说,两个直线相交的点有无数个,即:方程组有无数解。

        那么,二元一次方程组有没有可能无解呢?看看下面的例子:

\left\{\begin{matrix} x+y=1 \\ x+y=2 \end{matrix}\right. \Rightarrow \left\{\begin{matrix} x+y=1 \\ 0+0=1 \end{matrix}\right.

         可以看到,消元后,第二个方程变为了一个不可能成立的式子。容易看出,这两个方程是矛盾的,因此是无解的。

        从几何意义上看,这两个二维平面上的直线平行且不重合,没有交点,因此无解。

        从上面二元一次方程组的回顾中,我们可以将方程组推广到 n 维情形:

\left\{\begin{matrix} a_{11}x_{1}+a_{12}x_{2}+\cdot \cdot \cdot +a_{1n}x_{n}=b_{1}\\ a_{21}x_{1}+a_{22}x_{2}+\cdot \cdot \cdot +a_{2n}x_{n}=b_{2}\\ \cdot \cdot \cdot \\ a_{n1}x_{1}+a_{n2}x_{2}+\cdot \cdot \cdot +a_{nn}x_{n}=b_{n} \end{matrix}\right.

         同样按照上面的消元方法,不过这次稍微复杂,首先消去第 2 个到第 n 个方程的未知数 x_{1} 然后消去第 3 个到第 n 个方程的未知数 x_{2 } ,以此类推,直到消去最后一个方程的未知数 x_{n-1} ,然后自下而上,先求出 x_{n} ,代入倒数第二个方程,求出 x_{n-1} ,代入倒数第三个方程,依次类推,便可求出方程组的解。大家仔细看看消元的过程,是不是和初等行变换十分像,我们其实可以将上述方程组这样表示:

设            A=\begin{pmatrix} a_{11} &a_{12} &\cdot \cdot \cdot & a_{1n}\\ a_{21}&a_{22} &\cdot \cdot \cdot &a_{2n} \\ \cdot \cdot \cdot& \cdot \cdot \cdot & \cdot \cdot \cdot&\cdot \cdot \cdot \\ a_{n1}&a_{n2} &\cdot \cdot \cdot &a_{nn} \end{pmatrix}           x=\begin{pmatrix} x_{1}\\ x_{2}\\ \cdot \cdot \cdot\\ x_{n} \end{pmatrix}           b=\begin{pmatrix} b_{1}\\ b_{2}\\ \cdot \cdot \cdot\\ b_{n} \end{pmatrix}

上述方程组可以表示为:

Ax=b

为了表示方便,我们引入增广矩阵:

AA=\begin{pmatrix} A &b \end{pmatrix} =\begin{pmatrix} a_{11} &a_{12} &\cdot \cdot \cdot &a_{1n} &b_{1} \\ a_{21}&a_{22} &\cdot \cdot \cdot &a_{2n} &b_{2} \\ \cdot \cdot \cdot&\cdot \cdot \cdot &\cdot \cdot \cdot &\cdot \cdot \cdot &\cdot \cdot \cdot \\ a_{n1}&a_{n2} &\cdot \cdot \cdot &a_{nn} &b_{n} \end{pmatrix}

         当我们对未知数规定书写顺序,那么增广矩阵就和线性方程组就 一 一 对应了,对方程组进行消元就等价于对增广矩阵 AA 进行初等行变换,方程组的有效方程的个数就等于增广矩阵的秩,也等于系数矩阵的秩,也就是说,如果系数矩阵满秩,那么方程组有唯一解。当系数矩阵不满秩时,若没有矛盾方程出现,则方程组有无数解,若有矛盾方程出现,则方程组无解。

        从几何意义来看,这 n 个方程组相当于 n 维空间中 n 个 n-1 维的平面。n 个平面交于一点等价于方程组中的 “有效方程” 的个数为 n ,等价于系数矩阵 A 满秩,等价于增广矩阵 AA 的秩为 n ,等价于方程组有唯一解。

二、算法描述:

        输入系数矩阵 A 以及 向量 b ,求解向量 x ,使得 Ax=b 。

        1. 求出增广矩阵

        2. 将增广矩阵初等行变换,化为上三角形,在这个过程中,判断增广矩阵的秩是否是 n ,如果不是 n ,则说明方程组无解或有无数解。

        3. 自下而上依次计算出 x_{n},x_{n-1}\cdot \cdot \cdot x_{2},x_{1} 。

         详情请看下面代码:

三、代码实现:

        类定义为:

class Mat
{
public:int m = 1, n = 1; //行数和列数double mat[N][N] = { 0 };  //矩阵开始的元素Mat() {}Mat(int mm, int nn){m = mm; n = nn;}void create();//创建矩阵void Print();//打印矩阵void augmat(Mat a, Mat b);//求矩阵 a 和向量 b 的增广矩阵bool solve(Mat a, Mat b); //求线性方程组的解
};

        其中solve函数求解线性方程组,其定义如下:

bool Mat::solve(Mat a, Mat b) //a 为方阵 ,b 为列向量
//求线性方程组的解(ax=b ,求 x),矩阵 a 为方阵并且方程组有唯一解时返回 true
{if (a.n != a.m)//只求解是方阵时的情形{cout << "系数矩阵不是方阵" << endl;return false; //返回false}m = a.n; n = 1; //解向量中必定有 a.n( a.m )个分量,是 a.n * 1 的列向量Mat aa;aa.augmat(a, b); //求增广矩阵//下面代码将增广矩阵化为上三角矩阵,并判断增广矩阵秩是否为 nfor (int i = 1; i <= aa.m; i++){//寻找第 i 列不为零的元素int k;for (k = i; k <= aa.m; k++){if (fabs(aa.mat[k][i]) > 1e-10) //满足这个条件时,认为这个元素不为0break;}if (k <= aa.m)//说明第 i 列有不为0的元素{//交换第 i 行和第 k 行所有元素for (int j = i; j <= aa.n; j++)//从第 i 个元素交换即可,因为前面的元素都为0{//使用aa.mat[0][j]作为中间变量交换元素aa.mat[0][j] = aa.mat[i][j]; aa.mat[i][j] = aa.mat[k][j]; aa.mat[k][j] = aa.mat[0][j];}double c;//倍数for (int j = i + 1; j <= aa.m; j++){c = -aa.mat[j][i] / aa.mat[i][i];for (k = i; k <= aa.n; k++){aa.mat[j][k] += c * aa.mat[i][k];//第 i 行 a 倍加到第 j 行}}}else //没有找到则说明系数矩阵秩不为 n ,说明方程组中有效方程的个数小于 n{cout << "系数矩阵奇异,线性方程组无解或有无数解" << endl;return false;}}//自下而上求解for (int i = a.m; i >= 1; i--){mat[i][1] = aa.mat[i][aa.n];for (int j = a.m; j > i; j--){mat[i][1] -= mat[j][1] * aa.mat[i][j];}mat[i][1] /= aa.mat[i][i];}return true;
}

线性方程组的求解就完成了,这种求解方法叫 “高斯消元法” 。下面附上完整代码:

#include<iostream>
#include <cmath>
#define N 10
using namespace std;
class Mat
{
public:int m = 1, n = 1; //行数和列数double mat[N][N] = { 0 };  //矩阵开始的元素Mat() {}Mat(int mm, int nn){m = mm; n = nn;}void create();//创建矩阵void Print();//打印矩阵void augmat(Mat a, Mat b);//求矩阵 a 和向量 b 的增广矩阵bool solve(Mat a, Mat b); //求线性方程组的解
};void Mat::create()
{for (int i = 1; i <= m; i++){for (int j = 1; j <= n; j++){cin >> mat[i][j];}}
}
void Mat::Print()
{for (int i = 1; i <= m; i++){for (int j = 1; j <= n; j++){cout << mat[i][j] << "\t";}cout << endl;}
}
void Mat::augmat(Mat a, Mat b)//求矩阵 a 和向量 b 的增广矩阵
{m = a.m; n = a.n + 1;for (int i = 1; i <= a.m; i++){for (int j = 1; j <= a.n; j++){mat[i][j] = a.mat[i][j];}mat[i][n] = b.mat[i][1];}
}bool Mat::solve(Mat a, Mat b) //a 为方阵 ,b 为列向量
//求线性方程组的解(ax=b ,求 x),矩阵 a 为方阵并且方程组有唯一解时返回 true
{if (a.n != a.m)//只求解是方阵时的情形{cout << "系数矩阵不是方阵" << endl;return false; //返回false}m = a.n; n = 1; //解向量中必定有 a.n( a.m )个分量,是 a.n * 1 的列向量Mat aa;aa.augmat(a, b); //求增广矩阵//下面代码将增广矩阵化为上三角矩阵,并判断增广矩阵秩是否为 nfor (int i = 1; i <= aa.m; i++){//寻找第 i 列不为零的元素int k;for (k = i; k <= aa.m; k++){if (fabs(aa.mat[k][i]) > 1e-10) //满足这个条件时,认为这个元素不为0break;}if (k <= aa.m)//说明第 i 列有不为0的元素{//交换第 i 行和第 k 行所有元素for (int j = i; j <= aa.n; j++)//从第 i 个元素交换即可,因为前面的元素都为0{//使用aa.mat[0][j]作为中间变量交换元素aa.mat[0][j] = aa.mat[i][j]; aa.mat[i][j] = aa.mat[k][j]; aa.mat[k][j] = aa.mat[0][j];}double c;//倍数for (int j = i + 1; j <= aa.m; j++){c = -aa.mat[j][i] / aa.mat[i][i];for (k = i; k <= aa.n; k++){aa.mat[j][k] += c * aa.mat[i][k];//第 i 行 a 倍加到第 j 行}}}else //没有找到则说明系数矩阵秩不为 n ,说明方程组中有效方程的个数小于 n{cout << "系数矩阵奇异,线性方程组无解或有无数解" << endl;return false;}}//自下而上求解for (int i = a.m; i >= 1; i--){mat[i][1] = aa.mat[i][aa.n];for (int j = a.m; j > i; j--){mat[i][1] -= mat[j][1] * aa.mat[i][j];}mat[i][1] /= aa.mat[i][i];}return true;
}int main()
{Mat a(3, 3), b(3, 1);cout << "请输入 " << a.m << "*" << a.n << " 的矩阵:" << endl;a.create();cout << "请输入 " << b.m << "*" << b.n << " 的列向量:" << endl;b.create();Mat x;if (x.solve(a, b))//求线性方程组的解向量{cout << "解向量如下:" << endl << endl;x.Print();}return 0;
}

若有不足之处,欢迎大家指正!


http://www.ppmy.cn/news/43897.html

相关文章

即时通讯技术文集(第12期):网络保活、心跳机制等文章汇总 [共23篇]

为了更好地分类阅读52im.net 总计1000多篇精编文章&#xff0c;我将在每周三推送新的一期技术文集&#xff0c;本次是第12 期。 [- 1 -] 应用保活终极总结(一)&#xff1a;Android6.0以下的双进程守护保活实践 [链接] http://www.52im.net/thread-1135-1-1.html [摘要] 因为A…

( “树” 之 DFS) 687. 最长同值路径 ——【Leetcode每日一题】

687. 最长同值路径 给定一个二叉树的 root &#xff0c;返回 最长的路径的长度 &#xff0c;这个路径中的 每个节点具有相同值 。 这条路径可以经过也可以不经过根节点。 两个节点之间的路径长度 由它们之间的边数表示。 示例 1: 输入&#xff1a;root [5,4,5,1,1,5] 输出&…

采集工具如何帮助SEO优化关键词

随着互联网的发展&#xff0c;越来越多的企业开始意识到SEO优化对于企业的重要性。SEO优化可以帮助企业提高网站在搜索引擎中的排名&#xff0c;进而吸引更多的潜在客户。而关键词则是SEO优化的核心&#xff0c;如何找到合适的关键词&#xff0c;成为了企业优化的关键。在这里&…

java程序员学前端-Vue3篇

Vue 3 1. TypeScript 1) 动态类型的问题 前面我们讲过 js 属于动态类型语言&#xff0c;例如 function test(obj) { }obj 可能只是个字符串 test(hello, world)obj 也有可能是个函数 test(()>console.log(hello, world))obj 类型不确定&#xff0c;就给后期使用者…

leetcode1427. 字符串的左右移

题目 给定一个包含小写英文字母的字符串 s 以及一个矩阵 shift&#xff0c;其中 shift[i] [direction, amount]&#xff1a; direction 可以为 0 &#xff08;表示左移&#xff09;或 1 &#xff08;表示右移&#xff09;。 amount 表示 s 左右移的位数。 左移 1 位表示移除 s…

Windows应急响应 -Windows日志排查,系统日志,Web应用日志,

「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;对网络安全感兴趣的小伙伴可以关注专栏《网络安全入门到精通》 Windows日志分析一、查看日志二、日志分类三、筛选日志四、事件ID1、安全日志1.1、登录类…

Linux ubuntu更新meson版本

问题描述 在对项目源码用meson进行编译时&#xff0c;可能出现以下错误 meson.build:1:0: ERROR: Meson version is 0.45.1 but project requires > 0.58.0. 或者 meson_options.txt:1:0: ERROR: Unknown type feature. 等等&#xff0c;原因是meson版本跟设置的不适配。 …

【面试】记一次中小公司某一次面试题

文章目录1. MySQL中explain执行计划你比较关注哪些字段&#xff1f;2.char、varchar 和 text的区别&#xff1f;3. int(3)和int(11)查询的区别&#xff1f;4. 字段里NULL和空值的区别&#xff1f;5. spring中怎么解决循环依赖问题&#xff1f;5.1 重新设计5.2 使用注解 Lazy5.3…