ガウスの消去法
この章の目標
ガウスの消去法の前進消去と後退代入により連立一次方程式を解く手順を理解する。部分ピボット選択・完全ピボット選択の仕組み、有理数演算での高さ最小ピボット、LU分解との等価性を把握する。
前提知識
- 行列とベクトルの基本演算
- 連立一次方程式の基本概念
1. 概要
ガウスの消去法(Gaussian Elimination)は、連立一次方程式 $A\mathbf{x} = \mathbf{b}$ を解く最も基本的な直接法である。拡大係数行列 $[A|\mathbf{b}]$ に行基本変形を施して上三角行列に変換し(前進消去)、その後、後退代入により解を求める。
C. F. Gauss (1777--1855) にちなんで名付けられたが、同様の手法は紀元前の中国の『九章算術』にも見られる。
2. 前進消去
$n \times n$ の係数行列 $A$ と右辺ベクトル $\mathbf{b}$ から拡大係数行列を構成し、第 $k$ 列($k = 1, 2, \ldots, n-1$)について、$k$ 行目より下の全ての行から $k$ 行目の定数倍を引いて $a_{ik}$ を消去する。
消去の乗数
第 $k$ ステップにおいて、$i > k$ に対し乗数 $m_{ik}$ を
と定め、第 $i$ 行から第 $k$ 行の $m_{ik}$ 倍を引く:$a_{ij}^{(k+1)} = a_{ij}^{(k)} - m_{ik}\, a_{kj}^{(k)}$。
3. 後退代入
前進消去により上三角形 $U\mathbf{x} = \mathbf{c}$ が得られたら、最後の行から順に解を求める:
$$x_n = \dfrac{c_n}{u_{nn}}, \qquad x_k = \dfrac{1}{u_{kk}}\!\left(c_k - \displaystyle\sum_{j=k+1}^{n} u_{kj}\, x_j\right), \quad k = n{-}1, \ldots, 1$$4. 計算過程のアニメーション例
具体的な3変数の連立方程式で、前進消去と後退代入の全過程をステップごとに確認する。
拡大係数行列による計算例
上記のアニメーションと同じ連立方程式を、拡大係数行列の行操作として表記する。
前進消去:
$$\left[\begin{array}{ccc|c} 2 & 1 & -1 & 8 \\ -3 & -1 & 2 & -11 \\ -2 & 1 & 2 & -3 \end{array}\right]$$$R_2 \leftarrow R_2 + \dfrac{3}{2}R_1$, $R_3 \leftarrow R_3 + R_1$:
$$\left[\begin{array}{ccc|c} 2 & 1 & -1 & 8 \\ 0 & \dfrac{1}{2} & \dfrac{1}{2} & 1 \\ 0 & 2 & 1 & 5 \end{array}\right]$$$R_3 \leftarrow R_3 - 4R_2$:
$$\left[\begin{array}{ccc|c} 2 & 1 & -1 & 8 \\ 0 & \dfrac{1}{2} & \dfrac{1}{2} & 1 \\ 0 & 0 & -1 & 1 \end{array}\right]$$後退代入: 3行目から $x_3 = -1$、2行目に代入して $x_2 = 3$、1行目に代入して $x_1 = 2$。
部分ピボット選択付き C++ 実装例
#include <vector>
#include <cmath>
#include <stdexcept>
std::vector<double> gaussianElimination(
std::vector<std::vector<double>> A,
std::vector<double> b)
{
const int n = static_cast<int>(A.size());
for (int k = 0; k < n - 1; ++k) {
// 部分ピボット選択: k 列の絶対値最大行を探す
int pivot = k;
for (int i = k + 1; i < n; ++i)
if (std::abs(A[i][k]) > std::abs(A[pivot][k])) pivot = i;
if (pivot != k) { std::swap(A[k], A[pivot]); std::swap(b[k], b[pivot]); }
if (std::abs(A[k][k]) < 1e-12)
throw std::runtime_error("singular matrix");
// 消去
for (int i = k + 1; i < n; ++i) {
double m = A[i][k] / A[k][k];
for (int j = k; j < n; ++j) A[i][j] -= m * A[k][j];
b[i] -= m * b[k];
}
}
// 後退代入
std::vector<double> x(n);
for (int i = n - 1; i >= 0; --i) {
double s = b[i];
for (int j = i + 1; j < n; ++j) s -= A[i][j] * x[j];
x[i] = s / A[i][i];
}
return x;
}
実装上のアドバイス
- 行交換のコスト: 大規模行列では行ベクトル全体の物理コピーが高価。代わりに行インデックスの順列 $\pi$ を保持して間接参照する(
A[π[i]][j])方式が標準。 - 特異判定の閾値: $|a_{kk}| < \tau \cdot \max_i |a_{ik}|$($\tau$ は計算機イプシロンの 10〜100 倍程度)で特異とみなす。絶対値だけで判断すると行列のスケールに左右される。
- ブロック化: 性能が重要な場合、ブロック分割(LAPACK の
dgetrf)と BLAS-3 への委譲でキャッシュ効率を稼ぐ。教科書的な要素単位ループは小規模向け。
5. ピボット選択
前進消去の第 $k$ ステップにおいて、ピボット $a_{kk}^{(k)}$ がゼロまたはゼロに近い場合、ゼロ除算や大きな丸め誤差が生じる。これを避けるためにピボット選択を行う。
5.1 部分ピボット選択(Partial Pivoting)
第 $k$ 列の $k$ 行目以下で絶対値が最大の要素を選び、行の交換を行う。行の交換は連立方程式の式の順番を入れ替えるだけなので、変数の対応関係は変わらない。実用上はほとんどの場合これで十分であり、LAPACKなどの標準的な数値計算ライブラリでも採用されている。
5.2 完全ピボット選択(Complete Pivoting)
完全ピボット選択では、第 $k$ ステップにおいて $k$ 行目以下かつ $k$ 列目から右の全要素から絶対値最大のものを探し、行と列の両方を交換する。
列の交換は変数の順序の入れ替えに相当する。例えば第2列と第3列を交換すると、$x_2$ と $x_3$ の役割が入れ替わる。したがって、全ての消去が終わった後に列の交換履歴を逆順にたどって、変数の順序を元に戻す必要がある。
部分 vs 完全:完全ピボット選択は数値的安定性がわずかに優れるが、最大要素の探索に $O(n^2)$ の追加コストがかかる。部分ピボット選択の探索コストは $O(n)$ であり、実用上は部分ピボット選択で十分な場合がほとんどである。
5.3 有理数演算でのピボット選択
上記のピボット選択戦略は浮動小数点演算を前提としている。有理数(多倍長整数による分数)で厳密にガウスの消去法を実行する場合、丸め誤差は発生しないが、代わりに中間値の膨張(coefficient swell)が問題となる。消去の過程で分子・分母の桁数が爆発的に増大し、計算が極端に遅くなることがある。
この問題を軽減するために、浮動小数点とは逆に、絶対値が最小の(ゼロでない)要素をピボットに選ぶ戦略が有効である。より正確には、有理数 $p/q$(既約分数)の高さ $H(p/q) = \max(|p|, |q|)$ が最小の要素をピボットに選ぶことで、消去の乗数の分子・分母が小さくなり、中間値の膨張を抑えられる。
補足:有理数のガウスの消去法では、ピボット選択以外にも、各ステップで中間値を既約分数に約分する、あるいはBareissのアルゴリズム(分数を使わず整数のまま消去する手法)を用いるなど、中間値膨張を避ける工夫がある。
6. 計算量
$n$ 元連立一次方程式に対するガウスの消去法の計算量は次の通りである。
- 前進消去: $\dfrac{2}{3}n^3 + O(n^2)$ 回の浮動小数点演算
- 後退代入: $n^2 + O(n)$ 回の浮動小数点演算
- 全体: $O(n^3)$
7. LU分解との関係
ガウスの消去法の前進消去は、行列 $A$ を下三角行列 $L$ と上三角行列 $U$ の積に分解する操作と等価である:
$$A = LU$$消去の乗数 $m_{ik}$ が $L$ の対角より下の要素となり、消去後の上三角行列が $U$ である。一度 $LU$ 分解を行えば、異なる右辺 $\mathbf{b}$ に対して $O(n^2)$ で解が求まるため、同じ係数行列で複数の右辺を解く場合に効率的である。
8. 注意点
- 要素成長率と数値安定性: 部分ピボット選択付きガウスの消去法の数値安定性は、消去過程での要素成長率 $\rho_n = \max_{i,j,k} |a_{ij}^{(k)}| / \max_{i,j} |a_{ij}|$ に左右される。理論最悪値は $\rho_n \le 2^{n-1}$ で Wilkinson 行列により達成されるが、実用上の行列ではほぼ常に $\rho_n = O(n)$ に収まる。完全ピボットでは $\rho_n \le n^{1/2} (2 \cdot 3^{1/2} \cdots n^{1/(n-1)})^{1/2}$ と緩やかに増大する。
- スケーリング感受性: 行のスケールが極端に異なる行列では、絶対値最大という基準だけでは不適切なピボットを選ぶことがある。各行を $\|A_{i,:}\|_\infty$ で正規化してから判定する「スケール付き部分ピボット選択」を採用する実装もある。
- 特異・準特異の検出: $|a_{kk}^{(k)}| < \tau \cdot \|A\|$($\tau$ は計算機イプシロン程度)になったら、行列は数値的に特異とみなし、最小二乗法や SVD など別アルゴリズムに切替えるのが安全である。
- 条件数による精度限界: 計算された解 $\hat{\mathbf{x}}$ の相対誤差は概ね $\kappa(A) \cdot u$($u$ は丸めの単位)のオーダーであり、$\kappa(A) \approx 1/u$ になると有効桁が完全に失われる。事前に条件数推定(LAPACK の
?gecon等)を行うのが望ましい。 - 反復改良 (iterative refinement): 残差 $\mathbf{r} = \mathbf{b} - A\hat{\mathbf{x}}$ を高精度で計算し、$A\mathbf{d} = \mathbf{r}$ を $LU$ 分解で解いて $\hat{\mathbf{x}} \leftarrow \hat{\mathbf{x}} + \mathbf{d}$ を 1〜2 回反復すれば、$\kappa(A) \cdot u \ll 1$ の下で精度を取り戻せる。
- 密行列前提: ガウスの消去法は密行列向けで、疎行列に直接適用するとフィルイン(消去で 0 が非 0 に変わる現象)により疎性が失われる。
条件数と精度の具体例: ヒルベルト行列
$H_{ij} = 1/(i+j-1)$ で定義されるヒルベルト行列は典型的な悪条件行列で、$n = 10$ で $\kappa_2(H_{10}) \approx 1.6 \times 10^{13}$ に達する。倍精度($u \approx 1.1 \times 10^{-16}$)では
$$\kappa_2(H_{10}) \cdot u \approx 1.8 \times 10^{-3}$$つまり相対誤差は最大で $10^{-3}$ オーダー、有効数字 3 桁程度しか期待できない。$n = 15$ では $\kappa_2(H_{15}) \approx 10^{18}$ となり倍精度では実質的に意味のある解は出ない。対策は (i) 拡張精度(多倍長浮動小数点や四倍精度)への切替、(ii) 反復改良、(iii) 直交化を用いる QR / SVD 解法への切替、のいずれか。
疎行列とスパース LU 分解
幅 5 の対角帯行列のような疎行列でも、素朴にガウスの消去法を適用するとフィルインが連鎖し、最終的に完全密行列に近くなることがある。これを避けるため、スパース $LU$ ソルバは次の 3 段階で動く:
- 順序付け: 行列の対称な行・列置換でフィルイン数を最小化する。Minimum Degree、AMD (Approximate Minimum Degree)、Nested Dissection など。
- 記号分解: 数値計算前に、フィルインで非 0 になる位置のみを確定し、データ構造を確保する。
- 数値分解: 確定されたパターンに沿って実際の $LU$ 値を計算する。
代表的な実装: SuperLU(汎用)、UMFPACK(非対称、SciPy spsolve のバックエンド)、PARDISO(Intel MKL)、MUMPS(並列)。
9. よくある質問
Q1. ガウスの消去法とは何ですか?
連立一次方程式を前進消去と後退代入の2段階で解く直接法である。計算量は $O(n^3)$ であり、数値線形代数の最も基本的なアルゴリズムである。
Q2. ピボット選択はなぜ必要ですか?
ピボット要素が 0 またはゼロに近いとゼロ除算や大きな丸め誤差が発生する。部分ピボット選択により列内の絶対値最大の要素をピボットに選ぶことで数値的安定性が向上する。
Q3. ガウスの消去法とLU分解の関係は?
前進消去の過程は行列 $A$ を $L$(下三角)と $U$(上三角)に分解する操作と等価である。消去の乗数が $L$ の要素、消去後の行列が $U$ となる。
10. 参考資料
- Wikipedia「ガウスの消去法」(日本語版)
- Wikipedia「Gaussian elimination」(英語版)
- G. H. Golub & C. F. Van Loan, Matrix Computations, 4th ed., Johns Hopkins, 2013(§3 で $LU$ 分解、§3.4 で要素成長率と Wilkinson 行列)。
- L. N. Trefethen & D. Bau III, Numerical Linear Algebra, SIAM, 1997(Lecture 20-23 で $LU$ と部分ピボット)。
- N. J. Higham, Accuracy and Stability of Numerical Algorithms, 2nd ed., SIAM, 2002(§9 でガウスの消去法の後退誤差解析、§12 で反復改良)。
sangi での実装
この記事のアルゴリズムは sangi の LinAlg モジュール で利用できる。