参考文章:https://zhuanlan.zhihu.com/p/33184423
参考文件:《Matlab数学建模算法全收录》
2019.8.12更新:由于本人输入地过于仓促,文中出现了严重的错误,在此对之前的读者表示歉意。
2020.1.21更新
一,算法简介
1.1 内容简介
对于某个问题而言,没有很好的办法可以直接求得它的最优解,又或是直接求解太复杂。此时,我们尝试随机带入一些值,想要试一下,撞一下大运,看看能不能撞出比较好的解出来。
1.2 算法实现的过程
第一步,我们将根据可控变量的取值范围,来随机选取初始值,这是初次试值
第二步,接下来的试值中,我们会给每一个变量,产生一个微小的变化量delta=
注意,可控变量可能不是一维的,可能是二维或者更高维度。此时
第三步,我们自定义了的函数关系,这个函数是针对当前问题的,是变量到结果的映射。这个函数的输入端是向量
对于
1)如果新的结果更接近我们想要的结果(一般我们想要找到的是最值),我们就直接接受当前得到的
2)如果新的结果
并且,我们要求,如果你是以概率的方式接受了当前状态下的结果,那么在每次接受以后,温度将会下降,即
3)当 P 过于小的时候,或长时间找不到更优的解,那么结束算法。
1.3 重点讲解
1)不难发现,对于 P 而言,由于指数的分子恒为正,当指数分母趋于无穷大的时候,概率趋于1。这意味我们选择远离或者等于原来的结果的可能性会比较高。刚开始的时候,我们需要接触更多的区域,来试探解的结果,所以温度会设定的很高。随着算法的进行,温度逐渐降下来后,P 会逐渐变小。最后,我们能够得到一个尽可能接近最优解的解。
2)如何实现求解最大值呢?首先,使得
该算法计算的就是最大值。如果我们使得
二,算法的matlab编程
首先考虑一下算法的整体结构:输入端,循环处理的过程,输出端
输入端:
包括温度 T 、温度最小值 Tm 、温度下降系数 alpha 、变量矩阵 X 及其取值范围、自定义的函数 val()
变量矩阵 X 用于存储已知条件,它是一个 m*n 的矩阵,其中 m 代表已知点的个数,而 n 代表每个点的维度。
循环处理过程:
包括这几个部分,初始图像的绘制、初始值的求解、随机扰动的求解、是否采纳解的判断、绘图。
输出端:
主要需要最终的温度T,和最后得到的状态下的变量及其函数值
matlab编程
(原代码来源于https://zhuanlan.zhihu.com/p/33184423中的第二例,但是我将算法改进了一下,目的是使其更具有普遍性)
使用模拟退火算法求解到点集直线距离之和最短的点
%{
名称:模拟退火算法
用途:用于求某个函数的最值
输入:温度、温度最小值、温度下降系数、变量矩阵及其取值范围、自定义的函数val
输出:函数图像,以及最终得到的值y
注意:
%}
clc, clear
%用户输入参数
disp('请修改关于变量矩阵的自定义函数,初始及初始图像函数,还有循环过程中的绘图部分');
T_input=input('请以向量的形式依次输入tmp,tmp_min,alpha:');
tmp=T_input(1);tmp_min=T_input(2);alpha=T_input(3);
varied_data=input('请输入变量矩阵,每一列代表不同维度,每一行代表不同元素:');
extreme=input('需要计算最大值请输入-1,计算最小值请输入1:')
%实验参数,可以将上面内容注释掉,把这里取消注释进行实验
%tmp = 1000;
%tmp_min = 0.001;
%alpha = 0.98;
%extreme=-1;
%varied_data=[0 0;1.5 10;1.5 12; -2 11; -4 -8;-5 2;2 -1.5;4 -2.5;5 1;-2 -2;-3 8;3 6;-1.5 0;-2.5 0;0.1 0.2];
draw_dist(varied_data);
[varied_data_new,tmp,counter]=tuihuo(tmp,tmp_min,alpha,varied_data,extreme);
%%
%此为自定义函数,不同题目需要有不同的自定义函数,用户需要自行修改
function [result] = val(varied_data,varied_single_data)
x=varied_data(:,1);
y=varied_data(:,2);
xx=varied_single_data(1);
yy=varied_single_data(2);
result = 0;
num_of_dots = length(x);
for i = 1:num_of_dots
result = result + sqrt((xx - x(i))^2 + (yy - y(i))^2);
end
end
%%
%此为绘制初始图像的函数,针对不同的情况,用户需要自行更改
function []=draw_dist(varied_data)
x=varied_data(:,1);
y=varied_data(:,2);
x_min = floor(min(x));
y_min = floor(min(y));
x_max = ceil(max(x));
y_max = ceil(max(y));
resol = 10;
dx = x_min: 1/resol : x_max;
dy = y_min: 1/resol : y_max;
[xx, yy] = meshgrid(dx, dy);
zz = zeros(length(dy), length(dx));
for ii = 1:length(dx)
for jj = 1:length(dy)
zz(jj, ii) = val(varied_data,[dx(ii), dy(jj)]);
end
end
contour(xx, yy, zz);
hold on;
scatter(x, y, 'MarkerFaceColor','blue');hold on;
end
%%
%退火的主体函数
function [varied_data_new,tmp,counter]=tuihuo(tmp,tmp_min,alpha,varied_data,extreme)
%判断变量维度及个数
[m,n]=size(varied_data);
%自动生成初始值并判断
for i=1:1:n
varied_data_max(i)=max(varied_data(:,i));
varied_data_min(i)=min(varied_data(:,i));
varied_data_old(i) = (varied_data_max(i)-varied_data_min(i))*rand()+min(varied_data(:,i));%生成初始值
end
s_old = val(varied_data,varied_data_old);
s_new = s_old;
text_lt = text(0, 0, 'Init');
%计数器
counter = 0;
%退火的主体部分,一个循环
while(tmp > tmp_min)
%随机扰动,需要保证扰动后的变量处于定义域内
varied_new_mark=1;
while(sum(varied_new_mark))
for i=1:1:n
delta(i) = rand() - 0.5;%此处可以自行更改大小
varied_data_new(i)=varied_data_old(i) + delta(i);
if(varied_data_new(i)<varied_data_min(i) || varied_data_new(i)>varied_data_max(i))
varied_new_mark(i) = 1;%变量不能超出上下限
break
else
varied_new_mark(i) = 0;
end
end
end
s_new = val(varied_data,varied_data_new);
%求差值,extreme=1时,求最小值;=-1时,求最大值
dE = (s_new - s_old)*extreme;
%判断
j = judge(dE, tmp);
if(j)
s_old = s_new;
varied_data_old = varied_data_new;
end
%只有当dE < 0的情况下才降温
if(dE < 0)
%这里时循环过程中的绘图部分,请依照具体情况修改
delete(text_lt);
text_lt = text(-4, 6, {['Dist: ',num2str(s_old)];['Tmp: ', num2str(tmp)]});
scatter(varied_data_old(1), varied_data_old(2), '.');%这里可以修改字符出现的位置
pause(0.001);
tmp = tmp * alpha;
else
counter = counter + 1;
end
%当接受更差的解的概率太小时,若又长时间找不到更优的解,那么退出循环,结束算法
if(counter > 10000)%这里可以修改计数器的最大值
break;
end
end
end
%%
%{
名称:judge
用途:用于在随机扰动以后,判断是否选择这组数据
输入:扰动前后的变化量dE,退火温度t
输出:状态y,若y=1,为选择;y=0,为不选择
注意:
%}
function [y] = judge(dE, t)
if(dE < 0)
y = 1;
else
d = exp(-(dE / t));
if(d > rand)
y = 1;
else
y = 0;
end
end
end