对高斯-约旦消元法的深入研究(包含对其无解、无穷解的特判方法)

不考虑无解、无穷解的情况

附模板题

今天我们先从这个最基本的的高斯-约旦消元法谈起。
(之所以不讲最朴素的高斯消元,是因为高斯-约旦消元法相较于它简化了一个回带的过程)

我们先来解一个方程组玩玩!

x-2y+3z=6
4x-5y+6z=12
7x-8y+10z=21

很简单,对吧?
没错,就像小学那样消元(one by one)

为了方便,我们先将它的系数和等式右边的值转化为一个矩阵

1 -2 3 6
4 -5 6 12
7 -8 10 21

为了解这个方程组,我们希望通过加减消元把它变成如下的形式

1 0 0 a
0 1 0 b
0 0 1 c

因为这样我们就可以一眼看出它的解(x=a,y=b,z=c)

那怎么才能变出来呢?消元呗!
这里我们的方法是以列为单位消元。

首先我们将第一列转化为1 0 0的形式。
(注意:我们通常把未处理的方程中系数绝对值最大的一个作为处理对象来减小误差,因为我们待会儿有一个double类型的除法,可能这会儿你还是不懂,没事,待会儿看了代码就懂了)
所以我们先将矩阵变成这样子

7 -8 10 21
4 -5  6 12
1 -2  3  6

然后利用第一行和第二行,消去第二行的第一个元素(使其变成0)。
这里我们的方法是第二行减去第一行的4/7。
就变成了这个

7   -8   10   21
0  -3/7  2/7   0
1   -2    3    6

同理第三行减去第一行的1/7。

7   -8   10   21
0  -3/7  2/7   0
0  -6/7  11/7  3

现在第一列不就变好了吗?继续按照同样的方式对第二列开刀~~

因为在第二行和第三行中,第三行的第二个元素-6/7比第二行的第二个元素-3/7的绝对值大,所以两行交换

7   -8   10   21
0  -6/7  11/7  3
0  -3/7  2/7   0

接着用第一行减去第二行的-8/(-6/7),即28/3,然后用第三行减去第二行的(-3/7)/(-6/7),即1/2。

7   0   -14/3 -7
0  -6/7  11/7  3
0   0    -1/2 -3/2

现在第二列不也变好了吗?继续按照同样的方式对第三列开刀~~

先用第一行减去第三行的(-14/3)/(-1/2),即28/3,然后用第二行减去第三行的(11/7)/(-1/2),即-22/7。

7   0   0   7
0 -6/7 0 -12/7
0  0 -14/3  -14

好了,现在最后剩下的任务就是解如下的这个方程组了

7x+0y+0z=7
0x+(-6/7)y+0z=-12/7
0x+0y+(-14/3)z=-14

这个大家都会解吧!用每行等式右边的值除以该行的非零系数
最后我们就得到了一组解x=1,y=2,z=3。
(所以高斯消元其实是运用了小学解方程组的加减法的呢。)

还有点儿昏?正常,本人表述不清。
看看代码吧!

Code

#include<bits/stdc++.h>
using namespace std;

const int MAX=120;
const double eps=1e-8;//比较double类型大小时,用于控制精度

int n;
double Matrix[MAX][MAX];//存储系数和等式右边的常数

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n+1;j++) scanf("%lf",&Matrix[i][j]);
	}
	for(int i=1;i<=n;i++)
	{
		int t=i;
		for(int j=i+1;j<=n;j++)
		{
			if(fabs(Matrix[j][i])>fabs(Matrix[t][i])) t=j;//选取未处理行i列上系数绝对值最大的
		}
		if(t!=i)//交换两行
		{
			for(int j=1;j<=n+1;j++) swap(Matrix[t][j],Matrix[i][j]);
		}
		for(int j=1;j<=n;j++)//枚举行
		{
			if(j==i) continue;
			double tmp=Matrix[j][i];
			for(int k=i;k<=n+1;k++)//对该行的每个元素处理
			{
				Matrix[j][k]-=Matrix[i][k]*tmp/Matrix[i][i];
			}
		}
	}
	for(int i=1;i<=n;i++) Matrix[i][n+1]/=Matrix[i][i];//把Matrix[i][i]化为1后,Matrix[i][n+1]就是该方程的解
	for(int i=1;i<=n;i++) printf("%0.2lf\n",Matrix[i][n+1]);
	return 0;
}

关于无解和无穷解的特判

附模板题

