转载于:模拟退火
模拟退火解决问题:求多峰 / 多谷函数的最值。
需要满足条件: 函数要有一定的连续性,即函数值不能过于随机。
先用一句话概括: 如果新状态的解更优则修改答案,否则以一定概率接受新状态。
模拟退火时我们有三个参数:初始温度 T 0 T_0 T0 ,降温系数 d d d ,终止温度 T k T_k Tk 。其中 T 0 T_0 T0 是一个比较大的数, d d d 是一个非常接近 1 1 1 但是小于 1 1 1 的数, T k T_k Tk 是一个接近 0 0 0 的正数。
首先让温度 T = T 0 T=T_0 T=T0 ,然后进行一次转移尝试,再让 T = d ⋅ T T=d\cdot T T=d⋅T 。当 T < T k T<T_k T<Tk 时模拟退火过程结束,当前最优解即为最终的最优解。
注意为了使得解更为精确,我们通常不直接取当前解作为答案,而是在退火过程中维护遇到的所有解的最优值。
转移尝试:
我们定义当前温度为 T T T ,新状态与已知状态(由已知状态通过随机的方式得到)之间的能量(值)差为 Δ E ( Δ E ⩾ 0 ) \Delta E(\Delta E\geqslant 0) ΔE(ΔE⩾0) ,则发生状态转移(修改最优解)的概率为
P ( Δ E ) = { 1 新状态更优 e − Δ E T 新状态更劣 P(\Delta E)= \begin{cases} 1&\text{新状态更优}\\ e^\frac{-\Delta E}{T}&\text{新状态更劣} \end{cases} P(ΔE)={1eT−ΔE新状态更优新状态更劣
卡时
有一个 clock()
函数,返回程序运行时间。
可以把主程序中的 simulateAnneal(); 换成 while ((double)clock()/CLOCKS_PER_SEC < MAX_TIME) simulateAnneal();
。这样子就会一直跑模拟退火,直到用时即将超过时间限制。
这里的 MAX_TIME
是一个自定义的略小于时限的数(单位:秒)。
问题:求二元函数的极小值。
核心代码:
double ans = 1e18;
double rand(double l, double r) {
return double(rand()) / RAND_MAX * (r - l) + l;
}
double f(PDD p) { /* */ ; return /* */; }
void saa() {
PDD p = {rand(0, 1e4), rand(0, 1e4)}; // 在值域范围随机一个初始点
for(double t = 1e4; t > 1e-4; t *= 0.99) { // 迭代步长,t 初始值一般为值域宽度
PDD np = {rand(p.fi - t, p.fi + t), rand(p.se - t, p.se + t)};
double fnp = f(np), fp = f(p);
ans = min({ans, fnp, fp});
if(exp(- (fnp - fp) / t) > double(rand()) / RAND_MAX) p = np;
}
}
int main()
{
while(double(clock()) / CLOCKS_PER_SEC < 0.9)
saa();
cout << fixed << setprecision(0) << ans;
}