跳轉到

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):

  1. 做 PA = LU 分解,過程中記錄列交換次數 s。
  2. det(A) = (−1)ˢ × ∏ᵢ uᵢᵢ。

反矩陣(經多次求解):

  1. 對 A 做 LU 分解(一次)。
  2. 對單位矩陣每一列 eⱼ(j = 1..n)解 Ax = eⱼ。
  3. 把 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⁻¹ 時先想想能否改寫成矩陣—向量乘積。

練習#

  1. 把 det_lu 改成回傳「符號 + 對數絕對值」(log-det),避免大矩陣時數值溢位;用 100 階隨機矩陣測試 det 直接算是否回傳 inf,而 log-det 正常。
  2. 實作「用一次 LU 分解 + n 次代換」計算 A⁻¹,與 np.linalg.inv 比較結果與耗時(n = 200, 400, 800)。
  3. 構造矩陣 A = diag(10⁻⁸, 1, 1),計算 det(A) 與 κ(A)。說明為什麼「行列式接近 0」不能作為奇異的判斷標準,並提出一個更好的判斷方法。