C++高斯消元详解

前言

学了才发现这玩意贼简单虽然据说是复习。

直接来

高斯消元用于解一个线性方程组,就是:
{ a 1 , 1 ⋅ x 1 + a 1 , 2 ⋅ x 2 + ⋯ + a 1 , n ⋅ x n = a 1 , n + 1 a 2 , 1 ⋅ x 1 + a 2 , 2 ⋅ x 2 + ⋯ + a 2 , n ⋅ x n = a 2 , n + 1 ⋯ a n , 1 ⋅ x 1 + a n , 2 ⋅ x 2 + ⋯ + a n , n ⋅ x n = a n , n + 1 \begin{cases} a_{1,1}\cdot x_1+a_{1,2}\cdot x_2+\cdots+a_{1,n}\cdot x_n=a_{1,n+1}\\ a_{2,1}\cdot x_1+a_{2,2}\cdot x_2+\cdots+a_{2,n}\cdot x_n=a_{2,n+1}\\ \quad\quad\quad\quad\quad\quad\quad\quad\quad\cdots\\ a_{n,1}\cdot x_1+a_{n,2}\cdot x_2+\cdots+a_{n,n}\cdot x_n=a_{n,n+1}\\ \end{cases}a1,1x1+a1,2x2++a1,nxn=a1,n+1a2,1x1+a2,2x2++a2,nxn=a2,n+1an,1x1+an,2x2++an,nxn=an,n+1(其中a aa是已知量)


我们接下来都用系数表达式(注意所有的a i , n + 1 a_{i,n+1}ai,n+1与其他a aa的含义有不同,但我们将它们放在一起):
a 1 , 1 a 1 , 2 a 1 , 3 ⋯ a 1 , n − 1 a 1 , n a 1 , n + 1 a 2 , 1 a 2 , 2 a 2 , 3 ⋯ a 2 , n − 1 a 2 , n a 2 , n + 1 a 3 , 1 a 3 , 2 a 3 , 3 ⋯ a 3 , n − 1 a 3 , n a 3 , n + 1 ⋯ a n , 1 a n , 2 a n , 3 ⋯ a n , n − 1 a n , n a n , n + 1 \begin{matrix} a_{1,1} & a_{1,2} & a_{1,3} & \cdots & a_{1,n-1} & a_{1,n} & a_{1,n+1}\\ a_{2,1} & a_{2,2} & a_{2,3} & \cdots & a_{2,n-1} & a_{2,n} & a_{2,n+1}\\ a_{3,1} & a_{3,2} & a_{3,3} & \cdots & a_{3,n-1} & a_{3,n} & a_{3,n+1}\\ & & & \cdots\\ a_{n,1} & a_{n,2} & a_{n,3} & \cdots & a_{n,n-1} & a_{n,n} & a_{n,n+1}\\ \end{matrix}a1,1a2,1a3,1an,1a1,2a2,2a3,2an,2a1,3a2,3a3,3an,3a1,n1a2,n1a3,n1an,n1a1,na2,na3,nan,na1,n+1a2,n+1a3,n+1an,n+1


高斯消元的目的是把原方程变成这样:
1 a 1 , 2 ′ a 1 , 3 ′ ⋯ a 1 , n − 1 ′ a 1 , n ′ a 1 , n + 1 ′ 0 1 a 2 , 3 ′ ⋯ a 2 , n − 1 ′ a 2 , n ′ a 2 , n + 1 ′ 0 0 1 ⋯ a 3 , n − 1 ′ a 3 , n ′ a 3 , n + 1 ′ ⋯ 0 0 0 ⋯ 0 1 a n , n + 1 ′ \begin{matrix} 1 & {a_{1,2}}' & {a_{1,3}}' & \cdots & {a_{1,n-1}}' & {a_{1,n}}' & {a_{1,n+1}}'\\ 0 & 1 & {a_{2,3}}' & \cdots & {a_{2,n-1}}' & {a_{2,n}}' & {a_{2,n+1}}'\\ 0 & 0 & 1 & \cdots & {a_{3,n-1}}' & {a_{3,n}}' & {a_{3,n+1}}'\\ & & & \cdots\\ 0 & 0 & 0 & \cdots & 0 & 1 & {a_{n,n+1}}'\\ \end{matrix}1000a1,2100a1,3a2,310a1,n1a2,n1a3,n10a1,na2,na3,n1a1,n+1a2,n+1a3,n+1an,n+1

那么从下往上依次回代,就能得到所有的x xx值了,代码如下:

for(int i = N; i >= 1; i--) {
    Ans[i] = A[i][N + 1];
    for(int j = N; j >= i + 1; j--)
        Ans[i] -= Ans[j] * A[i][j];
}

所以我们枚举i ii:将a i , i a_{i,i}ai,i变为1 11(①),然后把a j , i   ( j > i ) a_{j,i}\ (j>i)aj,i (j>i)全部变成0 00(②)。

  • 对于①操作,非常简单,把第i ii行的系数全部除以a i , i a_{i,i}ai,i即可;
  • 对于②操作,你要根据加减消元法,用第i ii行的系数去把a j , i   ( i + 1 ≤ j ≤ n ) a_{j,i}\ (i+1\leq j\leq n)aj,i (i+1jn)消为0 00,其他不管。那就给把a i , i a_{i,i}ai,i变成a j , i a_{j,i}aj,ii ii行其他系数按等式的性质依次变),再将第j jj行的每个系数a j , k a_{j,k}aj,k减掉a i , k a_{i,k}ai,k。即:第j jj行的每个系数a j , k a_{j,k}aj,k都减去a j , i a i , i ⋅ a i , k = a j , i ⋅ a i , k \dfrac{a_{j,i}}{a_{i,i}}\cdot a_{i,k}=a_{j,i}\cdot a_{i,k}ai,iaj,iai,k=aj,iai,k

