模擬退火是什么?
模擬退火來自冶金學的專有名詞退火。退火是將材料加熱后再經特定速率冷卻,目的是增大晶粒的體積,並且減少晶格中的缺陷。材料中的原子原來會停留在使內能有局部最小值的位置,加熱使能量變大,原子會離開原來位置,而隨機在其他位置中移動。退火冷卻時速度較慢,使得原子有較多可能可以找到內能比原先更低的位置。
模擬退火的原理也和金屬退火的原理近似:我們將熱力學的理論套用到統計學上,將搜尋空間內每一點想像成空氣內的分子;分子的能量,就是它本身的動能;而搜尋空間內的每一點,也像空氣分子一樣帶有“能量”,以表示該點對命題的合適程度。算法先以搜尋空間內一個任意點作起始:每一步先選擇一個“鄰居”,然后再計算從現有位置到達“鄰居”的概率。
可以證明,模擬退火算法所得解依概率收斂到全局最優解。
演算步驟
- 初始化
生成一個可行的解作為當前解輸入迭代過程,並定義一個足夠大的數值作為初始溫度
- 迭代
迭代過程是模擬退火算法的核心步驟,分為新解的產生和接受新解兩部分
- 由一個產生函數從當前解產生一個位於解空間的新解;為便於后續的計算和接受,減少算法耗時,通常選擇由當前新解經過簡單地變換即可產生新解的方法,如對構成新解的全部或部分元素進行置換、互換等,注意到產生新解的變換方法決定了當前新解的鄰域結構,因而對冷卻進度表的選取有一定的影響。
- 計算與新解所對應的目標函數差。因為目標函數差僅由變換部分產生,所以目標函數差的計算最好按增量計算。事實表明,對大多數應用而言,這是計算目標函數差的最快方法。
- 判斷新解是否被接受,判斷的依據是一個接受准則,最常用的接受准則是Metropolis准則:若Δt′<0則接受S′作為新的當前解S,否則以概率exp(-Δt′/T)接受S′作為新的當前解S。
4.當新解被確定接受時,用新解代替當前解,這只需將當前解中對應於產生新解時的變換部分予以實現,同時修正目標函數值即可。此時,當前解實現了一次迭代。可在此基礎上開始下一輪試驗。而當新解被判定為舍棄時,則在原當前解的基礎上繼續下一輪試驗。
注:模擬退火算法與初始值無關,算法求得的解與初始解狀態S(是算法迭代的起點)無關;模擬退火算法具有漸近收斂性,已在理論上被證明是一種以概率1收斂於全局最優解的全局優化算法;模擬退火算法具有並行性。
- 停止准則
迭代過程的停止准則:溫度T降至某最低值時,完成給定數量迭代中無法接受新解,停止迭代,接受當前尋找的最優解為最終解。
- 退火方案
在某個溫度狀態T下,當一定數量的迭代操作完成后,降低溫度T,在新的溫度狀態下執行下一個批次的迭代操作。
- 上述內容摘自wiki百科
整個流程圖如下:
看不懂上面說的,怎么辦?
沒事,第一次看的時候我也看不懂,因此下面我用我的理解來講述這個歐皇(划去)算法
和之前一樣,我們通過一道題目來理解
\[[JSOI2004]平衡點 \]有\(n\)個重物,每個重物系在一條足夠長的繩子上。每條繩子自上而下穿過桌面上的洞,然后系在一起。X處就是公共的繩結。假設繩子是完全彈性的(不會造成能量損失),桌子足夠高(因而重物不會垂到地上),且忽略所有的摩擦。問繩結X最終平衡於何處。
注意:桌面上的洞都比繩結X小得多,所以即使某個重物特別重,繩結X也不可能穿過桌面上的洞掉下來,最多是卡在某個洞口處。
先講一下正解的思路:
在平面上確定一個原點,將所有的力在這個平面上正交分解,從而求得所有力的合力的方向。
然后使用一個半平面切割初始的凸包,形成一個新的凸包,這樣操作后凸包的面積會變小,進行多次二分,直到凸包的面積達到精度以內(即小到可以看做一個點),這時任選凸包的一個頂點就是答案。
若二分次數為\(k\),算法的時間復雜度為\(O(kn)\)
至此正解結束
但這個正解很low,顯示不出我們OIer真正的強大(暴力)之處,因此,我們選擇模擬退火來AC這道題!
代碼實現
很明顯,我們先開一個結構體儲存物體信息
struct Node
{
int x,y,w;
}object[2005];
//儲存物體的坐標和重量
然后我們需要一個函數來計算新的系統的能量,因為根據物理學知識,一個系統的能量之和越小則越穩定
double energy(double x,double y)//計算系統新的能量
{
double r=0,dx,dy;
for(register int i=1;i<=n;++i)
{
dx=x-object[i].x;
dy=y-object[i].y;
r+=sqrt(dx*dx+dy*dy)*object[i].w;
}
return r;
}//根據物理學知識,系統能量之和越小則越穩定
接着就是多次迭代退火,逼近最優值
void sa()//simulated annealing,模擬退火
{
double t=3000;//將初始溫度設為3000°C
while(t>eps)//控制精度
{
double newx=ansx+(rand()*2-RAND_MAX)*t;
double newy=ansy+(rand()*2-RAND_MAX)*t;
double neww=energy(newx,newy);
double delta=neww-answ;
if(delta<0)//新的系統能量更小(更穩定)
{
ansx=newx;
ansy=newy;
answ=neww;
}
else if(exp(-delta/t)*RAND_MAX>rand())//概率接受
{
ansx=newx;
ansy=newy;
}
t*=down;//操作完畢后降溫
}
}
然后在主函數中多次退火就行了。
交代碼前需要注意最重要的一步——洗臉!(笑)
模擬退火是玄學算法,因此需要多交幾次才能過
至於某臉黑的tqr……
附上AC代碼(調了很久才調出來的降溫參數)
#include<bits/stdc++.h>
#define eps 1e-15
using namespace std;
const double down=0.994;
//降溫系數,緩慢降溫,關於降溫參數的選取……這是個玄學問題
int n;
double ansx,ansy,answ;
//最終的答案
struct Node
{
int x,y,w;
}object[2005];
//儲存物體的坐標和重量
double energy(double x,double y)//計算系統新的能量
{
double r=0,dx,dy;
for(register int i=1;i<=n;++i)
{
dx=x-object[i].x;
dy=y-object[i].y;
r+=sqrt(dx*dx+dy*dy)*object[i].w;
}
return r;
}//根據物理學知識,系統能量之和越小則越穩定
void sa()//simulated annealing,模擬退火
{
double t=3000;//將初始溫度設為3000°C
while(t>eps)//控制精度
{
double newx=ansx+(rand()*2-RAND_MAX)*t;
double newy=ansy+(rand()*2-RAND_MAX)*t;
double neww=energy(newx,newy);
double delta=neww-answ;
if(delta<0)//新的系統能量更小(更穩定)
{
ansx=newx;
ansy=newy;
answ=neww;
}
else if(exp(-delta/t)*RAND_MAX>rand())//概率接受
{
ansx=newx;
ansy=newy;
}
t*=down;//操作完畢后降溫
}
}
template<class T>inline void read(T &res)
{
T flag=1;char c;
while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;res=c-'0';
while((c=getchar())>='0'&&c<='9')res=(res<<1)+(res<<3)+c-'0';res*=flag;
}
int main()
{
read(n);
for(register int i=1;i<=n;++i)
{
read(object[i].x);ansx+=object[i].x;
read(object[i].y);ansy+=object[i].y;
read(object[i].w);
}
ansx/=n;ansy/=n;
answ=energy(ansx,ansy);
sa();sa();sa();sa();sa();sa();
printf("%.3lf %.3lf",ansx,ansy);
return 0;
}
UPD:我發現0.994的參數,退八次火AC率很高,但不知道為什么,dalao求解qwq