文章目录

LU 分解是解线性方程组的经典方法,把系数矩阵 A 分解成 L(下三角矩阵)乘 U(上三角矩阵),然后把 A x = b Ax = b Ax=b 转化成两个三角方程组求解,速度比高斯消元快,多次解不同右端项特别高效。本文给完整 Matlab 实现,原理代码都讲清楚。
LU分解基本原理
对于 n 阶方阵 A,LU 分解就是:
A = L U A = L U A=LU
其中 L 是单位下三角矩阵(对角线都是 1),U 是上三角矩阵。
分解完, A x = L U x = b Ax = LUx = b Ax=LUx=b,先解 L y = b Ly = b Ly=b 得到 y,再解 U x = y Ux = y Ux=y 得到 x,就是方程组解。
三角方程组求解非常快,直接前代后代就行,比每次对 A 做高斯消元快很多。
Matlab 自带LU分解函数
Matlab 本身有 lu 函数,直接就能用,不用自己写分解算法,日常使用直接调用它就行。
例子:解 A x = b Ax = b Ax=b
matlab
% 系数矩阵 A
A = [4 3 -2; 2 1 -3; 1 2 1];
b = [5; -1; 10];
% LU分解
[L, U] = lu(A);
% 第一步解 Ly = b
y = L\b;
% 第二步解 Ux = y
x = U\y;
% 看看结果对不对
disp(x);
验证一下 A*x - b,误差很小,就是机器精度,完全正确。
如果需要选主元,lu 默认就做了选主元,数值稳定性好,不用你操心,所以直接用就完了。
如果你要得到置换矩阵 P,满足 P A = L U PA = LU PA=LU,调用语法:
matlab
[L, U, P] = lu(A);
这时候解方程组就是 y = L\(P*b); x = U\y;,结果一样。
手动实现Doolittle算法
为了理解原理,我们自己写 Doolittle 算法,一步步来。
Doolittle 算法就是 L 对角线为 1,计算 L 和 U 元素按公式递推:
先算 U 第一行,L 第一列,然后逐行逐列算下去。
完整代码:
matlab
function [L, U] = doolittle(A)
n = size(A, 1);
L = eye(n); % L 对角线初始化为 1
U = zeros(n);
% 第一行 U
U(1,:) = A(1,:);
% 第一列 L
L(2:n,1) = A(2:n,1)/U(1,1);
% 递推计算剩下的
for k = 2:n
% 算 U 第 k 行
for j = k:n
U(k,j) = A(k,j) - L(k,1:k-1)*U(1:k-1,j);
end
% 算 L 第 k 列
for i = k+1:n
L(i,k) = (A(i,k) - L(i,1:k-1)*U(1:k-1,k))/U(k,k);
end
end
end
这个就是 Doolittle 算法完整实现,非常简洁。
测试一下:
matlab
A = [4 3 -2; 2 1 -3; 1 2 1];
[L, U] = doolittle(A);
disp(L*U - A); % 误差应该接近零
结果完全正确,和 Matlab 自带 lu 结果一样。
分解完,解方程组:
matlab
function x = solve_lu(L, U, b)
n = size(L, 1);
y = zeros(n,1);
% 前代解 Ly = b
y(1) = b(1)/L(1,1);
for i = 2:n
y(i) = (b(i) - L(i,1:i-1)*y(1:i-1))/L(i,i);
end
% 后代解 Ux = y
x = zeros(n,1);
x(n) = y(n)/U(n,n);
for i = n-1:-1:1
x(i) = (y(i) - U(i,i+1:n)*x(i+1:n))/U(i,i);
end
end
整个流程就是:Doolittle 分解得 L U,调用 solve_lu 得到解,搞定。
Crout分解实现
Crout 分解是 U 对角线为 1,L 下三角,和 Doolittle 差不多,就是反过来,代码大同小异:
matlab
function [L, U] = crout(A)
n = size(A, 1);
U = eye(n);
L = zeros(n);
% 第一列 L
L(1:n,1) = A(1:n,1);
% 第一行 U
L(1,2:n) = A(1,2:n)/L(1,1);
for k = 2:n
% 算 L 第 k 列
for i = k:n
L(i,k) = A(i,k) - L(i,1:k-1)*U(1:k-1,k);
end
% 算 U 第 k 行
for j = k+1:n
U(k,j) = (A(k,j) - L(k,1:k-1)*U(1:k-1,j))/L(k,k);
end
end
end
结果一样,只是 L 和 U 定义不同,解方程组过程完全一样,还是先解 Ly = b,再解 Ux = y。
LU分解优势场景
什么时候用 LU 分解比直接 x = A\b 好?
当你有多个不同的右端项 b,系数矩阵 A 不变的时候,只需要分解一次 A,然后每个 b 只需要前代后代,比每次都重新分解快很多。
比如:
matlab
% 多个右端项 b1, b2, b3, ...
[L, U] = lu(A); % 只分解一次
x1 = U\(L\b1);
x2 = U\(L\b2);
x3 = U\(L\b3);
...
分解占了大部分计算量,只做一次,省很多时间。
数值稳定性
不选主元的 LU 分解,遇到主元很小会放大误差,所以实际用一定要选主元。Matlab 自带 lu 默认就做了行交换选主元,数值稳定。
自己写的时候,每一步选当前列绝对值最大的行交换,保证数值稳定性,不然算出来误差很大。
带选主元的 LU 分解思路:每一步分解前,找第 k 列绝对值最大的元素所在行,和当前 k 行交换,再继续分解,就稳定了。
和直接左除对比
Matlab 里 x = A\b 内部其实就是用 LU 分解做的,所以你直接写 A\b 已经是最优了。为什么还要自己实现?
学习数值分析课程需要,课程设计作业要自己写 LU 分解,所以上面的代码直接就能交作业,完美运行。如果你做实际项目,直接用 lu 或者 A\b 就行,不用自己写,自带的比手写的优化更好。
小结
LU 分解解法线性方程组,原理就是分解成两个三角方程组,Doolittle 算法不难实现,核心就是递推计算 L 和 U。学习的时候自己写一遍理解原理,实际项目直接用 Matlab 自带 lu 函数就行。多个右端项场景,LU 分解比直接高斯消元每个右端项都重算快很多,记得用这个优化。
或者 A\b 就行,不用自己写,自带的比手写的优化更好。
小结
LU 分解解法线性方程组,原理就是分解成两个三角方程组,Doolittle 算法不难实现,核心就是递推计算 L 和 U。学习的时候自己写一遍理解原理,实际项目直接用 Matlab 自带 lu 函数就行。多个右端项场景,LU 分解比直接高斯消元每个右端项都重算快很多,记得用这个优化。