LU分解法求解线性方程组Matlab实现

文章目录

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 分解比直接高斯消元每个右端项都重算快很多,记得用这个优化。

相关推荐
旖-旎1 小时前
《LeetCode 53 最大子数组和 || LeetCode 918 环形子数组的最大和》
c++·算法·leetcode·动态规划
变量未定义~1 小时前
单调栈+倍增思想 皇家守卫【算法赛】、单调队列 附近最小
算法
QN1幻化引擎1 小时前
给 AI 做一次「意识体检」——基于 QN1 幻化引擎的灵鉴意识识别框架与 DalinX V5 实测
大数据·数据结构·人工智能·算法·架构
拂拉氏1 小时前
【知识讲解】 AVL树从基本成员的介绍到核心接口的实现(插入、判断、删除等等)
数据结构·算法·avl树
可靠的仙人掌2 小时前
SAC(Soft Actor-Critic)算法底座
开发语言·算法·php
海石3 小时前
单调栈复健,顺便,牺牲一下吧,空间复杂度!一切献给AC
算法·leetcode
海石3 小时前
JS击败94%,Hard题想不到动态规划,那就用数组和栈试试
算法·leetcode
码少女4 小时前
数据结构——希尔排序
数据结构·排序算法
IDIOSF4 小时前
Apple Notes Liberator:把 Apple Notes 里的笔记搬出来
笔记·其他
星子yu5 小时前
【学习】怎么学好数据结构
数据结构·学习