「算法笔记」高斯消元

引子

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


例题

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


引导

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

{dx+ey=f (2)ax+by=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
  • 然后,对于所有没有被取出的行 j j ,对其进行消元。具体过程如下:
    • d=aj,iarow,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 <cmath>
      #include <cstdio>
      #include <iostream>
      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 <cmath>
      #include <cstdio>
      #include <iostream>
      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 <cmath>
      #include <cstdio>
      #include <cstring>
      #include <iostream>
      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),首先就应该想到高斯消元。这是信息学竞赛中的常用手段。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值