5.2 行列式與反矩陣#
概念說明#
行列式 det(A) 與反矩陣 A⁻¹ 是線性代數課的主角,但在計算上它們的地位很微妙:兩者幾乎都不該用定義去算。
行列式的定義是 n! 項的展開和(Leibniz 公式),20 階矩陣就有 2.4×10¹⁸ 項,直接展開毫無希望。聰明的方法是借道 LU 分解:由 PA = LU 得 det(A) = ±det(U),而上三角矩陣的行列式就是對角線元素乘積,排列的符號在軸選時順手記下即可。行列式是 LU 分解的免費副產品,成本同樣是 O(n³),不需要任何額外的迴圈結構。
反矩陣的常見誤區則是觀念性的:想解 Ax = b 時,許多人先算 A⁻¹ 再算 A⁻¹b。這樣做慢(多一整次矩陣乘法)、又不準(多一輪捨入誤差傳播),因此數值分析界有句格言——"Don't invert that matrix"。需要解系統就直接用 LU solve;需要 A⁻¹ 本身(例如做統計的共變異矩陣推導)才真的去算,而即使要算,正確做法也是對單位矩陣的 n 個列各做一次 LU solve,而非伴隨矩陣法。
本節還要介紹一個重要的診斷量:條件數 κ(A)。它告訴你「這個線性系統的答案可信嗎」,比行列式實用得多——行列式大小與病態程度無關,條件數才是真警報。
演算法步驟#
行列式(經 LU):
- 做 PA = LU 分解,過程中記錄列交換次數 s。
- det(A) = (−1)ˢ × ∏ᵢ uᵢᵢ。
反矩陣(經多次求解):
- 對 A 做 LU 分解(一次)。
- 對單位矩陣每一列 eⱼ(j = 1..n)解 Ax = eⱼ。
- 把 n 個解 xⱼ 排成矩陣,即 A⁻¹。
Python 程式碼範例#
import numpy as np
from fractions import Fraction
# ---- 行列式:LU 法 vs 遞迴餘因子展開 ----
def det_recursive(A):
A = [[Fraction(str(v)) for v in row] for row in A]
n = len(A)
if n == 1:
return A[0][0]
total = Fraction(0)
for j in range(n):
minor = [row[:j] + row[j+1:] for row in A[1:]]
total += (-1)**j * A[0][j] * det_recursive(minor)
return total
def det_lu(A):
A = A.astype(float).copy()
n, sign = len(A), 1
for k in range(n):
p = k + np.argmax(np.abs(A[k:, k]))
if p != k:
A[[k, p]] = A[[p, k]]; sign = -sign
if A[k, k] == 0:
return 0.0
A[k+1:, k:] -= np.outer(A[k+1:, k] / A[k, k], A[k, k:])
return sign * np.prod(np.diag(A))
A = np.array([[2., 1., 1.], [4., -6., 0.], [-2., 7., 2.]])
print("det_lu =", det_lu(A))
print("det 遞迴精確 =", det_recursive(A)) # 精確分數運算
print("np.linalg.det =", np.linalg.det(A))
# ---- 反矩陣的正確與錯誤用法 ----
b = np.array([5., -2., 9.])
x_good = np.linalg.solve(A, b) # 建議做法
x_bad = np.linalg.inv(A) @ b # 不建議做法
print("誤差差異:", np.abs(x_good - x_bad))
# ---- 條件數診斷 ----
H = np.array([[1/(i+j+1) for j in range(8)] for i in range(8)])
print("κ(Hilbert 8x8) =", f"{np.linalg.cond(H):.3e}")
複雜度與誤差分析#
LU 法算行列式:O(n³) 時間、原地 O(n²) 空間;遞迴餘因子法是 O(n!),n = 12 時已超過 4×10⁸ 次乘法,只能當教學工具(上例用 Fraction 展示它可以做精確計算,但只限小矩陣)。反矩陣需要 n 次 O(n²) 代換加一次 O(n³) 分解,總計約 2n³/3 次運算;而 inv(A) @ b 比 solve(A, b) 多花 O(n³) 的矩陣乘法且相對誤差多乘上一個 κ(A) 因子。條件數的解讀準則:κ ≈ 10ᵏ 表示答案大約可信到最後 n − k 位有效數字(雙精度下還要扣掉 ε ≈ 10⁻¹⁶ 的底噪)。實務守則:解系統用 solve、最小平方用 QR 或 SVD、真的需要 A⁻¹ 時先想想能否改寫成矩陣—向量乘積。
練習#
- 把
det_lu改成回傳「符號 + 對數絕對值」(log-det),避免大矩陣時數值溢位;用 100 階隨機矩陣測試det直接算是否回傳 inf,而 log-det 正常。 - 實作「用一次 LU 分解 + n 次代換」計算 A⁻¹,與
np.linalg.inv比較結果與耗時(n = 200, 400, 800)。 - 構造矩陣 A = diag(10⁻⁸, 1, 1),計算 det(A) 與 κ(A)。說明為什麼「行列式接近 0」不能作為奇異的判斷標準,並提出一個更好的判斷方法。