[复习笔记]高斯消元法

概念

引用自百度百科:

数学上,高斯消元法(或译:高斯消去法),是线性代数规划中的一个算法,可用来为线性方程组求解。但其算法十分复杂,不常用于加减消元法,求出矩阵的秩,以及求出可逆方阵的逆矩阵。不过,如果有过百万条等式时,这个算法会十分省时。一些极大的方程组通常会用迭代法以及花式消元来解决。当用于一个矩阵时,高斯消元法会产生出一个“行梯阵式”。高斯消元法可以用在电脑中来解决数千条等式及未知数。亦有一些方法特地用来解决一些有特别排列的系数的方程组。

应用

由以上概念可知,高斯消元法可以用于求解线性方程组(即n元一次方程组),这也是高斯消元法在OI和ACM竞赛中的主要应用。当然也有求行列式等其他用法,但我太菜不会,学会后会更新

原理

首先回忆一下我们求解二元一次方程组的方法:代入消元与加减消元,其核心思想就是消元。实际上,求解n元一次方程组也是如此,都是通过加减与代入的方法实现消元

由于线性方程组的加减与倍乘都与矩阵的初等行变换类似,于是我们可以用矩阵来代替这一线性方程组。

我们可以将一个线性方程组的未知数省略,只留下系数和常数,并将其等效为一个增广矩阵(注:不会细说线代知识,无线代基础的选手可以放心食用)

洛谷模板题)样例为例:

