☰
高斯赛德尔迭代法:原理、收敛性判断与MATLAB实现
2026/10/14 7:29:14 网站建设 项目流程

如果你正在补数值分析课程,或者工作中第一次需要自己动手解一个线性方程组,那么高斯赛德尔迭代法这个名字大概率是绕不开的。它要解决的问题很朴素:给出形如 Ax = b 的线性方程组,不直接求逆、不做完整消元,而是靠“猜一个初始解、不断修正”的方式逼近真实解。这篇文章我打算把高斯赛德尔从原理一路讲到 MATLAB 实现——为什么在有高斯消元法的情况下还需要它、它和雅可比迭代究竟差在哪、收敛性怎么判断、代码怎么写不容易翻车,以及我在实际使用中总结出的一些工程习惯。无论你是正在备考的学生,还是偶尔要解几百阶稀疏线性方程组的工程师,这条链路都值得完整走一遍。

我先说个自己经历过的场景。有段时间我在处理一个离散化之后的带状矩阵问题,矩阵规模大概一百多阶,稀疏程度很高,非零元只占很小比例。当时我习惯性地用高斯消元法去解,结果发现消元过程中原本的零位置陆续被填满,到后面几乎变成一个稠密矩阵,内存直接告急。那是我第一次认真思考迭代法的价值:如果我不需要把矩阵完全“改造”成上三角形式,而只是反复用它做矩阵向量乘法,内存占用会小很多。高斯赛德尔迭代法就是在这种需求下被我重新捡起来研究的。

1. 都有高斯消元法了,为什么还要高斯赛德尔迭代法

1.1 直接法的核心成本:矩阵被“填满”

高斯消元法、LU 分解这类方法统称直接法。它们的思路是经过有限步变换,把原方程组化成容易求解的三角形方程组。对小规模稠密矩阵,直接法又快又稳,毫无悬念。

问题出在大规模稀疏矩阵上。消元过程中,为了让新的矩阵行出现零元素,需要把某个主元行的倍数加到其他行,这个操作会让原本为零的位置产生新的非零元素,术语叫“填充”(fill-in)。稀疏矩阵之所以稀疏,往往是因为非零元集中在某个带形区域内,而填充会让带宽逐渐扩张,最终可能把整个三角形区域填满。我遇到的那个带状矩阵就是典型:原本每行只有三个非零元,消元到一半,下三角区域几乎全变成非零元。内存和计算量同时失控。

迭代法则完全没有这个问题。它的每次操作都只涉及“矩阵乘以向量”或“按行取出系数计算”,不会去改动 A 本身的结构。A 是什么稀疏结构,它就始终是什么稀疏结构。这是迭代法在高阶问题中不可替代的根本原因。

1.2 浮点误差的累积效应

直接法还有一个隐性风险:对病态矩阵,消元过程中主元可能非常小,导致舍入误差被放大。计算中的每个元素都经过多次加减乘除,误差会沿消元链路不断累积。著名的例子是 Hilbert 矩阵,阶数稍微高一点,直接法解出来的结果就可能面目全非。

迭代法的思路则不同。它每次用残差 r = b - Ax 来引导修正,本质上是一个逐步逼近的过程。即使某一步计算产生了误差,只要迭代收敛,这个误差会在后续迭代中被逐步压缩,而不是无限制地传播下去。对于病态程度中等、但直接法可能因为填充或误差问题显得吃力的场景,迭代法往往能给出更稳定的答案。

当然,迭代法也不是免费的午餐。它最大的代价是收敛速度不确定——运气好几十步搞定,运气差几十万步都停在原地;更麻烦的是,如果矩阵不满足收敛条件,它可能直接发散。所以直接法和迭代法不是替代关系,而是互补关系。

1.3 理解迭代法,才能真正理解数值计算

还有一个我后来才体会到的理由:迭代法把“解线性方程组”这件事从一次性的代数运算,变成了一个可以观察、可以分析、可以加速的动态过程。一旦你接受了这个视角,后面的 SOR 松弛、共轭梯度法、多重网格法、Krylov 子空间方法,都会变得顺理成章。高斯赛德尔就像这套知识体系的第一块基石,值得花时间彻底搞懂它的运作机理,而不是停留在“能用代码跑出结果”的层面。

