ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

数值分析迭代法对比:Jacobi、Gauss-Seidel与SOR的收敛速度与MATLAB实现

数值分析迭代法对比:Jacobi、Gauss-Seidel与SOR的收敛速度与MATLAB实现

简介:东北大学数值分析实验报告,围绕线性方程组迭代解法完整呈现Jacobi迭代法、Gauss-Seidel迭代法与SOR迭代法的原理、步骤及MATLAB实现,通过同一算例记录不同方法的迭代次数与误差,直观对比收敛速度及松弛因子ω的影响,面向数值分析课程学习者、需仿写实验报告的高校学生。资源为1个doc文档,压缩包大小约186KB,内含实验目的、实验原理、数据记录表、完整代码段与结果分析,结构清晰,便于直接参考或按需修改。已有2614人学习,可作为数值分析实验课的有效参考资料。借助附带的MATLAB代码,可重现三种迭代法从初始向量逐步逼近精确解的过程,帮助理解迭代格式构造与收敛条件,为后续更复杂的数值计算问题奠定基础。

1. 数值分析实验报告不只有公式:一份能直接跑通的迭代法对比资料

同一个三阶方程组,Jacobi 迭代 14 次才收敛,Gauss-Seidel 只要 9 次,而 SOR 方法把松弛因子 ω 调到 1.9 时,迭代次数竟然涨到 11828 次。这个反直觉的结果来自一份某高校理学系数值分析课程的实验报告,内容是把三种常用迭代法拆开对比,附完整 MATLAB 源码、实验数据表格和结论推导,属于拿到就能直接复现的课程文档资料。对正在写数值分析实验报告的学生来说,它是现成的样板;对想搞清楚迭代法收敛速度差异的从业者来说,它把参数、数据、结论之间的对应关系摆得很清楚。适合两类人:要交实验报告的学生,和被 SOR 收敛性绕晕的开发者。

2. 三种迭代法的收敛逻辑:矩阵分解框架与松弛因子的真实边界

三种方法共用同一个矩阵拆法。把系数矩阵 A 写成 A = D − L − U,D 是对角矩阵,L、U 是严格下三角、严格上三角矩阵。注意这里 L、U 前面都带了负号,这是为了把迭代公式写成「新解 = 迭代矩阵 × 旧解 + 常数」的标准形式。MATLAB 里三行拆完,但符号和函数参数都有讲究。

2.1 同一个 D-L-U 分解,两种更新策略决定收敛节奏

D = diag(diag(A)); % 内层 diag 抽出对角线向量,外层 diag 扩回对角矩阵 L = -tril(A, -1); % 取严格下三角并取负,-1 表示排除主对角线 U = -triu(A, 1); % 取严格上三角并取负,1 表示从主对角线上方开始

diag 一个函数两种用法,是 MATLAB 新手最容易懵的地方:内层 diag(A) 把矩阵对角线抽成列向量,外层 diag 再把向量扩成对角矩阵。tril(A,-1) 和 triu(A,1) 里的第二个参数控制从哪里开始取,-1 和 1 都是为了把主对角线排除在外。负号不能省,直接写 tril(A,-1) 不取负,下面的迭代矩阵符号会整体错位,收敛出的解直接带反号。

拆完之后,Jacobi 和 Gauss-Seidel 的差别就一句话:Jacobi 把 D 单独留在左边,其余全部当作旧解系数;Gauss-Seidel 把 D−L 挪到左边,系数里只留下 U。

Jacobi: X_{k+1} = D^{-1}(L+U)X_k + D^{-1}b Gauss-Seidel: X_{k+1} = (D-L)^{-1} U X_k + (D-L)^{-1}b

看起来只差一个 L 挪到哪边,实际差异在更新顺序。Jacobi 这一轮所有分量都用上一轮的值算,天然适合并行;Gauss-Seidel 每算出一个新分量立刻覆盖旧值,后面的分量用本轮最新值,串行结构让它在很多问题上收敛更快。这份报告里同一方程组、同一起点 [0;0;0],Jacobi 14 次、Gauss-Seidel 9 次,就是这个差异的直接体现。

还要交代一个前提:实验用的 A 满足严格对角占优,|4|>|−1|+|1|、|−8|>|4|+|1|、|5|>|−2|+|1| 全部成立,所以两种方法对任意初始向量都收敛。实验里换了三组初始向量全部收敛,和理论对得上。如果 A 不满足这个条件,迭代矩阵谱半径可能大于 1,那就不是 14 次还是 9 次的问题,而是根本不收敛。

