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

news2024/10/7 18:27:23

前言:

        上次博客,我写了一篇关于定义矩阵除法并且代码的文章。矩阵除法或许用处不大,不过在那一篇文章中,我认为比较好的一点是告诉了大家一种计算方法,即:若矩阵 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); //求增广矩阵

	//下面代码将增广矩阵化为上三角矩阵,并判断增广矩阵秩是否为 n
	for (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) //满足这个条件时,认为这个元素不为0
				break;
		}
		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); //求增广矩阵

	//下面代码将增广矩阵化为上三角矩阵,并判断增广矩阵秩是否为 n
	for (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) //满足这个条件时,认为这个元素不为0
				break;
		}
		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.coloradmin.cn/o/412594.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2021蓝桥杯真题大写 C语言/C++

题目描述 给定一个只包含大写字母和小写字母的字符串&#xff0c;请将其中所有的小写字母转换成大写字母后将字符串输出。 输入描述 输入一行包含一个字符串。 输出描述 输出转换成大写后的字符串。 输入输出样例 示例 输入 LanQiao 输出 LANQIAO 评测用例规模与约定 对于…

[架构之路-158]-《软考-系统分析师》-10-系统分析-1-5-逻辑设计、逻辑模型(系统分析师的主要职责之一)

目录 前言&#xff1a;什么是系统 科学内涵 常见的系统 第 10章 现有系 统 分 析 1 0 . 1 系统分析概述 1 . 系统分析的任务 2 . 系统分析的难点 3 . 对系统分析师的要求 1 0 . 2 详细调查 10.2.1 详细调查的原则 10.2.2 详细调査的内容 》对企业的实际运营和业务进…

async/await 函数到底要不要加 try catch ?

前言 写异步函数的时候&#xff0c;promise 和 async 两种方案都非常常见&#xff0c;甚至同一个项目里&#xff0c;不同的开发人员都使用不同的习惯, 不过关于两者的比较不是本文关注的重点&#xff0c;只总结为一句话&#xff1a;“async 是异步编程的终极解决方案”。 当使…

匿名管道与命名管道

匿名管道与命名管道一&#xff0c;进程间通信什么是进程间通信进程间通信的目的管道的概念二&#xff0c;匿名管道匿名管道的创建匿名管道使用匿名管道的特性以及四种场景匿名管道的原理通过匿名管道实现简易进程池。三&#xff0c;命名管道命名管道的创建命名管道的使用命名管…

vue3+vite+ts 接入QQ登录

说明 前提资料准备 在QQ互联中心注册成为开发者 站点&#xff1a;https://connect.qq.com/创建应用&#xff0c;如图 js sdk方式 下载对应的sdk包 sdk下载&#xff1a;https://wiki.connect.qq.com/sdk%e4%b8%8b%e8%bd%bd 使用 下载离线js sdk 打开&#xff1a;https:…

jQuery核心

目录 一、引入jQuery 二、jQuery的内涵 1、jQuery挂载在window对象上 2、jQuery是一个函数对象 三、jQuery函数的四种参数形式 1、参数是一个函数function 2、参数是一个选择器 3、参数是一个DOM对象 4、参数是一个HTML元素标签&#xff08;HTML代码&#xff09; 简介…

【Linux】八、Linux进程信号详解(完结)

目录 三、阻塞信号 3.1 信号其他相关常见概念 3.2 信号在内核中的表示 3.3 sigset_t 3.4 信号集操作函数 3.5 sigprocmask函数 3.6 sigpending函数 3.7 信号集实验 四、深入理解捕捉信号 4.1 进程地址空间二次理解&#xff08;内核空间与用户空间&#xff09; 4.2 用…

黑马的redis实战篇-短信登录

目录 四、实战篇-短信登录 4.1 导入黑马点评项目 1、后端&#xff1a; 2、前端 4.2 基于Session实现登录 1、发送验证码 2、短信验证码登录注册 3、校验登录状态 4.3 集群的session共享问题 4.4 基于Redis实现共享session登录 1、发送验证码 2、短信验证码登录注册 …

NumPy 秘籍中文第二版:六、特殊数组和通用函数

原文&#xff1a;NumPy Cookbook - Second Edition 协议&#xff1a;CC BY-NC-SA 4.0 译者&#xff1a;飞龙 在本章中&#xff0c;我们将介绍以下秘籍&#xff1a; 创建通用函数查找勾股三元组用chararray执行字符串操作创建一个遮罩数组忽略负值和极值使用recarray函数创建一…