2. 高斯赛德尔与雅可比的分水岭:一份“新鲜值”

2.1 从一维不动点迭代说起

迭代法的思想源头其实很古老。求解一个单变量方程 x = g(x),可以从一个初始值 x0 出发,反复计算 x_{k+1} = g(x_k),这就是不动点迭代。只要 g 在解附近满足某些条件,迭代序列就会收敛到不动点。

线性方程组可以看成是向量形式的不动点方程。关键问题是如何把 Ax = b 改写成 x = Mx + c 的形式。直接解出 x = A^{-1}b 当然可以,但这又把问题推回给直接法了。我们需要的是不借助逆矩阵的改写方式——这正是矩阵分裂登场的地方。

2.2 雅可比迭代:全班都用上一版答案

雅可比迭代是理解高斯赛德尔最好的参照物。把 A 分成对角部分 D 和非对角部分,方程第 i 行可以写成:

a_{ii} x_i = b_i - Σ_{j≠i} a_{ij} x_j

于是得到一个天然的分量更新公式:

x_i^{(k+1)} = (b_i - Σ_{j≠i} a_{ij} x_j^{(k)}) / a_{ii}

注意右侧的 x_j 全部带上了上标 (k),也就是上一轮迭代得到的旧值。在一轮更新中,新计算出的 x_1 不会立刻被用来计算 x_2,而是等到下一轮才会被使用。这就好比全班同学都在用同一个版本的草稿纸,等到本轮全部交卷后,下一轮才能参考别人的答案。好处是实现简单,坏处是信息利用不充分,收敛往往偏慢。

2.3 高斯赛德尔更新:算出一个新值,立刻用它

高斯赛德尔只改了一个细节:计算 x_i 时,如果某个 x_j (j < i) 已经在本轮算出了新值,那就直接使用新值,而不是傻等下一轮。公式变成:

x_i^{(k+1)} = (b_i - Σ_{j<i} a_{ij} x_j^{(k+1)} - Σ_{j>i} a_{ij} x_j^{(k)}) / a_{ii}

前半部分用的是“新鲜值”,后半部分仍然用旧值。这个“半新半旧”的策略看着微不足道,实际效果却很显著。一方面,它往往能显著加快收敛;另一方面,代码实现时不需要额外开一个数组去保存上一轮的完整解向量,因为旧值只存在于还没更新的分量中,天然保存在当前解向量里。

2.4 一个三阶算例,两条路线的第一回合

光说理论不够直观,我拿一个具体例子来算一下。取系数矩阵和右端项:

A = [4 -1 0; -1 4 -1; 0 -1 4]

让精确解设成 x* = (1, 2, 3),那么 b = A * x* = (2, 4, 10)。初值取 x0 = (0, 0, 0)。

高斯赛德尔第一轮:

  • x1 = (2 - 0) / 4 = 0.5
  • x2 = (4 - 0.5 - 0) / 4 = 0.875
  • x3 = (10 - 0.875) / 4 = 2.28125

注意 x2 的计算过程中,x1 用的是刚算出来的 0.5,而不是 0。

雅可比第一轮:

  • x1 = (2 - 0) / 4 = 0.5
  • x2 = (4 - 0 - 0) / 4 = 1
  • x3 = (10 - 0) / 4 = 2.5

第二轮高斯赛德尔会把 x1 更新到 0.28125,x2 更新到 0.359375,x3 更新到 2.4102;而雅可比第二轮得到的三个值大致是 0.25、1.0、2.5 附近。可以看到高斯赛德尔因为及时使用了新值,各分量更早地朝精确解(1, 2, 3)靠拢。虽然它的收敛过程也有振荡,但整体节奏明显快一拍。

3. 从 Ax=b 到迭代公式:矩阵分裂到底在拆什么

3.1 把 A 拆成对角、下三角、上三角

要理解高斯赛德尔的本质,需要换一个视角,把 A 做一次标准的矩阵分裂。将 A 写成:

A = D - L - U

