AcWing 883:高斯消元解线性方程组 ← 高斯-约旦消元法 + 普通高斯消元法

​​【题目来源】
https://www.acwing.com/problem/content/885/
https://www.luogu.com.cn/problem/P3389

【题目描述】
输入一个包含 n 个方程 n 个未知数的线性方程组。
方程组中的系数为实数。
求解这个方程组。

【输入格式】
第一行包含整数 n。
接下来 n 行,每行包含 n+1 个实数,表示一个方程的 n 个系数以及等号右侧的常数。

【输出格式】
如果给定线性方程组存在唯一解,则输出共 𝑛 行,其中第 𝑖 行输出第 𝑖 个未知数的解,结果保留两位小数。
注意:本题有 SPJ,当输出结果为 0.00 时,输出 -0.00 也会判对。在数学中,一般没有正零或负零的概念,所以严格来说应当输出 0.00,但是考虑到本题作为一道模板题,考察点并不在于此,在此处卡住大多同学的代码没有太大意义,故增加 SPJ,对输出 -0.00 的代码也予以判对。
如果给定线性方程组存在无数解,则输出 Infinite group solutions。
如果给定线性方程组无解,则输出 No solution。

【输入样例】
3
1.00 2.00 -1.00 -6.00
2.00 1.00 -3.00 -9.00
-1.00 -1.00 2.00 7.00

【输出样例】
1.00
-2.00
3.00

【数据范围】
1≤n≤100,所有输入系数以及常数均保留两位小数,绝对值均不超过 100。

【算法分析】
● 普通高斯消元法:目标 → 上三角矩阵(特点:左下角全 0;右上角保留系数;需要回代。)
对角线下方全部清 0,对角线上可以不是 1(当然我们这里做了归一,对角元变成了 1)
\begin{bmatrix} 1& *& *& |& *\\ 0& 1& *& |& *\\ 0& 0& 1& |& * \end{bmatrix} 。此矩阵对应的 LaTex 代码如下所示。     

\begin{bmatrix}
1&  *&  *&  |& *\\ 
0&  1&  *&  |& *\\ 
0&  0&  1&  |& *
\end{bmatrix}

普通高斯消元法(Gaussian Elimination)​ 是一种用于求解线性方程组的经典算法,也是线性代数中最基础的工具之一。它通过一系列初等变换,将增广矩阵化为行阶梯形矩阵或行最简形矩阵,从而求出方程组的解。
普通高斯消元法的本质是系统地消除未知数,将复杂的方程组转化为更容易求解的形式。整个过程分为两个阶段:
(1)前向消元(Forward Elimination):将矩阵化为上三角矩阵(行阶梯形)。
(2)回代求解(Back Substitution):从最后一个方程开始,逐个求解未知数。

● 高斯-约旦消元法:目标 → 最简行阶梯(对角矩阵)(特点:除主对角线是 1,其余系数位置都是 0;最后一列直接就是解,不用回代。)
对角线以外全部清 0,对角元强制为 1。
\begin{bmatrix} 1& 0& 0& |& x_1\\ 0& 1& 0& |& x_2\\ 0& 0& 1& |& x_3 \end{bmatrix} 。此矩阵对应的 LaTex 代码如下所示。     

\begin{bmatrix}
1& 0& 0& |& x_1\\
0& 1& 0& |& x_2\\
0& 0& 1& |& x_3 
\end{bmatrix}

高斯-约旦消元法,属于普通高斯消元法的变种,初等行变换不变,但是目标不是上三角,而是最简行阶梯形(对角矩阵)。即:
主元位置化为 1,本列其余所有元素(主元上面、下面)全部消为 0。
变换完成后,增广矩阵最后一列直接就是方程组的解,不需要回代。