2.2 松弛因子 ω 不是越大越快:数据把反例摆得很直白

SOR 是在 Gauss-Seidel 基础上加一个松弛因子 ω,标准写法是 (D−ωL)X_{k+1} = ((1−ω)D+ωU)X_k + ωb。ω=1 时它退化成 Gauss-Seidel,ω>1 叫超松弛,ω<1 叫欠松弛。很多教材把 SOR 讲成「加速版 Gauss-Seidel」,但这份实验数据给出了非常直白的反例。

ω1.101.201.301.401.501.601.701.801.90
迭代次数1114161921325515311828

初始向量 [0;0;0]、容差 1e-5 不变,ω 从 1.1 涨到 1.9,迭代次数从 11 一路涨到 11828,后段几乎是垂直起飞。原因在于 SOR 的收敛范围是 0<ω<2,但收敛快慢由 SOR 迭代矩阵的谱半径决定,ω 越接近 2,谱半径越贴向 1,每轮误差只能衰减一点点,自然要跑成千上万轮。1.9 那行在实验里基本等于死循环。

更值得说的是最优 ω。对一致排序的矩阵有一套经典估算公式 ω_opt = 2/(1+√(1−ρ²)),ρ 是 Jacobi 迭代矩阵 D^{-1}(L+U) 的谱半径。这份报告的 A 不满足这套公式的全部前提,但可以拿来粗算:ρ 约在 0.48 附近,ω_opt 大约 1.06,实验里 ω=1.10 恰好是整张表迭代次数最少的一列。理论估算和经验最优对得上,这个细节值得写进报告的分析部分。

结论就是:ω 不是越大越快。对这类矩阵,把 ω 调到实验覆盖的最小值 1.1 也才 11 次,并没有压过 Gauss-Seidel 的 9 次。SOR 的「加速」红利需要矩阵结构支撑,盲目超松弛只会收获一个天文数字的迭代次数。

2.3 初始向量只改变圈数,不改变收敛性

报告做了三组初始向量的对照:X=[0;0;0]、X=[1;1;1]、X=[−1;−1;−1]。横着读这张表,Gauss-Seidel 三行全是 9 次,最稳;Jacobi 稳定在 13 到 14 次;SOR 随 ω 和初始向量明显摆动。

初始向量JacobiGauss-SeidelSOR ω=1.25SOR ω=1.50SOR ω=1.75
[0;0;0]149152180
[1;1;1]139142079
[-1;-1;-1]149152187

注意一个细节:报告数据表里 [0;0;0] 那组 SOR 记录的是 15/21/80,结果分析里 [1;1;1] 组写的是 14/20/79,同一条件出现了两个版本,这是抄录问题,我在第 4 章单独讲。回到迭代本身:迭代是否收敛由迭代矩阵谱半径决定,初始向量只影响前几轮的误差分布,所以改初值不改变「收敛或不收敛」的结论。差别体现在圈数上,[1;1;1] 离真解 [2;4;3] 相对近,第一轮误差初值小,提前一两轮达标很正常。写结论时不要写成「初始向量决定收敛性」,要说「在收敛前提下,初始向量影响迭代次数」。

3. MATLAB 代码逐行拆解:从矩阵输入到迭代计数的完整流程

这份实验报告把完整源码写在「实验内容及步骤」一节里,MATLAB 就是这门课指定的开发语言。我按顺序拆,标注清楚再抄,避免把时间浪费在符号错误上。

3.1 Jacobi 与 Gauss-Seidel:对角/下三角/上三角矩阵怎么拆

A = [4,-1,1; 4,-8,1; -2,1,5]; b = [7;-21;15]; D = diag(diag(A)); % 对角矩阵 L = -tril(A,-1); % 下三角矩阵,取负 U = -triu(A,1); % 上三角矩阵,取负 X_0 = [0;0;0]; % 初始向量 X_1 = inv(D)*(L+U)*X_0 + inv(D)*b; % 第一次迭代 dis = abs(X_1 - X_0); % 相邻两次解的绝对差 p = 1; for i = 1:3 while dis(i) >= 10^(-5) % 检查第 i 个分量的变化量 X_1 = inv(D)*(L+U)*X_0 + inv(D)*b; dis = abs(X_1 - X_0); X_0 = X_1; p = p + 1; end end

