博客食用更佳bossbaby's blog
模擬退火算法(Simulate Anneal,SA)是一種通用概率演算法,用來在一個大的搜尋空間內找尋命題的最優解。模擬退火是由\(S.Kirkpatrick, C.D.Gelatt\)和\(M.P.Vecchi\)在1983年所發明的。\(V.Cern\)和\(yacute\)在1985年也獨立發明此演算法。模擬退火算法是解決\(TSP\)問題的有效方法之一。
\(TSP\)是啥我們等會再解釋(就是一道例題,給個link:\(TSP\),有興趣的童鞋可以先看着)
模擬退火的出發點是基於物理中固體物質的退火過程與一般組合優化問題之間的相似性。模擬退火算法是一種通用的優化算法,其物理退火過程由加溫過程、等溫過程、冷卻過程這三部分組成。
---引自《百度百科》
關於物理呢,本蒟蒻就不做過多的解釋了.
算法原理就是一個物體,在降溫的過程中,根據熱力學規律並結合計算機對離散數據的處理,在溫度為\(T\)時,出現能量差為\(\Delta E\)的降溫的概率為\(P(\Delta E)\)這個\(P\)函數我們在下一個部分給大家解釋.
算法解析
(現在我們要求這個函數圖像的最小值)
附圖:
要開始寫這個算法,我們就要引入一個叫做\(Metropolis\)接受准則的玩意兒了.
(英語大佬們不要把它當成那個大都會了...)
\(P=1(\Delta E>0)\)
\(P=exp(-\frac{\Delta E}{kT})(\Delta E<0)\)
顯然如果 $ \Delta E$ 為正的話轉移是一定會成功的, 但是對於 \(\Delta E < 0\) 我們則以上式中計算得到的概率接受這個新解.
然后我們維護溫度 \(T\) 即可. 這里我們有三個參數: 初溫 \(T_b\) , 降溫系數 \(D\) , 終溫 \(T_e\)
一般 \(T_b\) 是個比較大的數,取\(1000000\), \(D\) 是個接近 \(1\) 但是小於 \(1\) 的值,一般取\(0.97\), \(T_e\) 是個接近 \(0\) 的正值, 一般取\(1^{-14}\)即\(1e-14\).
首先讓溫度 \(T=T_b\) , 然后進行一次轉移嘗試, 然后讓 \(T*=D\).
當 \(T<T_e\) 時模擬退火過程結束, 當前解作為最優解.
轉移
轉移是整個模擬退火算法的重頭戲.它通過當前溫度進行一定程度的擾動,產生新解.其實擾動也並不復雜,當時學習這種算法時就不懂擾動是什么.它其實就是當前溫度\(T_0\),乘以一個隨機數\(R\)加在原解上得到新解.很多同學可能看不懂.現在我們假設估價函數為\(f(x)\),\(x\)為原解,我們要讓函數值最小.那么新解就是\(x_1=x+T_0*R(R \in [-1,0) \cup (0,1])\)那么\(\Delta E=f(x)-f(x_1)\)此處千萬不要把\(x\)和\(x_1\)記反了,要不然這樣使用\(Metrospolis\)准則就會出錯.我們在一次模擬退火完成后,可以再多來幾次怎么
生成R呢,有些同學可能會有問題,具體我們可以用
(double)(rand()-rand())/RAND_MAX
記得要用
srand(time(NULL))
初始化
例題
現在,你應該已經了解了模擬退火算法了
這里有幾道例題
- \(TSP\)問題
沒錯,就是文章開頭提到的那個\(TSP\)問題
具體請百度
- 平衡點
具體自己可以在洛谷上看
附帶代碼
#include<bits/stdc++.h>
#define LD long double
using namespace std;
/***** 模擬退火控制 *****/
const LD D=0.97,EPS=1e-14;
int times=10;
/***** ============ *****/
int n;
int w[1010],x[1010],y[1010];
LD bx=0,by=0;
LD cur_ans,new_ans,best;
inline LD Rand(){ //產生-1到1閉區間(除去0)的隨機數
return (LD)(rand()-rand())/((LD)RAND_MAX);
}
LD calc(LD cx,LD cy){ //估價函數
LD ret=0;
for(int i=1;i<=n;i++){
LD dx=cx-x[i],dy=cy-y[i];
ret+=sqrt(dx*dx+dy*dy)*w[i];
}
return ret;
}
int main(){
srand(time(NULL));
cin>>n;
for(int i=1;i<=n;i++){
cin>>x[i]>>y[i]>>w[i];
bx+=x[i];by+=y[i];
}
bx/=n;by/=n; //初始解
best=cur_ans=calc(bx,by);
while(times--){ //控制多次退火
cur_ans=best;
LD cx=bx,cy=by;
for(LD T=1000000;T>EPS;T*=D){ //模擬退火
LD nx=cx+T*Rand(),ny=cy+T*Rand();
new_ans=calc(nx,ny);
if(best>new_ans){ //更新最優解
best=new_ans;
bx=nx,by=ny;
}
if(cur_ans>new_ans||exp((cur_ans-new_ans)/T)>(LD)rand()/RAND_MAX){ //更新當前解並轉移
cur_ans=new_ans;
nx=cx,ny=cy;
}
}
}
cout<<fixed<<setprecision(3)<<bx<<" "<<by<<endl;
return 0;
}