1. 背景介绍
上(下)三角矩阵有许多性质使得计算机可以方便地对它们进行各种线性代数基础运算。例如对三角矩阵的求逆非常简单易理解。因此在计算非奇异矩阵的逆时常将原矩阵 A A A分解为一个下三角矩阵 L L L( L L L矩阵的对角线元素为 1 1 1,对角线以上部分元素为 0 0 0)和一个上三角矩阵 U U U( U U U矩阵对角线以下部分元素为 0 0 0)相乘的形式,再分别求下三角矩阵的逆 L − 1 L^{-1} L−1和上三角矩阵的逆 U − 1 U^{-1} U−1,再将它们相乘获得最终的结果。
理论上利用LU分解求矩阵逆分为三步:
- 对矩阵进行LU分解: A = L U A=LU A=LU;
- 求解线性方程组: L X = I LX=I LX=I, X = L − 1 X=L^{-1} X=L−1;
- 求解线性方程组: U B = L − 1 UB=L^{-1} UB=L−1, B = U − 1 L − 1 = A − 1 B=U^{-1}L^{-1}=A^{-1} B=U−1L−1=A−1;
LU分解可以用于任意宽高的矩阵,本文只讨论方阵。
2. 理论基础
在进行 L U LU LU分解时,需要构造一个下三角矩阵和一个上三角矩阵,这时需要满秩变换来逐向量地将原矩阵变换为上三角矩阵 U U U,并记录变换矩阵 L L L。
2.1 高斯消元
2.1节介绍
L
U
LU
LU分解的关键步骤-高斯消元。假设此时需要将
n
n
n维矩阵
A
A
A的第
k
k
k(
1
≤
k
≤
n
1 \leq k \leq n
1≤k≤n)列向量
a
⋅
,
k
=
[
a
1
,
k
,
a
2
,
k
,
⋯
,
a
n
,
k
]
T
a_{\cdot,k}=\left[a_{1,k},a_{2,k},\cdots,a_{n,k} \right]^T
a⋅,k=[a1,k,a2,k,⋯,an,k]T中的第
k
+
1
k+1
k+1个元素到第
n
n
n个元素变换为
0
0
0,需要构造变换矩阵
M
k
M_k
Mk。首先需要构造高斯消元向量
τ
k
\tau_k
τk:
τ
k
T
=
[
0
,
⋯
,
0
,
τ
k
+
1
,
⋯
,
τ
n
]
,
其
中
τ
i
=
a
i
,
k
a
k
,
k
,
i
=
k
+
1
,
⋯
,
n
\tau_k^T=\left[ 0,\cdots,0,\tau_{k+1},\cdots,\tau_{n} \right],\qquad 其中\tau_{i}= \frac {a_{i,k}}{a_{k,k}},i=k+1,\cdots,n
τkT=[0,⋯,0,τk+1,⋯,τn],其中τi=ak,kai,k,i=k+1,⋯,n
再定义辅助矩阵
E
k
E_k
Ek:
E
k
=
[
0
⋯
1
⋯
0
⋮
⋮
⋮
0
⋯
1
⋯
0
⋮
⋮
⋮
0
⋯
1
⋯
0
]
,
E
k
中
第
k
列
全
为
1
,
其
他
元
素
为
0
E_k= \begin{bmatrix} 0 & \cdots & 1 & \cdots & 0 \\ \vdots & & \vdots & & \vdots \\ 0 & \cdots & 1 & \cdots & 0 \\ \vdots & & \vdots & & \vdots \\ 0 & \cdots & 1 & \cdots & 0 \end{bmatrix},E_k中第k列全为1,其他元素为0
Ek=⎣⎢⎢⎢⎢⎢⎢⎡0⋮0⋮0⋯⋯⋯1⋮1⋮1⋯⋯⋯0⋮0⋮0⎦⎥⎥⎥⎥⎥⎥⎤,Ek中第k列全为1,其他元素为0
于是可以得到高斯消元矩阵
M
k
M_k
Mk:
M
k
=
I
−
τ
k
⊙
E
k
,
(
⊙
表
示
点
乘
)
M_k=I-\tau_k \odot E_k,(\odot表示点乘)
Mk=I−τk⊙Ek,(⊙表示点乘)
最后对
a
⋅
,
k
a_{\cdot,k}
a⋅,k进行高斯消元:
M
k
⋅
a
⋅
,
k
=
[
1
⋯
0
0
⋯
0
⋮
⋱
⋮
⋮
⋱
⋮
0
⋯
1
0
⋯
0
0
⋯
−
τ
k
+
1
1
⋯
0
⋮
⋱
⋮
⋮
⋱
⋮
0
⋯
−
τ
n
0
⋯
1
]
[
a
1
,
k
⋮
a
k
,
k
a
k
+
1
,
k
⋮
a
n
,
k
]
=
[
a
1
,
k
⋮
a
k
,
k
0
⋮
0
]
(2.1.1)
M_k \cdot a_{\cdot,k}= \begin{bmatrix} 1 & \cdots & 0 & 0 & \cdots & 0 \\ \vdots & \ddots & \vdots & \vdots & \ddots & \vdots \\ 0 & \cdots & 1 & 0 & \cdots & 0 \\ 0 & \cdots & -\tau_{k+1} & 1 & \cdots & 0 \\ \vdots & \ddots & \vdots & \vdots & \ddots & \vdots \\ 0 & \cdots & -\tau_{n} & 0 & \cdots & 1 \end{bmatrix} \begin{bmatrix} a_{1,k} \\ \vdots \\ a_{k,k} \\ a_{k+1,k} \\ \vdots \\ a_{n,k} \\ \end{bmatrix}= \begin{bmatrix} a_{1,k} \\ \vdots \\ a_{k,k} \\ 0 \\ \vdots \\ 0 \\ \end{bmatrix} \tag{2.1.1}
Mk⋅a⋅,k=⎣⎢⎢⎢⎢⎢⎢⎢⎢⎡1⋮00⋮0⋯⋱⋯⋯⋱⋯0⋮1−τk+1⋮−τn0⋮01⋮0⋯⋱⋯⋯⋱⋯0⋮00⋮1⎦⎥⎥⎥⎥⎥⎥⎥⎥⎤⎣⎢⎢⎢⎢⎢⎢⎢⎢⎡a1,k⋮ak,kak+1,k⋮an,k⎦⎥⎥⎥⎥⎥⎥⎥⎥⎤=⎣⎢⎢⎢⎢⎢⎢⎢⎢⎡a1,k⋮ak,k0⋮0⎦⎥⎥⎥⎥⎥⎥⎥⎥⎤(2.1.1)
2.2 矩阵三角化
对
n
n
n维方阵
A
A
A的每一列从左到右重复
n
n
n次高斯消元便可以得到上三角矩阵
U
U
U:
M
n
(
M
n
−
1
(
⋯
(
M
1
A
)
)
=
U
(2.1.1)
M_n(M_{n-1}(\cdots(M_1A))=U \tag{2.1.1}
Mn(Mn−1(⋯(M1A))=U(2.1.1)
L
L
L矩阵即是
n
n
n个高斯消元矩阵的乘积:
A
=
(
M
n
M
n
−
1
⋯
M
1
)
−
1
U
=
L
U
(2.1.2)
A=(M_nM_{n-1}\cdots M_1)^{-1}U=LU \tag{2.1.2}
A=(MnMn−1⋯M1)−1U=LU(2.1.2)
L = M 1 − 1 M 2 − 1 ⋯ M n − 1 (2.1.3) L=M_1^{-1}M_2^{-1}\cdots M_n^{-1} \tag{2.1.3} L=M1−1M2−1⋯Mn−1(2.1.3)
2.3 LU分解的一般形式
设非奇异矩阵
A
∈
R
n
×
n
A \in \mathbb{R}^{n\times n}
A∈Rn×n
A
=
[
α
ω
T
v
B
]
,
其
中
α
∈
R
,
ω
T
∈
R
1
×
(
n
−
1
)
,
v
∈
R
(
n
−
1
)
×
1
,
B
∈
R
(
n
−
1
)
×
(
n
−
1
)
A=\begin{bmatrix} \alpha & \omega^{T} \\ v & B \end{bmatrix}, \qquad 其中\alpha \in \mathbb{R}, \omega^T \in \mathbb{R}^{1\times(n-1)}, v \in \mathbb{R}^{(n-1)\times1}, B \in \mathbb{R}^{(n-1)\times(n-1)}
A=[αvωTB],其中α∈R,ωT∈R1×(n−1),v∈R(n−1)×1,B∈R(n−1)×(n−1)
那么高斯消元的第
1
1
1步分解结果是:
[
α
1
ω
1
T
v
1
B
1
]
=
[
1
0
z
1
/
α
1
I
1
]
[
1
0
0
B
1
−
z
1
w
1
T
/
α
1
]
[
α
1
ω
1
T
0
I
1
]
(2.3.1)
\begin{bmatrix} \alpha_1 & \omega_1^{T} \\ v_1 & B_1 \end{bmatrix} = \begin{bmatrix} 1 & 0 \\ z_1/\alpha_1 & I_{1} \end{bmatrix} \begin{bmatrix} 1 & 0 \\ 0 & B_1 - z_1w_1^T/\alpha_1 \end{bmatrix} \begin{bmatrix} \alpha_1 & \omega_1^{T} \\ 0 & I_{1} \end{bmatrix} \tag{2.3.1}
[α1v1ω1TB1]=[1z1/α10I1][100B1−z1w1T/α1][α10ω1TI1](2.3.1)
第
k
,
k
=
2
,
3
,
⋯
,
n
k, k=2,3,\cdots,n
k,k=2,3,⋯,n步可以重复以上步骤分解
B
k
−
1
−
z
k
−
1
w
k
−
1
T
/
α
k
−
1
=
L
k
−
1
U
k
−
1
B_{k-1} - z_{k-1}w_{k-1}^T/\alpha_{k-1} = L_{k-1}U_{k-1}
Bk−1−zk−1wk−1T/αk−1=Lk−1Uk−1。那么:
L
k
−
1
U
k
−
1
=
[
1
0
z
k
/
α
k
I
k
]
[
1
0
0
B
k
−
z
k
w
k
T
/
α
k
]
[
α
k
ω
k
T
0
I
k
]
=
[
1
0
z
k
/
α
k
L
k
]
[
α
k
ω
k
T
0
U
k
]
(2.3.2)
L_{k-1}U_{k-1}= \begin{bmatrix} 1 & 0 \\ z_k/\alpha_k & I_k \end{bmatrix} \begin{bmatrix} 1 & 0 \\ 0 & B_k - z_k w_k ^T/\alpha_k \end{bmatrix} \begin{bmatrix} \alpha_k & \omega_k^{T} \\ 0 & I_{k} \end{bmatrix}= \begin{bmatrix} 1 & 0 \\ z_k/\alpha_k & L_k \end{bmatrix} \begin{bmatrix} \alpha_k & \omega_k^{T} \\ 0 & U_k \end{bmatrix} \tag{2.3.2}
Lk−1Uk−1=[1zk/αk0Ik][100Bk−zkwkT/αk][αk0ωkTIk]=[1zk/αk0Lk][αk0ωkTUk](2.3.2)
3. 算法实现
对于方阵 A A A,假设在已经过 k − 1 k-1 k−1步高斯消元,那么下一步是对 A ( : , k ) A(:, k) A(:,k)进行高斯消元。再解下来便是利用高斯消元继续更新 A ( k + 1 : n , k + 1 : n ) A(k+1:n,k+1:n) A(k+1:n,k+1:n)。由此可以推导出3.1节中的算法。
3.1 朴素的LU分解算法
假设 A ∈ R n × n A \in \mathbb{R}^{n \times n} A∈Rn×n非奇异, L L L矩阵是下三角矩阵(对角线元素为 1 1 1), U U U是上三角矩阵,在进行 L U LU LU分解之后, A A A的下三角部分被 L L L覆盖(不包括对角线),上三角部分被 U U U覆盖(包括对角线)。以下是算法伪代码:
for k = 1 : n − 1 k = 1:n-1 k=1:n−1
ρ = k + 1 : n \rho = k+1:n ρ=k+1:n
A ( ρ , k ) = A ( ρ , k ) / A ( k , k ) A(\rho, k) = A(\rho, k) / A(k, k) A(ρ,k)=A(ρ,k)/A(k,k)
A ( ρ , ρ ) = A ( ρ , ρ ) − A ( ρ , k ) ⋅ A ( k , ρ ) A(\rho, \rho) = A(\rho, \rho) - A(\rho, k) \cdot A(k, \rho) A(ρ,ρ)=A(ρ,ρ)−A(ρ,k)⋅A(k,ρ)
end
朴素的 L U LU LU分解算法在C语言实现时有三层循环,那么排列组合 A 3 3 A^3_3 A33之后一共有 6 6 6个版本。假设矩阵在内存中以数组的形式按行顺序存储,那么一种版本的C语言代码为:
void LU_decomposition(double* A, const size_t n)
{
size_t i, j, k;
double r;
for (k=0; k<n-1; k++)
{
for (i=k+1; i<n; i++)
{
A[i * n + k] = A[i * n + k] / A[k * n + k];
for (j=k+1; j<n; j++)
A[i * n + j] = A[i * n + j] - A[i * n + k] * A[k * n + j];
}
}
}
注意此处编译器优化:
例如在第9行的C语言代码中,会取出A元素的第i * n + k个元素,编译器判断每一次取数的地址是等间隔递增的,因此在程序编译完成后的汇编代码会使用简单的加法替代乘法。在函数上下文中,参数A和n的值在寄存器r0和r1中,假设A[i * n + k]元素的地址已在r6寄存器中:MUL r5, r1, #8(行间隔字节量,双精度浮点数占8字节)
…
ADD r6, r6, r5
FLDD d0, [r6]
…
3.2 Gaxpy vs 外积
在工程应用中,两种计算量相同的算法常常会有不同的数据迁移量。考虑一下
n
×
n
n \times n
n×n的Gaxpy计算和外积计算:
y
=
y
+
A
x
,
y = y + Ax,
y=y+Ax,
A = A + y x T , A ∈ R n × n , x , y ∈ R n × 1 A = A + yx^T, \quad A \in \mathbb{R}^{n \times n}, \quad x, y \in \mathbb{R}^{n\times 1} A=A+yxT,A∈Rn×n,x,y∈Rn×1
比较两种计算量相同算法的计算形式,用 ← \gets ←表示内存和寄存器之间的读写,对于Gaxpy:
r
x
←
x
,
r
y
←
y
r_x \gets x, \quad r_y \gets y
rx←x,ry←y
for
j
=
1
:
n
j = 1:n
j=1:n
r
a
←
A
(
:
,
j
)
r_a \gets A(:, j)
ra←A(:,j)
r
y
=
r
y
+
r
a
r
x
(
j
)
r_y = r_y + r_a r_x(j)
ry=ry+rarx(j)
end
y
←
r
y
y \gets r_y
y←ry
需要 ( 3 + n ) n (3 + n)n (3+n)n次读写操作,而对于外积计算:
r
x
←
x
,
r
y
←
y
r_x \gets x, \quad r_y \gets y
rx←x,ry←y
for
j
=
1
:
n
j = 1:n
j=1:n
r
a
←
A
(
:
,
j
)
r_a \gets A(:, j)
ra←A(:,j)
r
a
=
r
a
+
r
y
r
x
(
j
)
r_a = r_a + r_y r_x(j)
ra=ra+ryrx(j)
A
(
:
,
j
)
←
r
a
A(:, j) \gets r_a
A(:,j)←ra
end
需要 ( 2 + 2 n ) n (2 + 2n) n (2+2n)n次读写操作,强弱悬殊高下立判。
3.3 存取优化-Gaxpy LU分解
Gaxpy的目标是结果以向量的形式来读写。那么这里计划的目标是在第
j
j
j步时计算出
L
L
L矩阵和
U
U
U矩阵的第
j
j
j列。当
j
=
1
j=1
j=1时,由于
U
(
2
:
n
,
1
)
U(2:n,1)
U(2:n,1)的值皆为
0
0
0,比较
A
=
L
U
A = LU
A=LU的第一列可知:
L
(
2
:
n
,
j
)
=
A
(
2
:
n
,
1
)
/
A
(
1
,
1
)
,
U
(
1
,
1
)
=
A
(
1
,
1
)
L(2:n, j) = A(2:n, 1) / A(1, 1), \quad U(1, 1) = A(1, 1)
L(2:n,j)=A(2:n,1)/A(1,1),U(1,1)=A(1,1)
那么假设现在
L
(
:
,
1
:
j
−
1
)
L(:, 1:j-1)
L(:,1:j−1)和
U
(
1
:
j
−
1
,
1
:
j
−
1
)
U(1:j-1, 1:j-1)
U(1:j−1,1:j−1)已知(隐含
A
A
A已知),那么根据以下算式可以求出
U
(
1
:
j
−
1
,
j
)
U(1:j-1, j)
U(1:j−1,j):
A
(
1
:
j
−
1
,
j
)
=
L
(
1
:
j
−
1
,
1
:
j
−
1
)
⋅
U
(
1
:
j
−
1
,
j
)
A(1:j-1, j) = L(1:j-1, 1:j-1) \cdot U(1:j-1, j)
A(1:j−1,j)=L(1:j−1,1:j−1)⋅U(1:j−1,j)
当
U
(
1
:
j
−
1
,
j
)
U(1:j-1, j)
U(1:j−1,j)已求出之后,根据:
A
(
j
:
n
,
j
)
=
L
(
j
:
n
,
1
:
j
)
⋅
U
(
1
:
j
,
j
)
A(j:n, j) = L(j:n, 1:j) \cdot U(1:j, j)
A(j:n,j)=L(j:n,1:j)⋅U(1:j,j)
可以求出
U
(
j
,
j
)
U(j, j)
U(j,j)和
L
(
j
+
1
:
n
,
j
)
L(j+1:n, j)
L(j+1:n,j)。为了缩减Gaxpy LU分解所需要的空间,可以添加
n
n
n维的辅助向量
v
v
v:
v
(
j
:
n
)
=
A
(
j
:
n
,
j
)
−
L
(
j
:
n
,
1
:
j
−
1
)
⋅
U
(
1
:
j
−
1
,
j
)
v(j:n) = A(j:n, j) - L(j:n, 1:j-1) \cdot U(1:j-1, j)
v(j:n)=A(j:n,j)−L(j:n,1:j−1)⋅U(1:j−1,j)
于是
L
(
j
+
1
:
n
,
j
)
=
v
(
j
+
1
:
n
)
/
v
(
j
)
L(j+1:n, j) = v(j+1:n) / v(j)
L(j+1:n,j)=v(j+1:n)/v(j),
U
(
j
,
j
)
=
v
(
j
)
U(j, j) = v(j)
U(j,j)=v(j)。算法伪代码如下:
for
j
=
1
:
n
j = 1:n
j=1:n
if
j
=
1
j = 1
j=1
v
=
A
(
:
,
1
)
v = A(:, 1)
v=A(:,1)
else
a
~
=
A
(
:
,
j
)
\tilde{a} = A(:, j)
a~=A(:,j)
Solve
L
(
1
:
j
−
1
,
1
:
j
−
1
)
⋅
z
=
a
~
(
1
:
j
−
1
)
L(1:j-1, 1:j-1) \cdot z = \tilde{a}(1:j-1)
L(1:j−1,1:j−1)⋅z=a~(1:j−1) for
z
∈
R
j
−
1
z \in \mathbb{R}^{j-1}
z∈Rj−1
U
(
1
:
j
−
1
,
j
)
=
z
U(1:j-1, j) = z
U(1:j−1,j)=z
end
U
(
j
,
j
)
=
v
(
j
)
U(j, j) = v(j)
U(j,j)=v(j)
L
(
j
+
1
:
n
,
j
)
=
v
(
j
+
1
:
n
)
/
v
(
j
)
L(j+1:n, j) = v(j+1:n) / v(j)
L(j+1:n,j)=v(j+1:n)/v(j)
end
下面是C语言代码:
void gaxpyLU_decomposition(double* A, double* v, size_t n)
{
size_t i, j, k;
v[0] = A[0];
for (i=1; i<n; i++)
{
v[i] = A[i * n];
A[i * n] = A[i * n] / v[0];
}
for (i=1; i<n; i++)
{
for (j=1; j<i; j++)
for (k=0; k<j; k++)
A[j * n + i] -= A[j * n + k] * A[k * n + i];
for (j=i; j<n; j++)
{
v[j] = A[j * n + i];
for (k=0; k<i; k++)
v[j] -= A[j * n + k] * A[k * n + i];
}
A[i * n + i] = v[i];
for (j=i+1; j<n; j++)
A[j * n + i] = v[j] / v[i];
}
}
3.4 行方向Gaxpy LU分解(本文独有)
根据内存中矩阵存储方式(按行或列),选择不同的分解方式可以减少数据存取的次数。下面推导按行方向的Gaxpy LU分解。
根据下角矩阵的性质可知:
L
(
i
,
j
)
=
0
,
i
,
j
∈
N
+
,
i
<
j
L(i, j) = 0, \quad i,j \in \mathbb{N^+}, \quad i<j
L(i,j)=0,i,j∈N+,i<j
U ( i , j ) = 0 , i , j ∈ N + , i > j U(i, j) = 0, \quad i, j \in \mathbb{N^+}, \quad i > j U(i,j)=0,i,j∈N+,i>j
计划目标是在第
j
j
j步时计算出第
j
j
j行的值(包括
L
L
L和
U
U
U),当
j
=
1
j=1
j=1时可知:
L
(
1
,
1
)
=
1
,
L
(
1
,
2
:
n
)
=
0
⟹
U
(
1
,
i
)
=
A
(
1
,
i
)
,
i
=
1
,
2
,
…
,
n
L(1, 1) = 1, \quad L(1, 2:n) = 0 \ \Longrightarrow \ U(1, i) = A(1, i), \quad i = 1,2,\dots, n
L(1,1)=1,L(1,2:n)=0 ⟹ U(1,i)=A(1,i),i=1,2,…,n
现在假设
L
(
1
:
j
−
1
,
1
:
j
−
1
)
L(1:j-1, 1:j-1)
L(1:j−1,1:j−1)和
U
(
1
:
j
−
1
,
1
:
j
−
1
)
U(1:j-1, 1:j-1)
U(1:j−1,1:j−1)已知,那么通过
A
(
j
,
1
:
j
−
1
)
=
L
(
j
,
1
:
j
−
1
)
⋅
U
(
1
:
j
−
1
,
1
:
j
−
1
)
A(j, 1:j-1) = L(j, 1:j-1) \cdot U(1:j-1, 1:j-1)
A(j,1:j−1)=L(j,1:j−1)⋅U(1:j−1,1:j−1)
可以求出
L
(
j
,
1
:
j
−
1
)
,
L(j, 1:j-1),
L(j,1:j−1),已知
L
(
j
,
j
)
=
1
L(j, j) = 1
L(j,j)=1,根据:
A
(
j
,
:
)
=
L
(
j
,
1
:
j
)
⋅
U
(
1
:
j
,
:
)
A(j, :) = L(j, 1:j) \cdot U(1:j, :)
A(j,:)=L(j,1:j)⋅U(1:j,:)
从而求出
U
(
j
,
:
)
U(j, :)
U(j,:)。该算法不需要辅助空间,可以进行原地分解,但是在伪代码中为了清晰还是区分了
L
L
L和
U
U
U矩阵,伪代码如下:
A
(
2
:
n
,
1
)
/
=
A
(
1
,
1
)
A(2:n, 1) \ / = A(1, 1)
A(2:n,1) /=A(1,1)
for
j
=
2
:
n
j=2:n
j=2:n
Solve
z
⋅
U
(
1
:
j
−
1
,
1
:
j
−
1
)
z \cdot U(1:j-1,1:j-1)
z⋅U(1:j−1,1:j−1) for
z
∈
R
j
−
1
z \in \mathbb{R}^{j-1}
z∈Rj−1
L
(
j
,
1
:
j
−
1
)
=
z
,
L
(
j
,
j
)
=
1
L(j, 1:j-1)=z, \ L(j,j)=1
L(j,1:j−1)=z, L(j,j)=1
Solve
L
(
j
,
1
:
j
)
⋅
w
=
A
(
j
,
:
)
L(j, 1:j) \cdot w = A(j, :)
L(j,1:j)⋅w=A(j,:) for
w
∈
R
j
w \in \mathbb{R}^{j}
w∈Rj
U
(
j
,
:
)
=
w
U(j, :) = w
U(j,:)=w
end
C语言代码如下:
void rowbased_gaxpyLU_decomposition(double* A, size_t n)
{
size_t i, j, k;
double r;
for (i=1; i<n; i++)
{
A[i * n] /= A[0];
for (j=1; j<i; j++)
{
for (k=0; k<j; k++) A[i * n + j] -= A[i * n + k] * A[k * n + j];
A[i * n + j] /= A[j * n + j];
}
for (j=i; j<n; j++)
for (k=0; k<i; k++) A[i * n + j] -= A[i * n + k] * A[k * n +j];
}
}
4 数值稳定性和主元分解
4.1 稳定性分析
在
L
U
LU
LU分解后若出现数值极大的元素,会导致误差和数值不稳定,原因请参考《Matrix Computations》第4版的3.3节。下面举一个简单的例子解释:
A
=
[
0.0001
1
1
1
]
=
[
1
0
10000
1
]
[
0.0001
1
0
−
9999
]
=
L
U
(4.1.1)
A = \begin{bmatrix} 0.0001 & 1 \\ 1 & 1 \end{bmatrix}= \begin{bmatrix} 1 & 0 \\ 10000 & 1 \end{bmatrix} \begin{bmatrix} 0.0001 & 1 \\ 0 & -9999 \end{bmatrix}=LU \tag{4.1.1}
A=[0.0001111]=[11000001][0.000101−9999]=LU(4.1.1)
A
A
A中数值极小的主元导致了分解后特别大数值的出现,克服此难题的一个方法是进行行交换,若使用行交换矩阵
P
P
P:
P
=
[
0
1
1
0
]
P = \begin{bmatrix} 0 & 1 \\ 1 & 0 \end{bmatrix}
P=[0110]
来左乘矩阵
A
A
A之后再进行
L
U
LU
LU分解:
P
A
=
[
1
1
0.0001
1
]
=
[
1
0
0.0001
1
]
[
1
1
0
0.9999
]
=
L
U
(4.1.2)
PA = \begin{bmatrix} 1 & 1 \\ 0.0001 & 1 \end{bmatrix}= \begin{bmatrix} 1 & 0 \\ 0.0001 & 1 \end{bmatrix} \begin{bmatrix} 1 & 1 \\ 0 & 0.9999 \end{bmatrix}=LU \tag{4.1.2}
PA=[10.000111]=[10.000101][1010.9999]=LU(4.1.2)
可以保证数值的稳定。
若要交换 A A A矩阵的第 i i i列和第 j j j列,只要交换单位矩阵 I I I的第 i i i列和第 j j j列得到矩阵 P P P左乘 A A A矩阵即可。
4.2 列主元 L U LU LU分解
参考2.2节中的高斯消元法,为了保证数值的稳定,在第
j
j
j步消元时,需要左乘行交换矩阵
Π
i
\Pi_i
Πi将第
j
j
j列中从
j
j
j到
n
n
n位置值最大的元素交换到第
j
j
j行,再进行高斯消元,完整的步骤如下:
M
n
−
1
Π
n
−
1
⋯
M
1
Π
1
A
=
U
(4.2.1)
M_{n-1} \Pi_{n-1} \cdots M_1 \Pi_1 A = U \tag{4.2.1}
Mn−1Πn−1⋯M1Π1A=U(4.2.1)
接下来需要找出
L
L
L矩阵,从最简单的关系开始:
P
A
=
L
U
,
P
=
Π
n
−
1
⋯
Π
1
(4.2.2)
PA = LU, \quad P = \Pi_{n-1} \cdots \Pi_1 \tag{4.2.2}
PA=LU,P=Πn−1⋯Π1(4.2.2)
结合式(4.2.1)可以得出以下结果:
M
~
n
−
1
M
~
n
−
2
⋯
M
~
1
P
A
=
U
(4.2.3)
\tilde{M}_{n-1} \tilde{M}_{n-2} \cdots \tilde{M}_{1} P A = U \tag{4.2.3}
M~n−1M~n−2⋯M~1PA=U(4.2.3)
由于
Π
i
A
Π
i
=
A
\Pi_i A \Pi_i= A
ΠiAΠi=A,显然:
M
~
k
=
(
Π
n
−
1
⋯
Π
k
+
1
)
M
k
(
Π
k
+
1
⋯
Π
n
−
1
)
(4.2.4)
\tilde{M}_k=(\Pi_{n-1} \cdots \Pi_{k+1})M_k(\Pi_{k+1} \cdots \Pi_{n-1}) \tag{4.2.4}
M~k=(Πn−1⋯Πk+1)Mk(Πk+1⋯Πn−1)(4.2.4)
由线性代数中所学的知识可知,左乘矩阵是进行行变换,而右乘矩阵则是进行列变换。直观地说,交换单位矩阵
I
I
I的第
i
i
i行和第
j
j
j行得到行交换矩阵
Π
\Pi
Π,用
Π
\Pi
Π左乘任意矩阵的结果便是交换该矩阵的第
i
i
i行和第
j
j
j行。
在进行
L
U
LU
LU分解时为保持数值稳定,会在每一步的高斯消元时进行行交换,并保存行交换的信息在矩阵
P
P
P中。最终分解的公式变为:
P
A
=
L
U
(4.2.5)
PA = LU \tag{4.2.5}
PA=LU(4.2.5)
以下算法伪代码完成了
4.2.5
4.2.5
4.2.5式的计算:
P
=
I
P = I
P=I
for
k
=
1
:
n
−
1
k=1:n-1
k=1:n−1
Find
μ
\mu
μ where
∣
A
(
μ
,
k
)
∣
=
∥
A
(
k
:
n
,
k
)
∥
∞
|A(\mu, k)| = \| A(k:n, k) \|_{\infty}
∣A(μ,k)∣=∥A(k:n,k)∥∞
E
x
c
h
a
n
g
e
(
A
(
μ
,
:
)
,
A
(
k
,
:
)
)
Exchange(A(\mu, :), A(k, :))
Exchange(A(μ,:),A(k,:))
E
x
c
h
a
n
g
e
(
P
(
μ
,
:
)
,
P
(
k
,
:
)
)
Exchange(P(\mu, :), P(k, :))
Exchange(P(μ,:),P(k,:))
if
A
(
k
,
k
)
≠
0
A(k, k) \neq 0
A(k,k)=0
ρ
=
k
+
1
:
n
\rho = k+1:n
ρ=k+1:n
A
(
ρ
,
k
)
=
A
(
ρ
,
k
)
/
A
(
k
,
k
)
A(\rho, k) = A(\rho, k)/A(k, k)
A(ρ,k)=A(ρ,k)/A(k,k)
A
(
ρ
,
ρ
)
=
A
(
ρ
,
ρ
)
−
A
(
ρ
,
k
)
A
(
k
,
ρ
)
A(\rho, \rho) = A(\rho, \rho) - A(\rho, k)A(k, \rho)
A(ρ,ρ)=A(ρ,ρ)−A(ρ,k)A(k,ρ)
end
end