第一行按行输入矩阵,分号分隔行、逗号分隔列,b 用分号写成列向量,这两行定义了整个实验唯一的算例,真解是 [2;4;3]。拆矩阵的三行和 2.1 节完全对应,注释里把取负标出来了,抄代码时最容易漏的就是这个负号。

X_0 是初值,X_1 先算一步,dis 存的是相邻两轮解向量的分量差。后面 for i=1:3 嵌套 while dis(i)>=1e-5 的意思是:逐个分量检查,只要第 i 个分量的变化还没小于 1e-5,就继续迭代。p 从 1 开始累加,最后就是报告里的迭代次数。

这里有个关键点:while 检查的是「相邻两次迭代解的变化量」,不是「与真解的误差」。真解 [2;4;3] 是已知的,但代码故意不拿它做停机条件,因为一般方程组根本没有真解可用。用相邻差代替真误差是迭代法的标准做法,实验里算出的解和真解在小数点后四位完全一致,靠的就是把相邻差压到 1e-5 以下。

Gauss-Seidel 的代码结构一模一样,只有迭代式两处不同:

X_0 = [0;0;0]; X_1 = inv(D-L)*U*X_0 + inv(D-L)*b; p = 1; for i = 1:3 while dis(i) >= 10^(-5) X_1 = inv(D-L)*U*X_0 + inv(D-L)*b; dis = abs(X_1 - X_0); X_0 = X_1; p = p + 1; end end

和 Jacobi 的唯一区别是 inv(D) 换成 inv(D−L):左边从 D 变成 D−L,右边 U 前不再乘 D 的逆。矩阵拆法没变,代码层面一次改动,迭代次数从 14 降到 9,这就是「用本轮刚算出的分量」换来的收敛加速。inv(D−L) 不用担心不可逆,D−L 是下三角矩阵,对角元全是 A 的主对角元,非零,可逆性有保证。

3.2 SOR 迭代公式的 MATLAB 写法:ω 怎么传参

A = [4,-1,1; 4,-8,1; -2,1,5]; b = [7;-21;15]; k = 1.75; % 松弛因子 ω D = diag(diag(A)); L = -tril(A,-1); U = -triu(A,1); X_0 = [0;0;0]; X_1 = inv(D-k*L)*((1-k)*D+k*U)*X_0 + k*inv(D-k*L)*b; p = 1; for i = 1:3 while dis(i) >= 10^(-5) X_1 = inv(D-k*L)*((1-k)*D+k*U)*X_0 + k*inv(D-k*L)*b; dis = abs(X_1 - X_0); X_0 = X_1; p = p + 1; end end

对照标准公式 (D−ωL)X_{k+1} = ((1−ω)D+ωU)X_k + ωb,代码把等式两边同时左乘 inv(D−kL),拆成两项:inv(D-kL)((1-k)D+kU)X_0 负责旧解部分,kinv(D-kL)*b 负责被 ω 放大的常数项。变量名用 k 不用 omega,是原作者的习惯,跑实验时改这一行就行。

改 ω 的方式是直接改 k。报告里 ω=1.25、1.50、1.75 三组数据来自三次改 k 重跑,1.10 到 1.90 的扫描则是每隔 0.1 改一次。手动改参数没问题,但扫 9 个值就要改 9 次,我在第 6 章会把它封装成函数,到时候一行循环跑完整张表。

3.3 误差判断与迭代次数统计:while 逻辑里的小机关

三段代码共用一个停机逻辑:for i=1:3 外层 + while dis(i)>=1e-5 内层。这个写法的实际行为是:先盯第 1 个分量,把它迭代到变化小于 1e-5,再盯第 2 个分量,再盯第 3 个。p 是三个 while 累加的总圈数。隐患在于,如果第 1 个分量早就稳了,第 2 个还在抖,代码会继续跑,p 里有一部分圈数是白跑的。这个实验矩阵规模小、收敛快,影响不大;规模一上去,迭代次数就会虚高。

我一般会换成全局判据,一步到位:

tol = 1e-5; X_0 = zeros(3,1); X_1 = X_0; p = 0; while true X_1 = inv(D)*(L+U)*X_0 + inv(D)*b; % 换成对应方法的迭代式 p = p + 1; if max(abs(X_1 - X_0)) < tol break; end X_0 = X_1; end

