「算法笔记」高斯消元

引子

高斯消元 (gauss elimination) ( g a u s s   e l i m i n a t i o n ) 是线性代数中的一个算法。可用来求解线性方程组,并可以求出矩阵的秩,以及求出可逆方阵的逆矩阵。
高斯消元法的原理是:用初等行变换把增广矩阵转换为行阶梯阵,然后回代求出方程的解。


例题

求解一个 n n 元线性方程组。


引导

想一想我们是如何求解二元一次方程组的。

{ax+by=c (1)dx+ey=f (2) { d x + e y = f   ( 2 ) a x + b y = c   ( 1 )

保证 a,b,c,d,e,f>0 a , b , c , d , e , f > 0 aebd a e ≠ b d
       (1)ad×(2)               ( 1 ) − a d × ( 2 )
=>(baed) y=cafd => ( b − a e d )   y = c − a f d
=>y=cdafbdae => y = c d − a f b d − a e
代入 (1) ( 1 ) x=cebfbdae x = c e − b f b d − a e

消元,然后带入,对不对?

现在我们就要使用编程模拟这个过程。


算法流程

方程组为:
a1,1x1+a1,2x2+...+a1,nxn=b1 a 1 , 1 x 1 + a 1 , 2 x 2 + . . . + a 1 , n x n = b 1
a2,1x1+a2,2x2+...+a2,nxn=b2 a 2 , 1 x 1 + a 2 , 2 x 2 + . . . + a 2 , n x n = b 2
... . . .
an,1x1+an,2x2+...+an,nxn=bn a n , 1 x 1 + a n , 2 x 2 + . . . + a n , n x n = b n

  • 进行第 i i 次消元时,先取出任意一行,记为 row r o w
  • 然后,对于所有没有被取出的行 j j ,对其进行消元。具体过程如下:
    • d=aj,iarow,i d = a j , i a r o w , i
    • bjbjd×bi b j ← b j − d × b i
    • aj,kaj,kd×arow,k a j , k ← a j , k − d × a r o w , k
  • 等到消元完毕时,将 xn,n1,...,1 x n , n − 1 , . . . , 1 依次代入,即可求出方程的解。
    算法时间复杂度 O(n3) O ( n 3 )

优化

每一次找一个使得 |arow,i| | a r o w , i | 最大的 row r o w ,可以有效减小精度误差。


代码

//  高斯消元解 n 元线性方程组
#include 
#include 
#include 
const double eps = 1e-18;
const int maxn = 105;
using namespace std;
typedef double db;
typedef long double ldb;
db t;
int n;
ldb a[maxn][maxn], b[maxn], x[maxn];
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            scanf("%lf", &t);
            a[i][j] = t;
        }
        scanf("%lf", &t);
        b[i] = t;
    }
    for (int i = 1; i <= n; i++) {
        int ind = i;
        for (int j = i + 1; j <= n; j++) {
            if (abs(a[j][i]) > abs(a[ind][i])) {
                ind = i;
            }
        }
        if (abs(a[ind][i]) < eps) {
            puts("-1");
            return 0;
        }
        if (ind != i) {
            swap(b[i], b[ind]);
            for (int j = 1; j <= n; j++) {
                swap(a[i][j], a[ind][j]);
            }
        }
        for (int j = i + 1; j <= n; j++) {
            double d = a[j][i] / a[i][i];
            b[j] -= b[i] * d;
            for (int k = i; k <= n; k++) {
                a[j][k] -= a[i][k] * d;
            }
        }
        /*
        for (int j = 1; j <= n; j++) {
            for (int k = 1; k <= n; k++) {
                printf("%lf ", a[j][k]);
            }
            printf("%lf\n", b[j]);
        }
        */
    }
    for (int i = n; i > 0; i--) {
        for (int j = i + 1; j <= n; j++) {
            b[i] -= x[j] * a[i][j];
        }
        x[i] = b[i] / a[i][i];
    }
    for (int i = 1; i <= n; i++) {
        t = x[i];
        printf("x[%d] = %.6lf;\n", i, t);
    }
    return 0;
}

练习

n×n n × n 的矩阵的行列式。


提示

只要将矩阵转化成上三角矩阵即可。


代码