以上就是高斯消元的过程,自己照着代码打一遍,模拟一下过程就能够很好地理解了。


还有一个细节,我们每次在操作①和操作②之前把max ⁡ j = i n a j , i \max\limits_{j=i}^{n}a_{j,i}j=imaxnaj,i所在的那行跟第i ii行换一个位置,可以减少精度误差。


还有一个细节,判断无解:若当前a i , i = 0 a_{i,i}=0ai,i=0,那么高斯消元就无法继续了(操作②无法进行),且x i x_ixi也不能确定,输出No Solution(但并不一定无解)。

代码

洛谷 P3389【模板】高斯消元法

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <vector>

int Read() {
    int x = 0;
    char c = getchar();
    while (c < '0' || c > '9')
        c = getchar();
    while (c >= '0' && c <= '9')
        x = x * 10 + (c ^ 48), c = getchar();
    return x;
}

const double eps = 1e-7;

inline double Abs(const double &x) {
    return x < 0 ? -x : x;
}

inline int Comp(const double &x,const double &y) {
    if (Abs(x-y) < eps)
        return 0;
    return Abs(x) > Abs(y) ? 1 : -1;
}

const int MAXN = 100;

int N;
double A[MAXN + 5][MAXN + 5], Ans[MAXN + 5];

int main() {
    N = Read();
    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 pos = i;
        for(int j = i + 1; j<= N; j++)
            if(Comp(A[j][i], A[pos][i]) == 1)
                pos = j;
        // 这里找A[j][i]最大的是为了减少精度误差 与算法正确性无关
        if(Comp(A[pos][i], 0) == 0) {
            puts("No Solution");
            return 0;
        }
        for(int j = 1; j <= N + 1; j++)
            std::swap(A[pos][j], A[i][j]);
        // 把A[j][i]最大的那行换上去
        double tmp = A[i][i];
        for(int j = i; j <= N + 1; j++)
            A[i][j] /= tmp;
        // 操作一
        for(int j = i + 1; j <= N; j++){
            tmp = A[j][i];
            for(int k = i; k <= N + 1; k++)
                A[j][k] -= A[i][k] * tmp;
        }
        // 操作二
    }
    for(int i = N; i >= 1; i--) {
        Ans[i] = A[i][N + 1];
        for(int j = N; j >= i + 1; j--)
            Ans[i] -= Ans[j] * A[i][j];
    }
    for(int i = 1; i <= N; i++)
        printf("%.2f\n", Ans[i]);
    return 0;
}

版权声明:本文为C20190102原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。