[note] 高斯消元

高斯消元是一种在 \(O(n^3)\) 的时间复杂度下解线性方程组的算法,方程组形如:

\[\left\{ \begin{aligned} &a_{1,1}x_{1}+a_{1,2}x_{2}+\dots+a_{1,n}x_{n}=b_1\\ &a_{2,1}x_{1}+a_{2,2}x_{2}+\dots+a_{2,n}x_{n}=b_2\\ &\dots\\ &a_{n,1}x_{1}+a_{n,2}x_{2}+\dots+a_{n,n}x_{n}=b_n \end{aligned} \right. \]

算法思路

1. 高斯消元

发现线性方程组的系数和等号右侧的常数是解方程的关键,因此我们可以把系数和常数拎出来,形成一个矩阵。
比如:

\[\left\{ \begin{aligned} 3x_1+7x_2+8x_3&=14\\ 6x_1-x_2-2x_3&=7\\ -12x_1+x_2-x_3&=-10\\ \end{aligned} \right. \]

可以改写为:

\[\left( \begin{array}{ccc|c} 3& 7& 8& 14\\ 6& -1& -2& 7\\ -12& 1& -1& -10 \end{array} \right) \]

这样的矩阵被称为增广矩阵

回忆解多元一次方程组的过程,我们发现我们可以对方程组进行如下操作:

  1. 交换任意两个方程的位置;
  2. 将一个方程加上另一个方程;
  3. 将整个方程乘上一个数。

对应到增广矩阵中,就是:

  1. 交换任意两行;
  2. 将某一行的数分别加上另一行对应的数;
  3. 将某一行的所有数乘上同一个数。

高斯消元的目的,就是通过上面的三种操作,将增广矩阵变为形式如下的上三角矩阵,得到 \(x_n\) 的值,然后依次代入得到整个方程的解:

\[\left( \begin{array}{ccc|c} \times& \times& \times& \times\\ \times& \times& \times& \times\\ \times& \times& \times& \times \end{array} \right) \Rightarrow \left( \begin{array}{ccc|c} \times& \times& \times& \times\\ 0& \times& \times& \times\\ 0& 0& \times& \times \end{array} \right) \Rightarrow \left( \begin{array}{ccc|c} \times& 0& 0& \times\\ 0& \times& 0& \times\\ 0& 0& \times& \times \end{array} \right) \]

考虑每次对一个未知数进行消元(即一列),设当前未知数为 \(x_i\)(第 \(i\) 列),我们按照以下步骤消元:

1. 无解判断

若这一列从第 \(i\) 行往下都是 \(0\),说明这个未知数无解或为任意解,判无解。

2. 选主元

选择这一列从第 \(i\) 行往下系数绝对值最大的那一个作为主元,将这一行与第 \(i\) 行交换
比如:

\[\ \left( \begin{array}{ccc|c} 3& 7& 8& 14\\ 6& -1& -2& 7\\ -12& 1& -1& -10 \end{array} \right) \Rightarrow \left( \begin{array}{ccc|c} \underline{3}& 7& 8& 14\\ \underline{6}& -1& -2& 7\\ \boxed{-12}& \underline{1}& \underline{-1}& \underline{-10} \end{array} \right) \Rightarrow \left( \begin{array}{ccc|c} -12& 1& -1& -10\\ 6& -1& -2& 7\\ 3& 7& 8& 14 \end{array} \right) \]

这一操作的目的是减少误差(原因见下文)。

3. 消元

对于第 \(j\) 行(\(j>i\)),为了使 \(a_{j,i}\) 消为 \(0\) ,我们需要将第 \(j\) 行减去第 \(i\) 行(主元行)的 \(\frac{a_{j,i}}{a_{i,i}}\) 倍,这样 \(a_{j,i}-a_{i,i}\times\frac{a_{j,i}}{a_{i,i}}=0\)

注意,消第 \(i\) 个未知数时,只需要将第 \(i\) 列第 \(i\) 行下面的数消为 \(0\) 即可。

\[\begin{aligned} &\left( \begin{array}{ccc|c} -12& 1& -1& -10\\ 6& -1& -2& 7\\ 3& 7& 8& 14 \end{array} \right)\\ \Rightarrow &\left( \begin{array}{ccc|c} \color{red}{-12}& \color{red}{1}& \color{red}{-1}& \color{red}{-10}\\ 6-(\textcolor{red}{-12})\times\textcolor{blue}{\frac{6}{-12}}& -1-\textcolor{red}{1}\times\textcolor{blue}{\frac{6}{-12}}& -2-(\textcolor{red}{-1})\times\textcolor{blue}{\frac{6}{-12}}& 7-(\textcolor{red}{-10})\times\textcolor{blue}{\frac{6}{-12}}\\ 3-(\textcolor{red}{-12})\times\textcolor{green}{\frac{3}{-12}}& 7-\textcolor{red}{1}\times\textcolor{green}{\frac{3}{-12}}& 8-(\textcolor{red}{-1})\times\textcolor{green}{\frac{3}{-12}}& 14-(\textcolor{red}{-10})\times\textcolor{green}{\frac{3}{-12}} \end{array} \right)\\ \Rightarrow &\left( \begin{array}{ccc|c} -12& 1& -1& -10\\ \underline{0}& -0.5& -2.5& 2\\ \underline{0}& 7.25& 7.75& 11.5 \end{array} \right) \end{aligned} \]

从左往右对每个未知数(每一列)进行以上三步操作,得到上三角矩阵:

\[\left( \begin{array}{ccc|c} -12& 1& -1& -10\\ 0& 7.25& 7.75& 11.5\\ 0& 0& -\frac{114}{58}& \frac{81}{29} \end{array} \right) \]

4. 得到答案

将上三角矩阵写成方程式,有

\[\left\{ \begin{aligned} -12x_1+x_2-x_3&=-10\\ 7.25x_2+7.75x_3&=11.5\\ -\frac{114}{58}x_3&=\frac{81}{29} \end{aligned} \right. \]

发现最后一个未知数的解我们已经知道了,将解依次代回就可以得到方程组的解了。

\[\left\{ \begin{aligned} x_1&=\frac{-10-x_2+x_3}{-12}=1.210526\\ x_2&=\frac{11.5-7.75x_3}{7.25}=3.105263\\ x_3&=-1.421053\\ \end{aligned} \right. \]

5.误差问题

考虑这样一组数据:

\[\left\{ \begin{aligned} 0.3 \times 10^{-11}x_1+x_2&=0.7\\ x_1+x_2&=0.9 \end{aligned} \right. \]

如果以第一行为主元进行消元,则有:

\[\left( \begin{array}{cc|c} 0.3\times 10^{-11}& 1& 0.7\\ 0& 1-\frac{10}{3}\times10^{11}\times1& 0.9-\frac{10}{3}\times10^{11}\times0.7 \end{array} \right) \]

会得到:

\[\left\{ \begin{aligned} x_1&=0.000000\\ x_2&=0.700000 \end{aligned} \right. \]

但如果以第二行为主元消元,则有:

\[\left( \begin{array}{cc|c} 1& 1& 0.9\\ 0& 1-0.3\times10^{-11}\times1& 0.7-0.3\times10^{-11}\times0.9 \end{array} \right) \]

会得到:

\[\left\{ \begin{aligned} x_1&=0.200000\\ x_2&=0.700000 \end{aligned} \right. \]

明显更接近正解。

所以,在高斯消元中,应选择系数绝对值最大的一行作为主元行,使 \(\frac{a_{j,i}}{a_{i,i}}\) 中的 \(a_{i,i}\) 绝对值更大,原分数值更小,减少计算时的误差。

时间复杂度 \(O(n^3)\)

模版代码

洛谷模版 P3389

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
const double eps=1e-7;
int n;
double a[105][105],ans[105];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n+1;j++)
		{
			scanf("%lf",&a[i][j]);
		}
	}
	for(int i=1;i<=n;i++)//枚举消去的未知数
	{
		//找主元行 
		int maxn=i; 
		for(int j=i+1;j<=n;j++)
		{
			if(fabs(a[j][i])>fabs(a[maxn][i])) maxn=j;
		}
		
		//判无解
		if(fabs(a[maxn][i])<eps)
		{
			printf("No Solution\n");
			return 0;
		}
		
		//将主元行交换到第 i 行
		for(int j=1;j<=n+1;j++)
		{
			swap(a[i][j],a[maxn][j]);
		}
		
		//消元
		for(int j=i+1;j<=n;j++)
		{
			for(int k=n+1;k>=i;k--)//注意倒序,否则 a[j][i] 的值会被提前修改 
			{
				a[j][k]-=a[i][k]*(a[j][i]/a[i][i]);
			}
		}
	}
	
	//代回,得到答案
	for(int i=n;i>=1;i--)
	{
		ans[i]=a[i][n+1];
		for(int j=n;j>i;j--) ans[i]-=a[i][j]*ans[j];
		ans[i]/=a[i][i];
	}
	
	for(int i=1;i<=n;i++)
	{
		printf("%.2lf\n",ans[i]);
	}
	return 0;
}

2. 高斯-约旦消元法

别看名字很高大上,实际上就是高斯消元法的一个小优化。

刚刚的消元过程中,我们的增广矩阵的变化为:

\[\left( \begin{array}{ccc|c} \times& \times& \times& \times\\ \times& \times& \times& \times\\ \times& \times& \times& \times \end{array} \right) \Rightarrow \left( \begin{array}{ccc|c} \times& \times& \times& \times\\ 0& \times& \times& \times\\ 0& 0& \times& \times \end{array} \right) \Rightarrow \left( \begin{array}{ccc|c} \times& 0& 0& \times\\ 0& \times& 0& \times\\ 0& 0& \times& \times \end{array} \right) \]

而高斯-约旦消元法则是跳过上三角矩阵,直接得到答案。

\[\left( \begin{array}{ccc|c} \times& \times& \times& \times\\ \times& \times& \times& \times\\ \times& \times& \times& \times \end{array} \right) \Rightarrow \left( \begin{array}{ccc|c} 1& 0& 0& \times\\ 0& 1& 0& \times\\ 0& 0& 1& \times\\ \end{array} \right) \]

具体来讲,对于第 \(i\) 个未知数,在选完主元行后,先将主元行整体除以 \(a_{i,i}\) 使 \(a_{i,i}\)(即对角线)等于 \(1\),然后将每一行都减去主元行的 \(a_{j,i}\) 倍即可。

时间复杂度 \(O(n^3)\)

模版代码

洛谷模版 P3389

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
const double eps=1e-7;
int n;
double a[105][105],ans[105];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n+1;j++)
		{
			scanf("%lf",&a[i][j]);
		}
	}
	for(int i=1;i<=n;i++)//枚举消去的未知数
	{
		//找主元行 
		int maxn=i; 
		for(int j=i+1;j<=n;j++)
		{
			if(fabs(a[j][i])>fabs(a[maxn][i])) maxn=j;
		}
		
		//判无解
		if(fabs(a[maxn][i])<eps)
		{
			printf("No Solution\n");
			return 0;
		}
		
		//将主元行交换到第 i 行
		for(int j=1;j<=n+1;j++)
		{
			swap(a[i][j],a[maxn][j]);
		}
		
		//将 a[i][i] 消为 1
		for(int j=n+1;j>=i;j--) a[i][j]/=a[i][i]; 
		
		//消元
		for(int j=1;j<=n;j++)//注意!!从 1 开始,对每一行进行消元 
		{
			if(j!=i)
			for(int k=n+1;k>=i;k--)//注意倒序,否则 a[j][i] 的值会被提前修改 
			{
				a[j][k]-=a[i][k]*a[j][i];
			}
		}
	}
	
	//直接得到答案
	
	for(int i=1;i<=n;i++)
	{
		printf("%.2lf\n",a[i][n+1]);
	}
	return 0;
}

3.异或方程组

异或方程组是形式如下的方程组,其中的 \(a\)\(b\)\(x\) 均为 \(0\)\(1\):

\[\left\{ \begin{aligned} &a_{1,1}x_1 \operatorname{xor} a_{1,2}x_2 \operatorname{xor} \dots \operatorname{xor} a_{1,n}x_n=b_1\\ &a_{2,1}x_1 \operatorname{xor} a_{2,2}x_2 \operatorname{xor} \dots \operatorname{xor} a_{2,n}x_n=b_2\\ &\dots\\ &a_{n,1}x_1 \operatorname{xor} a_{n,2}x_2 \operatorname{xor} \dots \operatorname{xor} a_{n,n}x_n=b_n \end{aligned} \right. \]

回忆上文,之所以线性方程组的增广矩阵可以高斯消元,是因为它可以:

  1. 交换任意两个方程的位置;
  2. 将一个方程加上另一个方程;
  3. 将整个方程乘上一个数。

推广到异或方程组,由于异或运算有交换律结合律,所以它可以:

  1. 交换任意两个方程的位置;
  2. 将一个方程异或上一个方程;
  3. 将整个方程异或上一个数。

因此,异或方程组可以通过高斯消元解决。

使用 std::bitset 进行优化,复杂度为 \(O(\frac{n^3}{\omega})\)

模版代码

int n;
bitset<N> a[N];
vector<bool> gauss()
{
	for(int i=1;i<=n;i++)
	{
		int maxn=i;
		for(int j=i+1;j<=n;j++)
		{
			if(a[j][i]>a[maxn][i]) maxn=j;
		}
		if(a[maxn][i]==0) return vector<bool>(0);
		if(i!=maxn) swap(a[i],a[maxn])
		for(int j=1;j<=n;j++)
		{
			if(j!=i&&a[j][i]) a[j]^=a[i];
		}
	}
	vector<bool> ans(n+1);
	for(int i=1;i<=n;i++) ans[i]=a[i][n+1];
	return ans;
}
posted @ 2025-07-05 15:25  Lijiangjun4  阅读(68)  评论(3)    收藏  举报