//  高斯消元求矩阵行列式
#include 
#include 
#include 
const int maxn = 105;
const double eps = 1e-6;
typedef long double ldb;
using namespace std;
double t;
int n;
ldb a[maxn][maxn];
double read() {
    scanf("%lf", &t);
    return t;
}
void write(ldb x) {
    t = x;
    printf("%lf;\n", t);
}
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            a[i][j] = read();
        }
    }
    ldb x = 1;
    for (int i = 1; i <= n; i++) {
        //  为了减小精度误差,取最大的系数
        int ind = i;
        for (int j = i + 1; j <= n; j++) {
            if (abs(a[ind][i]) < abs(a[j][i])) {
                ind = j;
            }
        }
        if (ind != i) {
            x = -x;
            for (int j = 1; j <= n; j++) {
                swap(a[i][j], a[ind][j]);
            }
        }
        if (abs(a[i][i]) < eps) {
            puts("0");
            return 0;
        }
        for (int j = i + 1; j <= n; j++) {
            ldb d = a[j][i] / a[i][i];
            for (int k = i; k <= n; k++) {
                a[j][k] -= d * a[i][k];
            }
        }
        printf("Step #%d of Gaussian Elimination\n", i);
        for (int j = 1; j <= n; j++) {
            for (int k = 1; k <= n; k++) {
                t = a[j][k];
                printf("%lf%c", t, k == n ? '\n' : ' ');
            }
        }
    }
    for (int i = 1; i <= n; i++) {
        x *= a[i][i];
    }
    printf("Det(A) = ");
    write(x);
    return 0;
}

高斯消元与动态规划

例题

【LightOJ1511】Snakes and Ladders

题解

如果没有蛇,概率动态规划就无后效性,推一下状态转移方程即可。
如果有蛇,我们就视 dp d p 值为未知数,列出一个 n n 元的线性方程组,用高斯消元法来求解即可。

代码
#include 
#include 
#include 
#include 
typedef long double ldb;
const ldb eps = 1e-6;
const int maxn = 105;
const int n = 100;
using namespace std;
int tasks, m, to[maxn];
ldb a[maxn][maxn], b[maxn], x[maxn];
int main() {
    scanf("%d", &tasks);
    for (int task = 1; task <= tasks; task++) {
        memset(to, 0, sizeof(to));
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                a[i][j] = 0;
            }
            b[i] = 0, x[i] = 0;
        }
        scanf("%d", &m);
        for (int u, v, i = 1; i <= m; i++) {
            scanf("%d %d", &u, &v);
            to[u] = v;
        }
        for (int i = 1; i <= n; i++) {
            if (i == n) {
                a[i][i] = 1;
            } else if (to[i]) {
                a[i][i] = 1, a[i][to[i]] = -1;
            } else if (i + 6 <= 100) {
                for (int j = i + 1; j <= i + 6; j++) {
                    a[i][j] = -1;
                }
                a[i][i] = 6, b[i] = 6;
            } else {
                a[i][i] = 100 - i;
                for (int j = i + 1; j <= n; j++) {
                    a[i][j] = -1;
                }
                b[i] = 6;
            }
        }
        for (int i = 1; i <= n; i++) {
            int ind = i;
            for (int j = i + 1; j <= n; j++) {
                if (abs(a[j][i]) > abs(a[ind][i])) {
                    ind = j;
                }
            }
            if (i != ind) {
                swap(b[i], b[ind]);
                for (int j = 1; j <= n; j++) {
                    swap(a[i][j], a[ind][j]);
                }
            }
            for (int j = i + 1; j <= n; j++) {
                ldb d = a[j][i] / a[i][i];
                b[j] -= b[i] * d;
                for (int k = 1; k <= n; k++) {
                    a[j][k] -= a[i][k] * d;
                }
            }
        }
        double tmp;
        for (int i = n; i >= 1; i--) {
            for (int j = i + 1; j <= n; j++) {
                b[i] -= a[i][j] * x[j];
            }
            x[i] = b[i] / a[i][i];
            tmp = x[i];
        }
        tmp = x[1];
        printf("Case %d: %.10lf\n", task, tmp);
    }
    return 0;
}
/*
100
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
*/
P.S.

其实,这种类型的题目有时也可以用迭代的方法来做,最后往往也能达到题目要求的精度。


总结

碰到一些有后向性的概率动态规划 (n500) ( n ≤ 500 ) ,首先就应该想到高斯消元。这是信息学竞赛中的常用手段。

你可能感兴趣的:(算法笔记,高斯消元,概率动态规划)