高斯消元学习体会
wjyyy
2018-05-03 19:18:10
高斯消元就像初中学的多元一次方程组(且当$n$元时有$n$个方程)的一种解法。
## 一、解法
其中经过多次($n^3$)消元后可呈倒三角形式
$\begin{bmatrix} 1 & -1 & 3\\ 0 & 8 & 5\\ 0 & 0 & 4 \end{bmatrix}$ $\begin{bmatrix} 2\\ 3\\ 8 \end{bmatrix}$
这样将$4x_3=8\Rightarrow x_3=2$带入②和①得
$\begin{bmatrix} 1 & -1 & 0\\ 0 & 8 & 0\\ 0 & 0 & 0 \end{bmatrix}$ $\begin{bmatrix} -4\\ -7\\ 0\end{bmatrix}$
可知$8x_2=-7\Rightarrow x_2=-\frac{8}{7}$
再次迭代可得
$\begin{bmatrix} 1 & 0 & 0\\ 0 & 0 & 0\\ 0 & 0 & 0 \end{bmatrix}$ $\begin{bmatrix} -\frac{36}{7}\\ 0\\ 0\end{bmatrix}$
$x_1=-\frac{36}{7}$就解出来了。
为了解决问题方便,我们可以将矩阵坐标为$i,j$的位置化为1,即
$\begin{bmatrix} 1 & -1 & 3\\ 0 & 8 & 5\\ 0 & 0 & 4 \end{bmatrix}$ $\begin{bmatrix} 2\\ 3\\ 8 \end{bmatrix}\rightarrow\begin{bmatrix} 1 & -1 & 3\\ 0 & 1 & \frac{5}{8}\\ 0 & 0 & 1 \end{bmatrix}$ $\begin{bmatrix} 2\\ \frac{3}{8}\\ 2 \end{bmatrix}$
就可以看得更清楚,让程序直接从$i+1$做计算
## 二、精度
为了保证精度,我们可以让小的去扩大,而不是去对大的除,同时要保证被除数不能是0,防止RE(这里是因为当小数点后8位均为0时已无意义,甚至会爆double)
## 三、判断
判断是否有唯一解
如果做到某一列发现这一列的系数全部为0,说明所在这一列的$x$为“自由元”,则不为唯一解,这个$x$可以取任意值。
如果做完后发现系数有矛盾即消不掉的系数,则该矩阵无解。
## 四、其他
如上这种做法,因为计算机不能存储分数(可以手动实现)精度丢失过大,但在一般的题中已经够用。(见代码高斯消元v1.0)
为防止~~变态题~~精度误差太大,做除法之前可以求出这一列的最小公倍数,互相消元时就不会出现小数了(还是要避免0的情况出现),但是这样就不能保证$i,j$位置上的数是1了,迭代回溯时要注意这一点。(代码待补充)
### 高斯消元v1.0
```cpp
#include<cstdio>
#include<algorithm>
double abs(double x)
{
if(x<0)
return -x;
return x;
}
double a[111][111];
int n;
bool gs()
{
for(int i=1;i<=n;i++)
{
double maxx=a[i][i];
int tmp=i;
for(int j=i;j<=n;j++)
if(abs(a[j][i])>abs(maxx))
{
maxx=a[j][i];
tmp=j;
}
if(abs(maxx)<=0.0000001)
return false;
if(i<n)
std::swap(a[i],a[tmp]);
double upd=a[i][i];
for(int j=i;j<=n+1;j++)
a[i][j]/=upd;
for(int k=i+1;k<=n;k++)
{
upd=a[k][i];
for(int j=i;j<=n+1;j++)
a[k][j]-=upd*a[i][j];
}
}
for(int i=n;i>=1;i--)
{
double upd=a[i][n+1];
for(int j=i-1;j>=1;j--)
{
a[j][n+1]-=a[j][i]*upd;
a[j][i]=0;
}
}
return true;
}
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]);
if(gs())
{
for(int i=1;i<=n;i++)
printf("%.2lf\n",a[i][n+1]);
}
else
puts("No Solution");
return 0;
}
```