蓝桥杯之单片机学习(终)——关于之前文章的错误及更正(附:第十四届蓝桥杯单片机赛题)

文章目录零、吐槽一、关于自创模板&#xff0c;和自写模板库的问题二、关于 详解A/D、D/A、PCF8591 这篇文章一些小错误三、模板最终版本main.cds1302,hds1302.conewire.honewire.ciic.hiic.c附、第十四届蓝桥杯单片机赛题零、吐槽 今年是矩阵键盘三个协议一起调用啊。真是一年…

“AI+机器人”持续为多领域增“智”添“质”,开启效益增长飞轮

近期&#xff0c;工信部等17部门联合推出《“机器人”应用行动实施方案》&#xff0c;全面加快机器人领域应用拓展。据方案提出&#xff0c;至2025年&#xff0c;制造业机器人密度较2020年将实现翻番&#xff0c;服务机器人及特种机器人行业应用深度与广度显著提升。机器人融合…

服务器被DDoS攻击,怎么破?

文章目录前言网站受到DDoS的症状判断是否被攻击查看网络带宽占用查看网络连接TCP连接攻击SYN洪水攻击防御措施TCP/IP内核参数优化iptables 防火墙预防防止同步包洪水&#xff08;Sync Flood&#xff09;Ping洪水攻击&#xff08;Ping of Death&#xff09;控制单个IP的最大并发…

基于SpringBoot的私人健身和教练的预约管理系统源码数据库论文

目 录 第一章 概述 1.1研究背景 1.2开发意义 1.3研究现状 1.4研究内容 1.5论文结构 第二章 开发技术介绍 2.1系统开发平台 2.2平台开发相关技术 2.2.1 Javar技术 2.2.2 Mysql数据库介绍 2.2.3 Mysql环境配置 2.2.4 B/S架构 2.2.5 Springboot框架 …

主动配电网故障恢复的重构与孤岛划分统一模型研究【升级版本】(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

V8引擎执行原理

v8是C编写的Google开源高性能JavaScript和WebAssembly引擎&#xff0c;它用于Chrome和Node.js等。 它实现ECMAScript和WebAssembly。 v8可独立运行&#xff0c;也可嵌入到任何C应用程序中。 parse模块 parse模块会将JavaScript代码转换成AST(抽象语法树)&#xff0c;因为解…

[LeetCode周赛复盘] 第 340 场周赛20230409

[LeetCode周赛复盘] 第 340 场周赛20230409 一、本周周赛总结二、 6361. 对角线上的质数1. 题目描述2. 思路分析3. 代码实现三、6360. 等值距离和1. 题目描述2. 思路分析3. 代码实现四、6359. 最小化数对的最大差值1. 题目描述2. 思路分析3. 代码实现五、 6353. 网格图中最少访…

【排序】排序这样写才对Ⅰ --插入排序与选择排序

Halo&#xff0c;这里是Ppeua。平时主要更新C语言&#xff0c;C&#xff0c;数据结构算法......感兴趣就关注我吧&#xff01;你定不会失望。 &#x1f308;个人主页&#xff1a;主页链接 &#x1f308;算法专栏&#xff1a;专栏链接 我会一直往里填充内容哒&#xff01; &…

Axios请求(对于ajax的二次封装)——Axios请求的响应结构、默认配置

Axios请求&#xff08;对于ajax的二次封装&#xff09;——Axios请求的响应结构、默认配置知识回调&#xff08;不懂就看这儿&#xff01;&#xff09;场景复现核心干货axios请求的响应结构响应格式详解实际请求中的响应格式axios请求的默认配置全局axios默认值&#xff08;了解…

Debug | wget 的安装与使用(Windows)

!wget -nc http://labfile.oss.aliyuncs.com/courses/780/WeatherData.zip 报错信息&#xff1a; wget 不是内部或外部命令&#xff0c;也不是可运行的程序或批处理文件。 分析&#xff1a; 在jupyter notebook中做机器学习时导入数据使用!wget遇到了这个问题&#xff0c;查到…

轻松上手git代码版本管理工具--协同开发-冲突解决、线上分支合并以及使用pycharm操作git

一、协同开发 多人合作开发一个项目---->多人公用一个远程仓库 以后台项目为例: git init # git管理设置忽略文件.gitignore git add .git commit -m 第一次提交,写完了首页功能远程新建一个远程仓库(空) 创建一个origin git remote add origin git@gitee.com:xx…