天天看點

淺談模拟退火

前置技能——爬山:https://www.cnblogs.com/AKMer/p/9555215.html

模拟退火是一種非常好的随機化算法,是爬山算法的改進版,它的靈感來源于金屬冶煉退火,和遺傳算法一樣,也是一種大自然饋贈給我們的自适應随機化算法。

有興趣的同學可以去看看遺傳算法:https://www.cnblogs.com/AKMer/p/9479890.html

關于模拟退火,牽扯到實體學的一些知識,有興趣的同學們可以去看百度百科。

百度百科:https://baike.baidu.com/item/%E6%A8%A1%E6%8B%9F%E9%80%80%E7%81%AB/8664695?fr=aladdin

模拟退火基本概念

(T):初始溫度。

(T_0):結束溫度。

(Delta T):溫度變化系數,一般在([0.950,0.999])之間,每次降溫溫度都乘以(Delta T)。

(calc(x)):狀态(x)所對應的權值。在講爬山算法的時候我們就提到過了,模拟退火也将狀态空間裡每一個狀态映射成一個個點,而狀态的優秀度就是一個個點對應的函數值。

溫度一般為double類型,由高溫逐漸降到低溫。

模拟退火與爬山的差別

爬山算法是一種鼠目寸光的貪心,隻會往目前優秀的鄰居狀态轉移。我們隻能通過增加爬山的人數來覆寫盡量多的狀态空間以保證算法效率。

然而模拟退火卻不一樣,對于一個比目前狀态更加優秀的狀态,我們可以毫不猶豫地轉移過去。

但是如果該鄰居狀态不比目前狀态優秀,我們也不會一棒子打死,而是根據實體學上一些知識來計算轉移機率。

然後在資訊學領域内,我們隻要記住一件事情就好了。那就是:

在溫度為(T)時,目前狀态(A)轉移到一個不優于它的狀态(B)的機率是(exp(frac{calc(B)-calc(A)}{T}))

别問我為啥去問實體學家去……總之按這個機率去轉移可以大大增加周遊到代表正解的狀态的幾率。

根據題意我們有時需要将(calc(B)-calc(A))改成(calc(A)-calc(B)),因為模拟退火算法需要滿足一條性質,那就是“在溫度不同的情況下,溫度越低時發生同一個轉移的機率就越低”,也就是當(calc(B)-calc(A))為定值的時候,(T)越小機率也越小,是以分子要保證是負數。

顯然,這個機率是屬于區間([0,1))的。

記得找鄰居節點的時候靈性一點,不要死死闆闆找挨在一起的,不然我前腳往一個差一點的狀态走了後腳就走回來了。還有就是更改狀态可以借鑒遺傳裡的變異。然後其餘的跟爬山就沒啥差別了。

說實話,會了退火之後感覺爬山就沒什麼卵用了……

求函數(f(x))峰值代碼

#include <ctime>
#include <cstdio>
#include <algorithm>
using namespace std;
#define ll long long

const double T_0=1e-5;
const double del_T=0.998;

double T=1e5;

ll f(int x) {
    //傳回函數值
}

int main() {
    srand(time(0));
    int pos=rand()-RAND_MAX/2;ll ans=f(pos);
    while(T>T_0) {
    	int x=pos+T,y=pos-T,nxt;//所謂的靈性找鄰居法
        if(f(x)>f(y))nxt=x;
        else nxt=y;//找下一個點
        if(f(nxt)>f(pos)||exp((f(nxt)-f(pos))/T)*RAND_MAX>rand())
            pos=nxt;//如果下一個點比目前點高或者在一定的機率之内我們就轉移
       	ans=max(ans,f(pos))
        T*=del_T
    }
    printf("%lld
",ans);
    return 0;
}//沒有什麼是退一萬次火解決不了的。如果有,那就再退一萬次。
           

注意:上面這個亂跳的代碼沒有啥正确性可言,隻是意思意思一下而已,不要當真。

關于爬山和模拟退火,網上流傳着這麼兩句比喻:

爬山算法:兔子朝着比現在高的地方跳去。它找到了不遠處的最高山峰。但是這座山不一定是珠穆朗瑪峰。這就是爬山算法,它不能保證局部最優值就是全局最優值。

模拟退火:兔子喝醉了。它随機地跳了很長時間。這期間,它可能走向高處,也可能踏入平地。但是,它漸漸清醒了并朝最高方向跳去。這就是模拟退火。

不知道你在閱讀了我的兩篇部落格之後,有沒有這種感受。如果沒有,那……

肯定是我太菜寫的太渣了。