前面我们讨论了高斯-约旦消元法的一般情况,那万一无解和无穷解怎么办呢?
我们先来看看什么情况下方程组无解

0*x1+0*x2+...0*xi+...0*xn=a(a!=0)

那什么时候无穷解呢?

0*x1+0*x2+...0*xi+...0*xn=a(a==0)

所以对所有行在处理完后,如果Matrix[i][i]==0&&Matrix[i][n+1]!=0 就是无解,Matrix[i][i]==0&&Matrix[i][n+1]==0就是无穷解。
所以我们就可以在我们上面的程序的最后加一段用于特判的代码

(注:flag== 0为唯一解,flag== 1 为无解 ,flag==2 为无穷解)

for(int i=1;i<=n;i++)
	{
		if(fabs(Matrix[i][i])<eps)
		{
			if(fabs(Matrix[i][n+1]>eps))
			{
				flag=1;
				break;
			}
			else flag=2; 
		}
		else f[i]=Matrix[i][n+1]/Matrix[i][i];
	}
	if(flag==1)
	{
		cout<<"-1";
		return 0;
	}
	if(flag==2)
	{
		cout<<"0";
		return 0;
	}

注意
按照以上两条规律,我们似乎可以特判出无解和无穷解,但请看看当我们的程序遇见了下面这条数据怎么办?

2
0 2 3
0 0 0

按理,由于Matrix[1][1]==0,所以我们就continue到第二行了(如果不continue,除数就为0了!),而Matrix[2][2] 又等于0,我们在最后特判时发现Matrix[1][1] == 0,而Matrix[1][n+1]!=0,所以我们的破程序就傻乎乎的判断为无解,而实际呢?当然是无穷解呀!

但我们将这两行对调后再输入呢?

2
0 0 0
0 2 3

程序又会正确的判断为无穷解,因为Matrix[1][1] ==0 && Matrix[1][N+1]==0

那原因是什么呢?
答:高斯消元的顺序可能会影响到最终的答案,主要是无解与无穷解的情况。

那怎么办呢?
答:这就是高斯-约旦消元法天生的缺陷,但考虑到出题人会拿一组数据卡你,于是我们就可以采用随机化算法,打乱行之间的顺序。
(这样也只能在概率上降低被卡的可能,由于我们用的是srand(time(NULL))来置的随机数,所以你最后能不能过那个数据就取决于组委会测你程序的时间(我也很无语),不能解决实质性问题)

还是看看整个的代码吧!

#include<bits/stdc++.h>
#define random(a,b) (a+rand()%(b-a+1))
using namespace std;

const double eps=1e-8;

int n,flag=0;
double Matrix[120][120],f[120];

int main()
{
	srand(time(NULL));
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n+1;j++) scanf("%lf",&Matrix[i][j]);
	}
	for(int i=1;i<=n-1;i++)//令人无语的随机数算法
	{
		for(int j=i+1;j<=n;j++)
		{
			int t=random(i,j);
			if(t&1)
			{
				for(int k=1;k<=n+1;k++) swap(Matrix[i][k],Matrix[j][k]);
			}
		}
	}
	for(int i=1;i<=n;i++)//高斯-约旦消元法
	{
		int t=i;
		for(int j=i+1;j<=n;j++)
		{
			if(fabs(Matrix[j][i])>fabs(Matrix[t][i])) t=j;
		}
		if(t!=i)
		{
			for(int j=1;j<=n+1;j++) swap(Matrix[i][j],Matrix[t][j]);
		}
		if(fabs(Matrix[i][i])<eps) continue;
		for(int j=1;j<=n;j++)
		{
			if(i==j) continue;
			double tmp=Matrix[j][i]/Matrix[i][i];
			for(int k=i;k<=n+1;k++) Matrix[j][k]-=Matrix[i][k]*tmp; 
		}
	}
	for(int i=1;i<=n;i++)//对其无解、无穷解的特判
	{
		if(fabs(Matrix[i][i])<eps)
		{
			if(fabs(Matrix[i][n+1]>eps))
			{
				flag=1;
				break;
			}
			else flag=2; 
		}
		else f[i]=Matrix[i][n+1]/Matrix[i][i];
	}
	if(flag==1)
	{
		cout<<"-1";
		return 0;
	}
	if(flag==2)
	{
		cout<<"0";
		return 0;
	}
	for(int i=1;i<=n;i++) printf("x%d=%0.2lf\n",i,f[i]);
	return 0;
}

好了,总算大功告成了!

学习厌倦了?点我有更多精彩哦!

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值