Contents

浅记Matlab知识点

参考资料

learning outcomes

记录一些 Matlab 常用语法与技巧

基础概念

数据类型

类型 说明
double 默认浮点数,64 位
single 单精度浮点数,32 位
logical 布尔值 true/false
char 字符向量(单引号)
string 字符串数组(双引号)
cell 任意类型的容器(大括号)
struct 结构体,字段可任意命名
categorical 类别型数据
datetime / duration 时间/时间间隔
table / timetable 表格数据

real(x)imag(x) 用于提取复数的实部和虚部,它们不是数据类型

  • 字符串

    • 字符串比较
      strcmp(s1, s2) 比较字符串 s1 和 s2 是否相等
      strncmp(s1, s2, n) 比较字符串 s1 和 s2 的前 n 个字符是否相等
      strcmpi(s1, s2) 忽略大小写,比较字符串 s1 和 s2 是否相等
      strncmpi(s1, s2, n) 同理

    • 字符串查找与替换
      findstr(s1, s2)
      strrep(s1, s2, s3) 将字符串 s1 中的所有 s2 都替换成 s3

变量管理

命令 作用
who 列出工作区变量名
whos 列出变量名、大小、字节数、类等详细信息
clear 删除全部变量
clear var1 var2 删除指定变量
clearvars -except varlist 保留特定变量,清除其余
save filename var1 var2 将变量保存为 .mat 文件
load filename 读取 .mat 文件到工作区

工作空间与路径

1
2
3
4
5
6
7
8
% 查看当前路径
pwd

% 添加路径(仅本次会话有效)
addpath('myFunctions')

% 永久添加路径(保存到 MATLABPATH)
savepath

数组与矩阵

创建矩阵

方法 示例 说明
直接定义 A = [1,2,3; 4,5,6]; 用空格/逗号分列,分号换行
zeros, ones Z = zeros(3,4); 全零 / 全一矩阵
eye I = eye(5); 单位矩阵
rand, randn R = rand(2,3); 均匀/正态随机数
linspace, logspace x = linspace(0,pi,100); 等间距向量
repmat B = repmat(A,2,3); 重复矩阵
meshgrid, ndgrid [X,Y] = meshgrid(-2:0.1:2); 生成网格坐标
sparse S = sparse(A); 转为稀疏矩阵
gallery M = gallery('poisson',5); 生成常用测试矩阵

[A, B] 拼接矩阵时,行数必须相同
[A; B] 列数必须相同

  • 常用矩阵函数

    1
    2
    3
    4
    5
    6
    
    % 生成特殊矩阵
    M = magic(5);          % 魔方矩阵
    V = vander(1:5);       % 范德蒙德矩阵
    H = hilb(5);           % 希尔伯特矩阵
    C = pascal(5);         % 帕斯卡矩阵
    P = companion([1 -5 6]); % 伴随矩阵
    

索引与切片

索引方式 示例 说明
线性索引 A(3) 按列优先顺序的第 3 个元素
逻辑索引 A(A>0) 取所有正元素
: 省略 A(:,3) 取第 3 列
end 关键字 A(end-1:end,:) 取倒数第二行到最后一行
子矩阵 A(1:2, 3:end) 取前两行、从第 3 列到末尾

A(:,[2,4]) = [] 删除第 2、4 列

矩阵变形与运算

操作 示例 说明
reshape B = reshape(A,4,3); 改变尺寸(元素不变)
transpose A.' 转置(不共轭)
ctranspose A' 共轭转置
diag d = diag(A); / D = diag(v); 提取/生成对角矩阵
triu / tril U = triu(A); 上/下三角矩阵
rot90 R = rot90(A,2); 逆时针旋转 180°
fliplr / flipud B = fliplr(A); 左右/上下翻转
permute B = permute(A,[2 1 3]); 维度置换
reshape, squeeze A = squeeze(B); 去除单例维度
bsxfun / 隐式扩展 C = A + B; (A: m×1, B: 1×n) 自动广播(R2016b+)

稀疏矩阵

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
% 创建稀疏矩阵
i = [1 2 3]; j = [1 2 3]; v = [10 20 30];
S = sparse(i,j,v,5,5);   % 5×5 稀疏矩阵


% 常用稀疏函数
nnz(S)          % 非零元素个数
issparse(S)     % 是否稀疏
full(S)         % 转为全矩阵
spdiags(d,0,n,n) % 对角稀疏矩阵

特殊容器