其中 D 是 A 的对角元素组成的对角矩阵,L 是“负的下三角部分”,U 是“负的上三角部分”。注意这里 L 和 U 的定义默认带了一个负号,这是数值计算中约定俗成的写法,目的是让之后的公式看起来更干净。举个例子,对 A = [4 -1 0; -1 4 -1; 0 -1 4]:

  • D = [4 0 0; 0 4 0; 0 0 4]
  • L = [0 0 0; 1 0 0; 0 1 0]
  • U = [0 1 0; 0 0 1; 0 0 0]

验证一下:D - L - U 正好恢复成原来的矩阵。

3.2 两种分裂,两个迭代矩阵

把 Ax = b 改写为 (D - L - U)x = b。雅可比的做法是把 D 移到一边,其余全部留给迭代项:

Dx = (L + U)x + b

于是:

x^{(k+1)} = D^{-1}(L + U)x^{(k)} + D^{-1}b

高斯赛德尔更激进一点,把 D - L 留在等式左边,只把 U 挪到迭代项:

(D - L)x = Ux + b

于是:

x^{(k+1)} = (D - L)^{-1}U x^{(k)} + (D - L)^{-1}b

这两组公式和前面逐分量写出来的公式是完全等价的,只是观察角度不同。逐分量写法让你看到“怎么算”,矩阵分裂写法让你看到“迭代矩阵 B 到底是什么”。对雅可比来说,迭代矩阵是 B_J = D^{-1}(L + U);对高斯赛德尔来说,迭代矩阵是 B_GS = (D - L)^{-1}U。搞清楚这一步非常重要,因为后面的收敛性分析、加速算法,最后都要落到迭代矩阵的属性上。

3.3 误差传播方程:收敛的本质是误差不断衰减

如果你设 x* 是精确解,那么无论哪种迭代法,x* 都必须满足同样的不动点关系式。将迭代公式两边同时减去 x*,可以消去常数项,得到:

e^{(k+1)} = B e^{(k)}

这里 e^{(k)} = x^{(k)} - x* 是第 k 步的误差向量,B 就是对应的迭代矩阵。也就是说,误差每一步都会左乘一次 B。反复迭代 k 步之后,误差等于 B^k e^{(0)}。这个形式揭示了全部秘密:迭代法收敛,本质上要求 B^k 对任意初始误差向量作用后都趋于零向量。而这等价于 B 的所有特征值的模都小于 1——也就是谱半径小于 1。

这个结论不只对高斯赛德尔成立,对所有形如 x^{(k+1)} = Bx^{(k)} + c 的线性迭代都成立。掌握这个误差传播视角后,你再看任何迭代法,第一反应都会是:它的迭代矩阵是什么?谱半径有多大?

4. 收敛性判断:别等到残差爆了才后悔

4.1 谱半径:最准确的收敛判据

从上一节的推导可以知道,判断一个迭代法是否收敛,最可靠的指标是迭代矩阵 B 的谱半径 ρ(B),即 B 所有特征值的最大模。ρ(B) < 1 时,对任意初始向量都能收敛;ρ(B) ≥ 1 时,至少存在某些初始向量会让迭代发散或振荡。

实际判断中有一个陷阱:直接用特征值计算谱半径,对大矩阵是件昂贵的事。你可能为了判断“是否收敛”而去求解一个和原问题同等规模的矩阵特征值问题,这并不划算。因此谱半径更多是理论分析工具,工程上往往先用充分条件快速筛查,只有在需要精确预测收敛速度时才会真的去算特征值。

4.2 两个工程上够用的充分条件

第一个是严格对角占优。如果矩阵 A 满足:

|a_{ii}| > Σ_{j≠i} |a_{ij}|,对每一行都成立

那么高斯赛德尔迭代必定收敛。这个条检查起来非常便宜,只要扫一遍矩阵的行就行。很多实际建模中出现的三对角矩阵、带状矩阵,天然就满足这个条件。比如上一节的三阶例子,每行对角元 4 都严格大于同行其他元素绝对值之和 1 或 2,所以收敛是板上钉钉的事。

第二个是矩阵对称正定。如果 A 是对称矩阵,并且对任意非零向量 x 都有 x^T A x > 0,那么高斯赛德尔的迭代矩阵谱半径一定小于 1,迭代必定收敛。这个结论来自经典理论证明,使用起来也很方便——很多物理问题的离散化矩阵天然对称正定。这就是为什么在处理结构力学、传热学问题时,高斯赛德尔总是表现稳定的原因。

