Acwing 883. 高斯消元解线性方程组
高斯消元 $O(n^3)$
-
通过初等行变换 把 增广矩阵 化为 阶梯型矩阵 并回代 得到方程的解
-
适用于求解 包含$n$ 个方程,$n$ 个未知数的多元线性方程组
例如该方程组
$$
\\left\\{
\\begin{array}{}
&a_{11}x_1+&a_{12}x_2+\ldots+&a_{1n}x_n=&b_1 \\\\
&a_{21}x_1+&a_{22}x_2+\ldots+&a_{2n}x_n=&b_2 \\\\
&\vdots &\vdots &\vdots&\vdots \\\\
&a_{n1}x_1+&a_{n2}x_2+\ldots+&a_{nn}x_n=&b_n \\\\
\\end{array}
\\right.
$$
增广矩阵为
$$
\begin{pmatrix} a_{11} & a_{12} & \cdots &a_{1n} & b_1 \\\\ a_{21} & a_{22} & \cdots &a_{2n} & b_2 \\\\ \vdots & \vdots & \vdots & \vdots& \vdots \\\\ a_{n1} & a_{n2} & \cdots &a_{nn} & b_n \\\\ \end{pmatrix}
$$
- 接下来的所有操作都用该增广矩阵,代替原方程组
前置知识:初等行(列)变换
- 把某一行乘一个非$0$的数 (方程的两边同时乘上一个非$0$数不改变方程的解)
- 交换某两行 (交换两个方程的位置)
- 把某行的若干倍加到另一行上去 (把一个方程的若干倍加到另一个方程上去)
接下来,运用初等行变换,把增广矩阵,变为阶梯型矩阵
阶梯型矩阵
$$
\begin{pmatrix} a_{11} & a_{12} & \cdots &a_{1n} & b_1 \\\\ &a_{2i}&a_{2(i+1)}&a_{2n} & b_2 \\\\ &&&\vdots& \vdots \\\\ &&&a_{nn} & b_n \\\\ \end{pmatrix}
$$
阶梯型矩阵并不一定非要长成这个样子,还可以是好多种其他形式
最后再把阶梯型矩阵从下到上回代到第一层即可得到方程的解
算法步骤
枚举每一列c,
- 找到当前列绝对值最大的一行
- 用初等行变换(2) 把这一行换到最上面(未确定阶梯型的行,并不是第一行)
- 用初等行变换(1) 将该行的第一个数变成 $1$ (其余所有的数字依次跟着变化)
- 用初等行变换(3) 将下面所有行的当且列的值变成 $0$
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 110;
const double eps = 1e-6;
int n;
double a[N][N];
int gauss()
{
int c, r;// c 代表 列 col , r 代表 行 row
for (c = 0, r = 0; c < n; c ++ )
{
int t = r;// 先找到当前这一列,绝对值最大的一个数字所在的行号
for (int i = r; i < n; i ++ )
if (fabs(a[i][c]) > fabs(a[t][c]))
t = i;
if (fabs(a[t][c]) < eps) continue;// 如果当前这一列的最大数都是 0 ,那么所有数都是 0,就没必要去算了,因为它的约束方程,可能在上面几行
for (int i = c; i < n + 1; i ++ ) swap(a[t][i], a[r][i]);//// 把当前这一行,换到最上面(不是第一行,是第 r 行)去
for (int i = n; i >= c; i -- ) a[r][i] /= a[r][c];// 把当前这一行的第一个数,变成 1, 方程两边同时除以 第一个数,必须要到着算,不然第一个数直接变1,系数就被篡改,后面的数字没法算
for (int i = r + 1; i < n; i ++ )// 把当前列下面的所有数,全部消成 0
if (fabs(a[i][c]) > eps)// 如果非0 再操作,已经是 0就没必要操作了
for (int j = n; j >= c; j -- )// 从后往前,当前行的每个数字,都减去对应列 * 行首非0的数字,这样就能保证第一个数字是 a[i][0] -= 1*a[i][0];
a[i][j] -= a[r][j] * a[i][c];
r ++ ;// 这一行的工作做完,换下一行
}
if (r < n)// 说明剩下方程的个数是小于 n 的,说明不是唯一解,判断是无解还是无穷多解
{// 因为已经是阶梯型,所以 r ~ n-1 的值应该都为 0
for (int i = r; i < n; i ++ )//
if (fabs(a[i][n]) > eps)// a[i][n] 代表 b_i ,即 左边=0,右边=b_i,0 != b_i, 所以无解。
return 2;
return 1;// 否则, 0 = 0,就是r ~ n-1的方程都是多余方程
}
// 唯一解 ↓,从下往上回代,得到方程的解
for (int i = n - 1; i >= 0; i -- )
for (int j = i + 1; j < n; j ++ )
a[i][n] -= a[j][n] * a[i][j];//因为只要得到解,所以只用对 b_i 进行操作,中间的值,可以不用操作,因为不用输出
return 0;
}
int main()
{
cin >> n;
for (int i = 0; i < n; i ++ )
for (int j = 0; j < n + 1; j ++ )
cin >> a[i][j];
int t = gauss();
if (t == 0)
{
for (int i = 0; i < n; i ++ ) printf("%.2lf\n", a[i][n]);
}
else if (t == 1) puts("Infinite group solutions");
else puts("No solution");
return 0;
}
高斯消元代码中最难理解的就是往回推的最后一步
这里的 i,j 其实代表的是 $x_i$和$x_j$ ,对于 i 行中的 $x_j$ 对应的系数是
a[i][j]
,而a[i][n]
则代表了此时 i 行的多项式的结果,为了得到$x_i$的解($x_i$的系数是1)就需要此时的结果减去后面的多项式的和即,其中
a[j][n]
代表$x_j$的的值(因为j行只有$x_j$和结果这两项)tql,终于懂了
+1
##### tql,终于搞懂了
orz
Orz
Orz
orz
orz
嗷呜嗷呜
哥们,for (int i = n; i >= c; i – ) a[r][i] /= a[r][c];这句话我把i>=c改成i>=0也ac了,是为什么呀???
emmm… 可能后面 a 数组中的值是 1? 尝试证明一下
我也不清楚啊
可能
因为c递增,当前行c-1列之前的已经都是0了,而且后面的运算不会再涉及c-1列之前的值
最后一列存储唯一解的结果
orz
补充:r表示消到了哪一行,c表示消到了哪一列。
/ 前面的过程做完之后,保证了是一个每行第一个都为1的行阶梯矩阵,但是每一行并不是只有一个,还可能存在后面几个,比如为:
[1 3 0 5
0 1 2 3
0 0 1 2],
要求得最后的答案,即最后一列,就要用最后一列减去除了每一列xi之外的所有其他数,比如对于第二行来说,表达的就是x2+2x3=3,
那么x2=3-2x3,因为x2的系数为1。又因为每一列都表示的是一个x,整个第三列都是x3,由第三行知,x3=2,所以说a[j][n]表示的就是
xj的值,因为n为最后一列,j为i+1,表示i的下一行,所以说a[j][n]就是xj的值,a[i][j]表示的xj的系数,所以减去就得到xi的值
/
对中间倒推过程的理解
orz
or2
orz
懂了
###tql
你的咋是翘臀
写得漂亮
nice
话说这组数据:
3
5 1 5 3
5 4 1 3
5 5 2 3
好像过不了,会输出-0.00 。。。
改成
然后就过了。。。。。
秀
对的,现在过不了了
这个位置的for循环j 是不是可以正着循环?
我觉得可以
不可以,正着a[i][c]改变后会影响后面的计算,必须倒着
不可以,正着a[i][c]改变后会影响后面的计算,必须倒着
现在题解有点问题了,就是可能在判断无限解,与无解处,a i n可能小于零导致本应无解,变成了无限解,加一个fabs即可
有个错误竟然这么多人没一个发现,虽然只是注释。// 从后往前,当前行的每个数字,都减去对应列 * 行首非0的数字,这样就能保证第一个数字是 a[i][c] -= 1*a[i][c]; 是a[i][c]而不是a[i][0]
数据增强了,-0.00过不了,加上下面这段就行了
现在你的题解好像过不去
请问为什么for (int i = c; i < n + 1; i ++ ) swap(a[t][i], a[r][i])这里要把每一行都换一次轮到最上方呢,直接和最上面的交换不行吗
我的意思是说直接swap(a[t], a[r])这样可以吗
因为同一行中c列之前的都是0,只要从c开始交换就可以了
赞
if (r < n)// 说明剩下方程的个数是小于 n 的,说明不是唯一解,判断是无解还是无穷多解
这句话 有问题 r 应该为遍历到的行数 如果 判断 当前行 以下 系数都为0了 那么 r 会停留下来 直到最后 但是 这一行的值却不为0 所以 无解
orz
if (fabs(a[t][c]) < eps) continue;不太理解,这样r不就没有++了吗?
r不用 c即可
关于:if (fabs(a[t][c]) < eps) continue;的理解
假设 c表示列,r表示行,此时我们进行到了 c=2 r=2
1 0 2 3
0 0 3 2
0 0 2 3
你会发现此时r行之下 的c列的绝对值最大值就是0.
说明此时的第c列已经化简好了那么,那么我们就不需要在进行下面的化简操作,
但是此时我们的第r行不用变,此时c加1 那么我们就接着从 第二行 第三列开始找绝对值最大的数。
如果我们的r向后移动了,那么此时我们的第2行是没有化简的,这显然是不对的。
以此为例,r如果向后移动了,此时r=3,c=3 但是此时我们的 第二行 第三个数 是 3 并不是1 这种最简的形态。
hh谢谢,之前已经搞懂了,感谢dl。
请问 “a[i][n] 代表 b_i ,即 左边=0,右边=b_i,0 != b_i“,中这个 b_i 是什么意思
比如 $0=5$ 出现这种式子的时候,就是无解
感谢
nice
为啥要用eps代表0呢 不能直接用0吗
因为浮点数不能直接判断是不是等于0,必须要设定一个精度eps
很详细,赞赞赞
为什么是找绝对值最大的一行?
初等行变换里面有一条是 用一个非0的数乘任意一行 对最终的解的结果是没有变化的 所以选择绝该列对值最大对应的行
不是只要不为0的一行就可以了吗
据说是因为找最大的那一行精度会更准
嗷好的