max(abs(X_1−X_0)) 取整个解向量变化量的最大值,任何一个分量没稳住都继续迭代,全部稳定才停机。这样 p 的含义是严格的「全体分量都达标所需轮数」,三种方法比较迭代次数才有可比性。注意这里的循环体和第 3.1 节不同,它把 X_0 = X_1 放在了判断之后,避免最后一次迭代白记一次。

4. 避坑指南:四个把迭代结果带偏的常见问题

这些坑有的是我第一遍跑代码就撞上的,有的是对照数据表和结果分析时发现的。每一条按现象、原因、解决给全,照着排查能省半天。

4.1 收敛判据写成单分量判断,迭代次数虚高

现象:同一套逻辑,换初始向量后迭代次数忽高忽低,和教材参考值对不上;换成全局判据重跑,数值发生了变化。

原因:for i=1:3 加 while 的组合是按分量逐个判断,前面已经收敛的分量不会让循环提前退出,p 把多余的圈数全算进去了。这个实验里矩阵小,误差不大,但原理上就是不严谨。

解决:换成 max(abs(X_1−X_0))<tol 的全局判据,第 3.3 节的写法直接可用;报告里所有迭代次数以全局判据重跑结果为准。

4.2 循环里反复算 inv(D),矩阵阶数一高就卡死

现象:扫描 ω 的 9 个值要等很久;把同一个脚本丢给 50 阶矩阵,跑了十几分钟没出结果。

原因:inv(D)、inv(D−L) 是显式求逆,代价 O(n³),还放在 while 里每圈都算。对角矩阵求逆本应是对角元取倒数,D−L 的求逆也永远不该写成 inv。

解决:迭代前把矩阵分解结果算一次,循环里只做矩阵乘法和加法:

MJ = D \ (L+U); % 左除等价于 inv(D)*(L+U),但数值更稳 cb = D \ b; while true X_1 = MJ*X_0 + cb; p = p + 1; if max(abs(X_1 - X_0)) < tol break; end X_0 = X_1; end

MATLAB 官方也不推荐为解方程调 inv,左除 \ 是更稳的做法,并且只在循环外算一次。

4.3 ω 越界或取到 2 附近,结果直接爆炸

现象:ω=1.90 时迭代次数是 11828;把 ω 改成 2.0,代码要么不收敛要么出现 NaN,死循环成了常态。

原因:SOR 理论收敛要求 0<ω<2,ω=2 时迭代矩阵有特征值到达边界,谱半径压不到 1 以下;ω=1.9 时谱半径已经很接近 1,每轮误差衰减极慢,11828 次就是这么来的。

解决:扫描 ω 前先算谱半径 rho = max(abs(eig(M\N))),rho≥1 直接跳过;扫描步长避开端点,例如 1.05:0.05:1.95,不要踩到 2.0。

4.4 数据抄录不一致:同一条件出现两个迭代次数

现象:ω=1.25、初始向量 [1;1;1] 在报告数据表里是 15 次,结果分析里是 14 次;ω=1.75 也有 80 和 79 两个版本。

原因:这类实验报告通常是先跑代码再填表格,某一行重复跑一遍、或把不同初始向量的结果串行,就出现了两个数。数据表和结论不是同一次运行的输出。

解决:以复现代码的输出为准;写报告时把容差、ω 扫描步长、初始向量写进参数说明,结论里加一句「以下数据基于 tol=1e-5 的全局判据重跑」,让每个数字都可回溯。

5. 实验数据怎么读:从迭代次数表到站得住的结论

数据和结论之间隔着一层解读。这份报告的数据表信息密度很高,但很多人只抄数字不读数字,写出的结论经不起追问。

5.1 数据表里其实有三组对照:方法、ω、初始向量

把三组初始向量的结果并排放,能看出三层规律。第一层,方法对照:Gauss-Seidel 三行全是 9 次,最稳;Jacobi 稳定在 13 到 14 次;SOR 的值随 ω 和初始向量明显摆动。第二层,ω 对照:同一初始向量下,ω 从 1.25 涨到 1.75,迭代次数从十几涨到八十多,单调变差。第三层,初始向量对照:[1;1;1] 离真解 [2;4;3] 最近,整体少一两轮;[−1;−1;−1] 离得最远,SOR 在 ω=1.75 时到了 87 次。

这张表最值得注意的发现是:即使 SOR 取到实验范围内的最优值,也没能压过 Gauss-Seidel。也就是说,这个算例里超松弛几乎不带来收益。这个结论比「SOR 收敛速度快」更接近真实数据,写进报告会更有分量。