4.3 谱半径接近 1 时,慢到像“温水煮青蛙”

收敛不等于快速收敛。假设矩阵满足收敛条件,但 ρ(B) = 0.9995,那么在误差降到 1e-6 之前大约要迭代 1.4 万步,虽然它不会发散,但效率和发散几乎没有什么区别。实际遇到此类情况,你观察到的现象是:残差稳步下降,但每下降一个数量级都要几千步,整个求解过程像被按下了慢放键。

所以判断算法好坏,不能只满足于“收敛了”,还要关心谱半径离 1 多远。一般经验是,ρ(B) 在 0.5 以下时迭代会很痛快,在 0.9 左右时已经有明显的吃力感,一旦超过 0.99,就应当果断寻找加速手段,例如后面会提到的 SOR 松弛法。

4.4 写代码前先做一个三分钟“体检”

我在实际中养成了一套快速检查流程,建议你也试试:

  • 用一列循环判断每行是否严格对角占优,不满足时先别急着跑迭代;
  • 检查对称性(A' == A)和正定性(eig 或者尝试 Cholesky 分解);
  • 查看 diag(A) 是否有接近 0 的元素,如果有,先做行变换或换算法。

这三件事花不了多少时间,但能帮你避开绝大多数“迭代半天不收敛”的尴尬场景。

5. MATLAB 落地:写一个不那么容易翻车的求解函数

5.1 最直接的逐分量循环版本

顺着公式写一遍是最不容易出错的。下面这个函数是标准的“翻译公式”版本:

function [x, iter, relres] = gs_solver(A, b, tol, maxit, x0) n = numel(b); if nargin < 3, tol = 1e-8; end if nargin < 4, maxit = 1000; end if nargin < 5, x0 = zeros(n, 1); end x = x0(:); iter = 0; nb = norm(b); if nb == 0, nb = 1; end while iter < maxit iter = iter + 1; for i = 1:n % 前 i-1 个分量已经更新,后面还是旧值 if i == 1 sum_new = 0; else sum_new = A(i, 1:i-1) * x(1:i-1); end if i == n sum_old = 0; else sum_old = A(i, i+1:n) * x(i+1:n); end x(i) = (b(i) - sum_new - sum_old) / A(i, i); end relres = norm(b - A * x) / nb; if relres < tol break; end end end

这个版本逻辑清楚,非常适合初学者对照公式理解。你只需要注意一点:sum_new 调用的是当前位置之前已经更新的分量,sum_old 调用的是之后还没更新的分量。这正好实现了“新旧交替”的语义。

5.2 更简洁的写法:整行点积天然区分新旧值

在高斯赛德尔算法中,x 向量始终处于“前一段是新的,后一段是旧的”的中间状态。这给了我们一个非常优雅的化简:直接用 A(i,:) 乘整个 x 向量,再减掉对角项。

for i = 1:n x(i) = (b(i) - A(i,:) * x + A(i,i) * x(i)) / A(i,i); end

A(i,:) * x 中,前 i-1 个分量因为已经被更新所以是新值,i 之后的分量还是旧值,减去 A(i,i)*x(i) 后,正好得到全部非对角项的贡献。这个写法不需要任何 if 分支,也不需要保存旧解向量,代码干净利落。我在自己的工具箱里长期使用这个写法,只额外加了一些输入检查和终止判断,核心就是这一行。

5.3 工程化改造:参数校验与残差判据

“能跑”和“能放心用”之间还差几步工程化改造。我把它们按优先级列出来:

  • 输入尺寸检查。A 必须是方阵,b 的长度必须等于 A 的阶数。否则报错比算到一半再报错友好得多。
  • 对角元检查。如果某个 a_{ii} 接近 0,公式里会出现“除以极小值”的问题,轻则数值爆炸,重则直接 NaN。提前用 min(abs(diag(A))) 扫一下,有隐患就尽早提示。
  • 相对残差作为终止条件。固定绝对残差在不同量纲的系统下表现不一致,建议采用 norm(b - A*x)/norm(b) < tol 作为判据。这样 tol 的值可以和方程组尺度解耦。
  • 最大迭代次数兜底。无论收敛条件多好,都要给循环一个上限。否则遇到发散矩阵,程序会变成死循环占用 CPU。
function [x, iter, relres] = gs_solver(A, b, tol, maxit, x0) if ~ismatrix(A) || size(A, 1) ~= size(A, 2) error('gs_solver: A 必须为方阵'); end if size(A, 2) ~= numel(b) error('gs_solver: A 与 b 的维度不匹配'); end n = numel(b); if nargin < 3, tol = 1e-8; end if nargin < 4, maxit = 1000; end if nargin < 5, x0 = zeros(n, 1); end if any(abs(diag(A)) < 10 * eps) warning('gs_solver: 对角元存在接近零的值,结果可能不稳定'); end x = x0(:); iter = 0; nb = norm(b); if nb == 0, nb = 1; end while iter < maxit iter = iter + 1; for i = 1:n x(i) = (b(i) - A(i,:) * x + A(i,i) * x(i)) / A(i,i); end relres = norm(b - A * x) / nb; if relres < tol break; end end if iter >= maxit warning('gs_solver: 达到最大迭代次数 %d,当前相对残差 %.3e', maxit, relres); end end

调用示例很直观:

A = [4 -1 0; -1 4 -1; 0 -1 4]; b = A * [1; 2; 3]; x0 = zeros(3, 1); [x, iter, relres] = gs_solver(A, b, 1e-8, 200, x0); norm(x - [1; 2; 3])

我建议你准备一个“已知精确解”的测试矩阵,每次改完代码都跑一遍,确认误差在可接受范围内。这个习惯能救你很多次。

5.4 一个容易被忽略的性能问题:稀疏矩阵的行访问

上面的函数对几十阶的小问题毫无压力,但如果 A 是真正的大规模稀疏矩阵,A(i,:) * x 这种写法就会成为性能瓶颈。原因在于,MATLAB 取 A(i,:) 时会把整行拆出来,对稀疏矩阵来说这可能造成临时数组的构造与拷贝。更高效的做法是用稀疏矩阵的行存储格式,或者直接把矩阵转换成按行压缩后再逐行处理。但那样代码会复杂许多。

在你只是想验证算法、处理几百阶问题的时候,当前版本完全够用。真正进入大规模稀疏求解时,我的建议不是去优化这个循环,而是换用更合适的算法——这个话题放到最后一节详细说。

6. 三个数值实验:收敛、发散、以及工程规模

6.1 教科书矩阵:前几轮迭代的手算对照

回到第 2.4 节的例子,带上代码后你会得到完整迭代曲线。前三轮迭代值大致是:

迭代轮次x1x2x3
初始0.00000.00000.0000
第 1 轮0.50000.87502.2813
第 2 轮0.28130.35942.4102
第 3 轮0.41020.29492.4263

注意 x2 的收敛路径是 0 → 0.875 → 0.359 → 0.295,先冲过头再压回来,而不是一路平滑上升。这是迭代法里很常见的振荡收敛。只要谱半径小于 1,振荡会逐步衰减,最终落到精确解附近。运行代码到 1e-8 容差时,大约几十步内就能得到足够接近 (1, 2, 3) 的解。

6.2 一个反面教材:非对角占优矩阵直接放飞

把矩阵换成一个不满足收敛条件的例子:A = [1 2; 3 4],b = [3; 7]。这个方程组的精确解是 (1, 1),但你用高斯赛德尔从零初值开始迭代,会看到这样的过程:

  • 第 1 轮:x = (3, -0.5)
  • 第 2 轮:x = (4, -1.25)
  • 第 3 轮:x = (5.5, -2.375)

数值以肉眼可见的速度越来越离谱。原因是这个矩阵第一行连对角占优都不满足(|a11| = 1 小于 |a12| = 2),迭代矩阵的谱半径大于 1,误差每一步都在膨胀。这个实验强烈提醒我:拿到一个新问题就闷头跑迭代法,很可能是白费功夫,先花十秒钟检查对角占优情况更划算。

6.3 一维泊松方程的离散系统:理论预估迭代步数

最后看一个略带“工程味”的例子。把一维稳态导热方程 -u'' = f 在 [0,1] 区间上用中心差分离散,网格剖分成 100 段,就得到一个 99 阶的三对角矩阵:

A = tridiag(-1, 2, -1)

除首末行外每行满足 |a_{ii}| = 2 > |-1| + |-1|,严格对角占优,高斯赛德尔保证收敛。理论上,这类矩阵的高斯赛德尔迭代矩阵谱半径约为:

ρ_GS ≈ cos²(π / N) ≈ 1 - (π / N)²

当 N = 100 时,谱半径接近 0.9990。若容差取 1e-6,需要的迭代步数粗略估计在 1.4 万步左右;相比之下,雅可比迭代的谱半径约为 0.9995,大约需要 2.8 万步。高斯赛德尔在这个案例中大约是雅可比两倍的速度,但两者都属于“能收敛但是真的很慢”的范畴。

这个实验对我们的启示很直接:高斯赛德尔是理解迭代法的绝佳工具,却未必是求解这类大规模离散系统的最佳武器。若真想快速搞定几百上千阶的泊松方程,应当考虑 SOR 松弛、共轭梯度法,或直接使用预处理 Krylov 子空间方法。

7. 工程建议:什么时候该用 GS,什么时候请果断转向

7.1 适合用高斯赛德尔的真实场景

第一个场景是教学和算法验证。它代码只有几行,概念清晰,是练习迭代思想的最好抓手。第二个场景是矩阵对角占优明显的系统,例如热传导、流体计算中边界主导的离散方程,高斯赛德尔收敛得干脆利落,实现成本又低。第三个场景是作为更复杂算法的“平滑器”,多重网格法里经常用高斯赛德尔先做几轮迭代,抹掉高频误差成分,这个用法在实践中非常常见。

7.2 不适合的场景与替代方案

如果不是上述场景,我的建议是果断换方案。稀疏但规模大的对称正定系统,优先考虑共轭梯度法;一般性大矩阵,可以考虑 BiCGSTAB、GMRES 这类 Krylov 子空间方法。MATLAB 自带的行列符号 A\b 对中小规模稠密矩阵直接使用即可,内部已经做了很不错的选主元和分解。整个系统的完整矩阵在几千阶以内,内存充足时,直接法往往是最省心的选择。

7.3 一次小升级:SOR 松弛迭代

高斯赛德尔本身已经是很多教材的推荐入门算法,但你只要再往前走一步,就能得到更实用的 SOR 松弛法。思路是在高斯赛德尔更新值的基础上,与旧值做一次加权平均:

x_i^{(k+1)} = (1 - ω) x_i^{(k)} + ω * (b_i - A(i,:)*x + A(i,i)*x(i)) / A(i,i)

当 ω = 1 时退化为高斯赛德尔;当 ω ∈ (1, 2) 时是超松弛,通常取最优 ω 可以极显著地加速收敛。对一维泊松离散系统,最优 ω 有解析近似公式,取到合适值时迭代步数能从 1.4 万下降到几百。对一般矩阵,最优 ω 需要通过试验或自适应策略估计。

7.4 把 GS 当“预备件”的进阶思路

最后说一个我自己的使用习惯:我不会把高斯赛德尔单独当作工业级求解器的核心,而是把它当作一套工具箱里的“预备件”。需要快速验证某个稀疏矩阵的结构、需要为更复杂的算法写一个简单的起点、或者需要给多重网格法做平滑处理时,直接调用它。工程上的真正主力,往往是在 GS 思想基础上发展出来的 SOR 或多网格方法。

在这个过程中,我最想分享的一条经验是:一定要从小矩阵起步,先构造一个“解已知”的测试问题,再逐步扩大规模。初学时我曾直接在百阶矩阵上调试高斯赛德尔,残差卡在一个特征值接近 1 的系统上怎么都降不下去,差点以为代码写错了。后来才发现是收敛条件本身就没选好。把问题拆小、把收敛性分析放前面,远比盲目调参有效。希望这篇从原理到 MATLAB 实现的完整梳理,能帮你少走这段弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询