类型 创建方式 常用操作
Cell array C = {1, 'text', [1 2]}; {} 访问,C{2} 取内容,C(2) 取子单元
Struct array s(1).name = 'Alice'; s(1).age = 28; s(1).name, s(2).age
Table T = table(A,B,'VariableNames',{'A','B'}); T.A, T{:,2}
Categorical cat = categorical({'red','blue','red'}); categories(cat), ismember(cat,'red')

运算符与常用函数

基本算术运算

运算符 说明
+ - * / ^ 加、减、乘、除、乘方(矩阵运算)
.* ./ .^ 对应元素的点运算
\ 左除(求解线性方程 AX = B

* 为矩阵乘法,.* 为对应元素相乘
/ 为右除(A/B = A*inv(B)),./ 为对应元素除法

点运算示例

1
2
3
x = 0.1:0.3:1;               % [0.1 0.4 0.7 1.0]
y = sin(x) .* cos(x);       % 对应元素相乘
z = (x.^2 + 1) ./ (x + 2);  % 对应元素除法

关系与逻辑运算

运算符 示例 说明
< > <= >= == ~= A > 0 关系比较,返回逻辑矩阵
& | A & B 按元素的逻辑与/或
&& || if a && b 短路逻辑,仅用于标量
~ ~A 逻辑非

位运算(整数)

运算符 示例 说明
bitand, bitor, bitxor bitand(a,b) 位与、位或、位异或
bitshift bitshift(a,2) 左移 2 位
bitcmp bitcmp(a) 位取反

常用数学函数

类别 常用函数 示例
指数/对数 exp, log, log10, log2 y = exp(x);
三角函数 sin, cos, tan, asin, acos, atan
sind, cosd, tand(角度)
y = sin(pi/4);
y = sind(45);
双曲函数 sinh, cosh, tanh y = sinh(x);
特殊函数 erf, erfc, gamma, psi y = erf(x);
统计函数 mean, median, std, var, mode, skewness, kurtosis m = mean(v);
线性代数 det, rank, trace, norm, cond, pinv d = det(A);
随机数 rand, randn, randi, randperm r = randi([1,10],5,1);
离散卷积 conv, deconv, filter, conv2 y = conv(x,h);
多项式 poly, polyval, polyfit, roots, polyder, polyint p = polyfit(x,y,3);
插值 interp1, interp2, interp3, griddedInterpolant, scatteredInterpolant yi = interp1(x,y,xi,'spline');
求根 fzero, roots, vpasolve (Symbolic) r = fzero(@(x) x^3-2,1);
求和/累积 sum, cumsum, prod, cumprod s = sum(A,2);

format 用于控制显示精度,常用选项有 format short, format long, format bank, format rat
它不影响数值的内部存储,只影响命令窗口的显示

控制结构

条件语句

1
2
3
4
5
6
7
if x > 0
    disp('正数');
elseif x == 0
    disp('零');
else
    disp('负数');
end

switch 语句

1
2
3
4
5
6
7
8
switch day
    case 'Mon'
        disp('星期一');
    case {'Tue','Wed','Thu'}
        disp('工作日');
    otherwise
        disp('周末');
end

循环语句

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
% for 循环
for k = 1:10
    if mod(k,2)==0, continue; end   % 跳过偶数
    fprintf('k = %d\n',k);
end

% while 循环
i = 1;
while i <= 5
    disp(i);
    i = i + 1;
end

错误捕获

1
2
3
4
5
try
    riskyOperation();
catch ME
    warning('出现错误: %s', ME.message);
end

函数编程

函数文件结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
function [y1, y2] = myFun(x, a, b)
%MYFUN 计算 y1 = a*x + b, y2 = a*x.^2 + b
%   输入:
%       x - 向量或标量
%       a, b - 标量系数
%   输出:
%       y1, y2 - 计算结果

    y1 = a*x + b;
    y2 = a*x.^2 + b;
end

注意,文件名必须与函数名相同(myFun.m),注释可用于 help

匿名函数与函数句柄

1
2
3
4
5
6
7
8
% 匿名函数
f = @(x,y) x.^2 + y.^2;
val = f(3,4);   % 25


% 将已有函数转为句柄
g = @sin;
g(pi/2)          % 1

可变参数

1
2
3
4
5
6
7
8
function out = varDemo(varargin)
% 可接受任意数量的输入参数
    n = nargin;          % 实际输入参数个数
    out = cell(1,n);
    for k = 1:n
        out{k} = varargin{k}^2;
    end
end

对应的输出参数可使用 varargout

持久化变量与全局变量

1
2
3
4
5
6
7
8
9
function count = counter(reset)
% 计数器示例
    persistent cnt    % 声明持久化变量 cnt
    if isempty(cnt) || (nargin && reset)
        cnt = 0;
    end
    cnt = cnt + 1;
    count = cnt;
end
1
2
3
% 声明全局变量
global gVar
gVar = 42;

子函数、私有函数、类

同一文件中,主函数下方的额外函数称为子函数,仅在该文件内部可见

私有函数放在 private 子文件夹中,仅同一文件夹下的函数可调用

可以使用 classdef 定义类,支持属性、方法、事件等面向对象高级特性(MATLAB OOP)

绘图与可视化

基本二维绘图

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
x = linspace(0,2*pi,200);
y = sin(x);

plot(x,y,'r-', 'LineWidth',1.5);    % 红色实线
hold on;

plot(x,cos(x),'b--','LineWidth',1);

legend('sin','cos');
title('正弦与余弦');
xlabel('x');
ylabel('y');
grid on;
  • 常用绘图属性

    属性 示例 说明
    'Color' 'r' / [0.2 0.6 0.8] 颜色
    'LineStyle' '--' 线型
    'Marker' 'o' 数据点标记
    'MarkerSize' 8 标记大小
    'MarkerFaceColor' 'y' 标记填充颜色
  • 常用图形属性与导出

    命令 说明
    axis equal 坐标轴等比例
    axis tight 紧凑坐标范围
    grid minor 次网格
    box on / box off 边框
    saveas(gcf,'myFig.png') 保存为 PNG
    exportgraphics(gcf,'myFig.pdf','ContentType','vector') 高质量矢量图
  • 颜色映射、光照、视角

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    % 颜色映射
    cmap = jet(256);
    colormap(cmap);
    caxis([-1 1]);   % 颜色范围
    
    
    % 光照与材质
    lighting gouraud;
    material shiny;
    camlight headlight;
    

子图布局

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
% 传统 subplot
subplot(2,2,1); plot(x, sin(x));
subplot(2,2,2); plot(x, cos(x));


% 新的 tiledlayout(R2019b+)
t = tiledlayout(2,2,'TileSpacing','Compact');
nexttile; plot(x, sin(x));
nexttile; plot(x, cos(x));
title(t,'正弦与余弦示例');

三维绘图

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
% 参数曲线
t = linspace(0,2*pi,200);
x = sin(t);
y = cos(t);
z = t;
plot3(x,y,z,'m-','LineWidth',1.5);
grid on;
xlabel('x'); ylabel('y'); zlabel('z');


% 曲面绘制
[X,Y] = meshgrid(-2:0.1:2);
Z = X.^2 - Y.^2;
figure;
surf(X,Y,Z,'EdgeColor','none');
colormap jet;
colorbar;
shading interp;
view(30,30);

数据分析与统计

描述统计

函数 说明
max, min 最大/最小值(可返回索引)
sum, prod 求和/乘积
cumsum, cumprod 累计求和/乘积
corrcoef 皮尔逊相关系数矩阵
cov 协方差矩阵
prctile 百分位数
histogram, boxplot 可视化分布
均值 mean(data) 数据的算术平均值
中位数 median(data) 数据的中间值
标准差 std(data) 数据的标准差
极差 range(data) 最大值与最小值之差
偏度 skewness(data) 数据分布的偏斜程度
峰度 kurtosis(data) 数据分布的峰态

线性回归与多项式拟合

1
2
3
4
5
6
7
8
9
% 线性最小二乘
p = polyfit(x, y, 1);    % 1 阶多项式(直线)
yFit = polyval(p, x);
plot(x,y,'o', x,yFit,'-r');


% 多项式拟合(高阶)
p3 = polyfit(x, y, 3);
y3 = polyval(p3, x);

插值与样条

1
2
3
4
5
6
7
8
9
xi = linspace(min(x),max(x),200);
yi_lin  = interp1(x,y,xi,'linear');
yi_spl  = interp1(x,y,xi,'spline');
yi_pch  = interp1(x,y,xi,'pchip');


% 二维样条
[Xi,Yi] = meshgrid(linspace(0,1,50));
Zi = interp2(X,Y,Z,Xi,Yi,'cubic');

高级统计(Statistics Toolbox)

功能 示例
ttest, ttest2 单样本/双样本 t 检验
anova1, anova2 方差分析
fitlm 线性模型(含系数显著性)
fitglm 广义线性模型
kmeans K 均值聚类
pca 主成分分析

数值计算

微分与积分

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
% 前向差分
dx = diff(y)./diff(x);


% 数值积分(推荐使用 integral 系列)
I1 = integral(@(t) exp(-t.^2),0,1);    % 标量函数
I2 = integral2(@(x,y) x.*y,0,1,0,2);    % 二维积分
[I3,err] = integral(@(t) sin(t)./t,0,Inf);    % 无穷区间


% 梯形法、辛普森法(已被 integral 取代)
I_trap = trapz(x,y);

quadquadl 已被 integralintegral2integral3 取代,后者支持向量化、奇异点、无限区间等

求解方程

目标 函数 示例
单变量根 fzero r = fzero(@(x) x^3-2,1);
多变量非线性方程组 fsolve (Optimization Toolbox) x = fsolve(@myfun, x0);
线性方程组 \(左除) x = A\b;
多项式根 roots r = roots([1 -5 6]);

优化

优化问题的核心是定义目标函数和约束条件,然后调用合适的求解器进行求解

无约束最小化(Unconstrained Minimization)

1
2
fun = @(x) (x-2)^2 + sin(5*x);
[xmin,fval] = fminunc(fun,0);

fun 是一个匿名函数,表示目标函数:$(x-2)^2 + \sin(5x)$,这是我们想要最小化的函数
fminunc 是用于求解无约束优化问题的函数,第二个参数 0 是初始猜测值,即从 $x=0$ 开始搜索最优解
返回值 xmin 是使目标函数取得最小值的 $x$,fval 是对应的最小函数值

注意,fminunc 在实际使用中通常需要配合 optimoptions 设置优化选项,比如算法类型、容差等,以确保收敛和性能

有约束最小化(Constrained Minimization)

1
2
3
4
A = [1 2; -1 2];
b = [4; 2];
lb = [0 0];
[xc,fvalc] = fmincon(fun, [1 1], A, b, [], [], lb, [], []);

fmincon 用于求解带约束的非线性优化问题
[1 1] 是变量的初始猜测值
Ab 定义了线性不等式约束:$A \cdot x \leq b$,具体为:

$$ \begin{cases} 1 \cdot x_1 + 2 \cdot x_2 \leq 4 \ -1 \cdot x_1 + 2 \cdot x_2 \leq 2 \end{cases} $$

[] 表示没有线性等式约束
lb = [0 0] 是变量的下界约束,即 $x_1 \geq 0, x_2 \geq 0$
其他参数为空,表示无上界或非线性约束

返回值 xc 是满足约束条件并使目标函数最小的变量值,fvalc 是对应的最小函数值

常微分方程(ODE)

1
2
3
4
% 示例:dy/dt = -2*y, y(0)=1
odefun = @(t,y) -2*y;
[t,y] = ode45(odefun, [0 5], 1);
plot(t,y);

常用求解器:

求解器 适用场景
ode45 非刚性(中等精度)
ode23 低精度、快速
ode15s 刚性问题
ode23t 事件驱动、刚性
ode113 高精度、光滑问题
ode15i 隐式微分代数方程 (DAE)

可通过 odeset 设置容差、事件函数、输出函数等。

矩阵分解与特征

分解 函数 备注
LU [L,U,P] = lu(A) 用于求解线性方程
QR [Q,R] = qr(A) 正交化
Cholesky R = chol(A) 对称正定矩阵
SVD [U,S,V] = svd(A) 奇异值分解,适用于任意矩阵
特征值 [V,D] = eig(A) eig(A,'vector') 返回特征值向量
inv(A) 推荐用 A\B 代替 inv(A)

为什么推荐用 A\B 代替 inv(A)

直接计算矩阵的逆 inv(A) 会显著放大数值误差,尤其当 矩阵接近奇异或条件数较大 时,误差更明显
A\B 是 MATLAB 推荐的求解线性方程组的方法,它底层使用高效且稳定的 矩阵分解(如 LU 分解)来求解 $Ax = B$,避免了显式计算逆矩阵

1
A_inv = A \ eye(size(A));

这里 eye(size(A)) 是与 A 同尺寸的单位矩阵,A \ I 实际上是求解 $AX = I$,解 $X$ 就是 $A$ 的逆矩阵

  • inv(A)A\B 来解线性方程组,以及如何用 A\eye(size(A)) 来计算逆矩阵

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    
    % 构造一个方阵 A 和向量 b
    A = [3, 1, 2;
        6, 3, 4;
        3, 1, 5];
    b = [0.5; 1; 1.5];
    
    % 1. 用 inv(A) 显式求逆,再乘以 b 求解 x
    x_inv = inv(A) * b;
    
    % 2. 用 A\b 直接求解线性方程组
    x_backslash = A \ b;
    
    % 3. 计算矩阵 A 的逆矩阵(不推荐,但演示用)
    A_inv = A \ eye(size(A));
    
    % 验证 A * A_inv 是否接近单位矩阵
    identity_approx = A * A_inv;
    
    % 显示结果
    disp('用 inv(A)*b 求解结果 x_inv:');
    disp(x_inv);
    
    disp('用 A\b 求解结果 x_backslash:');
    disp(x_backslash);
    
    disp('用 A\eye(size(A)) 计算的逆矩阵 A_inv:');
    disp(A_inv);
    
    disp('A * A_inv(应接近单位矩阵):');
    disp(identity_approx);
    

    x_invx_backslash 两者结果非常接近,但 A\b 更稳定且速度更快
    A_inv 是矩阵 A 的逆矩阵,理论上满足 A * A_inv = Iidentity_approx 会非常接近单位矩阵
    在实际应用中,求解线性方程组建议用 A\b,而不是先求逆再乘

符号计算(Symbolic Math Toolbox)

MATLAB 的符号计算工具箱允许你对数学表达式进行符号操作,而不是数值计算
这样你可以进行微分、积分、方程求解等数学推导,得到的是 解析表达式而非数值结果,适合需要解析解的数学、工程和科学计算任务

1
2
3
4
5
6
7
8
% 声明符号变量 x 和 y,之后对它们的运算都是符号运算
syms x y

f = sin(x) * exp(y);
dfdx = diff(f, x);      % 对函数 ( f ) 关于 ( x ) 求导
intf = int(f, y);       % 对函数 ( f ) 关于 ( y ) 积分
sol = solve(x^2 - 2 == 0, x);  % 解方程 ( x^2 - 2 = 0 ),求 ( x ) 的根
pretty(sol)             % 以可读形式显示
  • 示例:求解微分方程的解析解
    假设我们有一个简单的常微分方程 $\frac{dy}{dx} + y = \sin(x)$
    要求解函数 $y(x)$

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    syms y(x)
    
    % 定义微分方程
    ode = diff(y, x) + y == sin(x);
    
    % 求解析解
    sol = dsolve(ode);
    
    % 显示结果
    disp('微分方程的解析解为:');
    pretty(sol)
    

    运行后,MATLAB 会返回一个解析解,通常是一个符号表达式,类似于:

    $$ y(x) = \frac{1}{2} \left(\sin(x) - \cos(x)\right) + C e^{-x} $$

    $\frac{1}{2}(\sin(x) - \cos(x))$ 是该微分方程的一个特解
    $C e^{-x}$ 是齐次方程对应的通解,$C$ 是积分常数

性能优化与调试

预分配

1
2
3
4
5
N = 1e6;
A = zeros(N,1);    % 预分配,避免循环中自动扩展
for k = 1:N
    A(k) = sin(k);
end

向量化

1
2
3
4
5
6
7
8
% 循环版
for i = 1:numel(x)
    y(i) = sin(x(i))*cos(x(i));
end


% 向量化版(更快)
y = sin(x).*cos(x);

并行计算

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
% 并行 for
parfor i = 1:1000
    out(i) = heavyComputation(i);
end


% GPU 加速
gX = gpuArray(X);
gY = fft(gX);
Y = gather(gY);    % 拉回 CPU

前提:需要 Parallel Computing Toolbox

Profiling 与计时

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
tic;
% 代码块
% 。。。。。。
elapsed = toc;


profile on;
myFunction();    % 需要分析的函数
profile viewer;    % 打开分析报告
profile off;

调试技巧

命令 说明
dbstop if error 出错时自动进入调试模式
keyboard 手动进入交互调试
dbcont, dbquit 继续或退出调试
disp, fprintf 打印变量值
mlint / codeAnalyzer 静态代码检查

常见错误与注意事项

常见错误 说明 纠正方式
索引越界 A(5) 超出矩阵维度 使用 size(A) 检查,或 end 关键字
点运算忘记 A*B(矩阵乘)误写为 A.*B(对应元素乘) 根据需求选择 *.*
format 与存储混淆 format long 只影响显示,数值本身不变
clear all 过度使用 会清除路径、函数缓存,导致性能下降 使用 clearvars,或 clear 指定变量
循环中动态增长数组 速度极慢 预分配或使用 cell/struct 进行累积
使用 == 比较浮点数 直接相等判断不可靠 使用 abs(a-b) < epsisequaln
未开启向量化 代码慢 利用矩阵运算、bsxfun、隐式扩展
for 循环中的 i 与 $\sqrt{-1}$ 冲突 i 被重新定义为变量 在 MATLAB 中 i 和 j 默认是复数单位,表示 $\sqrt{-1}$。但 i 和 j 也可以被当作普通变量名使用,故需要避免使用 i/j 作为循环计数器
路径冲突 同名函数被自定义文件覆盖 使用 which -all funcName 检查路径优先级

实例代码

模拟退火算法

下面是模拟退火(Simulated Annealing)的通用实现,并用其求解 Rastrigin 函数的全局最小值

核心函数 simulatedAnnealing.m

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
function [x_best, f_best, history] = simulatedAnnealing(fun, x0, options)
%   输入参数
%   ----------
%   fun     : 目标函数句柄,要求返回标量值(最小化问题)。
%   x0      : 初始解(列向量),维度由问题决定。
%   options : 结构体,包含以下字段(若缺省则使用默认值)
%       .T0                - 初始温度 (default: 100)
%       .T_min             - 终止温度 (default: 1e-3)
%       .alpha             - 冷却系数 (0<alpha<1, default: 0.9)
%       .maxIterPerTemp    - 每个温度下的迭代次数 (default: 100)
%       .maxIter           - 总迭代上限 (default: 1e4)
%       .stepSize          - 产生新解的扰动尺度 (default: 0.1*range(x0))
%       .bounds            - 可选的变量上下界,形式为 [lb, ub],每行对应一个维度
%       .verbose           - 是否在控制台打印进度 (default: true)
%
%   输出参数
%   ----------
%   x_best  : 迄今为止找到的最优解(列向量)。
%   f_best  : 对应的目标函数值。
%   history : 结构体,记录每一步的温度、当前解、目标函数值等信息,便于后期绘图分析。
%
%   参考文献
%   ----------
%   Kirkpatrick, S., Gelatt, C. D., & Vecchi, M. P. (1983).
%   Optimization by simulated annealing. *Science*, 220(4598), 671-680.

    % ---------- 参数检查与默认值 ----------
    if nargin < 3, options = struct(); end
    if ~isfield(options, 'T0'),                options.T0 = 100; end
    if ~isfield(options, 'T_min'),             options.T_min = 1e-3; end
    if ~isfield(options, 'alpha'),             options.alpha = 0.9; end
    if ~isfield(options, 'maxIterPerTemp'),    options.maxIterPerTemp = 100; end
    if ~isfield(options, 'maxIter'),           options.maxIter = 1e4; end
    if ~isfield(options, 'stepSize')
        % 默认步长取变量范围的 10%(若没有给出 bounds,则取 x0 的 10%)
        if isfield(options, 'bounds')
            rng = options.bounds(:,2) - options.bounds(:,1);
        else
            rng = max(abs(x0), [], 1);
        end
        options.stepSize = 0.1 * max(rng, eps);
    end
    if ~isfield(options, 'bounds'), options.bounds = []; end
    if ~isfield(options, 'verbose'), options.verbose = true; end

    % ---------- 初始化 ----------
    T = options.T0;                     % 当前温度
    x_curr = x0(:);                     % 保证列向量
    f_curr = fun(x_curr);               % 当前目标函数值
    x_best = x_curr;
    f_best = f_curr;

    % 记录历史信息(可选)
    history.T = zeros(options.maxIter,1);
    history.f = zeros(options.maxIter,1);
    history.x = zeros(numel(x0), options.maxIter);
    iter = 0;

    % ---------- 主循环 ----------
    while T > options.T_min && iter < options.maxIter
        % 在当前温度下进行若干次邻域搜索
        for k = 1:options.maxIterPerTemp
            iter = iter + 1;
            % ---- 产生邻域解 ----
            % 这里采用高斯扰动,步长随温度线性衰减(也可以自行改为其他方式)
            sigma = options.stepSize * (T / options.T0);
            x_new = x_curr + sigma .* randn(size(x_curr));

            % 若给出了变量上下界,则进行投影
            if ~isempty(options.bounds)
                x_new = max(x_new, options.bounds(:,1));
                x_new = min(x_new, options.bounds(:,2));
            end

            f_new = fun(x_new);
            delta = f_new - f_curr;

            % ---- 接受准则 ----
            if delta < 0                         % 新解更好,必接受
                accept = true;
            else                                 % 以概率 e^{-delta/T} 接受
                accept = rand() < exp(-delta / T);
            end

            if accept
                x_curr = x_new;
                f_curr = f_new;
                % 更新全局最优
                if f_curr < f_best
                    x_best = x_curr;
                    f_best = f_curr;
                end
            end

            % ---- 记录历史 ----
            history.T(iter) = T;
            history.f(iter) = f_curr;
            history.x(:,iter) = x_curr;

            % ---- 终止条件(可选) ----
            if iter >= options.maxIter
                break;
            end
        end

        % ---- 降温 ----
        T = T * options.alpha;

        % ---- 打印进度(可选) ----
        if options.verbose && mod(iter, 500) == 0
            fprintf('Iter = %d, T = %.4e, f_best = %.6e\n', iter, T, f_best);
        end
    end

    % 去除未使用的历史记录
    history.T = history.T(1:iter);
    history.f = history.f(1:iter);
    history.x = history.x(:,1:iter);
end

T0 为初始温度,alpha 为冷却系数(常取 0.8~0.99),maxIterPerTemp 为每个温度的内部迭代次数

采用 高斯扰动 x_new = x_curr + sigma*randn,其中 sigma 随温度线性衰减,使得在高温时搜索范围大,低温时搜索更细致

若新解更好则必接受;否则以概率 exp(-ΔE/T) 接受,模拟“热运动”

简单的几何降温 T = alpha * T,也可以改为对数或指数等更复杂的调度

温度低于 T_min 或迭代次数超过上限 maxIter 时终止迭代

  • 邻域解投影(clip)
    若提供了变量上下界 bounds,则对产生的邻域解进行投影
    目的是确保解始终满足变量的上下界约束

    假设变量 $x$ 的每个分量 $x_i$ 都有对应的下界 $lb_i$ 和上界 $ub_i$,即:$lb_i \leq x_i \leq ub_i$
    算法在搜索新解时,通常会对当前解做扰动(比如加上一个随机噪声),这可能导致新解某些分量超出预设的上下界

    1
    2
    3
    4
    
    if ~isempty(options.bounds)
        x_new = max(x_new, options.bounds(:,1));    % 将所有小于下界的分量拉回到下界
        x_new = min(x_new, options.bounds(:,2));    % 将所有大于上界的分量拉回到上界
    end
    

    通过对每个分量分别进行限制:如果 $x_i^{new} < lb_i$,就将其设为 $lb_i$;如果 $x_i^{new} > ub_i$,就将其设为 $ub_i$;否则保持不变
    这样保证了新解始终在允许的搜索空间内

使用示例:Rastrigin 函数(常用的多峰基准函数)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
% demo_sa_rastrigin.m
clear; clc; close all;

% 目标函数(Rastrigin)
%   f(x) = 10*n + sum(x_i^2 - 10*cos(2*pi*x_i))
%   全局最小值位于 x = 0,f(0) = 0
rastrigin = @(x) 10*numel(x) + sum(x.^2 - 10*cos(2*pi*x));

% 维度(这里取 2 维,便于画图)
n = 2;

% 初始解(随机在 [-5.12, 5.12] 区间)
lb = -5.12*ones(n,1);
ub =  5.12*ones(n,1);
x0 = lb + (ub-lb).*rand(n,1);

% 模拟退火参数
opts = struct();
opts.T0 = 100;               % 初始温度
opts.alpha = 0.85;           % 冷却系数
opts.maxIterPerTemp = 200;   % 每个温度的迭代次数
opts.maxIter = 5e4;          % 总迭代上限
opts.bounds = [lb, ub];      % 变量上下界
opts.verbose = true;         % 打印进度

% 运行模拟退火
[x_opt, f_opt, hist] = simulatedAnnealing(rastrigin, x0, opts);

fprintf('\n=== 结果 ===\n');
fprintf('最优解 x* = [%g, %g]\n', x_opt(1), x_opt(2));
fprintf('对应目标值 f(x*) = %g\n', f_opt);

% ------------------- 绘制收敛曲线 -------------------
figure;
semilogy(hist.f, 'b-');
xlabel('迭代次数');
ylabel('目标函数值 (log)');
title('模拟退火收敛曲线');
grid on;

% ------------------- 2D 目标函数等高线 + 轨迹 -------------------
% 只在 2 维时绘制
if n == 2
    % 生成网格
    [X,Y] = meshgrid(linspace(lb(1), ub(1), 200), linspace(lb(2), ub(2), 200));
    Z = arrayfun(@(x,y) rastrigin([x;y]), X, Y);

    figure;
    contourf(X, Y, Z, 50, 'LineColor','none');
    hold on;
    plot(hist.x(1,:), hist.x(2,:), 'w.-', 'MarkerSize', 5);
    plot(x_opt(1), x_opt(2), 'rp', 'MarkerSize', 12, 'MarkerFaceColor','r');
    colorbar;
    xlabel('x_1'); ylabel('x_2');
    title('Rastrigin 函数等高线 + 模拟退火搜索轨迹');
    axis equal tight;
end
% --------------------------------------------------------------------
  • 运行结果(示例)

    1
    2
    3
    4
    5
    6
    
    Iter = 500, T = 2.3782e-02, f_best = 1.234567e-03
    Iter = 1000, T = 5.6471e-03, f_best = 3.456789e-04
    ...
    === 结果 ===
    最优解 x* = [0.00123, -0.00087]
    对应目标值 f(x*) = 0.000123
    
  • 如何在实际问题中使用

    • 定义目标函数
      对于 连续 优化问题,只需返回标量目标值即可
      若是 离散组合 优化(如旅行商问题),只需改写邻域生成方式(例如交换两个城市的顺序)以及约束处理

    • 设置参数
      T0 越大,初始搜索范围越广,但收敛速度会变慢
      alpha 越接近 1,降温更慢,搜索更细致;但迭代次数会增多
      maxIterPerTempstepSize 共同决定每个温度层的“探索强度”

      常用经验:T0 ≈ (ΔE_max) / log(acceptance_rate),其中 ΔE_max 为目标函数在随机点的最大差值,acceptance_rate 设为 0.8~0.9

    • 约束
      若有 边界约束,直接使用 options.bounds,函数会自动投影
      对于 更复杂的约束(如线性/非线性不等式),可以在生成新解后使用 罚函数投影 方法自行处理

    • 调参技巧
      先在 小规模(低维、松弛的约束)上调试参数,观察收敛曲线
      若出现 早熟收敛(很快停在局部最优),可以尝试增大 T0alpha(放慢降温);增加 maxIterPerTemp(在每个温度层多搜索);使用 自适应步长sigma = sigma0 * (1 + c*iter/ maxIter)

    MATLAB 自带的 simulannealbnd 已实现了更为完善的退火调度、约束处理和并行加速
    其他考虑的方向:比如加入 自适应冷却多点并行搜索、或是针对特定离散问题的邻域设计

模块化

如果有多个 .m 文件,想要实现模块化调用,可以通过函数调用的方式来组织代码

将代码封装成函数

每个功能模块写成一个函数,保存在单独的 .m 文件中,文件名和函数名一致

  • 文件 moduleA.m

    1
    2
    3
    4
    
    function output = moduleA(input)
        % 这里写模块A的代码
        output = input^2;    % 示例操作
    end
    
  • 文件 moduleB.m

    1
    2
    3
    4
    
    function result = moduleB(x, y)
        % 模块B的代码
        result = x + y;
    end
    

路径管理

确保所有模块函数所在的文件夹都在 MATLAB 的路径中。可以通过:

1
addpath('你的模块文件夹路径')

或者在 MATLAB 界面中使用“Set Path”工具添加路径

在主程序中调用这些函数

主程序可以是一个脚本或者函数,调用这些模块函数:

1
2
3
4
5
% 主程序 main.m
a = 5;
b = moduleA(a);    % 调用moduleA
c = moduleB(b, 10);    % 调用moduleB
disp(c);

使用类(可选)

如果模块之间关系复杂,可以考虑用面向对象编程(OOP),定义类和方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
classdef MyModule
    methods
        function output = moduleA(obj, input)
            output = input^2;
        end

        function result = moduleB(obj, x, y)
            result = x + y;
        end
    end
end

调用:

1
2
3
obj = MyModule();
a = obj.moduleA(5);
b = obj.moduleB(a, 10);