一、什么是模拟退火(Simulated Annealing)
模拟退火算法(SA)是一种基于概率的通用优化算法,用来在一个大的搜寻空间内寻找问题的最优解。
模拟退火算法从某一较高初温出发,伴随温度参数的不断下降,结合概率突跳特性在解空间中随机寻找目标函数的全局最优解,即即在局部最优解能概率性地跳出并最终趋于全局最优。
二、模拟退火的基本概念
1 温度(T)
在物理中我们知道,温度越高,物体内部分子热运动越剧烈;温度越低,分子热运动越平静。
温度在模拟退火算法中的作用体现可以类比物理中分子的热运动:温度越高,分子活动越剧烈,对当前状态产生的偏移越大。模拟退火算法会根据当前温度随机对当前状态进行偏移得到新状态。
令X,为当前状态,Xh+1为随机偏移得到的新状态:
在模拟退火算法中,温度会从较高的起始温度逐渐降低到结束温度。起始温度和结束温度作为模拟退火的两个参数,需要根据问题题的实际情况选取。
2 温度衰减率(K)
温度衰减率即在降温过程中,温度降低的速度,即后一时刻的温度与前一时刻温度的比值。
在模拟退火算法中,温度衰减率作为参数之一,可以根据问题题的具体情况和需求进行设置。
3、Metropolis准则⭐⭐⭐
Metropolis准则是确定是否选择当前解的规则。模拟退火算法会随机对当前状态进行偏移产生新状态,如果新状态对应的解比当前前状态对应的解更优,就采用该新状态;如果新解劣于当前解,那么就以一定概率采用新状态或保持不变。这种选择解的规则就称之为Metropolis准则。
总的来说,对于新解En+1和当前解En,采用新解的概率:
求最小值:
求最大值:
4、Markov链(L)
Markov链是概率论和数理统计中的概念。Markov链在模拟退火算法中的作用是:Markov链的长度决定了在每个温度下,对当前状态进行偏移的次数。
令L为Markov链的长度,L为模拟退火算法的参数之一,需要根据问题的实际情况选取。
⭐⭐⭐5、为什么要进行随机偏移、为什么Metropolis准则要以一定概率选则劣解?
避免陷入局部最优:在搜索初期,温度较高时,算法需要有较大的自由度来探索解空间。如果只接受优解(比当前解更好的解),那么算法很容易陷入局部最优解,因为一旦进入局部最优区域,就很难再跳出来。通过以一定概率接受劣解,算法可以跳出局部最优解的陷阱,继续在解空间中搜索,从而有可能找到全局最优解。
三、模拟退火的步骤
模拟退火遵循以下步骤
① 从初始温度开始
②对于当前温度T,对状态进行L次随机偏移
③对于每次偏移,对偏移后的新状态进行求解,并根据Metropolis准则选择是否采用该新状态。
④温度衰减
⑤如果当前温度低于结束温度,算法结束
四、例
点点之遥
题目描述
在二维平面上有几个点,其中第i个点的坐标为(xi,yi)。现请你在空间中找出任意一个点,使得该点到这几个点的距离之和最小。问这最小的距离之和为多少?
输入描述
第一行输入一个正整数n。
接下来几行每行包含两个整数 xi,yi,表示第i个点的坐标。
1 ≤ n ≤ 10^3, 0 ≤ xi,yi<=10^5
输出描述
输出仅一行,包含一个数,表示答案(四舍五入取整)。
#include <bits/stdc++.h>
#include <ctime>
using namespace std;
// 退火系数,控制温度下降的速度,每次温度乘以该系数降低
const double K = 0.99;
// 初始温度,模拟退火算法开始时的温度
const double ST = 1e6;
// 终止温度,当温度降到该值以下时,算法停止迭代
const double ET = 1e-9;
// 每个温度下的迭代次数,在每个温度下进行多次尝试以寻找更优解
const double L = 100;
// 用于存储输入的 n 个点的 x 坐标
int x[1004];
// 用于存储输入的 n 个点的 y 坐标
int y[1004];
// 表示输入的点的数量
int n;
// 目标函数,计算点 (nx, ny) 到所有 n 个点的欧几里得距离之和
double f(double nx, double ny) {
double dis = 0;
// 遍历所有的 n 个点
for (int i = 1; i <= n; i++) {
// 计算当前点 (nx, ny) 到第 i 个点 (x[i], y[i]) 的欧几里得距离
// pow(a, 2) 计算 a 的平方,sqrt 计算平方根
dis += sqrt(pow(nx - x[i], 2) + pow(ny - y[i], 2));
}
return dis;
}
// 模拟退火算法的实现函数
double sa() {
// 初始化温度为初始温度 ST
double T = ST;
// 初始化当前点的 x 坐标为 0
double X = 0;
// 初始化当前点的 y 坐标为 0
double Y = 0;
// 计算初始点 (X, Y) 的目标函数值,即能量 E
double E = f(X, Y);
// 初始化最优解为初始能量 E
double ans = E;
// 当温度 T 大于终止温度 ET 时,继续迭代
while (T > ET) {
// 在每个温度下进行 L 次迭代
for (int i = 1; i <= L; i++) {
// 生成新点的 x 坐标,在当前点 X 的基础上,根据当前温度 T 和随机数进行偏移
// rand() 生成一个 0 到 RAND_MAX 之间的随机整数
// (rand() * 2 - RAND_MAX) 得到一个 -RAND_MAX 到 RAND_MAX 之间的随机数
double nX = X + (rand() * 2 - RAND_MAX) * T;
// 生成新点的 y 坐标,同理
double nY = Y + (rand() * 2 - RAND_MAX) * T;
// 计算新点 (nX, nY) 的目标函数值,即新能量 nE
double nE = f(nX, nY);
// 计算新能量与当前能量的差值
double dE = nE - E;
// 判断是否接受新点
// 如果新能量小于当前能量(dE < 0),则肯定接受新点
// 或者以一定概率接受能量增加的新点,概率由 exp(-dE / T) 决定
// exp 是指数函数,rand() / RAND_MAX 得到一个 0 到 1 之间的随机小数
if (dE < 0 || exp(-dE / T) > rand() / RAND_MAX) {
// 更新当前点的 x 坐标为新点的 x 坐标
X = nX;
// 更新当前点的 y 坐标为新点的 y 坐标
Y = nY;
// 更新当前能量为新能量
E = nE;
// 更新最优解,取当前最优解和新能量中的较小值
ans = min(ans, E);
}
}
// 降低温度,乘以退火系数 K
T = K * T;
}
return ans;
}
int main()
{
// 根据当前时间设置随机数种子,确保每次运行程序时生成的随机数序列不同
srand(time(NULL));
// 读取输入的点的数量 n
cin >> n;
// 循环读取 n 个点的坐标
for (int i = 1; i <= n; i++) {
// 读取第 i 个点的 x 坐标
cin >> x[i];
// 读取第 i 个点的 y 坐标
cin >> y[i];
}
// 调用模拟退火算法函数 sa 求解最小距离和,并输出结果
cout << sa();
return 0;
}