这一章节考察的就是如何求解线性方程组:
或者可以用矩阵来表达:
首先,我们来考察一些特殊形式的方程:
对角方程组的函数形式如下:
显然,可以直接写出x_i = b_i / a_{ii} ,其中,i \in [1, n] 。
下面,我们考察一下一个稍微复杂一点的情况,即下三角矩阵的情况:
此时,问题的难度会多少复杂一点,但是也同样可以直接给出解答如下:
我们可以给出python伪代码如下:
def down_triangle(A, b):
n = len(A)
x = [0 for _ in range(n)]
for i in range(n):
y = b[i]
for j in range(i):
y -= A[i][j] * x[j]
x[i] = y / A[i][i]
return x
同样的,对于上三角函数的情况,我们同样有:
可以解得:
同样的,我们可以给出python伪代码如下:
def up_triangle(A, b):
n = len(A)
x = [0 for _ in range(n)]
for i in range(n-1, -1, -1):
y = b[i]
for j in range(i+1, n):
y -= A[i][j] * x[j]
x[i] = y / A[i][i]
return x
现在,我们来考察一下一般形式的多元线性方程的解法。
其核心的思路其实还是将其转换成三角矩阵然后进行求解。
我们同样给出python伪代码如下:
def gauss_elimination(A, b):
n = len(A)
for i in range(n-1):
for k in range(i+1, n):
c = A[k][i] / A[i][i]
for j in range(i, n):
A[k][j] -= c * A[i][j]
b[k] -= c * b[i]
return up_triangle(A, b)
Gauss-Jordan消元法和上述Gauss消元法本质上是一样的,不过Gauss消元法是将一般矩阵转换成三角矩阵,而Gauss-Jordan消元法是将一般矩阵转换成对角矩阵。
同样的,我们给出python伪代码如下:
def gauss_jordan_elimination(A, b):
n = len(A)
for i in range(n-1):
for k in range(i+1, n):
c = A[k][i] / A[i][i]
for j in range(i, n):
A[k][j] -= c * A[i][j]
b[k] -= c * b[i]
for j in range(n-1, 0, -1):
for i in range(j):
c = A[i][j] / A[j][j]
A[i][j] = 0
b[i] -= c * b[j]
res = [b[i] / A[i][i] for i in range(n)]
return res
直接分解法和上述消元法其实并没有本质上的不同,不过区别在于,直接分解法的核心思路在于基于三角阵的特异性从而不断地尝试将一般矩阵转换为三角阵的形式然后进行求解。
Dolittle分解的思路是说将一般n 阶矩阵A 转换成A = LU ,其中,L 是一个对角元素为1的下三角矩阵,而U 是一个上三角矩阵。
即:
而两个三角矩阵L 和U 都是相对比较容易求解的。
我们易有:
此时,对于待求解方程
即可变为:
更进一步的,我们可以将其拆分为两个方程:
分别解上述两个方程,即可得到最终的解x
:
同样的,我们可以给出python伪代码如下:
def dolittle_solve(A, b):
n = len(A)
L = [[0 for _ in range(n)] for _ in range(n)]
U = [[0 for _ in range(n)] for _ in range(n)]
for k in range(n):
L[k][k] = 1
for j in range(k, n):
U[k][j] = A[k][j]
for r in range(k):
U[k][j] -= L[k][r] * U[r][j]
for i in range(k+1, n):
L[i][k] = A[i][k]
for r in range(k):
L[i][k] -= L[i][r] * U[r][k]
L[i][k] /= U[k][k]
y = [0 for _ in range(n)]
for i in range(n):
y[i] = b[i]
for j in range(i):
y[i] -= L[i][j] * y[j]
x = [0 for _ in range(n)]
for i in range(n-1, -1, -1):
x[i] = y[i]
for j in range(i+1, n):
x[i] -= U[i][j] * x[j]
x[i] /= U[i][i]
return x
Courant分解和Dolittle分解本质上并无区别,依然是将一般的参数矩阵
拆分成两个三个矩阵,不过对于Courant分解而言,
是一个一般的下三角矩阵,而
是对角元素全为1的上三角矩阵。
即:
此时,仿上我们很快就能得到
和
的表达式如下:
同样的,我们可以通过下述函数对最终的解
进行求解:
最终结果表达式如下:
同样给出python伪代码实现如下:
def courant_solve(A, b):
n = len(A)
L = [[0 for _ in range(n)] for _ in range(n)]
U = [[0 for _ in range(n)] for _ in range(n)]
for k in range(n):
for i in range(k, n):
L[i][k] = A[i][k]
for r in range(k):
L[i][k] -= L[i][r] * U[r][k]
U[k][k] = 1
for j in range(k+1, n):
U[k][j] = A[k][j]
for r in range(k):
U[k][j] -= L[k][r] * U[r][j]
U[k][j] /= L[k][k]
y = [0 for _ in range(n)]
for i in range(n):
y[i] = b[i]
for j in range(i):
y[i] -= L[i][j] * y[j]
y[i] /= L[i][i]
x = [0 for _ in range(n)]
for i in range(n-1, -1, -1):
x[i] = y[i]
for j in range(i+1, n):
x[i] -= U[i][j] * x[j]
return x
追赶法本质上只是Courant分解的一种特殊情况而已。
它的解法和Courant分解完全一致,唯一不同的是,这里的A 矩阵是比较特殊的三元矩阵,即:
由此,展开矩阵L 和U 也可以同步修改为:
此时,由于矩阵L 和U 的特殊性,我们可以直接写出最终的解如下:
同样的,LDL^T 分解和上述Dolittle分解和Courant分解其实本质上也差不多,但是不同的是,这里针对的问题是对称正定矩阵。
因此,对于对称正定矩阵,我们总可以有:
此时,我们可以解得:
进而,我们可以将方程Ax = LDL^Tx = b 拆成三步进行完成:
解得:
同样,我们给出python伪代码如下:
def ldl_solve(A, b):
n = len(A)
l = [[0 for _ in range(n)] for _ in range(n)]
d = [0 for _ in range(n)]
for k in range(n):
d[k] = A[k][k]
for r in range(k):
d[k] -= d[r] * l[k][r] * l[k][r]
l[k][k] = 1
for i in range(k+1, n):
l[i][k] = A[i][k]
for r in range(k):
l[i][k] -= d[r] * l[i][r] * l[k][r]
l[i][k] /= d[k]
z = [0 for _ in range(n)]
for i in range(n):
z[i] = b[i]
for j in range(i):
z[i] -= l[i][j] * z[j]
y = [z[i]/d[i] for i in range(n)]
x = [0 for _ in range(n)]
for i in range(n-1, -1, -1):
x[i] = y[i]
for j in range(i+1, n):
x[i] -= l[j][i] * x[j]
return x