给定 nn 阶方阵 AA 与系数向量 bRn\boldsymbol b \in \mathbb R^n,求解满足

Ax=bA \boldsymbol x = \boldsymbol b

的未知向量 x\boldsymbol x 的问题被称为线性方程式问题
本节讨论计算机中如何求解该类问题

线性代数中,如果 detA0\det A \neq 0,则可以写出解

x=A1b\boldsymbol x = A^{-1} \boldsymbol b

但实际应用上,由于阶数 nn 可能非常巨大,使得逆矩阵计算困难。
此时需要考虑基于 Gauss 消元法的求解

如果系数矩阵是一个上三角或者下三角矩阵,那么求解该方程将极其容易
将系数矩阵 AA 改写为上三角矩阵 UU 与下三角矩阵 LL 的乘积,即 A=LUA = LU,称为 LU 分解
此时,方程可以改写为

L(Ux)=bL(U \boldsymbol x) = \boldsymbol b

我们可以很轻松地求解出 UxU \boldsymbol x,进而求解出 x\boldsymbol x

并且,我们可以要求下三角矩阵 LL 的对角线元素全为 11,以此来保证分解的唯一性

接下来让我们看看如何实现 LU 分解

# 无 Pivoting 的 LU 分解

LU 分解基于 Gauss 消元,而消元法中要求每一步所用的主元都不能为 00
如果出现了这种情况,就需要进行行交换,称为 Pivoting
我们先暂时考虑不会遇到所用主元为 00 的情况,也就是说对前 k 阶主子式都要求非零

det(A1)0,det(A2)0,,det(An)0\det(A_1)\neq 0,\quad \det(A_2)\neq 0,\quad \dots,\quad \det(A_n)\neq 0

假设我们已经实现了 LU 分解,也就是 A=LUA = LU
L=(ij)L = (\ell_{ij}) 进行列向量分解,对 U=(uij)U = (u_{ij}) 进行行向量分解,得到

A=LU=(l1l2ln)(u1Tu2TunT)=k=1nlkukTA = LU = \begin{pmatrix} \boldsymbol l_1 & \boldsymbol l_2 & \cdots & \boldsymbol l_n \end{pmatrix} \begin{pmatrix} \boldsymbol u_1^T \\ \boldsymbol u_2^T \\ \vdots \\ \boldsymbol u_n^T \end{pmatrix} = \sum_{k=1}^n \boldsymbol l_k \boldsymbol u_k^T

我们关注求和项,得到

lkukT=(00kknk)(00ukkukn)=(0000000000kkukkkkukn00nkukknkukn)\begin{aligned} \boldsymbol l_k \boldsymbol u_k^T &= \begin{pmatrix} 0 \\ 0 \\ \vdots \\ \ell_{kk} \\ \vdots \\ \ell_{nk} \end{pmatrix} \begin{pmatrix} 0 & 0 & \cdots & u_{kk} & \cdots & u_{kn} \end{pmatrix} \\ &= \begin{pmatrix} 0 & 0 & \cdots & 0 & \cdots & 0 \\ 0 & 0 & \cdots & 0 & \cdots & 0 \\ \vdots & \vdots & \ddots & \vdots & \ddots & \vdots \\ 0 & 0 & \cdots & \ell_{kk} u_{kk} & \cdots & \ell_{kk} u_{kn} \\ \vdots & \vdots & \ddots & \vdots & \ddots & \vdots \\ 0 & 0 & \cdots & \ell_{nk} u_{kk} & \cdots & \ell_{nk} u_{kn} \end{pmatrix} \end{aligned}

观察不难得到,每一个第 kk 个求和项实际上的贡献在于右下角开始的一个边长 (k)\ell(k) 的方阵
因此,通过这样的逐级分解,就可以在该种简单情况下得到 LU 分解

具体实现方法如下:
无 Pivoting 的 LU 分解算法

  • 关注 AA 的第一行第一列,这部分只能由单一的求和项给出,因此可以确定 1\boldsymbol \ell_1u1T\boldsymbol u_1^T 的值。只需要将 u1T\boldsymbol u_1^T 指定为 AA 的第一行,再相除得到 1\boldsymbol \ell_1
  • A1=A1u1TA_1 = A - \boldsymbol \ell_1 \boldsymbol u_1^T
  • 关注 A1A_1 的第二行第二列,这部分只能由单一的求和项给出,因此可以确定 2\boldsymbol \ell_2u2T\boldsymbol u_2^T 的值。只需要将 u2T\boldsymbol u_2^T 指定为 A1A_1 的第二行,再相除得到 2\boldsymbol \ell_2
  • 以此类推

以上,通过 n1n-1 次除法,n(n21)/3n(n^2-1)/3 次乘法,n(n21)/3n(n^2-1)/3 次减法,就可以得到 LU 分解。全体的计算量为 2n3/32n^3/3,因此时间复杂度为 O(n3)O(n^3)

算法实现

for(k=1; k<=n-1; k++){
    w = 1/a[k][k];
    for(i=k+1; i<=n; i++){
        a[i][k] = a[i][k] * w;
        for(j=k+1; j<=n; j++){
            a[i][j] = a[i][j] - a[i][k] * a[k][j];
        }
    }
}

这样的 LU 分解有一个极其显著的优点:不占用内存

在完成 LU 分解后,就可以快速求解方程了
y=Ux\boldsymbol y = U \boldsymbol x,我们先求解 Ly=bL \boldsymbol y = \boldsymbol b
LL 是一个下三角矩阵,通过每一次向矩阵的右侧移动消元,可以得到 y\boldsymbol y 的值。这个方法叫做 前代 (Forward Substitution)「前進消去」
算法实现

for(i=1; i<=n; i++){
    s = 0;
    for(j=1; j<=i-1; j++){
        s = s + a[i][j] * y[j];
    }
    y[i] = b[i] - s;
}

接下来求解 Ux=yU \boldsymbol x = \boldsymbol yUU 是一个上三角矩阵,通过每一次向矩阵的左侧移动消元,可以得到 x\boldsymbol x 的值。这个方法叫做 回代 (Backward Substitution)「後退代入」
算法实现

for(i=n; i>=1; i--){
    s = 0;
    for(j=i+1; j<=n; j++){
        s = s + a[i][j] * x[j];
    }
    x[i] = (y[i] - s) / a[i][i];
}

最终我们就可以得到方程的解 x\boldsymbol x

# 部分 Pivoting 的 LU 分解