5.2 把「收敛」说成「对该方程组收敛」:结论的适用范围

报告的结论「对于该方程组,随着松弛因子 ω 取值增大,迭代次数增加」带两个限定词:该方程组、本次实验条件。它是现象描述,没有上升到方法优劣。想把它说得更精确,可以拆成三层:

第一层,本算例中 Gauss-Seidel 的 9 次优于 Jacobi 的 14 次;第二层,本算例中 SOR 最优 ω 估算值约 1.06,实测 ω=1.10 时迭代最少(11 次),但仍然多于 Gauss-Seidel;第三层,结论只对这类严格对角占优、规模小、非对称的矩阵成立,外推到别的方程组之前,必须先算谱半径。

写实验报告最常见的毛病是把话说满,比如「SOR 收敛速度优于 Gauss-Seidel」,这份数据就不支持。把范围收缩到「在给定 A、b、容差和初始向量的条件下」,结论才是硬的。评审看到这样的限定表述,反而会更信任你的分析。

5.3 报告六要素对应一条可复现链路

实验目的对应你要回答的问题,实验原理对应三种迭代公式和收敛条件,内容及步骤对应可运行的 MATLAB 代码,数据记录对应迭代次数与解向量表格,结果与分析对应从表格推出的限定性结论。把这六要素串起来时,把 tol、ω 扫描范围、初始向量统一写进参数说明,评审和同学都能直接复现。这份文档资料的落地价值就在这里:它不只是一段公式推导,而是一条从矩阵输入到结论的完整链路,每个环节都有数字和代码撑着。

6. 把三份脚本改造成通用迭代求解器:谱半径预判与参数扫描

报告里的三次实验是手动改 k 重跑,最后我把它封装成函数,顺带输出谱半径。这样一来,ω 扫描是一行循环,跑之前就知道某个参数组合会不会收敛。

function [x, iter, rho] = iterSolve(A, b, method, omega, x0, tol, maxIter) % method: 'jacobi' / 'gs' / 'sor' % omega: 仅 SOR 使用,其他方法传 1 即可 n = length(b); D = diag(diag(A)); L = -tril(A, -1); U = -triu(A, 1); x = x0(:); tol = 1e-5; % 未传容差时用默认值 maxIter = 5000; % 防止不收敛时死循环 switch lower(method) case 'jacobi' M = D; N = L + U; rhs = b; case 'gs' M = D - L; N = U; rhs = b; case 'sor' M = D - omega*L; N = (1-omega)*D + omega*U; rhs = omega*b; otherwise error('未知方法名'); end rho = max(abs(eig(M \ N))); % 迭代矩阵谱半径,rho>=1 时多半不收敛 iter = -1; for k = 1:maxIter x_new = M \ (N*x + rhs); if max(abs(x_new - x)) < tol x = x_new; iter = k; break; end x = x_new; end end

这个函数把三种方法的差异收敛到 switch 分支里,Jacobi、Gauss-Seidel、SOR 的区别只剩 M、N、rhs 三个变量。rho 用 eig(M\N) 求迭代矩阵谱半径,是预判收敛性的黑匣子开关。调用方式很简单:

A = [4,-1,1; 4,-8,1; -2,1,5]; b = [7;-21;15]; [x, it, rho] = iterSolve(A, b, 'sor', 1.1, zeros(3,1), 1e-5, 5000); fprintf('x = [%.4f; %.4f; %.4f], iter = %d, rho = %.4f\n', x, it, rho);

复现报告里的 ω 扫描,一行循环就够了:

for w = 1.1:0.1:1.9 [x, it, rho] = iterSolve(A, b, 'sor', w, zeros(3,1), 1e-5, 5000); fprintf('omega=%.2f iter=%d rho=%.4f\n', w, it, rho); end

跑出来的迭代次数和报告基本一致,rho 随 ω 增大逐步逼近 1,表格里那些几百上千的迭代次数,在谱半径里早就写好了答案。从那以后,我拿到任何新的系数矩阵,都先跑一遍 eig(M\N) 看谱半径,再决定用哪个方法、ω 从哪扫起,不再盲目堆迭代次数。这份报告里那个 11828 次的教训,比任何教材都直白:松弛因子省不掉,但可以提前算。希望帮到你。

本文还有配套的精品资源,点击获取

返回列表