● “高斯-约旦消元法”核心代码解析(https://blog.csdn.net/hnjzsyjyj/article/details/162507345)
(一)选主元

int idx=r;
for(int i=r; i<n; i++) {
    if(fabs(a[i][c])>fabs(a[idx][c])) idx=i;
}

从第 r 行开始,往下找第 c 列上绝对值最大的那个元素。
idx 记录绝对值最大的行号。
为什么要找绝对值最大的?—— 减小浮点误差。

(二)判断是否跳过该列?

if(fabs(a[idx][c])<eps) continue;

如果找到的最大值都接近于 0,说明这一列全是 0。
这个未知数在当前剩余的方程中不存在,直接跳过该列(它可能是自由变量)。
continue 跳过后面的代码,进入下一列,但 r 不变(还没找到新的主元)。

(三)交换到当前行

swap(a[r],a[idx]);

把找到的主元行(第 idx 行)换到第 r 行。
swap(a[r], a[idx]) 交换的是整行,包括常数项。

(四)归一化

double div=a[r][c];
for(int j=c; j<=n; j++) a[r][j]/=div;

div 就是主元的值。
把第 r 行的所有元素(从第 c 列到第 n 列,含常数项)都除以 div。
结果:主元变成了 1,这一行其他元素相应缩放。

(五)消去其他行​​​​​​​

for(int i=0; i<n; i++) {
    if(i!=r && fabs(a[i][c])>eps) {
        double val=a[i][c];
        for(int j=c; j<=n; j++) {
            a[i][j]-=val*a[r][j];
        }
    }
}

对所有行(不只是下面的行),如果第 c 列不为 0,就消掉它。
val = a[i][c] 是第 i 行第 c 列的当前值。
a[i][j] -= val * a[r][j] 就是用第 r 行(归一化后主元为 1)去消。
消完后,第 i 行的第 c 列变成 0。
注意:这里消的是所有行(包括上面的),这是高斯-约旦消元法的特点,直接得到对角矩阵,不用回代。

【算法代码一:高斯‑约旦消元法】

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

const int N=105;
const double eps=1e-8;
double a[N][N];
int n;

int gauss() {
    int r=0;
    for(int c=0; c<n; c++) {
        //1.Select pivot
        int idx=r;
        for(int i=r; i<n; i++) {
            if(fabs(a[i][c])>fabs(a[idx][c])) idx=i;
        }
        if(fabs(a[idx][c])<eps) continue;
        swap(a[r],a[idx]);

        //2.Normalize the current row, and the pivot becomes 1
        double div=a[r][c];
        for(int j=c; j<=n; j++) a[r][j]/=div;

        //3.Eliminate all rows
        for(int i=0; i<n; i++) {
            if(i!=r && fabs(a[i][c])>eps) {
                double val=a[i][c];
                for(int j=c; j<=n; j++) {
                    a[i][j]-=val*a[r][j];
                }
            }
        }
        r++;
    }

    for(int i=r; i<n; i++) { //No solution
        if(fabs(a[i][n])>eps) return -1;
    }
    if(r<n) return 0; //Infinite solutions
    return 1; //Unique solution
}

int main() {
    cin>>n;
    for(int i=0; i<n; i++) {
        for(int j=0; j<=n; j++) {
            cin>>a[i][j];
        }
    }

    int t=gauss();
    if(t==-1) cout<<"No solution\n";
    else if(t==0) {
        printf("Infinite group solutions\n");
    } else {
        for(int i=0; i<n; i++) printf("%.2lf\n",a[i][n]);
    }
    return 0;
}

/*
in:
3
1.00 2.00 -1.00 -6.00
2.00 1.00 -3.00 -9.00
-1.00 -1.00 2.00 7.00

out:
1.00
-2.00
3.00
*/

【算法代码二:普通高斯消元法】

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

const int N=105;
const double eps=1e-8;
double a[N][N];
int n;

int gauss() {
    int r=0;
    for(int c=0; c<n; c++) {
        //1.Select pivot
        int idx=r;
        for(int i=r; i<n; i++) {
            if(fabs(a[i][c])>fabs(a[idx][c])) idx=i;
        }
        if(fabs(a[idx][c])<eps) continue;
        swap(a[r],a[idx]);

        //2.Normalize the current row, and the pivot becomes 1
        double div=a[r][c];
        for(int j=c; j<=n; j++) a[r][j]/=div;

        //3.Eliminate the following rows (Ordinary Gaussian)
        for(int i=r+1; i<n; i++) {
            if(fabs(a[i][c])>eps) {
                double val=a[i][c];
                for(int j=c; j<=n; j++) {
                    a[i][j]-=val*a[r][j];
                }
            }
        }
        r++;
    }

    for(int i=r; i<n; i++) { //No solution
        if(fabs(a[i][n])>eps) return -1;
    }
    if(r<n) return 0; //Infinite solutions

    //Ordinary Gaussian must perform back substitution
    for(int i=n-1; i>=0; i--) {
        for(int j=i+1; j<n; j++) {
            a[i][n]-=a[i][j]*a[j][n];
        }
    }
    return 1; //Unique solution
}

int main() {
    cin>>n;
    for(int i=0; i<n; i++) {
        for(int j=0; j<=n; j++) {
            cin>>a[i][j];
        }
    }

    int t=gauss();
    if(t==-1) cout<<"No solution\n";
    else if(t==0) {
        printf("Infinite group solutions\n");
    } else {
        for(int i=0; i<n; i++) printf("%.2lf\n",a[i][n]);
    }
    return 0;
}

/*
in:
3
1.00 2.00 -1.00 -6.00
2.00 1.00 -3.00 -9.00
-1.00 -1.00 2.00 7.00

out:
1.00
-2.00
3.00
*/



【参考文献】
https://blog.csdn.net/hnjzsyjyj/article/details/162507345
https://www.acwing.com/solution/content/22897/

​

posted @ 2026-11-11 22:57  Triwa  阅读(3)  评论(0)    收藏  举报