\(\left\{ \begin{matrix} x_1+3x_2+4x_3=5\\ x_1+4x_2+7x_3=4\\ 9x_1+3x_2+2x_3=2 \end{matrix} \right.\)

将其化为增广矩阵

\(\begin{bmatrix} 1&3&4&5\\ 1&4&7&3\\ 9&3&2&2 \end{bmatrix}\)

然后我们就可以通过矩阵的初等行变换(即方程的加减与倍乘)来实现消元,最终的目标是将其化为简化阶梯形矩阵(简单理解为除\(a_{ii}\)为1外其他系数都为0的矩阵)

即形如

\(\begin{bmatrix} 1&0&\cdots&0&b_{1}\\ 0&1&\cdots&0&b_{2}\\ \vdots&\vdots&\ddots&\vdots&\vdots\\ 0&0&\cdots&1&b_{n} \end{bmatrix}\)

易知此时的矩阵可以等价为

\(\left\{ \begin{matrix} x_1=b_1\\ x_2=b_2\\ \vdots\\ x_n=b_n \end{matrix} \right.\)

即原方程组的解集

算法过程

首先将矩阵化为阶梯形矩阵

也就是每一非零行都在每一零行之上,某一行的先导元素所在的列位于前一行先导元素的右边,某一先导元素所在列下方元素都是零的矩阵

具体为形如

\(\begin{bmatrix} a_{11}&a_{12}&\cdots&a_{1n}\\ 0&a_{22}&\cdots&a_{2n}\\ \vdots&\vdots&\ddots&\vdots\\ 0&0&\cdots&a_{mn} \end{bmatrix}\)

的矩阵

要实现这一过程的实现方法有很多,这里采用列主元消去法

对于第i个未知数,我们在还没有进行过消元的行(即第i行到第n行)中选取\(\left|a_i\right|\)最大那一个作为主元,将这一行交换到第i行并进行消元

你或许会发现,有时我们在第 i行到第n行找不到一个方程存在\(x_i\)即对于任意\(k \in [i,n]\),都有\(a_{ki}=0\)。此时该线性方程组就无解或有无数组解。由于我们已经对第1行到第i-1行进行过消元,所以第i行到第n行都不存在\(x_1、x_2\cdots x_{i-1}\)。假如我们可以通过第i行到第n行这n-i+1行解出除\(x_i\)外剩下n-i个未知数,回代后第1行到第i-1行这i-1行中仍有i个未知数,这显然是无法得到唯一解的。

具体的消元过程为

  1. 选择\(\left|a_i\right|\)最大的一个为主元,将其所在行一行与当前行交换
  2. 将主元系数化为1
  3. 将第i+1到第n行减去第i行的\(a_{ki}\)倍,实现消去\(x_i\)

该部分代码如下

for(re int i=1;i<=n;++i)
{
	re int p=i;
	for(re int j=i+1;j<=n;++j)
	if(fabs(mat[j][i])>fabs(mat[p][i]))
	p=j;//选择绝对值最大的为主元
	for(re int k=1;k<=n+1;++k) swap(mat[i][k],mat[p][k]);//交换两行
	if(fabs(mat[i][i])<=eps){puts("No Solution");return 0;}
	//找不到主元,无解或无数组解
	re double tmp=mat[i][i];
	for(re int j=i;j<=n+1;++j) mat[i][j]/=tmp;//化系数为1
	for(re int j=i+1;j<=n;++j)
		if(mat[j][i]!=0)
		{
			tmp=mat[j][i];
			for(re int k=i;k<=n+1;++k)
			mat[j][k]-=tmp*mat[i][k];
		}
	//实现消元
}

将以上过程完成后,我们就得到了阶梯形矩阵

接下来我们就可以进行回代。所谓回代,就是将已经求出来的未知数代回到上面的方程中。

由消元过程可知,最后一行\(a_1、a_2\cdots a_{n-1}=0\)。易知,该方程组有解,当且仅当最后一行,\(a_n=1\),此时的\(b_n\)即为\(x_n\)的解,将其代入第1行到第n-1行就可以将其他方程的\(x_n\)消去,回代\(x_n\)后,我们便得到了\(x_{n-1}\)的解,重复以上过程就可以解出所有未知数

该部分代码如下

for(re int i=n;i>=1;--i)
	for(re int j=i-1;j>=1;--j)
	{
		mat[j][n+1]-=mat[i][n+1]*mat[j][i];
		mat[j][i]=0;
	}

以上是高斯消元法的全过程

完整代码

#include <cmath>
#include <cstdio>
#include <algorithm>

using namespace std;

#define il inline
#define re register

const int N=110;
const double eps=1e-6;

namespace FastIO
{
char buf[1<<21],buf2[1<<21],*p1=buf,*p2=buf;
int p,p3=-1;
il int getc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++;}
il void flush(){fwrite(buf2,1,p3+1,stdout),p3=-1;}
#define isdigit(ch) (ch>=48&&ch<=57)
template <typename T>
il void read(T &x)
{
	re bool f=0;x=0;re char ch=getc();
	while(!isdigit(ch)) f|=(ch=='-'),ch=getc();
	while(isdigit(ch)) x=x*10+(ch^48),ch=getc();
	x=f?-x:x;
}
template <typename T>
il void print(T x)
{
	if(p3>(1<<20)) flush();
	if(x<0) buf2[++p3]=45,x=-x;
	re int a[50]={};
	do{a[++p]=x%10+48;}while(x/=10);
	do{buf2[++p3]=a[p];} while (--p);
	buf2[++p3]='\n';
}
}
using namespace FastIO;

int n;
double mat[N][N];

int main()
{
	read(n);
	for(re int i=1;i<=n;++i)
		for(re int j=1;j<=n+1;++j)
			read(mat[i][j]);
	for(re int i=1;i<=n;++i)
	{
		re int p=i;
		for(re int j=i+1;j<=n;++j)
			if(fabs(mat[j][i])>fabs(mat[p][i]))
				p=j;
		for(re int k=1;k<=n+1;++k) swap(mat[i][k],mat[p][k]);
		if(fabs(mat[i][i])<=eps){puts("No Solution");return 0;}
		re double tmp=mat[i][i];
		for(re int j=i;j<=n+1;++j) mat[i][j]/=tmp;
		for(re int j=i+1;j<=n;++j)
		if(mat[j][i]!=0)
		{
			tmp=mat[j][i];
			for(re int k=i;k<=n+1;++k)
			mat[j][k]-=tmp*mat[i][k];
		}
	}
	for(re int i=n;i>=1;--i)
		for(re int j=i-1;j>=1;--j)
		{
			mat[j][n+1]-=mat[i][n+1]*mat[j][i];
			mat[j][i]=0;
		}
	for(re int i=1;i<=n;++i)
	printf("%.2lf\n",mat[i][n+1]);
	flush();return 0;
}
posted @ 2022-09-17 20:46  watermonster1y1  阅读(338)  评论(0编辑  收藏  举报